Linux內核跟蹤之trace框架分析

------------------------------------------
本文系本站原創,歡迎轉載!
轉載請註明出處:http://ericxiao.cublog.cn/
------------------------------------------

一: 前言

本文主要是對trace的框架做詳盡的分析, 在後續的分析中,再來分析接入到框架中的幾個重要的tracer. 在下面的分析中,會涉及到ring buffer的操作,如果對這部份不熟悉的,請先參閱本站有關ring buffer分析的文章. 同以往的分析一樣,本文不會在trace的使用上花費較多的筆墨,而着重分析kernel中源代碼實現, 有關這部份的使用,請參閱kernel自帶的文檔: linux-2.6-tip/Documentation/trace/ftrace.txt, 分析的源代碼版本爲: v2.6.30-rc8,分析的代碼基本上位於kernel/trace/trace.c中.
Trace框架的初始化代碼如下所示:
early_initcall(tracer_alloc_buffers);
fs_initcall(tracer_init_debugfs);
late_initcall(clear_boot_tracer);
由此可見, 它的初始化主要由三個函數完成,且調用的順序是tracer_alloc_buffers() à tracer_init_debugfs() à clear_boot_tracer().下面依次分析這幾個函數.

二 tracer_alloc_buffers()分析

從這個函數的名稱就可以看出來, 它是爲trace分配buffer,這個函數較長,且涉及到很多細節,採用分段分析的方式,代碼如下示:
__init static int tracer_alloc_buffers(void)
{
     struct trace_array_cpu *data;
     int ring_buf_size;
     int i;
     int ret = -ENOMEM;
 
     if (!alloc_cpumask_var(&tracing_buffer_mask, GFP_KERNEL))
         goto out;
 
     if (!alloc_cpumask_var(&tracing_cpumask, GFP_KERNEL))
         goto out_free_buffer_mask;
 
     if (!alloc_cpumask_var(&tracing_reader_cpumask, GFP_KERNEL))
         goto out_free_tracing_cpumask;
 
     /* To save memory, keep the ring buffer size to its minimum */
     if (ring_buffer_expanded)
         ring_buf_size = trace_buf_size;
     else
         ring_buf_size = 1;
 
     cpumask_copy(tracing_buffer_mask, cpu_possible_mask);
     cpumask_copy(tracing_cpumask, cpu_all_mask);
     cpumask_clear(tracing_reader_cpumask);
 
從上面的代碼可以看出,它爲三個cpu位圖分配了空間併爲其執行了初始化過程, 另外對使用ring buffer的大小也做了確定.
首先我們來看一下ring buffer的大小的確定,基本的原則是使用最小的緩存區,在代碼中,如果ring_buffer_expanded爲0,ring buffer的大小會初始化爲1,那ring_buffer_expanded什麼時候會爲1呢?.
ring_buffer_expanded定義如下:
static int ring_buffer_expanded;
它的初始值爲0,但在代碼中需要判斷它的值,肯定是在內核啓動時對它進行了更改,搜索代碼,會發現:
static int __init set_ftrace(char *str)
{
     strncpy(bootup_tracer_buf, str, BOOTUP_TRACER_SIZE);
     default_bootup_tracer = bootup_tracer_buf;
     /* We are using ftrace early, expand it */
     ring_buffer_expanded = 1;
     return 1;
}
__setup("ftrace=", set_ftrace);
Ftrace參數用來指定內核啓動時的使用的tracer, 具體tracer會我們在後面的章節中進行分析,這裏只需要知道tracer相當於cgroup中的subsystem.
由此看到, 會將指定的tracer名稱保存在bootp_tracer_buf中, 並且將ring_buffer_expanded設置爲了1, 在此我們需要注意bootp_tracer_buf定義爲:
static char bootup_tracer_buf[BOOTUP_TRACER_SIZE] __initdata;
它是一個init段的數據結構,在kernel初始化完全之後,它所佔的空間會被會釋放掉,也就是說,啓動後,這個緩存區是不能再被使用的.
 
綜上分析, 如果指定了boot時的tracer, 因爲tracer馬上就會使用ring buffer,所在ring buffer的大小就能採用最小值了,需要將其擴大到trace_buf_size,定義如下:
 
#define TRACE_BUF_SIZE_DEFAULT   1441792UL /* 16384 * 88 (sizeof(entry)) */
static unsigned long        trace_buf_size = TRACE_BUF_SIZE_DEFAULT;
它默認是1441792, 不過這個初始化大小也是能調整的,它對應的啓動參數是”trace_buf_size=”,這個啓動參數可自行參照代碼進行分析.
 
接下來,我們來看一下那三個cpu位圖代表的是什麼意思.先來看它們的定義:
tracing_buffer_mask: 表示系統中所有的CPU
tracing_cpumask: 表示trace的cpu,只有在位圖中的CPU才能允許被trace
tracing_reader_cpumask: 用來記錄當前那一個CPU的ring buffer被pipe讀,阻止cpu上並行pipe操作。
關於這幾個參數我們在以後還會遇到,等遇到的時候纔來詳細分析
 
     /* TODO: make the number of buffers hot pluggable with CPUS */
     global_trace.buffer = ring_buffer_alloc(ring_buf_size,
                               TRACE_BUFFER_FLAGS);
     if (!global_trace.buffer) {
         printk(KERN_ERR "tracer: failed to allocate ring buffer!\n");
         WARN_ON(1);
         goto out_free_cpumask;
     }
     global_trace.entries = ring_buffer_size(global_trace.buffer);
 
 
#ifdef CONFIG_TRACER_MAX_TRACE
     max_tr.buffer = ring_buffer_alloc(ring_buf_size,
                            TRACE_BUFFER_FLAGS);
     if (!max_tr.buffer) {
         printk(KERN_ERR "tracer: failed to allocate max ring buffer!\n");
         WARN_ON(1);
         ring_buffer_free(global_trace.buffer);
         goto out_free_cpumask;
     }
     max_tr.entries = ring_buffer_size(max_tr.buffer);
     WARN_ON(max_tr.entries != global_trace.entries);
#endif
 
     /* Allocate the first page for all buffers */
     for_each_tracing_cpu(i) {
         data = global_trace.data[i] = &per_cpu(global_trace_cpu, i);
         max_tr.data[i] = &per_cpu(max_data, i);
     }
 
上面的幾段代碼用來初始化global_trace和max_tr,ring buffer的分配過程我們在上一篇文章中已經分析過了,這裏不再贅述. 我們有必要來弄懂一下,gloabal_trace和max_tr是用來幹什麼的.
Gloabal_trace很好理解,它就是tracer用來存放他們的信息.
Max_tr是跟CONFIG_TRACER_MAX_TRACE相關聯的,在Kconfig沒有找到它的相關部份,我們來看一下它的註釋:
/*
 * The max_tr is used to snapshot the global_trace when a maximum
 * latency is reached. Some tracers will use this to store a maximum
 * trace while it continues examining live traces.
 *
 * The buffers for the max_tr are set up the same as the global_trace.
 * When a snapshot is taken, the link list of the max_tr is swapped
 * with the link list of the global_trace and the buffers are reset for
 * the global_trace so the tracing can continue.
 */
根據上面的說明,max_tr是在計算延遲時等相關處理時用來做global_trace的快照。這樣說可能不是很理解,舉個例子,如果想要trace 系統的中斷關閉時間,中斷關閉是允許的,但是關閉時間過長就會影響系統性能,所以只有在關閉時間超過某一個值的時候纔會將它的相關信息記錄下來,所以將這些信息記錄在max_tr中(global_trace中此時已經存放了trace到的所有信息). 具體的操作等以後分析相關tracer的時候,遇到了再來分析.
另外,global_trace和max_tr的data數組都指向了Per_cpu變量的相關結構,裏面有一個成員是disabled,它表示控制對應cpu的tracer操作,如果該值大於1,表示該CPU的tracer操作是禁止的,這也可以理解上,CPU上一次只能有一個tracer在操作.接着往下面看
 
     trace_init_cmdlines();
 
     register_tracer(&nop_trace);
     current_trace = &nop_trace;
#ifdef CONFIG_BOOT_TRACER
     register_tracer(&boot_tracer);
#endif
     /* All seems OK, enable tracing */
     tracing_disabled = 0;
 
這段代碼中,初始化了cmdlines,然後註冊了一個空的tracer, 也就是nop_tracer, 如果指定了boot時候的tracer(這個過程在上面已經分析過了),將這個tacer註冊。
首先來看一下cmdlines,它是進程的名稱,也就是用ps看到的進程名字。它的初始化過程我們暫不分析,等後面聯合cmdlines的操作再來一起分析.
Tracint_disables是一個很重要的標誌,它的初始化是爲1,如果初始化成功,將其置爲0,表示整個trace系統已經可用了,否則,如果中途發生了錯誤,此值設爲1,整個trace會禁用.
 
     atomic_notifier_chain_register(&panic_notifier_list,
                          &trace_panic_notifier);
 
     register_die_notifier(&trace_die_notifier);
 
註冊了兩個notifier,用來在內核發生panic或者是die的時候,將trace中的消息打印出來。
 
     return 0;
 
out_free_cpumask:
     free_cpumask_var(tracing_reader_cpumask);
out_free_tracing_cpumask:
     free_cpumask_var(tracing_cpumask);
out_free_buffer_mask:
     free_cpumask_var(tracing_buffer_mask);
out:
     return ret;
}
 
在代碼中,我們遇到了tracer的註冊,在這裏就來分析tracer的註冊過程

2.1: tracer的註冊操作

