Chinaunix首页 | 论坛 | 博客
  • 博客访问: 379560
  • 博文数量: 73
  • 博客积分: 3574
  • 博客等级: 中校
  • 技术积分: 1503
  • 用 户 组: 普通用户
  • 注册时间: 2010-03-26 11:17
文章分类

全部博文(73)

文章存档

2012年(14)

2011年(15)

2010年(44)

分类: LINUX

2011-12-07 19:41:54

    很早之前写了篇文章分析的是linux提供的netconsole机制,netconsole能够实现dmesg信息的网络传输,而最近在看淘宝内核组文章的时候,看到他们提到的netoops机制,这也是一种基于网络的msg信息传输,不过只针对宕机后所产生的oops信息进行转储,并且为了便于调试丰富了msg信息的格式和内容。

    比较对这个netoops感兴趣,看文章它也是通过对netconsole机制的一些修改而实现,不过因为有着自己的特点,所以还是作为一个独立的分支在更新。不过一直没有搞到对应的patch,因此也一直无缘庐山真面目。
    
    研究netoops的主要一点就是对现有netconsole机制的不满,主要有两点,1.每次printk时都会调用通过console把消息发出,而如果我们只对宕机信息感兴趣的话,会出现大量的垃圾数据;2.对于整个系统宕掉的情况,由于netconsole使用了工作队列完成发包,因此在这种情况下,对应的工作队列会因为无法得到调度而没办法完成数据传输,获取不到所需的数据。
    主要问题就是这两个,在获取不到netoops的代码情况下,便打算自己实现类似的功能,怎么实现呢,最简单的就是去阉割netconsole了,针对这两个问题有两个解决方法,对第一个,在每次打印宕机信息的流程之前设定标志位,在把数据发送到netconsole时对标志位进行判断,只有在发生了oops时才发送数据;对于第二个,既然工作队列不行,那就自己搞吧,在netconsole理直接把封装好的数据包调用网卡驱动的发包函数发送出去。

    就这样,勉勉强看起来像netoops的样子了,不过这个方法最主要的就是每一句printk都会导致一个数据包的发送,抓包会看到有非常多的小包,而再系统宕机后网卡发送队列不会更新,如果队列满了,后面的数据包就没办法发送了(试验中遇到的一个最大的问题,调了半天才找到原因)。当然,还有其他一些隐患,比如基于slab的skb缓存可能不可靠等等,有机会后续会进行修改。

    罗里罗嗦的说了这么多,才到了今天的正题,kmsg_dump,在看代码的时候偶尔看到它,细看下却发现它正好能够满足netoops的几个要求。
  1. void kmsg_dump(enum kmsg_dump_reason reason)
  2. {
  3.     unsigned long end;
  4.     unsigned chars;
  5.     struct kmsg_dumper *dumper;
  6.     const char *s1, *s2;
  7.     unsigned long l1, l2;
  8.     unsigned long flags;

  9.     /* Theoretically, the log could move on after we do this, but
  10.      there's not a lot we can do about that. The new messages
  11.      will overwrite the start of what we dump. */
  12.     spin_lock_irqsave(&logbuf_lock, flags);
  13.     end = log_end & LOG_BUF_MASK;
  14.     chars = logged_chars;
  15.     spin_unlock_irqrestore(&logbuf_lock, flags);

  16.     if (chars > end) {
  17.         s1 = log_buf + log_buf_len - chars + end;
  18.         l1 = chars - end;

  19.         s2 = log_buf;
  20.         l2 = end;
  21.     } else {
  22.         s1 = "";
  23.         l1 = 0;

  24.         s2 = log_buf + end - chars;
  25.         l2 = chars;
  26.     }

  27.     rcu_read_lock();
  28.     list_for_each_entry_rcu(dumper, &dump_list, list)
  29.         dumper->dump(dumper, reason, s1, l1, s2, l2);
  30.     rcu_read_unlock();
  31. }
 代码逻辑比较简答,就不加注释了,主要工作就是对内核中的dump_list链表上的kmsg_dumper结构进行遍历,执行每一个结构的dump函数。
 这里比较重要的一点就是其传递给dump函数的参数是从printk的缓冲区中获取的,其中s1,l1,s2,l2按照注释,where s1 (length l1) contains the older messages and s2 (length l2) contains the newer,其实就是printk上次缓冲与这次保存的所有数据(具体还有一些疑问。。),因此能够通过这两个参数一次性获取所有dmesg信息。
  而kmsg_dump函数的调用时机也比较特别,只有在诸如panic、kexec等流程执行时才会被调用到,参数中的reason就是说的这个原因,内核里定义一共有如下几个原因:
  1. enum kmsg_dump_reason {
  2.     KMSG_DUMP_OOPS,
  3.     KMSG_DUMP_PANIC,
  4.     KMSG_DUMP_KEXEC,
  5.     KMSG_DUMP_RESTART,
  6.     KMSG_DUMP_HALT,
  7.     KMSG_DUMP_POWEROFF,
  8.     KMSG_DUMP_EMERG,
  9. };
因此,可以通过对调用reason的判断,完成特定的一些操作。

使用kmsg_dump的过程主要在于数据结构kmsg_dumper的构建。
  1. struct kmsg_dumper {
  2.     void (*dump)(struct kmsg_dumper *dumper, enum kmsg_dump_reason reason,
  3.             const char *s1, unsigned long l1,
  4.             const char *s2, unsigned long l2);
  5.     struct list_head list;
  6.     int registered;
  7. };
  这是一个kmsg_dumper的结构,主要在其中定义了一个回调函数,我们可以在回调函数中对自己感兴趣的reason进行过滤,并且通过kmsg_dump_register以及kmsg_dump_unregister进行注册,就能够完成需要信息的收集。

  由于这个函数的调用一般都是在系统发生异常时,一般情况下也没办法对这些数据做多余的处理,不过如果把上文提到的发包函数移到这里,正好能够实现oops时dmsg信息的发送,而且一次发送一整段的数据,解决了大量小包的问题,也免得自己多余的操作,这样一来,整个功能看起来就更像netoops了,哈哈。

阅读(5686) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~