详细流程
从代码角度,介绍启动时的调用流程。
start_kernel()
|-smp_setup_processor_id() ← 返回启动时的CPU号
|-local_irq_disable() ← 关闭当前CPU的中断
|-setup_arch() ← 完成与体系结构相关的初始化工作
| |-setup_memory_map() ← 建立内存图
| |-e820_end_of_ram_pfn() ← 找出最大的可用页帧号
| |-init_mem_mapping() ← 初始化内存映射机制
| |-initmem_init() ← 初始化内存分配器
| |-x86_init.paging.pagetable_init() ← 建立完整的页表
|-parse_early_param()
| |-parse_args() ← 调用两次parse_args()处理bootloader传递的参数
|-parse_args()
|-init_IRQ() ← 硬件中断初始化
|-softirq_init() ← 软中断初始化
|
|-vfs_caches_init_early()
|-vfs_caches_init() ← 根据参数计算可以作为缓存的页面数,并建立一个存放文件名称的slab缓存
| |-kmem_cache_create() ← 创建slab缓存
| |-dcache_init() ← 建立dentry和dentry_hashtable的缓存
| |-inode_init() ← 建立inode和inode_hashtable的缓存
| |-files_init() ← 建立filp的slab缓存,设置内核可打开的最大文件数
| |-mnt_init() ← 完成sysfs和rootfs的注册和挂载
| |-kernfs_init()
| |-sysfs_init() ← 注册挂载sysfs
| | |-kmem_cache_create() ← 创建缓存
| | |-register_filesystem()
| |-kobject_create_and_add() ← 创建fs目录
| |-init_rootfs() ← 注册rootfs文件系统
| |-init_mount_tree() ← 建立目录树,将init_task的命名空间与之联系起来
| |-vfs_kern_mount() ← 挂载已经注册的rootfs文件系统
| | |-alloc_vfsmnt()
| |-create_mnt_ns() ← 创建命名空间
| |-set_fs_pwd() ← 设置init的当前目录
| |-set_fs_root() ← 以及根目录
|
|-rest_init()
|-kernel_init() ← 通过kernel_thread()创建独立内核线程
| |-kernel_init_freeable()
| | |-do_basic_setup()
| | | |-do_initcalls() ← 调用子模块的初始化
| | | |-do_initcall_level()
| | | |-do_one_initcall() ← 调用一系列初始化函数
| | | |-populate_rootfs()
| | | |-unpack_to_rootfs()
| | |
| | |-prepare_namespace()
| | |-wait_for_device_probe()
| | |-md_run_setup()
| | |-initrd_load() ← 加载initrd
| | | |-create_dev()
| | | |-rd_load_image()
| | | |-identify_ramdisk_image() ← 检查映像文件的magic确定格式,minux、ext2等;并返回解压方法
| | | | |-decompress_method()
| | | |-crd_load() ← 解压
| | | |-deco()
| | |
| | |-mount_root()
| |
| |-run_init_process() ← 执行init,会依次查看ramdisk、命令行指定、/sbin/init等
|
|-kthreadd() ← 同样通过kernel_thread()创建独立内核线程
初始化
在初始化时通常可以分为两种:
A) 一种是关键而其必须按照特定顺序来完成,通常在 start_kernel() 中直接调用;
B) 以子系统、模块实现,通过 do_initcalls() 完成。
在 do_initcalls() 中调用时,会按照等级,从 level0 ~ level7 来初始化,其宏定义在 include/linux/init.h 中实现,简单分为了两类,内核以及模块的实现。
下面以 inet_init 的初始化为例,末行为最后的展开格式。
fs_initcall(inet_init); // net/ipv4/af_inet.c
#define fs_initcall(fn) __define_initcall(fn, 5) // include/linux/init.h
#define __define_initcall(fn, id) \ // 同上
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" #id ".init"))) = fn
static initcall_t __initcall_inet_init5 __used __attribute__((__section__(".initcall5.init"))) = inet_init;
do_initcalls() 是从特定的内存区域取出初始化函数的指针,然后调用该函数,通过 “vmlinux.lds.h” 定义的宏。
ipv4内核初始化相关
所在文件:
net/ipv4/af_inet.c
初始化函数定义:
static int __init inet_init ( void )
初始化函数调用:
fs_initcall ( inet_init ) ; //#define fs_initcall(fn) __define_initcall(“5”,fn,5)
这里的fs_initcall和module_init这样的函数是一样的功能,就是给系统内核添加一个功能函数。
这个宏的定义位于inlcludelinuxinit.h中:
# define __define_initcall ( level , fn , id )
static initcall_t __initcall_ ## fn ## id __used
__attribute__ (( __section__ ( " .initcall " level " .init " ))) = fn
其中 initcall_t 是个函数指针类型:typedef int (*initcall_t)(void);
而属性 attribute((section())) 则表示把对象放在一个这个由括号中的名称所指代的section中。
以这个宏定义的的含义是:
- 声明一个名称为__initcall_##fn##id的函数指针(其中##表示替换连接,);
- 将这个函数指针初始化为fn;
- 编译的时候需要把这个函数指针变量放置到名称为 “.initcall” level “.init”的section中(比如level=”1″,代表这个section的名称是 “.initcall1.init”)。
这些衍生宏宏的定义也位于 inlcludelinuxInit.h 中:
# define pure_initcall ( fn ) __define_initcall ( " 0 " , fn , 0 )
# define core_initcall ( fn ) __define_initcall ( " 1 " , fn , 1 )
# define core_initcall_sync ( fn ) __define_initcall ( " 1s " , fn , 1 s )
# define postcore_initcall ( fn ) __define_initcall ( " 2 " , fn , 2 )
# define postcore_initcall_sync ( fn ) __define_initcall ( " 2s " , fn , 2 s )
# define arch_initcall ( fn ) __define_initcall ( " 3 " , fn , 3 )
# define arch_initcall_sync ( fn ) __define_initcall ( " 3s " , fn , 3 s )
# define subsys_initcall ( fn ) __define_initcall ( " 4 " , fn , 4 )
# define subsys_initcall_sync ( fn ) __define_initcall ( " 4s " , fn , 4 s )
# define fs_initcall ( fn ) __define_initcall ( " 5 " , fn , 5 )
# define fs_initcall_sync ( fn ) __define_initcall ( " 5s " , fn , 5 s )
# define rootfs_initcall ( fn ) __define_initcall ( " rootfs " , fn , rootfs )
# define device_initcall ( fn ) __define_initcall ( " 6 " , fn , 6 )
# define device_initcall_sync ( fn ) __define_initcall ( " 6s " , fn , 6 s )
# define late_initcall ( fn ) __define_initcall ( " 7 " , fn , 7 )
# define late_initcall_sync ( fn ) __define_initcall ( " 7s " , fn , 7 s )
因此通过宏 core_initcall() 来声明的函数指针,将放置到名称为.initcall1.init的section中,而通过宏 postcore_initcall() 来声明的函数指针,将放置到名称为.initcall2.init的section中,依次类推。
在:include/asm-generic/vmlinux.lds.h:
define INITCALLS
* ( . initcallearly . init )
VMLINUX_SYMBOL ( __early_initcall_end ) = .; //注意这里的__early_initcall_end标志
* ( . initcall0 . init )
* ( . initcall0s . init )
* ( . initcall1 . init )
* ( . initcall1s . init )
* ( . initcall2 . init )
* ( . initcall2s . init )
* ( . initcall3 . init )
* ( . initcall3s . init )
* ( . initcall4 . init )
* ( . initcall4s . init )
* ( . initcall5 . init )
* ( . initcall5s . init )
* ( . initcallrootfs . init )
* ( . initcall6 . init )
* ( . initcall6s . init )
* ( . initcall7 . init )
* ( . initcall7s . init )
# define INIT_CALLS
VMLINUX_SYMBOL ( __initcall_start ) = .;
INITCALLS
VMLINUX_SYMBOL ( __initcall_end ) = .; //还有这里的__initcall_end
最终跟踪之后这个初始化的段会在arch/x86/kernel/vmlinux.lds.S这样的体系结构中内核二进制文件结构组织的配置文件中。
而在内核Makefile文件中有这样的编译语句:
vmlinux : $ ( vmlinux - lds ) $ ( vmlinux - init ) $ ( vmlinux - main ) vmlinux . o $ ( kallsyms . o ) FORCE
。。。
vmlinux - lds := arch /$ ( SRCARCH ) / kernel / vmlinux . lds
。。。
而在init/main.c 中:
static void __init do_initcalls ( void )
{
initcall_t * call ;
for ( call = __early_initcall_end ; call & lt ; __initcall_end ; call ++ )
do_one_initcall ( * call ) ;
/* Make sure there is no pending stuff from the initcall sequence */
flush_scheduled_work () ;
}
该函数的调用关系如下:
start_kernel --& gt ; rest_init -& gt ; kernel_thread ( kernel_init , NULL , CLONE_FS | CLONE_SIGHAND ) ;
|
-& gt ; kernel_init ( void * unused ) -& gt ; do_initcalls ( void )
也就是说对于所有的内核模块或是其它的以类似该形式加入到内核中的程序,都最终在内核所在的二进制文件中是有一个固定的段来存放的,而且内核在初始化的过程中也是找到这些段的地址让后做相应的加载和执行。
https://blog.csdn.net/xxiomg/article/details/100558790
https://gohalo.me/post/kernel-bootstrap.html