220 likes | 438 Views
Outline Process & job control Lab 5 Reminder Lab 5: Due Thursday. Minglong Shao shaoml+213@cs.cmu.edu Office hours: Thursdays 5-6PM Wean Hall 1315. Recitation 8 (Nov. 1). Process & concurrency. int fork(void) Create an identical copy of the parent process
E N D
Outline Process & job control Lab 5 Reminder Lab 5: Due Thursday Minglong Shao shaoml+213@cs.cmu.edu Office hours: Thursdays 5-6PM Wean Hall 1315 Recitation 8 (Nov. 1)
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; if((pid = fork()) == 0) { printf("a "); } else { printf("b "); if(fork() == 0) { printf("c "); } } if(waitpid(pid, NULL, 0) > 0) printf("d "); printf("e "); } Possible outputs 1. a b c d e e e ? 2. b a e c d e e ? 3. a b c e e d e ? 4. b e c e a d e ?
Example 1 (cont.) int main() { pid_t pid; if((pid = fork()) == 0) { printf("a "); } else { printf("b "); if(fork() == 0) { printf("c "); } } if(waitpid(pid, NULL, 0) > 0) printf("d "); printf("e "); } L1 “a “ “e “ “b “ (“d “) “e “ L0 “c “ (“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 4. b e c e a d e N 3. a b c e e d e Y
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 Shell pid=10 pgid=10 Fore- ground job Back- ground job #1 Back- ground job #2 pid=20 pgid=20 pid=32 pgid=32 pid=40 pgid=40 Background process group 32 Backgroud process group 40 Child Child pid=21 pgid=20 pid=22 pgid=20 Each job has a unique process group id int setpgid(pid_t pid, pid_t pgid); setpgid(0, 0); Foreground process group 20
Process tree for tsh UNIX shell pid=5 pgid=5 Foreground job receives SIGINT, SIGTSTP, when you type ctrl-c, ctrl-z pid=10 pgid=10 tsh Forward signals Fore- ground job Back- ground job #1 Back- ground job #2 pid=20 pgid=20 pid=32 pgid=32 pid=40 pgid=40 Background process group 32 Backgroud process group 40 Child Child 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 pid=21 pgid=20 pid=22 pgid=20 Foreground process group 20
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 execl(const char* path, const char *arg, …); int execlp(const char* file, const char *arg, …); int execle(const char*path, const char *arg, … char * const envp[]); int execv(const char *path, char *const argv[]); int 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 Signal Handler Child time Shell fork() addjob() execve() exit() sigchld_handler() deletejobs()
A problematic schedule time Signal Handler Child Shell 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