Basic Process Creation#
Process States#
Many of the states reflect the process’s runtime behavior: does it wait for something, or has it expired its timeslice, or is it offended (or the offender itself) in a realtime scenario. A process is always under the control of the scheduler.
Much more complex than can be shown on a sketch
⟶ Uninterruptible vs. interruptible sleep
⟶ Various ways to terminate a process
…
Creative Weirdness: fork()
#
Creates a child process
Exact copy of the calling process
On return - returns twice - both parent and child 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)
(Live demo start …)
#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!!
}
}
return
From main()
, And exit()
#
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
#include <unistd.h>
#include <print>
int main()
{
pid_t pid = fork();
if (pid == -1) {
perror("fork");
exit(1); // <-- same as "return 1;"
}
if (pid == 0) {
std::println("child: pid = {}, ppid = {}",
getpid(), getppid());
exit(0); // <-- same as "return 0;"
}
else {
std::println("parent: pid = {}, child pid = {}",
getpid(), pid);
exit(0); // <-- same as "return 0;"
}
}
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
#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 abc > /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, 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 - shown in
ps
as<defunct>
⟶ Carry information for parents
“Zombie”
“Reaped” by their parents when they call
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 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
)
#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;
}
}