Process Creation (fork())#
Process States#
Focus on
Creation
Termination: zombies, and orphanage
Other states are controlled by the scheduler
Preemption
Blocking
CPU assignment
…
⟶ much more complex than can be shown on a simple diagram
Creative Weirdness: fork()#
#include <unistd.h>
pid_t fork(void);
The most innocent-looking function
Creates a child process: exact copy of the calling process
Returns twice
Once in parent
Once in child
Both continue where they left off, independently
Parent’s return value: PID of the newly created child process
Child’s return value: 0 - can use
getpid()if needed (here)
Demo: fork()#
#include <unistd.h>
#include <print>
int main()
{
pid_t pid = fork(); // <-- returns TWICE!!
if (pid == -1) {
perror("fork");
return 1;
}
if (pid == 0) { // <-- child
std::println("child: pid = {}, ppid = {}",
getpid(), getppid());
return 0; // <-- careful!!
}
else { // <-- parent
std::println("parent: pid = {}, child pid = {}",
getpid(), pid);
return 0; // <-- careful!!
}
}
Sideway: return From main() Vs. exit()#
Following example code will always return from
main()main()is special (entry point from C startup/runtime)Return statement in
main()is specialTerminates process
Return value is exit status of the process
Only one byte: 0-255
Return from
main()has the same effect as callingexit()instead
Bugs Ahead: Code Flow Leakage#
Attention
fork()returns twice⟶ two code flow paths
Usually it is a good idea to keep them separate!
#include <unistd.h>
#include <print>
int main()
{
pid_t pid = fork();
if (pid == -1) {
perror("fork");
return 1;
}
if (pid == 0) {
std::println("child: pid = {}, ppid = {}",
getpid(), getppid());
/* <-- falling through into unconditional code*/;
}
else {
std::println("parent: pid = {}, child pid = {}",
getpid(), pid);
/* <-- falling through into unconditional code*/;
}
std::println("goodbye from {}", getpid()); // <-- executed by both
return 0;
}
File Descriptors Are Inherited#
Parent opens a file
Creates child process
⟶ file descriptor is inherited
Semantics just like
dup()(see here)
Demo: File Descriptor Inheritance#
/tmp/somefilecontainsabOpened for reading by parent
Creates child
In each of the parent/child branches, one byte is read
⟶ file offset apparently shared
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>
#include <print>
int main()
{
int fd = open("/tmp/somefile", O_RDONLY);
if (fd == -1) {
perror("open");
return 1;
}
pid_t pid = fork();
if (pid == -1) {
perror("fork");
return 1;
}
if (pid == 0) { // <-- child
char one_byte;
ssize_t nread = read(fd, &one_byte, 1);
assert(nread == 1);
std::println("child has read one byte: {0}/{0:#x}", one_byte);
return 0;
}
else { // <-- parent
std::println("parent pid = {}, child pid = {}", getpid(), pid);
char one_byte;
ssize_t nread = read(fd, &one_byte, 1);
assert(nread == 1);
std::println("parent has read one byte: {0}/{0:#x}", one_byte);
return 0;
}
}
$ echo ab > /tmp/somefile
$ ./sysprog-fork-fd-inher
parent pid = 267811, child pid = 267812
parent has read one byte: a/0x61
child has read one byte: b/0x62
Care For Your Children - Waiting#
Parents must care - wait - for their children
If they don’t, kids become zombies (see below)
⟶ retrieve the child’s termination status (see below)
If child has not yet terminated, parent blocks in
wait()
#include <unistd.h>
#include <sys/wait.h>
#include <assert.h>
#include <print>
int main()
{
pid_t pid = fork();
if (pid == -1) {
perror("fork");
return 1;
}
if (pid == 0) {
sleep(3);
std::println("child (PID={}): exiting", getpid());
return 7;
}
else {
std::println("parent (PID={}): waiting for child (PID={})", getpid(), pid);
pid_t waited_for = wait(NULL); // <-- wait, no termination status needed
if (waited_for == -1) {
perror("wait");
return 1;
}
std::println("parent (PID={}): child {} terminated", getpid(), waited_for);
return 0;
}
}
Copy-On-Write Memory (COW)#
Parent and child run the same code ⟶ share code memory pages
They do so read-only (code cannot be written to)
Question: how about data? Do they share data?
Answer: No!
Share data pages initially; copy is made at first write
⟶ Copy On Write (COW)
#include <unistd.h>
#include <sys/wait.h>
#include <assert.h>
#include <print>
int main()
{
int variable = 42;
pid_t pid = fork();
if (pid == -1) {
perror("fork");
return 1;
}
if (pid == 0) {
variable = 666; // <-- copy-on-write
return 7;
}
else {
wait(NULL); // <-- be sure child has touched variable
std::println("parent: variable=={}", variable);
return 0;
}
}
Waiting, And Exit Status#
Now what about status?
⟶ More information than just the exit value (7 in this case)
Exit status:
WIFEXITED(),WEXITSTATUS()
#include <unistd.h>
#include <sys/wait.h>
#include <assert.h>
#include <print>
int main()
{
pid_t pid = fork();
if (pid == -1) {
perror("fork");
return 1;
}
if (pid == 0) {
sleep(3);
std::println("child (PID={}): exiting", getpid());
return 7;
}
else {
std::println("parent (PID={}): waiting for child (PID={})", getpid(), pid);
int status;
pid_t waited_for = wait(&status);
if (waited_for == -1) {
perror("wait");
return 1;
}
std::println("child {0} has changed state, status {1:#x}, {1:#b}", waited_for, status);
if (WIFEXITED(status)) // <-- what if not? alternatives?
std::println("child has exited with exit status {}", WEXITSTATUS(status));
return 0;
}
}
More Exit Information#
More state changes than simple exit:
Signaled (see Signals), e.g.
SIGINT: Ctrl-C from terminalSIGTERM: similar, but explicitly sent by another processSIGSEGV,SIGBUS: software memory error (likely a pointer bug)More, see man -s 7 signal
Stopped and continued
Have to use
waitpid()(wait()does not give that information)SIGTSTP: Ctrl-Z from terminalSIGSTOP: explicitly sentSIGCONT: request to continue
#include <unistd.h>
#include <sys/wait.h>
#include <assert.h>
#include <print>
int main()
{
pid_t pid = fork();
if (pid == -1) {
perror("fork");
return 1;
}
if (pid == 0) {
char one_byte;
ssize_t nread = read(STDIN_FILENO, &one_byte, 1); // <-- continue when RETURN key hit
assert(nread == 1);
std::println("child (PID={}): exiting", getpid());
return 7;
}
else {
std::println("parent pid = {}, child pid = {}", getpid(), pid);
while (true) { // <-- loop: child could be
// <-- stopped and continued
// <-- multiple times in a row
int status;
pid_t waited_for = waitpid(pid, &status, WUNTRACED|WCONTINUED);
if (waited_for == -1) {
perror("wait");
return 1;
}
std::println("child {0} has changed state, status {1:#x}, {1:#b}", waited_for, status);
if (WIFEXITED(status)) {
std::println("child has regularly exited with exit status {}", WEXITSTATUS(status));
break;
}
else if (WIFSIGNALED(status)) {
std::println("child has been blown out of the water by signal {} (core dumped: {})", WTERMSIG(status), WCOREDUMP(status));
break;
}
else if (WIFSTOPPED(status))
std::println("child has been stopped by signal {}", WSTOPSIG(status));
else if (WIFCONTINUED(status))
std::println("child has been continued");
}
return 0;
}
}
Zombies: Consequences Of Not Caring For Children#
Terminated process are still there …
… until their parents
wait()for them⟶ Carry information for parents on how child has terminated
Usually a very short time span - unless parents are sloppy
Zombie state: shown in
psas<defunct>“Parents use
wait()to reap* zombies”
Demo: Zombification#
#include <unistd.h>
#include <sys/wait.h>
#include <assert.h>
#include <print>
int main()
{
pid_t pid = fork();
if (pid == -1) {
perror("fork");
return 1;
}
if (pid == 0) {
sleep(3);
std::println("child exiting");
return 7;
}
else {
std::println("parent pid = {}, child pid = {}", getpid(), pid);
pause(); // <-- don't care for kids
return 0;
}
}
Orphanage (Parent’s Death)#
Final question: what if a process’s parent terminates?
Nobody can then reap the zombie!
⟶ Parentless child is reparented to PID 1 (according to good ol’ UNIX)
On modern Linux, one can set a dedicated reaper process (man -s 2 PR_SET_CHILD_SUBREAPER)
Normally the login session leader (
systemd --user)
Demo: Orphanage/Reparenting#
#include <unistd.h>
#include <sys/wait.h>
#include <assert.h>
#include <print>
int main()
{
pid_t pid = fork();
if (pid == -1) {
perror("fork");
return 1;
}
if (pid == 0)
pause();
else {
std::println("parent pid = {}, child pid = {}", getpid(), pid);
std::println("parent exits");
return 0;
}
}