Signals

Prev: Process Control
TOC: Exceptional Control Flow
Next: Nonlocal Jumps

Signal terminology

Sending Signals

Process Groups

#include <unistd.h>

pid_t getpgrp(void);
										Returns process grp ID of calling process
#include <unistd.h>

int setpgid(pid_t pid, pid_t pgid);
											Return 0 on success, -1 on error

Sending Signals with /bin/kill Program

linux> /bin/kill -9 15213
linux> /bin/kill -9 -15213

Sending Signals from Keyboard

Pasted image 20260430134757.png

linux> ls | sort

Sending Signals with kill function

#include <system/types.h>
#include <signal.h>

int kill(pid_t pid, int sig);
												Return 0 if OK, -1 on error

Sending Signals with alarm Function

#include <unistd.h>

unsigned int alarm(unsigned int secs);
		Returns remaining seconds of previous alarm, or 0 if no previous alarm

Receiving Signals

#include <signal.h>
typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighander_t handler);
    Returns pointer to privious handler if OK, SIG_ERR on error (does NOT set errno)

Blocking and Unblocking Signals

sigset_t mask, prev_mask;

sigemptyset(&mask);
sigaddset(&mask, SIGINT);

//Blocking SIGINT
sigprocmask(SIG_BLOCK, &mask, &prev_mask);

//Restoring previous block set
sigprocmask(SIG_SETMASK, &prev_mask, NULL);

Writing Signal Handlers

Safe Signal Handling

#include "csapp.h"

ssize_t sio_putl(long v);
ssize_t sio_puts(char s[]);
Returns: numer of bytes transferred if OK, -1 on error
void sio_error(char s[]);
Returns nothing.
- sio_putl and sio_puts emits long and string respectively to the stdout. Likewise sio_error message prints the error message and terminates. - We have implementation of it too: ![Pasted image 20260430174045.png](/img/user/Attachments/Pasted%20image%2020260430174045.png) - Finally below is the image of all the async signal safe function in linux implementation: ![Pasted image 20260430173023.png](/img/user/Attachments/Pasted%20image%2020260430173023.png) - G2: Save and restore errno: - Many linux based functions set errno, calling such functions inside the signal handler might disrupt with the main's error handling. - So we gotta save the errno to a local variable on entry to the handler and restore it before the handler returns - Example:
void sigint_handler(int sig){
int olderrno = errno;
//do sm stuff
errno = olderrno;
return;
}
```
- G3: Protect accesses to shared global data structure by blocking all signals:
- If some data structure is is shared with main program by the handler or other handlers then we should temporarily block all signals while accessing this global structure(read/ writing).
- G4: Declare shared global variables with volatile:
- Like if some global var g never changes in main but can be changed by the handler then the compiler might try to optimize by making the global a constant and never looking up the var even if it changes.
- By making it volatile, the program will be forced to look it up every time it's referenced.
- G5: Declare flags with sig_atomic_t:
- Common signal handler pattern is to have the handler record the receipt signal by writing into the global flag and the main reading the global.
- Having the flag as sig_atomit_t means read/write to it can't be interrupted. (Also make it volatile following G4).

Correct Signal Handling

void handler2(int sig){
	int olderrno = errno;
	
	while(waitpid(-1, NULL, 0) >0){
		sio_puts("handler reaped child\n");
	}
	if(errno != ECHILD)
		Sio_error("waitpid_error");
	sleep(1);
	errno = olderrno;
}

Portable Signal Handling

#include <signal.h>

int sigaction(int signum, struct sigaction *act, struct sigaction *oldact);
											Returns 0 if OK, -1 on error
handler_t *Signal(int signum, handler_t *handler){
	struct sigaction action, old_action;
	
	action.sa_handler = handler;
	sigemptyset(&action.sa_mask);//only signal currently being handled is blocked
	action.sa_flag = SA_RESTART;//all functions if can be restarted will be after an intrrupt
	
	if(sigaction(signum, &action, &old_action) < 0)
		unix_error("Signal error");
	return (old_action.sa_handler);//returning the previous handler like normal signal function would do
}

Synchronizing Flows to Avoid Nasty Concurrency Bugs

Explicitly Waiting for Signals

volatile sig_atomic_t pid;

void sigchild_handler(int s)
{
	int olderrno = errno;
	pid = waitpid(-1, NULL, 0);
	errno = olderrno;
}

void signint_handler(int s)
{
}

int main(int argc, char **argv)
{
	sigset_t mask, prev;
	
	Signal(SIGCHILD, sigchild_handler);
	Signal(SIGINT, signint_handler);
	Sigemptyset(&mask);
	Sigaddset(&mask, SIGCHILD);
	
	while(1){
		Sigprocmask(SIG_BLOCK, &mask, &prev);
		if(fork() == 0)
			exit(0);
		
		pid = 0;
		Sigprocmask(SIG_SETMASK, &prev, NULL);
		//waiting for pid (which is a global var) to change from 0 to some child pid
		while(!pid)
			;
		//wasteful cuz we just doing infinite loop
		printf(".");
	}
	exit(0);
}
#include <signal.h>

int sigsuspend(const sigset_t *mask);
												Returns: -1
sigprocmask(SIGBLOCK, &mask, &prev);
pause();
sigprocmask(SIG_SETMASK, &prev, NULL);
volatile sig_atomic_t pid;

void sigchild_handler(int s)
{
	int olderrno = errno;
	pid = waitpid(-1, NULL, 0);
	errno = olderrno;
}
void sigint_handler(int s)
{
}

int main(int argc, char **argv){
	sigset_t mask, prev;
	
	Signal(SIGCHILD, sigchild_handler);
	Signal(SIGINT, sigint_handler);
	Sigemptyset(&mask);
	Sigaddset(&mask, SIGCHILD);
	
	while(1){
		Sigprocmask(SIG_BLOCK, &mask, &prev);
		if(fork() == 0)
			exit(0);
		pid = 0;
		while(!pid){
			sigsuspend(&prev); //here basically 
		}
		Sigprocmask(SIG_SETMAKS, &prev, NULL);
		printf(">");
	}
	exit(0);
}

Prev: Process Control
TOC: Exceptional Control Flow
Next: Nonlocal Jumps

Powered by Forestry.md