内核的start_kenel函数在调用rest_init函数之前,其主要工作与操作系统核心层相关,包括进程调度、内存管理和中断系统等主要模块的初始化。而rest_init函数将创建kernel_init进程,并由该进程调用do_basic_setup->do_initcalls函数完成所有外部设备的初始化。
1 extern initcall_t __initcall_start[], __initcall_end[], __early_initcall_end[]; 2 3 static void __init do_initcalls(void) 4 { 5 initcall_t *fn; 6 7 for (fn = __early_initcall_end; fn < __initcall_end; fn++) 8 do_one_initcall(*fn); 9 }
do_inicalls函数的主体是将的__early_inicall_end和__initcall_end指针之间的函数全部执行一遍,这两个指针在vmlinux.lds中定义。在生成的操作系统内核时,一些需要在linux系统初始化时的函数指针被加入到这两个参数之间,之后由do_initcalls函数统一调用这些函数。linux系统定义了一系列需要在系统初始化时执行的模块,如下所示:
1 #define early_initcall(fn) __define_initcall("early",fn,early) 2 3 /* 4 * A "pure" initcall has no dependencies on anything else, and purely 5 * initializes variables that couldn't be statically initialized. 6 * 7 * This only exists for built-in code, not for modules. 8 */ 9 #define pure_initcall(fn) __define_initcall("0",fn,0) 10 11 #define core_initcall(fn) __define_initcall("1",fn,1) 12 #define core_initcall_sync(fn) __define_initcall("1s",fn,1s) 13 #define postcore_initcall(fn) __define_initcall("2",fn,2) 14 #define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s) 15 #define arch_initcall(fn) __define_initcall("3",fn,3) 16 #define arch_initcall_sync(fn) __define_initcall("3s",fn,3s) 17 #define subsys_initcall(fn) __define_initcall("4",fn,4) 18 #define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s) 19 #define fs_initcall(fn) __define_initcall("5",fn,5) 20 #define fs_initcall_sync(fn) __define_initcall("5s",fn,5s) 21 #define rootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs) 22 #define device_initcall(fn) __define_initcall("6",fn,6) 23 #define device_initcall_sync(fn) __define_initcall("6s",fn,6s) 24 #define late_initcall(fn) __define_initcall("7",fn,7) 25 #define late_initcall_sync(fn) __define_initcall("7s",fn,7s) 26 27 #define __initcall(fn) device_initcall(fn) 28 29 #define __exitcall(fn) \ 30 static exitcall_t __exitcall_##fn __exit_call = fn 31 ………… 32 #define module_init(x) __initcall(x);
以上初始化模块按照_define_initcall定义的顺序执行。如果linux设备驱动程序采用build-in的方式而不是作为module形式加载时,将使用device_initcall函数或者device_initcall_sync函数加载。
在linux系统初始化时运行的模块需要使用以上的宏,定义该模块的函数指针之后该模块的函数指针将加入linux的__early_inicall_end和__initcall_end之间。
在linux内核的System.map文件中,可以找到__early_initcall_end和__initcall_end中的所有函数指针。
经过以上的分析,可以知道,这些入口函数,只要被编译进内核了,就一定会被链接器放到指定的位置,然后内核在运行时就一定会运行到指定的函数。
在内核要想编译某个文件可以经过如下步骤,下面以driver/video/cirrusfb.c文件为例。
这个文件中有个函数通过module_init注册。为了使这个函数在在内核运行时就运行,需要将这个函数编译进内核。可以通过以下步骤来确定编译了这个文件。
1:首先找这个文件所在的目录,打开这个目录下的Makefile文件,找到这个文件对应的选项
1 obj-$(CONFIG_FB_CIRRUS) += cirrusfb.o
可以看到要想编译这个文件,需要将CONFIG_FB_CIRRUS的值赋为y。这可以直接在arch/XXX/config/下的config文件中直接赋值。但是做到这不一定就可以正确编译到这个文件了。首先由于内核Makefile的编译方式,它会根据目录,一层层的往下调用Makefile,因此,要确保这个文件编译,就需要确定它的上层目录也是要被编译的。即driver目录下的Makefile要编译video目录
1 obj-y += video/
2:做到以上这些还不够,因为由于设备相关性本身的逻辑关系,还要把与这个文件相关的上层依赖都编译。这个可以看同目录下的Kconfig文件
1 config FB_CIRRUS 2 tristate "Cirrus Logic support" 3 depends on FB && (ZORRO || PCI) 4 select FB_CFB_FILLRECT 5 select FB_CFB_COPYAREA 6 select FB_CFB_IMAGEBLIT
需要打开FB和PCI,所以我同样可以在arch/XXX/config/下的config文件中也打开这个选项。打开后如下:
1 CONFIG_FB=y 2 CONFIG_FB_PUV3_UNIGFX=n 3 CONFIG_FB_CIRRUS=y
确保了以上这些后,就可以正确编译cirrusfb.c文件了,如此,module_init中的函数也会在运行内核时自动运行。