AOSP 8.0 系统启动之二init启动(二)

文章详细解析了Android系统init进程的第二阶段,包括创建进程会话密钥、初始化属性系统、新建epoll以及初始化子进程终止信号处理函数的过程。重点介绍了epoll_create1用于事件触发和signal_handler_init如何处理SIGCHLD信号,防止子进程成为僵尸进程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一、前言

二、源码分析

2.1 创建进程会话密钥并初始化属性系统

3.2、新建epoll并初始化子进程终止信号处理函数

3.1 epoll_create1

3.2 signal_handler_init

3.3 handle_signal


一、前言

上一篇中讲了init进程的第一阶段,我们接着讲第二阶段,主要有以下内容

  • 创建进程会话密钥并初始化属性系统
  • 进行SELinux第二阶段并恢复一些文件安全上下文
  • 新建epoll并初始化子进程终止信号处理函数
  • 设置其他系统属性并开启系统属性服务

本文涉及到的文件

platform/system/core/init/init.cpp
platform/system/core/init/keyutils.h
platform/system/core/init/property_service.cpp
platform/external/selinux/libselinux/src/label.c
platform/system/core/init/signal_handler.cpp
platform/system/core/init/service.cpp
platform/system/core/init/property_service.cpp

二、源码分析

2.1 创建进程会话密钥并初始化属性系统

第二阶段先有一个is_first_stage的判断,由于之前第一阶段最后有设置INIT_SECOND_STAGE,
因此直接跳过一大段代码。

int main(int argc, char** argv) {

    //同样进行ueventd/watchdogd跳转及环境变量设置

    ...

    //之前准备工作时将INIT_SECOND_STAGE设置为true,已经不为nullptr,所以is_first_stage为false
    bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);

    //is_first_stage为false,直接跳过
    if (is_first_stage) {
        ...
    }

    // At this point we're in the second stage of init.
    InitKernelLogging(argv); //上一节有讲,初始化日志输出
    LOG(INFO) << "init second stage started!";

    // Set up a session keyring that all processes will have access to. It
    // will hold things like FBE encryption keys. No process should override
    // its session keyring.
    keyctl(KEYCTL_GET_KEYRING_ID, KEY_SPEC_SESSION_KEYRING, 1); //初始化进程会话密钥

    // Indicate that booting is in progress to background fw loaders, etc.
    close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));//创建 /dev/.booting 文件,就是个标记,表示booting进行中

    property_init();//初始化属性系统,并从指定文件读取属性

    //接下来的一系列操作都是从各个文件读取一些属性,然后通过property_set设置系统属性

    // If arguments are passed both on the command line and in DT,
    // properties set in DT always have priority over the command-line ones.
    /*
     * 1.这句英文的大概意思是,如果参数同时从命令行和DT传过来,DT的优先级总是大于命令行的
     * 2.DT即device-tree,中文意思是设备树,这里面记录自己的硬件配置和系统运行参数,参考http://www.wowotech.net/linux_kenrel/why-dt.html
     */

    process_kernel_dt();//处理DT属性
    process_kernel_cmdline();//处理命令行属性

    // Propagate the kernel variables to internal variables
    // used by init as well as the current required properties.
    export_kernel_boot_props();//处理其他的一些属性

    // Make the time that init started available for bootstat to log.
    property_set("ro.boottime.init", getenv("INIT_STARTED_AT"));
    property_set("ro.boottime.init.selinux", getenv("INIT_SELINUX_TOOK"));

    // Set libavb version for Framework-only OTA match in Treble build.
    const char* avb_version = getenv("INIT_AVB_VERSION");
    if (avb_version) property_set("ro.boot.avb_version", avb_version);

    // Clean up our environment.
    unsetenv("INIT_SECOND_STAGE"); //清空这些环境变量,因为之前都已经存入到系统属性中去了
    unsetenv("INIT_STARTED_AT");
    unsetenv("INIT_SELINUX_TOOK");
    unsetenv("INIT_AVB_VERSION");

    ...

