Linux内核启动过程

本文详细介绍了Linux内核从加载到启动应用程序的全过程,包括从汇编代码开始的第一阶段,如设置页表、启用MMU,以及检查和创建页表。接着进入第二阶段,调用start_kernel函数进行内核初始化,处理启动参数,挂载根文件系统,并最终通过rest_init函数启动应用程序。启动过程中涉及的关键步骤包括挂载根文件系统、设置控制寄存器、运行init进程等,确保系统正常运行并执行指定的初始化脚本。
摘要由CSDN通过智能技术生成

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_mmummu使能后,会跳到__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

整个过程流程图:

kernel-w10

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值