linux内核初始化调用顺序

在这里插入图片描述
详细流程
从代码角度,介绍启动时的调用流程。

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中。
以这个宏定义的的含义是:

  1. 声明一个名称为__initcall_##fn##id的函数指针(其中##表示替换连接,);
  2. 将这个函数指针初始化为fn;
  3. 编译的时候需要把这个函数指针变量放置到名称为 “.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

https://gohalo.me/post/kernel-bootstrap.html

https://www.cnblogs.com/lcw/p/3337937.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值