ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

源码详解Android 9.0 系统启动流程之init进程(第一阶段)

2021-11-05 12:34:18  阅读:269  来源: 互联网

标签:std 系统启动 int argv init 源码 dev policy


Android系统启动流程 init进程

1. 背景

最近因工作需要,正在学习Android系统的启动流程,我们目前维护的代码主要是Android 9.0系统,因此结合代码,对整个流程进行学习熟悉。

2. 介绍

2.1 概述

Linux系统执行完初始化操作最后会执行根目录下的init文件,init是一个可执行程序,它的源码在platform/system/core/init/init.cpp。init进程是用户空间的第一个进程,我们熟悉的app应用程序都是以它为父进程的,init进程入口函数是main函数,这个函数做的事情还是比较多的,主要分为三个部分

  1. init进程第一阶段
  2. init进程第二阶段
  3. init.rc文件解析

依据这三个部分将init进程分三篇文章描述,本文主要讲解第一阶段,第一阶段主要有以下内容

  • ueventd/watchdogd跳转
  • 环境变量设置及创建文件系统目录并挂载相关的文件系统
  • 初始化日志输出、挂载分区设备
  • 启用SELinux安全策略
  • 开始第二阶段前的准备

2.2 init进程入口

Android init进程的入口文件在system/core/init/init.cpp中,
其中的main函数为主要介绍部分。

int main(int argc, char** argv) {
	 /*
     * 1.strcmp是String的一个函数,比较字符串,相等返回0
     * 2.C++中0也可以表示false
     * 3.basename是C库中的一个函数,得到特定的路径中的最后一个'/'后面的内容,
     * 比如/sdcard/miui_recovery/backup,得到的结果是backup
     * 4.InitKernelLogging做了一些重定向来记录一些系统log主要就是我们常用的dev/kmsg的输出
     */
    if (!strcmp(basename(argv[0]), "ueventd")) {
        return ueventd_main(argc, argv);
    }

    if (!strcmp(basename(argv[0]), "watchdogd")) {
        return watchdogd_main(argc, argv);
    }

    if (argc > 1 && !strcmp(argv[1], "subcontext")) {
        InitKernelLogging(argv);
        const BuiltinFunctionMap function_map;
        return SubcontextMain(argc, argv, &function_map);
    }

    if (REBOOT_BOOTLOADER_ON_PANIC) {
     	//初始化重启系统的处理信号,内部通过sigaction 注册信号,当监听到该信号时重启系统
        InstallRebootSignalHandlers();
    }

    bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);

    if (is_first_stage) {
    	//用于记录启动时间
        boot_clock::time_point start_time = boot_clock::now();

        // Clear the umask.
        umask(0);

    	//注册环境变量PATH
		// _PATH_DEFPATH 是定义在bionic/libc/include/paths.h中
        clearenv();
        setenv("PATH", _PATH_DEFPATH, 1);
        // Get the basic filesystem setup we need put together in the initramdisk
        // on / and then we'll let the rc file figure out the rest.
        mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
        mkdir("/dev/pts", 0755);
        mkdir("/dev/socket", 0755);
        mount("devpts", "/dev/pts", "devpts", 0, NULL);
        #define MAKE_STR(x) __STRING(x)
        mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC));
        // Don't expose the raw commandline to unprivileged processes.
        chmod("/proc/cmdline", 0440);
        gid_t groups[] = { AID_READPROC };
        setgroups(arraysize(groups), groups);
        mount("sysfs", "/sys", "sysfs", 0, NULL);
        mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL);

        mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11));

        if constexpr (WORLD_WRITABLE_KMSG) {
            mknod("/dev/kmsg_debug", S_IFCHR | 0622, makedev(1, 11));
        }

        mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8));
        mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9));

        // Mount staging areas for devices managed by vold
        // See storage config details at http://source.android.com/devices/storage/
        mount("tmpfs", "/mnt", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
              "mode=0755,uid=0,gid=1000");
        // /mnt/vendor is used to mount vendor-specific partitions that can not be
        // part of the vendor partition, e.g. because they are mounted read-write.
        mkdir("/mnt/vendor", 0755);

        // Now that tmpfs is mounted on /dev and we have /dev/kmsg, we can actually
        // talk to the outside world...
        InitKernelLogging(argv);

        LOG(INFO) << "init first stage started!";

        if (!DoFirstStageMount()) {
            LOG(FATAL) << "Failed to mount required partitions early ...";
        }

        SetInitAvbVersionInRecovery();

        // Enable seccomp if global boot option was passed (otherwise it is enabled in zygote).
        global_seccomp();

        // Set up SELinux, loading the SELinux policy.
        SelinuxSetupKernelLogging();
        SelinuxInitialize();

        // We're in the kernel domain, so re-exec init to transition to the init domain now
        // that the SELinux policy has been loaded.
        if (selinux_android_restorecon("/init", 0) == -1) {
            PLOG(FATAL) << "restorecon failed of /init failed";
        }

        setenv("INIT_SECOND_STAGE", "true", 1);

        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);

        char* path = argv[0];
        char* args[] = { path, nullptr };
        execv(path, args);

        // execv() only returns if an error happened, in which case we
        // panic and never fall through this conditional.
        PLOG(FATAL) << "execv(\"" << path << "\") failed";
    }
    ......
}

3. ueventd/watchdogd跳转

