使用eventpoll+signalfd方式来处理signal

使用eventpoll+signalfd方式来处理signal

 

android init进程signal处理采用了eventpoll+signal的方式,本文解析一下这种方式是如何实现的

eventpoll+signalfd方式signal处理流程

1. 将SIGCHLD block

调用sigprocmask系统调用将SIGCHLD signal block,即将init进程的task_struct.blocked SIGCHLD对应bit设置为1。设置为blocked是为了防止原生的signal处理flow去处理此signal,

2. 创建signalfd file

signalfd file是anon file,file.f_op为signalfd_fops,file.private_data指向一个signalfd_ctx,这个structure里的sigmask成员保存着signalfd所关注的signal,在给这个成员赋值前有一个signotset(mask),将mask取反了,所以bit为0代表是所关注的signal,而为1表示不是所关注的signal(这种表示含义由next_signal()取出pending signal的方式所决定)

3. 注册signalfd file到eventpoll

调用epoll_ctl(EPOLL_CTL_ADD)系统调用注册signalfd file到eventpoll,epoll_event.events为EPOLLIN

4. 调用epoll_wait系统调用等待目标signal产生时从此系统调用返回,此时获取了signalfd所关注signal的epoll_event,再调用epoll_event.data所指向的callback函数,即HandleSignalFd()

5. 调用signalfd file read系统调用获取signalfd_siginfo

HandleSignalFd()调用了signalfd file的read函数,以获取signalfd_siginfo,比如此时读取到的signalfd_siginfo的ssi_signo成员是SIGCHLD,则调用ReapAnyOutstandingChildren(),而这个函数是调用了waitid系统调用获取是哪个子进程exit了

 

通过上述流程就实现了init进程处理子进程exit时发给自己的SIGCHLD signal,这个不同于原生的sigaction方式需要在user space注册SIGCHLD signal sa_handler,在SIGCHLD signal产生时再回调user space的sa_handler。

 

至于为什么要采用eventpoll+signalfd的方式来处理signal,这个是因为通过一个eventpoll就能监视多个file的epoll_event的发生,即通过一个epoll_wait系统调用即可监视多个被监视file的epoll_event的产生,具体参考我的另外一篇文章:

https://www.cnblogs.com/aspirs/p/15861763.html

 

* 某个signal被设置为blocked,该signal还是可以被发送给进程,只是在原生signal处理flow上就不会再去处理它了,这块具体参考我的另外一篇文章:

https://www.cnblogs.com/aspirs/p/15504105.html

 

注:

在InstallSignalFdHandler()里有为init进程对SIGCHLD signal调用sigaction,将此signal的sa_handler设置为了SIG_DFL,同时sa_flags为SA_NOCLDSTOP。

这个的目的是什么呢?

这个是因为对父进程来说,SIGCHLD signal在子进程stop(比如收到SIGSTOP signal)或者exit时均会给父进程发送,这里设置sa_handler为SIG_DFL、sa_flags带有SA_NOCLDSTOP,将禁止子进程在stop时给父进程发送SIGCHLD signal,code逻辑见do_notify_parent_cldstop()

所以对于init的子进程stop时,将不会给init进程发送SIGCHLD signal,所以signalfd将接收不到此类SIGCHLD signal,只会接收到子进程exit类的SIGCHLD signal

system/core/init/init.cpp

540  static void InstallSignalFdHandler(Epoll* epoll) {
541      // Applying SA_NOCLDSTOP to a defaulted SIGCHLD handler prevents the signalfd from receiving
542      // SIGCHLD when a child process stops or continues (b/77867680#comment9).
543      const struct sigaction act { .sa_handler = SIG_DFL, .sa_flags = SA_NOCLDSTOP };
544      sigaction(SIGCHLD, &act, nullptr);
545  
546      sigset_t mask;
547      sigemptyset(&mask);
548      sigaddset(&mask, SIGCHLD);
549  
550      if (!IsRebootCapable()) {
551          // If init does not have the CAP_SYS_BOOT capability, it is running in a container.
552          // In that case, receiving SIGTERM will cause the system to shut down.
553          sigaddset(&mask, SIGTERM);
554      }
555  
556      if (sigprocmask(SIG_BLOCK, &mask, nullptr) == -1) {
557          PLOG(FATAL) << "failed to block signals";
558      }
559  
560      // Register a handler to unblock signals in the child processes.
561      const int result = pthread_atfork(nullptr, nullptr, &UnblockSignals);
562      if (result != 0) {
563          LOG(FATAL) << "Failed to register a fork handler: " << strerror(result);
564      }
565  
566      signal_fd = signalfd(-1, &mask, SFD_CLOEXEC);
567      if (signal_fd == -1) {
568          PLOG(FATAL) << "failed to create signalfd";
569      }
570  
571      if (auto result = epoll->RegisterHandler(signal_fd, HandleSignalFd); !result) {
572          LOG(FATAL) << result.error();
573      }
574  }

 

 

注:

因为init进程将SIGCHLD信号block了,init fork子进程时,子进程的task_struct.blocked将直接从父进程copy过来,所以在子进程内调用UnblockSignals()将SIGCHLD signal unblock,所以子进程对于SIGCHLD signal的处理方式还是走原生处理流程:

526  static void UnblockSignals() {
527      const struct sigaction act { .sa_handler = SIG_DFL };
528      sigaction(SIGCHLD, &act, nullptr);
529  
530      sigset_t mask;
531      sigemptyset(&mask);
532      sigaddset(&mask, SIGCHLD);
533      sigaddset(&mask, SIGTERM);
534  
535      if (sigprocmask(SIG_UNBLOCK, &mask, nullptr) == -1) {
536          PLOG(FATAL) << "failed to unblock signals for PID " << getpid();
537      }
538  }

 

 

int pthread_atfork(void (*prepare)(void), void (*parent)(void),
       void (*child)(void))

 

* pthread_atfork()注册了回调函数,父进程fork子进程时将会回调这些callback:

parent:在父进程里执行此callback;

child:在子进程里执行此callback。

 

posted @ 2022-02-04 16:31  aspirs  阅读(186)  评论(0编辑  收藏  举报