android6.0 init进程main之epoll

android6.0 init进程main之epoll

     对应代码android6.0_r72,kernel对应linux3.18

前言

init 进程监听 初始化,epoll 及 signal_handler_init

涉及文件

/system/core/init/Init.cpp
/system/core/init/signal_handler.cpp

init 进程 epoll 监听功能

init进程 epoll 整体代码结构如下:

// /system/core/init/Init.cpp
main()
{
......
    epoll_fd = epoll_create1(EPOLL_CLOEXEC);     // 创建 epoll 句柄
    if (epoll_fd == -1) {
        ERROR("epoll_create1 failed: %s\n", strerror(errno));
        exit(1);
    }
    signal_handler_init();  // epoll_ctl 设置添加需要监听的 fd 和 事件触发后的回调函数
......
    while (true) {
        ......
        epoll_event ev;
        int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, timeout));     // 开始等待事件触发
        if (nr == -1) {
            ERROR("epoll_wait failed: %s\n", strerror(errno));
        } else if (nr == 1) {
            ((void (*)()) ev.data.ptr)();
        }
    }
    return 0;
}

整体的调用扩展嵌入后如下:

main()
{
......
    epoll_fd = epoll_create1(EPOLL_CLOEXEC);     // 创建 epoll 句柄
    if (epoll_fd == -1) {
        ERROR("epoll_create1 failed: %s\n", strerror(errno));
        exit(1);
    }
    signal_handler_init();  // epoll_ctl 设置添加需要监听的 fd 和 事件触发后的回调函数
    
    |--> socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s) 
    |--> act.sa_handler = SIGCHLD_handler;
        |--> 循环 {
                 write(signal_write_fd, "1", 1)
             }
    |--> sigaction(SIGCHLD, &act, 0);
    |--> reap_any_outstanding_children();
        |--> while (wait_for_one_process())
            |--> 循环 {
                    waitpid(-1, &status, WNOHANG)
                }
            |--> service_find_by_pid(pid);
                |--> 循环 {
                    node_to_item(node, struct service, slist)    // 通过 offsetof 计算地址,返回指针
                }
            |--> kill(-pid, SIGKILL);
            |--> 循环 {
                    node_to_item(node, struct command, clist)    // 通过 offsetof 计算地址,返回指针
                    cmd->func(cmd->nargs, cmd->args)
                }
	|--> register_epoll_handler(signal_read_fd, handle_signal);
        |--> epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev)       // 调用 epoll_ctl 向 epoll 对象中添加回调函数;
......
    while (true) {
        ......
        epoll_event ev;
        int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, timeout));     // 开始等待事件触发,调用epoll_wait收集发生事件的连接
        if (nr == -1) {
            ERROR("epoll_wait failed: %s\n", strerror(errno));
        } else if (nr == 1) {
            ((void (*)()) ev.data.ptr)();
        }
    }
    return 0;
}

创建 epoll 监听文件

调用epoll_create1建立一个epoll对象(在epoll文件系统中给这个句柄分配资源);

// /system/core/init/Init.cpp
main()
{
......
    epoll_fd = epoll_create1(EPOLL_CLOEXEC);     // 创建 epoll 句柄
    if (epoll_fd == -1) {
        ERROR("epoll_create1 failed: %s\n", strerror(errno));
        exit(1);
    }
......
}

这里没有使用 epoll_create 而使用的是 epoll_create1,具体区别如下:

epoll_create epoll_create1 都是创建 epoll 文件描述符
#include
int epoll_create(int size);
int epoll_create1(int flags);
epoll_create() 创建一个新的 epoll 实例。从 Linux 2.6.8 开始,size 参数被忽略,但必须大于零; 看下面的注意事项。 epoll_create() 返回一个指向新创建的 epoll 实例的文件描述符。 这个文件描述符用于所有后续调用 epoll 的所有接口。 当不再需要文件描述符时,使用 close 关闭。 当所有指向这个 epoll 实例的文件描述符都关闭时,内核销毁实例并释放关联的重用资源。
epoll_create1() 如果 flags 为 0 , epoll_create1() 和删除了过时 size 参数的 epoll_create() 相同。 如果 flags 中包含以下值就有不同的表现: EPOLL_CLOEXEC 在文件描述符上面设置执行时关闭 (FD_CLOEXEC) 标志描述符。具体请参考 open 中对 O_CLOEXEC 标志的描述。
epoll_create epoll_create1 成功时,返回一个非负文件描述符。发生错误时,返回 -1,并且将 errno 设置为指示错误
errno错误
EINVAL size不是正数。
EINVAL 在epoll_create1()的flags标志中包含无效值。
EMFILE 达到了用户对epoll设置的最大实例数限制,在 /proc/sys/fs/epoll/max_user_instances 具体查看
EMFILE 达到了进程中文件描述符的最大限制
ENFILE 达到了系统范围内对打开文件总数的限制
ENOMEM 没有足够的内存来创建内核对象。
epoll_create()在2.6版中添加到内核中。glibc库从版本2.3.2开始提供了支持。
epoll_create1()在2.6.27版中添加到内核中。glibc库从2.9版开始提供了支持
备注:
最初 epoll_create()的参数 size 告诉内核添加到 epoll 实例的文件描述符个数,内核根据这个参数分配最初的描述事件结构体的空间,(如果有必要,内核会分配超过size的空间)
现在 size 不再需要了(内核动态的分配描述事件需要的空间),但是 size 必须要设置大于 0 的值,是为了新的应用能运行在旧的内核上。

