Android 8.0 开机流程 (三) Linux 内核 init 进程的启动

版权声明:版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/mahang123456/article/details/88741650

前面分析了kthredd 内核守护进程的分析,接下里会分析init 进程。init是用户空间的第一个进程,也是所有用户空间的所有父进程。这也是从内核态切换到用户态。

1.kernel_init

kernel_init 定义在 Kernel\init\main.c

static int __ref kernel_init(void *unused)
{
#if (MP_CHECKPT_BOOT == 1)
	unsigned int PiuTick;
	unsigned int PiuTime;
#endif
	int ret;

	kernel_init_freeable();//前期初始化动作
	/* need to finish all async __init code before freeing the memory */
	//等待所有异步调用完成,释放内存前,必须完成所有的异步_init代码
	async_synchronize_full();//
#if !defined(CONFIG_MP_MSTAR_STR_BASE)
	free_initmem(); //释放所有init.*段中的代码
#endif
	mark_readonly();
	system_state = SYSTEM_RUNNING; //设置系统状态为运行态
	numa_default_policy(); //设定NUMA系统的默认内存访问策略

	flush_delayed_fput();//释放有延时的文件结构体struct file 

	rcu_end_inkernel_boot();
#ifdef CONFIG_MP_DEBUG_TOOL_MEMORY_USAGE_MONITOR
	show_mm_time(0);
#endif

  //ramdisk_execute_command 值为/init 
	if (ramdisk_execute_command) {
		ret = run_init_process(ramdisk_execute_command);//执行根目录下的init程序
		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.
	 */
 //execute_command 如果有定义,就去相应的根目录下执行的相应的程序
	if (execute_command) {
		ret = run_init_process(execute_command);
		if (!ret)
			return 0;
		panic("Requested init %s failed (error %d).",
		      execute_command, ret);
	}
	// ramdisk_execute_command  execute_command定义的应用程序没有找到
	//在根目录下 /sbin/init  /etc/init  /bin/init  /bin/sh 找相应的应用程序
	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.");
}

在kthread_init 中 有一些相关的初始化,然后根据 ramdisk_execute_command  execute_command和去执行相应程序,如果没有找到相应的程序,再去/sbin/init  /etc/init  /bin/init  /bin/sh 找相应的应用程序。只需要找一个程序,执行即可。

2.kernel_init_freeable

kernel_init_freeable 定义在 Kernel\init\main.c

static noinline void __init kernel_init_freeable(void)
{
	/*
	 * Wait until kthreadd is all set-up.
	 */
    /*等待&kthreadd_done这个值complete,这个在rest_init方法中有写,在ktreadd进程启动完成后设置        
     为complete*/
	wait_for_completion(&kthreadd_done);

	/* Now the scheduler is fully set up and can do blocking allocations */
    //设置bitmask, 使得init进程可以使用PM并且允许I/O阻塞操作
	gfp_allowed_mask = __GFP_BITS_MASK;

	/*
	 * init can allocate pages on any node
	 */
    //init进程可以分配物理页面
	set_mems_allowed(node_states[N_MEMORY]);
	/*
	 * init can run on any cpu.
	 */
    //init进程可以在任意cpu上执行
    set_cpus_allowed_ptr(current, cpu_all_mask);
     /*设置到init进程的pid号给cad_pid,cad就是ctrl-alt-del,设置init进程来处理ctrl-alt-del信                
     号*/
	cad_pid = task_pid(current);
    //设置smp初始化时的最大CPU数量,然后将对应数量的CPU状态设置为present
	smp_prepare_cpus(setup_max_cpus);

     //调用__initcall_start到__initcall0_start之间的initcall_t函数指针
	do_pre_smp_initcalls();
    //开启watchdog_threads,watchdog主要用来监控、管理CPU的运行状态
	lockup_detector_init();

    //启动cpu0外的其他cpu核
	smp_init();
    //进程调度域初始化
	sched_init_smp();

	page_alloc_init_late();

    //初始化设备,驱动等,这个方法比较重要,将在下面单独讲
	do_basic_setup();

	/* Open the /dev/console on the rootfs, this should never fail */

    首先打开了/dev/console文件,在Linux中一切皆是文件,所以/dev/console文件代表的是控制台设备
   (其实就是串口)
    在Linux中,进程打开文件后会获得该文件的文件描述符。这里kernel_init进程就得到了控制台的文件描                
    述符,然后又使用sys_dup复制了2次….一共得到了3个文件描述符。这三个文件描述符分别是0、1、2.这    
    三个文件描述符就是所谓的:标准输入、标准输出、标准错误
    之后kernel_init所有的子进程都将继承这3个文件描述符,也就是说后面所有的进程一生出来,就默认拥
    有标准输入、标准输出、标准错误的文件描述符
    其实这一步的根本目的就是传承这3个文件描述符、


	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);
    // 标准输出
	(void) sys_dup(0);
    
    /*
	 * check if there is an early userspace init.  If yes, let it do all
	 * the work
	 */
     //如果 ramdisk_execute_command 没有赋值,则赋值为"/init",之前有讲到
	if (!ramdisk_execute_command)
		ramdisk_execute_command = "/init";
    // 尝试进入ramdisk_execute_command指向的文件,如果失败则重新挂载根文件系统
	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();
    // 加载I/O调度的电梯算法
	load_default_modules();
}

