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)所以从键盘驱动来说,它就把这个组合键当作一个字符来处理.
而kdb的patch在drivers/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.下面是效果图:(在串行终端上K和B都没有回显出来,只有D回显了.)
[root@localhost ~]# D
Entering kdb (current=0xffffffff805563a0, pid 0) due to Keyboard Entry
kdb>
不过像我这种习惯了按control-a进kdb的人,一般会把这里/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,这就是键盘驱动,我们看到会比较keycode与KEY_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的时候,这个函数会被调用.这个文件我们并没有改过,kdb的patch没有对它作任何修改.不过我们注意到这其中调用了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相关的行为.