xxx7xxxx
xxx7xxxx

Reputation: 824

Why it will terminate even if I used signl(SIGINT, sig_int)?

As you see, This is a sample in APUE.

#include "apue.h"

static void sig_int(int sig);

int main(int argc, char **argv)
{
    char buf[MAXLINE];
    pid_t pid;
    int status;

    if (signal(SIGINT, sig_int) == SIG_ERR) //sig_int is a simple handler function
        err_sys("signal error");

    printf("%% ");
    while (fgets(buf, MAXLINE, stdin) != NULL) {
    //This is a loop to implement a simple shell
    }
    return 0;
}

This is the signal handler

void sig_int(int sig)   
/*When I enter Ctrl+C, It'll say a got SIGSTOP, but it would terminate.*/
{
    if (sig == SIGINT)
        printf("got SIGSTOP\n");
}

When I enter Ctrl+C, It'll say got SIGSTOP, but it terminates right now.

Upvotes: 1

Views: 1563

Answers (1)

nos
nos

Reputation: 229058

The short version is that the signal interrupts the current system call. You're doing fgets(), which likely now blocks in a read() system-call. The read() call is interrupted, it returns -1 and sets errno to EINTR.

This causes fgets to return NULL, your loop ends, and the program is finished.

Some background

glibc on linux implements two different concepts for signal(). One where system calls are automatically restarted across signals, and one where they are not.

When a signal occurs and the process is blocked in a system call, the system call is interrupted("cancelled"). Execution resumes in the user space application, and the signal handler occurs. The interrupted system call will return an error, and set errno to EINTR.

What happens next depends on whether system calls are restarted or not across signals.

If system calls are restartable, the runtime (glibc) simply retries the system call. For the read() system call, this would be similar to read() being implemented as:

ssize_t read(int fd, void *buf, size_t len)
{
  ssize_t sz;
  while ((sz = syscall_read(fd, buf, len)) == -1 
          && errno == EINTR);
  return sz;

}

If system calls are not automatically restarted, read() would behave similar to:

ssize_t read(int fd, void *buf, size_t len)
{
  ssize_t sz;
  sz = syscall_read(fd, buf, len));
  return sz;
}

In the latter case it would be up to your application to check whether read() failed because it was interrupted by a signal. And it is up you, to determine if read() just failed temporarily due to a signal getting handled, and it's up you you to re-try the read() call

signal vs sigaction

By using sigaction() instead of signal(), you get control over whether system calls are restared or not. The relevant flag you specify with sigaction() is

SA_RESTART Provide behavior compatible with BSD signal semantics by making certain system calls restartable across signals. This flag is meaningful only when establishing a sig‐ nal handler. See signal(7) for a discussion of system call restarting.

BSD vs SVR4 semantics

If you use signal(), it depends on what semantics you want. As seen in the description of SA_RESTART, if it is BSD signal semantics, system calls are restarted. This is the default behavior in glibc.

Another difference is that BSD semantics leave the signal handler installed by signal() installed after a signal is handled. SVR4 semantics uninstalls the signal handler, and your signal handler will have to re-install the handler if you want to catch more signals.

apue.h

The "apue.h" however, defines the macro _XOPEN_SOURCE 600 before including <signal.h>. This will cause signal() to have SVR4 semantics, where system calls are not restarted. Which will cause your fgets() call to "fail".

Don't use signal(), use sigaction()

Due to all these differences in behavior, use sigaction() instead of signal. sigaction() lets you control what happens instead of having the semantics change based on a (possibly) hidden #define as is the case with signal()

Upvotes: 6

Related Questions