kthreadd和init进程的启动(二)

注:此文章主要基于展锐Android R代码加上学习总结自IngresGe大佬的分析

一、kthreadd

/bsp/kernel/kernel4.14/kernel/kthread.c

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);
	
	//允许kthreadd在任意CPU上运行
	set_cpus_allowed_ptr(tsk, cpu_all_mask);
	set_mems_allowed(node_states[N_MEMORY]);

	current->flags |= PF_NOFREEZE;
	cgroup_init_kthreadd();

	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;
}
二、init

启动init

kernel_init启动后,完成一些init的初始化操作,然后去系统根目录下依次找ramdisk_execute_command和execute_command设置的应用程序,如果这两个目录都找不到,就依次去根目录下找 /sbin/init,/etc/init,/bin/init,/bin/sh 这四个应用程序进行启动,只要这些应用程序有一个启动了,其他就不启动了。

Android系统一般会在根目录下放一个init的可执行文件,也就是说Linux系统的init进程在内核初始化完成后,就直接执行init这个文件。

static int __ref kernel_init(void *unused)
{
	int ret;
    //进行init进程的一些初始化操作
	kernel_init_freeable();
	/* need to finish all async __init code before freeing the memory */
	// 等待所有异步调用执行完成,,在释放内存前,必须完成所有的异步 __init 代码
	async_synchronize_full();
	ftrace_free_init_mem();
	// 释放所有init.* 段中的内存
	free_initmem();
	mark_readonly();
	// 设置系统状态为运行状态
	system_state = SYSTEM_RUNNING;
	// 设定NUMA系统的默认内存访问策略
	numa_default_policy();
    // 释放所有延时的struct file结构体
	rcu_end_inkernel_boot();

	pr_emerg("run init\n");
	//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/admin-guide/init.rst for guidance.");
}

进行init进程的一些初始化操作

static noinline void __init kernel_init_freeable(void)
{
	/*
	 * Wait until kthreadd is all set-up.
	 */
	wait_for_completion(&kthreadd_done);

	/* 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 = task_pid(current);

	smp_prepare_cpus(setup_max_cpus);

	workqueue_init();

	init_mm_internals();

	do_pre_smp_initcalls();
	lockup_detector_init();

	smp_init();
	sched_init_smp();

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

	do_basic_setup();

	test_executor_init();

	/* Open the /dev/console on the rootfs, this should never fail */
	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
	 */

	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..
	 *
	 * rootfs is available now, try loading the public keys
	 * and default modules
	 */

	integrity_load_keys();
	load_default_modules();
}
/*
 * Ok, the machine is now initialized. None of the devices
 * have been touched yet, but the CPU subsystem is up and
 * running, and memory and process management works.
 *
 * Now we can finally start doing some real work..
 */
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();
}

以上就是init启动的相关操作,接下来看看它启动之后会做哪些操作,从它的主函数入手

三、Init 进程入口

init进程主要完成的任务

system/core/init/main.cpp

/*
 1. 1.第一个参数argc表示参数个数,第二个参数是参数列表,也就是具体的参数
 2. 2.main函数有四个参数入口,
 *一是参数中有ueventd,进入ueventd_main
 *二是参数中有subcontext,进入InitLogging 和SubcontextMain
 *三是参数中有selinux_setup,进入SetupSelinux
 *四是参数中有second_stage,进入SecondStageMain
 *3.main的执行顺序如下:
 3.  (1)ueventd_main    init进程创建子进程ueventd,
 4.      并将创建设备节点文件的工作托付给ueventd,ueventd通过两种方式创建设备节点文件
 5.  (2)FirstStageMain  启动第一阶段
 6.  (3)SetupSelinux     加载selinux规则,并设置selinux日志,完成SELinux相关工作
 7.  (4)SecondStageMain  启动第二阶段
 */