int register_tracer(struct tracer *type)
__releases(kernel_lock)
__acquires(kernel_lock)
{
     struct tracer *t;
     int len;
     int ret = 0;
 
     if (!type->name) {
         pr_info("Tracer must have a name\n");
         return -1;
     }
 
     /*
      * When this gets called we hold the BKL which means that
      * preemption is disabled. Various trace selftests however
      * need to disable and enable preemption for successful tests.
      * So we drop the BKL here and grab it after the tests again.
      */
     unlock_kernel();
     mutex_lock(&trace_types_lock);
 
     tracing_selftest_running = true;
 
     for (t = trace_types; t; t = t->next) {
         if (strcmp(type->name, t->name) == 0) {
              /* already found */
              pr_info("Trace %s already registered\n",
                   type->name);
              ret = -1;
              goto out;
         }
     }
 
     if (!type->set_flag)
         type->set_flag = &dummy_set_flag;
     if (!type->flags)
         type->flags = &dummy_tracer_flags;
     else
         if (!type->flags->opts)
              type->flags->opts = dummy_tracer_opt;
     if (!type->wait_pipe)
         type->wait_pipe = default_wait_pipe;
 
 
#ifdef CONFIG_FTRACE_STARTUP_TEST
     if (type->selftest && !tracing_selftest_disabled) {
         struct tracer *saved_tracer = current_trace;
         struct trace_array *tr = &global_trace;
         int i;
 
         /*
          * Run a selftest on this tracer.
          * Here we reset the trace buffer, and set the current
          * tracer to be this tracer. The tracer can then run some
          * internal tracing to verify that everything is in order.
          * If we fail, we do not register this tracer.
          */
         for_each_tracing_cpu(i)
              tracing_reset(tr, i);
 
         current_trace = type;
         /* the test is responsible for initializing and enabling */
         pr_info("Testing tracer %s: ", type->name);
         ret = type->selftest(type, tr);
         /* the test is responsible for resetting too */
         current_trace = saved_tracer;
         if (ret) {
              printk(KERN_CONT "FAILED!\n");
              goto out;
         }
         /* Only reset on passing, to avoid touching corrupted buffers */
         for_each_tracing_cpu(i)
              tracing_reset(tr, i);
 
         printk(KERN_CONT "PASSED\n");
     }
#endif
 
     type->next = trace_types;
     trace_types = type;
     len = strlen(type->name);
     if (len > max_tracer_type_len)
         max_tracer_type_len = len;
 
首先,不允許tracer的名稱,即tracer->name爲空.
在修改和訪問tracer鏈表的時候都必須持有trace_types_lock, 然後遍歷trace_types鏈表,判斷當前鏈表中是否有同名的tracer,如果有,釋放鎖之後退出
然後對tracer的成員進行必要的補充,包括flag 和waite_pipe,它們究競是幹什麼的,在這裏將它放一放,等遇到的時候再分析
接下來,如果這個tracer定義了selftest操作,且自檢沒有被禁用的話,就會進行tracer的自檢,在代碼中,我們會看到,它先將global_trace的ring buffer清空,然後將該tracer設爲當前的tracer(current_trace = type;),然後調用selftest接後,接着將current_tracer回覆原值,再將ring buffer中的數據清空.
如果自檢成功,將tracer加入到trace_types鏈表。最後全局量max_tracer_type_len保存最大的tracer名稱長度,以便於在用戶空間讀取全部tracer名稱的時候,可以分配合適的緩存衝。
 
 out:
     tracing_selftest_running = false;
     mutex_unlock(&trace_types_lock);
 
     if (ret || !default_bootup_tracer)
         goto out_unlock;
 
     if (strncmp(default_bootup_tracer, type->name, BOOTUP_TRACER_SIZE))
         goto out_unlock;
 
     printk(KERN_INFO "Starting tracer '%s'\n", type->name);
     /* Do we want this tracer to start on bootup? */
     tracing_set_tracer(type->name);
     default_bootup_tracer = NULL;
     /* disable other selftests, since this will break it. */
     tracing_selftest_disabled = 1;
#ifdef CONFIG_FTRACE_STARTUP_TEST
     printk(KERN_INFO "Disabling FTRACE selftests due to running tracer '%s'\n",
            type->name);
#endif
 
 out_unlock:
     lock_kernel();
     return ret;
}
這部份代碼是對boot時設置的tracer的“安裝”部份,從代碼中也看出來了,這個過程是在tracing_set_tracer()中完成的。
另外,我們需要注意到,在selftest開始前,tracing_selftest_running被設置爲了true,測試完了,會將其設爲false,如果有指定boot時的tracer,會將tracing_selftest_disabled置爲1,也就是說禁用掉tracer自檢,以免自檢的時候清空了ring buffer,丟失數據.
 

2.2: tracer的”安裝”操作

從上面的代碼中可以看到,這個操作的相關接口是tracing_set_tracer(),代碼如下:
static int tracing_set_tracer(const char *buf)
{
     static struct trace_option_dentry *topts;
     struct trace_array *tr = &global_trace;
     struct tracer *t;
     int ret = 0;
 
     mutex_lock(&trace_types_lock);
 
     if (!ring_buffer_expanded) {
         ret = tracing_resize_ring_buffer(trace_buf_size);
         if (ret < 0)
              goto out;
         ret = 0;
     }
 
如果ring buffer還是默認大小(1 byte),需要將其調整了,因爲現在有了tracer,就會往裏面寫數據,以前的大小是肯定滿足不了的。
 
     for (t = trace_types; t; t = t->next) {
         if (strcmp(t->name, buf) == 0)
              break;
     }
     if (!t) {
         ret = -EINVAL;
         goto out;
     }
     if (t == current_trace)
         goto out;
 
從trace_types鏈表中找到名稱對應的tracer,如果找不到,或者當前的tracer就是指定的tracer,可以清理一下退出了
 
     trace_branch_disable();
     if (current_trace && current_trace->reset)
         current_trace->reset(tr);
 
     destroy_trace_option_files(topts);
 
     current_trace = t;
 
     topts = create_trace_option_files(current_trace);
 
     if (t->init) {
         ret = tracer_init(t, tr);
         if (ret)
              goto out;
     }
 
     trace_branch_enable(tr);
 out:
     mutex_unlock(&trace_types_lock);
 
     return ret;
}
這段代碼涉及到branch tracer的東西,在分析branch tracer的時候再來詳細的分析。我們在代碼中看到,在安裝tracer之前,會先調用reset將其重置,然後銷燬它的option文件,然後將它賦值給全局量current_trace,再創建option文件,再後由tracer_init()清空ring buffer中的內容,然後再調用init()
 

2.3: tracer 的option文件

下面來分析tracer option文件是怎麼組織的.我們先來看一下它在tracer中的結構,如下示:
struct tracer {
     ......
     ......
     int           (*set_flag)(u32 old_flags, u32 bit, int set);
     struct tracer_flags    *flags;
     ......
}
所謂option,就是在tracer中flag中的一個位圖.
與option相關的主要有兩個部份,一個是set_flag操作,它是用來設置flag中的相關標誌,原型如下:
     int           (*set_flag)(u32 old_flags, u32 bit, int set);
即原來的flag值是old_flags,現在要設置的位圖是bit,要將其設置爲set (或是1或是0)
 
另外一個是struct tracer_flags,定義如下:
struct tracer_flags {
     u32           val;
     struct tracer_opt  *opts;
};
Val用來存放tracer的flag, struct tracer_opt用來存放各位標誌位選項,如下:
struct tracer_opt {
     const char    *name; /* Will appear on the trace_options file */
     u32      bit; /* Mask assigned in val field in tracer_flags */
};
Name是這個標誌位的名稱,也就是debugfs中tracer option文件的名字, bit是它所佔的位圖.
分析完它的含義之後,我們可以返回register_tracer()來看一下它的option部份的附加補充了:
     if (!type->set_flag)
         type->set_flag = &dummy_set_flag;
     if (!type->flags)
         type->flags = &dummy_tracer_flags;
     else
         if (!type->flags->opts)
              type->flags->opts = dummy_tracer_opt;
 
如果tracer的set_flag接口爲空,則將其指定爲dummy_set_flag(),這個函數其實什麼都不做,就直接返回0,
如果沒有指定flags,則將其初始化爲dummy_tracer­_flags,如果沒有指定每個flags的具體位含義,則將其指定爲dummy_tracer_opt.
有興趣的可以跟蹤看一下,其實這些默認指定的全是空的。也就是說不會帶標誌
 
在代碼中,option文件的相關信息都是保存在struct trace_option_dentry中的,定義如下:
struct trace_option_dentry {
     struct tracer_opt      *opt;
     struct tracer_flags         *flags;
     struct dentry          *entry;
};
Opt是該文件對應的選項,flags是對應tracer的flags, entry是這個option所對應的dentry結構。
現在可以來看一下option文件的建立與銷燬了, 先來看建立過程:
static struct trace_option_dentry *
create_trace_option_files(struct tracer *tracer)
{
     struct trace_option_dentry *topts;
     struct tracer_flags *flags;
     struct tracer_opt *opts;
     int cnt;
 
     if (!tracer)
         return NULL;
 
     flags = tracer->flags;
 
     if (!flags || !flags->opts)
         return NULL;
 
參數有效性檢查
 
     opts = flags->opts;
 
     for (cnt = 0; opts[cnt].name; cnt++)
         ;
 
     topts = kcalloc(cnt + 1, sizeof(*topts), GFP_KERNEL);
     if (!topts)
         return NULL;
 
統計tracer中總共有幾個option,然後對應就會有多少個trace_option_dentry,爲其分配對應的空間
 
     for (cnt = 0; opts[cnt].name; cnt++)
         create_trace_option_file(&topts[cnt], flags,
                        &opts[cnt]);
 
     return topts;
}
 
調用create_trace_option_file()爲這幾個option創建對應的文件,該接口如下:
static void
create_trace_option_file(struct trace_option_dentry *topt,
               struct tracer_flags *flags,
               struct tracer_opt *opt)
{
     struct dentry *t_options;
 
     t_options = trace_options_init_dentry();
     if (!t_options)
         return;
 
     topt->flags = flags;
     topt->opt = opt;
 
     topt->entry = trace_create_file(opt->name, 0644, t_options, topt,
                       &trace_options_fops);
 
}
它先通過trace_options_init_dentry檢查debugfs/traceing/option目錄是否建立,如果沒有,則新建之。
然後在debugfs/traceing/option目錄下創建對應的option文件,文件名爲opt->name,操作文件集爲trace_options_fops,如下示:
static const struct file_operations trace_options_fops = {
     .open = tracing_open_generic,
     .read = trace_options_read,
     .write = trace_options_write,
};
 
Open操作爲:
int tracing_open_generic(struct inode *inode, struct file *filp)
{
     if (tracing_disabled)
         return -ENODEV;
 
     filp->private_data = inode->i_private;
     return 0;
}
從此可以看出,如果traing_disable爲1,那麼它會禁止所有ftrace的操作,這發生在初始化失敗,或者是中途操作失敗。
然後私有區結構也就是創建文件時的topt.
 
Read操作爲:
static ssize_t
trace_options_read(struct file *filp, char __user *ubuf, size_t cnt,
              loff_t *ppos)
{
     struct trace_option_dentry *topt = filp->private_data;
     char *buf;
 
     if (topt->flags->val & topt->opt->bit)
         buf = "1\n";
     else
         buf = "0\n";
 
     return simple_read_from_buffer(ubuf, cnt, ppos, buf, 2);
}
如果flags中的對應標誌被置位的話,返回1,否則返回0。
 
