Linux内核启动流程(文章最后流程图)

本文以Linux3.14版本源码为例分析其启动流程。各版本启动代码略有不同,但核心流程与思想万变不离其宗。

内核映像被加载到内存并获得控制权之后,内核启动流程开始。通常,内核映像以压缩形式存储,并不是一个可以执行的内核。因此,内核阶段的首要工作是自解压内核映像。

内核编译生成vmliunx后,通常会对其进行压缩,得到zImage(小内核,小于512KB)或bzImage(大内核,大于512KB)。在它们的头部嵌有解压缩程序。

通过linux/arch/arm/boot/compressed目录下的Makefile寻找到vmlinux文件的链接脚本(vmlinux.lds),从中查找系统启动入口函数。


 
 
  1. $(obj)/vmlinux: $(obj)/vmlinux.lds $(obj)/$(HEAD) $(obj)/piggy.$(suffix_y).o \
  2. $(addprefix $(obj)/, $(OBJS)) $(lib1funcs) $(ashldi3) \
  3. $(bswapsdi2) FORCE
  4. @$(check_for_multiple_zreladdr)
  5. $(call if_changed,ld)
  6. @$(check_for_bad_syms)

vmlinux.lds(linux/arch/arm/kernel/vmlinux.lds)链接脚本开头内容


 
 
  1. OUTPUT_ARCH(arm)
  2. ENTRY(stext)
  3. jiffies = jiffies_64;
  4. SECTIONS
  5. {

得到内核入口函数为 stext(linux/arch/arm/kernel/head.S)

内核引导阶段


 
 
  1. ENTRY(stext)
  2. bl __lookup_processor_type @ r5=procinfo r9=cpuid //处理器是否支持
  3. movs r10, r5 @ invalid processor (r5=0)?
  4. THUMB( it eq ) @ force fixup-able long branch encoding
  5. beq __error_p @ yes, error 'p' //不支持则打印错误信息
  6. bl __create_page_tables //创建页表
  7. /*
  8. * The following calls CPU specific code in a position independent
  9. * manner. See arch/arm/mm/proc-*.S for details. r10 = base of
  10. * xxx_proc_info structure selected by __lookup_processor_type
  11. * above. On return, the CPU will be ready for the MMU to be
  12. * turned on, and r0 will hold the CPU control register value.
  13. */
  14. ldr r13, =__mmap_switched @ address to jump to after //保存MMU使能后跳转地址
  15. @ mmu has been enabled
  16. adr lr, BSYM( 1f) @ return (PIC) address
  17. mov r8, r4 @ set TTBR1 to swapper_pg_dir
  18. ARM( add pc, r10, #PROCINFO_INITFUNC )
  19. THUMB( add r12, r10, #PROCINFO_INITFUNC )
  20. THUMB( mov pc, r12 )
  21. 1: b __enable_mmu //使能MMU后跳转到__mmap_switched
查找标签__mmap_switched所在位置:/linux/arch/arm/kernel/head-common.S

 
 
  1. __mmap_switched:
  2. /*
  3. * The following fragment of code is executed with the MMU on in MMU mode,
  4. * and uses absolute addresses; this is not position independent.
  5. *
  6. * r0 = cp #15 control register
  7. * r1 = machine ID
  8. * r2 = atags/dtb pointer
  9. * r9 = processor ID
  10. */
  11. //保存设备信息、设备树及启动参数存储地址
  12. b start_kernel

 

内核初始化阶段

从start_kernel函数开始,内核进入C语言部分,完成内核的大部分初始化工作。

函数所在位置:/linux/init/Main.c

start_kernel涉及大量初始化工作,只例举重要的初始化工作。


 
 
  1. asmlinkage void __ init start_kernel(void)
  2. {
  3. …… //类型判断
  4. smp_setup_processor_id(); //smp相关,返回启动CPU号
  5. ……
  6. local_irq_disable(); //关闭当前CPU中断
  7. early_boot_irqs_disabled = true;
  8. /*
  9. * Interrupts are still disabled. Do necessary setups, then
  10. * enable them
  11. */
  12. boot_cpu_init();
  13. page_address_init(); //初始化页地址
  14. pr_notice( "%s", linux_banner); //显示内核版本信息
  15. setup_arch(&command_line);
  16. mm_init_owner(&init_mm, &init_task);
  17. mm_init_cpumask(&init_mm);
  18. setup_command_line(command_line);
  19. setup_nr_cpu_ids();
  20. setup_per_cpu_areas();
  21. smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */
  22. build_all_zonelists( NULL, NULL);
  23. page_alloc_init(); //页内存申请初始化
  24. pr_notice( "Kernel command line: %s\n", boot_command_line); //打印内核启动命令行参数
  25. parse_early_param();
  26. parse_args( "Booting kernel", static_command_line, __start___param,
  27. __stop___param - __start___param,
  28. -1, -1, &unknown_bootoption);
  29. ……
  30. /*
  31. * Set up the scheduler prior starting any interrupts (such as the
  32. * timer interrupt). Full topology setup happens at smp_init()
  33. * time - but meanwhile we still have a functioning scheduler.
  34. */
  35. sched_init(); //进程调度器初始化
  36. /*
  37. * Disable preemption - early bootup scheduling is extremely
  38. * fragile until we cpu_idle() for the first time.
  39. */
  40. preempt_disable(); //禁止内核抢占
  41. if (WARN(!irqs_disabled(), "Interrupts were enabled *very* early, fixing it\n"))
  42. local_irq_disable(); //检查关闭CPU中断
  43. /*大量初始化内容 见名知意*/
  44. idr_init_cache();
  45. rcu_init();
  46. tick_nohz_init();
  47. context_tracking_init();
  48. radix_tree_init();
  49. /* init some links before init_ISA_irqs() */
  50. early_irq_init();
  51. init_IRQ();
  52. tick_init();
  53. init_timers();
  54. hrtimers_init();
  55. softirq_init();
  56. timekeeping_init();
  57. time_init();
  58. sched_clock_postinit();
  59. perf_event_init();
  60. profile_init();
  61. call_function_init();
  62. WARN(!irqs_disabled(), "Interrupts were enabled early\n");
  63. early_boot_irqs_disabled = false;
  64. local_irq_enable(); //本地中断可以使用了
  65. kmem_cache_init_late();
  66. /*
  67. * HACK ALERT! This is early. We're enabling the console before
  68. * we've done PCI setups etc, and console_init() must be aware of
  69. * this. But we do want output early, in case something goes wrong.
  70. */
  71. console_init(); //初始化控制台,可以使用printk了
  72. if (panic_later)
  73. panic( "Too many boot %s vars at `%s'", panic_later,
  74. panic_param);
  75. lockdep_info();
  76. /*
  77. * Need to run this when irqs are enabled, because it wants
  78. * to self-test [hard/soft]-irqs on/off lock inversion bugs
  79. * too:
  80. */
  81. locking_selftest();
  82. #ifdef CONFIG_BLK_DEV_INITRD
  83. if (initrd_start && !initrd_below_start_ok &&
  84. page_to_pfn(virt_to_page(( void *)initrd_start)) < min_low_pfn) {
  85. pr_crit( "initrd overwritten (0x%08lx < 0x%08lx) - disabling it.\n",
  86. page_to_pfn(virt_to_page(( void *)initrd_start)),
  87. min_low_pfn);
  88. initrd_start = 0;
  89. }
  90. #endif
  91. page_cgroup_init();
  92. debug_objects_mem_init();
  93. kmemleak_init();
  94. setup_per_cpu_pageset();
  95. numa_policy_init();
  96. if (late_time_init)
  97. late_time_init();
  98. sched_clock_init();
  99. calibrate_delay();
  100. pidmap_init();
  101. anon_vma_init();
  102. acpi_early_init();
  103. #ifdef CONFIG_X86
  104. if (efi_enabled(EFI_RUNTIME_SERVICES))
  105. efi_enter_virtual_mode();
  106. #endif
  107. #ifdef CONFIG_X86_ESPFIX64
  108. /* Should be run before the first non-init thread is created */
  109. init_espfix_bsp();
  110. #endif
  111. thread_info_cache_init();
  112. cred_init();
  113. fork_init(totalram_pages); //初始化fork
  114. proc_caches_init();
  115. buffer_init();
  116. key_init();
  117. security_init();
  118. dbg_late_init();
  119. vfs_caches_init(totalram_pages); //虚拟文件系统初始化
  120. signals_init();
  121. /* rootfs populating might need page-writeback */
  122. page_writeback_init();
  123. #ifdef CONFIG_PROC_FS
  124. proc_root_init();
  125. #endif
  126. cgroup_init();
  127. cpuset_init();
  128. taskstats_init_early();
  129. delayacct_init();
  130. check_bugs();
  131. sfi_init_late();
  132. if (efi_enabled(EFI_RUNTIME_SERVICES)) {
  133. efi_late_init();
  134. efi_free_boot_services();
  135. }
  136. ftrace_init();
  137. /* Do the rest non-__init'ed, we're now alive */
  138. rest_init();
  139. }

函数最后调用rest_init()函数


 
 
  1. /*最重要使命:创建kernel_init进程,并进行后续初始化*/
  2. static noinline void __init_refok rest_init( void)
  3. {
  4. int pid;
  5. rcu_scheduler_starting();
  6. /*
  7. * We need to spawn init first so that it obtains pid 1, however
  8. * the init task will end up wanting to create kthreads, which, if
  9. * we schedule it before we create kthreadd, will OOPS.
  10. */
  11. kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND); //创建kernel_init进程
  12. numa_default_policy();
  13. pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
  14. rcu_read_lock();
  15. kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
  16. rcu_read_unlock();
  17. complete(&kthreadd_done);
  18. /*
  19. * The boot idle thread must execute schedule()
  20. * at least once to get things moving:
  21. */
  22. init_idle_bootup_task(current);
  23. schedule_preempt_disabled();
  24. /* Call into cpu_idle with preempt disabled */
  25. //cpu_idle就是在系统闲置时用来降低电力的使用和减少热的产生的空转函数,函数至此不再返回,其余工作从kernel_init进程处发起
  26. cpu_startup_entry(CPUHP_ONLINE);
  27. }

kernel_init函数将完成设备驱动程序的初始化,并调用init_post函数启动用户进程

部分书籍介绍的内核启动流程基于经典的2.6版本,kernel_init函数还会调用init_post函数专门负责_init进程的启动,现版本已经被整合到了一起。


 
 
  1. static int __ ref kernel_init(void *unused)
  2. {
  3. int ret;
  4. kernel_init_freeable(); //该函数中完成smp开启 驱动初始化 共享内存初始化等工作
  5. /* need to finish all async __init code before freeing the memory */
  6. async_synchronize_full();
  7. free_initmem(); //初始化尾声,清除内存无用数据
  8. mark_rodata_ro();
  9. system_state = SYSTEM_RUNNING;
  10. numa_default_policy();
  11. flush_delayed_fput();
  12. if (ramdisk_execute_command) {
  13. ret = run_init_process(ramdisk_execute_command);
  14. if (!ret)
  15. return 0;
  16. pr_err( "Failed to execute %s (error %d)\n",
  17. ramdisk_execute_command, ret);
  18. }
  19. /*
  20. * We try each of these until one succeeds.
  21. *
  22. * The Bourne shell can be used instead of init if we are
  23. * trying to recover a really broken machine.
  24. *寻找init函数,创建一号进程_init (第一个用户空间进程)*/
  25. if (execute_command) {
  26. ret = run_init_process(execute_command);
  27. if (!ret)
  28. return 0;
  29. pr_err( "Failed to execute %s (error %d). Attempting defaults...\n",
  30. execute_command, ret);
  31. }
  32. if (!try_to_run_init_process( "/sbin/init") ||
  33. !try_to_run_init_process( "/etc/init") ||
  34. !try_to_run_init_process( "/bin/init") ||
  35. !try_to_run_init_process( "/bin/sh"))
  36. return 0;
  37. panic( "No working init found. Try passing init= option to kernel. "
  38. "See Linux Documentation/init.txt for guidance.");
  39. }
  40. static int __ ref kernel_init(void *unused)
  41. {
  42. int ret;
  43. kernel_init_freeable(); //该函数中完成smp开启 驱动初始化 共享内存初始化等工作
  44. /* need to finish all async __init code before freeing the memory */
  45. async_synchronize_full();
  46. free_initmem(); //初始化尾声,清除内存无用数据
  47. mark_rodata_ro();
  48. system_state = SYSTEM_RUNNING;
  49. numa_default_policy();
  50. flush_delayed_fput();
  51. if (ramdisk_execute_command) {
  52. ret = run_init_process(ramdisk_execute_command);
  53. if (!ret)
  54. return 0;
  55. pr_err( "Failed to execute %s (error %d)\n",
  56. ramdisk_execute_command, ret);
  57. }
  58. /*
  59. * We try each of these until one succeeds.
  60. *
  61. * The Bourne shell can be used instead of init if we are
  62. * trying to recover a really broken machine.
  63. *寻找init函数,创建一号进程_init (第一个用户空间进程)*/
  64. if (execute_command) {
  65. ret = run_init_process(execute_command);
  66. if (!ret)
  67. return 0;
  68. pr_err( "Failed to execute %s (error %d). Attempting defaults...\n",
  69. execute_command, ret);
  70. }
  71. if (!try_to_run_init_process( "/sbin/init") ||
  72. !try_to_run_init_process( "/etc/init") ||
  73. !try_to_run_init_process( "/bin/init") ||
  74. !try_to_run_init_process( "/bin/sh"))
  75. return 0;
  76. panic( "No working init found. Try passing init= option to kernel. "
  77. "See Linux Documentation/init.txt for guidance.");
  78. }

 

到此,内核初始化已经接近尾声,所有的初始化函数都已经调用,因此free_initmem函数可以舍弃内存的__init_begin至__init_end之间的数据。

当内核被引导并进行初始化后,内核启动了自己的第一个用户空间应用程序_init,这是调用的第一个使用标准C库编译的程序,其进程编号时钟为1.

_init负责出发其他必须的进程,以使系统进入整体可用的状态。

 

以下为内核启动流程图:

![在这里插入图片描述](https://img-blog.csdn.net/20170315225401860?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY2MyNDM0OTQ5MjY=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值