在main函数刚开始,会根据参数argv去判断是否需要启动ueventd和
watchdogd,其中ueventd是为了维护/dev/下的一些设备节点,比如删除或者增加,watchdogd就是常说的看门狗,是为了监测系统,保证系统的稳定性,比如出现锁死,卡死之类的情况能及时处理,关于watchdogd可自行百度了解,InitKernelLogging做了一些重定向来记录一些系统log主要就是我们常用的dev/kmsg的输出,关于REBOOT_BOOTLOADER_ON_PANIC这个是否定义是在init的mk文件中定义的,一般只有调式版本才会用到这个,定义了一些信号,当init进程崩溃时去重启bootloader,我们也更容易发现一些问题

3.1 ueventd_main

定义在system/core/init/ueventd.cpp

Android根文件系统的映像中不存在“/dev”目录,该目录是init进程启动后动态创建的。
因此,建立Android中设备节点文件的重任,也落在了init进程身上。为此,init进程创建子进程ueventd,并将创建设备节点文件的工作托付给ueventd。ueventd通过两种方式创建设备节点文件。

第一种方式对应“冷插拔”(Cold Plug),即以预先定义的设备信息为基础,当ueventd启动后,统一创建设备节点文件。这一类设备节点文件也被称为静态节点文件。

第二种方式对应“热插拔”(Hot Plug),即在系统运行中,当有设备插入USB端口时,ueventd就会接收到这一事件,为插入的设备动态创建设备节点文件。这一类设备节点文件也被称为动态节点文件。

DeviceHandler CreateDeviceHandler() {
    Parser parser;

    std::vector<Subsystem> subsystems;
    parser.AddSectionParser("subsystem", std::make_unique<SubsystemParser>(&subsystems));

    using namespace std::placeholders;
    std::vector<SysfsPermissions> sysfs_permissions;
    std::vector<Permissions> dev_permissions;
    parser.AddSingleLineParser("/sys/",
                               std::bind(ParsePermissionsLine, _1, &sysfs_permissions, nullptr));
    parser.AddSingleLineParser("/dev/",
                               std::bind(ParsePermissionsLine, _1, nullptr, &dev_permissions));

    parser.ParseConfig("/ueventd.rc");//解析.rc文件,这个后续再讲
    parser.ParseConfig("/vendor/ueventd.rc");
    parser.ParseConfig("/odm/ueventd.rc");

    /*
     * keep the current product name base configuration so
     * we remain backwards compatible and allow it to override
     * everything
     * TODO: cleanup platform ueventd.rc to remove vendor specific
     * device node entries (b/34968103)
     */
    std::string hardware = android::base::GetProperty("ro.hardware", "");
    parser.ParseConfig("/ueventd." + hardware + ".rc");

    auto boot_devices = fs_mgr_get_boot_devices();
    return DeviceHandler(std::move(dev_permissions), std::move(sysfs_permissions),
                         std::move(subsystems), std::move(boot_devices), true);
}

int ueventd_main(int argc, char** argv) {
    /*
     * init sets the umask to 077 for forked processes. We need to
     * create files with exact permissions, without modification by
     * the umask.
     */
    umask(000);//设置新建文件的默认值,这个与chmod相反,这里相当于新建文件后的权限为666

    InitKernelLogging(argv);//初始化日志输出

    LOG(INFO) << "ueventd started!";

    SelinuxSetupKernelLogging();//注册selinux相关的用于打印log
    SelabelInitialize();

    DeviceHandler device_handler = CreateDeviceHandler();
    UeventListener uevent_listener;

    if (access(COLDBOOT_DONE, F_OK) != 0) {
        ColdBoot cold_boot(uevent_listener, device_handler);
        cold_boot.Run();//冷启动
    }

    // We use waitpid() in ColdBoot, so we can't ignore SIGCHLD until now.
    signal(SIGCHLD, SIG_IGN);//忽略子进程终止信号
    // Reap and pending children that exited between the last call to waitpid() and setting SIG_IGN
    // for SIGCHLD above.
    while (waitpid(-1, nullptr, WNOHANG) > 0) {
    }
	//监听来自驱动的uevent,进行“热插拔”处理
    uevent_listener.Poll([&device_handler](const Uevent& uevent) {
        HandleFirmwareEvent(uevent);
        device_handler.HandleDeviceEvent(uevent);
        return ListenerAction::kContinue;
    });

    return 0;
}

3.2 watchdogd_main

定义在system/core/init/watchdogd.cpp

“看门狗”本身是一个定时器电路,内部会不断的进行计时(或计数)操作,计算机系统和”看门狗”有两个引脚相连接,正常运行时每隔一段时间就会通过其中一个引脚向”看门狗”发送信号,”看门狗”接收到信号后会将计时器清零并重新开始计时,而一旦系统出现问题,进入死循环或任何阻塞状态,不能及时发送信号让”看门狗”的计时器清零,当计时结束时,”看门狗”就会通过另一个引脚向系统发送“复位信号”,让系统重启。
watchdogd_main主要是定时器作用,而DEV_NAME就是那个引脚

