Linux内核启动过程
内核启动的最终目的:运行应用程序,应用程序在根文件系统中,所以需要先挂载根文件系统。
启动文件:arch/arm/kernel/head.s
第一阶段:
先判断是否支持机器ID(uboot启动内核时传入的参数):
第一阶段功能:链接内核时使用的虚拟地址,设置页表、使能MMU。赋值数据段、清除BSS段、调用
start_kernel
函数
ENTRY(stext)
msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode
@ and irqs disabled
mrc p15, 0, r9, c0, c0 @ get processor id
bl __lookup_processor_type @ r5=procinfo r9=cpuid
movs r10, r5 @ invalid processor (r5=0)?
beq __error_p @ yes, error 'p'
bl __lookup_machine_type @ r5=machinfo
movs r8, r5 @ invalid machine (r5=0)?
beq __error_a @ yes, error 'a'
bl __create_page_tables
ldr r13, __switch_data @ address to jump to after
@ mmu has been enabled
adr lr, __enable_mmu @ return (PIC) address
add pc, r10, #PROCINFO_INITFUNC
__lookup_processor_type
判断是否支持的机器id,linux支持的机器id位于arch/arm/include/generated/asm/mach-types.h
文件中,
bl __create_page_tables
创建页表。
adr lr, __enable_mmu
mmu使能后,会跳到__switch_data
中 。
.type __switch_data, %object
__switch_data:
.long __mmap_switched
.long __data_loc @ r4
.long __data_start @ r5
.long __bss_start @ r6
.long _end @ r7
.long processor_id @ r4
.long __machine_arch_type @ r5
.long cr_alignment @ r6
.long init_thread_union + THREAD_START_SP @ sp
.type __mmap_switched, %function
__mmap_switched:
adr r3, __switch_data + 4
ldmia r3!, {r4, r5, r6, r7}
cmp r4, r5 @ Copy data segment if needed
1: cmpne r5, r6
ldrne fp, [r4], #4
strne fp, [r5], #4
bne 1b
mov fp, #0 @ Clear BSS (and zero fp)
1: cmp r6, r7
strcc fp, [r6],#4
bcc 1b
ldmia r3, {r4, r5, r6, sp}
str r9, [r4] @ Save processor ID
str r1, [r5] @ Save machine type
bic r4, r0, #CR_A @ Clear 'A' bit
stmia r6, {r0, r4} @ Save control register values
b start_kernel
最终会跳入start_kernel
,这个就是内核启动的第一个C函数,进入第二阶段。
第二阶段:start_kernel
函数
第二阶段作用:进行内核初始化的全部工作,最后调用rest_init函数启动init过程。
start_kernel
位于init/Main.c
中。
start_kernel
中调用:
setup_arch(&command_line);//是处理 UBOOT传进来的启动参数的。获取内存地址空间的起始地址和大小。
setup_command_line(command_line); //处理命令行参数(来源于uboot`bootargs`环境变量)
如命令行:bootargc=ninitrd root=/dev/mtdblock3 init=/linuxrc console=tty SAC0 setup arch
,指定mount_root
挂载根文件系统到/dev/mtdblock3
上,终端设备是tty SAC0
最终会调用rest_init
函数,挂载根文件系统,启动应用。
static void noinline __init_refok rest_init(void)
__releases(kernel_lock)
{
int pid;
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
numa_default_policy();
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
kthreadd_task = find_task_by_pid(pid);
unlock_kernel();
/*
* The boot idle thread must execute schedule()
* at least one to get things moving:
*/
preempt_enable_no_resched();
schedule();
preempt_disable();
/* Call into cpu_idle with preempt disabled */
cpu_idle();
}
该函数会创建进程kernel_init
static int __init kernel_init(void * unused)
{
lock_kernel();
/*
* init can run on any cpu.
*/
set_cpus_allowed(current, CPU_MASK_ALL);
/*
* Tell the world that we're going to be the grim
* reaper of innocent orphaned children.
*
* We don't want people to have to make incorrect
* assumptions about where in the task array this
* can be found.
*/
init_pid_ns.child_reaper = current;
__set_special_pids(1, 1);
cad_pid = task_pid(current);
smp_prepare_cpus(max_cpus);
do_pre_smp_initcalls();
smp_init();
sched_init_smp();
cpuset_init_smp();
do_basic_setup();
/*
* 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..
*/
init_post();
return 0;
}
其中prepare_namespace()
会调用mount_root()
挂载根文件系统。
挂载完根文件系统后调用init_post()
启动应用程序
static int noinline init_post(void)
{
free_initmem();
unlock_kernel();
mark_rodata_ro();
system_state = SYSTEM_RUNNING;
numa_default_policy();
if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
printk(KERN_WARNING "Warning: unable to open an initial console.\n");
(void) sys_dup(0);
(void) sys_dup(0);
/*
写程序时经常用到 printf标准输出,scanf标准输入,err()标准错误。
它们是指3个文件。假设open(dev/ console)是打开的第0个文件。
sys_dup(0)是指复制的意思,复制后是第1个文件,则有了第0个文件和第1个文件。
第1个文件也是指向dev/ console另一个
sys_dup(0)也是复制文件,则这个是第3个文件,也是指向dev/ console这个文件
*/
if (ramdisk_execute_command) {
run_init_process(ramdisk_execute_command);
printk(KERN_WARNING "Failed to execute %s\n",
ramdisk_execute_command);
}
/*
* 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程序,若指定则运行指定init,否则运行默认的init程序
run_init_process(execute_command);
printk(KERN_WARNING "Failed to execute %s. Attempting "
"defaults...\n", execute_command);
}
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
panic("No init found. Try passing init= option to kernel.");
}
sys_open((const char __user *) "/dev/console", O_RDWR, 0)
打开/dev/console
然后再执行应用程序:
run_init_process("/sbin/init");//如果没找到init程序则继续执行下面的程序。
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
至此已经启动了第一个应用程序
init
整个过程流程图: