使用SIGALRM中断open()

时间:2015-08-03 10:17:47

标签: linux signals posix

我们有一个传统的嵌入式系统,它使用SDL从NFS共享中读取图像和字体。

如果出现网络问题,TTF_OpenFont()和IMG_Load()基本上会永久挂起。测试应用程序显示open()的行为方式相同。

我们想到,快速解决方法是在打开NFS共享上的文件的调用之前调用alarm()。由于SIGALRM中断,open()是否会因EINTR而失败,所以手册页并不完全清楚,因此我们整理了一个测试应用来验证这种方法。我们设置了一个信号处理程序,sigaction :: sa_flags设置为零,以确保没有设置SA_RESTART。

调用了信号处理程序,但open()没有被中断。 (我们观察到与SIGINT和SIGTERM相同的行为。)

我认为系统将open()视为" fast"操作即使在"慢" NFS等基础设施。

有没有办法改变这种行为并允许open()被信号中断?

1 个答案:

答案 0 :(得分:1)

  

手册页并不完全清楚open()是否会失败   当SIGALRM中断EINTR时,我们将测试应用程序放在一起   验证这种方法。

open(2)是一个缓慢的系统调用(慢速系统调用是那些可以永远睡眠的系统调用,并且只有在某些文件类型时才可以唤醒,并且如果,同时捕​​获信号)。通常,阻止调用者直到某些条件发生的打开通常是可中断的。已知的例子包括打开一个FIFO(命名管道),或者(在过去的时候)打开一个物理终端设备(它一直睡到调制解调器被拨打)。

NFS挂载的文件系统可能不会导致open(2)以可中断状态进入睡眠状态。毕竟,您最有可能打开常规文件,在这种情况下open(2)将无法中断。

  

有没有办法改变这种行为并允许open()   被信号打断了?

我不这么认为,不是没有对内核做一些(非平凡的)更改。

我会探讨使用setjmp(3) / longjmp(3)的可能性(如果您不熟悉,请参阅联系手册;它基本上是非本地的)。您可以在调用open(2)之前初始化环境缓冲区,并在信号处理程序中发出longjmp(3)。这是一个例子:

#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
#include <unistd.h>
#include <signal.h>

static jmp_buf jmp_env;

void sighandler(int signo) {
    longjmp(jmp_env, 1);
}

int main(void) {
    struct sigaction sigact;
    sigact.sa_handler = sighandler;
    sigact.sa_flags = 0;
    sigemptyset(&sigact.sa_mask);

    if (sigaction(SIGALRM, &sigact, NULL) < 0) {
        perror("sigaction(2) error");
        exit(EXIT_FAILURE);
    }

    if (setjmp(jmp_env) == 0) {
        /* First time through
         * This is where we would open the file
         */

        alarm(5);

        /* Simulate a blocked open() */
        while (1)
            ; /* Intentionally left blank */

        /* If open(2) is successful here, don't forget to unset
         * the alarm
         */

        alarm(0);
    } else {
        /* SIGALRM caught, open(2) canceled */
        printf("open(2) timed out\n");
    }
    return 0;
}

它的工作原理是在调用setjmp(3)之前借助open(2)保存上下文环境。 setjmp(3)第一次返回0,并返回传递给longjmp(3)的任何值。

请注意,此解决方案并不完美。以下是要记住的一些要点:

  • alarm(2)的通话和open(2)的通话(此处使用while (1) { ... }模拟)之间有一段时间窗口,此处可能会被抢占很长时间,所以在我们实际尝试打开文件之前警报到期的可能性。当然,如果有2或3秒的大超时,这很可能不会发生,但它仍然是一种竞争条件。
  • 同样,在成功打开文件和取消警报之间有一段时间窗口,在此过程中,该过程可能会被抢占很长时间,并且警报可能会在我们有机会取消之前到期。这稍微差一点,因为我们已经打开了文件,因此我们将“泄漏”文件描述符。同样,在实践中,这可能永远不会发生,但这仍然是一种竞争条件。
  • 如果代码捕获了其他信号,则在捕获SIGALRM时可能会在执行过程中有另一个信号处理程序。在信号处理程序中使用longjmp(3)将破坏这些其他信号处理程序的执行上下文,并且根据它们正在做什么,可能会发生非常讨厌的事情(如果信号处理程序正在操作程序中的其他数据结构,则会出现不一致状态,等等。)。就好像它开始执行一样,突然在中间某处坠毁。您可以通过以下方式修复它:a)仔细设置所有信号处理程序,以便SIGALRM在调用之前被阻塞(这可确保SIGALRM处理程序在其他处理程序完成之前不会开始执行)和b )在捕获SIGALRM之前阻止这些其他信号。这两个操作都可以通过使用必要的掩码设置sa_mask struct sigaction字段来完成(操作系统在开始执行处理程序之前将进程的信号掩码原子设置为该值,并在从返回之前取消设置它处理程序)。 OTOH,如果其余的代码没有捕获信号,那么这不是问题。
  • sleep(3)可以与alarm(2)一起实施,alarm(2)setitimer(2)共享相同的计时器;如果代码中的其他部分使用了这些函数中的任何一个,它们就会干扰,结果将是一个巨大的混乱。

在盲目使用这种方法之前,请确保权衡这些缺点。通常不鼓励使用setjmp(3) / longjmp(3),这使得程序更难以阅读,理解和维护。它并不优雅,但是现在我认为你没有选择,除非你愿意在项目中做一些核心重构。

如果你最终使用setjmp(3),那么至少要记录这些限制。