Write操作爲:
static ssize_t
trace_options_write(struct file *filp, const char __user *ubuf, size_t cnt,
               loff_t *ppos)
{
     struct trace_option_dentry *topt = filp->private_data;
     unsigned long val;
     char buf[64];
     int ret;
 
     if (cnt >= sizeof(buf))
         return -EINVAL;
 
     if (copy_from_user(&buf, ubuf, cnt))
         return -EFAULT;
 
     buf[cnt] = 0;
 
     ret = strict_strtoul(buf, 10, &val);
     if (ret < 0)
         return ret;
 
     ret = 0;
     switch (val) {
     case 0:
         /* do nothing if already cleared */
         if (!(topt->flags->val & topt->opt->bit))
              break;
 
         mutex_lock(&trace_types_lock);
         if (current_trace->set_flag)
              ret = current_trace->set_flag(topt->flags->val,
                                  topt->opt->bit, 0);
         mutex_unlock(&trace_types_lock);
         if (ret)
              return ret;
         topt->flags->val &= ~topt->opt->bit;
         break;
     case 1:
         /* do nothing if already set */
         if (topt->flags->val & topt->opt->bit)
              break;
 
         mutex_lock(&trace_types_lock);
         if (current_trace->set_flag)
              ret = current_trace->set_flag(topt->flags->val,
                                  topt->opt->bit, 1);
         mutex_unlock(&trace_types_lock);
         if (ret)
              return ret;
         topt->flags->val |= topt->opt->bit;
         break;
 
     default:
         return -EINVAL;
     }
 
     *ppos += cnt;
 
     return cnt;
}
這段代碼雖然長,但是很簡單,它就是調用tracer的set_flag()操作,如果返回0,則將標誌設置成相應值,否則,出錯退出.
 
Option文件的刪除很簡單,它就是刪除對應的dentry就可以了,相應的代碼請自行查看destroy_trace_option_files(),這裏就不詳細分析了.
到這裏,trace的第一個初始化函數tracer_alloc_buffers()就分析完了,我們接下來看第二個。
 

三: tracer_init_debugfs()的分析

tracer_init_debugfs()像它的名字一樣,它就是在debugfs中創建了很多的文件,由於篇幅關係,就不再一一分析所有文件的操作,只選幾個較複雜的進行分析
 

3.1 trace文件分析

Trace文件的創建過程如下:
debugfs/tracing/trace:
     trace_create_file("trace", 0644, d_tracer,
              (void *) TRACE_PIPE_ALL_CPU, &tracing_fops);
另外,在debugfs/tracing/cpu_nr(CPU序號)/trace位置也有此文件:
     trace_create_file("trace", 0644, d_cpu,
              (void *) cpu, &tracing_fops);
兩個文件的唯一區別是,它們的私有數據,前者是TRACE_PIPE_ALL_CPU,後者是該目錄對應的CPU.
它們的操作集都是一樣的,即都爲traint_fops, 定義如下:
static const struct file_operations tracing_fops = {
     .open         = tracing_open,
     .read         = seq_read,
     .write        = tracing_write_stub,
     .llseek       = seq_lseek,
     .release = tracing_release,
};
 
先來看open操作:
static int tracing_open(struct inode *inode, struct file *file)
{
     struct trace_iterator *iter;
     int ret = 0;
 
     /* If this file was open for write, then erase contents */
     if ((file->f_mode & FMODE_WRITE) &&
         !(file->f_flags & O_APPEND)) {
         long cpu = (long) inode->i_private;
 
         if (cpu == TRACE_PIPE_ALL_CPU)
              tracing_reset_online_cpus(&global_trace);
         else
              tracing_reset(&global_trace, cpu);
     }
 
     if (file->f_mode & FMODE_READ) {
         iter = __tracing_open(inode, file);
         if (IS_ERR(iter))
              ret = PTR_ERR(iter);
         else if (trace_flags & TRACE_ITER_LATENCY_FMT)
              iter->iter_flags |= TRACE_FILE_LAT_FMT;
     }
     return ret;
}
 
如果該文件是寫操作且不是追加操作, 就會調用tracing_reset_online_cpus()或者tracing_reset()清除所有CPU或者是對應CPU上的ring buffer.
例如,我們可以在用戶空間中進行如下操作來清除debugfs/tracing/trace或者debugfs/traceing/cpuN/trace文件中的內容:
echo “” > debugfs/tracing/trace
echo “” > debugfs/traceing/cpuN/trace
 
如果是讀操作,流程就會轉入__tracing_open(),在這裏我們注意一下TRACE_ITER_LATENCY_FMT標誌的含義,它是用來跟蹤狀態時差的, 比如,從禁用中斷到啓用中斷中間的時差.
如果全局trace_flags帶有這個標誌, 相應的要將iter的TRACE_FILE_LAT_FMT標誌置位.
 
__traing_open()比較繁長,採用分段分析的方式,如下:
static struct trace_iterator *
__tracing_open(struct inode *inode, struct file *file)
{
     long cpu_file = (long) inode->i_private;
     void *fail_ret = ERR_PTR(-ENOMEM);
     struct trace_iterator *iter;
     struct seq_file *m;
     int cpu, ret;
 
     if (tracing_disabled)
         return ERR_PTR(-ENODEV);
 
如果trace被禁用了,返回錯誤,這個是在每一個trace文件之前必須要檢查的, 因爲可能在運行的過程中trace發生了錯誤,此時就會禁用整個trace子系統
 
     iter = kzalloc(sizeof(*iter), GFP_KERNEL);
     if (!iter)
         return ERR_PTR(-ENOMEM);
 
     /*
      * We make a copy of the current tracer to avoid concurrent
      * changes on it while we are reading.
      */
     mutex_lock(&trace_types_lock);
     iter->trace = kzalloc(sizeof(*iter->trace), GFP_KERNEL);
     if (!iter->trace)
         goto fail;
 
     if (current_trace)
         *iter->trace = *current_trace;
 
     if (!alloc_cpumask_var(&iter->started, GFP_KERNEL))
         goto fail;
 
     cpumask_clear(iter->started);
 
     if (current_trace && current_trace->print_max)
         iter->tr = &max_tr;
     else
         iter->tr = &global_trace;
     iter->pos = -1;
     mutex_init(&iter->mutex);
     iter->cpu_file = cpu_file;
 
分配並初始化一個讀操作的迭代器,iter-> trace指向當前使用的trace, 注意在取current_trace的時候必須要持有trace_types_lock,這樣是爲了避免set_tracer操作的競爭.
另外要注意的是,iter->trace分配了一個緩存區,並用來存放具體的trace拷貝,(爲什麼需要存放一個拷貝,而不是指針?)
接下來分配並初始化了iter->started, 這是在test_cpu_buff_start()中被使用的. 簡單的來說,在第一次顯示這個cpu上的信息的時候,就會將CPU在這個位圖中置位,並顯示出
##### CPU n buffer started ####
當再次顯示這個CPU的信息的時候,就會判斷該CPU在此位圖中是否被置位,如果有的話,說明已經打印過一次了.
然後確定iter使用的緩存區,如果當前tracer的print_max被置1,就使用max_tr, 否則使用gloal_trace.
將cpufile保存在iter->cpu_filer中.
 
     /* Notify the tracer early; before we stop tracing. */
     if (iter->trace && iter->trace->open)
         iter->trace->open(iter);
 
     /* Annotate start of buffers if we had overruns */
     if (ring_buffer_overruns(iter->tr->buffer))
         iter->iter_flags |= TRACE_FILE_ANNOTATE;
 
     if (iter->cpu_file == TRACE_PIPE_ALL_CPU) {
          for_each_tracing_cpu(cpu) {
 
              iter->buffer_iter[cpu] =
                   ring_buffer_read_start(iter->tr->buffer, cpu);
         }
     } else {
         cpu = iter->cpu_file;
         iter->buffer_iter[cpu] =
                   ring_buffer_read_start(iter->tr->buffer, cpu);
     }
 
準備工作都已經做完了,我們需要調用trace的open接口,來通知open操作即將開始.如果iter操作的緩衝區有數據被沖刷掉,就設置iter的TRACE_FILE_ANNOTATE標誌,這個標誌用在test_cpu_buff_start()中,如果沒有這個標誌,則不會顯示
”##### CPU %u buffer started ####”. 也就是說,只有在ring buffer中有數據被覆蓋了纔會顯示這個字符串.
接下來,就是爲操作的CPU初始化一個ring buffer的iter這個過程在分析ring buffer的時候就已經分析過了,這裏不再贅述.
注意在這裏並沒有對每個CPU上ring buffer的iter的初始化結果做檢查, 如果初始化失敗的話,採用reader的方式讀,但是這樣會造成死循環,這在後面的代碼可以看到
 
     /* TODO stop tracer */
     ret = seq_open(file, &tracer_seq_ops);
     if (ret < 0) {
         fail_ret = ERR_PTR(ret);
         goto fail_buffer;
     }
 
     m = file->private_data;
     m->private = iter;
 
     /* stop the trace while dumping */
     tracing_stop();
 
     mutex_unlock(&trace_types_lock);
 
     return iter;
 
將iter設置爲seq_file的私有區,然後禁止iter->trace的寫操作,這是因爲在讀trace文件的過程,不允許再有數據往裏面寫.
 
 fail_buffer:
     for_each_tracing_cpu(cpu) {
         if (iter->buffer_iter[cpu])
              ring_buffer_read_finish(iter->buffer_iter[cpu]);
     }
     free_cpumask_var(iter->started);
 fail:
     mutex_unlock(&trace_types_lock);
     kfree(iter->trace);
     kfree(iter);
 
     return fail_ret;
}
 
tracer_seq_ops的定義如下:
static struct seq_operations tracer_seq_ops = {
     .start        = s_start,
     .next         = s_next,
     .stop         = s_stop,
     .show         = s_show,
};
 
Seq file文件操作在之前的分析見過很多次了,在這就不詳細分析這個文件系統的實現了。來依次看它裏面的接口:
static void *s_start(struct seq_file *m, loff_t *pos)
{
     struct trace_iterator *iter = m->private;
     static struct tracer *old_tracer;
     int cpu_file = iter->cpu_file;
     void *p = NULL;
     loff_t l = 0;
     int cpu;
 
     /* copy the tracer to avoid using a global lock all around */
     mutex_lock(&trace_types_lock);
     if (unlikely(old_tracer != current_trace && current_trace)) {
         old_tracer = current_trace;
         *iter->trace = *current_trace;
     }
     mutex_unlock(&trace_types_lock);
 
     atomic_inc(&trace_record_cmdline_disabled);
 
     if (*pos != iter->pos) {
         iter->ent = NULL;
         iter->cpu = 0;
         iter->idx = -1;
 
         ftrace_disable_cpu();
 
         if (cpu_file == TRACE_PIPE_ALL_CPU) {
              for_each_tracing_cpu(cpu)
                   ring_buffer_iter_reset(iter->buffer_iter[cpu]);
         } else
              ring_buffer_iter_reset(iter->buffer_iter[cpu_file]);
 
 
         ftrace_enable_cpu();
 
         for (p = iter; p && l < *pos; p = s_next(m, p, &l))
              ;
 
     } else {
         l = *pos - 1;
         p = s_next(m, p, &l);
     }
 
     trace_event_read_lock();
     return p;
}
首先,如果當前的tracer發生了改變,那就將iter->tracer更新。
注意,在上面的代碼中還有這個操作:
atomic_inc(&trace_record_cmdline_disabled);
也就是說,在讀操作進行的時候,會禁止進程cmdline的記錄.
 