int watchdogd_main(int argc, char **argv) {
    InitKernelLogging(argv);

    int interval = 10;
    if (argc >= 2) interval = atoi(argv[1]);//atoi作用是将字符串转变为数值

    int margin = 10;
    if (argc >= 3) margin = atoi(argv[2]);

    LOG(INFO) << "watchdogd started (interval " << interval << ", margin " << margin << ")!";

    int fd = open(DEV_NAME, O_RDWR|O_CLOEXEC);//打开文件 /dev/watchdog
    if (fd == -1) {
        PLOG(ERROR) << "Failed to open " << DEV_NAME;
        return 1;
    }

    int timeout = interval + margin;
    //ioctl是设备驱动程序中对设备的I/O通道进行管理的函数,WDIOC_SETTIMEOUT是设置超时时间
    int ret = ioctl(fd, WDIOC_SETTIMEOUT, &timeout);
    if (ret) {
        PLOG(ERROR) << "Failed to set timeout to " << timeout;
        ret = ioctl(fd, WDIOC_GETTIMEOUT, &timeout);
        if (ret) {
            PLOG(ERROR) << "Failed to get timeout";
        } else {
            if (timeout > margin) {
                interval = timeout - margin;
            } else {
                interval = 1;
            }
            LOG(WARNING) << "Adjusted interval to timeout returned by driver: "
                         << "timeout " << timeout
                         << ", interval " << interval
                         << ", margin " << margin;
        }
    }

    while (true) {//每间隔一定时间往文件中写入一个空字符,这就是看门狗的关键了
        write(fd, "", 1);
        sleep(interval);
    }
}

3.3 InstallRebootSignalHandlers

定义在system/core/init/init.cpp
这个函数主要作用将各种信号量,如SIGABRT,SIGBUS等的行为设置为SA_RESTART,一旦监听到这些信号即执行重启系统

static void InstallRebootSignalHandlers() {
    // Instead of panic'ing the kernel as is the default behavior when init crashes,
    // we prefer to reboot to bootloader on development builds, as this will prevent
    // boot looping bad configurations and allow both developers and test farms to easily
    // recover.
    struct sigaction action;
    memset(&action, 0, sizeof(action));
    sigfillset(&action.sa_mask);//将所有信号加入至信号集
    action.sa_handler = [](int signal) {
        // These signal handlers are also caught for processes forked from init, however we do not
        // want them to trigger reboot, so we directly call _exit() for children processes here.
        if (getpid() != 1) {
            _exit(signal);
        }

        // Calling DoReboot() or LOG(FATAL) is not a good option as this is a signal handler.
        // RebootSystem uses syscall() which isn't actually async-signal-safe, but our only option
        // and probably good enough given this is already an error case and only enabled for
        // development builds.
        RebootSystem(ANDROID_RB_RESTART2, "bootloader");//重启系统
    };
    action.sa_flags = SA_RESTART;
    sigaction(SIGABRT, &action, nullptr);
    sigaction(SIGBUS, &action, nullptr);
    sigaction(SIGFPE, &action, nullptr);
    sigaction(SIGILL, &action, nullptr);
    sigaction(SIGSEGV, &action, nullptr);
#if defined(SIGSTKFLT)
    sigaction(SIGSTKFLT, &action, nullptr);
#endif
    sigaction(SIGSYS, &action, nullptr);
    sigaction(SIGTRAP, &action, nullptr);
}

4. 环境变量设置及创建文件系统目录并挂载相关的文件系统

is_first_stage 值是通过获取INIT_SECOND_STAGE是否指向空来判断的,此时系统刚启动,环境变量等尚未初始化,所以此时is_first_stage 的值是ture,会进入if语句,刚开始会通过umask来清空和初始化创建文件的权限掩码,通过clearenv来清空环境变量,之后就会去创建一些开机用到的系统文件并将其挂载。

定义在system/core/init/init.cpp

int main(int argc, char** argv) {
	.....
	//查看是否有环境变量INIT_SECOND_STAGE
    bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);

    if (is_first_stage) {
        boot_clock::time_point start_time = boot_clock::now();

        // Clear the umask.
        umask(0);//清空文件权限

    	//注册环境变量PATH
		// _PATH_DEFPATH 是定义在bionic/libc/include/paths.h中
        clearenv();
        setenv("PATH", _PATH_DEFPATH, 1);
        // Get the basic filesystem setup we need put together in the initramdisk
        // on / and then we'll let the rc file figure out the rest.
        mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
        mkdir("/dev/pts", 0755);
        mkdir("/dev/socket", 0755);
        mount("devpts", "/dev/pts", "devpts", 0, NULL);
        #define MAKE_STR(x) __STRING(x)
        mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC));
        // Don't expose the raw commandline to unprivileged processes.
        chmod("/proc/cmdline", 0440);
        gid_t groups[] = { AID_READPROC };
        setgroups(arraysize(groups), groups);
        mount("sysfs", "/sys", "sysfs", 0, NULL);
        mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL);

        mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11));

        if constexpr (WORLD_WRITABLE_KMSG) {
            mknod("/dev/kmsg_debug", S_IFCHR | 0622, makedev(1, 11));
        }

        mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8));
        mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9));

        // Mount staging areas for devices managed by vold
        // See storage config details at http://source.android.com/devices/storage/
        mount("tmpfs", "/mnt", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
              "mode=0755,uid=0,gid=1000");
        // /mnt/vendor is used to mount vendor-specific partitions that can not be
        // part of the vendor partition, e.g. because they are mounted read-write.
        mkdir("/mnt/vendor", 0755);
        ......
    }
    ......
}

4.1 clearenv

主要负责清空环境变量。

int clearenv() {
  char** e = environ;
  if (e != NULL) {
    for (; *e; ++e) {
      *e = NULL;
    }
  }
  return 0;
}

4.2 setenv

定义在uboot\common\cmd_nvedit.c

