一:链接脚本分析启动入口
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();
}