子信号和父信号之间的并发竞争

标签 c linux concurrency signals fork

我在过去的 CMU 考试中发现了这个问题,但我无法理解输出是如何可能的。

基本上,它背后的想法是有一个阻止用户定义信号的父进程,然后父进程派生一个子进程。并且基于首先运行的进程(又名:赢得比赛)可能会有不同的输出。 Here is the question that is being asked in the exam (请阅读)

这是考试的代码:

int i = 1;
void handler (int sig) {
    i++;
}
int main() {
    pid_t pid;
    sigset_t s;
    sigemptyset(&s);
    sigaddset(&s, SIGUSR1);
    signal(SIGUSR1, handler);
    sigprocmask(SIG_BLOCK, &s, 0);
    pid = fork();
        <LINE A>
    if (pid != 0) {
        i = 2;
        <LINE B>
    } else {
        i = 3;
        <LINE C>
    }
    sigprocmask(SIG_UNBLOCK, &s, 0);
    pause(); /* pause to allow all signals to arrive */
    printf("%d\n", i);
    exit(0);
}

因为我们需要放置函数,所以有3种情况需要测试:

kill(pid,USRSIG1);

在 LINE A 或 LINE B 或 LINE C 中找到可能的输出。

现在这是我所做的,我将函数放在 LINE A 中。

假设我们运行程序,然后父级将创建一个空集 s,将信号 SIGSUR1 添加到其中,然后它将为 SIGUSR1 信号分配一个自定义处理程序,并阻塞集 s 中的信号。这些是哪几行

    sigset_t s;
    sigemptyset(&s);
    sigaddset(&s, SIGUSR1);
    signal(SIGUSR1, handler);
    sigprocmask(SIG_BLOCK, &s, 0);

然后 parent 会跑线

    pid = fork();

这将从进程中创建一个新的子进程。

现在有 2 种情况将决定输出。操作系统安排父级或子级先运行。

假设父进程先运行。然后会执行LINE A(也就是kill函数)

因为它是父进程,所以 pid 值将是子进程的进程 ID。所以它会将 USRSIG1 发送给 child ,但由于它被阻止了,所以它什么都不做

if 语句为全局变量 i 赋值。如果进程是父进程则 i = 2,否则 i = 3。因此在我们的父进程中,我们将有 i = 2。

    if (pid != 0) { //if i am a parent then i = 2
        i = 2;
        <LINE B>
    } else { //if i am a child then i = 3
        i = 3;
        <LINE C>
    }

下一行将在父级中执行,它将解除对 SIGUSR1 信号的阻塞 sigprocmask(SIG_UNBLOCK, &s, 0); 并且父进程将暂停,直到它收到信号

现在子进程将运行,它会向进程组中的所有进程(包括它自己)发送一个 kill(0,SIGUSR1) 信号。但由于它在 child 体内被阻塞,所以什么也不会发生。 parent 将收到信号,它会将我增加 1(所以现在我在 parent 中 = 3)。它(父级)将从函数 pause 恢​​复以打印 I 的值(即 3)并退出。

child 现在从 kill 函数中恢复,因为它是 child ,if 语句将不为真(因此 child 中 i 的值 = 3)。 child 解锁来自 set 和 pause() 的信号。

由于没有其他进程向子进程发送信号,它将永远暂停,父进程的输出仅为 3。如果我们走另一条路( child 跑在 parent 之前),那么输出将只有 4。

让我感到困惑的是,考试的解决方案说每次运行有 2 个输出?我不明白这怎么可能,因为其中一个进程将停留在 pause() 中。

解决方案的关键是 LINE A 的可能输出是:

3 4, 4 3, 3 5, or 5 3

这就是我从问题中所能理解的全部内容。任何帮助或提示将不胜感激。

最佳答案

如果 child 先跑,输出将为 5,因为它会收到来自自身和 parent 的信号。如果两个进程在进入 pause() 之前都完成了 kill(pid,USRSIG1),则两者都不会终止或打印。

POSIX 还允许进程因延迟信号而终止和打印(例如,如果使用网络消息而不是共享内存),并允许子进程将任何 int 值打印为 i 不是 volatile sig_atomic_t 类型。

从评论中可以看出,考试作者错误地认为 pause() 会神奇地等待所有已发送或将要发送到进程的信号被接收到。

如果 sigprocmask(SIG_UNBLOCK, &s, 0); pause(); 被替换为对 sigsuspend 的适当调用,它将充当考试作者状态。 parent 将收到 1 个信号, child 将收到 1 或 2 个信号,因为来自 parent 的信号可能为时已晚或与来自其自身的信号相结合。

关于子信号和父信号之间的并发竞争,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46744792/

相关文章:

c - 如何链接到 FreeImage?

linux - vim中的光标可以改成下划线吗?

concurrency - 可以对并发代码进行静态检查吗?

c++ - 整数读取需要临界区保护吗?

c# - TPL 数据流,Post() 和 SendAsync() 之间的功能区别是什么?

c - 当 infix 或 postfix 遇到 "-"符号时,它似乎给了我随机值

c - kbhit() 和 getch() 和系统 ("cls"的可移植替代方案)

c - C++ 中的分配相当于 C 中的什么

c - memcpy,段错误

linux - 创建可以导入变量并针对来自外部文本文件的变量执行代码的 bash 脚本