i.MX6ULL系统移植:Linux移植5 - Linux 内核启动流程概述

参考博客:

https://blog.csdn.net/lushoumin/article/details/85274105
https://blog.csdn.net/weixin_44895651/article/details/108739520
https://blog.csdn.net/linuxweiyh/article/details/83382154
https://blog.csdn.net/perfect1t/article/details/81741531
https://blog.csdn.net/dai__liang/article/details/110678470

一、Linux 内核启动流程函数调用

在这里插入图片描述

二、start_kernel 分析

在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();	// 初始化锁的状态跟踪模块
	set_task_stack_end_magic(&init_task);
	smp_setup_processor_id();	// 获取当前正在执行初始化的处理器ID
	debug_objects_early_init();	// 主要作用是对调试对象进行早期的初始化,其实就是HASH锁和静态对象池进行初始化。

	/*
	 * Set up the the initial canary ASAP:
	 */
	boot_init_stack_canary();

	cgroup_init_early();	// 控制组进行早期的初始化

	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);	// 主要作用是在输出终端上显示版本信息、编译的电脑用户名称、编译器版本、编译时间
	setup_arch(&command_line);	// 对内核架构进行初始化
	mm_init_cpumask(&init_mm);	// 设置最开始的初始化任务属于init_mm内存
	setup_command_line(command_line);	// 保存命令行,以便后面可以使用
	setup_nr_cpu_ids();	// 设置最多有多少个nr_cpu_ids结构
	setup_per_cpu_areas();	// 设置SMP体系每个CPU使用的内存空间,同时拷贝初始化段里数据
	smp_prepare_boot_cpu();	/* arch-specific boot-cpu hooks */	// 为SMP系统里引导CPU进行准备工作

	build_all_zonelists(NULL, NULL);	// 初始化所有内存管理节点列表,以便后面进行内存管理初始化。
	page_alloc_init();	// 设置内存页分配通知器

	pr_notice("Kernel command line: %s\n", boot_command_line);	// 输出命令参数到显示终端
	parse_early_param();	// 分析命令行最早使用的参数
	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);
	pidhash_init();	// 进程ID的HASH表初始化,这样可以提供通PID进行高效访问进程结构的信
	vfs_caches_init_early();	// 虚拟文件系统的缓存初始化
	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(); 
	rcu_init();	// 初始化直接读拷贝更新的锁机制

	/* trace_printk() and trace points may be used after this */
	trace_init();

	context_tracking_init();
	radix_tree_init();	// 初始化radix树,radix树基于二进制键值的查找树
	/* init some links before init_ISA_irqs() */
	early_irq_init();
	init_IRQ();	// 初始化中断相关的工作
	tick_init();
	rcu_init_nohz();
	init_timers();	// 初始化引导CPU的时钟相关的数据结构
	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();	// 打开本CPU的中断

	kmem_cache_init_late();

	/*
	 * 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();	// 测试锁的API是否使用正常,进行自我测试

#ifdef CONFIG_BLK_DEV_INITRD
	if (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;
	}
#endif
	page_ext_init();
	debug_objects_mem_init();	// 创建调试对象内存缓存
	kmemleak_init();
	setup_per_cpu_pageset();	// 创建每个CPU的高速缓存集合数组
	numa_policy_init();	// 初始化NUMA的内存访问策略
	if (late_time_init)
		late_time_init();	// 主要运行时钟相关后期的初始化功能
	sched_clock_init();
	calibrate_delay();
	pidmap_init();	// 进程位图初始化
	anon_vma_init();
	acpi_early_init();
#ifdef CONFIG_X86
	if (efi_enabled(EFI_RUNTIME_SERVICES))
		efi_enter_virtual_mode();	// 初始化EFI的接口,并进入虚拟模式
#endif
#ifdef CONFIG_X86_ESPFIX64
	/* Should be run before the first non-init thread is created */
	init_espfix_bsp();
