【摘要】:Linux系统在初始化时,将在do_initcalls函数中执行__initcall_xyz_initx函数,从而执行xyz_init函数。Linux系统使用这种方法规范初始化模块的执行,并保证这些模块可以按照指定的顺序依次执行。在Linux内核的System.map[4]文件中,可以找到在__early_initcall_end和__initcall_end之间所有的函数指针,其中与PCI总线初始化相关的函数如源代码14-4所示,这些函数将按照在以下源代码中出现的顺序依次执行。
一个处理器系统首先从Firmware开始执行,并由Firmware开始引导Linux内核。Linux系统首先从./init/main.c文件的start_kernel函数开始执行。不同的处理器系统使用的Firm-ware并不相同,如x86处理器系统使用BIOS,而PowerPC处理器系统使用U-Boot。有些处理器系统,最初的初始化操作可能由E2PROM完成,之后执行Firmware中的程序。值得注意的是,在x86处理器中常用的Grub并不是Firmware,而是Linux系统的引导程序。
start_kernel函数在调用rest_init函数之前,其主要工作与操作系统核心层相关,包括进程调度、内存管理和中断系统等主要模块的初始化。而rest_init函数将创建kernel_init进程,并由该进程调用do_basic_setup→do_initcalls函数[2]完成所有外部设备的初始化,包括PCI总线的初始化,该函数如源代码14-1所示。
源代码14-1 do_initcalls函数
do_initcalls函数的主体是将__early_initcall_end和__initcall_end指针之间的函数全部执行一遍,这两个指针在vmlinux.lds文件中定义。在生成操作系统内核时,一些需要在Linux系统初始化时执行的函数指针被加入到__early_initcall_end和__initcall_end参数之间,之后由do_initcalls函数统一调用这些函数。Linux系统定义了一系列需要在系统初始化时执行的模块,如源代码14-2所示。这段代码在./include/linux/init.h文件中。
源代码14-2 Linux系统的初始化模块
以上初始化模块按照__define_initcall定义的顺序执行,首先执行early_initcall初始化模块,之后是pure_initcall模块、core_initcall模块等,最后执行late_initcall_sync。如果Linux设备驱动程序采用built-in[3]的方式而不是作为Module形式加载时,将使用device_initcall函数或者device_initcall_sync函数进行加载。
在Linux系统初始化时运行的模块需要使用以上的xxx_initcall宏,定义该模块的函数指针,之后该模块的函数指针将加入到Linux内核的__early_initcall_end和__initcall_end之间。我们以xyz_init模块的加载为例说明这些xxx_initcall函数的使用,xyz_init函数用来加载某个模块。该函数的初始化过程如源代码14-3所示。(www.chuimin.cn)
源代码14-3 xxx_initcall函数
这段代码首先使用宏xxx_initcall定义了一个__initcall_xyz_initx函数,该函数存放xyz函数的指针。在生成Linux系统内核时,链接器将这个函数指针存放在__early_initcall_end和__initcall_end参数之间。
Linux系统在初始化时,将在do_initcalls函数中执行__initcall_xyz_initx函数,从而执行xyz_init函数。Linux系统使用这种方法规范初始化模块的执行,并保证这些模块可以按照指定的顺序依次执行。
在Linux内核的System.map[4]文件中,可以找到在__early_initcall_end和__initcall_end之间所有的函数指针,其中与PCI总线初始化相关的函数如源代码14-4所示,这些函数将按照在以下源代码中出现的顺序依次执行。
源代码14-4 System.map文件中与PCI总线初始化相关的函数
每一次编译Linux内核时,都可能会产生一个新的System.map,但是源代码14-4中函数指针的顺序不会发生变化,其执行顺序也不会发生变化。下面将依次分析这些函数的功能。并在后续章节,逐步解析这些函数的实现方法。
相关推荐