Android Framework ---Init进程(下)

csharp 复制代码
system/core/init/ 
init.cpp 
init_parser.cpp
signal_handler.cpp
builtins.cpp

system/core/rootdir/
init.rc
init.zygote64.rc

3 重启服务

signal_handler_init() 初始化子进程退出的信号处理函数,并调用epoll_ctl设置signal fd可读的回调函数

scss 复制代码
void signal_handler_init() {
    int s[2];
    // 创建socket pair
    if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s) == -1) {
        exit(1);
    }
    signal_write_fd = s[0];
    signal_read_fd = s[1];
    //当捕获信号SIGCHLD,则写入signal_write_fd
    struct sigaction act;
    memset(&act, 0, sizeof(act));
    act.sa_handler = SIGCHLD_handler;
    //SA_NOCLDSTOP使init进程只有在其子进程终止时才会受到SIGCHLD信号
    act.sa_flags = SA_NOCLDSTOP;
    sigaction(SIGCHLD, &act, 0);

    //进入waitpid来处理子进程是否退出的情况
    reap_any_outstanding_children();
    //调用epoll_ctl方法来注册epoll的回调函数
    register_epoll_handler(signal_read_fd, handle_signal);
}

每个进程在处理其他进程发送的signal信号时都需要先注册,当进程的运行状态改变或终止时会产生某种signal信号,init进程是所有用户空间进程的父进程,当其子进程终止时产生SIGCHLD信号,init进程调用信号安装函数sigaction(),传递参数给sigaction结构体,便完成信号处理的过程。

两个重要的函数:SIGCHLD_handler和handle_signal,如下:

csharp 复制代码
//写入数据
static void SIGCHLD_handler(int) {
    //向signal_write_fd写入1,直到成功为止
    if (TEMP_FAILURE_RETRY(write(signal_write_fd, "1", 1)) == -1) {
        ERROR("write(signal_write_fd) failed: %s\n", strerror(errno));
    }
}

//读取数据
static void handle_signal() {
    char buf[32];
    //读取signal_read_fd中的数据,并放入buf
    read(signal_read_fd, buf, sizeof(buf));
    reap_any_outstanding_children();
}

reap_any_outstanding_children(),进入waitpid来处理子进程是否退出的情况

rust 复制代码
static void reap_any_outstanding_children() {
    while (wait_for_one_process()) { }
}

static bool wait_for_one_process() {
    int status;
    //等待任意子进程,如果子进程没有退出则返回0,否则则返回该子进程pid。
    pid_t pid = TEMP_FAILURE_RETRY(waitpid(-1, &status, WNOHANG));
    if (pid == 0) {
        return false;
    } else if (pid == -1) {
        return false;
    }
    //根据pid查找到相应的service
    service* svc = service_find_by_pid(pid);
    std::string name;

    if (!svc) {
        return true;
    }

    //当flags为RESTART,且不是ONESHOT时,先kill进程组内所有的子进程或子线程
    if (!(svc->flags & SVC_ONESHOT) || (svc->flags & SVC_RESTART)) {
        kill(-pid, SIGKILL);
    }

    //移除当前服务svc中的所有创建过的socket
    for (socketinfo* si = svc->sockets; si; si = si->next) {
        char tmp[128];
        snprintf(tmp, sizeof(tmp), ANDROID_SOCKET_DIR"/%s", si->name);
        unlink(tmp);
    }

    //当flags为EXEC时,释放相应的服务
    if (svc->flags & SVC_EXEC) {
        waiting_for_exec = false;
        list_remove(&svc->slist);
        free(svc->name);
        free(svc);
        return true;
    }
    svc->pid = 0;
    svc->flags &= (~SVC_RUNNING);

    //对于ONESHOT服务,使其进入disabled状态
    if ((svc->flags & SVC_ONESHOT) && !(svc->flags & SVC_RESTART)) {
        svc->flags |= SVC_DISABLED;
    }
    //禁用和重置的服务,都不再自动重启
    if (svc->flags & (SVC_DISABLED | SVC_RESET))  {
        svc->NotifyStateChange("stopped"); //设置相应的service状态为stopped
        return true;
    }

    //服务在4分钟内重启次数超过4次,则重启手机进入recovery模式
    time_t now = gettime();
    if ((svc->flags & SVC_CRITICAL) && !(svc->flags & SVC_RESTART)) {
        if (svc->time_crashed + CRITICAL_CRASH_WINDOW >= now) {
            if (++svc->nr_crashed > CRITICAL_CRASH_THRESHOLD) {
                android_reboot(ANDROID_RB_RESTART2, 0, "recovery");
                return true;
            }
        } else {
            svc->time_crashed = now;
            svc->nr_crashed = 1;
        }
    }
    svc->flags &= (~SVC_RESTART);
    svc->flags |= SVC_RESTARTING;

    //执行当前service中所有onrestart命令
    struct listnode* node;
    list_for_each(node, &svc->onrestart.commands) {
        command* cmd = node_to_item(node, struct command, clist);
        cmd->func(cmd->nargs, cmd->args);
    }
    //设置相应的service状态为restarting
    svc->NotifyStateChange("restarting");
    return true;
}

