System calls for process management
Mythili Vutukuru
CSE, IIT Bombay
API for process management
• What API does OS provide to user programs to manage processes?
• How to create, run, terminate processes?
• API = Application Programming Interface
= functions available to write user programs
• API provided by OS is a set of “system calls”
• System call is a function call into OS code that runs at higher CPU privilege level
• Sensitive operations (e.g., access to hardware) are allowed only at a higher
privilege level
• Some “blocking” system calls cause the process to be blocked and
context switched out (e.g., read() from disk), while others (e.g., getpid()
to get PID) can return immediately
2
Portability of code across OS
• POSIX API: standard set of system calls (and some C library functions)
available to user programs, defined for portability
• Programs written using POSIX API can run on any POSIX compliant OS
• Most modern OSes are POSIX compliant
• Program may still need to be recompiled for different architectures
• Program language libraries hide the details of invoking system calls
• The printf function in libc calls the write system call to write to screen
• User programs usually do not need to worry about invoking system calls
• ABI (application binary interface) is the interface between machine
code and underlying hardware: ISA, calling convention, …
3
Process related system calls (in Unix)
• fork() creates a new child process
• All processes are created by forking from a parent
• OS starts init process after boot up, which forks other processes
• The init process is ancestor of all processes, including shell/terminal
• exec() makes a process execute a given executable
• exit() terminates a process
• wait() causes a parent to block until child terminates
• Many variants of the above system calls exist in language libraries
with different arguments
4
Process creation: fork
• Parent process calls “fork” system call to create (spawn) a new process
• New child process created with new PID
• Memory image of parent is copied into that of child
• Parent and child run different copies of same code
Parent memory image copied to create
child memory image
Image credit: OSTEP
What happens after fork?
• Parent and child resume execution in their copies of the code
• Child starts executing with a return value of 0 from fork
• Parent resumes executing with a return value equal to child PID
• Parent and child run independently
• Any changes in parent’s data after fork does not impact child
int ret = fork() Child resumes int ret = fork()
if(ret == 0) { here if(ret == 0) {
print “I am child” Parent resumes print “I am child”
} here }
else if(ret > 0) { else if(ret > 0) {
print “I am parent” print “I am parent”
} }
Image credit: OSTEP
Example code with fork
• Parent and child run independently and print to screen
• Order of execution of parent and child can vary
8
Image credit: OSTEP
Example code with fork int ret = fork()
int x = 1
• What values of x are printed? if(ret == 0) {
• Parent and child both start with their own print “I am child”
independent copies of variable x in their x = x+1
print x
memory images
}
• Child increments its copy of x, prints 2 else if(ret > 0) {
• Parent decrements its copy of x, prints 0 print “I am parent”
x = x -1
print x
}
Image credit: CSAPP
Example code with nested fork fork()
fork()
• Total 4 processes (1 parent + 3 child) print hello
exit
• Hello printed 4 times
Image credit: CSAPP
Exit system call
• When a process finishes execution, it calls exit system call to
terminate
• OS switches the process out and never runs it again
• Exit is automatically called at end of main
• Exiting process cannot clean up its memory, and memory must be
freed up by someone else (why? More on this later.)
• Terminated process exists in a zombie state
• How are zombies cleaned up?
Wait system call
• Parent calls wait system call to reap (clean up …
memory of) a zombie child int ret = fork()
if(ret == 0) {
• Wait cleans up memory of one terminated child
print “I am child”
and returns in parent process exit()
• If child still running, wait system call blocks }
parent until child exits else if(ret > 0) {
• If child terminated already, wait reaps child and print “I am parent”
returns immediately wait()
}
• If parent with no child calls wait, it returns …
immediately without reaping anything
More on wait
• Wait system call variant waitpid reaps a specific child with a given PID,
while regular wait reaps any terminated child
• Read man pages for more details on arguments to waitpid and wait
• Wait system call “reaps” one dead child at a time (in any order)
• Every fork must be followed by call to wait at some point in parent
• What if parent has exited while child is still running?
• Child will continue to run, becomes orphan
• Orphans adopted by init process, reaped by init when they terminate
• If parent forks children, but does not bother calling wait for long time,
system memory fills up with zombies
• Common programming error, exhausts system memory
14
Image credit: OSTEP
Example code with fork and wait
• Order of printing of child and parent is deterministic now
• Why? Parent waits until child prints and exits, then prints
Image credit: OSTEP
Exec system call
• Isn’t it impractical to run the same code in all
processes? …
int ret = fork();
• Sometimes parent creates child to do similar work.. if(ret == 0) {
• .. but other times, child may want to run different code exec(“some_executable”)
}
• Child process uses “exec” system call to get a new else if(ret > 0) {
“memory image” print “I am parent”
• Allows a process to switch to running different code }
• Exec system call takes another executable as argument …
• Memory image is reinitialized with new executable, new
code, data, stack, heap, …
17
Image credit: OSTEP
Example code with exec
• Many variants of exec system call (execvp used in example), which
differ in the arguments provided (read more in man pages)
• If exec successful, child gets new memory image, never comes back to
the code in old memory image after exec
• Print statement after exec doesn’t run if exec successful
• If exec unsuccessful, reverts back to original memory image
Image credit: OSTEP
Shell / Terminal
• After bootup, the init process is first process created
• The init process spawns a shell like bash
• All future processes are created by forking from existing
processes like init or shell
• Shell reads user command, forks a child, execs the command
executable, waits for it to finish, and reads next command
• Common commands like ls, echo, cat are all readily
available executables that are simply exec-ed by the shell
19
$echo hello
hello
Example shell code $
• How does the shell run a user command?
do forever {
• Read input from user input(command)
• Shell process forks a child process
int ret = fork()
• Child process runs exec with “echo”
program executable as argument, calls exit if(ret == 0) {
when done exec(command)
• Parent shell calls wait, blocks till child }
terminates, reaps it, goes back for next else {
input wait()
}
}
More on shell and commands
• Some commands already exist as programs written by OS developers
and compiled into executables
• Shell runs such command by simply calling exec in child process
• Some commands are implemented directly in shell code itself
• Think: why doesn’t shell exec command directly? Why fork a child?
• Do we want the shell program code to be rewritten fully?
• For “cd” command, “chdir” system call used to change directory of
parent process itself, no child process is forked. Why?
• Every process has a current working directory
• Do we want to change directory of some child process or shell itself?
$sleep 10 &
$
Foreground and background execution
• By default, user command runs in foreground, shell cannot accept next
command until previous one finishes
• Background execution: when we type command followed by &
• Shell starts child to run command, but does not wait for command to finish
• Background processes reaped at a later time by shell
• When? Periodically? When next input is typed?
• How? There is a way to invoke wait where parent is not blocked even if child has
not exited (explore it on your own)
• It is also possible to run multiple commands in the foreground
• One after the other serially (next command starts after previous finishes)
• Or, all start at same time in parallel
• Explore how such things can be done in the standard Linux shell 22
$ls > foo.txt
$
I/O redirection
• Every process has some I/O channels (“files”) open, which can be
accessed by file descriptors
• STDIN, STDOUT, STDERR open by default for all processes
• Parent shell can manipulate these file descriptors of child before
exec in order to do things like I/O redirection
• E.g., output redirection is done by closing the default STDOUT and
opening a regular file in its place
STDIN from keyboard
Process P STDOUT to screen STDOUT to file
STDERR to screen
23
Open uses the first available file
descriptor (STDOUT in this case)
Image credit: OSTEP
Shell commands with pipes
• Shell can also “pipe” the output of one command into another, by
connecting STDOUT of one child to the STDIN of another child via a
pipe (a communication mechanism provided by kernel)
Image credit: Dive Into Systems