SIGSTOP/SIGCONT POSIX 行为

SIGSTOP/SIGCONT POSIX behavior

我正在研究信号:特别是 SIGSTOPSIGCONT。 这是我写的一个测试程序。这个想法是创建一个 N + 1 的链 进程(包括主进程)。每个人都必须等待其 child 停止,然后停止 本身。主进程必须唤醒它的 child 当后者有 停止了。

为此,f 函数递归地创建流程链。每个 该进程在 SIGCHLD 信号上使用 sigsuspend,除了最后一个 child谁直接ps自己。当它的 child 停止时,一个进程 将收到 SIGCHLD 信号,然后它可以在轮到它时停止。什么时候 主进程收到 SIGCHLD 信号,这意味着所有的 进程处于停止状态,因此它向其发送 SIGCONT 信号 child。每个进程将 SIGCONT 发送到它自己的 child 然后退出,分开 来自刚刚退出的最后一个 child。

我试图说清楚:删除了 return 代码测试并写了一些 评论。

当执行程序时,一切似乎都很好,但是 SIGCONT 链。一些进程被唤醒,但不是全部。看着 运行 程序(例如 ps)一切似乎都很好:否 阻塞的进程。我真的不明白这有什么问题 程序。欢迎任何帮助或提示。

这是一个示例跟踪。如您所见,"fork chain" 运行良好,进程在 SIGCHLD 上暂停。然后最后一个 child 生成并停止 ps。这会在 parents 上创建一个“SIGCHLD 链”,因为每个进程都会停止 ps 本身。当主进程收到 SIGCHLD 的通知时,它会将 SIGCONT 发送到它的 child,后者被唤醒,然后将 SIGCONT 发送到它自己的 child 等. 你可以注意到这条链是不完整的:

$ ./bin/trycont 
n   pid     log
0   6257    "suspending on SIGCHLD"
1   6258    "suspending on SIGCHLD"
2   6259    "suspending on SIGCHLD"
3   6260    "suspending on SIGCHLD"
4   6261    "suspending on SIGCHLD"
5   6262    "last child - stopping"
4   6261    "got SIGCHLD"
4   6261    "stopping"
3   6260    "got SIGCHLD"
3   6260    "stopping"
2   6259    "got SIGCHLD"
2   6259    "stopping"
1   6258    "got SIGCHLD"
1   6258    "stopping"
0   6257    "got SIGCHLD"
0   6257    "sending SIGCONT to 6258"
1   6258    "awakened - sending SIGCONT to 6259"
2   6259    "awakened - sending SIGCONT to 6260"
# <- not the expected trace

程序如下:src/trycont.c

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>

/* number of created processes with fork
 */
#define N 5

#define printHeader() printf("n\tpid\tlog\n");
#define printMsg(i, p, str, ...) printf("%d\t%d\t" #str "\n", i, p, ##__VA_ARGS__)

void f(int n);
void handler(int sig);

sigset_t set;
struct sigaction action;

int main(int argc, char *argv[])
{
    /* mask SIGCHLD
     */
    sigemptyset(&set);
    sigaddset(&set, SIGCHLD);
    sigprocmask(SIG_SETMASK, &set, NULL);

    /* handler will be called when SIGCHLD is sent to the process
     * during the handler, SIGCHLD will be masked (sa_mask)
     */
    action.sa_mask = set;
    action.sa_handler = handler;
    action.sa_flags = 0;

    /* SIGCHLD will trigger action
     */
    sigaction(SIGCHLD, &action, NULL);

    /* start
     */
    printHeader();
    f(N);

    exit(EXIT_SUCCESS);
}

void f(int n)
{
    pid_t p, pc;
    int myIndex;

    myIndex = N - n;
    p = getpid();

    if (n == 0)
    {
        /* last child
         */
        printMsg(myIndex, p, "last child - stopping");
        kill(p, SIGSTOP);
        printMsg(myIndex, p, "END REACHED");
        exit(EXIT_SUCCESS);
    }

    pc = fork();

    if (pc == 0)
    {
        /* recursion
         */
        f(n - 1);

        /* never reached
         * because of exit
         */
    }

    /* father
     */

    /* suspending on SIGCHLD
     * need to unmask the signal
     * and suspend
     */
    printMsg(myIndex, p, "suspending on SIGCHLD");

    sigfillset(&set);
    sigdelset(&set, SIGCHLD);
    sigsuspend(&set);

    printMsg(myIndex, p, "got SIGCHLD");

    if (n < N)
    {
        /* child process
         * but not last
         */
        printMsg(myIndex, p, "stopping");
        kill(p, SIGSTOP);

        printMsg(myIndex, p, "awakened - sending SIGCONT to %d", pc);
        kill(pc, SIGCONT);
    }
    else
    {
        /* root process
         */
        printMsg(myIndex, p, "sending SIGCONT to %d", pc);
        kill(pc, SIGCONT);
    }

    exit(EXIT_SUCCESS);
}

void handler(int sig)
{
    switch (sig)
    {
    case SIGCHLD:
        /* when the process received SIGCHLD
         * we can ignore upcoming SIGCHLD
         */
        action.sa_handler = SIG_IGN;
        sigaction(SIGCHLD, &action, NULL);
        break;
    default:
        break;
    }
}

如果您需要,这里有一个 Makefile:

CC=gcc
DEFINES=-D_POSIX_C_SOURCE
STD=-std=c11 -Wall -Werror
OPTS=-O2
CFLAGS=$(STD) $(DEFINES) $(OPTS) -g
LDFLAGS=

SRC=src
OBJ=obj
BIN=bin

DIRS=$(BIN) $(OBJ)

.PHONY: mkdirs clean distclean

all: mkdirs $(BIN)/trycont

$(BIN)/%: $(OBJ)/%.o
    $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $<

$(OBJ)/%.o: $(SRC)/%.c
    $(CC) $(CFLAGS) -c -o $@ $<

mkdirs:
    - mkdir $(DIRS)

clean:
    rm -vf -- $(OBJ)/*.o

distclean: clean
    rm -vfr -- $(DIRS)

当第一个进程终止时,您的一些(全部?)后代进程正在死于系统生成的 SIGHUP。

这是expected POSIX behavior在某些情况下。

当您从 shell 启动根进程时,它是一个 进程组领导者 ,其后代是该组的成员。当该领导者终止时,进程组是 孤立的。当系统检测到任何成员停止的新孤立进程组时,进程组的每个成员都会收到一个 SIGHUP,然后是一个 SIGCONT。

因此,当领导者终止时,您的一些后代进程仍然停止,因此每个人都会收到一个 SIGHUP,然后是一个 SIGCONT,实际上,这意味着他们死于 SIGHUP。

究竟哪些后代仍然停止(或者甚至只是愉快地向 exit() 前进)是一场计时赛跑。在我的系统上,领导者终止得如此之快以至于 none 的后代能够打印任何东西。