thread creation posix process management
play

thread creation / POSIX / process management 1 Changelog 21 - PowerPoint PPT Presentation

thread creation / POSIX / process management 1 Changelog 21 January 2020 (between 12:30pm and 3:30pm lecture): added alternate version of typical pattern slide 1 last time handling non-system-call exceptions context switching in xv6


  1. xv6: struct proc if waiting, }; pointers to current registers/PC of process (user and kernel) stored on its kernel stack (if not currently running) thread’s state the kernel stack for this process every process has one kernel stack is process running? or waiting? or fjnished? waiting for what ( chan )? char name[16]; enum procstate { UNUSED, EMBRYO, SLEEPING, RUNNABLE, RUNNING, ZOMBIE }; process ID to identify process in system calls information about address space pgdir — used by processor sz — used by OS only information about open fjles, etc. // Process name (debugging) // Current directory struct proc { // Process ID uint sz; // Size of process memory (bytes) // Page table // Bottom of kernel stack for this process enum procstate state; // Process state int pid; // Parent process // Trap frame for current syscall // swtch() here to run process // If non-zero, sleeping on chan int killed; // If non-zero, have been killed // Open files 6 pde_t* pgdir; char *kstack; struct proc *parent; struct trapframe *tf; struct context *context; void *chan; struct file *ofile[NOFILE]; struct inode *cwd;

  2. xv6: struct proc if waiting, }; pointers to current registers/PC of process (user and kernel) stored on its kernel stack (if not currently running) the kernel stack for this process every process has one kernel stack is process running? or waiting? or fjnished? waiting for what ( chan )? char name[16]; enum procstate { UNUSED, EMBRYO, SLEEPING, RUNNABLE, RUNNING, ZOMBIE }; process ID to identify process in system calls information about address space pgdir — used by processor sz — used by OS only information about open fjles, etc. // Process name (debugging) // Current directory struct proc { // Trap frame for current syscall uint sz; // Size of process memory (bytes) // Page table // Bottom of kernel stack for this process enum procstate state; // Process state int pid; // Process ID // Parent process // swtch() here to run process // If non-zero, sleeping on chan int killed; // If non-zero, have been killed // Open files 6 pde_t* pgdir; char *kstack; ≈ thread’s state struct proc *parent; struct trapframe *tf; struct context *context; void *chan; struct file *ofile[NOFILE]; struct inode *cwd;

  3. xv6: struct proc if waiting, }; pointers to current registers/PC of process (user and kernel) stored on its kernel stack (if not currently running) thread’s state the kernel stack for this process every process has one kernel stack is process running? or waiting? or fjnished? waiting for what ( chan )? char name[16]; enum procstate { UNUSED, EMBRYO, SLEEPING, RUNNABLE, RUNNING, ZOMBIE }; process ID to identify process in system calls information about address space pgdir — used by processor sz — used by OS only information about open fjles, etc. // Process name (debugging) // Current directory struct proc { // Process ID uint sz; // Size of process memory (bytes) // Page table // Bottom of kernel stack for this process enum procstate state; // Process state int pid; // Parent process // Trap frame for current syscall // swtch() here to run process // If non-zero, sleeping on chan int killed; // If non-zero, have been killed // Open files 6 pde_t* pgdir; char *kstack; struct proc *parent; struct trapframe *tf; struct context *context; void *chan; struct file *ofile[NOFILE]; struct inode *cwd;

  4. xv6: struct proc if waiting, }; pointers to current registers/PC of process (user and kernel) stored on its kernel stack (if not currently running) thread’s state the kernel stack for this process every process has one kernel stack is process running? or waiting? or fjnished? waiting for what ( chan )? char name[16]; enum procstate { UNUSED, EMBRYO, SLEEPING, RUNNABLE, RUNNING, ZOMBIE }; process ID to identify process in system calls information about address space pgdir — used by processor sz — used by OS only information about open fjles, etc. // Process name (debugging) // Current directory struct proc { // Process ID uint sz; // Size of process memory (bytes) // Page table // Bottom of kernel stack for this process enum procstate state; // Process state int pid; // Parent process // Trap frame for current syscall // swtch() here to run process // If non-zero, sleeping on chan int killed; // If non-zero, have been killed // Open files 6 pde_t* pgdir; char *kstack; struct proc *parent; struct trapframe *tf; struct context *context; void *chan; struct file *ofile[NOFILE]; struct inode *cwd;

  5. xv6: struct proc if waiting, }; pointers to current registers/PC of process (user and kernel) stored on its kernel stack (if not currently running) thread’s state the kernel stack for this process every process has one kernel stack is process running? or waiting? or fjnished? waiting for what ( chan )? char name[16]; enum procstate { UNUSED, EMBRYO, SLEEPING, RUNNABLE, RUNNING, ZOMBIE }; process ID to identify process in system calls information about address space pgdir — used by processor sz — used by OS only information about open fjles, etc. // Process name (debugging) // Current directory struct proc { // Process ID uint sz; // Size of process memory (bytes) // Page table // Bottom of kernel stack for this process enum procstate state; // Process state int pid; // Parent process // Trap frame for current syscall // swtch() here to run process // If non-zero, sleeping on chan int killed; // If non-zero, have been killed // Open files 6 pde_t* pgdir; char *kstack; struct proc *parent; struct trapframe *tf; struct context *context; void *chan; struct file *ofile[NOFILE]; struct inode *cwd;

  6. xv6: struct proc if waiting, }; pointers to current registers/PC of process (user and kernel) stored on its kernel stack (if not currently running) thread’s state the kernel stack for this process every process has one kernel stack is process running? or waiting? or fjnished? waiting for what ( chan )? char name[16]; enum procstate { UNUSED, EMBRYO, SLEEPING, RUNNABLE, RUNNING, ZOMBIE }; process ID to identify process in system calls information about address space pgdir — used by processor sz — used by OS only information about open fjles, etc. // Process name (debugging) // Current directory struct proc { // Process ID uint sz; // Size of process memory (bytes) // Page table // Bottom of kernel stack for this process enum procstate state; // Process state int pid; // Parent process // Trap frame for current syscall // swtch() here to run process // If non-zero, sleeping on chan int killed; // If non-zero, have been killed // Open files 6 pde_t* pgdir; char *kstack; struct proc *parent; struct trapframe *tf; struct context *context; void *chan; struct file *ofile[NOFILE]; struct inode *cwd;

  7. xv6: struct proc if waiting, }; pointers to current registers/PC of process (user and kernel) stored on its kernel stack (if not currently running) thread’s state the kernel stack for this process every process has one kernel stack is process running? or waiting? or fjnished? waiting for what ( chan )? char name[16]; enum procstate { UNUSED, EMBRYO, SLEEPING, RUNNABLE, RUNNING, ZOMBIE }; process ID to identify process in system calls information about address space pgdir — used by processor sz — used by OS only information about open fjles, etc. // Process name (debugging) // Current directory struct proc { // Process ID uint sz; // Size of process memory (bytes) // Page table // Bottom of kernel stack for this process enum procstate state; // Process state int pid; // Parent process // Trap frame for current syscall // swtch() here to run process // If non-zero, sleeping on chan int killed; // If non-zero, have been killed // Open files 6 pde_t* pgdir; char *kstack; struct proc *parent; struct trapframe *tf; struct context *context; void *chan; struct file *ofile[NOFILE]; struct inode *cwd;

  8. process control blocks generally contains process’s context(s) (registers, PC, …) if context is not on a CPU (in xv6: pointers to these, actual location: process’s kernel stack) process’s status — running, waiting, etc. information for system calls, etc. open fjles memory allocations process IDs related processes 7

  9. xv6 myproc xv6 function: myproc() retrieves pointer to currently running struct proc 8

  10. myproc: using a global variable struct cpu cpus[NCPU]; struct proc* myproc( void ) { ... c = mycpu(); using special "ID" register ... return p; } 9 struct cpu *c; /* finds entry of cpus array as array index */ p = c − >proc;

  11. this class: focus on Unix Unix-like OSes will be our focus we have source code used to from 2150, etc.? have been around for a while xv6 imitates Unix 10

  12. Unix history 11 1969 Unnamed PDP-7 operating system 1969 Open Source 1971 to 1973 Unix 1971 to 1973 Mixed/Shared Source Version 1 to 4 1974 to 1975 Unix 1974 to 1975 Closed Source Version 5 to 6 PWB/Unix 1978 No future releases 1978 BSD 1.0 to 2.0 Unix 1979 1979 Version 7 Unix/32V 1980 1980 BSD 3.0 to 4.1 Xenix 1981 System III 1981 1.0 to 2.3 1982 1982 Xenix 3.0 1983 BSD 4.2 1983 SunOS 1 to 1.1 System V R1 to R2 1984 SCO Xenix 1984 Unix 1985 1985 Version 8 SCO Xenix AIX System V V/286 BSD 4.3 1986 1.0 R3 HP-UX 1986 SunOS 1.0 to 1.2 SCO Xenix Unix 1.2 to 3.0 1987 V/386 1987 9 and 10 HP-UX (last versions 1988 BSD 4.3 System V 1988 2.0 to 3.0 from T ahoe R4 Bell Labs) 1989 SCO Xenix 1989 V/386 BSD 4.3 1990 1990 Reno BSD NET/2 1991 1991 Linux 0.0.1 SunOS 4 Minix 386BSD 1.x NexTSTEP/ 1992 OPENSTEP 1992 HP-UX 1.0 to 4.0 NetBSD 6 to 11 0.8 to 1.0 Linux SCO Unix UnixWare 1993 BSD 1993 0.95 to 1.2.x 4.4 to 3.2.4 1.x to 2.x FreeBSD 1994 4.4 lite2 (System V 1994 1.0 to R4.2) 1995 2.2.x NetBSD 1995 OpenBSD OpenServer 1.1 to 1.2 1.0 to 2.2 Solaris 1996 5.0 to 5.04 1996 2.1 to 9 1997 1997 NetBSD 1.3 FreeBSD 1998 1998 3.0 to 3.2 OpenServer Minix 1999 Mac OS X 1999 AIX 5.0.5 to 5.0.7 2.x Server 3.0-7.2 2000 2000 2001 to 2004 2001 to 2004 Linux 2005 2.x 2005 UnixWare 7.x 2006 to 2007 (System V 2006 to 2007 OpenBSD R5) 2.3-6.1 Solaris 2008 2008 Mac OS X, FreeBSD NetBSD 10 OS X, 3.3-11.x 1.3-7.1 OpenServer HP-UX 2009 macOS 6.x 2009 11i+ DragonFly 10.0 to 10.12 Minix BSD 2010 (Darwin 2010 3.1.0-3.4.0 1.0 to 4.8 1.2.1 to 17) OpenSolaris 2011 2011 & derivatives Linux 2012 to 2015 (illumos, etc.) 2012 to 2015 3.x Solaris 11.0-11.3 2016 2016 Linux 4.x OpenServer 2017 2017 10.x

  13. POSIX: standardized Unix Portable Operating System Interface (POSIX) “standard for Unix” current version online: http://pubs.opengroup.org/onlinepubs/9699919799/ (almost) followed by most current Unix-like OSes …but OSes add extra features …and POSIX doesn’t specify everything 12

  14. what POSIX defjnes POSIX specifjes the library and shell interface source code compatibility doesn’t care what is/is not a system call… doesn’t specify binary formats… idea: write applications for POSIX, recompile and run on all implementations this was a very important goal in the 80s/90s at the time, Linux was very immature 13

  15. POSIX process management essential operations process information: getpid process creation: fork running programs: exec* also posix_spawn (not widely supported), … waiting for processes to fjnish: waitpid (or wait ) process destruction, ‘signaling’: exit , kill 14

  16. POSIX process management essential operations process creation: fork running programs: exec* also posix_spawn (not widely supported), … waiting for processes to fjnish: waitpid (or wait ) process destruction, ‘signaling’: exit , kill 15 process information: getpid

  17. getpid pid_t my_pid = getpid(); printf("my pid is %ld\n", ( long ) my_pid); 16

  18. process ids in ps cr4bd@machine:~$ ps PID TTY TIME CMD 14777 pts/3 00:00:00 bash 14798 pts/3 00:00:00 ps 17

  19. POSIX process management essential operations process information: getpid running programs: exec* also posix_spawn (not widely supported), … waiting for processes to fjnish: waitpid (or wait ) process destruction, ‘signaling’: exit , kill 18 process creation: fork

  20. fork pid_t fork() — copy the current process returns twice: in parent (original process): pid of new child process in child (new process): 0 everything (but pid) duplicated in parent, child: memory fjle descriptors (later) registers 19

  21. fork and PCBs eax (return val.) =42, copy copy child process control block … … fd 1: … fd 0: … open fjles user memory kernel stack ecx=133, … user regs user regs memory parent process control block … … fd 1: … fd 0: … open fjles user memory kernel stack ecx=133, … eax (return val.) =42, 20

  22. fork and PCBs eax (return val.) =42, copy copy child process control block … … fd 1: … fd 0: … open fjles user memory kernel stack ecx=133, … user regs user regs memory parent process control block … … fd 1: … fd 0: … open fjles user memory kernel stack ecx=133, … eax (return val.) =42, 20

  23. fork and PCBs eax (return val.) =42, copy copy child process control block … … fd 1: … fd 0: … open fjles user memory kernel stack ecx=133, … user regs user regs memory parent process control block … … fd 1: … fd 0: … open fjles user memory kernel stack ecx=133, … eax (return val.) =42, 20

  24. fork and PCBs eax (return val.) =42, copy copy child process control block … … fd 1: … fd 0: … open fjles user memory kernel stack ecx=133, … user regs user regs memory parent process control block … … fd 1: … fd 0: … open fjles user memory kernel stack ecx=133, … eax (return val.) =42, 20

  25. fork and PCBs eax (return val.) =420, copy copy child process control block … … fd 1: … fd 0: … open fjles user memory kernel stack ecx=133, … user regs user regs memory parent process control block … … fd 1: … fd 0: … open fjles user memory kernel stack ecx=133, … eax (return val.) =42 child (new) pid , 20

  26. fork example perror("Fork failed"); [432] child [100] parent of [432] Parent pid: 100 Example output: from error number stored in special global variable errno (example error message : “Resource temporarily unavailable”) prints out Fork failed: error message (not necessary if you were using C++’s cout, etc.) POSIX doesn’t specify (some systems it is, some not…) cast in case pid_t isn’t int getpid — returns current process pid } return 0; } } else { #include <stdlib.h> pid_t child_pid = fork(); #include <stdio.h> #include <unistd.h> #include <sys/types.h> pid_t pid = getpid(); printf("Parent pid: %d\n", ( int ) pid); if (child_pid > 0) { printf("[%d] child\n", ( int ) my_pid); pid_t my_pid = getpid(); printf("[%d] parent of [%d]\n", ( int ) my_pid, ( int ) child_pid); pid_t my_pid = getpid(); 21 int main( int argc, char *argv[]) { /* Parent Process */ } else if (child_pid == 0) { /* Child Process */

  27. fork example perror("Fork failed"); [432] child [100] parent of [432] Parent pid: 100 Example output: from error number stored in special global variable errno (example error message : “Resource temporarily unavailable”) prints out Fork failed: error message (not necessary if you were using C++’s cout, etc.) POSIX doesn’t specify (some systems it is, some not…) cast in case pid_t isn’t int getpid — returns current process pid } return 0; } } else { #include <stdlib.h> printf("[%d] child\n", ( int ) my_pid); #include <stdio.h> #include <unistd.h> #include <sys/types.h> printf("Parent pid: %d\n", ( int ) pid); pid_t child_pid = fork(); if (child_pid > 0) { pid_t my_pid = getpid(); printf("[%d] parent of [%d]\n", ( int ) my_pid, ( int ) child_pid); pid_t my_pid = getpid(); 21 int main( int argc, char *argv[]) { pid_t pid = getpid(); /* Parent Process */ } else if (child_pid == 0) { /* Child Process */

  28. fork example perror("Fork failed"); [432] child [100] parent of [432] Parent pid: 100 Example output: from error number stored in special global variable errno (example error message : “Resource temporarily unavailable”) prints out Fork failed: error message (not necessary if you were using C++’s cout, etc.) POSIX doesn’t specify (some systems it is, some not…) cast in case pid_t isn’t int getpid — returns current process pid } return 0; } } else { #include <stdlib.h> pid_t child_pid = fork(); #include <stdio.h> #include <unistd.h> #include <sys/types.h> pid_t pid = getpid(); printf("Parent pid: %d\n", ( int ) pid); if (child_pid > 0) { printf("[%d] child\n", ( int ) my_pid); pid_t my_pid = getpid(); printf("[%d] parent of [%d]\n", ( int ) my_pid, ( int ) child_pid); pid_t my_pid = getpid(); 21 int main( int argc, char *argv[]) { /* Parent Process */ } else if (child_pid == 0) { /* Child Process */

  29. fork example perror("Fork failed"); [432] child [100] parent of [432] Parent pid: 100 Example output: from error number stored in special global variable errno (example error message : “Resource temporarily unavailable”) prints out Fork failed: error message (not necessary if you were using C++’s cout, etc.) POSIX doesn’t specify (some systems it is, some not…) cast in case pid_t isn’t int getpid — returns current process pid } return 0; } } else { #include <stdlib.h> pid_t child_pid = fork(); #include <stdio.h> #include <unistd.h> #include <sys/types.h> pid_t pid = getpid(); printf("Parent pid: %d\n", ( int ) pid); if (child_pid > 0) { printf("[%d] child\n", ( int ) my_pid); pid_t my_pid = getpid(); printf("[%d] parent of [%d]\n", ( int ) my_pid, ( int ) child_pid); pid_t my_pid = getpid(); 21 int main( int argc, char *argv[]) { /* Parent Process */ } else if (child_pid == 0) { /* Child Process */

  30. fork example perror("Fork failed"); [432] child [100] parent of [432] Parent pid: 100 Example output: from error number stored in special global variable errno (example error message : “Resource temporarily unavailable”) prints out Fork failed: error message (not necessary if you were using C++’s cout, etc.) POSIX doesn’t specify (some systems it is, some not…) cast in case pid_t isn’t int getpid — returns current process pid } return 0; } } else { #include <stdlib.h> pid_t child_pid = fork(); #include <stdio.h> #include <unistd.h> #include <sys/types.h> pid_t pid = getpid(); printf("Parent pid: %d\n", ( int ) pid); if (child_pid > 0) { printf("[%d] child\n", ( int ) my_pid); pid_t my_pid = getpid(); printf("[%d] parent of [%d]\n", ( int ) my_pid, ( int ) child_pid); pid_t my_pid = getpid(); 21 int main( int argc, char *argv[]) { /* Parent Process */ } else if (child_pid == 0) { /* Child Process */

  31. a fork question child Done! Child 100 Done! In child parent child parent Done! Done! In child Child 100 parent parent int main() { child parent 100 . Give two possible outputs. (Assume no crashes, etc.) Exercise: Suppose the pid of the parent process is 99 and child is } printf("Done!\n"); } printf("Child %d\n", pid); printf("In child\n"); if (pid == 0) { pid_t pid = fork(); 22 } else {

  32. a fork question child Done! Child 100 Done! In child parent child parent Done! Done! In child Child 100 parent parent int main() { child parent 100 . Give two possible outputs. (Assume no crashes, etc.) Exercise: Suppose the pid of the parent process is 99 and child is } printf("Done!\n"); } printf("Child %d\n", pid); printf("In child\n"); if (pid == 0) { pid_t pid = fork(); 22 } else {

  33. POSIX process management essential operations process information: getpid process creation: fork also posix_spawn (not widely supported), … waiting for processes to fjnish: waitpid (or wait ) process destruction, ‘signaling’: exit , kill 23 running programs: exec*

  34. exec* exec* — replace current program with new program * — multiple variants same pid, new process image int execv(const char *path, const char **argv) path: new program to run argv: array of arguments, termianted by null pointer 24

  35. execv example ... including ls (‘list directory’) containing many common programs, on Unix / bin is a directory (but probably should match it) need not match fjrst argument path of executable to run convention: fjrst argument is program name when program’s main is run used to compute argv, argc } 25 ... exit(1); perror("execv"); execv("/bin/ls", args); if (child_pid == 0) { child_pid = fork(); /* child process */ char *args[] = {"ls", "-l", NULL}; /* execv doesn't return when it works. So, if we got here, it failed. */ } else if (child_pid > 0) { /* parent process */

  36. execv example ... including ls (‘list directory’) containing many common programs, on Unix / bin is a directory (but probably should match it) need not match fjrst argument path of executable to run convention: fjrst argument is program name when program’s main is run used to compute argv, argc } 25 ... exit(1); perror("execv"); execv("/bin/ls", args); if (child_pid == 0) { child_pid = fork(); /* child process */ char *args[] = {"ls", "-l", NULL}; /* execv doesn't return when it works. So, if we got here, it failed. */ } else if (child_pid > 0) { /* parent process */

  37. execv example ... including ls (‘list directory’) containing many common programs, on Unix / bin is a directory (but probably should match it) need not match fjrst argument path of executable to run convention: fjrst argument is program name when program’s main is run used to compute argv, argc } 25 ... exit(1); perror("execv"); execv("/bin/ls", args); if (child_pid == 0) { child_pid = fork(); /* child process */ char *args[] = {"ls", "-l", NULL}; /* execv doesn't return when it works. So, if we got here, it failed. */ } else if (child_pid > 0) { /* parent process */

  38. exec and PCBs memory discarded old memory (more on this later) not changed! copy arguments new stack, heap, … executable fjle loaded from the process control block user regs … … fd 1: … fd 0: (terminal …) open fjles user memory kernel stack ecx=133, … eax=42, 26

  39. exec and PCBs memory discarded old memory (more on this later) not changed! copy arguments new stack, heap, … executable fjle loaded from the process control block user regs … … fd 1: … fd 0: (terminal …) open fjles user memory kernel stack ecx=133 init. val., … eax=42 init. val. , 26

  40. exec and PCBs memory discarded old memory (more on this later) not changed! copy arguments new stack, heap, … executable fjle loaded from the process control block user regs … … fd 1: … fd 0: (terminal …) open fjles user memory kernel stack ecx=133 init. val., … eax=42 init. val. , 26

  41. exec and PCBs memory discarded old memory (more on this later) not changed! copy arguments new stack, heap, … executable fjle loaded from the process control block user regs … … fd 1: … fd 0: (terminal …) open fjles user memory kernel stack ecx=133 init. val., … eax=42 init. val. , 26

  42. exec and PCBs memory discarded old memory (more on this later) not changed! copy arguments new stack, heap, … executable fjle loaded from the process control block user regs … … fd 1: … fd 0: (terminal …) open fjles user memory kernel stack ecx=133 init. val., … eax=42 init. val. , 26

  43. why fork/exec? could just have a function to spawn a new program Windows CreateProcess() ; POSIX’s (rarely used) posix_spawn some other OSs do this (e.g. Windows) needs to include API to set new program’s state e.g. without fork: need function to set new program’s current directory e.g. with fork: just change your current directory before exec but allows OS to avoid ‘copy everything’ code probably makes OS implementation easier 27

  44. posix_spawn pid_t new_pid; } if (error_code == 0) { ); argv, 28 "/bin/ls", &new_pid, int error_code = posix_spawn( const char argv[] = { "ls", "-l", NULL }; NULL /* null = copy current process's open files; if not null, do something else */ , NULL /* null = no special settings for new process */ , NULL /* null = copy current process's "environment variables", if not null, do something else */ /* handle error */

  45. some opinions (via HotOS ’19) 29

  46. POSIX process management essential operations process information: getpid process creation: fork running programs: exec* also posix_spawn (not widely supported), … process destruction, ‘signaling’: exit , kill 30 waiting for processes to fjnish: waitpid (or wait )

  47. wait/waitpid pid_t waitpid(pid_t pid, int *status, int options) wait for a child process (with pid= pid ) to fjnish sets *status to its “status information” options? see manual page (command man waitpid ) 0 — no options 31 pid=-1 → wait for any child process instead

  48. exit statuses int main() { return 0; */ } 32 /* or exit(0);

  49. waitpid example #include <sys/wait.h> ... child_pid = fork(); if (child_pid > 0) { int status; waitpid(child_pid, &status, 0); ... 33 /* Parent process */ } else if (child_pid == 0) { /* Child process */

  50. the status #include <sys/wait.h> ... waitpid(child_pid, &status, 0); if (WIFEXITED(status)) { printf("main returned or exit called with %d\n", WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { printf("killed by signal %d\n", WTERMSIG(status)); ... } W* macros to decode it 34 } else { “status code” encodes both return value and if exit was abnormal

  51. the status #include <sys/wait.h> ... waitpid(child_pid, &status, 0); if (WIFEXITED(status)) { printf("main returned or exit called with %d\n", WEXITSTATUS(status)); printf("killed by signal %d\n", WTERMSIG(status)); ... } W* macros to decode it 34 } else if (WIFSIGNALED(status)) { } else { “status code” encodes both return value and if exit was abnormal

  52. aside: signals signals are a way of communicating between processes they are also how abnormal termination happens default wait’s status will tell you when and what signal killed a program constants in signal.h SIGINT — control-C SIGTERM — kill command (by default) SIGSEGV — segmentation fault SIGBUS — bus error SIGABRT — abort() library function … 35 kernel communicating “something bad happened” → kills program by

  53. waiting for all children #include <sys/wait.h> ... while ( true ) { if (errno == ECHILD) { break ; } } } 36 pid_t child_pid = waitpid( − 1, &status, 0); if (child_pid == (pid_t) − 1) { /* no child process to wait for */ } else { /* some other error */ /* handle child_pid exiting */

  54. typical pattern parent fork waitpid child process exec exit() 37

  55. typical pattern (alt) parent fork waitpid child process exec exit() 38

  56. typical pattern (detail) } } … main() { … } … waitpid(pid,…); } else if (pid > 0) { … exec…(…); if (pid == 0) { pid = fork(); … … pid = fork(); waitpid(pid,…); } else if (pid > 0) { … exec…(…); if (pid == 0) { pid = fork(); … } … waitpid(pid,…); } else if (pid > 0) { … exec…(…); if (pid == 0) { 39

  57. multiple processes? while (...) { pid = fork(); if (pid == 0) { exec ... } else if (pid > 0) { pids.push_back(pid); } } for (pid_t pid : pids) { waitpid(pid, ...); ... } 40 /* retrieve exit statuses in order */

  58. multiple processes? while (...) { pid = fork(); if (pid == 0) { exec ... } else if (pid > 0) { pids.push_back(pid); } } handleProcessFinishing(pid); } 41 /* retrieve exit statuses as processes finish */ while ((pid = waitpid( − 1, ...)) != − 1) {

  59. parent and child processes every process (but process id 1) has a parent process ( getppid() ) this is the process that can wait for it creates tree of processes (Linux pstree command): 42

  60. parent and child questions… what if parent process exits before child? child’s parent process becomes process id 1 (typically called init ) what if parent process never waitpid() s (or equivalent) for child? child process stays around as a “zombie” can’t reuse pid in case parent wants to use waitpid() what if non-parent tries to waitpid() for child? waitpid fails 43

  61. POSIX process management essential operations process information: getpid process creation: fork running programs: exec* also posix_spawn (not widely supported), … waiting for processes to fjnish: waitpid (or wait ) process destruction, ‘signaling’: exit , kill 44

  62. exercise (1) A and C A and B B. L1 (newline) L2 (newline) L2 E. C. (newline) L2 L2 (newline) L1 F. all of the above G. something else D. L1 int main() { A. for ( int i = 0; i < 2; ++i) { pids[i] = fork(); if (pids[i] == 0) { args[1] = extra[i]; execv("/bin/echo", args); } } for ( int i = 0; i < 2; ++i) { waitpid(pids[i], NULL, 0); } } Assuming fork and execv do not fail, which are possible outputs? 45 pid_t pids[2]; const char *args[] = {"echo", "ARG", NULL}; const char *extra[] = {"L1", "L2"};

  63. exercise (2) (newline) 0 (newline) 1 (newline) 0 (newline) 2 F. C and D C. 1 (newline) 0 (newline) 2 B. G. all of the above D. 1 (newline) 0 (newline) 2 (newline) 0 H. something else 0 A, B, and C int main() { for ( int i = 0; i < 2; ++i) { pid_t pids[2]; for ( int i = 0; i < 2; ++i) { pids[i] = fork(); if (pids[i] == 0) { execv("/bin/echo", args); } } printf("1\n"); fflush(stdout); waitpid(pids[i], NULL, 0); E. } printf("2\n"); fflush(stdout); } Assuming fork and execv do not fail, which are possible outputs? A. 0 (newline) 0 (newline) 1 (newline) 2 46 const char *args[] = {"echo", "0", NULL};

  64. 47

  65. backup slides 48

  66. context switch in xv6 will mostly talk about kernel thread switch : xv6 function: swtch() save kernel registers for A, restore for B in xv6: separate from saving/restoring user registers one of many possible OS design choices additional process switch pieces: ( switchuvm() ) changing address space (page tables) telling processor new stack pointer for exceptions 49

  67. swtch prototype save current context into *old start running context from new trick: struct context* = thread’s stack pointer top of stack contains saved registers, etc. 50 void swtch( struct context **old, struct context *new);

  68. swtch prototype save current context into *old start running context from new trick: struct context* = thread’s stack pointer top of stack contains saved registers, etc. 50 void swtch( struct context **old, struct context *new);

  69. thread switching in xv6: C swtch(...); ... ... // (3) ... ... // (2) in thread A: // (0) -- called earlier in thread B: ... // (4) ... // (1) 51 /* switch from A to B */ swtch(&(a − >context), b − >context); /* returns to (2) */ /* later on switch back to A */ swtch(&(b − >context), a − >context) /* returns to (4) */

  70. thread switching in xv6: C swtch(...); ... ... // (3) ... ... // (2) in thread A: // (0) -- called earlier in thread B: ... // (4) ... // (1) 51 /* switch from A to B */ swtch(&(a − >context), b − >context); /* returns to (2) */ /* later on switch back to A */ swtch(&(b − >context), a − >context) /* returns to (4) */

  71. thread switching in xv6: C swtch(...); ... ... // (3) ... ... // (2) in thread A: // (0) -- called earlier in thread B: ... // (4) ... // (1) 51 /* switch from A to B */ swtch(&(a − >context), b − >context); /* returns to (2) */ /* later on switch back to A */ swtch(&(b − >context), a − >context) /* returns to (4) */

  72. thread switching in xv6: C swtch(...); ... ... // (3) ... ... // (2) in thread A: // (0) -- called earlier in thread B: ... // (4) ... // (1) 51 /* switch from A to B */ swtch(&(a − >context), b − >context); /* returns to (2) */ /* later on switch back to A */ swtch(&(b − >context), a − >context) /* returns to (4) */

  73. thread switching in xv6: C swtch(...); ... ... // (3) ... ... // (2) in thread A: // (0) -- called earlier in thread B: ... // (4) ... // (1) 51 /* switch from A to B */ swtch(&(a − >context), b − >context); /* returns to (2) */ /* later on switch back to A */ swtch(&(b − >context), a − >context) /* returns to (4) */

  74. thread switching in xv6: C swtch(...); ... ... // (3) ... ... // (2) in thread A: // (0) -- called earlier in thread B: ... // (4) ... // (1) 51 /* switch from A to B */ swtch(&(a − >context), b − >context); /* returns to (2) */ /* later on switch back to A */ swtch(&(b − >context), a − >context) /* returns to (4) */

  75. thread switching in xv6: how? SP new (B) stack SP SP SP struct context (saved into A arg) SP swtch return addr. SP SP saved user regs old (A) stack saved user regs new (B) stack callee-saved registers swtch arguments swtch(A, B) pseudocode: read+use swtch return address from stack save caller-saved registers to stack write swtch return address to stack write all callee-saved registers to stack save old stack pointer into arg A read B arg as new stack pointer read all callee-saved registers from stack restore caller-saved registers from stack caller-saved registers … caller-saved registers swtch arguments swtch return addr. callee-saved registers old (A) stack … 52

  76. old (A) stack new (B) stack thread switching in xv6: how? (saved into A arg) callee-saved registers SP SP SP struct context SP SP swtch(A, B) pseudocode: SP SP saved user regs saved user regs swtch return addr. swtch arguments caller-saved registers … write swtch return address to stack (x86 call ) save old stack pointer into arg A read+use swtch return address from stack (x86 ret ) … restore caller-saved registers from stack caller-saved registers swtch arguments swtch return addr. callee-saved registers old (A) stack 52 save caller-saved registers to stack write all callee-saved registers to stack new (B) stack read B arg as new stack pointer read all callee-saved registers from stack

  77. old (A) stack new (B) stack thread switching in xv6: how? swtch(A, B) pseudocode: saved user regs saved user regs SP SP SP SP (saved into A arg) struct context SP SP callee-saved registers swtch return addr. swtch arguments caller-saved registers … save caller-saved registers to stack write swtch return address to stack (x86 call ) save old stack pointer into arg A read+use swtch return address from stack (x86 ret ) restore caller-saved registers from stack … caller-saved registers swtch arguments swtch return addr. callee-saved registers old (A) stack 52 SP → write all callee-saved registers to stack new (B) stack read B arg as new stack pointer read all callee-saved registers from stack

  78. old (A) stack new (B) stack thread switching in xv6: how? swtch(A, B) pseudocode: saved user regs saved user regs SP SP SP SP (saved into A arg) struct context SP SP callee-saved registers swtch return addr. swtch arguments caller-saved registers caller-saved registers write swtch return address to stack (x86 call ) save old stack pointer into arg A read+use swtch return address from stack (x86 ret ) restore caller-saved registers from stack … … swtch arguments swtch return addr. callee-saved registers old (A) stack 52 SP → save caller-saved registers to stack write all callee-saved registers to stack new (B) stack read B arg as new stack pointer read all callee-saved registers from stack

  79. old (A) stack new (B) stack thread switching in xv6: how? swtch(A, B) pseudocode: saved user regs saved user regs SP SP SP SP (saved into A arg) struct context SP SP callee-saved registers swtch return addr. swtch arguments caller-saved registers … write swtch return address to stack (x86 call ) write all callee-saved registers to stack save old stack pointer into arg A read+use swtch return address from stack (x86 ret ) restore caller-saved registers from stack … caller-saved registers swtch arguments swtch return addr. callee-saved registers old (A) stack 52 SP → save caller-saved registers to stack new (B) stack read B arg as new stack pointer read all callee-saved registers from stack

  80. old (A) stack new (B) stack thread switching in xv6: how? swtch(A, B) pseudocode: saved user regs saved user regs SP SP SP SP (saved into A arg) struct context SP SP callee-saved registers swtch return addr. swtch arguments caller-saved registers … write swtch return address to stack (x86 call ) save old stack pointer into arg A read B arg as new stack pointer read+use swtch return address from stack (x86 ret ) restore caller-saved registers from stack … caller-saved registers swtch arguments swtch return addr. callee-saved registers old (A) stack 52 SP → save caller-saved registers to stack write all callee-saved registers to stack new (B) stack read all callee-saved registers from stack

  81. old (A) stack new (B) stack thread switching in xv6: how? swtch(A, B) pseudocode: saved user regs saved user regs SP SP SP (saved into A arg) struct context SP SP SP callee-saved registers swtch return addr. swtch arguments caller-saved registers … write swtch return address to stack (x86 call ) save old stack pointer into arg A read B arg as new stack pointer read all callee-saved registers from stack read+use swtch return address from stack (x86 ret ) restore caller-saved registers from stack … caller-saved registers swtch arguments swtch return addr. callee-saved registers old (A) stack 52 save caller-saved registers to stack write all callee-saved registers to stack new (B) stack SP →

  82. old (A) stack new (B) stack thread switching in xv6: how? struct context callee-saved registers new (B) stack SP SP SP SP (saved into A arg) swtch(A, B) pseudocode: SP SP saved user regs saved user regs swtch return addr. swtch arguments caller-saved registers restore caller-saved registers from stack write swtch return address to stack (x86 call ) save old stack pointer into arg A read all callee-saved registers from stack … read+use swtch return address from stack (x86 ret ) … caller-saved registers swtch arguments swtch return addr. callee-saved registers old (A) stack 52 save caller-saved registers to stack write all callee-saved registers to stack read B arg as new stack pointer SP →

  83. old (A) stack new (B) stack thread switching in xv6: how? struct context callee-saved registers new (B) stack SP SP SP SP (saved into A arg) swtch(A, B) pseudocode: SP SP saved user regs saved user regs swtch return addr. swtch arguments caller-saved registers … write swtch return address to stack (x86 call ) save old stack pointer into arg A … restore caller-saved registers from stack read+use swtch return address from stack (x86 ret ) caller-saved registers swtch arguments swtch return addr. callee-saved registers old (A) stack 52 save caller-saved registers to stack write all callee-saved registers to stack read B arg as new stack pointer SP → read all callee-saved registers from stack

  84. old (A) stack new (B) stack thread switching in xv6: how? struct context callee-saved registers new (B) stack SP SP SP SP (saved into A arg) swtch(A, B) pseudocode: SP SP saved user regs saved user regs swtch return addr. swtch arguments caller-saved registers … write swtch return address to stack (x86 call ) save old stack pointer into arg A … restore caller-saved registers from stack read+use swtch return address from stack (x86 ret ) caller-saved registers swtch arguments swtch return addr. callee-saved registers old (A) stack 52 save caller-saved registers to stack write all callee-saved registers to stack SP → read B arg as new stack pointer read all callee-saved registers from stack

  85. thread switching in xv6: how? swtch(A, B) pseudocode: saved user regs saved user regs SP SP SP SP (saved into A arg) struct context SP SP SP new (B) stack callee-saved registers swtch return addr. swtch arguments caller-saved registers … write swtch return address to stack (x86 call ) save old stack pointer into arg A … restore caller-saved registers from stack read+use swtch return address from stack (x86 ret ) caller-saved registers swtch arguments swtch return addr. callee-saved registers old (A) stack 52 old (A) stack save caller-saved registers to stack write all callee-saved registers to stack new (B) stack read B arg as new stack pointer read all callee-saved registers from stack

  86. struct context **from_context struct context *to_context thread switching in xv6: assembly = where to fjnd new context context stored on thread’s stack context address = top of stack callee-saved registers: ebp, ebx, esi, edi other parts of context? eax, ecx, …: saved by swtch’s caller esp: same as address of context program counter: saved by call of swtch save stack pointer to fjrst argument (stack pointer now has all info) restore stack pointer from second argument restore program counter (and other saved registers) from stack of new thread = where to save current context two arguments: .globl swtch pushl %edi swtch: movl 4(%esp), %eax movl 8(%esp), %edx # Save old callee-save registers pushl %ebp pushl %ebx pushl %esi # Switch stacks ret movl %esp, (%eax) movl %edx, %esp # Load new callee-save registers popl %edi popl %esi popl %ebx popl %ebp 53

Recommend


More recommend