kgdb源代码分析(2.6.27)第五章-kgdb初始化时机

/ 0评 / 0

kgdb源代码分析(2.6.27)

本文由penny编写, 你可以通过发邮件到[email protected]联系penny,
也可以直接评论本文章与penny交流.

0. 概述
1. 异步通知
2. 准备工作
3.gdb 远程串行协议(GDB remote serial protocol)
4. 命令实现:
4.1 断点.
4.2 continue 和 step
5 初始化时机.

5.初始化时机

看完上面这些内容,难免有人会问,那kgdb在什么时候起来的呀?假如我们用
"kgdboc=ttyS0,115200 kgdbwait"作为启动参数.就在 drivers/serial/kgdboc.c 中的
module_init(init_kgdboc)里面.就这样完了吗?那内核究竟在启动的那个阶段对kgdb有反应?
这里就扯远一点,暂时离开一下 kgdb.我们从module_init开始,它的定义在include/linux/init.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
259 #define module_init(x) __initcall(x);
 
204 #define __initcall(fn) device_initcall(fn)
 
199 #define device_initcall(fn) __define_initcall("6",fn,6)
 
159 /* initcalls are now grouped by functionality into separate
160 * subsections. Ordering inside the subsections is determined
161 * by link order.
162 * For backwards compatibility, initcall() puts the call in
163 * the device init subsection.
164 *
165 * The `id' arg to __define_initcall() is needed so that multiple initcalls
166 * can point at the same handler without causing duplicate-symbol build errors.
167 */
168
169 #define __define_initcall(level,fn,id) \
170 static initcall_t __initcall_##fn##id __used \
171 __attribute__((__section__(".initcall" level ".init"))) = fn
 
(include/linux/init.h)
135 typedef int (*initcall_t)(void);

一个程序编译好之后有代码段,只读数据段,数据段等一些段,而
__attribute__((__section__(...)))这个gcc的编译扩展属性,它能够把函数或数据放入
指定名字的段中。

那__define_initcall这一团是什么意思呢?首先这是一个定义和初始化的语句,
initcall_t是一个函数指针类型,所以这里定义一个静态的函数指针,它的名字是
__initcall_##fn##id,"##"在宏里面是连接两个字符串,所以这个变量名是根据进来的参数
名字不一样而不同.并且把传进来fn(我们这里就是init_kgdboc())赋给这个变量。最重要的
是把这个变量放在".initcall" level ".init"段中,我们的level是6,所以定义的这个函数指针
放在.initcall6.init这个段中。

连接器允许我们通过连接脚本修改默认段的起始位置,段的内容等很多东西,先来看看
x86的脚本arch/x86/kernel/vmlinux_32.lds.S。从名字我们大概可以猜出我们上面的
那些段对应于这个文件中的:

1
2
3
4
133 .initcall.init : AT(ADDR(.initcall.init) - LOAD_OFFSET) {134   __initcall_start = .;
135   INITCALLS
136   __initcall_end = .;
137 }

连接脚本里面,'.'代表当前位置,所以__initcall_start和__initcall_end分别代表.
initcall.init段的开始位置和结束位置,这种东西可以理解为一个常量,后者我们下面马上会用到。

而 INITCALLS 则在 include/asm-generic/vmlinux.lds.h 里面定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
363 #define INITCALLS \
364 *(.initcallearly.init) \
365 VMLINUX_SYMBOL(__early_initcall_end) = .; \
366 *(.initcall0.init) \
367 *(.initcall0s.init) \
368 *(.initcall1.init) \
369 *(.initcall1s.init) \
370 *(.initcall2.init) \
371 *(.initcall2s.init) \
372 *(.initcall3.init) \
373 *(.initcall3s.init) \
374 *(.initcall4.init) \
375 *(.initcall4s.init) \
376 *(.initcall5.init) \
377 *(.initcall5s.init) \
378 *(.initcallrootfs.init) \
379 *(.initcall6.init) \
380 *(.initcall6s.init) \
381 *(.initcall7.init) \
382 *(.initcall7s.init)

像__initcall_start和__initcall_end一样,__early_initcall_end的值也是段里面的一个位置。
上面两段话合起来就是说在连接内核时,把所有放在中间文件(输入文件)中的.initcallxx.init段里面的东西都连接到输出文件的.initcall.init段中。
所以我们刚才的那个函数指针最后会在目标文件的.initcall.init段中.那现在问题是谁会去管这个段? 它就是do_initcalls
而INITCALLS则在include/asm-generic/vmlinux.lds.h里面定义:

1
2
3
4
5
6
7
8
9
10
11
(init/main.c)
749 static void __init do_initcalls(void)
750 {
751           initcall_t *call;
752
753           for (call = __early_initcall_end; call < __initcall_end; call++)
754           do_one_initcall(*call);
755
756   /* Make sure there is no pending stuff from the initcall sequence */
757   flush_scheduled_work();
758 }

do_initcalls()遍历一次放在__early_initcall_end和__initcall_end之间的函数指针,并
依次调用它们指向的函数,当然包括我们的init_kgdboc()了。那do_initcalls()又是在
那里被调用了呢?

1
2
3
4
5
start_kernel()
    '->rest_init()
          '->kernel_init()
                 '->do_basic_setup()
                      '->do_initcalls()

这样清楚了吧,do_initcalls 是在 init 进程里面才被调用的。init_kgdboc()所做的事
情由于本人知识有限,暂时说不出来, 不过它所干的最重要的两件事情是: 根据内核启动参
数寻找对应的驱动和在初始化完成后直接执行一条 asm("int $3")汇编手动触发一个中断,
把控制权交给 kgdb。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据