参考博客:
https://blog.csdn.net/lushoumin/article/details/85274105
https://blog.csdn.net/weixin_44895651/article/details/108739520
https://blog.csdn.net/linuxweiyh/article/details/83382154
https://blog.csdn.net/perfect1t/article/details/81741531
https://blog.csdn.net/dai__liang/article/details/110678470
一、Linux 内核启动流程函数调用
二、start_kernel 分析
在start_kernel函数里,需要非常注意的是里面初始化函数的顺序,这些初始化函数不能随便调换初始化顺序,否则就会导致系统运行出错。
asmlinkage __visible void __init start_kernel(void)
{
char *command_line;
char *after_dashes;
/*
* Need to run as early as possible, to initialize the
* lockdep hash:
*/
lockdep_init(); // 初始化锁的状态跟踪模块
set_task_stack_end_magic(&init_task);
smp_setup_processor_id(); // 获取当前正在执行初始化的处理器ID
debug_objects_early_init(); // 主要作用是对调试对象进行早期的初始化,其实就是HASH锁和静态对象池进行初始化。
/*
* Set up the the initial canary ASAP:
*/
boot_init_stack_canary();
cgroup_init_early(); // 控制组进行早期的初始化
local_irq_disable(); // 关闭当前CPU的所有中断响应
early_boot_irqs_disabled = true;
/*
* Interrupts are still disabled. Do necessary setups, then
* enable them
*/
boot_cpu_init(); // 设置当前引导系统的CPU在物理上存在,在逻辑上可以使用,并且初始化准备好。
page_address_init(); // 初始化高端内存的映射表
pr_notice("%s", linux_banner); // 主要作用是在输出终端上显示版本信息、编译的电脑用户名称、编译器版本、编译时间
setup_arch(&command_line); // 对内核架构进行初始化
mm_init_cpumask(&init_mm); // 设置最开始的初始化任务属于init_mm内存
setup_command_line(command_line); // 保存命令行,以便后面可以使用
setup_nr_cpu_ids(); // 设置最多有多少个nr_cpu_ids结构
setup_per_cpu_areas(); // 设置SMP体系每个CPU使用的内存空间,同时拷贝初始化段里数据
smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */ // 为SMP系统里引导CPU进行准备工作
build_all_zonelists(NULL, NULL); // 初始化所有内存管理节点列表,以便后面进行内存管理初始化。
page_alloc_init(); // 设置内存页分配通知器
pr_notice("Kernel command line: %s\n", boot_command_line); // 输出命令参数到显示终端
parse_early_param(); // 分析命令行最早使用的参数
after_dashes = parse_args("Booting kernel",
static_command_line, __start___param,
__stop___param - __start___param,
-1, -1, &unknown_bootoption); // 对传入内核参数进行解释,如果不能识别的命令就调用最后参数的函数
if (!IS_ERR_OR_NULL(after_dashes))
parse_args("Setting init args", after_dashes, NULL, 0, -1, -1,
set_init_arg);
jump_label_init();
/*
* These use large bootmem allocations and must precede
* kmem_cache_init()
*/
setup_log_buf(0);
pidhash_init(); // 进程ID的HASH表初始化,这样可以提供通PID进行高效访问进程结构的信
vfs_caches_init_early(); // 虚拟文件系统的缓存初始化
sort_main_extable(); // 对内核内部的异常表进行堆排序,以便加速访问
trap_init(); // 对异常进行初始化
mm_init(); // 标记那些内存可以使用,并且告诉系统有多少内存可以使用,当然是除了内核使用的内存以外
/*
* Set up the scheduler prior starting any interrupts (such as the
* timer interrupt). Full topology setup happens at smp_init()
* time - but meanwhile we still have a functioning scheduler.
*/
sched_init(); // 对进程调度器进行初始化
/*
* Disable preemption - early bootup scheduling is extremely
* fragile until we cpu_idle() for the first time.
*/
preempt_disable(); // 关闭优先级调度
if (WARN(!irqs_disabled(),
"Interrupts were enabled *very* early, fixing it\n"))
local_irq_disable(); // 判断是否过早打开中断,如果是这样,就会提示,并把中断关闭
idr_init_cache();
rcu_init(); // 初始化直接读拷贝更新的锁机制
/* trace_printk() and trace points may be used after this */
trace_init();
context_tracking_init();
radix_tree_init(); // 初始化radix树,radix树基于二进制键值的查找树
/* init some links before init_ISA_irqs() */
early_irq_init();
init_IRQ(); // 初始化中断相关的工作
tick_init();
rcu_init_nohz();
init_timers(); // 初始化引导CPU的时钟相关的数据结构
hrtimers_init(); // 初始化高精度的定时器,并设置回调函数
softirq_init(); // 初始化软件中断
timekeeping_init(); // 初始化系统时钟计时,并且初始化内核里与时钟计时相关的变量
time_init(); // 初始化系统时钟
sched_clock_postinit();
perf_event_init();
profile_init(); // 分配内核性能统计保存的内存,以便统计的性能变量可以保存到这里
call_function_init();
WARN(!irqs_disabled(), "Interrupts were enabled early\n");
early_boot_irqs_disabled = false;
local_irq_enable(); // 打开本CPU的中断
kmem_cache_init_late();
/*
* HACK ALERT! This is early. We're enabling the console before
* we've done PCI setups etc, and console_init() must be aware of
* this. But we do want output early, in case something goes wrong.
*/
console_init(); // 初始化控制台,从这个函数之后就可以输出内容到控制台了(以前信息全部缓存)
if (panic_later)
panic("Too many boot %s vars at `%s'", panic_later,
panic_param);
lockdep_info(); // 打印锁的依赖信息,用来调试锁
/*
* Need to run this when irqs are enabled, because it wants
* to self-test [hard/soft]-irqs on/off lock inversion bugs
* too:
*/
locking_selftest(); // 测试锁的API是否使用正常,进行自我测试
#ifdef CONFIG_BLK_DEV_INITRD
if (initrd_start && !initrd_below_start_ok &&
page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {
pr_crit("initrd overwritten (0x%08lx < 0x%08lx) - disabling it.\n",
page_to_pfn(virt_to_page((void *)initrd_start)),
min_low_pfn);
initrd_start = 0;
}
#endif
page_ext_init();
debug_objects_mem_init(); // 创建调试对象内存缓存
kmemleak_init();
setup_per_cpu_pageset(); // 创建每个CPU的高速缓存集合数组
numa_policy_init(); // 初始化NUMA的内存访问策略
if (late_time_init)
late_time_init(); // 主要运行时钟相关后期的初始化功能
sched_clock_init();
calibrate_delay();
pidmap_init(); // 进程位图初始化
anon_vma_init();
acpi_early_init();
#ifdef CONFIG_X86
if (efi_enabled(EFI_RUNTIME_SERVICES))
efi_enter_virtual_mode(); // 初始化EFI的接口,并进入虚拟模式
#endif
#ifdef CONFIG_X86_ESPFIX64
/* Should be run before the first non-init thread is created */
init_espfix_bsp();
#endif
thread_info_cache_init(); // 线程信息的缓存初始化
cred_init();
fork_init(); // 根据当前物理内存计算出来可以创建进程(线程)的数量,并进行进程环境初始化
proc_caches_init(); // 进程缓存初始化
buffer_init(); // 初始化文件系统的缓冲区,并计算最大可以使用的文件缓存
key_init(); // 初始化安全键管理列表和结构
security_init(); // 初始化安全管理框架,以便提供访问文件/登录等权限
dbg_late_init();
vfs_caches_init(totalram_pages); // 虚拟文件系统进行缓存初始化,提高虚拟文件系统的访问速度
signals_init(); // 初始化信号队列缓存
/* rootfs populating might need page-writeback */
page_writeback_init();
proc_root_init(); // 初始化系统进程文件系统,主要提供内核与用户进行交互的平台,方便用户实时查看进程的信息
nsfs_init();
cpuset_init(); // 初始化CPUSET,CPUSET主要为控制组提供CPU和内存节点的管理的结构
cgroup_init(); // 初始化进程控制组
taskstats_init_early(); // 初始化任务状态相关的缓存、队列和信号量
delayacct_init(); // 初始化每个任务延时计数。当一个任务等CPU运行,或者等IO同步时,都需要计算等待时间
check_bugs(); // 检查CPU配置、FPU等是否非法使用不具备的功能
acpi_subsystem_init(); // 初始化ACPI电源管理
sfi_init_late();
if (efi_enabled(EFI_RUNTIME_SERVICES)) {
efi_late_init();
efi_free_boot_services();
}
ftrace_init(); // 初始化内核跟踪模块
/* Do the rest non-__init'ed, we're now alive */
rest_init();
}
三、rest_init分析
static noinline void __init_refok rest_init(void)
{
int pid;
rcu_scheduler_starting();
smpboot_thread_init();
/*
* We need to spawn init first so that it obtains pid 1, however
* the init task will end up wanting to create kthreads, which, if
* we schedule it before we create kthreadd, will OOPS.
*/
kernel_thread(kernel_init, NULL, CLONE_FS);
numa_default_policy();
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
rcu_read_lock();
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
rcu_read_unlock();
complete(&kthreadd_done);
/*
* The boot idle thread must execute schedule()
* at least once to get things moving:
*/
init_idle_bootup_task(current);
schedule_preempt_disabled();
/* Call into cpu_idle with preempt disabled */
cpu_startup_entry(CPUHP_ONLINE);
}
1、调用kernel_thread函数启动了2个内核线程,分别是:kernel_init和kthreadd。
2、调用schedule函数开启了内核的调度系统。
3、调用cpu_idle函数结束了整个内核的启动。
四、kernel_init分析
static int __ref kernel_init(void *unused)
{
int ret;
kernel_init_freeable();
/* need to finish all async __init code before freeing the memory */
async_synchronize_full();
free_initmem();
mark_rodata_ro();
system_state = SYSTEM_RUNNING;
numa_default_policy();
flush_delayed_fput();
if (ramdisk_execute_command) {
ret = run_init_process(ramdisk_execute_command);
if (!ret)
return 0;
pr_err("Failed to execute %s (error %d)\n",
ramdisk_execute_command, ret);
}
/*
* We try each of these until one succeeds.
*
* The Bourne shell can be used instead of init if we are
* trying to recover a really broken machine.
*/
if (execute_command) {
ret = run_init_process(execute_command);
if (!ret)
return 0;
panic("Requested init %s failed (error %d).",
execute_command, ret);
}
if (!try_to_run_init_process("/sbin/init") ||
!try_to_run_init_process("/etc/init") ||
!try_to_run_init_process("/bin/init") ||
!try_to_run_init_process("/bin/sh"))
return 0;
panic("No working init found. Try passing init= option to kernel. "
"See Linux Documentation/init.txt for guidance.");
}
五、kthreadd分析
int kthreadd(void *unused)
{
struct task_struct *tsk = current;
/* Setup a clean context for our children to inherit. */
set_task_comm(tsk, "kthreadd");
ignore_signals(tsk);
set_cpus_allowed_ptr(tsk, cpu_all_mask);
set_mems_allowed(node_states[N_MEMORY]);
current->flags |= PF_NOFREEZE;
for (;;) {
set_current_state(TASK_INTERRUPTIBLE);
if (list_empty(&kthread_create_list))
schedule();
__set_current_state(TASK_RUNNING);
spin_lock(&kthread_create_lock);
while (!list_empty(&kthread_create_list)) {
struct kthread_create_info *create;
create = list_entry(kthread_create_list.next,
struct kthread_create_info, list);
list_del_init(&create->list);
spin_unlock(&kthread_create_lock);
create_kthread(create);
spin_lock(&kthread_create_lock);
}
spin_unlock(&kthread_create_lock);
}
return 0;
}
六、linux下3个特殊进程
1、idle进程
idle进程由系统自动创建, 运行在内核态。idle进程其pid=0,其前身是系统创建的第一个进程,也是唯一一个没有通过fork或者kernel_thread产生的进程。完成加载系统后,演变为进程调度、交换。
2、kernel_init进程
init进程由idle通过kernel_thread创建,在内核空间完成初始化后, 加载init程序, 并最终允许在用户态。由0进程创建,完成系统的初始化. 是系统中所有其它用户进程的祖先进程。Linux中的所有进程都是有init进程创建并运行的。
首先Linux内核启动,然后在用户空间中启动init进程,再启动其他系统进程。在系统启动完成完成后,init将变为守护进程监视系统其他进程。
3、kthreadd进程
kthreadd进程由idle通过kernel_thread创建,并始终运行在内核空间, 负责所有内核线程的调度和管理。
它的任务就是管理和调度其他内核线程kernel_thread, 会循环执行一个kthread的函数,该函数的作用就是运行kthread_create_list全局链表中维护的kthread, 当我们调用kernel_thread创建的内核线程会被加入到此链表中,因此所有的内核线程都是直接或者间接的以kthreadd为父进程 。