kdb代码分析(三)

来源:互联网 发布:高效能程序员的修炼 编辑:程序博客网 时间:2024/04/27 17:19

Linux中有些模块,你看明白它怎么初始化的你基本上就能明白它是怎么工作了,比如usb-storage,以及usb hub driver,但有些模块就没有这么简单了,就比如uhci/ehci,就比如kdb.初始化完了之后故事才刚刚拉开帷幕,如果拿近期百家讲坛热播的纪连海老师讲的李连英的故事对比,那么现在也就相当于李连英公公刚刚进宫,刚刚开始他那伟大的太监生涯.

usb-storage那样的模块,你可以很清楚它的结构,从哪里开始到哪里结束,整个就是一条直线.kdb就不一样了,它初始化完成了之后,就将准备应对多种情况了,比如你进入kdb,这就有多种情形,你可以直接调用相关的宏进入kdb,也可以设置断点来进入kdb,你可以按pause键进入kdb,也可以在serial console上按ctrl-a进入kdb,还可能是系统崩溃了自动进入kdb.总而言之有诸多的可能,所以就要有相应的代码来应付.下面我们首先就先来看一下,从串行终端上按了ctrl-a之后,为什么就可以进入kdb.

很明显,这里牵涉到了serial console的驱动,更准确地说其实是Intel 8250串口芯片驱动.虽然串口芯片很多,但是Intel 8250无疑是最有名的,大多数服务器上的串口都是8250芯片. kdb-v4.4-2.6.22-common-1这个patch中说了,以下四个文件是作了修改的.

     17  drivers/serial/8250.c         |   53

     18  drivers/serial/8250_early.c   |   34

     19  drivers/serial/sn_console.c   |   73

     25  include/linux/console.h       |    5

很长一段时间我一直困惑,计算机怎么知道我按了”control-a”,后来才明白,键盘上的control-a实际上对应的是ASCII码中的001.(control-b对应002,control-c对应003,…另外,control-@对应000)所以从键盘驱动来说,它就把这个组合键当作一个字符来处理.

kdbpatchdrivers/serial/8250.c中加了这么一段:

   3295 Index: linux/drivers/serial/8250.c

   3296 ===================================================================

   3297 --- linux.orig/drivers/serial/8250.c

   3298 +++ linux/drivers/serial/8250.c

   3299 @@ -45,6 +45,19 @@

   3300  #include <asm/irq.h>

   3301

   3302  #include "8250.h"

   3303 +#include <linux/kdb.h>

   3304 +#ifdef CONFIG_KDB

   3305 +/*

   3306 + * kdb_serial_line records the serial line number of the first serial console.

   3307 + * NOTE: The kernel ignores characters on the serial line unless a user space

   3308 + * program has opened the line first.  To enter kdb before user space has opened

   3309 + * the serial line, you can use the 'kdb=early' flag to lilo and set the

   3310 + * appropriate breakpoints.

   3311 + */

   3312 +

   3313 +static int  kdb_serial_line = -1;

   3314 +static const char *kdb_serial_ptr = kdb_serial_str;

   3315 +#endif /* CONFIG_KDB */

   3316

   3317  /*

   3318   * Configuration:

   3319 @@ -1287,6 +1300,20 @@ receive_chars(struct uart_8250_port *up,

   3320

   3321         do {

   3322                 ch = serial_inp(up, UART_RX);

   3323 +#ifdef CONFIG_KDB

   3324 +               if ((up->port.line == kdb_serial_line) && kdb_on == 1) {

   3325 +                   if (ch == *kdb_serial_ptr) {

   3326 +                       if (!(*++kdb_serial_ptr)) {

   3327 +                           atomic_inc(&kdb_8250);

   3328 +                           kdb(KDB_REASON_KEYBOARD, 0, get_irq_regs());

   3329 +                           atomic_dec(&kdb_8250);

   3330 +                           kdb_serial_ptr = kdb_serial_str;

   3331 +                           break;

   3332 +                       }

   3333 +                   } else

   3334 +                       kdb_serial_ptr = kdb_serial_str;

   3335 +               }

   3336 +#endif /* CONFIG_KDB */

   3337                 flag = TTY_NORMAL;

   3338                 up->port.icount.rx++;

   3339

这里的kdb_serial_str其实就是control-a,或者说用ascii码的形式表示为”/001”.这里的receive_char很明显,就是串行终端接收字符时调用的函数.ch就是接收到的字符,如果它是control-a,那么3328添加的代码就会执行,换言之,kdb()函数会被调用.