2.2 新建epoll并初始化子进程终止信号处理函数

    epoll_fd = epoll_create1(EPOLL_CLOEXEC);//创建epoll实例,并返回epoll的文件描述符

    if (epoll_fd == -1) {
        PLOG(ERROR) << "epoll_create1 failed";
        exit(1);
    }

    signal_handler_init();//主要是创建handler处理子进程终止信号,创建一个匿名socket并注册到epoll进行监听

2.2.1 epoll_create1

EPOLL类似于POLL,是Linux中用来做事件触发的,跟EventBus功能差不多

linux很长的时间都在使用select来做事件触发,它是通过轮询来处理的,轮询的fd数目越多,自然耗时越多,对于大量的描述符处理,EPOLL更有优势

epoll_create1是epoll_create的升级版,可以动态调整epoll实例中文件描述符的个数
EPOLL_CLOEXEC这个参数是为文件描述符添加O_CLOEXEC属性,参考http://blog.youkuaiyun.com/gqtcgq/article/details/48767691

2.2.2 signal_handler_init

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

void signal_handler_init() {
    // Create a signalling mechanism for SIGCHLD.
    int s[2];
    if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s) == -1) { //创建socket并返回文件描述符
        PLOG(ERROR) << "socketpair failed";
        exit(1);
    }

    signal_write_fd = s[0];
    signal_read_fd = s[1];

    // Write to signal_write_fd if we catch SIGCHLD.
    struct sigaction act;
    memset(&act, 0, sizeof(act));
    act.sa_handler = SIGCHLD_handler; //act处理函数
    act.sa_flags = SA_NOCLDSTOP;
    sigaction(SIGCHLD, &act, 0);

    ServiceManager::GetInstance().ReapAnyOutstandingChildren();//具体处理子进程终止信号

    register_epoll_handler(signal_read_fd, handle_signal);//注册signal_read_fd到epoll中
}


void register_epoll_handler(int fd, void (*fn)()) {
    epoll_event ev;
    ev.events = EPOLLIN; //监听事件类型,EPOLLIN表示fd中有数据可读
    ev.data.ptr = reinterpret_cast<void*>(fn); //回调函数赋值给ptr
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) { //注册事件
        PLOG(ERROR) << "epoll_ctl failed";
    }
}

这个函数主要的作用是注册SIGCHLD信号的处理函数

init是一个守护进程,为了防止init的子进程成为僵尸进程(zombie process),
需要init在子进程在结束时获取子进程的结束码,通过结束码将程序表中的子进程移除,
防止成为僵尸进程的子进程占用程序表的空间(程序表的空间达到上限时,系统就不能再启动新的进程了,会引起严重的系统问题)

在linux当中,父进程是通过捕捉SIGCHLD信号来得知子进程运行结束的情况,SIGCHLD信号会在子进程终止的时候发出,了解这些背景后,我们来看看init进程如何处理这个信号

首先,调用socketpair,这个方法会返回一对文件描述符,这样当一端写入时,另一端就能被通知到,
socketpair两端既可以写也可以读,这里只是单向的让s[0]写,s[1]读

然后,新建一个sigaction结构体,sa_handler是信号处理函数,指向SIGCHLD_handler,
SIGCHLD_handler做的事情就是往s[0]里写个"1",这样s1就会收到通知,SA_NOCLDSTOP表示只在子进程终止时处理,
因为子进程在暂停时也会发出SIGCHLD信号

sigaction(SIGCHLD, &act, 0) 这个是建立信号绑定关系,也就是说当监听到SIGCHLD信号时,由act这个sigaction结构体处理

ReapAnyOutstandingChildren 这个后文讲

最后,register_epoll_handler的作用就是注册一个监听,当signal_read_fd(之前的s[1])收到信号,触发handle_signal

终上所述,signal_handler_init函数的作用就是,接收到SIGCHLD信号时触发handle_signal

2.2.3 handle_signal

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

static void handle_signal() {
    // Clear outstanding requests.
    char buf[32];
    read(signal_read_fd, buf, sizeof(buf));

    ServiceManager::GetInstance().ReapAnyOutstandingChildren();
}

首先清空signal_read_fd中的数据,然后调用ReapAnyOutstandingChildren,之前在signal_handler_init中调用过一次,
它其实是调用ReapOneProcess

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值