Recitation 5

Recitation 8 (Nov. 1)
Outline
 Process & job control
 Lab 5
Reminder
 Lab 5:
 Due Thursday
Minglong Shao
[email protected]
Office hours:
Thursdays 5-6PM
Wean Hall 1315
Process & concurrency
 int fork(void)


Create an identical copy of the parent process
Return values differentiate parent from child
 Any scheduling order of processes are possible


Unless code explicitly imposes some order
(synchronization)
Context switching can happen at any point
Example 1
int main()
{
pid_t pid;
Possible outputs
if((pid = fork()) == 0) {
printf("a ");
}
else {
printf("b ");
if(fork() == 0) {
printf("c ");
}
}
if(waitpid(pid, NULL, 0) > 0)
printf("d ");
printf("e ");
}
1.
2.
3.
4.
a
b
a
b
b
a
b
e
c
e
c
c
d
c
e
e
e
d
e
a
e
e
d
d
e
e
e
e
?
?
?
?
Example 1 (cont.)
int main()
{
pid_t pid;
L1 “a “
if((pid = fork()) == 0) {
printf("a ");
}
else {
printf("b ");
if(fork() == 0) {
printf("c ");
}
}
if(waitpid(pid, NULL, 0) > 0)
printf("d ");
printf("e ");
L0
“e “
“b “
“c “
(“d “)
“e “
(“d “)
“e “
L2
In L1, pid = 0  wait for any child process
In L0 and L2, pid is the process id of L1
 wait for process L1
}
1. a b c d e e e N
2. b a e c d e e Y
3. a b c e e d e Y
4. b e c e a d e N
Lab 5
 A tiny shell (tsh) with job control & I/O redirection
 Key points:



Reap all child processes
Handle SIGCHLD, SIGTSTP, SIGINT
Avoid race hazard
Process tree for shell
pid=10
pgid=10
Shell
Background
job #1
pid=20 Forepgid=20 ground
job
pid=32
pgid=32
Background
process group 32
Child
pid=21
pgid=20
Child
pid=22
pgid=20
Foreground
process group 20
Background
job #2
pid=40
pgid=40
Backgroud
process group 40
Each job has a unique process group id
int setpgid(pid_t pid, pid_t pgid);
setpgid(0, 0);
Process tree for tsh
pid=5
pgid=5
pid=10
pgid=10
UNIX
shell
tsh
Background
job #1
pid=20 Forepgid=20 ground
job
Foreground job
receives SIGINT, SIGTSTP,
when you type ctrl-c, ctrl-z
pid=32
pgid=32
Background
process group 32
Child
pid=21
pgid=20
Child
pid=22
pgid=20
Foreground
process group 20
Background
job #2
pid=40
pgid=40
Backgroud
process group 40
int kill(pid_t pid, int sig)
pid > 0: send sig to specified process
pid = 0: send sig to all processes in same group
pid < -1: send sig to group -pid
Execute program
int execve(const char *fname,
char *const argv[],
char *const envp[]);
Examples:
execve(“/bin/ls”, NULL, NULL);
execve(“./mytest”, argv, envp);
“front-end” functions for execve
int
int
int
*
int
int
execl(const char* path, const char *arg, …);
execlp(const char* file, const char *arg, …);
execle(const char*path, const char *arg, … char
const envp[]);
execv(const char *path, char *const argv[]);
execvp(const char *file, char *const argv[]);
Reaping child process
waitpid(pid_t pid, int *status, int options)
pid: wait for child process with pid (process id or group id)
-1: wait for any child process
status: tell why child terminated
options:
WNOHANG: return immediately if no children zombied
WUNTRACED: report status of terminated or stopped children
In Lab 5, use
waitpid(-1, &status, WNOHANG|WUNTRACED)
In sigchld_handler():
while ((c_pid = waitpid(…)) > 0) { … }
Check status
 int status;
waitpid(pid, &status, WNOHANG|WUNTRACED)
Macros to evaluate status:
 WIFEXITED(status): child exited normally
 WEXITSTATUS(status): returns code when child exits
 WIFSIGNALED(status): child exited because a signal is
not caught
 WTERMSIG(status): gives the terminating signal number
 WIFSTOPPED(status): child is currently stopped
 WSTOPSIG(status): gives the stop signal number
Reaping child process in tsh
 Where to put waitpid(…)
 Two waits
 In sigchld_handler(): for bg processes
 In eval(): for fg processes
 One wait
 In sigchld_handler(): for both

tsh should still wait for fg job to complete, how?
Busy wait for foreground job
In eval():
if(fork() != 0) { /* parent */
addjob(…);
while(fg process still alive){
/* do nothing */
}
}
Sleep
In eval():
if(fork() != 0) { /* parent */
addjob(…);
while(fg process still alive){
sleep(1);
}
}
Race hazard
 A data structure is shared by two pieces of code
that can run concurrently
 Different behaviors of program depending upon
how the schedule interleaves the execution of
code.
An example of race hazard
sigchld_handler() {
while ((pid = waitpid(…)) > 0){
deletejob(pid);
}
}
eval() {
pid = fork();
if(pid == 0)
{ /* child */
execve(…);
}
/* parent */
/* signal handler may run BEFORE addjob()*/
addjob(…);
}
An okay schedule
time
Shell
Signal Handler
Child
fork()
addjob()
execve()
exit()
sigchld_handler()
deletejobs()
A problematic schedule
time
Shell
Signal Handler
Child
fork()
execve()
exit()
sigchld_handler()
deletejobs()
addjob()
Job added to job list after the signal handler tried to delete it!
Solution: blocking signals
sigchld_handler() {
pid = waitpid(…);
deletejob(pid);
}
eval() {
sigprocmask(SIG_BLOCK, …)
pid = fork();
if(pid == 0)
{ /* child */
sigprocmask(SIG_UNBLOCK, …)
execve(…);
}
/* parent */
/* signal handler might run BEFORE addjob() */
addjob(…);
sigprocmask(SIG_UNBLOCK, …)
}
I/O redirection
 Do it before call execve() in child process
eval() {
pid = fork();
if(pid == 0)
{ /* child */
/* Redirect I/O */
if (redirect_input)
dup2(…);
if (redirect_output)
dup2(…);
execve(…);
}
addjob(…);
}
dup2(int oldfd, int newfd)
 Covered in Chapter 11
oldfd: old file descriptor
newfd: new file descriptor
Some examples:
Get input from my_infd instead of standard input
dup2(my_infd, STDIN_FILENO);
Print output to my_outfd instead of standard output
dup2(my_outfd, STDOUT_FILENO);
Reminders
 Some important system calls:


fork(), execve(), waitpid(), sigprocmask(),
setpgid(), kill() …
Check man pages for details about system calls

man 2 kill
 Check return values of all system calls
 STEP by STEP


Test your shell by typing commands first
I/O redirection: last step (15 pts out of 135 pts)
 Start now!
Virtual memory
 One of the most important concepts in CS
 Why use virtual memory (VM)





Use RAM as a cache for disk
Easier memory management
Access protection
Enable “partial swapping”
Share memory efficiently