【问题标题】:protect sem_wait() from signals using pthread_sigmask()使用 pthread_sigmask() 保护 sem_wait() 免受信号影响
【发布时间】:2015-03-16 21:51:51
【问题描述】:

我有一个通过第三方库访问硬件资源 (SPI) 的库。我的库以及 SPI 资源被多个进程访问,因此我需要使用信号量锁定资源,锁定功能如下:

static int spi_lock(void)
{
    struct timespec ts;

    if (clock_gettime(CLOCK_REALTIME, &ts) == -1)
    {
        syslog(LOG_ERR,"failed to read clock: %s\n", SPISEM, strerror(errno));
        return 3;
    }
    ts.tv_sec += 5;
    if (sem_timedwait(bcoms->spisem, &ts) == -1)
    {
        syslog(LOG_ERR,"timed out trying to acquire %s: %s\n", SPISEM, strerror(errno));
        return 1;
    }
    return 0;
}

static int spi_unlock(void)
{
    int ret = 1;

    if (sem_post(bcoms->spisem))
    {
        syslog(LOG_ERR,"failed to release %s: %s\n", SPISEM, strerror(errno));
        goto done;
    }
    ret = 0;
done:
    return ret;
}

现在我的问题是该库在守护程序中使用,并且该守护程序通过终止信号停止。有时我在持有信号量锁时收到终止信号,因此服务器无法成功重新启动,因为锁被永久占用。为了解决这个问题,我试图阻止如下所示的信号(我正在等待硬件在 atm 上进行测试):

static int spi_lock(void)
{
    sigset_t nset;
    struct timespec ts;

    sigfillset(&nset);
    sigprocmask(SIG_BLOCK, &nset, NULL);

    if (clock_gettime(CLOCK_REALTIME, &ts) == -1)
    {
        syslog(LOG_ERR,"failed to read clock: %s\n", SPISEM, strerror(errno));
        return 3;
    }
    ts.tv_sec += 5; // 5 seconds to acquire the semaphore is HEAPS, so we better bloody get it !!! 
    if (sem_timedwait(bcoms->spisem, &ts) == -1)
    {
        syslog(LOG_ERR,"timed out trying to acquire %s: %s\n", SPISEM, strerror(errno));
        return 1;
    }
    return 0;
}

static int spi_unlock(void)
{
    sigset_t nset;
    int ret = 1;

    if (sem_post(bcoms->spisem))
    {
        syslog(LOG_ERR,"failed to release %s: %s\n", SPISEM, strerror(errno));
        goto done;
    }

    sigfillset(&nset);
    sigprocmask(SIG_UNBLOCK, &nset, NULL);
    ret = 0;
done:
    return ret;
}

但是在阅读了 sigprocmask() 的手册页后,它说在多线程系统中使用 pthread_sigmask(),而我要保护的服务器之一将是多线程的。我不明白的是,如果我在库中使用 pthread_sigmask(),并且主父线程在我的库中生成一个使用这些锁定函数的 SPI 读取线程,读取线程将受到保护,但主线程不能当我在读取线程上禁用信号的情况下持有互斥锁时,仍然会收到终止信号并关闭守护程序,这让我无处可去?如果是这样,这个锁定问题是否有更好的解决方案?

谢谢。

【问题讨论】:

  • 这是我过去在另一个操作系统上处理此问题的方法。服务器在启动时会清理信号量。这可能意味着 1) 删除信号量,然后创建一个新信号量 2) 在服务器打开信号量时使用“create”标志,或 3) 仅使用控制函数将信号量重置为其初始状态。
  • 您可能希望在“拥有”设备时锁定文件,而不是使用信号量。当拥有它的进程终止时,文件锁将自动解除。

标签: c linux multithreading


【解决方案1】:

确实,您已经正确分析了问题 - 屏蔽信号并不能保护您。屏蔽信号不是防止进程因处于不一致状态的共享数据(如文件或共享信号量)而终止的正确工具。

如果您想在某些信号上正常退出,您可能应该做的是让程序安装信号处理程序以捕获终止请求并将其输入到您的正常程序逻辑中。您可以使用多种方法:

  1. 通过管道向您自己发送终止请求。如果您的程序是围绕一个可以等待管道输入的poll 循环构建的,那么这将非常有效。

  2. 使用sem_post,一个异步信号安全同步函数,将信号报告给程序的其余部分。

  3. 从主线程启动一个专用的信号处理线程,然后阻塞主线程中的所有信号(以及通过继承,所有其他新线程)。这个线程可以做for(;;) pause();,因为pause是异步信号安全的,你可以从信号处理程序中调用任何你想要的函数——包括与其他线程同步所需的pthread同步函数。

请注意,这种方法仍然不是“完美的”,因为您永远无法捕获或阻止SIGKILL。如果用户决定使用 SIGKILL (kill -9) 终止您的进程,则信号量可能会处于不良状态,您无能为力。

【讨论】:

    【解决方案2】:

    我认为你的方法行不通。您不能阻止 SIGKILL 或 SIGSTOP。除非您说守护程序正在收到不同的信号(例如 SIGHUP)。但即便如此,我认为阻止来自库调用的所有信号是不好的做法。这可能会对调用应用程序产生不利影响。例如,应用程序可能依赖于特定的信号,而错过任何此类信号都可能导致其无法正常运行。

    事实证明,使用信号量可能不是一种简单的方法来解决您的问题。因此,另一种方法是改用“flock”之类的东西。这解决了您的问题,因为它基于打开的文件描述符。如果持有flock的进程死亡,则关联的文件描述符将自动关闭,从而释放flock。

    【讨论】:

    • 阻止来自库代码的信号是完全可以接受和惯用的。它不会导致它们丢失。它会在信号可能干扰的关键部分短暂地推迟它们,仅此而已。
    • 你确定吗? sigprocmask 手册页说:“如果在阻塞时生成 SIGBUS、SIGFPE、SIGILL 或 SIGSEGV,则结果未定义,除非信号由 kill(2)、sigqueue(3) 或 raise(3) 生成”
    • 除非调用未定义的行为,否则这些都不会生成,所以没有问题。
    • 这不是真的。在编写库时,您(通常)无法控制应用程序上下文是什么。例如,应用程序可能正在对 SEGV 信号进行一些特定的内存管理。
    • 如果您仅在执行库代码期间阻止信号,则没有理由生成任何这些信号,除非您的库代码中可能存在错误或调用者违反了您的库的合同代码。无论哪种情况,行为都已经未定义。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-19
    • 2012-12-20
    • 1970-01-01
    • 2014-04-01
    • 1970-01-01
    • 2016-09-24
    相关资源
    最近更新 更多