int main(int argc, char** argv) {
    //当argv[0]的内容为ueventd时,strcmp的值为0,!strcmp为1
    //1表示true,也就执行ueventd_main,ueventd主要是负责设备节点的创建、权限设定等一些列工作
    if (!strcmp(basename(argv[0]), "ueventd")) {
        return ueventd_main(argc, argv);
    }
 
   //当传入的参数个数大于1时,执行下面的几个操作
    if (argc > 1) {
        //参数为subcontext,初始化日志系统,
        if (!strcmp(argv[1], "subcontext")) {
            android::base::InitLogging(argv, &android::base::KernelLogger);
            const BuiltinFunctionMap function_map;
            return SubcontextMain(argc, argv, &function_map);
        }
 
      //参数为“selinux_setup”,启动Selinux安全策略
        if (!strcmp(argv[1], "selinux_setup")) {
            return SetupSelinux(argv);
        }
       //参数为“second_stage”,启动init进程第二阶段
        if (!strcmp(argv[1], "second_stage")) {
            return SecondStageMain(argc, argv);
        }
    }
 // 默认启动init进程第一阶段
    return FirstStageMain(argc, argv);
}
3.1 ueventd_main

system/core/init/ueventtd.cpp

int ueventd_main(int argc, char** argv) {
    //设置新建文件的默认值,这个与chmod相反,这里相当于新建文件后的权限为666
    umask(000); 
 
    //初始化内核日志,位于节点/dev/kmsg, 此时logd、logcat进程还没有起来,
    //采用kernel的log系统,打开的设备节点/dev/kmsg, 那么可通过cat /dev/kmsg来获取内核log。
    android::base::InitLogging(argv, &android::base::KernelLogger);
 
    //注册selinux相关的用于打印log的回调函数
    SelinuxSetupKernelLogging(); 
    SelabelInitialize();
 
    //解析xml,根据不同SOC厂商获取不同的hardware rc文件
    auto ueventd_configuration = ParseConfig({"/ueventd.rc", "/vendor/ueventd.rc",
                                              "/odm/ueventd.rc", "/ueventd." + hardware + ".rc"});
 
    //冷启动
    if (access(COLDBOOT_DONE, F_OK) != 0) {
        ColdBoot cold_boot(uevent_listener, uevent_handlers);
        cold_boot.Run();
    }
    for (auto& uevent_handler : uevent_handlers) {
        uevent_handler->ColdbootDone();
    }
 
    //忽略子进程终止信号
    signal(SIGCHLD, SIG_IGN);
    // Reap and pending children that exited between the last call to waitpid() and setting SIG_IGN
    // for SIGCHLD above.
       //在最后一次调用waitpid()和为上面的sigchld设置SIG_IGN之间退出的获取和挂起的子级
    while (waitpid(-1, nullptr, WNOHANG) > 0) {
    }
 
    //监听来自驱动的uevent,进行“热插拔”处理
    uevent_listener.Poll([&uevent_handlers](const Uevent& uevent) {
        for (auto& uevent_handler : uevent_handlers) {
            uevent_handler->HandleUevent(uevent); //热启动,创建设备
        }
        return ListenerAction::kContinue;
    });
    return 0;
}

由init开启的一个子进程用来创建设备节点文件,有两种方式

  1. “冷插”(Cold Plug):即以预先定义的设备信息为基础,当ueventd启动后,统一创建设备节点文件。这一类设备节点文件也被称为静态节点文件。
  2. “热插拔”(Hot Plug):即在系统运行中,当有设备插入USB端口时,ueventd就会接收到这一事件,为插入的设备动态创建设备节点文件。这一类设备节点文件也被称为动态节点文件。

其中完成的主要操作如下:

  • 设置文件权限
  • 初始化内核日志(/dev/kmsg),可通过cat /dev/kmsg来获取内核log
  • 注册selinux相关的用于打印log的回调函数
  • 解析xml,根据不同SOC厂商获取不同的hardware rc文件
  • 冷启动
  • 监听热启动进行处理
3.2 FirstStageMain