这个函数主要作用是将一个键值对放到一个Char数组中,如果数组中有key就替换,没有就插入


int setenv(const char *varname, const char *varvalue)
{
	const char * const argv[4] = { "setenv", varname, varvalue, NULL };

	/* before import into hashtable */
	if (!(gd->flags & GD_FLG_ENV_READY))
		return 1;

	if (varvalue == NULL || varvalue[0] == '\0')
		return _do_env_set(0, 2, (char * const *)argv);
	else
		return _do_env_set(0, 3, (char * const *)argv);
}

4.3 mount

mount是用来挂载文件系统的,mount属于Linux系统调用,函数原型如下:

int mount(const char *source, const char *target, const char *filesystemtype,
unsigned long mountflags, const void *data);
参数:
source:将要挂上的文件系统,通常是一个设备名。

target:文件系统所要挂载的目标目录。

filesystemtype:文件系统的类型,可以是"ext2","msdos","proc","ntfs","iso9660"。。。

mountflags:指定文件系统的读写访问标志,可能值有以下

            参数  含义
            MS_BIND 执行bind挂载,使文件或者子目录树在文件系统内的另一个点上可视。
            MS_DIRSYNC  同步目录的更新。
            MS_MANDLOCK 允许在文件上执行强制锁。
            MS_MOVE 移动子目录树。
            MS_NOATIME  不要更新文件上的访问时间。
            MS_NODEV    不允许访问设备文件。
            MS_NODIRATIME   不允许更新目录上的访问时间。
            MS_NOEXEC   不允许在挂上的文件系统上执行程序。
            MS_NOSUID   执行程序时,不遵照set-user-ID和set-group-ID位。
            MS_RDONLY   指定文件系统为只读。
            MS_REMOUNT  重新加载文件系统。这允许你改变现存文件系统的mountflag和数据,而无需使用先卸载,再挂上文件系统的方式。
            MS_SYNCHRONOUS  同步文件的更新。
            MNT_FORCE   强制卸载,即使文件系统处于忙状态。
            MNT_EXPIRE  将挂载点标记为过时。
data:文件系统特有的参数

在init初始化过程中,Android分别挂载了tmpfs,devpts,proc,sysfs,selinuxfs这5类文件系统。

tmpfs是一种虚拟内存文件系统,它会将所有的文件存储在虚拟内存中,如果你将tmpfs文件系统卸载后,那么其下的所有的内容将不复存在。tmpfs既可以使用RAM,也可以使用交换分区,会根据你的实际需要而改变大小。tmpfs的速度非常惊人,毕竟它是驻留在RAM中的,即使用了交换分区,性能仍然非常卓越。
由于tmpfs是驻留在RAM的,因此它的内容是不持久的。断电后,tmpfs的内容就消失了,这也是被称作tmpfs的根本原因。

devpts文件系统为伪终端提供了一个标准接口,它的标准挂接点是/dev/ pts。只要pty的主复合设备/dev/ptmx被打开,就会在/dev/pts下动态的创建一个新的pty设备文件。

proc文件系统是一个非常重要的虚拟文件系统,它可以看作是内核内部数据结构的接口,通过它我们可以获得系统的信息,同时也能够在运行时修改特定的内核参数。

与proc文件系统类似,sysfs文件系统也是一个不占有任何磁盘空间的虚拟文件系统。它通常被挂接在/sys目录下。sysfs文件系统是Linux2.6内核引入的,它把连接在系统上的设备和总线组织成为一个分级的文件,使得它们可以在用户空间存取

selinuxfs也是虚拟文件系统,通常挂载在/sys/fs/selinux目录下,用来存放SELinux安全策略文件

4.4 mknod

mknod用于创建Linux中的设备文件,函数原型如下:

int mknod(const char* path, mode_t mode, dev_t dev) {

}
参数:
path:设备所在目录
mode:指定设备的类型和读写访问标志,可能的类型
    参数  含义
    S_IFMT  type of file ,文件类型掩码
    S_IFREG regular 普通文件
    S_IFBLK block special 块设备文件
    S_IFDIR directory 目录文件
    S_IFCHR character special 字符设备文件
    S_IFIFO fifo 管道文件
    S_IFNAM special named file 特殊文件
    S_IFLNK symbolic link 链接文件

dev: 表示设备,由makedev(1, 9) 函数创建,9为主设备号、1为次设备号

5. 初始化日志输出、挂载分区设备

定义在system/core/init/init.cpp

int main(int argc, char** argv) {
    ......
        if (is_first_stage) {
            ......
        	InitKernelLogging(argv);

        	LOG(INFO) << "init first stage started!";

        	if (!DoFirstStageMount()) {
            	LOG(FATAL) << "Failed to mount required partitions early ...";
        }
        ......
        }
    ......
 }

5.1 InitKernelLogging

定义在system\core\init\log.cpp

void InitKernelLogging(char* argv[]) {
    // Make stdin/stdout/stderr all point to /dev/null.
    int fd = open("/sys/fs/selinux/null", O_RDWR);//打开文件
    //若开启失败,则记录log
    if (fd == -1) {
        int saved_errno = errno;
        android::base::InitLogging(argv, &android::base::KernelLogger, InitAborter);
        errno = saved_errno;
        PLOG(FATAL) << "Couldn't open /sys/fs/selinux/null";
    }
     /*
     * dup2(int old_fd, int new_fd) 的作用是复制文件描述符,将old复制到new,下文中将
     *  0、1、2绑定到null设备上,通过标准的输入输出无法输出信息
     */
    dup2(fd, 0);//重定向标准输入stdin
    dup2(fd, 1);//重定向标准输出stdout
    dup2(fd, 2);//重定向标准错误stderr
    if (fd > 2) close(fd);

    android::base::InitLogging(argv, &android::base::KernelLogger, InitAborter);//初始化log
}

