Files
2025-02-SystemProgramming/notes/10.md
2025-11-30 21:19:04 +09:00

6.3 KiB

Signals and Nonlocal Jumps

Shell

Linux Process Hierachy: all is rooted from init or systemd (pid = 1).

A shell is an application that runs programs on behalf of the user.

  • sh Original Unix shell
  • csh BSD Unix C Shell
  • bash GNU Bourne-Again Shell (default Linux shell)

Signals

All signal is a small message that notifies a process that an event has occured in the system.

Signal is sent by kernel.

Signal is identified by small integer ID (1-30).

Only information in a signal is its ID and the fact that it arrived.

ID name Default Action Corresponding Event
2 SIGINT terminate user types Ctrl+C
9 SIGKILL terminate kill program
11 SIGSEGV terminate segmentation violation
14 SIGALRM terminate timer expired
17 SIGCHLD ignore child process stopped or terminated

Sending a signal is essentially updating some state in the context of the destination process. Kernel sends a signal for one of the following reasons:

A destination process receives a signal when it is forced by the kernel and reacts some way to the signal.

  • Ignore
  • Terminate (with optional core-dump)
  • Catch
    • executing a user-level handler function called signal handler.

Pending & Blocking

A signal is pending if sent but not yet received. There can be at most one pending signal of any particular type because signals are not queued. Signal is managed by bitmask. So subsequent signals of same type is discarded.

  • sets k bit when delivered
  • clears k bit when received

Blocking signals: a process can block the receipt of certain signals. It is not received until unblocked. It is also managed by bitmask.

  • can be set and cleared by using sigprocmask

Receiving

Suppose kernel is returning from an exception handler and is ready to pass control to process p Kernel computes pnb = pending(p) & ~blocked(p).

  • if pnb == 0, no unblocked pending signals for p, so just return to p normally.
  • otherwise, choose least nonzero bit k in pnb and force process p to receive signal k
    • The receipt of the signal triggers action by p
    • Repeat for all nonzero k

Default Action & Signal Handler

Each signal type has a predefined default action:

  • terminates
  • stops until restarted by a SIGCONT signal
  • ignores
handler_t * signal(int signum, handler_t *handler);
  • SIG_IGN ignore
  • SIG_DFL revert to default action
  • handler function pointer

Blocking and Unblocking

  • Implicit blocking Kernel blocks any pending signals of type currently being handled.
  • Explicit blocking sigprocmask()

Supporting functions:

  • sigemptyset() create empty set
  • sigfillset() add every signal number to set
  • sigaddset() add signal number to set
  • sigdelset() delete signal number from set
sigset_t mask, prev_mask;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);

sigprocmaksk(SIG_BLOCK, &mask, &prev_mask);
// critical section
sigprocmask(SIG_SETMASK, &prev_mask, NULL);

Safe Signal Handling

Handlers are concurrent with main program and share global data structures. This can lead to troubles.

Some guidlines help to avoid troubles:

  1. Keep you handlers as simple as possible
  2. Call only async-signal-safe functions in your handlers
  3. Save and restore errno on entry and exit
  4. Protect accesses to shared data structure by temporarily blocking all signals
  5. Declare global variables as volatile
  6. Declare global flags as volatile sig_atomic_t

Async-Signal-Safety

A function satisfying either two conditions(below) is Async-Signal-Safety function:

  • reentrant: not modifying global data (or static data), only use local data(stack frame).
  • non-interruptible: blocking singnals during modifying global data.

POSIX guarantees that the 117 functions to be async-signal-safe, including:

  • _exit, write, wait, waitpid, sleep, kill

IMPORTANT: printf, malloc, sprintf, eixt are NOT async-signal-safe.

Correct Signal Handling Example for reaping multiple childs

void child_handler(int sig) {
  int olderrno = errno; pid_t pid;
  while ((pid = wait(NULL)) > 0) {
    ccount --;
    sio_puts("Handler reaped child ");
    sio_putl((long) pid);
    sio_puts(" \n");
  }
  if (errno != ECHILD) {
    sio_error("wait error");
  }
  errno = olderrno;
}

Portable Signal Handling

Different UNIX systems have different signal handling sematnics.

So sigaction is introduced to provide a portable interface for signal handling.

handler_t * Signal(int signum, handler_t *handler) {
  struct sigaction action, old_action;
  action.sa_handler = handler;
  sigemptyset(&action.sa_mask); // block sigs of type being handled
  action.sa_flags = SA_RESTART; // restart syscalls if possible
  if (sigaction(signum, &action, &old_action) < 0)
    unix_error("Signal error");
  return (old_action.sa_handler);
}

Concurrent Flows to lead Races

void handler(int sig) {
  int olderrno = errno;
  pid_t pid;
  while ((pid = waitpid(-1, NULL, 0)) > 0) {
    deletejob(pid);
  }
  if (errno != ECHILD) sio_error("waitpid error");
  errno = olderrno;
}

int main() {
  while (1) {
    if ((pid = Fork()) == 0) {
      execve("/bin/date", argv, NULL);
    }
  }
  addjob(pid);
}

In above example, the ecf flow can be executed before the deletejob; thus it cannot be deleted. Therefore, we need to synchronize the two concurrent flows.

void handler(int sig) {
  int olderrno = errno;

  sigset_t mask_all, prev_all;
  pid_t pid;

  sigfillset(&mask_all);
  while ((pid = waitpid(-1, NULL, 0)) > 0) {
    sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
    deletejob(pid);
    sigprocmask(SIG_SETMASK, &prev_all, NULL);
  }
  if (errno != ECHILD) sio_error("waitpid error");
  errno = olderrno;
}

int main() {
  pid_t pid;
  sigset_t mask_all, prev_one;

  sigfillset(&mask_all);
  signal(SIGCHLD, handler);

  while (1) {
    sigprocmask(SIG_BLOCK, &mask_all, &prev_one); // Block SIGCHLD
    if ((pid = Fork()) == 0) {
      sigprocmask(SIG_SETMASK, &prev_one, NULL); // Unblock SIGCHLD
      execve("/bin/date", argv, NULL);
    }
    sigprocmask(SIG_BLOCK, &mask_one, NULL);
    addjob(pid);
    sigprocmask(SIG_SETMASK, &prev_one, NULL);
  }
}