很早之前写了篇文章分析的是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的几个要求。
- void kmsg_dump(enum kmsg_dump_reason reason)
-
{
-
unsigned long end;
-
unsigned chars;
-
struct kmsg_dumper *dumper;
-
const char *s1, *s2;
-
unsigned long l1, l2;
-
unsigned long flags;
-
-
/* Theoretically, the log could move on after we do this, but
-
there's not a lot we can do about that. The new messages
-
will overwrite the start of what we dump. */
-
spin_lock_irqsave(&logbuf_lock, flags);
-
end = log_end & LOG_BUF_MASK;
-
chars = logged_chars;
-
spin_unlock_irqrestore(&logbuf_lock, flags);
-
-
if (chars > end) {
-
s1 = log_buf + log_buf_len - chars + end;
-
l1 = chars - end;
-
-
s2 = log_buf;
-
l2 = end;
-
} else {
-
s1 = "";
-
l1 = 0;
-
-
s2 = log_buf + end - chars;
-
l2 = chars;
-
}
-
-
rcu_read_lock();
-
list_for_each_entry_rcu(dumper, &dump_list, list)
-
dumper->dump(dumper, reason, s1, l1, s2, l2);
-
rcu_read_unlock();
-
}
代码逻辑比较简答,就不加注释了,主要工作就是对内核中的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就是说的这个原因,内核里定义一共有如下几个原因:
- enum kmsg_dump_reason {
-
KMSG_DUMP_OOPS,
-
KMSG_DUMP_PANIC,
-
KMSG_DUMP_KEXEC,
-
KMSG_DUMP_RESTART,
-
KMSG_DUMP_HALT,
-
KMSG_DUMP_POWEROFF,
-
KMSG_DUMP_EMERG,
-
};
因此,可以通过对调用reason的判断,完成特定的一些操作。
使用kmsg_dump的过程主要在于数据结构kmsg_dumper的构建。
- struct kmsg_dumper {
-
void (*dump)(struct kmsg_dumper *dumper, enum kmsg_dump_reason reason,
-
const char *s1, unsigned long l1,
-
const char *s2, unsigned long l2);
-
struct list_head list;
-
int registered;
-
};
这是一个kmsg_dumper的结构,主要在其中定义了一个回调函数,我们可以在回调函数中对自己感兴趣的reason进行过滤,并且通过kmsg_dump_register以及kmsg_dump_unregister进行注册,就能够完成需要信息的收集。
由于这个函数的调用一般都是在系统发生异常时,一般情况下也没办法对这些数据做多余的处理,不过如果把上文提到的发包函数移到这里,正好能够实现oops时dmsg信息的发送,而且一次发送一整段的数据,解决了大量小包的问题,也免得自己多余的操作,这样一来,整个功能看起来就更像netoops了,哈哈。
阅读(5686) | 评论(0) | 转发(0) |