这里需要说明的是,dup2函数的作用是用来复制一个文件的描述符,
通常用来重定向进程的stdin、stdout和stderr。
它的函数原形是:

int dup2(int oldfd, int targetfd)

该函数执行后,targetfd将变成oldfd的复制品。

因此上述过程其实就是:创建出__null__设备后,将0、1、2绑定到__null__设备上。
因此init进程调用InitKernelLogging函数后,通过标准的输入输出无法输出信息。

5.1.1 InitLogging

定义在system\core\base\logging.cpp

InitLogging主要工作是设置logger和aborter的处理函数,然后设置日志系统输出等级

//此处设置的是KernelLogger
void InitLogging(char* argv[], LogFunction&& logger, AbortFunction&& aborter) {
 /*
 * C++中foo(std::forward<T>(arg))表示将arg按原本的左值或右值,传递给foo方法,
 * LogFunction& 这种表示是左值,LogFunction&&这种表示是右值
 */
  SetLogger(std::forward<LogFunction>(logger));//设置logger处理函数
  SetAborter(std::forward<AbortFunction>(aborter));//设置aborter处理函数

  if (gInitialized) {
    return;
  }

  gInitialized = true;

  // Stash the command line for later use. We can use /proc/self/cmdline on
  // Linux to recover this, but we don't have that luxury on the Mac/Windows,
  // and there are a couple of argv[0] variants that are commonly used.
  if (argv != nullptr) {
    SetDefaultTag(basename(argv[0]));
  }

  const char* tags = getenv("ANDROID_LOG_TAGS");//获取系统当前日志输出等级
  if (tags == nullptr) {
    return;
  }

  //根据TAG决定最小记录等级
  std::vector<std::string> specs = Split(tags, " ");//将tags以空格拆分成数组
  for (size_t i = 0; i < specs.size(); ++i) {
    // "tag-pattern:[vdiwefs]"
    std::string spec(specs[i]);
    if (spec.size() == 3 && StartsWith(spec, "*:")) {//如果字符数为3且以*:开头
      //那么根据第三个字符来设置日志输出等级(比如*:d,就是DEBUG级别)
      switch (spec[2]) {
        case 'v':
          gMinimumLogSeverity = VERBOSE;
          continue;
        case 'd':
          gMinimumLogSeverity = DEBUG;
          continue;
        case 'i':
          gMinimumLogSeverity = INFO;
          continue;
        case 'w':
          gMinimumLogSeverity = WARNING;
          continue;
        case 'e':
          gMinimumLogSeverity = ERROR;
          continue;
        case 'f':
          gMinimumLogSeverity = FATAL_WITHOUT_ABORT;
          continue;
        // liblog will even suppress FATAL if you say 's' for silent, but that's
        // crazy!
        case 's':
          gMinimumLogSeverity = FATAL_WITHOUT_ABORT;
          continue;
      }
    }
    LOG(FATAL) << "unsupported '" << spec << "' in ANDROID_LOG_TAGS (" << tags
               << ")";
  }
}

5.1.2 KernelLogger

定义在system\core\base\logging.cpp

在InitKernelLogging方法中有句调用android::base::InitLogging(argv, &android::base::KernelLogger);这句的作用就是将KernelLogger函数作为log日志的处理函数,KernelLogger主要作用就是将要输出的日志格式化之后写入到 /dev/kmsg 设备中

void KernelLogger(android::base::LogId, android::base::LogSeverity severity,
                  const char* tag, const char*, unsigned int, const char* msg) {
  // clang-format off
  static constexpr int kLogSeverityToKernelLogLevel[] = {
      [android::base::VERBOSE] = 7,              // KERN_DEBUG (there is no verbose kernel log
                                                 //             level)
      [android::base::DEBUG] = 7,                // KERN_DEBUG
      [android::base::INFO] = 6,                 // KERN_INFO
      [android::base::WARNING] = 4,              // KERN_WARNING
      [android::base::ERROR] = 3,                // KERN_ERROR
      [android::base::FATAL_WITHOUT_ABORT] = 2,  // KERN_CRIT
      [android::base::FATAL] = 2,                // KERN_CRIT
  };
  // clang-format on
  //static_assert是编译断言,如果第一个参数为true,那么编译就不通过,这里是判断kLogSeverityToKernelLogLevel数组个数不能大于7
  static_assert(arraysize(kLogSeverityToKernelLogLevel) == android::base::FATAL + 1,
                "Mismatch in size of kLogSeverityToKernelLogLevel and values in LogSeverity");
  //打开 /dev/kmsg 文件
  static int klog_fd = TEMP_FAILURE_RETRY(open("/dev/kmsg", O_WRONLY | O_CLOEXEC));
  if (klog_fd == -1) return;

  //根据传入的日志等级得到Linux的日志等级,也就是kLogSeverityToKernelLogLevel对应下标的映射
  int level = kLogSeverityToKernelLogLevel[severity];

  // The kernel's printk buffer is only 1024 bytes.
  // TODO: should we automatically break up long lines into multiple lines?
  // Or we could log but with something like "..." at the end?
  char buf[1024];
  size_t size = snprintf(buf, sizeof(buf), "<%d>%s: %s\n", level, tag, msg);//格式化日志输出
  if (size > sizeof(buf)) {
    size = snprintf(buf, sizeof(buf), "<%d>%s: %zu-byte message too long for printk\n",
                    level, tag, size);
  }

  iovec iov[1];
  iov[0].iov_base = buf;
  iov[0].iov_len = size;
  //通过iovec将log发送到dev/kmsg
  TEMP_FAILURE_RETRY(writev(klog_fd, iov, 1));//将日志写入到 /dev/kmsg 中
}