第一阶段主要完成了:

  1. 挂载分区
  2. 创建设备节点和关键目录
  3. 初始化日志系统
  4. 启动selinux安全策略
    system\core\init\first_stage_init.cpp
int FirstStageMain(int argc, char** argv) {
    //init crash时重启引导加载程序
    //这个函数主要作用将各种信号量,如SIGABRT,SIGBUS等的行为设置为SA_RESTART,一旦监听到这些信号即执行重启系统
    if (REBOOT_BOOTLOADER_ON_PANIC) {
        InstallRebootSignalHandlers();
    }
    //清空文件权限
    umask(0);
 
    CHECKCALL(clearenv());
    CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1));
 
    //在RAM内存上获取基本的文件系统,剩余的被rc文件所用
    CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"));
    CHECKCALL(mkdir("/dev/pts", 0755));
    CHECKCALL(mkdir("/dev/socket", 0755));
    CHECKCALL(mount("devpts", "/dev/pts", "devpts", 0, NULL));
#define MAKE_STR(x) __STRING(x)
    CHECKCALL(mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC)));
#undef MAKE_STR
 
    // 非特权应用不能使用Andrlid cmdline
    CHECKCALL(chmod("/proc/cmdline", 0440));
    gid_t groups[] = {AID_READPROC};
    CHECKCALL(setgroups(arraysize(groups), groups));
    CHECKCALL(mount("sysfs", "/sys", "sysfs", 0, NULL));
    CHECKCALL(mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL));
 
    CHECKCALL(mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11)));
 
    if constexpr (WORLD_WRITABLE_KMSG) {
        CHECKCALL(mknod("/dev/kmsg_debug", S_IFCHR | 0622, makedev(1, 11)));
    }
 
    CHECKCALL(mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8)));
    CHECKCALL(mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9)));
 
 
    //这对于日志包装器是必需的,它在ueventd运行之前被调用
    CHECKCALL(mknod("/dev/ptmx", S_IFCHR | 0666, makedev(5, 2)));
    CHECKCALL(mknod("/dev/null", S_IFCHR | 0666, makedev(1, 3)));
 
 
    //在第一阶段挂在tmpfs、mnt/vendor、mount/product分区。其他的分区不需要在第一阶段加载,
    //只需要在第二阶段通过rc文件解析来加载。
    CHECKCALL(mount("tmpfs", "/mnt", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
                    "mode=0755,uid=0,gid=1000"));
    
    //创建可供读写的vendor目录
    CHECKCALL(mkdir("/mnt/vendor", 0755));
    // /mnt/product is used to mount product-specific partitions that can not be
    // part of the product partition, e.g. because they are mounted read-write.
    CHECKCALL(mkdir("/mnt/product", 0755));
 
    // 挂载APEX,这在Android 10.0中特殊引入,用来解决碎片化问题,类似一种组件方式,对Treble的增强,
    // 不写谷歌特殊更新不需要完整升级整个系统版本,只需要像升级APK一样,进行APEX组件升级
    CHECKCALL(mount("tmpfs", "/apex", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
                    "mode=0755,uid=0,gid=0"));
 
    // /debug_ramdisk is used to preserve additional files from the debug ramdisk
    CHECKCALL(mount("tmpfs", "/debug_ramdisk", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
                    "mode=0755,uid=0,gid=0"));
