Linux内核的启动过程是一个复杂而精妙的流程,其核心入口点是start_kernel()
函数,可以视为内核的"主函数"。下面我将详细解析这一关键函数的实现原理。
Linux内核的启动流程可以概括为以下几个阶段:
1. 引导加载程序阶段(Bootloader)
2. 内核解压和重定位
3. 体系结构特定的初始化
4. start_kernel()
函数执行
5. 各个子系统的初始化
6. 用户空间初始化
start_kernel()
函数定义在init/main.c
中,是内核初始化过程的核心入口点。以下是其主要执行流程:
asmlinkage __visible void __init start_kernel(void)
{
char *command_line;
char *after_dashes;
// 设置任务栈的canary值以防止栈溢出攻击
boot_init_stack_canary();
// 初始化cgroup的早期子系统
cgroup_init_early();
// 关闭本地中断
local_irq_disable();
// 设置大内核锁(BKL)计数器
lockdep_init();
// 打印内核版本信息
printk(KERN_NOTICE "%s", linux_banner);
// 初始化体系结构相关设置
setup_arch(&command_line);
// 初始化页表
mm_init_owner(&init_mm, &init_task);
mm_init_cpumask(&init_mm);
// 设置控制台
console_init();
// 初始化内存管理
mem_init();
// 初始化进程调度器
sched_init();
// 初始化RCU机制
rcu_init();
// 初始化中断系统
init_IRQ();
tick_init();
init_timers();
hrtimers_init();
softirq_init();
timekeeping_init();
time_init();
// 初始化性能分析工具
profile_init();
// 初始化控制台日志级别
console_init();
// 初始化安全模块
security_init();
// 初始化虚拟文件系统
vfs_caches_init(totalram_pages);
// 初始化信号系统
signals_init();
// 初始化页缓存
pagecache_init();
// 初始化进程间通信
ipc_init();
// 初始化/proc文件系统
proc_root_init();
// 初始化cgroup
cgroup_init();
// 初始化网络协议栈
sock_init();
// 初始化块设备层
block_dev_init();
// 初始化驱动程序模型
driver_init();
// 初始化平台设备
platform_bus_init();
// 初始化系统调用
sysctl_init();
// 初始化延迟工作队列
delayed_work_init();
// 初始化init进程
rest_init();
}
mem_init()
函数负责初始化内核的内存管理系统:
- 释放不再需要的引导内存
- 初始化伙伴系统(Buddy System)
- 初始化slab分配器
- 建立内存区域(ZONE)信息
sched_init()
函数初始化进程调度器:
- 初始化运行队列
- 设置调度类(CFS、实时调度等)
- 初始化负载均衡机制
- 设置调度时钟
init_IRQ()
函数负责中断系统的初始化:
- 初始化中断描述符表(IDT)
- 设置硬件中断控制器
- 注册中断处理程序
- 初始化软中断机制
vfs_caches_init()
初始化虚拟文件系统:
- 初始化dcache和inode缓存
- 注册根文件系统
- 初始化各种文件系统类型
start_kernel()
最后调用rest_init()
完成剩余初始化:
static noinline void __init_refok rest_init(void)
{
int pid;
// 创建内核线程init
pid = kernel_thread(kernel_init, NULL, CLONE_FS);
// 创建kthreadd线程
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
// 调度器开始运行
cpu_startup_entry(CPUHP_ONLINE);
}
不同体系结构(x86、ARM等)在内核启动初期有不同的初始化过程:
现代Linux内核采用多种技术优化启动过程:
使用__init
宏标记初始化函数,这些函数在内核初始化完成后会被释放:
#define __init __section(.init.text) __cold notrace
内核支持某些子系统的并行初始化,通过do_pre_smp_initcalls()
和do_basic_setup()
实现。
非关键子系统可以采用延迟初始化策略,通过late_initcall()
宏实现。
内核启动参数通过parse_early_param()
和parse_args()
处理:
- 早期参数:影响内存、控制台等关键子系统
- 常规参数:影响驱动程序和其他子系统
内核初始化完成后,会启动用户空间的init进程(PID 1):
1. 尝试执行/sbin/init
2. 尝试执行/etc/init
3. 尝试执行/bin/init
4. 最后尝试/bin/sh
内核提供多种调试启动过程的方法:
- initcall_debug
参数:打印所有初始化调用的耗时
- earlyprintk
:早期控制台输出
- kgdb
:内核调试器
- ftrace
:函数跟踪
随着Linux内核的发展,start_kernel()
的实现也在不断演进:
- 支持更多的硬件架构
- 更细粒度的初始化顺序控制
- 更好的并行初始化支持
- 增强的安全特性初始化
理解start_kernel()
的实现原理对于内核开发、驱动开发和系统调优都具有重要意义,它是掌握Linux内核工作原理的关键入口点。