Linux内核启动流程与模块机制

本文旨在简单的介绍一下Linux的启动流程与模块机制:

Linux启动的C入口位于/Linux.2.6.22.6/init/main.c::start_kernel()

下图简要的描述了一下内核初始化的流程:

本文我们分析一下do_initcalls ()函数,他负责大部分模块的初始化(比如U盘驱动就是在这里被初始化的)。

 1 static void __init do_initcalls(void)
 2 {
 3     initcall_t *call;
 4     int count = preempt_count();
 5 
 6     for (call = __initcall_start; call < __initcall_end; call++) {
 7         ktime_t t0, t1, delta;
 8         char *msg = NULL;
 9         char msgbuf[40];
10         int result;
11         ....
12         }
13 }

它会循环执行一段区域里面的内容,那么这段区域里面是什么呢?

先把相关的定义贴出来:

1         __initcall_start = .;
2             INITCALLS
3         __initcall_end = .;
 1 #define INITCALLS                            \
 2       *(.initcall0.init)                        \
 3       *(.initcall0s.init)                        \
 4       *(.initcall1.init)                        \
 5       *(.initcall1s.init)                        \
 6       *(.initcall2.init)                        \
 7       *(.initcall2s.init)                        \
 8       *(.initcall3.init)                        \
 9       *(.initcall3s.init)                        \
10       *(.initcall4.init)                        \
11       *(.initcall4s.init)                        \
12       *(.initcall5.init)                        \
13       *(.initcall5s.init)                        \
14       *(.initcallrootfs.init)                \
15       *(.initcall6.init)                        \
16       *(.initcall6s.init)                        \
17       *(.initcall7.init)                        \
18       *(.initcall7s.init)
1 #define __define_initcall(level,fn,id) \
2     static initcall_t __initcall_##fn##id __attribute_used__ \
3     __attribute__((__section__(".initcall" level ".init"))) = fn

这里涉及的是链接器的知识,链接器在链接时会把文件分段,而do_initcalls ()里面的for循环就是循环的某段区域(这里指initcall段),我们把每个模块的入口函数放到这个区域里,这样模块在内核初始化的过程中就会跑到。这里还涉及另一个重要的知识点就是:GCC支持用户通过__attribute__来自定义段(比如通过__init修饰的函数只在初始化过程中执行一次,因为这段区域在初始化之后就被释放掉了)。链接器的相关知识是理解这里的重点,大家可以找点这方面的资料看看。

下面我们以U盘驱动程序为例,看看他是如何被加载的:

/Linux2.6.22.6/drivers/usb/storage/usb.c::

1 module_init(usb_stor_init);
2 module_exit(usb_stor_exit);

module_init()是一个宏,这里表示指定usb_stor_init()为U盘驱动程序的入口点。

#define module_init(x)    __initcall(x);
#define __initcall(fn)    device_initcall(fn)
#define device_initcall(fn)        __define_initcall("6",fn,6)

到这里,大概可以知道,程序在链接时,usb_stor_init()会被放到*(.initcall6.init)可以访问到的地方,这样在do_initcalls ()的for循环里面,usb_stor_init()就会被执行。

到此,也可以大概清楚Linux的模块机制是什么样子的了。

 1 #define pure_initcall(fn)                __define_initcall("0",fn,1)
 2 
 3 #define core_initcall(fn)                __define_initcall("1",fn,1)
 4 #define core_initcall_sync(fn)          __define_initcall("1s",fn,1s)
 5 #define postcore_initcall(fn)            __define_initcall("2",fn,2)
 6 #define postcore_initcall_sync(fn)       __define_initcall("2s",fn,2s)
 7 #define arch_initcall(fn)                __define_initcall("3",fn,3)
 8 #define arch_initcall_sync(fn)          __define_initcall("3s",fn,3s)
 9 #define subsys_initcall(fn)              __define_initcall("4",fn,4)
10 #define subsys_initcall_sync(fn)         __define_initcall("4s",fn,4s)
11 #define fs_initcall(fn)                    __define_initcall("5",fn,5)
12 #define fs_initcall_sync(fn)            __define_initcall("5s",fn,5s)
13 #define rootfs_initcall(fn)              __define_initcall("rootfs",fn,rootfs)
14 #define device_initcall(fn)              __define_initcall("6",fn,6)
15 #define device_initcall_sync(fn)        __define_initcall("6s",fn,6s)
16 #define late_initcall(fn)               __define_initcall("7",fn,7)
17 #define late_initcall_sync(fn)          __define_initcall("7s",fn,7s)

上面的定义表示模块也是分优先级的,这也很好理解,还以U盘为例,内核必须先完成USB控制器以及USB总线驱动的初始化,然后U盘的驱动才能初始化成功,所以USB_Core的模块入口定义是下面这样的:

/Linux2.6.22.6/drivers/usb/core/usb.c::

subsys_initcall(usb_init);
module_exit(usb_exit);

 

posted @ 2017-09-24 11:51  zhuqingzhu  阅读(688)  评论(0编辑  收藏  举报