设置 epoll 监听对象及唤醒处理函数

init 进程启动的服务的守护
signal_handler_init 初始化子进程退出的信号处理过程

// /system/core/init/Init.cpp
void signal_handler_init() {
    // Create a signalling mechanism for SIGCHLD.
    int s[2];
	// socketpair()创造一对未命名的、相互连接的UNIX域套接字
    if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s) == -1) {
        ERROR("socketpair failed: %s\n", strerror(errno));
        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;  // 设置信号处理函数句柄,当有信号产生时,会向上面创建的socket写入数据, epoll 监控到该 socket 对中的 fd 可读时, 就会调用注册的函数去处理该事件 
    act.sa_flags = SA_NOCLDSTOP;  // 设置标志,表示只有当子进程终止时才接受 SIGCHID 信号
    sigaction(SIGCHLD, &act, 0);  // 初始化 SIGCHLD 信号处理方式 
    // 此时收到的信号,为当前时刻之前推出的子进程,此时先将这些子进程处理了。
    reap_any_outstanding_children();
    // 将此 signal_read_fd 注册到 epoll 
    register_epoll_handler(signal_read_fd, handle_signal);
}

调用了 reap_any_outstanding_children 处理在此之前发生的事件

// /system/core/init/signal_handler.cpp
static void reap_any_outstanding_children() {
    while (wait_for_one_process()) {
    }
}

while 循环调用 wait_for_one_process 处理

// /system/core/init/signal_handler.cpp
static bool wait_for_one_process() {
    int status;

    // pid_t pid = TEMP_FAILURE_RETRY(waitpid(-1, &status, WNOHANG));
	// 这里使用了WNOHANG(wait no hung)参数调用waitpid,即使没有子进程退出,它也会立即返回,不会像wait那样永远等下去。
	// TEMP_FAILURE_RETRY 宏替代开始
    pid_t pid = -1;
    do {
        pid = waitpid(-1, &status, WNOHANG); 
    } while (pid == -1 && errno == EINTR);
    // TEMP_FAILURE_RETRY 宏替代结束

    if (pid == 0) {
        return false;
    } else if (pid == -1) {
        ERROR("waitpid failed: %s\n", strerror(errno));
        return false;
    }

    service* svc = service_find_by_pid(pid);

    std::string name;
    if (svc) {
        name = android::base::StringPrintf("Service '%s' (pid %d)", svc->name, pid);
    } else {
        name = android::base::StringPrintf("Untracked pid %d", pid);
    }

    NOTICE("%s %s\n", name.c_str(), DescribeStatus(status).c_str());

    if (!svc) {
        return true;
    }

    // TODO: all the code from here down should be a member function on service.

    if (!(svc->flags & SVC_ONESHOT) || (svc->flags & SVC_RESTART)) {
        NOTICE("Service '%s' (pid %d) killing any children in process group\n", svc->name, pid);
        kill(-pid, SIGKILL);
    }

    // Remove any sockets we may have created.
    for (socketinfo* si = svc->sockets; si; si = si->next) {
        char tmp[128];
        snprintf(tmp, sizeof(tmp), ANDROID_SOCKET_DIR"/%s", si->name);
        unlink(tmp);
    }

    if (svc->flags & SVC_EXEC) {
        INFO("SVC_EXEC pid %d finished...\n", svc->pid);
        waiting_for_exec = false;
        list_remove(&svc->slist);
        free(svc->name);
        free(svc);
        return true;
    }

    svc->pid = 0;
    svc->flags &= (~SVC_RUNNING);

    // Oneshot processes go into the disabled state on exit,
    // except when manually restarted.
    if ((svc->flags & SVC_ONESHOT) && !(svc->flags & SVC_RESTART)) {
        svc->flags |= SVC_DISABLED;
    }

    // Disabled and reset processes do not get restarted automatically.
    if (svc->flags & (SVC_DISABLED | SVC_RESET))  {
        svc->NotifyStateChange("stopped");
        return true;
    }

    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) {
                ERROR("critical process '%s' exited %d times in %d minutes; "
                      "rebooting into recovery mode\n", svc->name,
                      CRITICAL_CRASH_THRESHOLD, CRITICAL_CRASH_WINDOW / 60);
                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;

    // Execute all onrestart commands for this service.
    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);
    }
    svc->NotifyStateChange("restarting");
    return true;
}