当init子进程退出时,会产生SIGCHLD信号,并发送给init进程,通过socket套接字传递数据,调用到wait_for_one_process()方法,根据是否是oneshot,来决定是重启子进程,还是放弃启动。

4 属性服务

当某个进程A,通过property_set()修改属性值后,init进程会检查访问权限,当权限满足要求后,则更改相应的属性值,属性值一旦改变则会触发相应的触发器(即rc文件中的on开头的语句),在Android Shared Memmory(共享内存区域)中有一个_system_property_area_区域,里面记录着所有的属性值。对于进程A通过property_get()方法,获取的也是该共享内存区域的属性值。

property_init

ini 复制代码
void property_init() {
    //用于保证只初始化_system_property_area_区域一次
    if (property_area_initialized) {
        return;
    }
    property_area_initialized = true;
    //创建共享内存
    if (__system_property_area_init()) {
        return;
    }
    pa_workspace.size = 0;
    pa_workspace.fd = open(PROP_FILENAME, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
}

该方法核心功能在执行__system_property_area_init()方法,创建用于跨进程的共享内存。主要工作如下:

  • 执行open(),打开名为"/dev/properties"的共享内存文件,并设置大小为128KB;
  • 执行mmap(),将该内存映射到init进程;
  • 将该内存的首地址保存在全局变量__system_property_area__,后续的增加或者修改属性都基于该变量来计算位置。

start_property_service启动属性服务

scss 复制代码
void start_property_service() {
    property_set_fd = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
                                   false, 0666, 0, 0, nullptr, sehandle);
    listen(property_set_fd, 8);
    //设置property文件描述符可读的回调函数
    register_epoll_handler(property_set_fd, handle_property_set_fd);
}

创建并监听名叫"property_service"的socket,再利用epoll_ctl设置property文件描述符触发可读时的回调函数为handle_property_set_fd,接下来看看该函数的实现。

handle_property_set_fd()

arduino 复制代码
static void handle_property_set_fd() {
    static constexpr uint32_t kDefaultSocketTimeout = 2000; /* ms */

    int s = accept4(property_set_fd, nullptr, nullptr, SOCK_CLOEXEC);

    struct ucred cr;
    socklen_t cr_size = sizeof(cr);
    getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0);

    SocketConnection socket(s, cr);
    uint32_t timeout_ms = kDefaultSocketTimeout; //设置2秒超时

    uint32_t cmd = 0;
    if (!socket.RecvUint32(&cmd, &timeout_ms)) {
        socket.SendUint32(PROP_ERROR_READ_CMD);
        return;
    }

    switch (cmd) {
    case PROP_MSG_SETPROP: {
    
        //设置property
        handle_property_set(socket, prop_value, prop_value, true);
        break;
      }
 
}

依次调用handle_property_set,检查属性名是否合规is_legal_property_name,设置属性名和属性值property_set

不同属性执行逻辑有所不同,主要区分如下:

  • 属性名以ctl.开头,则表示是控制消息,控制消息用来执行一些命令。例如:

    • setprop ctl.start bootanim 查看开机动画;
    • setprop ctl.stop bootanim 关闭开机动画;
    • setprop ctl.start pre-recovey 进入recovery模式;
  • 属性名以ro.开头,则表示是只读的,不能设置,所以直接返回;

  • 属性名以persist.开头,则需要把这些值写到对应文件;需要注意的是,persist用于持久化保存某些属性值,当同时也带来了额外的IO操作。

总结

可见init进程在开机之后的核心工作就是响应property变化事件和回收僵尸进程。

  1. 当某个进程调用property_set来改变一个系统属性值时,系统会通过socket向init进程发送一个property变化的事件通知,那么property fd会变成可读,init进程采用epoll机制监听该fd则会 触发回调handle_property_set_fd()方法。
  2. 回收僵尸进程,在Linux内核中,如父进程不等待子进程的结束直接退出,会导致子进程在结束后变成僵尸进程,占用系统资源。为此,init进程专门安装了SIGCHLD信号接收器,当某些子进程退出时发现其父进程已经退出,则会向init进程发送SIGCHLD信号,init进程调用回调方法handle_signal()来回收僵尸子进程。
相关推荐
找藉口是失败者的习惯4 小时前
Jetpack Compose 如何布局解析
android·xml·ui
Estar.Lee8 小时前
查手机号归属地免费API接口教程
android·网络·后端·网络协议·tcp/ip·oneapi
温辉_xh9 小时前
uiautomator案例
android
工业甲酰苯胺10 小时前
MySQL 主从复制之多线程复制
android·mysql·adb
少说多做34310 小时前
Android 不同情况下使用 runOnUiThread
android·java
Estar.Lee11 小时前
时间操作[计算时间差]免费API接口教程
android·网络·后端·网络协议·tcp/ip
找藉口是失败者的习惯12 小时前
从传统到未来:Android XML布局 与 Jetpack Compose的全面对比
android·xml
Jinkey13 小时前
FlutterBasic - GetBuilder、Obx、GetX<Controller>、GetxController 有啥区别
android·flutter·ios
大白要努力!15 小时前
Android opencv使用Core.hconcat 进行图像拼接
android·opencv
天空中的野鸟16 小时前
Android音频采集
android·音视频