如何使信号中断 sem_wait() 但不终止进程?

How to make a signal interrupt sem_wait() but not terminate the process?

我正在使用 sem_wait() 作为 IPC 应用程序的一部分,但我想要 等待被 ctrl-C 中断,并在以下情况下继续程序 有时候是这样的。然而,目前,任何信号都会终止我的程序。

sem_wait 手册页说“sem_wait() 函数是可中断的 通过传递信号。”所以我希望到达标有 ###result == EINTR 当我按 ctrl-C 或以其他方式发送时 处理信号。但就目前而言,我没有:该程序只是 以通常的符号相关消息终止。

我大概必须以某种方式更改信号处理程序,但是如何更改?我尝试定义 void handler(int){} 并用 signal(SIGINT, handler) 注册它。这当然可以防止终止,但它不会神奇地使 sem_wait() 可中断——大概我应该在注册期间或在处理程序本身中做一些不同的事情。

我正在使用 Ubuntu 20.04 LTS 和 macOS 10.13.6 如果这有什么不同的话。

/* Welcome to `semtest.c`. Compile with::
 * 
 *     gcc semtest.c -o semtest           # Darwin
 *     gcc semtest.c -o semtest -pthread  # Linux
 * 
 * Run with::
 * 
 *     ./semtest wait
 * 
 * Terminate by either (a) sending a signal (ctrl-c or `kill`)
 * or (b) running `./semtest post`
 */

#include <string.h>
#include <stdio.h>
#include <semaphore.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>     /* For O_* constants */
#include <sys/stat.h>  /* For S_* mode constants */
#include <unistd.h>    /* For getpid() */

int main(int argc, const char * argv[])
{
    sem_t * semaphore = NULL;
    char cmd;
    int result;
    
    if(argc == 2 && !strcmp(argv[1], "wait")) cmd = argv[1][0];
    else if(argc == 2 && !strcmp(argv[1], "post")) cmd = argv[1][0];
    else return fprintf(stderr, "usage:    %s wait\n   or:    %s post", argv[0], argv[0]);
    
#   define SEMAPHORE_NAME "/test"
    
    fprintf(stderr, "%s is running with PID %d\n", argv[0], getpid());
    if(cmd == 'w')
    {
        sem_unlink(SEMAPHORE_NAME);
        semaphore = sem_open(SEMAPHORE_NAME, O_CREAT, (S_IRUSR | S_IWUSR), 0);
        if(semaphore == SEM_FAILED)
            return fprintf(stderr, "failed to create/open semaphore \"%s\" - sem_open() failed with error %d\n", SEMAPHORE_NAME, errno);
        fprintf(stderr, "waiting for semaphore \"%s\"...\n", SEMAPHORE_NAME);
        errno = 0;
        
        /* signal(SIGINT, something...? );  */
        
        result = sem_wait(semaphore);
        
        /* ### Want to reach this line with result==EINTR when signal arrives */
            
        fprintf(stderr, "sem_wait() returned %d (errno is %d)\n", result, errno);
        sem_close(semaphore);
    }
    if(cmd == 'p')
    {
        semaphore = sem_open(SEMAPHORE_NAME, O_CREAT, (S_IRUSR | S_IWUSR), 0);
        if(semaphore == SEM_FAILED)
            return fprintf(stderr,"failed to create/open semaphore \"%s\" - sem_open() failed with error %d\n", SEMAPHORE_NAME, errno);
        fprintf(stderr, "posting semaphore \"%s\"\n", SEMAPHORE_NAME);
        errno = 0;
        result = sem_post(semaphore);
        if(result) fprintf(stderr, "sem_post() failed with return code %d (errno is %d)\n", result, errno);
        sem_close(semaphore);
    }
    return 0;
}

仅调用 return EINTR 如果您已经为相关信号注册了信号处理程序。

演示:

$ perl -M5.010 -e'
   $SIG{INT} = sub { } if $ARGV[0];
   sysread(STDIN, $buf, 1) or warn $!;
   say "ok";
' 0
^C

$ echo $?
130             # Killed by SIGINT

$ perl -M5.010 -e'
   $SIG{INT} = sub { } if $ARGV[0];
   sysread(STDIN, $buf, 1) or warn $!;
   say "ok";
' 1
^CInterrupted system call at -e line 3.
ok

是的,这是 Perl,但这不是特定于语言的问题。你会在 C 中得到相同的结果。

是的,这是 read,但这是一个特定于调用的问题。 sem_wait.

的结果相同

写起来更容易。

($SIG{INT} = ... 导致调用 sigactionsysreadread 的薄包装。请注意 $ARGV[0] 类似于 argv[1].)

使用signal()函数是处理您想要的信号的最简单方法。

sighandler_t signal(int signum, sighandler_t handler);

在您的例子中,signum 参数是 SIGINT (^C),handle 参数是您需要根据以下语法定义的函数。

/* Type of a signal handler.  */
typedef void (*__sighandler_t) (int);

将您要在处理函数中访问的所有变量声明为全局变量。

#include <signal.h>

sem_t * semaphore = NULL;

void sigint_handler( int signal );

int main(int argc, const char * argv[]) {
    signal(SIGINT, sigint_handler);
    // (...)
    return 0;
}

void sigint_handler( int signal ) {
    // (...)
}

虽然signal()功能可能有效,但出于兼容性考虑,建议使用sigaction()功能。

The behavior of signal() varies across UNIX versions, and has also varied historically across different versions of Linux. Avoid its use: use sigaction(2) instead.

int sigaction(int signum, const struct sigaction *act,
                     struct sigaction *oldact);

在您的程序上下文中使用 sigaction() 应该是这样的。

#include <signal.h>

sem_t * semaphore = NULL;
    
void sigint_handler( int signal );
    
int main(int argc, const char * argv[]) {
    struct sigaction act = { 0 };
    act.sa_handler = sigint_handler;
    sigaction(SIGINT, &act, NULL);
    // (...)
    return 0;
}
    
void sigint_handler( int signal ) {
    // (...)
}