2013-01-20 26 views
5

首先,我知道互斥锁通常不被认为是同步安全的。这个问题涉及使用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。它由正常代码更新并从信号处理程序读取。受控代码是一个链式信号处理程序,因此它必须在信号处理程序上下文中运行(它可能使用infocontext)。因此,所有对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); 
} 

的逻辑是如下:内sigfoo_handlerSIGFOO被阻塞,因此它不能中断pthread_mutex_lock。在update_gvarsSIGFOO不能在当前线程的pthread_sigmask重保护的关键区域时提出的,因此它不能中断pthread_mutex_lock无论是。假设没有其他信号(并且我们始终可以阻止任何其他可能有问题的信号),锁定/解锁应始终在当前线程上以正常,不可中断的方式进行,并且使用锁定/解锁应确保其他线程不会干扰。我是对的,还是应该避免这种做法?

回答

1

你明明知道你是为未定义行为的领土与sig_atomic_t提及。话虽如此,我可以看到这个精确的例子不适用于现代类Unix系统的唯一方法是如果信号是用SA_NODEFER设置的。

互斥是足以确保不同的线程(包括另一线程运行信号处理程序)和sigmask将防止在这个线程递归互斥信号处理程序之间的正确同步。

这就是说,你在信号处理程序里面有锁的深水里。一个信号处理程序可能足够安全,但如果您有两个信号处理程序使用不同的锁来执行相同的技巧,则最终会发生锁定排序死锁。这可以通过应用进程sigmasks而不是线程sigmasks来缓解。例如,信号处理程序中的一个简单的调试fprintf肯定会违反锁定顺序。

我会退避三舍,并重新设计我的应用程序,因为这样的东西在一个讯号处理器是它变得​​太复杂,太容易突破的迹象。接触一个sig_atomic_t的信号处理程序是C标准中唯一定义的东西,因为其他任何东西都是正确的。

+0

如果信号设置为'SA_NODEFER',那么我应该可以在处理程序本身中以相同的方式使用'sigprocmask',不是吗? – nneonneo

+1

另外,由于在两个线程中同时使用'sigprocmask'肯定是一个禁忌,所以进程的sigmask是不可能的。如果有多个信号处理程序,我怀疑处理它的最好方法是阻塞线程sigmask中的所有信号。 – nneonneo

+0

我会重新设计应用程序,如果可以的话,显然,但有很好的理由为什么这样的事情在我的情况下是必要的。 – nneonneo

1

我发现本文https://www.cs.purdue.edu/homes/rego/cs543/threads/signals.pdf,讨论中的签名的处理程序由

  1. 运行AS-不安全的代码安全地在正常上下文代码AS-不安全块屏蔽掉信号(探索作为效率较低)OR
  2. 保护的正常上下文代码AS-不安全块有防止AS-不安全代码在处理程序从如果设置被输入一个全球sig_atomic挥发性标志(探索作为有效)

这种方法满足P的部分OSIX标准,指出 调用AS-不安全的功能SIG-处理器仅被视为不安全,如果sighandler中断的AS-不安全的功能(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-处理程序运行从任何 AS-不安全的代码,而是发生冲突只需要处理这个互斥体和这些变量的相同/相似的AS不安全的代码。

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

然而,IMO,信号量/互斥体没有充分的理由对任何数据操作或OS处理比那些包含在其他互斥/信号灯他们过去了,所以我觉得从信号处理程序调用sem_wait(&sem)/pthread_mutex_lock(&mx);应该如果保证从不与sem_wait/pthread_mutex_lock冲突到相同的互斥体,即使POSIX标准在技术上说它不应该是安全的(反义参数不止是欢迎词),也是安全的。

+1

那么,我认为你是正确的论点或多或少是“我们在生产中使用它,没有任何损失”:D。但是,当然,任何正确性都可能仅仅取决于运气(例如,内核的行为是正确的,libc的行为是正确的,等等),而不是根据标准严格正确。 – nneonneo