Linux内核跟踪之ring buffer的实现.docx
《Linux内核跟踪之ring buffer的实现.docx》由会员分享,可在线阅读,更多相关《Linux内核跟踪之ring buffer的实现.docx(49页珍藏版)》请在冰豆网上搜索。
Linux内核跟踪之ringbuffer的实现
Linux内核跟踪之ringbuffer的实现
一:
前言
Ringbuffer是整个trace系统使用缓存管理的一种方式,由于trace可能在内核运行的任何时候发生,这种kernel的不确定状态决定了ringbuffer的写操作中不能有任何引起睡眠的操作,而且ringbuffer的操作频率极高,所以在ringbuffer实现里有很多高效的方式来处理多处理器,读写同步的机制.理解ringbuffer是我们理解整个kerneltrace的基础.本文分析的源代码版本为linuxkernel2.6.30,分析的代码基本位于kernel/trace/ring_buffer.c中.另外,为了描述的方便,下文ringbuffer用RB来代替.
二:
ringbuffer的基本数据结构
在深入到代码之前,我们先来看一下RB所用到的几个基本的数据结构,这样我们对RB就会有一个全局性的了解.整个RB的数据结构框架如下所示:
2010-8-1317:
29上传
下载附件(35.65KB)
ringbuffer用structring_buffer来表示,数据结构定义如下:
structring_buffer{
/*RB中的页面数*/
unsigned pages;
/*RB的标志,目前只有RB_FL_OVERWRITE可用*/
unsigned flags;
/*ringbuffer中包含的cpu个数*/
int cpus;
/*整个ringbuffer的禁用标志,用原子操作了防止竞争*/
atomic_t record_disabled;
/*cpu位图*/
cpumask_var_t cpumask;
/*RB访问锁*/
structmutex mutex;
/*CPU的缓存区页面,每个CPU对应一项*/
structring_buffer_per_cpu **buffers;
#ifdefCON**_HOTPLUG_CPU
/*多CPU情况下的cpuhotplug通知链表*/
structnotifier_block cpu_notify;
#endif
/*RB所用的时间,用来计数时间戳*/
u64 (*clock)(void);
}
在RB的操作中,我们可以禁止全局的RB操作,例如,完全禁用掉Trace功能后,整个RB都是不允许再操做的,这时,就可以将原子变量record_disabled加1.相反的,如果启用的话,将其减1即可.只有当record_disabled的值等于0时,才允许操作RB.
同时,有些时候,要对RB的一些数据进行更新,比如,我要重新设置一下RB的缓存区大小,这都需要串行操作,因此,在ring_buffer结构中有mutex成员,用来避免这些更改RB的操作的竞争.
每个cpu的缓存区结构为:
structring_buffer_per_cpu{
/*该cpubuffer所在的CPU*/
int cpu;
/*cpubuffer所属的RB*/
structring_buffer *buffer;
/*读锁,用了避免读者的操行操作,有时在
*写者切换页面的时候,也需要持有此锁
*/
spinlock_t reader_lock;/*serializereaders*/
raw_spinlock_t lock;
structlock_class_key lock_key;
/*cpubuffer的页面链表*/
structlist_head pages;
/*起始读位置*/
structbuffer_page *head_page;/*readfromhead*/
/*写位置*/
structbuffer_page *tail_page;/*writetotail*/
/*提交位置,只有当被写的页面提交过后
*才允许被读
*/
structbuffer_page *commit_page; /*committedpages*/
/*reader页面,用来交换读页面*/
structbuffer_page *reader_page;
unsignedlong nmi_dropped;
unsignedlong commit_overrun;
unsignedlong overrun;
unsignedlong read;
local_t entries;
/*最新的页面commit时间*/
u64 write_stamp;
/*最新的页面read时间*/
u64 read_stamp;
/*cpubuffer的禁用启用标志*/
atomic_t record_disabled;
}
首先,对每一个cpu的操作限制是由ring_buffer_per_cpu->record_disabled来实现的.同ring_buffer一样,禁用加1,启用减1.
从上图的全局结构关联图中,我们也可以看到,每个cpu都有一系列的页面,这样页面都链入在pages中.
该页面的结构如下:
structbuffer_page{
/*用来形成链表*/
structlist_headlist; /*listofbufferpages*/
/*写的位置*/
local_t write; /*indexfornextwrite*/
/*读的位置*/
unsigned read; /*indexfornextread*/
/*页面中有多少项数据*/
local_t entries; /*entriesonthispage*/
structbuffer_data_page*page; /*Actualdatapage*/
};
具体的缓存区是由structbuffer_data_page指向的,实际上,它是具体页面的管理头部,结构如下:
structbuffer_data_page{
/*页面第一次被写时的时间戳*/
u64 time_stamp; /*pagetimestamp*/
/*提交的位置*/
local_t commit; /*writecommittedindex*/
/*用来存放数据的缓存区*/
unsignedchar data[]; /*dataofbufferpage*/
};
这里就有一个疑问了,为什么提交页面要放到structbuffer_date_page中,而不放到structbuffer_page呢?
三:
ringbuffer的初始化
Ringbuffer的初始化函数为ring_buffer_alloc().代码如下:
structring_buffer*ring_buffer_alloc(unsignedlongsize,unsignedflags)
{
structring_buffer*buffer;
intbsize;
intcpu;
/*Paranoid!
Optimizesoutwhenalliswell*/
/*如果structbuffer_page的大小超过了structpage的大小,编译时会报错
*因为ring_buffer_page_too_big()其实并不存在.
*/
if(sizeof(structbuffer_page)>sizeof(structpage))
ring_buffer_page_too_big();
/*keepitinitsowncacheline*/
/*allocandinitstructring_buffer*/
buffer=kzalloc(ALIGN(sizeof(*buffer),cache_line_size()),
GFP_KERNEL);
if(!
buffer)
returnNULL;
/*initcpumask*/
if(!
alloc_cpumask_var(&buffer->cpumask,GFP_KERNEL))
gotofail_free_buffer;
/*BUF_PAGE_SIZEmeansthedatasizeofperpage,
*size/BUF_PAGE_SIZEcancalculatepagenumberofpercpu.
*/
buffer->pages=DIV_ROUND_UP(size,BUF_PAGE_SIZE);
buffer->flags=flags;
/*buffer->clockisthetimestapoflocalcpu*/
buffer->clock=trace_clock_local;
/*needatleasttwopages*/
if(buffer->pages==1)
buffer->pages++;
/*
*Incaseofnon-hotplugcpu,ifthering-bufferisallocated
*inearlyinitcall,itwillnotbenotifiedofsecondarycpus.
*Inthatoffcase,weneedtoallocateforallpossiblecpus.
*/
#ifdefCON**_HOTPLUG_CPU
get_online_cpus();
cpumask_copy(buffer->cpumask,cpu_online_mask);
#else
cpumask_copy(buffer->cpumask,cpu_possible_mask);
#endif
/*numberofcpu*/
buffer->cpus=nr_cpu_ids;
/*allocandinitbufferforpercpu,Notice:
buffer->buffersisadoublepointer*/
bsize=sizeof(void*)*nr_cpu_ids;
buffer->buffers=kzalloc(ALIGN(bsize,cache_line_size()),
GFP_KERNEL);
if(!
buffer->buffers)
gotofail_free_cpumask;
for_each_buffer_cpu(buffer,cpu){
buffer->buffers[cpu]=
rb_allocate_cpu_buffer(buffer,cpu);
if(!
buffer->buffers[cpu])
gotofail_free_buffers;
}
#ifdefCON**_HOTPLUG_CPU
buffer->cpu_notify.notifier_call=rb_cpu_notify;
buffer->cpu_notify.priority=0;
register_cpu_notifier(&buffer->cpu_notify);
#endif
put_online_cpus();
mutex_init(&buffer->mutex);
returnbuffer;
fail_free_buffers:
for_each_buffer_cpu(buffer,cpu){
if(buffer->buffers[cpu])
rb_free_cpu_buffer(buffer->buffers[cpu]);
}
kfree(buffer->buffers);
fail_free_cpumask:
free_cpumask_var(buffer->cpumask);
put_online_cpus();
fail_free_buffer:
kfree(buffer);
returnNULL;
}
结合我们上面分析的数据结构,来看这个函数,应该很简单,首先,我们在这个函数中遇到的第一个疑问是:
为什么RB至少需要二个页面呢?
我们来假设一下只有一个页面的情况,RB开始写,因为head和tail是重合在一起的,当写完一个页面的时候,tail后移,因为只有一个页面,还是会指向这个页面,这样还是跟head重合在一起,如果带有RB_FL_OVERWRITE标志的话,head会后移试图清理这个页面,但后移之后还是指向这个页面,也就是说tail跟head还是会重合.假设此时有读操作,读完了head的数据,造成head后移,同样head和tail还是重合在一起.因此就造成了,第一次写完这个页面,就永远无法再写了,因为这时候永远都是一个满的状态.
也就是说,这里需要两个页面是为了满足缓存区是否满的判断,即tail->next==head
然后,我们面临的第二个问题是,RB怎么处理hotplugcpu的情况呢?
看下面的代码:
/*numberofcpu*/
buffer->cpus=nr_cpu_ids;
/*allocandinitbufferforpercpu,Notice:
buffer->buffersisadoublepointer*/
bsize=sizeof(void*)*nr_cpu_ids;
buffer->buffers=kzalloc(ALIGN(bsize,cache_line_size()),
GFP_KERNEL);
从上面的代码看到,在初始化RB的时候,它为每个可能的CPU都准备了一个“框”,下面来看下这个“框”的初始化:
for_each_buffer_cpu(buffer,cpu){
buffer->buffers[cpu]=
rb_allocate_cpu_buffer(buffer,cpu);
if(!
buffer->buffers[cpu])
gotofail_free_buffers;
}
从此可以看到,它只为当时存在的CPU分配了缓存区.
到这里,我们大概可以猜到怎么处理hotplugcpu的情况了:
在有CPU加入时,为这个CPU对应的“框”对应分配内存,在CPU拨除或掉线的情况下,释放掉该CPU对应的内存.到底是不是跟我们所想的一样呢?
我们继续看代码:
#ifdefCON**_HOTPLUG_CPU
buffer->cpu_notify.notifier_call=rb_cpu_notify;
buffer->cpu_notify.priority=0;
register_cpu_notifier(&buffer->cpu_notify);
#endif
如上代码片段,它为hotplugCPU注册了一个notifier,它对优先级是0,对应的处理函数是rb_cpu_notify,代码如下:
staticintrb_cpu_notify(structnotifier_block*self,
unsignedlongaction,void*hcpu)
{
structring_buffer*buffer=
container_of(self,structring_buffer,cpu_notify);
longcpu=(long)hcpu;
switch(action){
/*CPU处理active的notify*/
caseCPU_UP_PREPARE:
caseCPU_UP_PREPARE_FROZEN:
/*如果cpu已经位于RB的cpu位图,说明已经为其准备好了
*缓存区,直接退出
*/
if(cpu_isset(cpu,*buffer->cpumask))
returnNOTIFY_OK;
/*否则,它是一个新的CPU,则为其分配缓存,如果
*分配成功,则将其在cpu位图中置位*/
buffer->buffers[cpu]=
rb_allocate_cpu_buffer(buffer,cpu);
if(!
buffer->buffers[cpu]){
WARN(1,"failedtoallocateringbufferonCPU%ld\n",
cpu);
returnNOTIFY_OK;
}
smp_wmb();
cpu_set(cpu,*buffer->cpumask);
break;
caseCPU_DOWN_PREPARE:
caseCPU_DOWN_PREPARE_FROZEN:
/*
*Donothing.
* Ifweweretofreethebuffer,thentheuserwould
* loseanytracethatwasinthebuffer.
*/
/*如果是CPU处于deactive的notify,则不需要将其占的缓存
*释放,因为一旦释放,我们将失去该cpu上的trace信息*/
break;
default:
break;
}
returnNOTIFY_OK;
}
首先,RB的结构体中内嵌了structnotifier_block,所以,我们利用其位移差就可以取得对应的RB结构,上面的代码比较简单,不过,与我们之前的估计有点差别,即,在CPU处理deactive状态的时候,并没有将其对应的缓存释放,这是为了避免丢失该CPU上的trace信息.
接下来我们看一下对每个CPU对应的缓存区的初始化,它是在rb_allocate_cpu_buffer()中完成的,代码如下:
staticstructring_buffer_per_cpu*
rb_allocate_cpu_buffer(structring_buffer*buffer,intcpu)
{
structring_buffer_per_cpu*cpu_buffer;
structbuffer_page*bpage;
unsignedlongaddr;
intret;
/*allocandinitastructring_buffer_per_cpu*/
cpu_buffer=kzalloc_node(ALIGN(sizeof(*cpu_buffer),cache_line_size()),
GFP_KERNEL,cpu_to_node(cpu));
if(!
cpu_buffer)
returnNULL;
cpu_buffer->cpu=cpu;
cpu_buffer->buffer=buffer;
spin_lock_init(&cpu_buffer->reader_lock);
cpu_buffer->lock=(raw_spinlock_t)__RAW_SPIN_LOCK_UNLOCKED;
INIT_LIST_HEAD(&cpu_buffer->pages);
/*allocandinitcpubuffer->reader_page*/
bpage=kzalloc_node(ALIGN(sizeof(*bpage),cache_line_size()),
GFP_KERNEL,cpu_to_node(cpu));
if(!
bpage)
gotofail_free_buffer;
cpu_buffer->reader_page=bpage;
addr=__get_free_page(GFP_KERNEL);
if(!
addr)
gotofail_free_reader;
bpage->page=(void*)addr;
rb_init_page(bpage->page);
INIT_LIST_HEAD(&cpu_buffer->reader_page->list);
/*allocandinitthepagelist, head_page,tail_pageandcommit_pageareallpointtothefistpage*/
ret=rb_allocate_pages(cpu_buffer,buffer->pages);
if(ret<0)
gotofail_free_reader;
cpu_buffer->head_page
=list_entry(cpu_buffer->pages.next,structbuffer_page,list);
cpu_buffer->tail_page=cpu_buffer->commit_page=cpu_buffer->head_page;
returncpu_buffer;
fail_free_reader:
free_buffer_page(cpu_buffer->reader_page);
fail_free_buffer:
kfree(cpu_buffer);