由於在初始化的時候,將iter->pos設爲了-1.因此,在第一次讀的時候,上面的if是滿足的,因爲會設操作CPU上的ring buffer iter重置,而且,if中的for循環也是不滿足的。因此在第一次讀的時候,會返回了一個空的event,即iter-> ent.
 
否則的話,會轉入到s_next()一直到數據讀滿爲止,來跟蹤看一下s_next():
static void *s_next(struct seq_file *m, void *v, loff_t *pos)
{
     struct trace_iterator *iter = m->private;
     int i = (int)*pos;
     void *ent;
 
     (*pos)++;
 
     /* can't go backwards */
     if (iter->idx > i)
         return NULL;
 
     if (iter->idx < 0)
         ent = find_next_entry_inc(iter);
     else
         ent = iter;
 
     while (ent && iter->idx < i)
         ent = find_next_entry_inc(iter);
 
     iter->pos = *pos;
 
     return ent;
}
裏面的核心操作在find_next_entry_inc()中,代碼如下:
static void *find_next_entry_inc(struct trace_iterator *iter)
{
     iter->ent = __find_next_entry(iter, &iter->cpu, &iter->ts);
 
     if (iter->ent)
         trace_iterator_increment(iter);
 
     return iter->ent ? iter : NULL;
}
這個函數較簡單,就是從ring buffer中取出下一塊數據,該數據對應的CPU和時間戳會分別保存在iter->cpu和iter->ts中,如果取數據成功,調用trace_iterator_increment()來在ring buffer中跳過這個數據,以便在下次讀的時候就可以讀到新數據了.
讀取成功返回iter,失敗返回NULL.
 
__find_next_entry()代碼如下:
static struct trace_entry *
__find_next_entry(struct trace_iterator *iter, int *ent_cpu, u64 *ent_ts)
{
     struct ring_buffer *buffer = iter->tr->buffer;
     struct trace_entry *ent, *next = NULL;
     int cpu_file = iter->cpu_file;
     u64 next_ts = 0, ts;
     int next_cpu = -1;
     int cpu;
 
     /*
      * If we are in a per_cpu trace file, don't bother by iterating over
      * all cpu and peek directly.
      */
     if (cpu_file > TRACE_PIPE_ALL_CPU) {
         if (ring_buffer_empty_cpu(buffer, cpu_file))
              return NULL;
         ent = peek_next_entry(iter, cpu_file, ent_ts);
         if (ent_cpu)
              *ent_cpu = cpu_file;
 
         return ent;
     }
 
     for_each_tracing_cpu(cpu) {
 
         if (ring_buffer_empty_cpu(buffer, cpu))
              continue;
 
         ent = peek_next_entry(iter, cpu, &ts);
 
         /*
          * Pick the entry with the smallest timestamp:
          */
         if (ent && (!next || ts < next_ts)) {
              next = ent;
              next_cpu = cpu;
              next_ts = ts;
         }
     }
 
     if (ent_cpu)
         *ent_cpu = next_cpu;
 
     if (ent_ts)
         *ent_ts = next_ts;
 
     return next;
}
如果是指對單個CPU的操作,那就很簡單了,只需要調用peek_next_entry()取這個CPU上的數據即可。
對於所有CPU的情況,要比較複雜一點,它是輪流取每個CPU中的數據,然後將時間戳最小的那塊數據返回,ent_cpu和ent_ts就存放這塊數據所在的CPU和它的時間戳
來跟蹤看一下peek_next_entry():
static struct trace_entry *
peek_next_entry(struct trace_iterator *iter, int cpu, u64 *ts)
{
     struct ring_buffer_event *event;
     struct ring_buffer_iter *buf_iter = iter->buffer_iter[cpu];
 
     /* Don't allow ftrace to trace into the ring buffers */
     ftrace_disable_cpu();
 
     if (buf_iter)
         event = ring_buffer_iter_peek(buf_iter, ts);
     else
         event = ring_buffer_peek(iter->tr->buffer, cpu, ts);
 
     ftrace_enable_cpu();
 
     return event ? ring_buffer_event_data(event) : NULL;
}
從這個函數我們就可以看出來了,如果ring buffer iter分配失敗,也就是上文代碼中buf_iter==NULL的情況,就會採用reader的方式將其讀出.
 
從這裏我們可以看出,trace的數據在ring buffer的基礎上又封裝了一個頭部,即struct trace_entry, 關於trcace的數據存放方法,在下文中等遇到時再來進行單獨的分析.
 
__find_next_entry()這條支路的情況分析完了,接下來看下trace_iterator_increment(),來看一下它是怎麼在ring buffer中跳過這塊緩存的:
static void trace_iterator_increment(struct trace_iterator *iter)
{
     /* Don't allow ftrace to trace into the ring buffers */
     ftrace_disable_cpu();
 
     iter->idx++;
     if (iter->buffer_iter[iter->cpu])
         ring_buffer_read(iter->buffer_iter[iter->cpu], NULL);
 
     ftrace_enable_cpu();
}
上面的代碼可以看出,就是調用ring_buffer_read()從緩存區中再讀一次,這個接口與前面調用的ring_buffer_iter_peek()相比,它會調用rb_advance_iter()來使iter跳過這塊數據.
在這裏沒必要擔心上次讀出的數據,也就是ring_buffer_iter_peek()讀出的數據會和ring_buffer_read()的數據不同。因爲在iter讀操作時,整個ring buffer都是禁寫的,它裏面的數據無法更新。
在這裏有一個問題,爲什麼用iter方式讀ring buffer中數據的時候要使用消耗掉這塊數據,而在reader方式下就不需要呢?
 
我們看到了數據是怎麼取出來了,接下來看數據的顯示, 從上文中的分析,可以得知,trace文件屬於seq file, 它的數據顯示是在struct seq_operations中的show操作完全的,在這裏即爲s_show().由於數據的顯示和數據的存放是緊密聯繫的,所以在這裏就暫時不分析s_show(),等下文中分析完了數據存放之後,再來分析這一部份.
 
文件關閉調用的接口爲tracing_release(),代碼如下:
static int tracing_release(struct inode *inode, struct file *file)
{
     struct seq_file *m = (struct seq_file *)file->private_data;
     struct trace_iterator *iter;
     int cpu;
 
     if (!(file->f_mode & FMODE_READ))
         return 0;
 
     iter = m->private;
 
     mutex_lock(&trace_types_lock);
     for_each_tracing_cpu(cpu) {
         if (iter->buffer_iter[cpu])
              ring_buffer_read_finish(iter->buffer_iter[cpu]);
     }
 
     if (iter->trace && iter->trace->close)
         iter->trace->close(iter);
 
     /* reenable tracing if it was previously enabled */
     tracing_start();
     mutex_unlock(&trace_types_lock);
 
     seq_release(inode, file);
     mutex_destroy(&iter->mutex);
     free_cpumask_var(iter->started);
     kfree(iter->trace);
     kfree(iter);
     return 0;
}
如果不是讀操作,就不用進行後面的資源釋放了,直接退出即可.否則,釋放讀操作曾經分配過的資源, 調用tracer的close接口來通知tracer要進行close操作了, 然後調用tracing_start()來啓用ring buffer的寫操作.
 

3.2 trace_pipe文件的操作

Trace_pipe也是讀取CPU上對應的數據,它和trace文件不同的時,讀該文件是一個阻塞型的,即如果沒有數據可讀的話,就會阻塞,一直到數據到達爲止.
文件的建立過程:
在debugfs/tracing/目錄下:
     trace_create_file("trace_pipe", 0444, d_tracer,
              (void *) TRACE_PIPE_ALL_CPU, &tracing_pipe_fops);
在debugfs/tracing/cpuN/目錄下:
     trace_create_file("trace_pipe", 0444, d_cpu,
              (void *) cpu, &tracing_pipe_fops);
兩者的實現都是一樣的, 不過就是操作的CPU不同,一個是操作所有CPU,另一個是操作對應的CPU.
它的文件操作集爲:
static const struct file_operations tracing_pipe_fops = {
     .open         = tracing_open_pipe,
     .poll         = tracing_poll_pipe,
     .read         = tracing_read_pipe,
     .splice_read  = tracing_splice_read_pipe,
     .release = tracing_release_pipe,
};
先來看open操作:
static int tracing_open_pipe(struct inode *inode, struct file *filp)
{
     long cpu_file = (long) inode->i_private;
     struct trace_iterator *iter;
     int ret = 0;
 
     if (tracing_disabled)
         return -ENODEV;
 
     mutex_lock(&trace_types_lock);
 
     /* We only allow one reader per cpu */
     if (cpu_file == TRACE_PIPE_ALL_CPU) {
         if (!cpumask_empty(tracing_reader_cpumask)) {
              ret = -EBUSY;
              goto out;
         }
         cpumask_setall(tracing_reader_cpumask);
     } else {
         if (!cpumask_test_cpu(cpu_file, tracing_reader_cpumask))
              cpumask_set_cpu(cpu_file, tracing_reader_cpumask);
         else {
              ret = -EBUSY;
              goto out;
         }
     }
 
從這個地方可以看出tracing_reader_cpumask的用途了, 它就是pipe read時,要操作的CPU集. 因爲整個open過程是加鎖保護的,所以在traceing_reader_cpumask進行上述代碼判斷可以保證該文件同時只能有一個pipe reader在執行
 
     /* create a buffer to store the information to pass to userspace */
     iter = kzalloc(sizeof(*iter), GFP_KERNEL);
     if (!iter) {
         ret = -ENOMEM;
         goto out;
     }
 
     /*
      * We make a copy of the current tracer to avoid concurrent
      * changes on it while we are reading.
      */
     iter->trace = kmalloc(sizeof(*iter->trace), GFP_KERNEL);
     if (!iter->trace) {
         ret = -ENOMEM;
         goto fail;
     }
     if (current_trace)
         *iter->trace = *current_trace;
 
     if (!alloc_cpumask_var(&iter->started, GFP_KERNEL)) {
         ret = -ENOMEM;
         goto fail;
     }
 
     /* trace pipe does not show start of buffer */
     cpumask_setall(iter->started);
 
     if (trace_flags & TRACE_ITER_LATENCY_FMT)
         iter->iter_flags |= TRACE_FILE_LAT_FMT;
 
     iter->cpu_file = cpu_file;
     iter->tr = &global_trace;
     mutex_init(&iter->mutex);
     filp->private_data = iter;
 
     if (iter->trace->pipe_open)
         iter->trace->pipe_open(iter);
 
out:
     mutex_unlock(&trace_types_lock);
     return ret;
 
fail:
     kfree(iter->trace);
     kfree(iter);
     mutex_unlock(&trace_types_lock);
     return ret;
}
分配並初始化struct trace_iterator, 這個操作跟trace文件的操作大同小異,不過需要注意的是,它們之間的差別:
1): pipe read剛開始就將iter->started置位了所有CPU, 結合我們在上面的分析,就可得知,在pipe read下就不會打印出” ##### CPU n buffer started ####”這樣的無用信息.
2): 調用tracer的pipe_open()操作來通知tracer.
3): 操作trace文件的時候,會禁用寫操作,而pipe_open是開寫操作執行的
4): 在讀ring buffer的方式上,pipe操作沒有去初始化讀ring buffer的iter,也就是說,pipe讀方式是以reader方式進行的,它會一邊讀,一邊消耗ring buffer中的數據頁
5):  pipe_read只能讀取global_trace中的內容
 
