Use eventpoll+signalfd to process signals

Use eventpoll+signalfd to process signals


signal processing flow in eventpoll+signalfd mode

1. Set SIGCHLD to block

Call sigprocmask, and the system call will SIGCHLD signal block, that is, the task of init process_ struct. The corresponding bit of blocked SIGCHLD is set to 1. Set to blocked to prevent the native signal processing flow from processing this signal,

2. Create signalfd file

Signalfd file is an anon file, file f_ OP is signalfd_fops,file.private_data points to a signalfd_ctx, the sigmask member in this structure holds the signal concerned by signalfd. Before assigning a value to this member, there is a signotset(mask), which reverses the mask, so bit 0 represents the signal concerned, while bit 1 represents not the signal concerned (the meaning of this representation is determined by the way next_signal() takes out the pending signal)

3. Register signalfd# file to eventpoll

Call epoll_ctl(EPOLL_CTL_ADD) system calls to register signalfd # file to eventpoll, is EPOLLIN

4. Call epoll_ The wait system call returns from this system call when waiting for the target signal to be generated. At this time, the epoll of the signal concerned by signalfd is obtained_ Event, and then call epoll_event. The callback function pointed to by data, that is, HandleSignalFd()

5. Call signalfd file read system call to get signalfd_siginfo

HandleSignalFd() called the read function of signalfd file to get signalfd_siginfo, such as the signalfd read at this time_ SSI of siginfo_ If the Signo member is SIGCHLD, call realanyoutstandingchildren(), and this function calls waitid system call to get which child process exit s


Through the above process, the sigcld signal sent to itself by the init process when processing the child process exit is realized. This is different from the original sigaction method. Sigcld signal SA needs to be registered in user space_ Handler, which calls back the Sa of user space when SIGCHLD} signal is generated_ handler.


As for why we use the method of eventpoll+signalfd to process signals, this is because the epoll of multiple files can be monitored through one eventpoll_ Event occurs through an epoll_wait system call can monitor epoll of multiple monitored files_ For the generation of event, please refer to another article:


*If a signal is set to blocked, it can still be sent to the process, but it will not be processed on the native signal processing flow. For details, please refer to another article:



In InstallSignalFdHandler(), you can call sigaction for the init process on SIGCHLD # signal, and the Sa of this signal_ Set handler to SIG_DFL, simultaneous sa_flags is SA_NOCLDSTOP.

What is the purpose of this?

This is because for the parent process, sigcld , signal will be sent to the parent process when the child process stops (such as receiving SIGSTOP , signal) or exit s. SA is set here_ Handler is SIG_DFL,sa_flags with SA_NOCLDSTOP will prohibit the child process from sending sigcld # signal to the parent process when stopping. See do for code logic_ notify_ parent_ cldstop()

Therefore, when the child process of init stop s, sigcld  signal will not be sent to the init process, so signalfd will not receive such sigcld  signal, but only sigcld  signal of the child process exit class


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);
546      sigset_t mask;
547      sigemptyset(&mask);
548      sigaddset(&mask, SIGCHLD);
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      }
556      if (sigprocmask(SIG_BLOCK, &mask, nullptr) == -1) {
557          PLOG(FATAL) << "failed to block signals";
558      }
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      }
566      signal_fd = signalfd(-1, &mask, SFD_CLOEXEC);
567      if (signal_fd == -1) {
568          PLOG(FATAL) << "failed to create signalfd";
569      }
571      if (auto result = epoll->RegisterHandler(signal_fd, HandleSignalFd); !result) {
572          LOG(FATAL) << result.error();
573      }
574  }




Because the init process blocks the SIGCHLD signal, when the init fork subprocess, the task of the subprocess_ struct. Blocked will copy directly from the parent process, so calling UnblockSignals() in the child process will unblock sigcld # signal. Therefore, the child process still follows the native processing process for sigcld # signal:

526  static void UnblockSignals() {
527      const struct sigaction act { .sa_handler = SIG_DFL };
528      sigaction(SIGCHLD, &act, nullptr);
530      sigset_t mask;
531      sigemptyset(&mask);
532      sigaddset(&mask, SIGCHLD);
533      sigaddset(&mask, SIGTERM);
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() registers the callback function. When the parent process forks the child process, it will call back these callbacks:

Parent: execute this callback in the parent process;

Child: execute this callback in the child process.


Added by hach22 on Fri, 04 Feb 2022 10:46:11 +0200