linux启动流程

在分析之前一定要先编译!

因为有一些关键的文件是要经过编译后才有的!比如**./arch/arm/kernel/vmlinux.lds**链接文件。

1. 找到链接文件

链接文件可以在Makefile中找到:
在这里插入图片描述

2. 根据链接文件找到入口

OUTPUT_ARCH(arm)
ENTRY(stext)//入口在stext
jiffies = jiffies_64;
SECTIONS
{
 /DISCARD/ : {
  *(.ARM.exidx.exit.text) *(.ARM.extab.exit.text) *(.ARM.exidx.text.exit) *(.ARM.extab.text.exit) *(.exitcall.exit) *(.discard) *(.discard.*) *(.modinfo) *(.gnu.version*)
 }
 . = (((0xC0000000))) + 0x00008000;
 .head.text : {
  _text = .;
  KEEP(*(.head.text))
 }
 . = ALIGN(1<<20);
 .text : {
  _stext = .;
  . = ALIGN(8); __idmap_text_start = .; *(.idmap.text) __idmap_text_end = .; __entry_text_start = .; *(.entry.text) __entry_text_end = .; . = ALIGN(8); __irqentry_text_start = .; *(.irqentry.text) __irqentry_text_end = .; . = ALIGN(8); __softirqentry_text_start = .; *(.softirqentry.text) __softirqentry_text_end = .; . = ALIGN(8); *(.text.hot .text.hot.*) *(.text .text.fixup) *(.text.unlikely .text.unlikely.*) *(.text.unknown .text.unknown.*) . = ALIGN(8); __noinstr_text_start = .; *(.noinstr.text) __noinstr_text_end = .; *(.text..refcount) *(.ref.text) *(.text.asan.* .text.tsan.*) . = ALIGN(8); __sched_text_start = .; *(.sched.text) __sched_text_end = .; . = ALIGN(8); __cpuidle_text_start = .; *(.cpuidle.text) __cpuidle_text_end = .; . = ALIGN(8); __lock_text_start = .; *(.spinlock.text) __lock_text_end = .; . = ALIGN(8); __kprobes_text_start = .; *(.kprobes.text) __kprobes_text_end = .; *(.gnu.warning) *(.glue_7) *(.glue_7t) *(.vfp11_veneer) *(.v4_bx) . = ALIGN(4); *(.got) . = ALIGN(4); __proc_info_begin = .; *(.proc.info.init) __proc_info_end = .;
 }
 . = ALIGN(1<<20);
 _etext = .;
.....
.....

3. 找到入口函数(grep -r “stext”)查找

在这里插入图片描述

一、head.S

发现在arch/arm/kernel/head.S中。

80 ENTRY(stext)
......
91 @ ensure svc mode and all interrupts masked
92 safe_svcmode_maskall r9 //进入SCV,并且关闭IRQ与FIQ
93 
94 mrc p15, 0, r9, c0, c0 @ get processor id //获得处理器id
95 bl __lookup_processor_type @ r5=procinfo r9=cpuid//调用函数__lookup_processor_type 检查当前系统是否支持此 CPU如果支持就获取 procinfo 信 息
96 movs r10, r5 @ invalid processor (r5=0)?
97 THUMB( it eq ) @ force fixup-able long branch encoding
98 beq __error_p @ yes, error 'p'
99 
......
107
108 #ifndef CONFIG_XIP_KERNEL
......
113 #else
114 ldr r8, =PLAT_PHYS_OFFSET @ always constant in this case
115 #endif
116
117 /*
118 * r1 = machine no, r2 = atags or dtb,
119 * r8 = phys_offset, r9 = cpuid, r10 = procinfo
120 */
121 bl __vet_atags //调用函数__vet_atags 验证 atags 或设备树(dtb)的合法性
......
128 bl __create_page_tables//创建页表
129
130 /*
131 * The following calls CPU specific code in a position independent
132 * manner. See arch/arm/mm/proc-*.S for details. r10 = base of
133 * xxx_proc_info structure selected by __lookup_processor_type
134 * above. On return, the CPU will be ready for the MMU to be
135 * turned on, and r0 will hold the CPU control register value.
136 */
137 ldr r13, =__mmap_switched @ address to jump to after//,__mmap_switched 最终会调用 start_kernel 函数。
138 @ mmu has been enabled
139 adr lr, BSYM(1f) @ return (PIC) address
140 mov r8, r4 @ set TTBR1 to swapper_pg_dir
141 ldr r12, [r10, #PROCINFO_INITFUNC]
142 add r12, r12, r10
143 ret r12
144 1: b __enable_mmu //又会调用__mmap_switched
145 ENDPROC(stext)
__enable_mmu

​	->__mmap_switched

​		->start_kernel
81 __mmap_switched:
82 adr r3, __mmap_switched_data
83 
84 ldmia r3!, {r4, r5, r6, r7}
85 cmp r4, r5 @ Copy data segment if needed
86 1: cmpne r5, r6
87 ldrne fp, [r4], #4
88 strne fp, [r5], #4
89 bne 1b
90 
91 mov fp, #0 @ Clear BSS (and zero fp)
92 1: cmp r6, r7
93 strcc fp, [r6],#4
94 bcc 1b
95 
96 ARM( ldmia r3, {r4, r5, r6, r7, sp})
97 THUMB( ldmia r3, {r4, r5, r6, r7} )
98 THUMB( ldr sp, [r3, #16] )
99 str r9, [r4] @ Save processor ID
100 str r1, [r5] @ Save machine type
101 str r2, [r6] @ Save atags pointer
102 cmp r7, #0
103 strne r0, [r7] @ Save control register values
104 b start_kernel//进入start_kernel
105 ENDPROC(__mmap_switched)
二、start_kernel(在init/main.c中)
asmlinkage __visible void __init start_kernel(void) 
{
	char *command_line;
	char *after_dashes;
	lockdep_init();
	/* lockdep 是死锁检测模块,此函数会初始化
  * 两个 hash 表。此函数要求尽可能早的执行! 
 */
	set_task_stack_end_magic(&init_task);
	/* 设置任务栈结束魔术数,
*用于栈溢出检测
*/
	smp_setup_processor_id();
	/* 跟 SMP 有关(多核处理器),设置处理器 ID。
 * 有很多资料说 ARM 架构下此函数为空函数,那是因
 * 为他们用的老版本 Linux,而那时候 ARM 还没有多
 * 核处理器。
*/
	debug_objects_early_init();
	/* 做一些和 debug 有关的初始化 */
	boot_init_stack_canary();
	/* 栈溢出检测初始化 */
	cgroup_init_early();
	/* cgroup 初始化,cgroup 用于控制 Linux 系统资源*/
	local_irq_disable();
	/* 关闭当前 CPU 中断 */
	early_boot_irqs_disabled = true;
	page_address_init();
	/* 页地址相关的初始化 */
	pr_notice("%s", linux_banner);
	/* 打印 Linux 版本号、编译时间等信息 */
	setup_arch(&command_line);
	/* 架构相关的初始化,此函数会解析传递进来的
 * ATAGS 或者设备树(DTB)文件。会根据设备树里面
 * 的 model 和 compatible 这两个属性值来查找
 * Linux 是否支持这个单板。此函数也会获取设备树
 * 中 chosen 节点下的 bootargs 属性值来得到命令
 * 行参数,也就是 uboot 中的 bootargs 环境变量的
* 值,获取到的命令行参数会保存到
*command_line 中。
 */
	mm_init_cpumask(&init_mm);
	/* 看名字,应该是和内存有关的初始化 */
	setup_command_line(command_line);
	/* 好像是存储命令行参数 */
	setup_nr_cpu_ids();
	/* 如果只是 SMP(多核 CPU)的话,此函数用于获取
 * CPU 核心数量,CPU 数量保存在变量
 * nr_cpu_ids 中。
*/
	setup_per_cpu_areas();
	/* 在 SMP 系统中有用,设置每个 CPU 的 per-cpu 数据 */
	smp_prepare_boot_cpu();
	build_all_zonelists(NULL, NULL);
	/* 建立系统内存页区(zone)链表 */
	page_alloc_init();
	/* 处理用于热插拔 CPU 的页 */
	/*
 * 中断关闭期间做一些重要的操作,然后打开中断
 */
	boot_cpu_init();
	/* 跟 CPU 有关的初始化 */
	page_address_init();
	/* 页地址相关的初始化 */
	pr_notice("%s", linux_banner);
	/* 打印 Linux 版本号、编译时间等信息 */
	setup_arch(&command_line);
	/* 架构相关的初始化,此函数会解析传递进来的
 * ATAGS 或者设备树(DTB)文件。会根据设备树里面
 * 的 model 和 compatible 这两个属性值来查找
 * Linux 是否支持这个单板。此函数也会获取设备树
 * 中 chosen 节点下的 bootargs 属性值来得到命令
 * 行参数,也就是 uboot 中的 bootargs 环境变量的
* 值,获取到的命令行参数会保存到
*command_line 中。
 */
	mm_init_cpumask(&init_mm);
	/* 看名字,应该是和内存有关的初始化 */
	setup_command_line(command_line);
	/* 好像是存储命令行参数 */
	setup_nr_cpu_ids();
	/* 如果只是 SMP(多核 CPU)的话,此函数用于获取
 * CPU 核心数量,CPU 数量保存在变量
 * nr_cpu_ids 中。
*/
	setup_per_cpu_areas();
	/* 在 SMP 系统中有用,设置每个 CPU 的 per-cpu 数据 */
	smp_prepare_boot_cpu();
	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();
	setup_log_buf(0);
	/* 设置 log 使用的缓冲区*/
	pidhash_init();
	/* 构建 PID 哈希表,Linux 中每个进程都有一个 ID,
 * 这个 ID 叫做 PID。通过构建哈希表可以快速搜索进程
 * 信息结构体。
 */
	vfs_caches_init_early();
	/* 预先初始化 vfs(虚拟文件系统)的目录项和
* 索引节点缓存
*/
	sort_main_extable();
	/* 定义内核异常列表 */
	trap_init();
	/* 完成对系统保留中断向量的初始化 */
	mm_init();
	/* 内存管理初始化 */
	sched_init();
	/* 初始化调度器,主要是初始化一些结构体 */
	preempt_disable();
	/* 关闭优先级抢占 */
	if (WARN(!irqs_disabled(), 
	/* 检查中断是否关闭,如果没有的话就关闭中断 */
	"Interrupts were enabled *very* early, fixing it\n"))
	 local_irq_disable();
	idr_init_cache();
	/* IDR 初始化,IDR 是 Linux 内核的整数管理机
 * 制,也就是将一个整数 ID 与一个指针关联起来。
 */
	rcu_init();
	/* 初始化 RCU,RCU 全称为 Read Copy Update(读-拷贝修改) */
	trace_init();
	/* 跟踪调试相关初始化 */
	context_tracking_init();
	radix_tree_init();
	/* 基数树相关数据结构初始化 */
	early_irq_init();
	/* 初始中断相关初始化,主要是注册 irq_desc 结构体变
 * 量,因为 Linux 内核使用 irq_desc 来描述一个中断。
 */
	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();
	/* slab 初始化,slab 是 Linux 内存分配器 */
	console_init();
	/* 初始化控制台,之前 printk 打印的信息都存放
 * 缓冲区中,并没有打印出来。只有调用此函数
 * 初始化控制台以后才能在控制台上打印信息。
 */
	if (panic_later) 
	 panic("Too many boot %s vars at `%s'", panic_later,
	 panic_param);
	lockdep_info();
	/* 如果定义了宏 CONFIG_LOCKDEP,那么此函数打印一些信息。*/
	locking_selftest() 
	/* 锁自测 */
	......
	 page_ext_init();
	debug_objects_mem_init();
	kmemleak_init();
	/* kmemleak 初始化,kmemleak 用于检查内存泄漏 */
	setup_per_cpu_pageset();
	numa_policy_init();
	if (late_time_init)
	 late_time_init();
	sched_clock_init();
	calibrate_delay();
	/* 测定 BogoMIPS 值,可以通过 BogoMIPS 来判断 CPU 的性能
 * BogoMIPS 设置越大,说明 CPU 性能越好。
 */
	pidmap_init();
	/* PID 位图初始化 */
	anon_vma_init();
	/* 生成 anon_vma slab 缓存 */
	acpi_early_init();
	......
	 thread_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();
	/* 初始化信号 */
	page_writeback_init();
	/* 页回写初始化 */
	proc_root_init();
	/* 注册并挂载 proc 文件系统 */
	nsfs_init();
	cpuset_init();
	/* 初始化 cpuset,cpuset 是将 CPU 和内存资源以逻辑性
 * 和层次性集成的一种机制,是 cgroup 使用的子系统之一
 */
	cgroup_init();
	/* 初始化 cgroup */
	taskstats_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();
	rest_init();
	/* rest_init 函数 */
}

start_kernel主要做的就是各种初始化。

三、rest_init(init/main.c)
noinline void __ref rest_init(void)
{
	struct task_struct *tsk;
	int pid;

	rcu_scheduler_starting();//开启RCU调度
	/*
	 * 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.
	 */
	pid = user_mode_thread(kernel_init, NULL, CLONE_FS);//创建进程号为1的init内核进程
	/*
	 * Pin init on the boot CPU. Task migration is not properly working
	 * until sched_init_smp() has been run. It will set the allowed
	 * CPUs for init to the non isolated CPUs.
	 */
	rcu_read_lock();
	tsk = find_task_by_pid_ns(pid, &init_pid_ns);
	tsk->flags |= PF_NO_SETAFFINITY;
	set_cpus_allowed_ptr(tsk, cpumask_of(smp_processor_id()));
	rcu_read_unlock();

	numa_default_policy();
	pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);//创建 kthreadd 内核进程,此内核进程的 PID 为 2。kthread进程负责所有内核进程的调度和管理。
	rcu_read_lock();
	kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
	rcu_read_unlock();

	/*
	 * Enable might_sleep() and smp_processor_id() checks.
	 * They cannot be enabled earlier because with CONFIG_PREEMPTION=y
	 * kernel_thread() would trigger might_sleep() splats. With
	 * CONFIG_PREEMPT_VOLUNTARY=y the init task might have scheduled
	 * already, but it's stuck on the kthreadd_done completion.
	 */
	system_state = SYSTEM_SCHEDULING;

	complete(&kthreadd_done);

	/*
	 * The boot idle thread must execute schedule()
	 * at least once to get things moving:
	 */
	schedule_preempt_disabled();
	/* Call into cpu_idle with preempt disabled */
	cpu_startup_entry(CPUHP_ONLINE);//idle进程为rest_init进程也就是主进程,进程号为0
}

创建了init进程与kthread进程和idle进程。

四、init进程

对应kernel_init函数在init/main.c中

static int __ref kernel_init(void *unused)
{
	int ret;

	/*
	 * Wait until kthreadd is all set-up.
	 */
	wait_for_completion(&kthreadd_done);

	kernel_init_freeable();//做一些重要的初始化工作
	/* need to finish all async __init code before freeing the memory */
	async_synchronize_full();

	system_state = SYSTEM_FREEING_INITMEM;
	kprobe_free_init_mem();
	ftrace_free_init_mem();
	kgdb_free_init_mem();
	exit_boot_config();
	free_initmem();
	mark_readonly();

	/*
	 * Kernel mappings are now finalized - update the userspace page-table
	 * to finalize PTI.
	 */
	pti_finalize();

	system_state = SYSTEM_RUNNING;
	numa_default_policy();

	rcu_end_inkernel_boot();

	do_sysctl_args();

    //获取在 bootargs 中使用“rdinit=xxx”即可,xxx 为具体的 init 程序名字。
	if (ramdisk_execute_command) {
		ret = run_init_process(ramdisk_execute_command);
		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.
	 */
    //获取 bootargs 中使用“init=xxxx”就可以了,比如“init=/linuxrc”表示根文件系统中的 linuxrc 就是要执行的用户空间 init 程序。
	if (execute_command) {
		ret = run_init_process(execute_command);
		if (!ret)
			return 0;
		panic("Requested init %s failed (error %d).",
		      execute_command, ret);
	}

	if (CONFIG_DEFAULT_INIT[0] != '\0') {
		ret = run_init_process(CONFIG_DEFAULT_INIT);
		if (ret)
			pr_err("Default init %s failed (error %d)\n",
			       CONFIG_DEFAULT_INIT, ret);
		else
			return 0;
	}
	//如果以上两个init都没有找到,就分别找下面的四个
	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;
	//如果都没有找到启动失败
	panic("No working init found.  Try passing init= option to kernel. "
	      "See Linux Documentation/admin-guide/init.rst for guidance.");
}

kernel_init_freeable:

static noinline void __init kernel_init_freeable(void)
{
	/* Now the scheduler is fully set up and can do blocking allocations */
	gfp_allowed_mask = __GFP_BITS_MASK;

	/*
	 * init can allocate pages on any node
	 */
	set_mems_allowed(node_states[N_MEMORY]);

	cad_pid = get_pid(task_pid(current));

	smp_prepare_cpus(setup_max_cpus);

	workqueue_init();

	init_mm_internals();

	rcu_init_tasks_generic();
	do_pre_smp_initcalls();
	lockup_detector_init();

	smp_init();
	sched_init_smp();

	padata_init();
	page_alloc_init_late();
	/* Initialize page ext after all struct pages are initialized. */
	page_ext_init();

	do_basic_setup();//用于完成 Linux 下设备驱动初始化工作!非常重要。do_basic_setup 会调用 driver_init 函数完成 Linux 下驱动模型子系统的初始化。

	kunit_run_all_tests();

	wait_for_initramfs();
	console_on_rootfs();//打开控制台

	/*
	 * check if there is an early userspace init.  If yes, let it do all
	 * the work
	 */
	if (init_eaccess(ramdisk_execute_command) != 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();
}

console_on_rootfs:

void __init console_on_rootfs(void)
{
	struct file *file = filp_open("/dev/console", O_RDWR, 0);//控制台由bootargs中传递的参数控制

	if (IS_ERR(file)) {
		pr_err("Warning: unable to open an initial console.\n");
		return;
	}
	init_dup(file);//1
	init_dup(file);//2
	init_dup(file);//3
	fput(file);
}

一共有4个?

总结kernel_init:(init进程(内核态))

1.调用kernel_init_freeable做一些初始化工作

​ ->do_basic_setup 设备驱动初始化

​ ->console_on_rootfs打开标准输入输出和错误

尝试获取init进程(用户态)

2.尝试获取execute_command = bootargs 中使用“rdinit=xxx”

3.尝试获取execute_command = bootargs 中使用“init=/linuxrc”

4.如果以上都没有依次获取:

"/sbin/init","/etc/init“,"/bin/init","/bin/sh"

ct file *file = filp_open(“/dev/console”, O_RDWR, 0);//控制台由bootargs中传递的参数控制

if (IS_ERR(file)) {
	pr_err("Warning: unable to open an initial console.\n");
	return;
}
init_dup(file);//1
init_dup(file);//2
init_dup(file);//3
fput(file);

}


一共有4个?

#### 总结kernel_init:(init进程(内核态))

1.调用kernel_init_freeable做一些初始化工作

​	->do_basic_setup 设备驱动初始化

​	->console_on_rootfs打开标准输入输出和错误

##### 尝试获取init进程(用户态)

2.尝试获取execute_command = bootargs 中使用“rdinit=xxx”

3.尝试获取execute_command = bootargs 中使用“init=/linuxrc”

4.如果以上都没有依次获取:

“/sbin/init”,“/etc/init“,”/bin/init",“/bin/sh”

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值