接下來分析pipe的Read操作,它對應的接口爲tracing_read_pipe(),代碼如下:
static ssize_t
tracing_read_pipe(struct file *filp, char __user *ubuf,
           size_t cnt, loff_t *ppos)
{
     struct trace_iterator *iter = filp->private_data;
     static struct tracer *old_tracer;
     ssize_t sret;
 
     /* return any leftover data */
     sret = trace_seq_to_user(&iter->seq, ubuf, cnt);
     if (sret != -EBUSY)
         return sret;
 
     trace_seq_init(&iter->seq);
 
     /* copy the tracer to avoid using a global lock all around */
     mutex_lock(&trace_types_lock);
     if (unlikely(old_tracer != current_trace && current_trace)) {
         old_tracer = current_trace;
         *iter->trace = *current_trace;
     }
     mutex_unlock(&trace_types_lock);
 
如果iter中還有數據,將就數據返回到用戶空間,如果已經沒數據了,將iter->seq重置位,然後加鎖獲得cuuurent_trace.
在這裏就有一個疑問了,如果等我們獲得了current_trace後,即運行到mutex_unlock(&trace_types_lock)這條語句後,用戶更改了當前的tracer, 而且此時tracer很快就往ring buffer中寫入了數據.這樣就會造成pipe_read讀出來的數據,其實已經不是iter->trace的數據了,而這個問題在trace的操作中是不存在的,因爲在trace的操作過程中,寫操作是禁止的.
是到分析iter->seq的時候了,它的類型爲struct trace_seq, 定義如下:
struct trace_seq {
     unsigned char      buffer[PAGE_SIZE];
     unsigned int       len;
     unsigned int       readpos;
};
該結構用於將ring buffer中取出的信息經過轉換之後存放在這裏, 從結構可以看出,它能存放的最大大小(也就是read操作能夠讀出的有效數據的最大長度)是一個頁面.
Len表示trace_seq中的數據長度, readpos是指當前讀位置.
那可以看出,trace_seq中有len大小的數據,已經被讀出了readpos,而餘下len-readpos的數據
 
     /*
      * Avoid more than one consumer on a single file descriptor
      * This is just a matter of traces coherency, the ring buffer itself
      * is protected.
      */
     mutex_lock(&iter->mutex);
     if (iter->trace->read) {
         sret = iter->trace->read(iter, filp, ubuf, cnt, ppos);
         if (sret)
              goto out;
     }
 
waitagain:
     sret = tracing_wait_pipe(filp);
     if (sret <= 0)
         goto out;
 
     /* stop when tracing is finished */
     if (trace_empty(iter)) {
         sret = 0;
         goto out;
     }
 
     if (cnt >= PAGE_SIZE)
         cnt = PAGE_SIZE - 1;
 
     /* reset all but tr, trace, and overruns */
     memset(&iter->seq, 0,
            sizeof(struct trace_iterator) -
            offsetof(struct trace_iterator, seq));
     iter->pos = -1;
 
如果iter中已經沒有數據了, 爲了使讀操作串行化,必須先持有iter->mutex, 這是因爲,如果讀操作並行,可以因爲競爭關係,在下面的處理中,會使一些讀操作異常返回,導致退出.
可是爲什麼在trace文件操作的時候爲什麼不需要保證它的串行呢? 這是因爲trace文件採用的是seq file的方式,它內部就是加鎖保證順序訪問的.
然後調用tracer->read()來通知tracer pipe讀操作已經開始了.注意tracer->read()在操作trace文件是不會被操作的,其實這裏改爲tracer->pipe_read()可能會更合適
接着調用tracing_wait_pipe()來等待,一直到有數據到達爲止.
如果等待返回了,檢查ring buffer 後還是沒數據,說明traceing_waite_pipe()是異常返回(比如說收到了信號),操作返回.
確實有數據可讀之後,就需要做一些必須的初始化工作.
 
     trace_event_read_lock();
     while (find_next_entry_inc(iter) != NULL) {
         enum print_line_t ret;
         int len = iter->seq.len;
 
         ret = print_trace_line(iter);
         if (ret == TRACE_TYPE_PARTIAL_LINE) {
              /* don't print partial lines */
              iter->seq.len = len;
              break;
         }
         if (ret != TRACE_TYPE_NO_CONSUME)
              trace_consume(iter);
 
         if (iter->seq.len >= cnt)
              break;
     }
     trace_event_read_unlock();
 
因爲在print_trace_line()中將ring buffer中的數據進行轉換的時候需要用到trace_event,爲了防止跟trace_event註冊和撤消的競爭,這裏需要持有trace_event_mutex
然後調用find_next_entry_inc()從ring buffer中取數據,這個過程我們在分析trace方件的讀操作就已經分析過了, 這裏不再進行分析了.
接着接用print_trace_line()將取到的數據放到iter->seq中.
注意print_trace_line()的返回值:
1: TRACE_TYPE_PARTIAL_LINE:
     說明剛纔填允到iter中的數據是不需要的,因此將iter->seq的數據長度恢復到寫之前的值
2: TRACE_TYPE_NO_CONSUME
     說明這些數據在處理的時候已經從ring buffer中消耗掉了,否則調用trace_consume()將讀過的event消耗掉。
如果已經取到了足夠的數據,就可以停止從ring buffer中取數據了
 
     /* Now copy what we have to the user */
     sret = trace_seq_to_user(&iter->seq, ubuf, cnt);
     if (iter->seq.readpos >= iter->seq.len)
         trace_seq_init(&iter->seq);
 
     /*
      * If there was nothing to send to user, inspite of consuming trace
      * entries, go back to wait for more entries.
      */
     if (sret == -EBUSY)
         goto waitagain;
 
out:
     mutex_unlock(&iter->mutex);
 
     return sret;
}
剩餘的就很容易了,將讀取到的數據返回即可.
這裏的加鎖以保護串行是不是有問題呢?因爲剛開始進入到的這個函數的時候,會有從iter->seq中取數據以及清空iter->seq的操作,這會跟後面的iter->seq操作產生競爭.
 
有必要來分析一下tracing_wait_pipe()的操作,代碼如下:
static int tracing_wait_pipe(struct file *filp)
{
     struct trace_iterator *iter = filp->private_data;
 
     while (trace_empty(iter)) {
 
         if ((filp->f_flags & O_NONBLOCK)) {
              return -EAGAIN;
         }
 
         mutex_unlock(&iter->mutex);
 
         iter->trace->wait_pipe(iter);
 
         mutex_lock(&iter->mutex);
 
         if (signal_pending(current))
              return -EINTR;
 
         /*
          * We block until we read something and tracing is disabled.
          * We still block if tracing is disabled, but we have never
          * read anything. This allows a user to cat this file, and
          * then enable tracing. But after we have read something,
          * we give an EOF when tracing is again disabled.
          *
          * iter->pos will be 0 if we haven't read anything.
          */
         if (!tracer_enabled && iter->pos)
              break;
     }
 
     return 1;
}
從上面的代碼中很容易看出:
1: 如果打開文件時帶了O_NONBLOCK標誌,會退出,因爲在這個地方會造成阻塞.
2: 調用trace->wait_pipe()阻塞,直到有數據爲止
3: 如果trace被禁用(tracer_enable == 0)而且iter->pos不爲0,就會退出等待.
   爲什麼呢? 我們知道,在tracing_open_pipe()分配iter用的是kzalloc,即iter結構中的
   成員全部爲0,而且沒有對iter->pos做特別的初始化.因此可得知,open的時候iter->pos爲0,
  然後在tracing_read_pipe()中,判斷讀取數據之後,會將iter->pos置爲-1
  結合註釋可得知這樣做的原因:
如果trace被禁用了,但我們還沒有讀到任何數據,那就一直等待,到有數據爲止.如果是在讀數據的中途(曾經讀出過數據),那就給用戶空間返回EOF.
這樣做,主要是允許這樣的情況:
在open的時候,trace是禁用的,然後pipe read一直阻塞,直到trace啓用爲止.
 
接着跟蹤看一下默認的tracer-> wait_pipe(),即default_wait_pipe,代碼如下:
void default_wait_pipe(struct trace_iterator *iter)
{
     DEFINE_WAIT(wait);
 
     prepare_to_wait(&trace_wait, &wait, TASK_INTERRUPTIBLE);
 
     if (trace_empty(iter))
         schedule();
 
     finish_wait(&trace_wait, &wait);
}
這段代碼比較容易,就是將等待隊列掛在trace_wait上,然後一直睡眠,直到有數據爲止,注意這裏並沒有信號的檢測,即如果沒數據的話,不管用戶進行什麼樣的操作,它都會阻塞在這個地方.
 
結合tracing_wait_pipe()和default_wait_pipe()的代碼來看:
1; 有數據default_wait_pipe()纔會返回,如果一個tracer一段時間內沒有寫數據,那麼即使用按ctrl+C也無法中止這個過程.
2: 如果tracer使用的是default_wait_pipe(),不可能會因爲trace disable造成pipe_read退出的現象. 這是因爲對tracer_enabled進行判斷的時候, default_wait_pipe()肯定檢測到取到了數據.
這個地方是不是錯誤呢?
 
Poll接口的基本原理跟pipe read是一樣的,它也在檢測到有數據時纔會返回,在這裏就不詳細分析這個過程了.
Close操作就是釋放掉open時候分配的資源.也不對它進行詳細分析了.

四: trace數據的組織

上面分析到具體的數據讀取方面的東西的時候全部都略過了,下面我們從數據的存放開始來研究這個問題.

