【问题标题】:Blocking new signals while in handler在处理程序中阻止新信号
【发布时间】:2014-03-07 16:45:26
【问题描述】:

我有一个管理子进程(fork、execve)的父进程。我在父级中创建了一个处理程序来捕获来自子级的 SIGCHLD 信号,以便调用waitpid() 并采取适当的措施,例如重新启动子级。

我从sigaction() 的手册页了解到,在信号处理程序中,默认情况下会阻止更多相同类型的信号。我绝对希望这种行为,所以我决定测试它。

我在信号处理程序的末尾放置了一个睡眠(我自己的实现在循环中使用clock_nanosleep() 并在中断时恢复)并向孩子发送了一个 SIGINT。这适时让它退出并将 SIGCHLD 发送给父级。我记录了这个事实并开始睡眠 10 秒钟。现在,我向新孩子发送了另一个 SIGINT(sighandler 第一次重新启动它),并惊讶地看到另一个日志和睡眠发生了。

这怎么可能?当我使用调试器附加到父级时,它清楚地显示两个不同的线程被中断以调用我的信号处理程序,现在都处于睡眠状态。如果这样继续下去,我会用完线程!

我知道将长时间睡眠放入信号处理程序是一件愚蠢的事情,但它确实说明了这一点;我希望看到 /proc/[PID]/status 中标记为待处理的第二个信号,但它已被传递。

这是我的代码的相关部分:

设置 SIGCHLD 处理程序:

typedef struct SigActType {
    struct sigaction act;
    int              retval;
    void             (*func)(int);
}SigActType;
static SigActType sigActList[64];

public void setChildHandler(void (*func)(int)) {
    SigActType *sat = &sigActList[SIGCHLD];

    sat->act.sa_sigaction = sigchldHandler;
    sigemptyset(&sat->act.sa_mask);
    sigaddset (&sat->act.sa_mask, SIGTERM);
    sigaddset (&sat->act.sa_mask, SIGINT);
    sigaddset (&sat->act.sa_mask, SIGCHLD);
    sat->act.sa_flags = SA_SIGINFO;
    sat->retval = 0;
    sat->func = func;
    sigaction(SIGCHLD, &sat->act, NULL);
}

static void sigchldHandler(int sig, siginfo_t *si, void *thing) {
    SigActType *sat = &sigActList[SIGCHLD];

    if (sat->func) {
        sat->func(si->si_pid);
    }
}

并使用这个:

int main(int argc, char **argv) {
    setChildHandler(manageChildSignals);
    ...
}

static void manageChildSignals(int d) {
    if ((pid = waitpid(-1, &stat, WAIT_MYPGRP)) > 0) {
        ... restart child if appropriate
    }
    printf("start of pause...\n");
    mySleep(10);
    printf("end of pause...\n");
}

标准输出清楚地显示:

(when I type kill -2 [PID]
start of pause
(when the new child is started and I type kill -2 [NEWPID]
start of pause
...10 seconds slide past...
end of pause
end of pause

我很困惑为什么会发生这种情况。如您所见,我什至将 SIGCHLD 添加到 sigaction() 的块掩码中,以鼓励它做正确的事情。

欢迎任何指点!

【问题讨论】:

  • mysleep 是做什么的?
  • 它是在循环中使用clock_nanosleep()sleep() 的实现,同时它返回EINTR,因此即使被中断也会休眠正确的时间
  • 哦,你正在使用多个线程。
  • 道歉;我应该说清楚的。是的,main() 设置信号处理程序和工作线程然后进入睡眠状态。其他线程完成工作。
  • 在这种情况下,每个线程总是有资格被强制跳转到信号。

标签: c linux multithreading signals


【解决方案1】:

默认情况下会阻止相同类型的信号。

是的,但仅适用于调用线程sigaction()

来自man sigaction(我粗体强调):

sa_mask 指定应该被阻止的信号掩码(即, 添加到信号处理程序所在线程的信号掩码 在信号处理程序执行期间调用)。

由于信号分配是每个进程的,因此任何其他未阻塞相关信号的线程都可能接收到它,即被中断并处理它。

如果这种行为不是您想要的,您或许应该修改程序处理信号的方式的设计,使默认情况下每个线程的所有信号都被阻塞,并且只有一个特定线程的信号接收被解除阻塞。

更新:

信号掩码由子线程从父线程继承。

如果信号处理只能由一个特定线程完成,则在创建任何其他线程之前让主线程阻塞所有信号。然后创建一个特定线程来进行信号处理,并让该线程解除对要处理的信号的阻塞。这一概念还允许每个信号一个线程的模型。

在多线程环境中,使用pthread_sigmask() 屏蔽每个线程基础上的信号。

请注意sigprocmask()在多线程进程中的行为是未指定的,那么请使用pthread_sigmask()

【讨论】:

  • "仅适用于调用 sigaction 的线程"?那大概就可以解释了。那么我的任何其他线程都可能被中断来处理这个信号吗?只是不是我称之为 sigaction 的“主要”吗?
  • 确实如此。但我认为处理信号的不同线程不会引起问题。我以为sigaction() 应用到了进程中!
  • 用于“更新”。是的,我想到了这一点,并在孩子开始时将它们放回去。
  • @jrichemont: 那就是为一个进程的所有线程安装处理。然而,制作是为实际接收信号的线程完成的。掩码是在每个线程基础上完成的。请在我的回答中查看 pthread_sigmask()` 的附加内容。
猜你喜欢
  • 2016-04-05
  • 2013-11-29
  • 1970-01-01
  • 1970-01-01
  • 2013-07-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多