Class17 cs230s23
Class17 cs230s23
Topics
Processes
At any given time, system has multiple active processes
Only one can execute at a time on any single core
Each process appears to have total control of
processor + private memory space
CS 230 S ‘23
Review (cont.)
Spawning processes
Call fork
One call, two returns
Process completion
Call exit
One call, no return
CS 230 S ‘23
execve: Loading and Running Programs
int execve(char *filename, char *argv[], char *envp[])
CS 230 S ‘23
ECF Exists at All Levels of a System
Exceptions
Hardware and operating system kernel software
Previous Lecture
Process Context Switch
Hardware timer and kernel software
Signals
This Lecture
Kernel software and application software
Nonlocal jumps
Textbook and
Application code supplemental slides
CS 230 S ‘23
(partial) Taxonomy Handled in kernel
Handled in user process
ECF
Asynchronous
Synchronous
Signals
CS 230 S ‘23
Today
Shells
Signals
Nonlocal jumps
CS 230 S ‘23
Linux Process Hierarchy
[0]
init [1]
Daemon …
e.g. httpd Login shell Login shell
Simple shell
Described in the textbook, starting at p. 753
Implementation of a very elementary shell
Purpose
Understand what happens when you type commands
Understand use and operation of process control operations
CS 230 S ‘23
Simple Shell Example
linux> ./shellex
> /bin/ls -l csapp.c Must give full pathnames for programs
-rw-r--r-- 1 bryant users 23053 Jun 15 2015 csapp.c
> /bin/ps
PID TTY TIME CMD
31542 pts/2 00:00:01 tcsh
32017 pts/2 00:00:00 shellex
32019 pts/2 00:00:00 ps
> /bin/sleep 10 & Run program in background
32031 /bin/sleep 10 &
> /bin/ps
PID TTY TIME CMD
31542 pts/2 00:00:01 tcsh
32024 pts/2 00:00:00 emacs
32030 pts/2 00:00:00 shellex
32031 pts/2 00:00:00 sleep Sleep is running
32033 pts/2 00:00:00 ps in background
> quit
CS 230 S ‘20
Simple Shell Implementation
Basic loop
Read line from command line
Execute the requested operation
Built-in command (only one implemented is quit)
Load and execute program from file
/* evaluate */
eval(cmdline);
}
... shellex.c
CS 230 S ‘23
Simple Shell eval Function
void eval(char *cmdline)
{
char *argv[MAXARGS]; /* Argument list execve() */
char buf[MAXLINE]; /* Holds modified command line */
int bg; /* Should the job run in bg or fg? */
pid_t pid; /* Process id */
strcpy(buf, cmdline);
bg = parseline(buf, argv);
if (argv[0] == NULL)
return; /* Ignore empty lines */
parseline will parse ‘buf’ into ‘argv’ and
if (!builtin_command(argv)) { not input line ended in ‘&’
return whether or
if ((pid = Fork()) == 0) { /* Child runs user job */
if (execve(argv[0], argv, environ) < 0) {
printf("%s: Command not found.\n", argv[0]);
exit(0);
}
}
/* Parent waits for foreground job to terminate */
if (!bg) {
int status;
if (waitpid(pid, &status, 0) < 0)
unix_error("waitfg: waitpid error");
}
else
printf("%d %s", pid, cmdline);
}
return;
}
shellex.c
CS 230 S ‘23
Simple Shell eval Function
void eval(char *cmdline)
{
char *argv[MAXARGS]; /* Argument list execve() */
char buf[MAXLINE]; /* Holds modified command line */
int bg; /* Should the job run in bg or fg? */
pid_t pid; /* Process id */
strcpy(buf, cmdline);
bg = parseline(buf, argv);
if (argv[0] == NULL)
return; /* Ignore empty lines */ Ignore empty lines.
if (!builtin_command(argv)) {
if ((pid = Fork()) == 0) { /* Child runs user job */
if (execve(argv[0], argv, environ) < 0) {
printf("%s: Command not found.\n", argv[0]);
exit(0);
}
}
/* Parent waits for foreground job to terminate */
if (!bg) {
int status;
if (waitpid(pid, &status, 0) < 0)
unix_error("waitfg: waitpid error");
}
else
printf("%d %s", pid, cmdline);
}
return;
}
shellex.c
CS 230 S ‘23
Simple Shell eval Function
void eval(char *cmdline)
{
char *argv[MAXARGS]; /* Argument list execve() */
char buf[MAXLINE]; /* Holds modified command line */
int bg; /* Should the job run in bg or fg? */
pid_t pid; /* Process id */
strcpy(buf, cmdline);
bg = parseline(buf, argv);
if (argv[0] == NULL)
return; /* Ignore empty lines */
if (!builtin_command(argv)) {
if ((pid = Fork()) == 0) { /* Child runs user job */
if (execve(argv[0], argv, environ) < 0) {
printf("%s: Command not found.\n", argv[0]);
exit(0);
}
} If it is a ‘built in’ command, then handle
it
here in this program. Otherwise fork/exec
/* Parent waits for foreground job to terminate */
if (!bg) { the program specified in argv[0]
int status;
if (waitpid(pid, &status, 0) < 0)
unix_error("waitfg: waitpid error");
}
else
printf("%d %s", pid, cmdline);
}
return;
}
shellex.c
CS 230 S ‘23
Simple Shell eval Function
void eval(char *cmdline)
{
char *argv[MAXARGS]; /* Argument list execve() */
char buf[MAXLINE]; /* Holds modified command line */
int bg; /* Should the job run in bg or fg? */
pid_t pid; /* Process id */
strcpy(buf, cmdline);
bg = parseline(buf, argv);
if (argv[0] == NULL)
return; /* Ignore empty lines */
if (!builtin_command(argv)) {
if ((pid = Fork()) == 0) { /* Child runs user job */
if (execve(argv[0], argv, environ) < 0) {
printf("%s: Command not found.\n", argv[0]);
exit(0);
}
} Create child
/* Parent waits for foreground job to terminate */
if (!bg) {
int status;
if (waitpid(pid, &status, 0) < 0)
unix_error("waitfg: waitpid error");
}
else
printf("%d %s", pid, cmdline);
}
return;
}
shellex.c
CS 230 S ‘23
Simple Shell eval Function
void eval(char *cmdline)
{
char *argv[MAXARGS]; /* Argument list execve() */
char buf[MAXLINE]; /* Holds modified command line */
int bg; /* Should the job run in bg or fg? */
pid_t pid; /* Process id */
strcpy(buf, cmdline);
bg = parseline(buf, argv);
if (argv[0] == NULL)
return; /* Ignore empty lines */
if (!builtin_command(argv)) {
if ((pid = Fork()) == 0) { /* Child runs user job */
if (execve(argv[0], argv, environ) < 0) {
printf("%s: Command not found.\n", argv[0]);
exit(0);
}
}
/* Parent waits for foreground job to terminate */
if (!bg) {
int status;
if (waitpid(pid, &status, 0) < 0)
Start argv[0].
unix_error("waitfg: waitpid error");
} Remember execve only returns on error.
else
printf("%d %s", pid, cmdline);
}
return;
}
shellex.c
CS 230 S ‘23
Simple Shell eval Function
void eval(char *cmdline)
{
char *argv[MAXARGS]; /* Argument list execve() */
char buf[MAXLINE]; /* Holds modified command line */
int bg; /* Should the job run in bg or fg? */
pid_t pid; /* Process id */
strcpy(buf, cmdline);
bg = parseline(buf, argv);
if (argv[0] == NULL)
return; /* Ignore empty lines */
if (!builtin_command(argv)) {
if ((pid = Fork()) == 0) { /* Child runs user job */
if (execve(argv[0], argv, environ) < 0) {
printf("%s: Command not found.\n", argv[0]);
exit(0);
}
}
/* Parent waits for foreground job to terminate */
if (!bg) {
int status;
if (waitpid(pid, &status, 0) < 0)
unix_error("waitfg: waitpid error");
}
else If running child in
printf("%d %s", pid, cmdline);
foreground, wait until it is
}
return; done.
}
shellex.c
CS 230 S ‘23
Simple Shell eval Function
void eval(char *cmdline)
{
char *argv[MAXARGS]; /* Argument list execve() */
char buf[MAXLINE]; /* Holds modified command line */
int bg; /* Should the job run in bg or fg? */
pid_t pid; /* Process id */
strcpy(buf, cmdline);
bg = parseline(buf, argv);
if (argv[0] == NULL)
return; /* Ignore empty lines */
if (!builtin_command(argv)) {
if ((pid = Fork()) == 0) { /* Child runs user job */
if (execve(argv[0], argv, environ) < 0) {
printf("%s: Command not found.\n", argv[0]);
exit(0);
}
}
/* Parent waits for foreground job to terminate */
if (!bg) {
int status; If running child in
if (waitpid(pid, &status, 0) < 0)
unix_error("waitfg: waitpid error"); background, print pid and
} continue doing other stuff.
else
printf("%d %s", pid, cmdline);
}
return;
}
shellex.c
CS 230 S ‘23
Simple Shell eval Function
void eval(char *cmdline)
{
char *argv[MAXARGS]; /* Argument list execve() */
char buf[MAXLINE]; /* Holds modified command line */
int bg; /* Should the job run in bg or fg? */
pid_t pid; /* Process id */
strcpy(buf, cmdline);
bg = parseline(buf, argv);
if (argv[0] == NULL)
return; /* Ignore empty lines */
if (!builtin_command(argv)) {
if ((pid = Fork()) == 0) { /* Child runs user job */
if (execve(argv[0], argv, environ) < 0) {
printf("%s: Command not found.\n", argv[0]);
exit(0);
}
}
/* Parent waits for foreground job to terminate */
if (!bg) {
int status;
if (waitpid(pid, &status, 0) < 0)
unix_error("waitfg: waitpid error"); Oops. There is a
}
else
printf("%d %s", pid, cmdline);
problem with
}
return; this code.
}
shellex.c
CS 230 S ‘23
Problem with Simple Shell Example
Shell designed to run indefinitely
Should not accumulate unneeded resources
Memory
Child processes
File descriptors
CS 230 S ‘23
Today
Shells
Signals
Nonlocal jumps
CS 230 S ‘23
Signals
A signal is a small message that notifies a process that
an event of some type has occurred in the system
Akin to exceptions and interrupts
Sent from the kernel (sometimes at the request of another
process) to a process
Signal type is identified by small integer ID’s (1-30)
Only information in a signal is its ID and the fact that it arrived
CS 230 S ‘23
Signal Concepts: Sending a Signal
Kernel sends (delivers) a signal to a destination process
by updating some state in the context of the
destination process
CS 230 S ‘23
Signal Concepts: Sending a Signal
User level
Process B
Process A
Process C
kernel
Process B
Process A
Process C
kernel
Process B
Process A
Process C
kernel
Process B
Process A
Process C
kernel
Process B
Process A
Process C
kernel
CS 230 S ‘23
Signal Concepts: Sending a Signal
User level
Process B
Process A
Process C
kernel
Background Background
process group 32 process group 40
Child Child
CS 230 S ‘23
Sending Signals from the Keyboard
Typing ctrl-c (ctrl-z) causes the kernel to send a SIGINT (SIGTSTP)
to every job in the foreground process group
SIGINT – default action is to terminate each process
SIGTSTP – default action is to stop (suspend) each process
pid=10
pgid=10 Shell
Background Background
process group 32 process group 40
Child Child
pid=21 pid=22
pgid=20 pgid=20
Foreground
process group 20 CS 230 S ‘23
Example of ctrl-c and ctrl-z
bluefish> ./forks 17 STAT (process state) Legend:
Child: pid=28108 pgrp=28107
Parent: pid=28107 pgrp=28107 First letter:
<types ctrl-z> S: sleeping
Suspended T: stopped
bluefish> ps w R: running
PID TTY STAT TIME COMMAND
27699 pts/8 Ss 0:00 -tcsh
28107 pts/8 T 0:01 ./forks 17 Second letter:
28108 pts/8 T 0:01 ./forks 17 s: session leader
28109 pts/8 R+ 0:00 ps w +: foreground proc group
bluefish> fg
./forks 17 See “man ps” for more
<types ctrl-c> details
bluefish> ps w
PID TTY STAT TIME COMMAND
27699 pts/8 Ss 0:00 -tcsh
28110 pts/8 R+ 0:00 ps w
CS 230 S ‘23
Sending Signals with kill Function
void fork12()
{
pid_t pid[N];
int i;
int child_status;
Process q Process p
user code
user code
CS 230 S ‘23
Receiving Signals
Suppose kernel is returning from an exception
handler and is ready to pass control to process p
If (pnb == 0)
Pass control to next instruction in the logical flow for p
Else
Choose least nonzero bit k in pnb and force process p to
receive signal k
The receipt of the signal triggers some action by p
Repeat for all nonzero k in pnb
Pass control to next instruction in logical flow for p
CS 230 S ‘23
Default Actions
Each signal type has a predefined default action, which
is one of:
The process terminates
The process stops until restarted by a SIGCONT signal
The process ignores the signal
CS 230 S ‘23
Installing Signal Handlers
The signal function modifies the default action
associated with the receipt of signal signum:
handler_t *signal(int signum, handler_t *handler)
return 0;
} sigint.c
CS 230 S ‘23
Signals Handlers as Concurrent
Flows
A signal handler is a separate logical flow (not process)
that runs concurrently with the main program
But, this flow exists only until returns to main program
Time
CS 230 S ‘23
Another View of Signal Handlers as
Concurrent Flows
Process A Process B
kernel code
Inext
user code (main)
CS 230 S ‘23
Nested Signal Handlers
Handlers can be interrupted by other handlers
CS 230 S ‘23
Blocking and Unblocking Signals
Implicit blocking mechanism
Kernel blocks any pending signals of type currently being
handled
e.g., a SIGINT handler can’t be interrupted by another SIGINT
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
CS 230 S ‘23
Temporarily Blocking
Signals
sigset_t mask, prev_mask;
Sigemptyset(&mask);
Sigaddset(&mask, SIGINT);
CS 230 S ‘23
Safe Signal Handling
Handlers are tricky because they are concurrent with
main program and share the same global data
structures
Shared data structures can become corrupted.
CS 230 S ‘23
Guidelines for Writing Safe
Handlers
G0: Keep your handlers as simple as possible
e.g., set a global flag and return
G1: Call only async-signal-safe functions in your handlers
printf, sprintf, malloc, and exit are not safe!
G2: Save and restore errno on entry and exit
So that other handlers don’t overwrite your value of errno
G3: Protect accesses to shared data structures by
temporarily blocking all signals
To prevent possible corruption
G4: Declare global variables as volatile
To prevent compiler from storing them in a register
G5: Declare global flags as volatile sig_atomic_t
flag: variable that is only read or written (e.g. flag = 1, not flag++)
Flag declared this way does not need to be protected like other
globals
CS 230 S ‘23
Async-Signal-Safety
Function is async-signal-safe if either reentrant (e.g., all variables
stored on stack frame, CS:APP3e 12.7.2) or non-interruptible by
signals
Posix guarantees 117 functions to be async-signal-safe
Source: “man 7 signal-safety”
Popular functions on the list:
_exit, write, wait, waitpid, sleep, kill
Popular functions that are not on the list:
printf, sprintf, malloc, exit
Unfortunate fact: write is the only async-signal-safe output function
CS 230 S ‘23
Safe Formatted Output: Option #1
Use the reentrant SIO (Safe I/O library) from csapp.c in
your handlers
ssize_t sio_puts(char s[]) /* Put string */
ssize_t sio_putl(long v) /* Put long */
void sio_error(char s[]) /* Put msg & exit */
sigintsafe.c
CS 230 S ‘23
volatile int ccount = 0;
Correct Signal
void child_handler(int sig) {
int olderrno = errno; Handling
pid_t pid; Pending signals are
if ((pid = wait(NULL)) < 0)
Sio_error("wait error"); not queued
ccount--; For each signal type,
Sio_puts("Handler reaped child ");
Sio_putl((long)pid); one bit indicates
Sio_puts(" \n"); whether or not signal
sleep(1); is pending…
errno = olderrno;
} …thus at most one
This code is incorrect! pending signal of any
void fork14() { particular type.
pid_t pid[N];
int i;
ccount = N;
N == 5 You can’t use
Signal(SIGCHLD, child_handler); signals to count
for (i = 0; i < N; i++) { events, such as
if ((pid[i] = Fork()) == 0) { children terminating.
Sleep(1); whaleshark> ./forks 14
exit(0); /* Child exits */
Handler reaped child 23240
}
}
Handler reaped child 23241
while (ccount > 0) /* Parent spins */ . . .(hangs)
;
} forks.c CS 230 S ‘23
E
int setjmp(jmp_buf j)
Must be called before longjmp
Identifies a return site for a subsequent longjmp
Called once, returns one or more times
Implementation:
Remember where you are by storing the current register
context, stack pointer, and PC value in jmp_buf
Return 0 CS 230 S ‘23
setjmp/longjmp (cont)
void longjmp(jmp_buf j, int i)
Meaning:
return from the setjmp remembered by jump buffer j again ...
… this time returning i instead of 0
Called after setjmp
Called once, but never returns
longjmp Implementation:
Restore register context (stack pointer, base pointer, PC value)
from jump buffer j
Set %eax (the return value) to i
Jump to the location indicated by the PC stored in jump buf j
CS 230 S ‘23
setjmp/longjmp Example
Goal: return directly to original caller from a deeply-
nested function
void bar(void)
{
if (error2)
longjmp(buf, 2);
}
CS 230 S ‘23
jmp_buf buf;
int main()
{
switch(setjmp(buf)) {
case 0:
foo();
break;
case 1:
printf("Detected an error1 condition in foo\n");
break;
case 2:
printf("Detected an error2 condition in foo\n");
break;
default:
printf("Unknown error condition in foo\n");
}
exit(0);
}
CS 230 S ‘23
Limitations of Nonlocal Jumps
Works within stack discipline
Can only long jump to environment of function that has been
called but not yet completed
Before longjmp After longjmp
jmp_buf env; env
P1 P1
P1()
{
if (setjmp(env)) { P2
/* Long Jump to here */
} else {
P2();
P2
}
} P2
P2()
{ . . . P2(); . . . P3(); } P3
P3()
{
longjmp(env, 1);
}
CS 230 S ‘23
Limitations of Long Jumps (cont.)
Works within stack discipline
Can only long jump to environment of function that has been
called but not yet completed
jmp_buf env;
P1
P1() P2
{ env
P2(); P3(); At setjmp
}
P2() P1
{
if (setjmp(env)) { env
/* Long Jump to here */ X P2
}
} P2 returns P1
P3() env
{ X P3
longjmp(env, 1);
} At longjmp
CS 230 S ‘23
Putting It All Together: A Program
That Restarts Itself When ctrl-c’d
#include "csapp.h"
sigjmp_buf buf;
greatwhite> ./restart
void handler(int sig) starting
{ processing...
siglongjmp(buf, 1); processing...
} processing...
int main()
restarting
Ctrl-c
{ processing...
if (!sigsetjmp(buf, 1)) { processing...
Signal(SIGINT, handler); restarting
Sio_puts("starting\n"); processing... Ctrl-c
} processing...
else processing...
Sio_puts("restarting\n");
while(1) {
Sleep(1);
Sio_puts("processing...\n");
}
exit(0); /* Control never reaches here */
} restart.c CS 230 S ‘23