4.1: 數據的存放

經過上一篇文章對ring buffer的分析,我們知道ring buffer中的數據都帶有一個ring_buffer_event的頭部,在trace中,每一塊數據又被帶上了trace_entry的頭部,它用來存放trace數據的公共信息。結構定義如下示:
struct trace_entry {
     unsigned short         type;
     unsigned char      flags;
     unsigned char      preempt_count;
     int           pid;
     int           tgid;
};
Type是數據的類型,不同的類型,顯示數據也是不一樣的,這在後面會分析到,flags表示當前的cpu標誌信息,從此處可以得到中斷是否被禁用, preempt_count,表示搶佔位計數,pid和tgid分別表示當前進程的pid和tgid。
不同的tracer需要不同的數據,那它是怎麼來表示和分配的呢?我們以debugfs/tracing/trace_marker文件爲例進行分析。
Trace_marker表示用戶可以自已輸入一些東西以便做爲了mark之用。它的寫操作過程就不加詳細分析了。它的重要的操作是在trace_vprintk()中完成的,代碼如下:
int trace_vprintk(unsigned long ip, const char *fmt, va_list args)
{
     ......
     ......
     raw_local_irq_save(irq_flags);
     __raw_spin_lock(&trace_buf_lock);
     len = vsnprintf(trace_buf, TRACE_BUF_SIZE, fmt, args);
 
     len = min(len, TRACE_BUF_SIZE-1);
     trace_buf[len] = 0;
 
     size = sizeof(*entry) + len + 1;
     event = trace_buffer_lock_reserve(tr, TRACE_PRINT, size, irq_flags, pc);
     if (!event)
         goto out_unlock;
     entry = ring_buffer_event_data(event);
     entry->ip          = ip;
 
     memcpy(&entry->buf, trace_buf, len);
     entry->buf[len] = 0;
     if (!filter_check_discard(call, entry, tr->buffer, event))
         ring_buffer_unlock_commit(tr->buffer, event);
 
......
}
首先來看一下存放數據的數據結構,如下:
struct print_entry {
     struct trace_entry ent;
     unsigned long      ip;
     char          buf[];
};
它除了本身所存放的數據外,還帶有一個struct trace_entry, buf[]是一個變長的數組,它的實際大小等於要存放的字符所佔空間。
據此可以得知,它要從ring buffer中分配的數據大小爲
sizeof(struct print_entry) + strlen(string) + 1
從上面可以看到,有一個統一的,trace從ring buffer中分配空間的接口,即爲trace_buffer_lock_reserve().代碼如下:
struct ring_buffer_event *trace_buffer_lock_reserve(struct trace_array *tr,
                                int type,
                                unsigned long len,
                                unsigned long flags, int pc)
{
     struct ring_buffer_event *event;
 
     event = ring_buffer_lock_reserve(tr->buffer, len);
     if (event != NULL) {
         struct trace_entry *ent = ring_buffer_event_data(event);
 
         tracing_generic_entry_update(ent, flags, pc);
         ent->type = type;
     }
 
     return event;
}
先看一下參數的含義:
Tr: 我們在前面分析過了,它是trace封裝的一個結構,裏面含有要操作的ring buffer
Type: 寫入ring buffer的數據類型
Len: 需要從ring buffer中分配的總長度(不計算event頭部)
Flags: CPU的標誌
Pc: 當前進程的搶佔位計數
 
Ring_buffer_lock_reserver()是RB的一個接口,在分析RB的時候已經分析過了.
ring_buffer_event_data()也是RB的一個接口,即從分配得到的event中取出data部份
重點來看一下tracing_generic_entry_update(),代碼如下:
void
tracing_generic_entry_update(struct trace_entry *entry, unsigned long flags,
                   int pc)
{
     struct task_struct *tsk = current;
 
     entry->preempt_count        = pc & 0xff;
     entry->pid             = (tsk) ? tsk->pid : 0;
     entry->tgid            = (tsk) ? tsk->tgid : 0;
     entry->flags =
#ifdef CONFIG_TRACE_IRQFLAGS_SUPPORT
         (irqs_disabled_flags(flags) ? TRACE_FLAG_IRQS_OFF : 0) |
#else
         TRACE_FLAG_IRQS_NOSUPPORT |
#endif
         ((pc & HARDIRQ_MASK) ? TRACE_FLAG_HARDIRQ : 0) |
         ((pc & SOFTIRQ_MASK) ? TRACE_FLAG_SOFTIRQ : 0) |
         (need_resched() ? TRACE_FLAG_NEED_RESCHED : 0);
}
從此可以看出, trace_entry-> preempt_count保存的是當前進程搶佔計數,它是進程preempt_count字段的最低8位.(似乎這裏0xff用PREEMPT_MASK代替更合適一些)
trace_entry->pid和trace_entry->tgid分別存放當前進程的pid和tgid
trace_entry->flags表示了很多信息,具體如下:
第一位: 表示中斷禁用位,禁用爲1,啓用爲0
第二位: 表示當前平臺是否支持irq flag的檢測(沒有配置CONFIG_TRACE_IRQFLAGS_SUPPORT),不支持此位置1,否則爲0
第三位: 表示當前是否需要重調度,是爲1,否則爲0
第四位: 表示是否處於硬中斷壞境(響應外部設備的中斷),是爲1,否則爲0
第五位: 表示是否處於軟中斷環境,是爲1,否則爲0
這裏是否還可以增加一位用來顯示是否在NMI中?
當然trace_entry->type表示數據的類型
 
數據的commit!!! 爲什麼trace_vprintk()在寫完數據提交後,爲什麼不需要採用wake_up的commit?

4.2: 數據的讀取

前面分析了數據的存放過程,現在來看一下怎麼將數據從ring buffer中取出來.
上面分析過,trace框架只提供了一個基本的頭部,每個tracer還有自己的數據擴充,那怎麼知道trace_entry後面擴充的是怎麼樣的數據呢? trace_entry->type就可以派上用場了,可以憑藉它找到它所對應的輸出規則。
 
在trace中,這種輸出規則是用trace_event來表示的,該結構如下示:
typedef enum print_line_t (*trace_print_func)(struct trace_iterator *iter,
                             int flags);
struct trace_event {
     struct hlist_node  node;
     struct list_head   list;
     int           type;
     trace_print_func   trace;
     trace_print_func   raw;
     trace_print_func   hex;
     trace_print_func   binary;
};
可能trace_event會通過list鏈在一起(其type是自動分配的情況下),同時爲了快速和type相對應,它們又通過node鏈在一個哈錶鏈裏。
Type表示對應的數據類型,也即前面分析的trace_entry->type.
Trace, raw, hex,binary分別表示數據輸出的幾種方式,具體的在後面會分析到.
 
對於trace_event不外乎幾種操作: 註冊trace_event,根據type找到trace_event,撤消trace_event,分別來分析這幾個過程:
 
Trace_event的註冊:
Trace_event的註冊是通過register_ftrace_event()來完成的,代碼如下:
int register_ftrace_event(struct trace_event *event)
{
     unsigned key;
     int ret = 0;
 
     down_write(&trace_event_mutex);
 
     if (WARN_ON(!event))
         goto out;
 
     INIT_LIST_HEAD(&event->list);
 
     if (!event->type) {
         struct list_head *list = NULL;
 
         if (next_event_type > FTRACE_MAX_EVENT) {
 
              event->type = trace_search_list(&list);
              if (!event->type)
                   goto out;
 
         } else {
             
              event->type = next_event_type++;
              list = &ftrace_event_list;
         }
 
         if (WARN_ON(ftrace_find_event(event->type)))
              goto out;
 
         list_add_tail(&event->list, list);
 
     } else if (event->type > __TRACE_LAST_TYPE) {
         printk(KERN_WARNING "Need to add type to trace.h\n");
         WARN_ON(1);
         goto out;
     } else {
         /* Is this event already used */
         if (ftrace_find_event(event->type))
              goto out;
     }
 
     if (event->trace == NULL)
         event->trace = trace_nop_print;
     if (event->raw == NULL)
         event->raw = trace_nop_print;
     if (event->hex == NULL)
         event->hex = trace_nop_print;
     if (event->binary == NULL)
         event->binary = trace_nop_print;
 
     key = event->type & (EVENT_HASHSIZE - 1);
 
     hlist_add_head(&event->node, &event_hash[key]);
 
     ret = event->type;
 out:
     up_write(&trace_event_mutex);
 
     return ret;
}
爲了保護ftrace_event_list和event_hash[],所以在進行操作之前必須持有鎖。
接下來是對event->type的處理,可能有以下三點情況:
1: 沒有指定event->type: 這種情況就需要爲其分配一個type
2:  指定了event->type,但是它的值超過了__TRACE_LAST_TYPE,這是一種非法的情況,__TRACE_LAST_TYPE的相關定義如下:
enum trace_type {
     __TRACE_FIRST_TYPE = 0,
 
     TRACE_FN,
     TRACE_CTX,
     TRACE_WAKE,
     TRACE_STACK,
     TRACE_PRINT,
     TRACE_BPRINT,
     TRACE_SPECIAL,
     TRACE_MMIO_RW,
     TRACE_MMIO_MAP,
     TRACE_BRANCH,
     TRACE_BOOT_CALL,
     TRACE_BOOT_RET,
     TRACE_GRAPH_RET,
     TRACE_GRAPH_ENT,
     TRACE_USER_STACK,
     TRACE_HW_BRANCHES,
     TRACE_SYSCALL_ENTER,
     TRACE_SYSCALL_EXIT,
     TRACE_KMEM_ALLOC,
     TRACE_KMEM_FREE,
     TRACE_POWER,
     TRACE_BLK,
     TRACE_KSYM,
 
     __TRACE_LAST_TYPE,
};
enum trace_type表示了系統自定義的type類型,超過__TRACE_LAST_TYPE是屬於自動分配的type
 
3: 指定了type,type也沒有超過trace_type,但是這個trace_type已經註冊過了。
     這種情況是不允許的,不可能爲一個type對應多個trace_event.
從上面的代碼中可以看出,只有在type末指定的情況,纔會將trace_evnet加入ftrace_event_list鏈表.
 
在trace_evnet-> trace, raw, hex, binary爲空的情況下,爲其選擇一個默認處理函數,這個函數什麼都不會做,直接返回一個已處理標誌。
 
然後,爲了加快從type到trace_event的查找速度,以type爲鍵值,取其低7位做爲哈希值,加入event_hash[]的對應哈希鏈表中。
 
來分析它的type分配過程,如下代碼段:
         if (next_event_type > FTRACE_MAX_EVENT) {
 
              event->type = trace_search_list(&list);
              if (!event->type)
                   goto out;
 
         } else {
             
              event->type = next_event_type++;
              list = &ftrace_event_list;
         }