#endif
	thread_info_cache_init();	// 线程信息的缓存初始化
	cred_init();
	fork_init();	// 根据当前物理内存计算出来可以创建进程(线程)的数量,并进行进程环境初始化
	proc_caches_init();	// 进程缓存初始化
	buffer_init();	// 初始化文件系统的缓冲区,并计算最大可以使用的文件缓存
	key_init();	// 初始化安全键管理列表和结构
	security_init();	// 初始化安全管理框架,以便提供访问文件/登录等权限
	dbg_late_init();
	vfs_caches_init(totalram_pages);	// 虚拟文件系统进行缓存初始化,提高虚拟文件系统的访问速度
	signals_init();	// 初始化信号队列缓存
	/* rootfs populating might need page-writeback */
	page_writeback_init();
	proc_root_init();	// 初始化系统进程文件系统,主要提供内核与用户进行交互的平台,方便用户实时查看进程的信息
	nsfs_init();
	cpuset_init();	// 初始化CPUSET,CPUSET主要为控制组提供CPU和内存节点的管理的结构
	cgroup_init();	// 初始化进程控制组
	taskstats_init_early();	// 初始化任务状态相关的缓存、队列和信号量
	delayacct_init();	// 初始化每个任务延时计数。当一个任务等CPU运行,或者等IO同步时,都需要计算等待时间

	check_bugs();	// 检查CPU配置、FPU等是否非法使用不具备的功能

	acpi_subsystem_init();	// 初始化ACPI电源管理
	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分析

static noinline void __init_refok rest_init(void)
{
	int pid;

	rcu_scheduler_starting();
	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);
	numa_default_policy();
	pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
	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);
}

1、调用kernel_thread函数启动了2个内核线程,分别是:kernel_init和kthreadd。

2、调用schedule函数开启了内核的调度系统。

3、调用cpu_idle函数结束了整个内核的启动。

四、kernel_init分析

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

	kernel_init_freeable();
	/* 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) {
		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.
	 */
	if (execute_command) {
		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;

	panic("No working init found.  Try passing init= option to kernel. "
	      "See Linux Documentation/init.txt for guidance.");
}

五、kthreadd分析

int kthreadd(void *unused)
{
	struct task_struct *tsk = current;

	/* Setup a clean context for our children to inherit. */
	set_task_comm(tsk, "kthreadd");
	ignore_signals(tsk);
	set_cpus_allowed_ptr(tsk, cpu_all_mask);
	set_mems_allowed(node_states[N_MEMORY]);

	current->flags |= PF_NOFREEZE;

	for (;;) {
		set_current_state(TASK_INTERRUPTIBLE);
		if (list_empty(&kthread_create_list))
			schedule();
		__set_current_state(TASK_RUNNING);

		spin_lock(&kthread_create_lock);
		while (!list_empty(&kthread_create_list)) {
			struct kthread_create_info *create;

			create = list_entry(kthread_create_list.next,
					    struct kthread_create_info, list);
			list_del_init(&create->list);
			spin_unlock(&kthread_create_lock);

			create_kthread(create);

			spin_lock(&kthread_create_lock);
		}
		spin_unlock(&kthread_create_lock);
	}

	return 0;
}

六、linux下3个特殊进程

1、idle进程

idle进程由系统自动创建, 运行在内核态。idle进程其pid=0,其前身是系统创建的第一个进程,也是唯一一个没有通过fork或者kernel_thread产生的进程。完成加载系统后,演变为进程调度、交换。

2、kernel_init进程

init进程由idle通过kernel_thread创建,在内核空间完成初始化后, 加载init程序, 并最终允许在用户态。由0进程创建,完成系统的初始化. 是系统中所有其它用户进程的祖先进程。Linux中的所有进程都是有init进程创建并运行的。

首先Linux内核启动,然后在用户空间中启动init进程,再启动其他系统进程。在系统启动完成完成后,init将变为守护进程监视系统其他进程。

3、kthreadd进程

kthreadd进程由idle通过kernel_thread创建,并始终运行在内核空间, 负责所有内核线程的调度和管理。

它的任务就是管理和调度其他内核线程kernel_thread, 会循环执行一个kthread的函数,该函数的作用就是运行kthread_create_list全局链表中维护的kthread, 当我们调用kernel_thread创建的内核线程会被加入到此链表中,因此所有的内核线程都是直接或者间接的以kthreadd为父进程 。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值