不过这里我们需要注意的是,当我还是青春期的时候,当我还在看琼瑶剧的时候,当我还迷恋快乐大本营的时候,人们要从串行终端进入kdb得按control-a,但后来人们发现control-a进入不了kdb,要进入kdb得按另外的键,这就是escape键后接KDB.

关于这一点,原因是kdb_serial_str这个字符串经过了修改,曾几何时,它是被定义为/001,但现在你会发现,这个字符串被定义为”/eKDB”,”/e”实际上对应你敲击的键盘就是escape.关于这个字符串的定义,我们可以从patch里面找到:

   8639 +/*

   8640 + * kdb_serial_str is the sequence that the user must enter on a serial

   8641 + * console to invoke kdb.  It can be a single character such as "/001"

   8642 + * (control-A) or multiple characters such as "/eKDB".  NOTE: All except the

   8643 + * last character are passed through to the application reading from the serial

   8644 + * console.

   8645 + *

   8646 + * I tried to make the sequence a CONFIG_ option but most of CML1 cannot cope

   8647 + * with '/' in strings.  CML2 would have been able to do it but we lost CML2.

   8648 + * KAO.

   8649 + */

   8650 +const char kdb_serial_str[] = "/eKDB";

   8651 +EXPORT_SYMBOL(kdb_serial_str);

所以结合上面的代码来看,kdb_serial_str表示一个const的字符串,kdb_serial_ptr则是一个char型指针,指针开始指向kdb_serial_str,然后不停的游荡,每次串行终端上有输入,换言之,ch有值,就拿它和kdb_serial_ptr所指向的字符相比较,如果相同就令kdb_serial_ptr指向下一个字符,然后接着如果你继续输入,就继续比较,直到比较完了以后发现,你输入的恰恰就是<escape>KDB,那么调用kdb(),从而进入kdb.下面是效果图:(在串行终端上KB都没有回显出来,只有D回显了.)

[root@localhost ~]# D

Entering kdb (current=0xffffffff805563a0, pid 0) due to Keyboard Entry

kdb>

不过像我这种习惯了按control-akdb的人,一般会把这里/eKDB手工改为/001.