next_event_type表示下一次要分配的type,如果它超過了最大值,那就只能從頭搜索一次,看是否有被釋放掉的type.如果當前再無type可用,返回0。
最大值的爲FTRACE_MAX_EVENT,定義如下:
#define FTRACE_MAX_EVENT                           \
     ((1 << (sizeof(((struct trace_entry *)0)->type) * 8)) - 1)
它就是表示struct trace_entry->type所佔的位數
 
在next_event_type沒有超過最大值的情況下,很容易處理,直接取next_event_type的值就可以了,然後將ftrace_event_list鏈表的末尾。
那也就是說,next_event_type末超過範圍之前,ftrace_event_list中的數據都是按type從小到大的順序排列的。
如果超過了呢?來看一下trace_search_list(),代碼如下:
static int trace_search_list(struct list_head **list)
{
     struct trace_event *e;
     int last = __TRACE_LAST_TYPE;
 
     if (list_empty(&ftrace_event_list)) {
         *list = &ftrace_event_list;
         return last + 1;
     }
 
     /*
      * We used up all possible max events,
      * lets see if somebody freed one.
      */
     list_for_each_entry(e, &ftrace_event_list, list) {
         if (e->type != last + 1)
              break;
         last++;
     }
 
     /* Did we used up all 65 thousand events??? */
     if ((last + 1) > FTRACE_MAX_EVENT)
         return 0;
 
     *list = &e->list;
     return last + 1;
}
這個函數只有一個參數,且是一個二級指針list,表示要插入的位置,返回分配得到的type,如果失敗,返回0.
 
__TRACE_LAST_TYPE是動態分配type的起始值,如果ftrace_event_list爲空,說明之前分配type的trace_event全部都釋放掉了,所以直取__TRACE_LAST_TYPE即可.
在其它的情況下,因爲ftrace_event_list中的數據都是從小到大排列來的,所以只需要一個循環找到其中的“空洞”位置即可。
 
從註冊過程中我們也看到了,需要調用ftrace_find_event()來判斷該type是否被使用,其它就是去尋找type對應的trace_event,也就是我們接下來要分析的操作,代碼如下:
struct trace_event *ftrace_find_event(int type)
{
     struct trace_event *event;
     struct hlist_node *n;
     unsigned key;
 
     key = type & (EVENT_HASHSIZE - 1);
 
     hlist_for_each_entry(event, n, &event_hash[key], node) {
         if (event->type == type)
              return event;
     }
 
     return NULL;
}
該函數很簡單,就是一個計算哈希值,然後從對應哈希鏈中匹配的過程。
在這裏需要注意一個問題,在整個尋找的過程中是沒有加鎖的。因此,我們在調用它之前必須持有trace_event_mutex
 
接下來分析trace_event的撤消過程,它是在unregister_ftrace_event(),代碼如下:
int unregister_ftrace_event(struct trace_event *event)
{
     down_write(&trace_event_mutex);
     __unregister_ftrace_event(event);
     up_write(&trace_event_mutex);
 
     return 0;
}
先是臨界區保護,然後調用__unregister_ftrace_event(),如下:
int __unregister_ftrace_event(struct trace_event *event)
{
     hlist_del(&event->node);
     list_del(&event->list);
     return 0;
}
很簡單,只需要對應從ftrace_event_list, event_hash[]中刪除即可。
 
分析完了trace_event的操作之後,可以來看一下數據到底是怎麼顯示的,從前面分析trace文件和trace_pipe的操作可得知,它是在print_trace_line()中完成的,代碼如下:
static enum print_line_t print_trace_line(struct trace_iterator *iter)
{
     enum print_line_t ret;
 
     if (iter->trace && iter->trace->print_line) {
         ret = iter->trace->print_line(iter);
         if (ret != TRACE_TYPE_UNHANDLED)
              return ret;
     }
 
如果當前的trace定義了print_line,那就調用它
 
     if (iter->ent->type == TRACE_BPRINT &&
              trace_flags & TRACE_ITER_PRINTK &&
              trace_flags & TRACE_ITER_PRINTK_MSGONLY)
         return trace_print_bprintk_msg_only(iter);
 
     if (iter->ent->type == TRACE_PRINT &&
              trace_flags & TRACE_ITER_PRINTK &&
              trace_flags & TRACE_ITER_PRINTK_MSGONLY)
         return trace_print_printk_msg_only(iter);
 
如果當前的消息類型是TRACE_BPRINT/TRACE_PRINT, 而且trace標誌帶有TRACE_ITER_PRINTK和TRACE_ITER_PRINTK_MSGONLY,那就調用
trace_print_bprintk_msg_only()/trace_print_printk_msg_only()
 
     if (trace_flags & TRACE_ITER_BIN)
         return print_bin_fmt(iter);
 
     if (trace_flags & TRACE_ITER_HEX)
         return print_hex_fmt(iter);
 
     if (trace_flags & TRACE_ITER_RAW)
         return print_raw_fmt(iter);
 
TRACE_ITER_BIN,TRACE_ITER_HEX,TRACE_ITER_RAW表示消息分別以二進制,十六進制,原始形式輸出
 
     return print_trace_fmt(iter);
 
否則調用print_trace_fmt()
 
}
分別來分析一下上面出現的幾個函數:
enum print_line_t trace_print_bprintk_msg_only(struct trace_iterator *iter)
{
     struct trace_seq *s = &iter->seq;
     struct trace_entry *entry = iter->ent;
     struct bprint_entry *field;
     int ret;
 
     trace_assign_type(field, entry);
 
     ret = trace_seq_bprintf(s, field->fmt, field->buf);
     if (!ret)
         return TRACE_TYPE_PARTIAL_LINE;
 
     return TRACE_TYPE_HANDLED;
}
 
trace_assign_type()用來將entry按照其type轉換成想來的類型,定義如下:
#define trace_assign_type(var, ent)                     \
     do {                                 \
         IF_ASSIGN(var, ent, struct ftrace_entry, TRACE_FN); \
         IF_ASSIGN(var, ent, struct ctx_switch_entry, 0);   \
         ......
         .......
         __ftrace_bad_type();                      \
     } while (0)
 
#undef IF_ASSIGN
#define IF_ASSIGN(var, entry, etype, id)       \
     if (FTRACE_CMP_TYPE(var, etype)) {        \
         var = (typeof(var))(entry);      \
         WARN_ON(id && (entry)->type != id);  \
         break;                      \
     }
#define FTRACE_CMP_TYPE(var, type) \
     __builtin_types_compatible_p(typeof(var), type *)
 
__builtin_types_compatible_p是gcc的擴展,用來檢測兩個類型是否一樣.
其實IF_ASSIGN(var, entry, etype, id)就是表示,如果var和etype類型是一樣的,那麼就把entry轉換成etype類型,並且賦值給var.
注意上面的這個宏在預編譯的時候就會被替換掉,如果某處的trace_assign_type()不合法(var的類型沒有出現在這個列表中)就會被替換成__ftrace_bad_type,而這個函數是沒有被定義的,所以在編譯的時候就會報錯,這就可以提前發現錯誤,而並不需要等到kernel運行起來,報error了才發現。
 
TRACE_BPRINT所用的數據結構爲struct bprint_entry,定義如下:
struct bprint_entry {
     struct trace_entry ent;
     unsigned long      ip;
     const char         *fmt;
     u32           buf[];
};
 
現在有必要來介紹一下bprint是什麼東西了,例如,如下打印語句:
Printk(“a=%d, b=%ul\n”, a, b);
如果打印格式字符串是一個靜態的,那就會適用bprint了,它會將brpint_entry->fmt指向打印的字符串,然後將要打印的參數按一定的方式保存在bprint_entry->buf[]中,這樣就可以比一般的print打印要節省空間。
 
trace_print_printk_msg_only()代碼如下:
enum print_line_t trace_print_printk_msg_only(struct trace_iterator *iter)
{
     struct trace_seq *s = &iter->seq;
     struct trace_entry *entry = iter->ent;
     struct print_entry *field;
     int ret;
 
     trace_assign_type(field, entry);
 
     ret = trace_seq_printf(s, "%s", field->buf);
     if (!ret)
         return TRACE_TYPE_PARTIAL_LINE;
 
     return TRACE_TYPE_HANDLED;
}
Struct print_entry在上面的分析中提到過,並舉了一個例子,在這裏就不詳細分析了,它就是將buf中的內容存放到iter->seq中.
 
print_bin_fmt()代碼如下:
static enum print_line_t print_bin_fmt(struct trace_iterator *iter)
{
     struct trace_seq *s = &iter->seq;
     struct trace_entry *entry;
     struct trace_event *event;
 
     entry = iter->ent;
 
     if (trace_flags & TRACE_ITER_CONTEXT_INFO) {
         SEQ_PUT_FIELD_RET(s, entry->pid);
         SEQ_PUT_FIELD_RET(s, iter->cpu);
         SEQ_PUT_FIELD_RET(s, iter->ts);
     }
 
     event = ftrace_find_event(entry->type);
     return event ? event->binary(iter, 0) : TRACE_TYPE_HANDLED;
}
如果定義了TRACE_ITER_CONTEXT_INFO,則需要將pid, cpu, time信息打印出來。
SEQ_PUT_FIELD_RET定義如下:
#define SEQ_PUT_FIELD_RET(s, x)                \
do {                             \
     if (!trace_seq_putmem(s, &(x), sizeof(x)))     \
         return TRACE_TYPE_PARTIAL_LINE;      \
} while (0)
trace_seq_putmem()代碼如下:
int trace_seq_putmem(struct trace_seq *s, const void *mem, size_t len)
{
     if (len > ((PAGE_SIZE - 1) - s->len))
         return 0;
 
     memcpy(s->buffer + s->len, mem, len);
     s->len += len;
 
     return len;
}
從此可以看出,它就是直接將數據拷貝到緩存裏。
接着調用ftrace_find_event()找到對應的event,然後調用該event的binary()操作.
 
print_hex_fmt()和print_bin_fmt()大同小異,只不過數據是以十六進制方式顯示。
print_raw_fmt()代碼如下:
static enum print_line_t print_raw_fmt(struct trace_iterator *iter)
{
     struct trace_seq *s = &iter->seq;
     struct trace_entry *entry;
     struct trace_event *event;
 
     entry = iter->ent;
 
     if (trace_flags & TRACE_ITER_CONTEXT_INFO) {
         if (!trace_seq_printf(s, "%d %d %llu ",
                         entry->pid, iter->cpu, iter->ts))
              goto partial;
     }
 
     event = ftrace_find_event(entry->type);
     if (event)
         return event->raw(iter, 0);
 
     if (!trace_seq_printf(s, "%d ?\n", entry->type))
         goto partial;
 