5.2 DoFirstStageMount

定义在platform/system/core/init/init_first_stage.cpp

主要作用是初始化特定设备并挂载

bool DoFirstStageMount() {
    // Skips first stage mount if we're in recovery mode.
    if (IsRecoveryMode()) {//如果是刷机模式,直接跳过挂载
        LOG(INFO) << "First stage mount skipped (recovery mode)";
        return true;
    }

    // Firstly checks if device tree fstab entries are compatible.
    //如果fstab/compatible的值不是android,fstab,直接跳过挂载
    if (!is_android_dt_value_expected("fstab/compatible", "android,fstab")) {
        LOG(INFO) << "First stage mount skipped (missing/incompatible fstab in device tree)";
        return true;
    }
	
	//满足上述条件时,就会调用FirstStageMount的DoFirstStageMount函数
    std::unique_ptr<FirstStageMount> handle = FirstStageMount::Create();
    if (!handle) {
        LOG(ERROR) << "Failed to create FirstStageMount";
        return false;
    }
    return handle->DoFirstStageMount();//主要是初始化特定设备并挂载
}

5.2.1 FirstStageMount::Create()

定义在system/core/init/init_first_stage.cpp

FirstStageMount::FirstStageMount()
    : need_dm_verity_(false), device_tree_fstab_(fs_mgr_read_fstab_dt(), fs_mgr_free_fstab) {
    if (!device_tree_fstab_) {
        LOG(INFO) << "Failed to read fstab from device tree";
        return;
    }
    // Stores device_tree_fstab_->recs[] into mount_fstab_recs_ (vector<fstab_rec*>)
    // for easier manipulation later, e.g., range-base for loop.
    for (int i = 0; i < device_tree_fstab_->num_entries; i++) {
        mount_fstab_recs_.push_back(&device_tree_fstab_->recs[i]);//将挂载信息放入数组中存起来
    }

    auto boot_devices = fs_mgr_get_boot_devices();
    device_handler_ =
        std::make_unique<DeviceHandler>(std::vector<Permissions>{}, std::vector<SysfsPermissions>{},
                                        std::vector<Subsystem>{}, std::move(boot_devices), false);
}

std::unique_ptr<FirstStageMount> FirstStageMount::Create() {
    if (IsDtVbmetaCompatible()) {
        return std::make_unique<FirstStageMountVBootV2>();
    } else {
        return std::make_unique<FirstStageMountVBootV1>();
    }
}

5.2.2 handle->DoFirstStageMount

定义在system/core/init/init_first_stage.cpp

bool FirstStageMount::DoFirstStageMount() {
    // Nothing to mount.
    if (mount_fstab_recs_.empty()) return true;

    if (!InitDevices()) return false;

    if (!MountPartitions()) return false;

    return true;
}

6. 启用SELinux安全策略

SELinux是 Linux的一个扩张强制访问控制安全模块。在这种访问控制体系的限制下,进程只能访问那些在他的任务中所需要文件

int main(int argc, char** argv) {
    ......
        if (is_first_stage) {
            ......
            //此处应该是初始化安全框架:Android Verified Boot
    		//AVB主要用于防止系统文件本身被篡改,还包含了防止系统回滚的功能,
    		//以免有人试图回滚系统并利用以前的漏洞
        	SetInitAvbVersionInRecovery();//在刷机模式下初始化avb的版本,不是刷机模式直接跳过

        	// Enable seccomp if global boot option was passed (otherwise it is enabled in zygote).
        	// 此处决定是否使能全局seccomp; 如果没有使能, zygote也会使能
			// 读取/proc/cmdline内容, 如果包含androidboot.seccomp=global则调用set_global_seccomp_filter
			// set_global_seccomp_filter位于bionic/libc/seccomp/seccomp_policy.cpp
        	global_seccomp();

        	// Set up SELinux, loading the SELinux policy.
        	SelinuxSetupKernelLogging();// 将selinux日志重定向到内核日志输出, 即/dev/kmsg
        	SelinuxInitialize();//加载SELinux policy,也就是安全策略,

        	// We're in the kernel domain, so re-exec init to transition to the init domain now
        	// that the SELinux policy has been loaded.
        	//按selinux policy要求,重新设置init文件属性
        	if (selinux_android_restorecon("/init", 0) == -1) {//restorecon命令用来恢复SELinux文件属性即恢复文件的安全上下文
            	PLOG(FATAL) << "restorecon failed of /init failed";
        	}
        	......
    	}
    	......
 }

6.1 SelinuxSetupKernelLogging

设置log callback,可以调用selinux_log把log写入到kmsg里面

定位在system\core\init\selinux.cpp

void SelinuxSetupKernelLogging() {
    selinux_callback cb;
    cb.func_log = selinux_klog_callback;
    selinux_set_callback(SELINUX_CB_LOG, cb);
}

6.2 SelinuxInitialize

