6

首先,我知道互斥锁通常不被认为是异步安全的。这个问题涉及在sigprocmask具有异步信号和信号处理程序的多线程程序中使用使互斥锁安全。

我有一些概念上的代码,如下所示:

struct { int a, b; } gvars;

void sigfoo_handler(int signo, siginfo_t *info, void *context) {
    if(gvars.a == 42 || gvars.b == 13) {
        /* run a chained signal handler */
    }
}

/* called from normal code */
void update_gvars(int a, int b) {
    gvars.a = a;
    gvars.b = b;
}

gvars是一个全局变量,它太大而无法放入单个sig_atomic_t. 它由普通代码更新并从信号处理程序中读取。受控代码是一个链接的信号处理程序,因此它必须在信号处理程序上下文中运行(它可以使用infoor context)。gvars因此,必须通过某种同步机制来控制所有访问。更复杂的是,该程序是多线程的,任何线程都可能收到一个SIGFOO.

问题:通过组合sigprocmask(或pthread_sigmask) 和pthread_mutex_t,是否可以使用如下代码保证同步?

struct { int a, b; } gvars;
pthread_mutex_t gvars_mutex;

void sigfoo_handler(int signo, siginfo_t *info, void *context) {
    /* Assume SIGFOO's handler does not have NODEFER set, i.e. it is automatically blocked upon entry */
    pthread_mutex_lock(&gvars_mutex);
    int cond = gvars.a == 42 || gvars.b == 13;
    pthread_mutex_unlock(&gvars_mutex);

    if(cond) {
        /* run a chained signal handler */
    }
}

/* called from normal code */
void update_gvars(int a, int b) {
    sigset_t set, oset;
    sigemptyset(&set);
    sigaddset(&set, SIGFOO);
    pthread_sigmask(SIG_BLOCK, &set, &oset);
    pthread_mutex_lock(&gvars_mutex);
    gvars.a = a;
    gvars.b = b;
    pthread_mutex_unlock(&gvars_mutex);
    pthread_sigmask(SIG_SETMASK, &oset, NULL);
}

逻辑如下: inside被阻塞sigfoo_handlerSIGFOO因此它不能中断pthread_mutex_lock。在-protected 关键区域期间update_gvarsSIGFOO不能在当前线程中引发within ,pthread_sigmask因此它也不能中断pthread_mutex_lock。假设没有其他信号(并且我们总是可以阻止任何其他可能有问题的信号),锁定/解锁应该始终在当前线程上以正常、不间断的方式进行,并且使用锁定/解锁应该确保其他线程不会不要干涉。我是对的,还是应该避免这种方法?

4

2 回答 2

2

我发现这篇论文https://www.cs.purdue.edu/homes/rego/cs543/threads/signals.pdf讨论了在 sig 处理程序中安全地运行 AS-unsafe 代码

  1. 屏蔽正常上下文代码的 AS 不安全块中的信号(被探索为效率较低)或
  2. 使用全局 sig_atomic volatile 标志保护正常上下文代码的 AS 不安全块,该标志可防止在处理程序中输入 AS 不安全代码(如果设置为有效)

这种方法满足 POSIX 标准的一部分,即在 sig-handlers 中调用 AS-unsafe 函数仅在 sighandler 中断 AS-unsafe 函数时才被认为是不安全的 ( http://pubs.opengroup.org/onlinepubs/9699919799/functions /V2_chap02.html#tag_15_04_03_03:向下滚动到函数列表后的第 1 段)

我认为你在这里玩弄的本质上是这个想法的一个更细粒度的版本,因为你不是试图阻止

pthread_mutex_lock(&gvars_mutex);
int cond = gvars.a == 42 || gvars.b == 13;
pthread_mutex_unlock(&gvars_mutex);

从 sig-handler 运行,以免与任何AS 不安全代码发生冲突,而只是与处理此互斥锁和这些变量的相同/相似的 AS 不安全代码冲突。

不幸的是,POSIX 似乎只有一个纯代码的信号安全概念:一个函数要么是安全的,要么是不安全的,不管它的参数是什么。

但是,IMO,信号量/互斥量没有充分的理由对任何数据或操作系统句柄进行操作,除了它们传递的互斥量/信号量中包含的那些,所以我认为从信号处理程序调用sem_wait(&sem)/pthread_mutex_lock(&mx);应该是安全的,如果它得到保证永远不要与同一个互斥体的sem_wait/发生冲突pthread_mutex_lock,即使 POSIX 标准在技术上说它不应该是安全的(反论点比受欢迎的多)。

于 2017-11-15T10:04:20.580 回答
1

您显然知道您在提到 sig_atomic_t 时进入了未定义的行为领域。话虽如此,我能看到这个确切的例子在现代类 Unix 系统上不起作用的唯一方法是,如果信号是用 SA_NODEFER 设置的。

互斥锁足以确保不同线程之间的正确同步(包括在另一个线程中运行的信号处理程序),并且 sigmask 将阻止该线程中的信号处理程序递归互斥锁。

话虽这么说,你在信号处理程序内部有锁的深水。一个信号处理程序可能足够安全,但如果您有两个信号处理程序使用不同的锁执行相同的技巧,您最终会出现锁排序死锁。这可以通过应用进程 sigmasks 而不是线程 sigmasks 在一定程度上得到缓解。例如,信号处理程序中的简单调试 fprintf 肯定会违反锁定顺序。

我会后退并重新设计我的应用程序,因为信号处理程序中的此类内容表明它变得太复杂且太容易破坏。触及一个 sig_atomic_t 的信号处理程序是 C 标准中唯一定义的东西,因为正确处理其他任何事情的复杂性都在爆炸式增长。

于 2013-01-25T12:44:14.463 回答