Linux内核跟踪之ring buffer的实现.docx

上传人:b****6 文档编号:6004667 上传时间:2023-01-02 格式:DOCX 页数:49 大小:73.62KB
下载 相关 举报
Linux内核跟踪之ring buffer的实现.docx_第1页
第1页 / 共49页
Linux内核跟踪之ring buffer的实现.docx_第2页
第2页 / 共49页
Linux内核跟踪之ring buffer的实现.docx_第3页
第3页 / 共49页
Linux内核跟踪之ring buffer的实现.docx_第4页
第4页 / 共49页
Linux内核跟踪之ring buffer的实现.docx_第5页
第5页 / 共49页
点击查看更多>>
下载资源
资源描述

Linux内核跟踪之ring buffer的实现.docx

《Linux内核跟踪之ring buffer的实现.docx》由会员分享,可在线阅读,更多相关《Linux内核跟踪之ring buffer的实现.docx(49页珍藏版)》请在冰豆网上搜索。

Linux内核跟踪之ring buffer的实现.docx

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);

 

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 自然科学 > 化学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1