AVB相关的信息目前还不太了解,不做深入分析。
此处,我们看看selinux_initialize相关的工作:
定位在system\core\init\selinux.cpp

void SelinuxInitialize() {
    Timer t;

    LOG(INFO) << "Loading SELinux policy";
    if (!LoadPolicy()) {// 加载SELinux策略
        LOG(FATAL) << "Unable to load SELinux policy";
    }

    bool kernel_enforcing = (security_getenforce() == 1);// 从kernel获取SELinux的状态,和getenforce的实现一样
    bool is_enforcing = IsEnforcing();// 从bootargs里面获取
    if (kernel_enforcing != is_enforcing) {
        if (security_setenforce(is_enforcing)) {// 如果kernel里面的SELinux状态和bootargs的不一致,要设置成bootargs里面传过来的值
            PLOG(FATAL) << "security_setenforce(%s) failed" << (is_enforcing ? "true" : "false");
        }
    }

    if (auto result = WriteFile("/sys/fs/selinux/checkreqprot", "0"); !result) {// 由内核强制执行检查保护
        LOG(FATAL) << "Unable to write to /sys/fs/selinux/checkreqprot: " << result.error();
    }

    // init's first stage can't set properties, so pass the time to the second stage.
    // 获取SELinux策略加载时间并设置到设置环境变量INIT_SELINUX_TOOK
    setenv("INIT_SELINUX_TOOK", std::to_string(t.duration().count()).c_str(), 1);
}

6.2.1 LoadPolicy

从Android8.0之后,因为Project Treble,system和vendor策略分离,所以Android P上走的是LoadSplitPolicy

bool LoadPolicy() {
	/*
     * 加载SELinux策略
     * 如果IsSplitPolicyDevice返回true, 执行LoadSplitPolicy, 否则执行LoadMonolithicPolicy
     */
    return IsSplitPolicyDevice() ? LoadSplitPolicy() : LoadMonolithicPolicy();
}

IsSplitPolicyDevice

IsSplitPolicyDevice 检测/system/etc/selinux/plat_sepolicy.cil文件是否存在

constexpr const char plat_policy_cil_file[] = "/system/etc/selinux/plat_sepolicy.cil";

bool IsSplitPolicyDevice() {
    return access(plat_policy_cil_file, R_OK) != -1;
}

LoadSplitPolicy

bool LoadSplitPolicy() {
    // IMPLEMENTATION NOTE: Split policy consists of three CIL files:
    // * platform -- policy needed due to logic contained in the system image,
    // * non-platform -- policy needed due to logic contained in the vendor image,
    // * mapping -- mapping policy which helps preserve forward-compatibility of non-platform policy
    //   with newer versions of platform policy.
    //
    // secilc is invoked to compile the above three policy files into a single monolithic policy
    // file. This file is then loaded into the kernel.

    // Load precompiled policy from vendor image, if a matching policy is found there. The policy
    // must match the platform policy on the system image.
    /*
     * 依次查找以下预编译SELinux文件
     * "/vendor/etc/selinux/precompiled_sepolicy"
     * "/odm/etc/selinux/precompiled_sepolicy"
     */
    std::string precompiled_sepolicy_file;
    if (FindPrecompiledSplitPolicy(&precompiled_sepolicy_file)) {// 先找odm分区,再找vendor分区
        unique_fd fd(open(precompiled_sepolicy_file.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY));// open file
        if (fd != -1) {
            if (selinux_android_load_policy_from_fd(fd, precompiled_sepolicy_file.c_str()) < 0) {// 加载到kernel
                LOG(ERROR) << "Failed to load SELinux policy from " << precompiled_sepolicy_file;
                return false;
            }
            return true;
        }
    }
    // No suitable precompiled policy could be loaded

    LOG(INFO) << "Compiling SELinux policy";

    // Determine the highest policy language version supported by the kernel
    set_selinuxmnt("/sys/fs/selinux");
    // 将策略文件写入到 /sys/fs/selinux/load
    int max_policy_version = security_policyvers();
    if (max_policy_version == -1) {
        PLOG(ERROR) << "Failed to determine highest policy version supported by kernel";
        return false;
    }

    // We store the output of the compilation on /dev because this is the most convenient tmpfs
    // storage mount available this early in the boot sequence.
    char compiled_sepolicy[] = "/dev/sepolicy.XXXXXX";
    unique_fd compiled_sepolicy_fd(mkostemp(compiled_sepolicy, O_CLOEXEC));
    if (compiled_sepolicy_fd < 0) {
        PLOG(ERROR) << "Failed to create temporary file " << compiled_sepolicy;
        return false;
    }

    // Determine which mapping file to include
    std::string vend_plat_vers;
    if (!GetVendorMappingVersion(&vend_plat_vers)) {
        return false;
    }
    std::string mapping_file("/system/etc/selinux/mapping/" + vend_plat_vers + ".cil");

    // vendor_sepolicy.cil and plat_pub_versioned.cil are the new design to replace
    // nonplat_sepolicy.cil.
    std::string plat_pub_versioned_cil_file("/vendor/etc/selinux/plat_pub_versioned.cil");
    std::string vendor_policy_cil_file("/vendor/etc/selinux/vendor_sepolicy.cil");

    if (access(vendor_policy_cil_file.c_str(), F_OK) == -1) {
        // For backward compatibility.
        // TODO: remove this after no device is using nonplat_sepolicy.cil.
        vendor_policy_cil_file = "/vendor/etc/selinux/nonplat_sepolicy.cil";
        plat_pub_versioned_cil_file.clear();
    } else if (access(plat_pub_versioned_cil_file.c_str(), F_OK) == -1) {
        LOG(ERROR) << "Missing " << plat_pub_versioned_cil_file;
        return false;
    }

    // odm_sepolicy.cil is default but optional.
    std::string odm_policy_cil_file("/odm/etc/selinux/odm_sepolicy.cil");
    if (access(odm_policy_cil_file.c_str(), F_OK) == -1) {
        odm_policy_cil_file.clear();
    }
    const std::string version_as_string = std::to_string(max_policy_version);

    // clang-format off
    std::vector<const char*> compile_args {
        "/system/bin/secilc",
        plat_policy_cil_file,
        "-m", "-M", "true", "-G", "-N",
        // Target the highest policy language version supported by the kernel
        "-c", version_as_string.c_str(),
        mapping_file.c_str(),
        "-o", compiled_sepolicy,
        // We don't care about file_contexts output by the compiler
        "-f", "/sys/fs/selinux/null",  // /dev/null is not yet available
    };
    // clang-format on

    if (!plat_pub_versioned_cil_file.empty()) {
        compile_args.push_back(plat_pub_versioned_cil_file.c_str());
    }
    if (!vendor_policy_cil_file.empty()) {
        compile_args.push_back(vendor_policy_cil_file.c_str());
    }
    if (!odm_policy_cil_file.empty()) {
        compile_args.push_back(odm_policy_cil_file.c_str());
    }
    compile_args.push_back(nullptr);

    if (!ForkExecveAndWaitForCompletion(compile_args[0], (char**)compile_args.data())) {
        unlink(compiled_sepolicy);
        return false;
    }
    unlink(compiled_sepolicy);

    LOG(INFO) << "Loading compiled SELinux policy";
    if (selinux_android_load_policy_from_fd(compiled_sepolicy_fd, compiled_sepolicy) < 0) {
        LOG(ERROR) << "Failed to load SELinux policy from " << compiled_sepolicy;
        return false;
    }

    return true;
}

