当前位置: 代码迷 >> 综合 >> Linux-4.1.15启动分析
  详细解决方案

Linux-4.1.15启动分析

热度:16   发布时间:2023-10-24 00:48:56.0

一:链接脚本分析启动入口

arch/arm/kernel/vmlinux.lds

 *  arch/arm/include/asm/page.h**  Copyright (C) 1995-2003 Russell King** This program is free software; you can redistribute it and/or modify* it under the terms of the GNU General Public License version 2 as* published by the Free Software Foundation.*/
OUTPUT_ARCH(arm)
ENTRY(stext)
jiffies = jiffies_64;
SECTIONS
{... ...

由链接脚本可知启动入口为“ENTRY(stext)”,位于arch/arm/kernel/head.S

二:Linux启动流程分析

1、arch/arm/kernel/head.S

注释说明linux内核启动之前要求关闭MMU,关闭D-cache,不关心I-cache,r0=0.....

/** Kernel startup entry point.* ---------------------------** This is normally called from the decompressor code.  The requirements* are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,* r1 = machine nr, r2 = atags or dtb pointer.*/ENTRY(stext)
... ...safe_svcmode_maskall r9    @ 确保CPU处于SVC模式,并且关闭了所有中断mrc	p15, 0, r9, c0, c0		@ get processor id  
(1)	bl	__lookup_processor_type		@ r5=procinfo r9=cpuidmovs	r10, r5				@ invalid processor (r5=0)?THUMB( it	eq )		@ force fixup-able long branch encodingbeq	__error_p			@ yes, error 'p'... .../** r1 = machine no, r2 = atags or dtb,* r8 = phys_offset, r9 = cpuid, r10 = procinfo*/
(2)	bl	__vet_atags
... ...bl	__create_page_tables  @ 创建页表/** The following calls CPU specific code in a position independent* manner.  See arch/arm/mm/proc-*.S for details.  r10 = base of* xxx_proc_info structure selected by __lookup_processor_type* above.  On return, the CPU will be ready for the MMU to be* turned on, and r0 will hold the CPU control register value.*/
(3)	ldr	r13, =__mmap_switched		@ address to jump to after@ mmu has been enabledadr	lr, BSYM(1f)			@ return (PIC) addressmov	r8, r4				@ set TTBR1 to swapper_pg_dirldr	r12, [r10, #PROCINFO_INITFUNC]add	r12, r12, r10ret	r12
(4)	b	__enable_mmu

(1)bl    __lookup_processor_type   检查当前系统是否支持此CPU,如果支持就获取procinfo信息保存在proc_info_list结构体中。

__lookup_processor_type:adr	r3, __lookup_processor_type_dataldmia	r3, {r4 - r6}sub	r3, r3, r4			@ get offset between virt&physadd	r5, r5, r3			@ convert virt addresses toadd	r6, r6, r3			@ physical address space
1:	ldmia	r5, {r3, r4}			@ value, maskand	r4, r4, r9			@ mask wanted bitsteq	r3, r4beq	2fadd	r5, r5, #PROC_INFO_SZ		@ sizeof(proc_info_list)cmp	r5, r6blo	1bmov	r5, #0				@ unknown processor
2:	ret	lr
ENDPROC(__lookup_processor_type)struct proc_info_list {unsigned int		cpu_val;unsigned int		cpu_mask;unsigned long		__cpu_mm_mmu_flags;	/* used by head.S */unsigned long		__cpu_io_mmu_flags;	/* used by head.S */unsigned long		__cpu_flush;		/* used by head.S */const char		*arch_name;const char		*elf_name;unsigned int		elf_hwcap;const char		*cpu_name;struct processor	*proc;struct cpu_tlb_fns	*tlb;struct cpu_user_fns	*user;struct cpu_cache_fns	*cache;
};

(2)bl    __vet_atags    验证atags或设备树(dtb)的合法性

__vet_atags:tst	r2, #0x3			@ aligned?bne	1fldr	r5, [r2, #0]
#ifdef CONFIG_OF_FLATTREEldr	r6, =OF_DT_MAGIC		@ is it a DTB?cmp	r5, r6beq	2f
#endifcmp	r5, #ATAG_CORE_SIZE		@ is first tag ATAG_CORE?cmpne	r5, #ATAG_CORE_SIZE_EMPTYbne	1fldr	r5, [r2, #4]ldr	r6, =ATAG_COREcmp	r5, r6bne	1f2:	ret	lr				@ atag/dtb pointer is ok1:	mov	r2, #0ret	lr
ENDPROC(__vet_atags)

(3)    ldr    r13, =__mmap_switched  将函数地址保存在r13寄存器中,最终调用start_kernel函数

__mmap_switched:adr	r3, __mmap_switched_dataldmia	r3!, {r4, r5, r6, r7}cmp	r4, r5				@ Copy data segment if needed
1:	cmpne	r5, r6ldrne	fp, [r4], #4strne	fp, [r5], #4bne	1bmov	fp, #0				@ Clear BSS (and zero fp)
1:	cmp	r6, r7strcc	fp, [r6],#4bcc	1bARM(	ldmia	r3, {r4, r5, r6, r7, sp})THUMB(	ldmia	r3, {r4, r5, r6, r7}	)THUMB(	ldr	sp, [r3, #16]		)str	r9, [r4]			@ Save processor IDstr	r1, [r5]			@ Save machine typestr	r2, [r6]			@ Save atags pointercmp	r7, #0strne	r0, [r7]			@ Save control register valuesb	start_kernel
ENDPROC(__mmap_switched)

(4)    b    __enable_mmu    调用__turn_mmu_on函数打开MMU,最后执行r13寄存器中保存的函数,即(3)中讲到的start_kernel函数

__enable_mmu:... ...b	__turn_mmu_on
ENDPROC(__enable_mmu)
ENTRY(__turn_mmu_on)mov	r0, r0instr_syncmcr	p15, 0, r0, c1, c0, 0		@ write control regmrc	p15, 0, r3, c0, c0, 0		@ read id reginstr_syncmov	r3, r3mov	r3, r13ret	r3
__turn_mmu_on_end:
ENDPROC(__turn_mmu_on)

2、\init\Main.c 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();                            @ 死锁检测模块,此函数会初始化两个hash表,需优先执行set_task_stack_end_magic(&init_task);      @ 设置任务栈结束魔术数,用于栈溢出检测smp_setup_processor_id();                  @ 多核处理器,设置处理器IDdebug_objects_early_init();                @ debug相关初始化/** Set up the the initial canary ASAP:*/boot_init_stack_canary();                  @ 栈溢出检测初始化cgroup_init_early();                       @ cgroup控制linux系统资源初始化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);             @ 打印linux版本号,编译时间等信息setup_arch(&command_line);                 @ 架构初始化,会解析传递进来的atags或者设备树文件。mm_init_cpumask(&init_mm);                 @ 内存初始化setup_command_line(command_line);          @ 存储命令行参数setup_nr_cpu_ids();                        @ 获取CPU核心数setup_per_cpu_areas();                     @ 设置每个CPU数据smp_prepare_boot_cpu();	/* arch-specific boot-cpu hooks */build_all_zonelists(NULL, NULL);           @ 建立系统内存页区(zone)链表page_alloc_init();                         @ 处理用于热插拔CPU的页pr_notice("Kernel command line: %s\n", boot_command_line);parse_early_param();                       @ 解析命令行中的console参数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);                           @ 设置log使用的缓冲区pidhash_init();                             @ 构建PID哈希表vfs_caches_init_early();                    @ 预先初始化vfs的目录项和索引节点缓存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();                           @ IDR初始化rcu_init();                                 @ 初始化RCU/* trace_printk() and trace points may be used after this */trace_init();                               @ 跟踪调试相关初始化context_tracking_init();    radix_tree_init();                          @ 基数树相关数据结构初始化/* init some links before init_ISA_irqs() */early_irq_init();                           @ 中断初始化init_IRQ();tick_init();                                @ tick初始化rcu_init_nohz();    init_timers();                              @ 初始化定时器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();                         @ 使能中断kmem_cache_init_late();                     @ linux内存分配器slab初始化/** 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();                         @ 锁自测#ifdef CONFIG_BLK_DEV_INITRDif (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;}
#endifpage_ext_init();debug_objects_mem_init();kmemleak_init();                             @ 检测内存泄漏初始化setup_per_cpu_pageset();numa_policy_init();if (late_time_init)late_time_init();sched_clock_init();calibrate_delay();pidmap_init();                               @ PID位图初始化anon_vma_init();                             @ 生成anon_vma slab缓存acpi_early_init();
#ifdef CONFIG_X86if (efi_enabled(EFI_RUNTIME_SERVICES))efi_enter_virtual_mode();
#endif
#ifdef CONFIG_X86_ESPFIX64/* Should be run before the first non-init thread is created */init_espfix_bsp();
#endifthread_info_cache_init();cred_init();    fork_init();                                 @ 初始化一些结构体以使用fork函数proc_caches_init();                          @ 给各种资源管理结构分配缓存buffer_init();                               @ 初始化缓冲缓存key_init();                                  @ 初始化密匙security_init();dbg_late_init();vfs_caches_init(totalram_pages);             @ 为VFS创建缓存signals_init();                              @ 初始化信号/* rootfs populating might need page-writeback */page_writeback_init();                       @ 页回写初始化proc_root_init();                            @ 注册并挂载proc文件系统nsfs_init();cpuset_init();                               @ 初始化cpusetcgroup_init();                               @ 初始化cgrouptaskstats_init_early();                      @ 进程状态初始化delayacct_init();check_bugs();                                @ 检查写缓存一致性acpi_subsystem_init();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函数
}

