【问题标题】:How to make a signal interrupt sem_wait() but not terminate the process?如何使信号中断 sem_wait() 但不终止进程?
【发布时间】:2021-03-05 04:22:48
【问题描述】:

我正在使用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;
}

【问题讨论】:

  • 我大概必须以某种方式更改信号处理程序,但是如何?就是这样。那么你真的在问如何为 ctrl-c 编写和设置信号处理程序吗?
  • 继续阅读sigaction()
  • 您忘记阅读 signal(7)signal-safety(7)sigaction(2)。你可以在 Linux 上使用 signalfd(2)poll(2)
  • @kaylum 是也不是。事实上,我已经阅读了 signal(7) 并尝试注册一个空的信号处理程序。它只是阻止了终止,没有中断sem_wait()。我想我对一个什么都不做的处理程序一无所获并不感到惊讶,所以我认为这个尝试不值得一提,并假设我需要在处理程序中一些特别的事情。但我使用signal() 注册它,我想我没有意识到signal()sigaction() 之间的关键区别。

标签: c signals semaphore


【解决方案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() 函数。

signal() 的行为因 UNIX 版本而异,并且 历史上在不同版本的 Linux 中有所不同。避免其 使用:使用 sigaction(2) 代替。

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 ) {
    // (...)
}

【讨论】:

  • 谢谢。事实上,我已经用signal() 尝试过这个并发现它不起作用(在两个系统上它都阻止了终止但没有中断sem_wait())。切换到sigaction() 是让它发挥作用的关键。我认为要明确这个答案,它应该以sigaction 开头,而不是signal
【解决方案2】:

如果您已为相关信号注册了信号处理程序,则调用仅返回 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} = ... 导致对sigaction 的调用,而sysreadread 的薄包装。注意$ARGV[0] 类似于argv[1]。)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-28
    • 1970-01-01
    相关资源
    最近更新 更多