3

The codes is as below, and is the same as the one in book apue3e:

#include "apue.h"
#include "sys/wait.h"

static void sig_int(int);

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

  if (signal(SIGINT, sig_int) == SIG_ERR) {
    err_sys("signal error");
  }

  printf("%% ");
  while (fgets(buf, MAXLINE, stdin) != NULL) {
    if (buf[strlen(buf)-1] == '\n') {
      buf[strlen(buf)-1] = '\0';
    }

    if ((pid = fork()) < 0) {
      err_sys("fork error");
    } else if (pid == 0) {
      execlp(buf, buf, (char *)NULL);
      err_ret("couldn't execlvp: %s\n", buf);
      exit(127);
    }

    if ((pid = waitpid(pid, &status, 0)) < 0) {
      err_sys("waitpid_error");
    }
    printf("%% ");
  }
  exit(0);
}

static void
sig_int(int signo/* arguments */) {
  /* code */
  printf("Interrupted\n%%3 ");
}

So, my question is why this signal handler doesn't handle the SIGINT signal and exit immediately after pressing the Ctrl+c which i was testing on archlinux.

handora
  • 559
  • 5
  • 14
  • maybe not related but CTRL+C in MSYS (Windows) just kills the process (no signal is sent). What is your terminal configuration? – Jean-François Fabre Sep 11 '17 at 09:03
  • @Jean-FrançoisFabre definitely not related. – n. m. could be an AI Sep 11 '17 at 10:01
  • Try running under gdb. Give gdb `handle SIGINT nostop print pass` command and answer `y`. Then run the ptogram and press Ctrl-C. – n. m. could be an AI Sep 11 '17 at 10:12
  • It prints the `Interrupted\n %` and `[Inferior 1 (process 22039) exited normally]`, and may be `fgets` returns `NULL` and causes it to exit normally, but why `fgets` returns `NULL` still confuses me. `errno` shows not cleaned `Interrupted system call`, so i think it may not be `EINTR`. – handora Sep 11 '17 at 13:52

1 Answers1

2

[W]hy this signal handler doesn't handle the SIGINT signal and exit immediately after pressing the Ctrl+c which i was testing on archlinux.

Given

static void
sig_int(int signo/* arguments */) {
  /* code */
  printf("Interrupted\n%%3 ");
}

and

signal(SIGINT, sig_int)

Your process doesn't exit when you press CTRL-C for the simple reason your signal handler doesn't cause the process to exit.

You replaced the default SIGINT handler with your own, so the default action of exiting the process no longer happens.

Since you're running on Linux, I'll refer to the GNU glibc documentation on termination signals:

24.2.2 Termination Signals

These signals are all used to tell a process to terminate, in one way or another. They have different names because they’re used for slightly different purposes, and programs might want to handle them differently.

The reason for handling these signals is usually so your program can tidy up as appropriate before actually terminating. For example, you might want to save state information, delete temporary files, or restore the previous terminal modes. Such a handler should end by specifying the default action for the signal that happened and then reraising it; this will cause the program to terminate with that signal, as if it had not had a handler. (See Termination in Handler.)

The (obvious) default action for all of these signals is to cause the process to terminate.

...

Macro: int SIGINT

The SIGINT (“program interrupt”) signal is sent when the user types the INTR character (normally C-c).

The Termination in Handler glibc documentation states:

24.4.2 Handlers That Terminate the Process

Handler functions that terminate the program are typically used to cause orderly cleanup or recovery from program error signals and interactive interrupts.

The cleanest way for a handler to terminate the process is to raise the same signal that ran the handler in the first place. Here is how to do this:

volatile sig_atomic_t fatal_error_in_progress = 0;

void
fatal_error_signal (int sig)
{

  /* Since this handler is established for more than one kind of signal, 
     it might still get invoked recursively by delivery of some other kind
     of signal.  Use a static variable to keep track of that. */
  if (fatal_error_in_progress)
    raise (sig);
  fatal_error_in_progress = 1;


  /* Now do the clean up actions:
     - reset terminal modes
     - kill child processes
     - remove lock files */
  …


  /* Now reraise the signal.  We reactivate the signal’s
     default handling, which is to terminate the process.
     We could just call exit or abort,
     but reraising the signal sets the return status
     from the process correctly. */
  signal (sig, SIG_DFL);
  raise (sig);
}

Also, note that there can be significant differences between signal() and sigaction(). See What is the difference between sigaction and signal?

Finally, calling printf() from with a signal handler is undefined behavior. Only async-signal-safe functions can be safely called from within a signal handler. See POSIX 2.4 Signal Concepts for the gory details.

Andrew Henle
  • 32,625
  • 3
  • 24
  • 56
  • Thanks a lot, but I think my question is not about clean and recover, but just why it exist immediately. In my idea, i think it may be caused by `fgets` returns `NULL` after the signal handler, and the `errno` is not `EINTR`. – handora Sep 11 '17 at 14:03
  • @handora Your program may exot because calling printf from signal handler is forbidden. Though in my experience it never caused a problem under Linux, and your program behaves as expected on my system, it is still not allowed. Your implementation may be different or I just might be too lucky. Remove `printf` from the handler and try again. – n. m. could be an AI Sep 11 '17 at 18:32
  • 1
    @handora It's also possible that you send more than one `SIGINT` to your program - the default action of `signal()` is often to reset handling to the default after the handler is called once. See https://stackoverflow.com/questions/231912/what-is-the-difference-between-sigaction-and-signal for the problems with `signal()`. – Andrew Henle Sep 11 '17 at 20:00
  • I replaced the `signal` with `sigaction` and it turns out to be true, thanks a lot. – handora Sep 12 '17 at 00:41