signal_handler_init 还调用了 register_epoll_handler 添加套接字 和回调函数

// /system/core/init/Init.cpp
// 这里的 fn 就是 handle_signal()
void register_epoll_handler(int fd, void (*fn)()) {
    epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.ptr = reinterpret_cast<void*>(fn);
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) {
        ERROR("epoll_ctl failed: %s\n", strerror(errno));
    }
}

回调函数 handle_signal

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

    reap_any_outstanding_children();
}

调用了 reap_any_outstanding_children(); 此函数上面已经分析,不做分析了。
至此,已经将监听对象和回调函数都已经注册进 epoll 了。

epoll_wait 收集发生事件

main()
{
......
    while (true) {
        ......
        epoll_event ev;
        int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, timeout));     // 开始等待事件触发
        if (nr == -1) {
            ERROR("epoll_wait failed: %s\n", strerror(errno));
        } else if (nr == 1) {
            ((void (*)()) ev.data.ptr)();
        }
    }
    return 0;
}

epoll_wait 之后 init 的 epoll 功能正式启动工作,来监听子进程死掉的事件,然后进行重启相关子进程等。

epoll 解析

调用 epoll_create 时,内核除了帮我们在 epoll 文件系统里建了个 file 结点,在内核 cache 里建了个红黑树用于存储以后 epoll_ctl 传来的 socket 外,还会再建立一个 rdllist 双向链表,用于存储准备就绪的事件,当 epoll_wait 调用时,仅仅观察这个 rdllist 双向链表里有没有数据即可。有数据就返回,没有数据就 sleep,等到 timeout 时间到后即使链表没数据也返回。

所有添加到 epoll 中的事件都会与设备(如网卡)驱动程序建立回调关系,也就是说相应事件的发生时会调用这里的回调方法。这个回调方法在内核中叫做 ep_poll_callback,它会把这样的事件放到上面的rdllist 双向链表中。在 epoll中对于每一个事件都会建立一个 epitem 结构体

当调用 epoll_wait 检查是否有发生事件的连接时,只是检查 eventpoll 对象中的rdllist 双向链表是否有 epitem 元素而已,如果 rdllist 链表不为空,则这里的事件复制到用户态内存(使用共享内存提高效率)中,同时将事件数量返回给用户。因此 epoll_waitx 效率非常高。epoll_ctl 在向 epoll 对象中添加、修改、删除事件时,从 rbr 红黑树中查找事件也非常快,也就是说 epoll 是非常高效的,它可以轻易地处理百万级别的并发连接。

整体来说如下:
一颗红黑树,一张准备就绪句柄链表,少量的内核cache,就帮我们解决了大并发下的socket处理问题。

执行epoll_create()时,创建了红黑树和就绪链表;

执行epoll_ctl()时,如果增加socket句柄,则检查在红黑树中是否存在,存在立即返回,不存在则添加到树干上,然后向内核注册回调函数,用于当中断事件来临时向准备就绪链表中插入数据;

执行epoll_wait()时立刻返回准备就绪链表里的数据即可。

epoll 模型 流程

epoll模型原来的流程:

epoll_create(); // 创建监听红黑树
epoll_ctl(); // 向书上添加监听fd
epoll_wait(); // 监听
有监听fd事件发送—>返回监听满足数组—>判断返回数组元素—>
lfd满足accept—>返回cfd---->read()读数据—>write()给客户端回应。

epoll反应堆模型的流程:

epoll_create(); // 创建监听红黑树
epoll_ctl(); // 向书上添加监听fd
epoll_wait(); // 监听
有客户端连接上来—>lfd调用acceptconn()—>将cfd挂载到红黑树上监听其读事件—>
epoll_wait()返回cfd—>cfd回调recvdata()—>将cfd摘下来监听写事件—>
epoll_wait()返回cfd—>cfd回调senddata()—>将cfd摘下来监听读事件—>…—>

你可能感兴趣的:(linux,android,init,c++,android,init)