在 kernel_init_freeable 中 启用了smp SMP(Symmetric Multi-Processor) 所谓对称多处理器结构,是指服务器中多个CPU对称工作,各CPU共享相同的物理内存及总线结构。初始化了设备驱动,加载了文件系统和获取了控制台。打开标准输入,标准输出。

2.1.do_basic_setup

do_basic_setup 定义在 Kernel\init\main.c

static void __init do_basic_setup(void)
{
    ////针对SMP系统,初始化内核control group的cpuset子系统。
	cpuset_init_smp();
    // 初始化共享内存
	shmem_init();
    // 初始化驱动程序模型
	driver_init();
    //创建/proc/irq目录, 并初始化系统中所有中断对应的子目录
	init_irq_proc();
    // 执行内核的构造函数
	do_ctors();
    // 启用usermodehelper
	usermodehelper_enable();
    //遍历initcall_levels数组,调用里面的initcall函数,这里主要是对设备、驱动、文件系统进行初始
     化,之所有将函数封装到数组进行遍历,主要是为了好扩展
	do_initcalls();
}

do_basic_setup 是在系统起来以后做一些基础初始化,相应子系统的初始化。cpuset子系统 设备驱动子系统,irq子系统的初始化等。

2.2.driver_init

driver_init 定义在 Kernel\drivers\base\init.c

void __init driver_init(void)
{
	/* These are the core pieces */
    // 注册devtmpfs文件系统,启动kdevtmpfs进程
	devtmpfs_init();
    // 初始化驱动模型中的部分子系统,kset:devices 和 kobject:dev、 dev/block、 dev/char
	devices_init();
    // 初始化驱动模型中的bus子系统,kset:bus、devices/system
	buses_init();
    // 初始化驱动模型中的class子系统,kset:class
	classes_init();
    // 初始化驱动模型中的firmware子系统 ,kobject:firmware
	firmware_init();
    // 初始化驱动模型中的hypervisor子系统,kobject:hypervisor
	hypervisor_init();

	/* These are also core pieces, but must come after the
	 * core core pieces.
	 */
    // 初始化驱动模型中的bus/platform子系统,这个节点是所有platform设备和驱动的总线类型,即所有        
       platform设备和驱动都会挂载到这个总线上
	platform_bus_init();
    // 初始化驱动模型中的devices/system/cpu子系统,该节点包含CPU相关的属性
	cpu_dev_init();
    //初始化驱动模型中的/devices/system/memory子系统,该节点包含了内存相关的属性,如块大小等
	memory_dev_init();
    //初始化系统总线类型为容器
	container_dev_init();
    /初始化创建,访问和解释设备树的过程。
	of_core_init();
}

driver_init 中linux 内核驱动模型的子系统基本上已经初始化完成,建立了linux 驱动框架。但是它只是创建了目录,具体的驱动加载在do_install 函数中。

3.free_initmem

free_initmem 定义在 Kernel\arch\arm64\mm\init.c

void free_initmem(void)
{
	free_reserved_area(__va(__pa(__init_begin)), __va(__pa(__init_end)),
			   0, "unused kernel");
	/*
	 * Unmap the __init region but leave the VM area in place. This
	 * prevents the region from being reused for kernel modules, which
	 * is not supported by kallsyms.
	 */
	unmap_kernel_range((u64)__init_begin, (u64)(__init_end - __init_begin));
}

所有使用__init标记过的函数和使用__initdata标记过的数据,在free_initmem函数执行后,都不能使用,它们曾经获得的内存现在可以重新用于其他目的。

4.flush_delayed_fput

flush_delayed_fput 定义在 Kernel\fs\file_table.c中

void flush_delayed_fput(void)
{
	delayed_fput(NULL);
}

static void delayed_fput(struct work_struct *unused)
{
	struct llist_node *node = llist_del_all(&delayed_fput_list);
	struct llist_node *next;

	for (; node; node = next) {
		next = llist_next(node);
        ////释放struct file
		__fput(llist_entry(node, struct file, f_u.fu_llist));
	}
}

释放 delayed_fput_list 链表中struct 文件结构体,用户空间每打开一个文件,内核就会关联一个struct 文件结构体。

5.run_init_process

run_init_process 定义在 Kernel\init\main.c

static int run_init_process(const char *init_filename)
{
	argv_init[0] = init_filename;
	return do_execve(getname_kernel(init_filename),//执行程序
		(const char __user *const __user *)argv_init,
		(const char __user *const __user *)envp_init);
}

run_init_process 执行一个可执行程序。kthread_init 中执行/.init文件 

6.小结

在init进程中,相关初始化后。就会去执行用户空间的init程序。从内核态转化到用户态。Linux内核的启动流程已完毕。主要就是kthread_init 进程和kthreadd守护进程的创建。后面就会进入到用户空间 Android 的启动流程分析。

猜你喜欢

转载自blog.csdn.net/mahang123456/article/details/88741650