简体   繁体   中英

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

I am using sem_wait() as part of an IPC application, but I want the wait to be interruptible by ctrl-C, and to continue the program if this happens. Currently, however, any signal terminates my program.

The sem_wait man page says "the sem_wait() function is interruptible by the delivery of a signal." So I am hoping to reach the line marked ### with result == EINTR when I press ctrl-C or otherwise send this process a signal. But as it stands, I do not: the program just terminates with the usual signum-dependent message.

I presumably have to change the signal handler somehow, but how? I tried defining void handler(int){} and registering it with signal(SIGINT, handler) . That prevents termination of course but it doesn't magically make sem_wait() interruptible—presumably there's something different I should be doing during registration, or in the handler itself.

I'm on Ubuntu 20.04 LTS and macOS 10.13.6 if this makes any difference.

/* 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;
}

Using the signal() function is the simplest way to handle the signal you want.

sighandler_t signal(int signum, sighandler_t handler);

In your case, the signum parameter is SIGINT ( ^C ) and the handle parameter is a function you need to define according the following syntax.

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

Declare all variables you want to access in the handler function as global.

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

Although the signal() function may work, it is recommended to use the sigaction() function for compatibility purposes.

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);

Using sigaction() in the context of your program should be something like this.

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

Calls only return EINTR if you have registered a signal handler for the signal in question.

Demo:

$ 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

Yeah, this is Perl, but this is not a language-specific matter. You'll get the same results in C.

Yeah, this is read , but this is a call-specific matter. You'll get the same results for sem_wait .

This was just easier to write.

( $SIG{INT} =... results in a call to sigaction , and sysread is a thin wrapper around read . Note that $ARGV[0] is akin to argv[1] .)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM