http://linux.chinaitlab.com/kernel/821106.html
一: 前言
本文主要是对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提供的众多全局标志也比较繁锁,在本文中并没有对这些标志一一进行分析,相关用途可查看相关源码,这部分源码都很简单,自行阅读即可。