#undef CHECKCALL
 
    //把标准输入、标准输出和标准错误重定向到空设备文件"/dev/null"
    SetStdioToDevNull(argv);
    //在/dev目录下挂载好 tmpfs 以及 kmsg 
    //这样就可以初始化 /kernel Log 系统,供用户打印log
    InitKernelLogging(argv);
 
    ...
 
    /* 初始化一些必须的分区
     *主要作用是去解析/proc/device-tree/firmware/android/fstab,
     * 然后得到"/system", "/vendor", "/odm"三个目录的挂载信息
     */
    if (!DoFirstStageMount()) {
        LOG(FATAL) << "Failed to mount required partitions early ...";
    }
 
    struct stat new_root_info;
    if (stat("/", &new_root_info) != 0) {
        PLOG(ERROR) << "Could not stat(\"/\"), not freeing ramdisk";
        old_root_dir.reset();
    }
 
    if (old_root_dir && old_root_info.st_dev != new_root_info.st_dev) {
        FreeRamdisk(old_root_dir.get(), old_root_info.st_dev);
    }
 
    SetInitAvbVersionInRecovery();
 
    static constexpr uint32_t kNanosecondsPerMillisecond = 1e6;
    uint64_t start_ms = start_time.time_since_epoch().count() / kNanosecondsPerMillisecond;
    setenv("INIT_STARTED_AT", std::to_string(start_ms).c_str(), 1);
 
    //启动init进程,传入参数selinux_steup
    // 执行命令: /system/bin/init selinux_setup
    const char* path = "/system/bin/init";
    const char* args[] = {path, "selinux_setup", nullptr};
    execv(path, const_cast<char**>(args));
    PLOG(FATAL) << "execv(\"" << path << "\") failed";
 
    return 1;
}
3.3 SetupSelinux

此阶段主要完成了:初始化selinux,加载SELinux规则,配置SELinux相关log输出,并启动第二阶段
system\core\init\selinux.cpp

/*此函数初始化selinux,然后执行init以在init selinux中运行*/
int SetupSelinux(char** argv) {
       //初始化Kernel日志
    InitKernelLogging(argv);
 
       // Debug版本init crash时重启引导加载程序
    if (REBOOT_BOOTLOADER_ON_PANIC) {
        InstallRebootSignalHandlers();
    }
 
    //注册回调,用来设置需要写入kmsg的selinux日志
    SelinuxSetupKernelLogging();
   
     //加载SELinux规则
    SelinuxInitialize();
 
    /*
       *我们在内核域中,希望转换到init域。在其xattrs中存储selabel的文件系统(如ext4)不需要显式restorecon,
       *但其他文件系统需要。尤其是对于ramdisk,如对于a/b设备的恢复映像,这是必需要做的一步。
       *其实就是当前在内核域中,在加载Seliux后,需要重新执行init切换到C空间的用户态
       */
    if (selinux_android_restorecon("/system/bin/init", 0) == -1) {
        PLOG(FATAL) << "restorecon failed of /system/bin/init failed";
    }
 
  //准备启动innit进程,传入参数second_stage
    const char* path = "/system/bin/init";
    const char* args[] = {path, "second_stage", nullptr};
    execv(path, const_cast<char**>(args));
 
    /*
       *执行 /system/bin/init second_stage, 进入第二阶段
       */
    PLOG(FATAL) << "execv(\"" << path << "\") failed";
 
    return 1;
}
3.4 SecondStageMain
  1. 创建进程会话密钥并初始化属性系统
  2. 进行SELinux第二阶段并恢复一些文件安全上下文
  3. 新建epoll并初始化子进程终止信号处理函数
  4. 启动匹配属性的服务端
  5. 解析init.rc等文件,建立rc文件的action 、service,启动其他进程

此阶段内容过于繁杂,主要了解了一下rc文件的解析。
之前启动进程都是通过exec传参的方式启动,如果每个都是这样的方式启动就会无比繁琐,所以推出了init.rc这个机制。

init.rc文件解析

init.rc文件解析


init.rc主要包含五种类型语句:Action Command Service Option Import

action由一组command命令组成,包含一个触发器,以on开头

command常用命令:

    class_start <service_class_name>: 启动属于同一个class的所有服务;
    class_stop <service_class_name> : 停止指定类的服务
    start <service_name>: 启动指定的服务,若已启动则跳过;
    stop <service_name>: 停止正在运行的服务
    setprop <name> <value>:设置属性值
    mkdir <path>:创建指定目录
    symlink <target> <sym_link>: 创建连接到<target>的<sym_link>符号链接;
    write <path> <string>: 向文件path中写入字符串;
    exec: fork并执行,会阻塞init进程直到程序完毕;
    exprot <name> <name>:设定环境变量;
    loglevel <level>:设置log级别
    hostname <name> : 设置主机名
    import <filename> :导入一个额外的init配置文件