知道了serial console这边是如何进入kdb,我们再来看本地键盘,在这里只要你按Pause键就可以进入kdb,这又是为什么呢?看人家的patch改了什么:

   3182 Index: linux/drivers/char/keyboard.c

   3183 ===================================================================

   3184 --- linux.orig/drivers/char/keyboard.c

   3185 +++ linux/drivers/char/keyboard.c

   3186 @@ -40,6 +40,9 @@

   3187  #include <linux/sysrq.h>

   3188  #include <linux/input.h>

   3189  #include <linux/reboot.h>

   3190 +#ifdef CONFIG_KDB

   3191 +#include <linux/kdb.h>

   3192 +#endif /* CONFIG_KDB */

   3193

   3194  extern void ctrl_alt_del(void);

   3195

   3196 @@ -1138,6 +1141,13 @@ static void kbd_keycode(unsigned int key

   3197                         if (keycode < BTN_MISC && printk_ratelimit())

   3198                                 printk(KERN_WARNING "keyboard.c: can't emulate rawmode for keycode %d/n", keycode);

   3199

   3200 +#ifdef CONFIG_KDB

   3201 +       if (down && !rep && keycode == KEY_PAUSE && kdb_on == 1) {

   3202 +               kdb(KDB_REASON_KEYBOARD, 0, get_irq_regs());

   3203 +               return;

   3204 +       }

   3205 +#endif /* CONFIG_KDB */

   3206 +

   3207  #ifdef CONFIG_MAGIC_SYSRQ             /* Handle the SysRq Hack */

   3208         if (keycode == KEY_SYSRQ && (sysrq_down || (down == 1 && sysrq_alt))) {

   3209                 if (!sysrq_down) {

很显然,就是修改drivers/char/keyboard.c,这就是键盘驱动,我们看到会比较keycodeKEY_PAUSE,如果相同,就说明你输入的是pause,于是3202行这里我们看到kdb()再一次被调用.

这样我们就明白了为什么从串行终端按control-a以及从本地键盘按pause键会触发kdb.

但这些方式都太直接了,而且是你主动要进kdb,颇有一种纸上谈兵的味道.须知有的时候,进入kdb并不是你主观上期望的,往往是系统崩溃的时候自动进入的,这又是怎么回事儿呢?

来看一个关键的函数,来自kernel/panic.c:

     60 NORET_TYPE void panic(const char * fmt, ...)

     61 {

     62         long i;

     63         static char buf[1024];

     64         va_list args;

     65 #if defined(CONFIG_S390)

     66         unsigned long caller = (unsigned long) __builtin_return_address(0);

     67 #endif

     68

     69         /*

     70          * It's possible to come here directly from a panic-assertion and not

     71          * have preempt disabled. Some functions called from here want

     72          * preempt to be disabled. No point enabling it later though...

     73          */

     74         preempt_disable();

     75

     76         bust_spinlocks(1);

     77         va_start(args, fmt);

     78         vsnprintf(buf, sizeof(buf), fmt, args);

     79         va_end(args);

     80         printk(KERN_EMERG "Kernel panic - not syncing: %s/n",buf);

     81         bust_spinlocks(0);

     82

     83         /*

     84          * If we have crashed and we have a crash kernel loaded let it handle

     85          * everything else.

     86          * Do we want to call this before we try to display a message?

     87          */

     88         crash_kexec(NULL);

     89

     90 #ifdef CONFIG_SMP

     91         /*

     92          * Note smp_send_stop is the usual smp shutdown function, which

     93          * unfortunately means it may not be hardened to work in a panic

     94          * situation.

     95          */

     96         smp_send_stop();

     97 #endif

     98

     99         atomic_notifier_call_chain(&panic_notifier_list, 0, buf);

    100

    101         if (!panic_blink)

    102                 panic_blink = no_blink;

    103

    104         if (panic_timeout > 0) {

    105                 /*

    106                  * Delay timeout seconds before rebooting the machine.

    107                  * We can't use the "normal" timers since we just panicked..

    108                  */

    109                 printk(KERN_EMERG "Rebooting in %d seconds..",panic_timeout);

    110                 for (i = 0; i < panic_timeout*1000; ) {

    111                         touch_nmi_watchdog();

    112                         i += panic_blink(i);

    113                         mdelay(1);

    114                         i++;

    115                 }

    116                 /*      This will not be a clean reboot, with everything

    117                  *      shutting down.  But if there is a chance of

118                  *      rebooting the system it will be rebooted.

    119                  */

    120                 emergency_restart();

    121         }

    122 #ifdef __sparc__

    123         {

    124                 extern int stop_a_enabled;

    125                 /* Make sure the user can actually press Stop-A (L1-A) */

    126                 stop_a_enabled = 1;

    127                 printk(KERN_EMERG "Press Stop-A (L1-A) to return to the boot prom/n");

    128         }

    129 #endif

    130 #if defined(CONFIG_S390)

    131         disabled_wait(caller);

    132 #endif

    133         local_irq_enable();

    134         for (i = 0;;) {

    135                 touch_softlockup_watchdog();

    136                 i += panic_blink(i);

    137                 mdelay(1);

    138                 i++;

    139         }

    140 }

    141

    142 EXPORT_SYMBOL(panic);

每当系统崩溃的时候,或者我们经常说的Oops的时候,这个函数会被调用.这个文件我们并没有改过,kdbpatch没有对它作任何修改.不过我们注意到这其中调用了atomic_notifier_call_chain(),此函数的第一个参数不是别人,正是&panic_notifier_list,你不要厚着脸皮说你没见过这玩艺,kdb_init()中就露过脸了,当时我们有下面这句:

  12434 +       atomic_notifier_chain_register(&panic_notifier_list, &kdb_block);

那会儿是向panic_notifier_list这张表注册,这会儿就该使用这张表了, atomic_notifier_call_chain这么一调用,凡是注册到这张表里的结构体变量都会受到影响,它们所关联的那个函数就会被调用,对于kdb_block来说,前面咱们也介绍过,与之关联的那个函数就是kdb_panic(),所以这时候,kdb_panic()会被调用,从而进入了kdb.

关于kdb()以及KDB_ENTER()我们现在能告诉你的就是,这俩都能带你进入kdb,至于它们具体是怎么做的,先搁一搁,下面即将会讲到.

这就是三种进入kdb的情形.如果说你们公司有一台服务器,跑的是Linux,上面装了kdb.那么前两种方法进入kdb是你人为的,是故意的,或者说恶意的,类似于恶意讨薪,恶意取款,恶意打工大多数和老百姓相关的行为;而后一种方法进入kdb往往意味着真的是系统出了问题,这种情况是kdb真正发挥作用的时候,是合理的,类似于合理贪污,合理违法,合理拆迁等大多数和go-vern-ment相关的行为.