使用异步信号保证互斥安全

时间:2013-01-20 05:27:07

标签: c synchronization signals posix

首先,我知道互斥锁通常不被视为异步安全。这个问题涉及使用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_handler内,SIGFOO被阻止,因此无法中断pthread_mutex_lock。在update_gvars内,在SIGFOO - 受保护的关键区域内,当前线程中无法引发pthread_sigmask,因此它也无法中断pthread_mutex_lock。假设没有其他信号(并且我们总是可以阻止任何其他可能有问题的信号),锁定/解锁应始终以正常,不间断的方式在当前线程上进行,并使用lock /解锁应确保其他线程不会干扰。我是对的,还是应该避免这种做法?

2 个答案:

答案 0 :(得分:1)

你提到sig_atomic_t时,你显然知道自己处于未定义的行为领域。话虽这么说,我能看到这个完全不符合现代类Unix系统的确切示例的唯一方法是使用SA_NODEFER设置信号。

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

话虽这么说,你在深水中,信号处理人员内有锁。一个信号处理程序可能足够安全,但如果你有两个信号处理程序使用不同的锁执行相同的技巧,最终会导致锁定序列死锁。通过应用进程sigmas而不是线程sigmasks可以稍微减轻这种情况。例如,信号处理程序中的简单调试fprintf肯定会违反锁定顺序。

我会退回并重新设计我的应用程序,因为在信号处理程序中这样的东西是一个迹象,它变得太复杂,太容易破解。触摸一个sig_atomic_t的信号处理程序是C标准中唯一定义的东西,因为获得其他任何东西的复杂性都在增加。

答案 1 :(得分:1)

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

  1. 屏蔽AS-unsafe正常上下文代码块中的信号(探讨效率较低)或
  2. 使用全局sig_atomic易失性标志保护AS不安全的正常上下文代码块,以防止在处理程序中输入AS不安全代码(如果设置有效)
  3. 这种方法满足了POSIX标准的要求 如果sighandler中断了AS不安全的函数(http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_03_03:向下滚动到函数列表后的第1段),则只在sig-handler中调用AS不安全的函数才被认为是不安全的。

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

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

    从一个sig-handler与任何 AS-unsafe代码冲突运行,而只是使用这个相同/类似的AS-unsafe代码来处理这个互斥锁和这些变量。

    不幸的是,POSIX似乎只有信号安全的代码概念:无论其参数如何,函数都是安全的或不安全的。

    然而,IMO,信号量/互斥量没有充分理由对除了它们传递的互斥量/信号量中包含的数据或操作系统句柄之外的任何数据或操作系统句柄进行操作,因此我认为调用sem_wait(&sem) / {{1来自信号处理程序应该是安全的,如果它保证永远不会与pthread_mutex_lock(&mx); / sem_wait冲突到同一个互斥锁,即使POSIX标准在技术上说它不应该是安全的(反...争论不止欢迎)。