options:

Options是Service的可选项,与service配合使用

    disabled: 不随class自动启动,只有根据service名才启动;
    oneshot: service退出后不再重启;
    user/group: 设置执行服务的用户/用户组,默认都是root;
    class:设置所属的类名,当所属类启动/退出时,服务也启动/停止,默认为default;
    onrestart:当服务重启时执行相应命令;
    socket: 创建名为/dev/socket/<name>的socket
    critical: 在规定时间内该service不断重启,则系统会重启并进入恢复模式

default: 意味着disabled=false,oneshot=false,critical=false。

解析init.rc
system/core/init/init.cpp

static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {
    Parser parser = CreateParser(action_manager, service_list);

    std::string bootscript = GetProperty("ro.boot.init_rc", "");
    if (bootscript.empty()) {
	std::string bootmode = GetProperty("ro.bootmode", "");
	if (bootmode == "charger") {
		parser.ParseConfig("/vendor/etc/init/charge.rc");
	} else {
        	parser.ParseConfig("/init.rc");
        	if (!parser.ParseConfig("/system/etc/init")) {
            		late_import_paths.emplace_back("/system/etc/init");
        	}
        	if (!parser.ParseConfig("/product/etc/init")) {
            		late_import_paths.emplace_back("/product/etc/init");
        	}
        	if (!parser.ParseConfig("/product_services/etc/init")) {
            		late_import_paths.emplace_back("/product_services/etc/init");
        	}
        	if (!parser.ParseConfig("/odm/etc/init")) {
            		late_import_paths.emplace_back("/odm/etc/init");
        	}
        	if (!parser.ParseConfig("/vendor/etc/init")) {
            		late_import_paths.emplace_back("/vendor/etc/init");
        	}
	}
    } else {
        parser.ParseConfig(bootscript);
    }
}

创建解析对象,service on import

Parser CreateParser(ActionManager& action_manager, ServiceList& service_list) {
    Parser parser;

    parser.AddSectionParser("service", std::make_unique<ServiceParser>(&service_list, subcontexts));
    parser.AddSectionParser("on", std::make_unique<ActionParser>(&action_manager, subcontexts));
    parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));

    return parser;
}

init.rc中===>import /init.${ro.zygote}.rc 通过此值来判断加载哪一个rc文件
在/system/core/rootdir下,存在init.zygoteXXX.rc,此例为init.zygote32.rc

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main
    priority -20
    
     //设置用户 root
    user root  
    
    //访问组支持 root readproc reserved_disk
    group root readproc reserved_disk
    
    //创建一个socket,名字叫zygote,以tcp形式  ,可以在/dev/socket 中看到一个 zygote的socket
    socket zygote stream 660 root system
    socket usap_pool_primary stream 660 root system
    
    // onrestart 指当进程重启时执行后面的命令
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart audioserver
    onrestart restart cameraserver
    onrestart restart media
    onrestart restart netd
    onrestart restart wificond
    
    // 创建子进程时,向 /dev/cpuset/foreground/tasks 写入pid
    writepid /dev/cpuset/foreground/tasks

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server

定义一个名为zygote的service,执行/system/bin/app_process二进制文件,传入四个参数
-Xzygote ---->将作为虚拟机启动时所需的参数
/system/bin ---->代表虚拟机程序所在目录
--zygote ---->指明以ZygoteInit.java类中的main函数作为虚拟机执行入口
--start-system-server ---->启动systemServer进程

*以上就是对init进程启动及主要流程的一个学习记录,大佬的分析思路很清晰,赞一个!还有很多不懂的地方还需跟进学习。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值