     return TRACE_TYPE_HANDLED;
partial:
     return TRACE_TYPE_PARTIAL_LINE;
}
Raw方式的輸出要比前兩種要好看多了,它將各數值轉換成了字符串格式。並且在最後還會將數據的type輸出.
 
print_trace_fmt()代碼如下:
static enum print_line_t print_trace_fmt(struct trace_iterator *iter)
{
     struct trace_seq *s = &iter->seq;
     unsigned long sym_flags = (trace_flags & TRACE_ITER_SYM_MASK);
     struct trace_entry *entry;
     struct trace_event *event;
 
     entry = iter->ent;
 
     test_cpu_buff_start(iter);
 
test_cpu_buff_start()函數比較簡單,如果:
1: trace標誌定義了TRACE_ITER_ANNOTATE
2: 該條信息不是第一條顯示的信息
3: 從來沒有爲該CPU打印過註釋
那就爲這個CPU打印註釋信息,格式如下:
“##### CPU N buffer started ####\n"
 
     event = ftrace_find_event(entry->type);
 
     if (trace_flags & TRACE_ITER_CONTEXT_INFO) {
         if (iter->iter_flags & TRACE_FILE_LAT_FMT) {
              if (!trace_print_lat_context(iter))
                   goto partial;
         } else {
              if (!trace_print_context(iter))
                   goto partial;
         }
     }
 
對應的,找到type對應的trace_event,如果定義了TRACE_ITER_CONTEXT_INFO,那就需要打印pid,cpu,time等信息.
在TRACE_FILE_LAT_FMT被定義與沒被定義的情況下,打印的消息格式是不相同的,在這裏就不詳細分析trace_print_lat_context()和trace_print_context(),它們的代碼都很簡單,可自行閱讀.
 
     if (event)
         return event->trace(iter, sym_flags);
 
     if (!trace_seq_printf(s, "Unknown type %d\n", entry->type))
         goto partial;
 
     return TRACE_TYPE_HANDLED;
partial:
     return TRACE_TYPE_PARTIAL_LINE;
}
相應的,最後調用event的trace操作, 如果沒有找到對應的event,則顯示錯誤提示:
“Unknown type %d\n”
 
到這裏,就對trace中的數據如何組織,如果存放,以及如何顯示的分析告一段落了.
 

五: cmdline的存放和查找

Cmdline就是進程的名字,也就是我們用ps指用所列出的進程列表.其實它的操作不外乎這幾個:
1: 如何存放pid與cmdline的對應關係.
2: 如何查找pid對應的cmdline.
3: 保存cmdline的時機.
下文的分析主要會圍繞着這幾個方面.
 

5.1: cmdline的初始化

經過上面的分析,我們知道cmdline的初始化操作是在trace_init_cmdlines()中完全的,代碼如下:
static void trace_init_cmdlines(void)
{
     memset(&map_pid_to_cmdline, NO_CMDLINE_MAP, sizeof(map_pid_to_cmdline));
     memset(&map_cmdline_to_pid, NO_CMDLINE_MAP, sizeof(map_cmdline_to_pid));
     cmdline_idx = 0;
}
看其來它很簡單,就是將map_pid_to_cmdline, map_cmdline_to_pid全部都清成初始化狀態NO_CMDLINE_MAP表示它是沒有映射的,也就是空閒的.並將cmdline_idx置爲了0.
它們的初始分別如下:
static unsigned map_pid_to_cmdline[PID_MAX_DEFAULT+1];
static unsigned map_cmdline_to_pid[SAVED_CMDLINES];
static int cmdline_idx;
另外還有一個存放cmdline的二維數組:
static char saved_cmdlines[SAVED_CMDLINES][TASK_COMM_LEN];
 
看了上面的定義之後,我們可能會隱約猜到這個算法了, saved_cmdlines無疑是用來存放cmdline的.
TASK_COMM_LEN是最大的進程名字大小,因此可得知,裏面最多可以存放SAVED_CMDLINES個cmdline.
而PID_MAX_DEFAULT 表示的是進程最大的pid大小, map_pid_to_cmdline[]無疑是每個pid都會對應一項.爲什麼最大的pid值是PID_MAX_DEFAULT,但是map_pid_to_cmdline[]的大小卻定義爲PID_MAX_DEFAULT+1呢? 因爲還得要加上pid等於0的情況(idle進程).
從map_pid_to_cmdline的名字上看來,它是映射從pid到cmdline,那是否是每個pid對應map_pid_to_cmdline[]中的每一個項,然後這一項裏面存放的是在saved_cmdlines[][TASK_COMM_LEN]中的序號.然後就找到了cmdline?
 
map_cmdline_to_pid[SAVED_CMDLINES]又是用來幹什麼的呢? 從上面可以的分析可以看出,一次只能保存SAVED_CMDLINES個進程的cmdline,那這個數組應該是用來控制那些進程應該要映射.哪些進程的映射被”擠出”?
不過上面的分析只是我們看到代碼的猜測而已,我們來看一下具體的存放過程.
 

5.2: cmdline的存放

Cmdline的存放操作是在trace_save_cmdline()中完成的,代碼如下:
static void trace_save_cmdline(struct task_struct *tsk)
{
     unsigned pid, idx;
 
     if (!tsk->pid || unlikely(tsk->pid > PID_MAX_DEFAULT))
         return;
 
     /*
      * It's not the end of the world if we don't get
      * the lock, but we also don't want to spin
      * nor do we want to disable interrupts,
      * so if we miss here, then better luck next time.
      */
     if (!__raw_spin_trylock(&trace_cmdline_lock))
         return;
 
     idx = map_pid_to_cmdline[tsk->pid];
     if (idx == NO_CMDLINE_MAP) {
         idx = (cmdline_idx + 1) % SAVED_CMDLINES;
 
         /*
          * Check whether the cmdline buffer at idx has a pid
          * mapped. We are going to overwrite that entry so we
          * need to clear the map_pid_to_cmdline. Otherwise we
          * would read the new comm for the old pid.
          */
         pid = map_cmdline_to_pid[idx];
         if (pid != NO_CMDLINE_MAP)
              map_pid_to_cmdline[pid] = NO_CMDLINE_MAP;
 
         map_cmdline_to_pid[idx] = tsk->pid;
         map_pid_to_cmdline[tsk->pid] = idx;
 
         cmdline_idx = idx;
     }
 
     memcpy(&saved_cmdlines[idx], tsk->comm, TASK_COMM_LEN);
 
     __raw_spin_unlock(&trace_cmdline_lock);
}
從上面的入口參數的檢測可以看出,它不能夠存放pid=0的情況,即然這樣爲什麼面要將map_pid_to_cmdline[]要定義成PID_MAX_DEFAULT+1大小, PID_MAX_DEFAULT大小不就夠了麼?
同時,在操作的過程中必須要持有trace_cmdline_lock鎖,但是如果這個鎖已經持有了,就不再等待,而是退出.可能是因爲不想在這個過程中耽誤太多的時候.
 
從上面的代碼中可以看也, map_pid_to_cmdline[]的作用正如我們之前所猜測的: 每一個進程以pid爲鍵值對應map_pid_to_cmdline[]中的一項, 而該項存放的是在saved_cmdlines中的對應項.
舉個例個, pid爲15, 假設map_pid_to_cmdline[15] = 30, 那麼它的cmdline就存放在saved_cmdlines[30][].
 
如果對應在map_pid_to_cmdline[]數組中的值爲NO_CMDLINE_MAP, 這個值是我們之前初始化的狀態,說明並沒有爲這個pid建立映射。那就必須要爲它分配一個在save_cmdlines中的存放位置了。怎麼樣存放呢?
首先,我們必須要考慮到這個問題,因爲只能存放SAVED_CMDLINES個進程的cmdline,在空間都存放滿了之前,我們肯定要將最老的值移出。在這個過程中map_cmdline_to_pid[]和cmdline_idx就派上用場了。
 
其實map_cmdline_to_pid中的第一項都表示save_cmdlines對應項的狀態。舉個例子,假設我們想知道save_cmdlines[13]的存放狀態,這些狀態都在map_cmdline_to_pid[13]中。
如果map_cmdline_to_pid[13]中的值爲NO_CMDLINE_MAP,說明save_cmdlines[13]就是空閒的,否則,假設map_cmdline_to_pid[13]存放的值爲99,那就表示save_cmdline[99]存放的是pid爲99的進程的cmdline. 當然也會有map_pid_to_cmdline[99] == 13.
 
如下圖所示:
 
 
 
Cmdline_idx在這個過程中是從上到下遞增指向map_pid_to_cmdline[]中的項。如果有衝突,就將其替換,因爲這是存放在save_cmdlines中最老的數據了,並且將它對應在map_pid_to_cmdline[]中的項設爲NO_CMDLINE_MAP,表示末曾爲它建立映射。
 

5.3: cmdline的查找

Cmdline的查找過程很容易了,就是找到進程對應在map_pid_to_cmdline中的值,如果有映射就找到在save_cmdlines中的對應項就可以了。它的操作是在trace_find_cmdline()中完成的,代碼如下:
void trace_find_cmdline(int pid, char comm[])
{
     unsigned map;
 
     if (!pid) {
         strcpy(comm, "<idle>");
         return;
     }
 
     if (pid > PID_MAX_DEFAULT) {
         strcpy(comm, "<...>");
         return;
     }
 
     preempt_disable();
     __raw_spin_lock(&trace_cmdline_lock);
     map = map_pid_to_cmdline[pid];
     if (map != NO_CMDLINE_MAP)
         strcpy(comm, saved_cmdlines[map]);
     else
         strcpy(comm, "<...>");
 
     __raw_spin_unlock(&trace_cmdline_lock);
     preempt_enable();
}
這段代碼比較簡單,就不加詳細分析了。
 

5.4: cmdline的保存時機

那究竟是在什麼時候纔將進程的cmdline保存呢?
實際上,kernel提供了兩個函數用來啓動記錄cmdline和停止記錄.它們分別是tracing_start_cmdline_record()和tracing_stop_cmdline_record()但是它們是和一個名爲”sched_switch”的tracer相關的。在這裏就不詳細的它的執行。它實際上就是在發生進程切換的事件的過程中插入了一些hook函數,然後在這些hook函數再記錄下pid對應的cmdline.
 

六: 小結

Trace的框架並不複雜,複雜的是框架中還跟具體的tracer有千絲萬縷的聯繫,所以在分析代碼時,涉及到具體tracer的部份全部都跳過去了,這些部份只能等分析完對應的tracer操作之後再自行回來對照。另外,trace提供的衆多全局標誌也比較繁鎖,在本文中並沒有對這些標誌一一進行分析,相關用途可查看相關源碼,這部分源碼都很簡單,自行閱讀即可。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章