LoadSplitPolicy 加载selinux 策略时,首先从预先编译好的二进制文件precompiled_sepolicy读取

如果没有找到的话,再用secilc命令编译所有的cil,重新得到一个二进制策略文件

FindPrecompiledSplitPolicy
这个函数会查找两个地方,/odm/etc/selinux/vendor/etc/selinux

/vendor/etc/selinux/precompiled_sepolicy
/odm/etc/selinux/precompiled_sepolicy
如果有odm分区,precompiled_sepolicy会放在odm分区,否则在vendor分区,因此读取的时候也是先找odm再找vendor

LoadMonolithicPolicy

从文件/sepolicy加载策略

bool LoadMonolithicPolicy() {
    LOG(VERBOSE) << "Loading SELinux policy from monolithic file";
    if (selinux_android_load_policy() < 0) {
        PLOG(ERROR) << "Failed to load monolithic SELinux policy";
        return false;
    }
    return true;
}

6.2.2 checkreqprot

设置"checkreqprot"标记的初始值。

"0"表示由内核强制执行检查保护(包括其中隐含的所有执行保护)
"1"表示由应用程序自己主动请求执行检查保护
默认值由内核在编译时确定,也可以在运行时通过/sys/fs/selinux/checkreqprot修改

7. 开始第二阶段前的准备

这里主要就是设置一些变量如INIT_SECOND_STAGE,INIT_STARTED_AT,为第二阶段做准备,然后再次调用init的main函数,启动用户态的init进程

int main(int argc, char** argv) {
    ......
        if (is_first_stage) {
            ......
            setenv("INIT_SECOND_STAGE", "true", 1);

        	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);

        	char* path = argv[0];
        	char* args[] = { path, nullptr };
        	execv(path, args);//再次调用init的main函数,启动用户态的init进程

        	// execv() only returns if an error happened, in which case we
        	// panic and never fall through this conditional.
        	PLOG(FATAL) << "execv(\"" << path << "\") failed";
        	// 内核态的进程不应该退出
        	}
    ......
 }

可能大家不明白调用execv方法就重新执行main方法,下面给出该方法原型解释。

函数原型
int execv(const char *progname, char *const argv[]);   //#include <unistd.h>
功能介绍
    execv会停止执行当前的进程,并且以progname应用进程替换被停止执行的进程,进程ID没有改变。
参数:
    progname: 被执行的应用程序。
    argv: 传递给应用程序的参数列表, 注意,这个数组的第一个参数应该是应用程序名字本身,并且最后一个参数应该为NULL,不参将多个参数合并为一个参数放入数组。
返回值:
    如果应用程序正常执行完毕,那么execv是永远不会返回的;当execv在调用出错了,此时它的返回值应该是-1,具体的错误代码可以通过全局变量errno查看,还可以通过stderr得到具体的错误描述字符。

第一阶段结束时,会复位一些信息,并设置一些环境变量,
最后启动用户态的用户态的init进程,进入init阶段二。

8. 小结

梳理源代码真是让人痛苦的一件事,庞大的代码量很难完全梳理一遍,很多细致的函数了解功能及可,细致分析过于浪费时间,下一篇文章讲解init进程第二阶段。

标签:std,系统启动,int,argv,init,源码,dev,policy
来源: https://blog.csdn.net/IT_xiao_bai0516/article/details/121149449

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有