180 likes | 331 Views
L5: Writing Your Own Unix Shell October 1 6 , 2006. 15-213 “The course that gives CMU its Zip!”. Topics L5: Shell Lab Processes Signals Reminders Shell Lab Due Oct 25, 2006 (wed). Section A (Donnie Kim) recitation6 .ppt (some slides courtesy of Kun Gao(S05) and Minglong Shao(F04).
E N D
L5: Writing Your Own Unix Shell October 16, 2006 15-213“The course that gives CMU its Zip!” • Topics • L5: Shell Lab • Processes • Signals • Reminders • Shell Lab Due Oct 25, 2006 (wed) Section A (Donnie Kim) recitation6.ppt (some slides courtesy of Kun Gao(S05) and Minglong Shao(F04)
L5: Tiny Shell (tsh) • Things to learn from this lab: • Process Control (Ch 8) • Process ID (PID) and Process Group ID • Parent and Child process • Loading and running program • fork(), execve(), waitpid() • Signals (Ch 8) • Sending and receiving signals • Pending signal • Blocking/unblocking signal (avoiding race hazards) • I/O redirection (Ch 11) • dup2()
Process Control • Process ID, Process Group ID and Parent Process ID • Each process has its own, unique process ID • pid_t getpid(void); // returns my pid • Every process belong to exactly one process group • pid_t getpgrp(void); // returns my prg id • Process creates process (parent – child) • pid_t getppid(void); // returns my parent’s pid [dhjkim@bluefish tshlab-handout]$ ps -jf UID PID PPID PGID SID C STIME TTY TIME CMD dhjkim 5469 5465 5469 5469 0 00:17 pts/7 00:00:00 -tcsh dhjkim 6284 5469 6284 5469 99 00:54 pts/7 02:58:42 ./test dhjkim 10139 5469 10139 5469 0 03:53 pts/7 00:00:00 ps -jf
fork: Creating New Processes • int fork(void) • creates a new process (child process) that is identical to the calling process (parent process) • returns 0 to the child process • returns child’s pid to the parent process if (fork() == 0) { printf("hello from child\n"); } else { printf("hello from parent\n"); } Fork is interesting (and often confusing) because it is called once but returns twice • Any Scheduling order is Possible! • First parent then child or first child then parent can be executed depending on how OS scheduler decides
exec: Loading and Running Programs • int execve(char *fname, char *argv[], char *envp[]) • New Program (*fname) overwrites its state and takes over the process’ PID main() { if (fork() == 0) { execve("/usr/bin/ls", NULL, NULL); } wait(NULL); exit(); }
waitpid(): Waiting for a Specific Process • waitpid(pid, &status, options) • Can wait for specific process, and reap terminated child process • Various options • pid > 0: wait for process with PID=pid • -1: wait for any process • pid < -1: wait for any process from group abs(pid) • By default, waitpid blocks until at least one zombie process becomes available. • options: • WNOHANG: return immediately if no zombies available • WUNTRACED: also return if some process has been stopped • WNOHANG|WUNTRACED combination is very useful in the shell lab:it detects all the necessary events, and doesn’t block if no ‘’events’’
Signals • How to send signals • To a single process • int kill(pid_t pid, int sig) • To every process in group abs(gid) • int kill(pid_t gid, int sig) // gid < 0 • pid_t getpid(void); // returns my pid • How to receive signals • Signal handler • handler_t *signal (int signum, handler_t *handler) • How to block and unblock signals • Explicitly Blocking Signals • int sigprocmask(int how, …, sigset_t *oldset)
Process 1 blocked pending kill(pid, SIGINT) other events Signals : How it actually works Process 2 1 OS signal manager • divide by zero: SIGFPE • ctrl-c: SIGINT • child process exit: SIGCHLD OS Kernel
blocked pending Signals : How it actually works Process 2 Process 2 first checks pending/blocked vector when it gets scheduled 0 1 OS signal manager OS Kernel
Your task • eval() : Main routine that parses and interprets the command line • [300 lines, including helper functions] • sigchld_handler: Catches SIGCHILD signals [15 lines] • sigint_handler: Catches SIGINT(ctrl-c) signals [15 lines] • sigint_handler: Catches SIGSTP(ctrl-z) signals [15 lines]
Overview • eval() : 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 should have a unique process group id int setpgid(pid_t pid, pid_t pgid); setpgid(0, 0); Foreground process group 20
Overview UNIX shell pid=5 pgid=5 • eval() : 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 process with PID=pid pid = 0: send sig to all processes in my group pid = -1: send sig to all processes with PID>1 pid < -1: send sig to group abs(pid) pid=21 pgid=20 pid=22 pgid=20 Foreground process group 20
Reaping Child Process • When fg or bg job is finished (child process terminated) shell (parent proces) has to reap the child, otherwise? • Foreground job: • We can wait • Background job: • Can we wait? • Where should waitpid() go?
Race Hazards • sigchld_handler() { • … waitpid(…)) … { • deletejob(pid); • } • } • eval() { • pid = fork(); • if(pid == 0) • { /* child */ • execve(…); • } • /* parent */ • /* signal handler may run BEFORE addjob()*/ • addjob(…); • }
Race Hazards (Solution?) • 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, …) • }
Pop Quiz #include <unistd.h> #include <stdio.h> int cnt = 0; int main(void) { if (fork() == 0){ cnt ++; // in child fork(); cnt++; } cnt ++; printf("%d", cnt); return 0; }