forkとsigsuspend


```cpp:a.cpp

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include <random>
#include <cstdio>
#include <cstring>
#include <map>
#include <vector>
#include <string>

class child_info {
    public:
        pid_t pid;
        int status;
        bool running;
        std::string tag;
        child_info() {
            this->pid = 0;
            this->status = 0;
            this->running = false;
        }
};

typedef std::map< pid_t, child_info > Mpc;
Mpc Child_table;
volatile sig_atomic_t Signal_flag = 0; // SIGHUP、SIGINT、SIGQUIT、SIGTERMを受けたら1
volatile sig_atomic_t Child_flag = 0; // SIGCHLD受けたら1
volatile sig_atomic_t Alarm_flag = 0; // SIGARLM受けたら1

typedef void(*Handler_func)(int); 

void handler_alarm(int) {
    Alarm_flag = 1;
}

void handler_user1(int) {

}

void handler_child(int) {
    Child_flag = 1;
}

void handler_finish(int) {
    Signal_flag = 1;
}

void _sleep(double t) {
    // doubleで渡ってくる秒数を解釈して、いい感じにsleepを呼ぶ。
    // sigtimedwaitを使って指定した時間待ってみる。
    // 指定した時間中にTERMを補足した場合は、Signal_flagを1にして返る。
    if (t <= 0) {
        return;
    }
    double numf;
    double numi;
    numf = modf(t, &numi);
    timespec ts;
    ts.tv_sec = static_cast<long>(numi);
    ts.tv_nsec = static_cast<long>(numf) * 1000000000;
    sigset_t ss;
    sigemptyset(&ss);
    sigaddset(&ss, SIGTERM); // TERMだけ確認する。
    errno = 0;
    if (sigtimedwait(&ss, nullptr, &ts) < 0) {
        if (errno == EAGAIN) {
            // 単にsleep秒を満了しただけ。
        } else {
            // EAGAIN以外の場合、引数が不正か、待っていないシグナルを受けた可能性がある。
            printf("sigtimedwait fail. %s\n", strerror(errno));
        }
    } else {
        // TERMを受けた。
        // sigtimedwaitでは、たぶんシグナルハンドラは呼ばれないので、
        // ここでSignal_flagを1にする。
        Signal_flag = 1;
    }
}

void run_child() {
    std::random_device rd;
    std::mt19937 mt( rd() );
    std::uniform_real_distribution<double> sleep_time( 1.0, 10.0 );
    
    printf("start child process. [%d]\n", getpid());
    _sleep(sleep_time(mt));
    printf("end child process. [%d]\n", getpid());
}

void gen_child() {
    int limit = 3;
    while (1) {
        pid_t pid = fork();
        if (pid < 0) {
            printf("fork fail. %s\n", strerror(errno));
            // 子の生み出しに失敗した場合は、1秒待ってリトライ。
            // 3回リトライしてもダメなら、諦めー。
            _sleep(1.0);
            if (limit-- > 0) {
                continue;
            } else {
                break;
            }
        } else if (!pid) {
            // 親側が、子を管理するテーブルに情報をセットするまで、
            // 子側の処理は停止したい。
            sigset_t ss;
            sigemptyset(&ss);
            // 親からのSIGUSR1を待つ。不測の事態が起きた場合を考えたほうが良い・・・？
            sigsuspend(&ss);
            run_child();
            _exit(1);
        }

        Child_table[pid].pid = pid;
        Child_table[pid].running = true;
        Child_table[pid].tag = "run_child";

        // 準備ができたので、子のsleepを解除する。
        // これ、親がSIGUSR1を子に投げる前に、何かの理由で即死した場合、
        // 子は、sigsuspendで延々とSIGUSR1を待つ形になるか・・・。うーん。
        kill(pid, SIGUSR1);
        break;
    }
}

size_t check_child_table() {
    while (1) {
        int status = 0;
        pid_t pid = waitpid(-1, &status, WNOHANG);
        if (pid > 0) {
            if (Child_table.find(pid) != Child_table.end()) {
                Child_table[pid].status = status;
                Child_table[pid].running = false;
            }
        } else {
            break;
        }
    }
    std::vector<pid_t> erase_id;
    for (auto &it : Child_table) {
        if (!it.second.running) {
            erase_id.push_back(it.first);
        }
    }
    for (auto &it : erase_id) {
        Child_table.erase(it);
    }
    return Child_table.size();
}

void alarm_event() {
    time_t ntime = time(nullptr);
    // ctimeが末尾に\nくっつけてくるので、printfに\nを渡していない
    printf("alarm_event. %s", ctime(&ntime));   
}

void check_alarm_signal() {
    // sigtimedwaitを使って、シグナル集合にあるかもしれないSIGALRMを削除する。
    sigset_t ss;
    sigemptyset(&ss);
    sigaddset(&ss, SIGALRM);
    timespec ts;
    ts.tv_sec = 0;
    ts.tv_nsec = 0;
    sigtimedwait(&ss, nullptr, &ts);
}

void child_cleanup() {
    if (!check_child_table()) {
        return;
    }
    // 子プロセス全体にSIGTERMを送る。自分自身も含まれるが、ブロックしているので問題ないはず。
    killpg(getpgrp(), SIGTERM);
    
    // この時点で、ブロックしているシグナルにAlarmが残っていると、
    // 子プロセスが即座にkillされてしまうので、
    // 確認して、もし残っているなら空打ちさせておく。
    check_alarm_signal();
    
    // 10秒以内にすべての子プロセスが終わらない場合、残っている子プロセスにkillを送る。
    // この処理に入る前に、事前に作ったタイマーを停止させておくこと。
    alarm(10);
    
    sigset_t ss;
    sigemptyset(&ss);
    
    while (1) {
        sigsuspend(&ss); // さっきのTERMが1回発火するが、ここでなら問題ない。
        if (Child_flag) {
            Child_flag = 0;
            if (!check_child_table()) {
                break;
            }
        }
        if (Alarm_flag) {
            // 10秒経過してしまったので。
            // まだ動作している子プロセスに、killを送って終了させる。
            // これはkillpgではできない。自分自身もkillされてしまうので。。
            for ( auto &it: Child_table ) {
                kill(it.first, SIGKILL);
            }
            break;
        }
    }
}

int run() {
    size_t cycle = 0;
    
    sigset_t newss;
    sigset_t oldss;
    sigemptyset(&newss);
    
    sigaddset(&newss, SIGHUP);
    sigaddset(&newss, SIGINT);
    sigaddset(&newss, SIGQUIT);
    sigaddset(&newss, SIGTERM);
    sigaddset(&newss, SIGCHLD);
    sigaddset(&newss, SIGUSR1);
    sigaddset(&newss, SIGALRM);
    sigprocmask(SIG_BLOCK, &newss, &oldss);
    
    // 一定間隔でalarmシグナルを発生させる。
    timer_t timerid;
    struct itimerspec itimer;
    itimer.it_value.tv_sec = 60; 
    itimer.it_value.tv_nsec = 0; 
    itimer.it_interval.tv_sec = 60; 
    itimer.it_interval.tv_nsec =  0; 
    timer_create(CLOCK_REALTIME, nullptr, &timerid);
    timer_settime(timerid, CLOCK_REALTIME, &itimer, nullptr);
    
    while (!Signal_flag) {
        ++cycle;
        printf("start %lu cycle.\n", cycle);
        for (int i = 0; i < 10; ++i) {
            gen_child();
        }
        while (!Signal_flag) {
            sigsuspend(&oldss);
            if (Child_flag) {
                Child_flag = 0;
                if (!check_child_table()) {
                    break;
                }
            }
            if (Alarm_flag) {
                Alarm_flag = 0;
                alarm_event();
            }
            if (Signal_flag) {
                // タイマー停止。
                timer_delete(timerid);
                child_cleanup();
            }
        }
        printf("end %lu cycle.\n", cycle);
    }
    
    timer_delete(timerid);
    return 0;
}

void set_sigaction_ign(int sig) {
    struct sigaction sa;
    memset(&sa, 0, sizeof(struct sigaction));
    sa.sa_handler = SIG_IGN;
    sigemptyset(&sa.sa_mask);
    errno = 0;
    if (sigaction(sig, &sa, nullptr)) {
        printf("sigaction fail. %s\n", strerror(errno));
        exit(1);
    }
}

void set_sigaction(int sig, Handler_func func) {
    struct sigaction sa;
    memset(&sa, 0, sizeof(struct sigaction));
    sa.sa_handler = func;
    sigemptyset(&sa.sa_mask);
    errno = 0;
    if (sigaction(sig, &sa, nullptr)) {
        printf("sigaction fail. %s\n", strerror(errno));
        exit(1);
    }
}

void init_signal() {
    set_sigaction(SIGCHLD, &handler_child);
    set_sigaction(SIGUSR1, &handler_user1);
    set_sigaction(SIGALRM, &handler_alarm);
    set_sigaction(SIGHUP, &handler_finish);
    set_sigaction(SIGINT, &handler_finish);
    set_sigaction(SIGQUIT, &handler_finish);
    set_sigaction(SIGTERM, &handler_finish);
    set_sigaction_ign(SIGPIPE);
}

int main(int argc, char **argv) {
    init_signal();
    return run();
}

```


子を産んで、看取って、また産んで、
ということをしたい。。。
上記例では、10個産んで、全部終わるのを待ち、また10個産んでを繰り返す。

これで、ひとまず動作するかなぁ、、というところですが、
不足している内容や、こういった場合は停止してしまう、
とかあれば、突っ込みください。


追記
alarmイベントも起こして、子の終了か、alarmのイベントか見て、
処理するようにした。

追記
hup、int、quit、termを受けた場合の処理を追加。
親がtermを受けた場合、その時点で動作している子プロセスに
termを送り、できる限り早く終了する。
（デーモンプロセスの終了処理を想定）