问答 店铺
热搜: ZYNQ FPGA discuz

QQ登录

只需一步,快速开始

微信登录

微信扫码,快速开始

微信扫一扫 分享朋友圈

已有 13 人浏览分享

开启左侧

ZYNQ 内核启动流程分析

[复制链接]
13 0
经验分享
经验分享: 01-AMD(XILINX) » X
1.1 内核启动流程
Uboot打印完“Starting kernel…”,就完成自己的使命,接下来控制权就交给了内核。由于Linux内核的启动流程是一个复杂而典型的过程,涉及多个关键步骤,且要比uboot启动流程复杂得多,因此下面我们简要分析。

下图是内核启动流程图:

  1. 1.        OUTPUT_ARCH(aarch64)
  2. 2.        ENTRY(stext)
  3. 3.        jiffies = jiffies_64;
  4. 4.        SECTIONS
  5. 5.        {
  6. 6.         /DISCARD/ : {
  7. 7.          *(.ARM.exidx.exit.text) *(.ARM.extab.exit.text) *(.ARM.exidx.text.exit) *(.ARM.extab.text.exit) *(.exitcall.exit) *(.discard) *(.discard.*)
  8. 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
越努力越幸运!加油!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

12

关注

71

粉丝

614

主题
精彩推荐
热门资讯
    网友晒图
      图文推荐
        
        • 微信公众平台

        • 扫描访问手机版