6.2 子系统的初始化
所以接下来说do_basic_setup函数,任然是来自init/main.c:
778/* 779 * Ok, the machine is now initialized. None of the devices 780 * have been touched yet, but the CPU subsystem is up and 781 * running, and memory and process management works. 782 * 783 * Now we can finally start doing some real work.. 784 */ 785static void __init do_basic_setup(void) 786{ 787 init_workqueues(); 788 cpuset_init_smp(); 789 usermodehelper_init(); 790 init_tmpfs(); 791 driver_init(); 792 init_irq_proc(); 793 do_ctors(); 794 do_initcalls(); 795} |
第一个函数init_workqueues,初始化工作队列events,每个CPU一个,对工作队列机制感兴趣的同学请访问博客“下半部分”
http://blog.csdn.net/yunsongice/archive/2010/03/07/5354011.aspx。
第二个函数cpuset_init_smp是个空函数,因为没有配置CONFIG_CPUSETS。
第三个函数usermodehelper_init,初始化工作队列khelper,每个CPU一个。
第四个函数init_tmpfs,注册并安装tmpfs文件系统,它的file_system_type结构如下:
static struct file_system_type tmpfs_fs_type = {
.owner = THIS_MODULE,
.name = "tmpfs",
.get_sb = shmem_get_sb,
.kill_sb = kill_litter_super,
};
第五个函数driver_init,建立设备驱动模型sysfs的kset、kobject和subsystem结构,并向其中注册cpu、内存和总线的驱动。
第六个函数init_irq_proc,向/proc文件系统中增加子目录irq来显示中断描述符表中的所有元素。
第七个函数do_ctors,不是太明白,忽略它。
最后来说说第八个函数do_initcalls();
首先说一下,内核中有一个专门的节,用来存放初始化末尾要被调用的函数。举个例子init/initramfs.c中的最后一句是:
rootfs_initcall(populate_rootfs);
而:
#define rootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs)
#define __define_initcall(level,fn,id) /
static initcall_t __initcall_##fn##id __attribute_used__ /
__attribute__((__section__(".initcall" level ".init"))) = fn
可以看出来这个populate_rootfs被放在.initcallrootfs.init节中了。然后呢,这个节在链接的时候会被output到.initcall.init节中 (为了避免交叉过多)。
好了,在明白了initcall相关信息后,继续看看do_initcalls();
它的本质就是
for (call = __initcall_start; call < __initcall_end; call++)
result = (*call)();
这样所有的initcall就会被调用了,这是为了避免把所有代码集合到一块的一个方法,虽然这带来了一个问题,谁在前谁在后?内核专门为这个大节分了一些类,哪些在前哪些在后连接的时候会安排的。
正好,现在知道 populate_rootfs会被执行,它处理initfamfs和initrd,首先是执行unpack_to_rootfs,然后检查是否有initrd。代码就不列出来了。
再回到调用do_basic_setup()的kernel_init中:接下来
if (!ramdisk_execute_command)
ramdisk_execute_command = "/init";
if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
ramdisk_execute_command = NULL;
prepare_namespace();
}
这段代码检查是否有必要mount根文件系统,如果vmlinuz中带有initfamfs,而且其中已经有init,那么就不这么做了(我现在工作用的目标系统就是这样的,里面有个init),否则的话内核还要mount init所在的(也是所有用户态进程的最除根文件系统)根文件系统,挂在根文件系统和执行init是linux启动过程最后要做的事情。
好了,就不说mount_root的细节了,prepare_namespace还是很值得一看的,可以去翻源代码。如果它失败了,就是panic VFS no root found 这样的错误了。现在假设已经有了根文件系统了,这样就到了kernel_init中的最后一条函数调用了——init_post。