Processes Focus: Process model Process management case study: Unix/Linux/Mac OS X (Windows is a little different.)
Operating Systems Problem: unwieldy hardware resources Solution: operating system
Operating Systems , a 240 view Focus: key abstractions provided by kernel barely scraping the surface Abstractions: process virtual memory Virtualization mechanisms and hardware support: context-switching exceptional control flow address translation, paging, TLBs
Processes Program = code (static) Process = a running program instance (dynamic) code + state Key illusions: Logical control flow Each process seems to have exclusive use of the CPU Private address space Each process seems to have exclusive use of full memory Why are these abstractions important? How are these abstractions implemented?
Implementing logical control flow Abstraction: every process has full control over the CPU Process A Process B Process C time Implementation: time-sharing Process A Process B Process C time
Context Switching Kernel (shared OS code) switches between processes Control flow passes between processes via context switch. Context = Process A Process B user code context switch kernel code time user code kernel code context switch user code 7
fork pid_t fork() 1. Clone current parent process to create identical child process, including all state (memory, registers, program counter, …). 2. Continue executing both copies with one difference: returns 0 to the child process • returns child’s process ID ( pid ) to the parent process • pid_t pid = fork(); if (pid == 0) { printf("hello from child\n"); } else { printf("hello from parent\n"); } fork is unique: called in one process, returns in two processes! (once in parent, once in child) 9
Creating a new process with fork Process n pid_t pid = fork(); if (pid == 0) { printf("hello from child\n"); 1 } else { printf("hello from parent\n"); Child Process m } à m à 0 pid_t pid = fork(); pid_t pid = fork(); if (pid == 0) { if (pid == 0) { printf("hello from child\n"); printf("hello from child\n"); 2 } else { } else { printf("hello from parent\n"); printf("hello from parent\n"); } } pid_t pid = fork(); pid_t pid = fork(); 3 if (pid == 0) { if (pid == 0) { printf("hello from child\n"); printf("hello from child\n"); } else { } else { printf("hello from parent\n"); printf("hello from parent\n"); } } Which prints first? hello from parent hello from child 10
fork again Parent and child continue from private copies of same state. Memory contents ( code , globals, heap , stack , etc.), Register contents, program counter , file descriptors… Only difference: return value from fork() Relative execution order of parent/child after fork() undefined void fork1() { int x = 1; pid_t pid = fork(); if (pid == 0) { printf("Child has x = %d\n", ++x); } else { printf("Parent has x = %d\n", --x); } printf("Bye from process %d with x = %d\n", getpid(), x); } 11
fork-exec fork-exec model: clone current process fork() replace process code and context (registers, memory) execv() with a fresh program. See man 3 execv , man 2 execve // Example arguments: path="/usr/bin/ls”, // argv[0]="/usr/bin/ls”, argv[1]="-ahl", argv[2]=NULL void fork_exec(char* path, char* argv[]) { pid_t pid = fork(); if (pid != 0) { printf("Parent: created a child %d\n”, pid); } else { printf("Child: exec-ing new program now\n"); execv(path, argv); } printf("This line printed by parent only!\n"); } 12
When you run the command ls Exec-ing a new program in a shell: Stack 1 Code/state of shell process. Heap Replaced by code/state of ls. Copy of code/state Data of shell process. Code: /usr/bin/bash fork() : child child parent Stack Stack Stack 2 2 3 exec() : Heap Heap Data Data Data Code: /usr/bin/bash Code: /usr/bin/bash Code: /usr/bin/ls Code/state of shell process. 13
execv : load/start program Stack bottom Null-terminated int execv(char* filename, env var strings char* argv[]) Null-terminated argument strings loads/starts program in current process: unused Executable filename envp[n] == NULL envp[n-1] With argument list argv … overwrites code, data, and stack envp[0] Keeps pid, open files, a few other items argv[argc] == NULL does not return argv[argc-1] … unless error argv[0] Linker vars envp argv argc Also sets up environment . See also: execve. Stack frame for main Stack top 14
wait for child processes to terminate pid_t waitpid(pid_t pid, int* stat, int ops ) Suspend current process (i.e. parent) until child with pid ends. On success: Return pid when child terminates. Reap child. If stat != NULL , waitpid saves termination reason where it points. See also: man 3 waitpid 16
Zombies! Terminated process still consumes system resources Reaping with wait/waitpid What if parent doesn’t reap? If any parent terminates without reaping a child, then child will be reaped by init process (pid == 1) What if parent runs a long time? e.g. , shells and servers 17
HCBye waitpid example CTBye void fork_wait() { int child_status; pid_t child_pid == fork(); if (child_pid == 0) { printf("HC: hello from child\n"); } else { if (-1 == waitpid(child_pid, &child_status, 0) { perror("waitpid"); exit(1); } printf("CT: child %d has terminated\n”, child_pid); } printf("Bye\n"); exit(0); } 18
Error-checking Check return results of system calls for errors! (No exceptions.) Read documentation for return values. Use perror to report error, then exit. void perror(char* message) Print " <message> : <reason that last system call failed.> "
Recommend
More recommend