rest_init()函数功能介绍:创建进程,线程

static noinline void __init_refok rest_init(void)
{int pid;rcu_scheduler_starting();                                     @ 启动RCU锁调度器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);                   @ 创建kernel_init线程,也就是init内核进程,进程PID为1.numa_default_policy();pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);  @ 创建kthreadd内核进程,PID为2,负责所有内核进程的调度和管理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);                               @ 进入idle空闲进程,PID为0,其他进程要工作需抢占idle进程
}

kernel_init()函数功能介绍:寻找init进程,启动linux内核

static int __ref kernel_init(void *unused)
{int ret;kernel_init_freeable();                                         @ 初始化init进程/* 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) {                                  @ 其值为“/init”,也就是根目录下的init程序。              ret = run_init_process(ramdisk_execute_command);            @ 如果存在“/init”程序就执行run_init_process函数来运行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) {                                           @ 同上,寻找一个一个可以运行的init程序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;@ 都为空,linux启动失败!panic("No working init found.  Try passing init= option to kernel. ""See Linux Documentation/init.txt for guidance.");
}

    kernel_init_freeable(): 初始化init进程

static noinline void __init kernel_init_freeable(void)
{/** Wait until kthreadd is all set-up.*/wait_for_completion(&kthreadd_done);        @ 等待kthreadd进程准备就绪... ...smp_init();                                 @ SMP初始化sched_init_smp();                           @ 多核(SMP)调度初始化do_basic_setup();                           @ linux设备驱动初始化,会调用driver_init完成linux下驱动模型子系统的初始化@ 以标准输入(0)的方式打开设备“/dev/console”文件描述符/* Open the /dev/console on the rootfs, this should never fail */if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)pr_err("Warning: unable to open an initial console.\n");(void) sys_dup(0);                          @ 标准输出(1),标准错误(2)(void) sys_dup(0);/** check if there is an early userspace init.  If yes, let it do all* the work*/if (!ramdisk_execute_command)ramdisk_execute_command = "/init";if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {ramdisk_execute_command = NULL;prepare_namespace();                    @ 挂载根文件系统}/** Ok, we have completed the initial bootup, and* we're essentially up and running. Get rid of the* initmem segments and start the user-mode stuff..** rootfs is available now, try loading the public keys* and default modules*/integrity_load_keys();load_default_modules();
}