learn from process control code

fork

1
2
3
4
if ((pid = fork()) < 0){
fprintf(stderr,"fork error,%s\n".stderr(errno));
exit(0);
}

get pid and get parent pid

1
2
pid_t getpid(void);
pid_t getppid(void);

Three states:

  • running
  • stopped: suspended and will not be scheduled. Until it received SigCont
  • terminated: signal, call exit, return from main routine
1
2
void exit(int status);
// does not return, exit status, called by EXITSTATUS
1
2
pid_t fork(void);
//child -> 0; parent -> the pid of the child; -1 error

each process of the parent and the child share the same stack, code, variables

Reaping child processes💀

1
2
3
4
pid_t waitpid(pid_t pid, int *statusp,int option);
//reaped -> pid of child;
WNOHANG -> 0
error -> -1

wait set: determined by pid. -1 for all children, or a child pid
options:

  • default option: 0 -> if child in wait set already terminated, return immediately; else wait for a child terminated
  • WNOHANG: return immediately if none child has terminated
  • WUNTRACED: return when child is terminated or stopped
  • WCONTINUED: until terminated or child received a SIGCONT
    status of a reaped child: statusp
  • WIFEXITED(status)
  • WEXITSTATUS(status)
  • …

error condition:
calling process has no child -> return -1 and set errno to ECHILD
waitpid is interrupted -> return -1 and set errno to EINTR

1
pid_t wait(int *statusp); == waitpid(-1,&status, 0);

a typical example of using waitpid

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int main(){
int status,i;
pid_t pid;
int N = 2;
for (int i = 0;i < N;i++){
if((pid == Fork()) == 0)
exit(100+i);
}
while( (pid = waitpid(-1, &status, 0)) > 0){
if(WIFEXITED(status)){ //if is terminated by exit()
print("terminated normally);
//use WEXITSTATUS(status) to check the exit status
}
else {
//pass
}
}
if (errno != ECHILD) //if all finish -> set errno to ECHILD
unix_error("waitpid error")

}

more utils

1
2
3
unsigned int sleep(unsigned int secs);
// return 0 when sleep is over
// return the remaining secs when interrupted by signal
1
2
int pause(void)
//return -1 , sleep until signal received
1
2
3
int execve(const char* file, const char *argv[],
const char *envp[]);
//succeed --> never return, fail-> return -1

Signal 😰😰😰

Overview

send by kernel

  • illegal instruction => SIGILL
  • illegal memory reference => SIGSEGV
  • Ctrl + C => SIGINT to each process in the foregound process group
  • Ctrl + Z => SIGTSTP to each process in the foregound
  • kill -9 pid => SIGKILL
  • a child process terminates or stops => SIGCHILD

Sending Signal

Kernel detected some system event
OR a process invoke a kill function

Process group

1
2
3
4
5
6
7
8
9
10
11
pid_t getpgrp(void);
//return the process group ID

int setpgid(pid_t pid, pid_t pgid);
//success -> 0 , error -> -1
//if pid == 0 -> pid is the current pid
//if pgid == 0 -> use the pid

example: in process 15213
setpgid(0,0) => create new group id = 15213 & add process 15213 to this group

kill

1
2
3
4
5
6
/bin/kill -9 15213 //sending SIGKILL to process 15213
/bin/kill -9 -15213 //sending SIGKILL to process group 15213

int kill (pid_t pid, int sig);
// return 0 if OK else 1
// if pid < 0, means a pgid

Receiving Signal

Sighandler
Received : being handled now
Pending : waiting to be handled. Only ONE per signal. a bit for each signal
Blocked : a mask to block certain signal
check (pending & ~blocked) bit vector

Synchronous Signals : generated by process itself. divide by ZERO. alarm
Asynchronous Signals : be sent from other processes or OS. Ctrl+C

1
2
3
typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighander_t handler) //link a signal to handler

blocking signal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
//change the mask by set, and save the previous set
//how:
//SIG_BLOCK: add set to blocked
//SIG_UNBLOCK: delete set from blocked
//SIG_SETMASK: let block = set

//functions to manipulate set
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set); //all signals
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum); //return 1 if is member


a typical structure to block

1
2
3
4
5
6
7
8
9
10
11
12
sigset_t mask, prev_mask;

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

/*block here*/
sigprocmask(SIG_MASK, &mask, &prev_mask);
.
.
.
sigprocmask(SIG_SETMASK, &prev_mask, NULL);
/*unblock here*/

How to write a safe signal handler

  1. simple
  2. async-siganl-safe function: no printf, sprintf, malloc, exit
  3. save and restore errno
  4. when access to a global data structure, should block all signals
  5. declare global variables with volatile, make the compiler not to optimize
  6. declare global flag with volatile sig_atomaic_t flag, atomic for read OR write

signals cannot be used to count the occurrence of events in other processes

right version of signal handler

1
2
3
4
5
6
7
8
9
10
11
12
13
void handler2(int sig){
int olderrno = errno;

while (waitpid(-1, NULL, 0) > 0){
Sio_puts("Handler reaped child");
}

if(errno != ECHILD){
Sio_error("waitpid error")
}
Sleep(1);
errno = olderrno;
}

Tricky example about racing
Wrong example: deletejob before addjob

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void handler(int sig){
int olderrno = errno;
sigset_t mask_all, prev_all;
pid_t pid;

sigfillset(&maskall);
while((pid = waitpid(-1, NULL, 0)) > 0){
//when access to global variables, mask all signals
sigprocmask(SIG_BLOCK, &maskall, &prev_all);
deletejob(pid);
sigprocmask(SIG_SETMASK, &prev_mask, NULL);
}
if(errno != ECHILD)
sio_error("waitpid error");
errno = olderrno;
}

int main(){
.
.
.
while(1){
Sigprocmask(SIG_BLOCK, &mask_one, &prev_one);
if((pid = Fork()) == 0){
sigprocmask(SIG_SETMASK, &prev_one, NULL);
exexcve("/bin/date",argv,NULL);

}
sigprocmask(SIG_BLOCK,&mask_all,NULL);
addjob(pid);
sigprocmask(SIG_SETMASK,&prev_one,NULL);

}


}

![[Pasted image 20241207111723.png]]