1.1 内核启动流程
Uboot打印完“Starting kernel…”,就完成自己的使命,接下来控制权就交给了内核。由于Linux内核的启动流程是一个复杂而典型的过程,涉及多个关键步骤,且要比uboot启动流程复杂得多,因此下面我们简要分析。
下图是内核启动流程图:
1. OUTPUT_ARCH(aarch64)
2. ENTRY(stext)
3. jiffies = jiffies_64;
4. SECTIONS
5. {
6. /DISCARD/ : {
7. *(.ARM.exidx.exit.text) *(.ARM.extab.exit.text) *(.ARM.exidx.text.exit) *(.ARM.extab.text.exit) *(.exitcall.exit) *(.discard) *(.discard.*)
8. } 复制代码
1.2 链接脚本
首先看到内核的链接脚本,在arch/arm64/kernel/vmlinux.lds:
1. OUTPUT_ARCH(aarch64)
2. ENTRY(stext)
3. jiffies = jiffies_64;
4. SECTIONS
5. {
6. /DISCARD/ : {
7. *(.ARM.exidx.exit.text) *(.ARM.extab.exit.text) *(.ARM.exidx.text.exit) *(.ARM.extab.text.exit) *(.exitcall.exit) *(.discard) *(.discard.*)
8. }
一键获取完整项目代码
第2行,ENTRY指明了kernel入口为stext,stext是一个汇编函数,定义在文件arch/arm64/kernel/head.S中,
该文件是ARM架构下Linux内核启动的早期汇编代码,负责处理器模式设置,页表初始化等关键操作:
1. /*
2. * Kernel startup entry point.
3. * ---------------------------
4. *
5. * This is normally called from the decompressor code. The requirements
6. * are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,
7. * r1 = machine nr, r2 = atags or dtb pointer.
8. *
9. * This code is mostly position independent, so if you link the kernel at
10. * 0xc0008000, you call this at __pa(0xc0008000).
11. *
12. * See linux/arch/arm/tools/mach-types for the complete list of machine
13. * numbers for r1.
14. *
15. * We're trying to keep crap to a minimum; DO NOT add any machine specific
16. * crap here - that's what the boot loader (or in extreme, well justified
17. * circumstances, zImage) is for.
18. */
19. .arm
20.
21. __HEAD
22. ENTRY(stext)
23. ARM_BE8(setend be ) @ 确保BE8字节
24.
25. THUMB( badr r9, 1f ) @ Kernel is always entered in ARM.
26. THUMB( bx r9 ) @ If this is a Thumb-2 kernel,
27. THUMB( .thumb ) @ 切换到Thumb模式.
28. THUMB(1: )
29.
30. #ifdef CONFIG_ARM_VIRT_EXT
31. bl __hyp_stub_install @ 虚拟化扩展支持
32. #endif
33. @ ensure svc mode and all interrupts masked
34. safe_svcmode_maskall r9 @ 强制进入SVC模式,关闭中断
35.
36. mrc p15, 0, r9, c0, c0 @ 读取处理器ID
37. bl __lookup_processor_type @ 验证处理器支持 r5=procinfo r9=cpuid
38. movs r10, r5 @ invalid processor (r5=0)?
39. THUMB( it eq ) @ force fixup-able long branch encoding
40. beq __error_p @ yes, error 'p'
41.
42. #ifdef CONFIG_ARM_LPAE
43. mrc p15, 0, r3, c0, c1, 4 @ read ID_MMFR0
44. and r3, r3, #0xf @ extract VMSA support
45. cmp r3, #5 @ long-descriptor translation table format?
46. THUMB( it lo ) @ force fixup-able long branch encoding
47. blo __error_lpae @ only classic page table format
48. #endif
49.
50. #ifndef CONFIG_XIP_KERNEL
51. adr r3, 2f @ 获取运行时地址
52. ldmia r3, {r4, r8}
53. sub r4, r3, r4 @ 计算物理偏移量(PHYS_OFFSET - PAGE_OFFSET)
54. add r8, r8, r4 @ 最终physical偏移 => 8 (PHYS_OFFSET)
55. #else
56. ldr r8, =PLAT_PHYS_OFFSET @ XIP内核直接使用常量
57. #endif
58.
59. /*
60. * r1 = machine no, r2 = atags or dtb,
61. * r8 = phys_offset, r9 = cpuid, r10 = procinfo
62. */
63. bl __vet_atags
64. #ifdef CONFIG_SMP_ON_UP
65. bl __fixup_smp
66. #endif
67. #ifdef CONFIG_ARM_PATCH_PHYS_VIRT
68. bl __fixup_pv_table
69. #endif
70. bl __create_page_tables @ 创建页表
71.
72. /*
73. * The following calls CPU specific code in a position independent
74. * manner. See arch/arm/mm/proc-*.S for details. r10 = base of
75. * xxx_proc_info structure selected by __lookup_processor_type
76. * above.
77. *
78. * The processor init function will be called with:
79. * r1 - machine type
80. * r2 - boot data (atags/dt) pointer
81. * r4 - translation table base (low word)
82. * r5 - translation table base (high word, if LPAE)
83. * r8 - translation table base 1 (pfn if LPAE)
84. * r9 - cpuid
85. * r13 - virtual address for __enable_mmu -> __turn_mmu_on
86. *
87. * On return, the CPU will be ready for the MMU to be turned on,
88. * r0 will hold the CPU control register value, r1, r2, r4, and
89. * r9 will be preserved. r5 will also be preserved if LPAE.
90. */
91. ldr r13, =__mmap_switched @ address to jump to after
92. @ mmu has been enabled
93. badr lr, 1f @ return (PIC) address
94. #ifdef CONFIG_ARM_LPAE
95. mov r5, #0 @ high TTBR0
96. mov r8, r4, lsr #12 @ TTBR1 is swapper_pg_dir pfn
97. #else
98. mov r8, r4 @ set TTBR1 to swapper_pg_dir
99. #endif
100. ldr r12, [r10, #PROCINFO_INITFUNC]
101. add r12, r12, r10
102. ret r12
103. 1: b __enable_mmu
104. ENDPROC(stext)
105. .ltorg
106. #ifndef CONFIG_XIP_KERNEL
107. 2: .long .
108. .long PAGE_OFFSET
109. #endif
一键获取完整项目代码
由前面提示可知bootloader在跳转到kernel前需要确保如下配置:
关闭MMU
关闭D-cache
I-Cache无所谓
r0=0
r1=machine nr(也就是机器ID)
r2=atags或者设备树(dtb)首地址
Linux内核的入口电stext其实相当于内核的入口函数,具体内容就不一一解释了,主要看一下如何找到内核入口函数start_kernel。
第91行,将函数_mmap_switched的地址保存到r13寄存器中,_mmap_switched定义在文件arch/arm/kernel/head-common.S中,该函数最终会调用start_kernel函数
第103行,调用_enable_mmu函数使能MMU,_enable_mmu定义在文件arch/arm/kernel/head.S中,该函数最终会通过调用_turn_mmu_on来打开MMU,_turn_mmu_on最后会执行r13寄存器里面保存的_mmap_switched函数。
_mmap_switched函数:
1. __mmap_switched:
2.
3. mov r7, r1
4. mov r8, r2
5. mov r10, r0
6.
7. adr r4, __mmap_switched_data
8. mov fp, #0
9.
10. #if defined(CONFIG_XIP_DEFLATED_DATA)
11. ARM( ldr sp, [r4], #4 )
12. THUMB( ldr sp, [r4] )
13. THUMB( add r4, #4 )
14. bl __inflate_kernel_data @ decompress .data to RAM
15. teq r0, #0
16. bne __error
17. #elif defined(CONFIG_XIP_KERNEL)
18. ARM( ldmia r4!, {r0, r1, r2, sp} )
19. THUMB( ldmia r4!, {r0, r1, r2, r3} )
20. THUMB( mov sp, r3 )
21. sub r2, r2, r1
22. bl memcpy @ copy .data to RAM
23. #endif
24.
25. ARM( ldmia r4!, {r0, r1, sp} )
26. THUMB( ldmia r4!, {r0, r1, r3} )
27. THUMB( mov sp, r3 )
28. sub r2, r1, r0
29. mov r1, #0
30. bl memset @ clear .bss
31.
32. ldmia r4, {r0, r1, r2, r3}
33. str r9, [r0] @ Save processor ID
34. str r7, [r1] @ Save machine type
35. str r8, [r2] @ Save atags pointer
36. cmp r3, #0
37. strne r10, [r3] @ Save control register values
38. mov lr, #0
39. b start_kernel
40. ENDPROC(__mmap_switched)
一键获取完整项目代码
第39行,可以看到调用了start_kernel函数来启动Linux内核,而start_kernel函数定义在文件init/main.c中。
1.3 start_kernel函数
start_kernel函数是Linux内核初始化阶段的核心入口函数:
1. asmlinkage __visible void __init start_kernel(void)
2. {
3. char *command_line;
4. char *after_dashes;
5. /*基本子系统设置*/
6. set_task_stack_end_magic(&init_task); /* 为init_task(进程0)设置栈底魔术字(0x57AC6E9D)*/
7. smp_setup_processor_id(); /*设置当前CPU ID(对于SMP系统)*/
8. debug_objects_early_init(); /*初始化调试对象追踪(链表校验等)*/
9.
10. cgroup_init_early(); /*初始化控制组(cgroup)子系统基础结构*/
11.
12. local_irq_disable(); /*确保本地中断关闭*/
13. early_boot_irqs_disabled = true; /*标记中断状态*/
14.
15. /*
16. * Interrupts are still disabled. Do necessary setups, then
17. * enable them.
18. */
19. /*CPU与内存初始化*/
20. boot_cpu_init(); /*激活引导CPU(设置possible/present/active三个掩码)*/
21. page_address_init(); /*初始化高端内存页地址跟踪结构(非64位系统需要)*/
22. pr_notice("%s", linux_banner); /*打印内核版本信息*/
23. setup_arch(&command_line); /*结构相关初始化(关键步骤,解析设备树/EFI等)*/
24. /*
25. * Set up the the initial canary and entropy after arch
26. * and after adding latent and command line entropy.
27. */
28. add_latent_entropy();
29. add_device_randomness(command_line, strlen(command_line));
30. boot_init_stack_canary();
31. /*内存管理*/
32. mm_init_cpumask(&init_mm); /*初始化init_mm的CPU掩码*/
33. setup_command_line(command_line); /*保存原始命令行参数到boot_command_line*/
34. setup_nr_cpu_ids(); /*确定系统中CPU数量*/
35. setup_per_cpu_areas(); /*设置每CPU数据区域(SMP相关)*/
36. smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */
37. boot_cpu_hotplug_init();
38.
39. build_all_zonelists(NULL); /*构建内存区域列表*/
40. page_alloc_init(); /*页分配器初始化*/
41.
42. pr_notice("Kernel command line: %s", boot_command_line);
43. /*命令行参数处理*/
44. parse_early_param();
45. after_dashes = parse_args("Booting kernel",
46. static_command_line, __start___param,
47. __stop___param - __start___param,
48. -1, -1, NULL, &unknown_bootoption);
49. if (!IS_ERR_OR_NULL(after_dashes))
50. parse_args("Setting init args", after_dashes, NULL, 0, -1, -1,
51. NULL, set_init_arg);
52.
53. jump_label_init(); /*初始化动态代码补丁机制*/
54.
55. /*
56. * These use large bootmem allocations and must precede
57. * kmem_cache_init()
58. */
59. setup_log_buf(0); /*设置内核环形日志缓冲区*/
60. vfs_caches_init_early(); /*虚拟文件系统早期初始化(dcache, icache)*/
61. sort_main_extable();
62. trap_init(); /*设置异常向量表(x86: IDT, ARM: vector table)*/
63. mm_init(); /*内存管理子系统核心初始化(mem_init执行前准备)*/
64.
65. ftrace_init();
66.
67. /* trace_printk can be enabled here */
68. early_trace_init();
69.
70. /*
71. * Set up the scheduler prior starting any interrupts (such as the
72. * timer interrupt). Full topology setup happens at smp_init()
73. * time - but meanwhile we still have a functioning scheduler.
74. */
75. sched_init(); /*初始化进程调度器(创建运行队列,初始化调度类)*/
76. /*
77. * Disable preemption - early bootup scheduling is extremely
78. * fragile until we cpu_idle() for the first time.
79. */
80. preempt_disable(); /*禁用抢占(直到第一个进程启动)*/
81. if (WARN(!irqs_disabled(),
82. "Interrupts were enabled *very* early, fixing it"))
83. local_irq_disable();
84. radix_tree_init();
85.
86. /*
87. * Set up housekeeping before setting up workqueues to allow the unbound
88. * workqueue to take non-housekeeping into account.
89. */
90. housekeeping_init();
91.
92. /*
93. * Allow workqueue creation and work item queueing/cancelling
94. * early. Work item execution depends on kthreads and starts after
95. * workqueue_init().
96. */
97. workqueue_init_early();
98.
99. rcu_init();
100.
101. /* Trace events are available after this */
102. trace_init();
103.
104. if (initcall_debug)
105. initcall_debug_enable();
106.
107. context_tracking_init();
108. /* init some links before init_ISA_irqs() */
109. early_irq_init(); /*分配中断描述符表(irq_desc数组)*/
110. init_IRQ(); /*架构相关中断控制器初始化*/
111. tick_init(); /*初始化时钟事件框架*/
112. rcu_init_nohz();
113. init_timers();
114. hrtimers_init();
115. softirq_init();
116. timekeeping_init(); /*时间保持子系统(维护CLOCK_MONOTONIC等)*/
117. time_init(); /*架构相关时间初始化(读取RTC、校准TSC等)*/
118. printk_safe_init();
119. perf_event_init();
120. profile_init();
121. call_function_init();
122. WARN(!irqs_disabled(), "Interrupts were enabled early");
123.
124. early_boot_irqs_disabled = false;
125. local_irq_enable();
126.
127. kmem_cache_init_late();
128.
129. /*
130. * HACK ALERT! This is early. We're enabling the console before
131. * we've done PCI setups etc, and console_init() must be aware of
132. * this. But we do want output early, in case something goes wrong.
133. */
134. console_init(); /*初始化控制台(此时可输出到终端*/
135. printk("## start_kernel() -- > console_init()");
136. if (panic_later)
137. panic("Too many boot %s vars at `%s'", panic_later,
138. panic_param);
139.
140. lockdep_init();
141.
142. /*
143. * Need to run this when irqs are enabled, because it wants
144. * to self-test [hard/soft]-irqs on/off lock inversion bugs
145. * too:
146. */
147. locking_selftest();
148.
149. /*
150. * This needs to be called before any devices perform DMA
151. * operations that might use the SWIOTLB bounce buffers. It will
152. * mark the bounce buffers as decrypted so that their usage will
153. * not cause "plain-text" data to be decrypted when accessed.
154. */
155. mem_encrypt_init();
156.
157. #ifdef CONFIG_BLK_DEV_INITRD
158. if (initrd_start && !initrd_below_start_ok &&
159. page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {
160. pr_crit("initrd overwritten (0x%08lx < 0x%08lx) - disabling it.",
161. page_to_pfn(virt_to_page((void *)initrd_start)),
162. min_low_pfn);
163. initrd_start = 0;
164. }
165. #endif
166. page_ext_init();
167. kmemleak_init();
168. debug_objects_mem_init();
169. setup_per_cpu_pageset();
170. numa_policy_init();
171. acpi_early_init();
172. if (late_time_init)
173. late_time_init();
174. sched_clock_init();
175. calibrate_delay();
176. pid_idr_init();
177. anon_vma_init();
178. #ifdef CONFIG_X86
179. if (efi_enabled(EFI_RUNTIME_SERVICES))
180. efi_enter_virtual_mode();
181. #endif
182. thread_stack_cache_init();
183. cred_init();
184. fork_init();
185. proc_caches_init();
186. uts_ns_init();
187. buffer_init();
188. key_init();
189. security_init();
190. dbg_late_init();
191. vfs_caches_init();
192. pagecache_init();
193. signals_init();
194. seq_file_init();
195. proc_root_init(); /*挂在proc文件系统*/
196. nsfs_init();
197. cpuset_init();
198. cgroup_init();
199. taskstats_init_early();
200. delayacct_init();
201.
202. check_bugs();
203.
204. acpi_subsystem_init();
205. arch_post_acpi_subsys_init();
206. sfi_init_late();
207.
208. if (efi_enabled(EFI_RUNTIME_SERVICES)) {
209. efi_free_boot_services();
210. }
211.
212. /* Do the rest non-__init'ed, we're now alive */
213. printk("## run rest_init()");
214. rest_init(); /*启动用户空间*/
215. printk("## after rest_init ()");
216. }
一键获取完整项目代码
start_kernel函数时Linux内核中非常重要的函数,它是整个内核初始化的核心函数,负责初始化内核的各个子系统、驱动程序以及其它关键组件,并最终将控制权转移到用户空间。同时可以看到在start_kernel函数中调用了大量函数,而每一个函数都是一个庞大的知识点,如果想要学习Linux内核,那么这些函数就需要去详细的研究,本课程注重嵌入式Linux入门,因此不会去将太多关于Linux内核的知识。
这边看到最后第212行,start_kernel函数最后调用了rest_init函数,内核的生命周期就是从执行start_kernel函数的第一条语句开始的,直到最后调用rest_init函数,内核都将不再从这个函数中返回。
1.4 启动初始进程
1.4.1 进程概念
内核进程:内核进程是由内核创建和调度的线程,运行在内核态,用于处理内核的各类任务。与用户进程不同,内核进程不直接与用户空间交互,主要用于执行内核内部的工作,如处理中断、管理设备、调度任务等。
运行空间:内核进程运行在内核地址空间,而普通用户进行运行在用户地址空间
权限:内核进程可以直接访问内核数据结构,而用户进程通过系统调用与内核交互
交互:内核进程通常不与用户交互,其生命周期完全由内核管理
创建方式:内核进程的创建通常通过kernel_thread函数实现
用户进程:用户进程是在用户空间中执行的进程,用户通过编写和执行应用程序来创建用户进程。用户进程通过系统调用与内核交互,进行资源分配、文件操作、网络通信等。
运行空间:用户进程运行在用户态,受限于用户空间的权限,不能直接访问硬件和内核数据结构
权限:内核线程运行在内核态,具有更高的权限,能够直接操作内核资源
1.4.2 三个特殊进程
Linux下有三个特殊的进程,分别是idle进程、init进程、kthreadd进程。
idle进程(PID=0),其前身为init_task进程,它由系统自动创建,运行在内核态,是系统创建的第一个进程,也是唯一一个没有通过fork或者kernel_thread产生的进程。完成加载系统后,先后创建kernel_init进程(最终演变为init进程)和kthreadd进程,并最终演变idle进程。idle进程不参与进程调度机制,当系统中没有任何进程可以调度时,cpu就会进入该进程,以免cpu闲置,因此它也可以被称为空闲进程。
Linux在没有进程概念的情况下从初始化部分的代码一直执行到调用start_kernel函数为止,接着创建原始进程init_task(即0号进程)。原始进程init_task开始执行start_kernel函数完成Linux内核的初始化工作(包括初始化页表、初始化中断向量表、初始化系统时间等),直到执行到start_kernel函数中最后一个函数rest_init函数。在rest_init函数中开始产生另外两个进程,即init进程和kthreadd进程,最终原始进程演变为idle进程。
rest_init函数也定义在init/main.c中:
1. static noinline void __ref rest_init(void)
2. {
3. struct task_struct *tsk;
4. int pid;
5.
6. rcu_scheduler_starting();
7. /*
8. * We need to spawn init first so that it obtains pid 1, however
9. * the init task will end up wanting to create kthreads, which, if
10. * we schedule it before we create kthreadd, will OOPS.
11. */
12. pid = kernel_thread(kernel_init, NULL, CLONE_FS);
13. /*
14. * Pin init on the boot CPU. Task migration is not properly working
15. * until sched_init_smp() has been run. It will set the allowed
16. * CPUs for init to the non isolated CPUs.
17. */
18. rcu_read_lock();
19. tsk = find_task_by_pid_ns(pid, &init_pid_ns);
20. set_cpus_allowed_ptr(tsk, cpumask_of(smp_processor_id()));
21. rcu_read_unlock();
22.
23. numa_default_policy();
24. pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
25. rcu_read_lock();
26. kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
27. rcu_read_unlock();
28.
29. /*
30. * Enable might_sleep() and smp_processor_id() checks.
31. * They cannot be enabled earlier because with CONFIG_PREEMPT=y
32. * kernel_thread() would trigger might_sleep() splats. With
33. * CONFIG_PREEMPT_VOLUNTARY=y the init task might have scheduled
34. * already, but it's stuck on the kthreadd_done completion.
35. */
36. system_state = SYSTEM_SCHEDULING;
37.
38. complete(&kthreadd_done);
39.
40. /*
41. * The boot idle thread must execute schedule()
42. * at least once to get things moving:
43. */
44. schedule_preempt_disabled();
45. /* Call into cpu_idle with preempt disabled */
46. cpu_startup_entry(CPUHP_ONLINE);
47. }
一键获取完整项目代码
第12行,调用kernel_thread函数创建kernel_init内核进程也就是1号进程,负责系统初始化和管理用户空间的其他用户进程,但是在kthreadd进程后运行,该进程随后转向用户空间,并最终演变为init进程
第24行,调用kernel_thread函数创建kthreadd内核进程也就是2号进程,用于管理和调度其它内核进程,该进程一直在内核中
第46行,调用cpu_startup_entry函数,该函数又会调用cpu_idle_loop函数使得0号进程在启动完kernel_init和kthreadd进程后,加入到idle调度类自身退化为idle进程,即最终演变为idle进程。
接下来重点看一下init进程,此时还不是init进程,而是kernel_init进程,它运行在内核空间,kernel_init也定义在init/main.c中:
1. static int __ref kernel_init(void *unused)
2. {
3. int ret;
4.
5. kernel_init_freeable();
6. /* need to finish all async __init code before freeing the memory */
7. async_synchronize_full();
8. ftrace_free_init_mem();
9. jump_label_invalidate_initmem();
10. free_initmem();
11. mark_readonly();
12.
13. /*
14. * Kernel mappings are now finalized - update the userspace page-table
15. * to finalize PTI.
16. */
17. pti_finalize();
18.
19. system_state = SYSTEM_RUNNING;
20. numa_default_policy();
21.
22. rcu_end_inkernel_boot();
23.
24. if (ramdisk_execute_command) {
25. ret = run_init_process(ramdisk_execute_command);
26. if (!ret)
27. return 0;
28. pr_err("Failed to execute %s (error %d)",
29. ramdisk_execute_command, ret);
30. }
31.
32. /*
33. * We try each of these until one succeeds.
34. *
35. * The Bourne shell can be used instead of init if we are
36. * trying to recover a really broken machine.
37. */
38. if (execute_command) {
39. ret = run_init_process(execute_command);
40. if (!ret)
41. return 0;
42. panic("Requested init %s failed (error %d).",
43. execute_command, ret);
44. }
45. if (!try_to_run_init_process("/sbin/init") ||
46. !try_to_run_init_process("/etc/init") ||
47. !try_to_run_init_process("/bin/init") ||
48. !try_to_run_init_process("/bin/sh"))
49. return 0;
50.
51. panic("No working init found. Try passing init= option to kernel. "
52. "See Linux Documentation/admin-guide/init.rst for guidance.");
53. }
一键获取完整项目代码
第5行,通过调用kernel_init_freeable函数来做一些init进程初始化工作
第39行,通过调用run_init_process函数开始执行init程序,该init程序会替换kernel_init进程并从kernel进程,使得内核空间的kernel_init进程最终转化为第一个用户程序,也就是1号进程init,Linux中的所有用户进程都是由init进程创建并运行的。
最后看一下kernel_init_freeable函数,该函数定义在init/main.c中:
1. static noinline void __init kernel_init_freeable(void)
2. {
3. /*
4. * Wait until kthreadd is all set-up.
5. */
6. wait_for_completion(&kthreadd_done);
7.
8. /* Now the scheduler is fully set up and can do blocking allocations */
9. gfp_allowed_mask = __GFP_BITS_MASK;
10.
11. /*
12. * init can allocate pages on any node
13. */
14. set_mems_allowed(node_states[N_MEMORY]);
15.
16. cad_pid = task_pid(current);
17.
18. smp_prepare_cpus(setup_max_cpus);
19.
20. workqueue_init();
21.
22. init_mm_internals();
23.
24. do_pre_smp_initcalls();
25. lockup_detector_init();
26.
27. smp_init();
28. sched_init_smp();
29.
30. page_alloc_init_late();
31.
32. do_basic_setup();
33.
34. /* Open the /dev/console on the rootfs, this should never fail */
35. if (ksys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
36. pr_err("Warning: unable to open an initial console.");
37.
38. (void) ksys_dup(0);
39. (void) ksys_dup(0);
40. /*
41. * check if there is an early userspace init. If yes, let it do all
42. * the work
43. */
44.
45. if (!ramdisk_execute_command)
46. ramdisk_execute_command = "/init";
47.
48. if (ksys_access((const char __user *)
49. ramdisk_execute_command, 0) != 0) {
50. ramdisk_execute_command = NULL;
51. prepare_namespace();
52. }
53.
54. /*
55. * Ok, we have completed the initial bootup, and
56. * we're essentially up and running. Get rid of the
57. * initmem segments and start the user-mode stuff..
58. *
59. * rootfs is available now, try loading the public keys
60. * and default modules
61. */
62.
63. integrity_load_keys();
64. load_default_modules();
65. }
一键获取完整项目代码
第32行,调用do_basic_setup函数完成Linux下设备驱动初始化工作
第35行,打开设备“/dev/console ”,因为在Linux下一切皆为文件,所以/dev/console也是一个文件,此文件为控制文件。每个文件都有一个文件描述符,此处文件描述符为0,作为标准输入0。
第51行,调用prepare_namespace函数来挂在根文件系统 。根文件系统也是由命令行参数指定的,也就是uboot的bootargs环境变量。比如“root=/dev/mmcblk0p2 rootwait rw”就表示根文件系统在/dev/mmcblk0p2中,也就是SD分区2中。
至此,kernel启动流程分析结束!
————————————————
版权声明:本文为CSDN博主「米联客(milianke)」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u011570052/article/details/159748753