UNIS Template
Processes and Threads
Shuaiwen
USYD Future System Architecture Lab (FSA)
https://shuaiwen-leon-song.github.io/
To understand what a process is
To learn the basics of exceptional control flow
To learn how to control child processes
To learn about other process related functions
To learn how to load and execute programs
To learn about signals
To learn about pipes
Objectives
POSIX APIs
2
How to run programs?
How does the operating system run programs?
3
How to run programs?
How does the operating system run programs?
Need abstraction of executing program
4
How to run programs?
How does the operating system run programs?
Need abstraction of executing program
process = memory state + machine state
5
The OS must handle multiple processes in execution at any given time…
How do we do this?
Multi-Processing
What is a process?
An independent logical control flow that provides the illusion that our program has exclusive use of the processor (CPU Virtualization)
A private address space that provides the illusion that our program has exclusive use of the memory system (Memory Virtualization)
For advanced leaners, please visit here to learn more:
https://pages.cs.wisc.edu/~remzi/OSTEP/
7
How do we control process?
We need to identify process:
Get process id use getpid() function
8
#include
#include
int main(int argc, char const *argv[])
{
printf(“pid = %d\n”, getpid());
return 0;
}
8
How do we control process?
Need a way to create and destroy processes
9
A process is in one of three states
Running
Stopped
Terminated
Create & Terminate Processes
A process is in one of three states
Running Stopped Terminated
In the running state it is either executing on the CPU or
is waiting to be executed and eventually scheduled by the kernel
Create & Terminate Processes
A process is in one of three states
Running Stopped Terminated
In the stopped state the execution of the process is suspended and will not be scheduled. A process stops if it receives a SIGSTOP, SIGTSTP, SIGTTIN, or SIGTTOU signal
(more on signals later)
Create & Terminate Processes
More detailed POSIX signals refer to : https://en.wikipedia.org/wiki/Signal_(IPC)
A process is in one of three states
Running Stopped Terminated
In the terminated state the process is stopped permanently. A process becomes terminated for one of three reasons: (1) receiving a signal to terminate, (2) returning from main, or (3) calling the exit function.
Create & Terminate Processes
A process is in one of three states
Running Stopped Terminated So, how do we create a running process?
Create & Terminate Processes
We use the fork() function to create a new process.
pid_t pid = fork(); if (pid == 0) {
printf(“hello from child\n”);
} else {
printf(“hello from parent\n”);
}
Create & Terminate Processes
We use the fork() function to create a new process.
pid_t pid = fork(); if (pid == 0) {
printf(“hello from child\n”);
} else {
printf(“hello from parent\n”);
}
fork() creates a new process.
It is almost identical to the parent – but, has a different PID. It gets a copy of the parent’s virtual address space,
both heap and stack.
Create & Terminate Processes
We use the fork() function to create a new process.
pid_t pid = fork(); if (pid == 0) {
printf(“hello from child\n”);
} else {
printf(“hello from parent\n”);
}
fork() returns different results to the parent and child:
Parent gets the pid of the new child
Child gets 0 (so it can easily know it’s the child) -‐
if it needs its own pid it can simply call getpid to obtain it
Create & Terminate Processes
We use the fork() function to create a new process.
pid_t pid = fork(); if (pid == 0) {
printf(“hello from child\n”);
} else {
printf(“hello from parent\n”);
}
Parent
Child
Create & Terminate Processes
We use the fork() function to create a new process.
pid_t pid = fork(); if (pid == 0) {
printf(“hello from child\n”);
} else {
printf(“hello from parent\n”);
}
Parent
Child
pid_t pid = fork(); if (pid == 0) {
printf(“hello from child\n”);
} else {
printf(“hello from parent\n”);
}
Create & Terminate Processes
We use the fork() function to create a new process.
pid_t pid = fork(); if (pid == 0) {
printf(“hello from child\n”);
} else {
printf(“hello from parent\n”);
}
Parent
Child
pid_t pid = fork(); if (pid == 0) {
printf(“hello from child\n”);
} else {
printf(“hello from parent\n”);
}
Create & Terminate Processes
We use the fork() function to create a new process.
pid_t pid = fork(); if (pid == 0) {
printf(“hello from child\n”);
} else {
printf(“hello from parent\n”);
}
pid_t pid = fork(); if (pid == 0) {
printf(“hello from child\n”);
} else {
printf(“hello from parent\n”);
}
Create & Terminate Processes
After forking, parent and child process share file descriptors but not virtual memory (although
it gets a copy of the parent’s virtual address space, both heap and stack. )
Sharing Between Two Processes
Can two processes share the same shared memory segment?
Yes and no. Typically with modern operating systems, when another process is forked from the first, they share the same memory space with a copy-on-write set on all pages. Any updates made to any of the read-write memory pages causes a copy to be made for the page so there will be two copies and the memory page will no longer be shared between the parent and child process. This means that only read-only pages or pages that have not been written to will be shared.
If a process has not been forked from another then they typically do not share any memory. One exception is if you are running two instances of the same program then they may share code and maybe even static data segments but no other pages will be shared.
22
22
What does this print out?
void main()
{
printf(“L0\n”); fork(); printf(“L1\n”); fork(); printf(“Bye\n”);
}
L1 Bye
Bye L0 L1 Bye
Bye
Create & Terminate Processes
What does this print out?
void main()
{
printf(“L0\n”); fork(); printf(“L1\n”); fork(); printf(“L2\n”); fork(); printf(“Bye\n”);
}
Bye
L2 Bye
Bye
L1 L2 Bye
Bye
L2 Bye
Bye
L0 L1 L2 Bye
Does it always print in order?
Create & Terminate Processes
A tree hierarchy right?
24
What does this print out?
void main()
{
printf(“L0\n”);
if (fork() != 0) {
printf(“L1\n”);
if (fork() != 0) {
printf(“L2\n”); fork();
}
}
printf(“Bye\n”);
}
How many lines of output will it produce?
A) 4
B) 5
C) 6
D) 7
E) 8
Create & Terminate Processes
25
What does this print out?
void main()
{
printf(“L0\n”);
if (fork() != 0) {
printf(“L1\n”);
if (fork() != 0) {
printf(“L2\n”); fork();
}
}
printf(“Bye\n”);
}
Bye
Bye
Bye
L0 L1 L2 Bye
Create & Terminate Processes
What happens here?
void main()
{
if (fork() == 0) {
/* Child */
printf(“Terminating Child, PID = %d\n”, getpid());
exit(0);
} else {
printf(“Running Parent, PID = %d\n”, getpid());
while (1)
; /* Infinite loop */
}
}
Zombie!
Create & Terminate Processes
Linux
./main
Running Parent, PID = 24753
Terminating Child, PID = 24754
kill -9 24754
ps
PID TTY TIME CMD
24721 pts/4 00:00:00 bash
24753 pts/4 00:00:04 main
24754 pts/4 00:00:00 main
24755 pts/4 00:00:00 ps
Create & Terminate Processes
Interrupt or stop signal. Ctrol -Z
28
Linux
Zombie process cannot be killed, kill the parent process instead.
Create & Terminate Processes
What about this one?
Zombie?
void main()
{
if (fork() == 0) {
/* Child */
printf(“Running Child, PID = %d\n”, getpid());
while (1)
; /* Infinite loop */
} else {
printf(“Terminating Parent, PID = %d\n”, getpid());
exit(0);
}
}
Create & Terminate Processes
./main
Terminating Parent, PID = 24950
Running Child, PID = 24951
:~/ctest$ ps -f
UID PID PPID C STIME TTY TIME CMD
donglin 24911 24910 0 13:39 pts/5 00:00:00 -bash
donglin 24951 1 99 13:40 pts/5 00:00:27 ./main
donglin 24957 24911 0 13:40 pts/5 00:00:00 ps -f
Create & Terminate Processes
Note: PPID=1, this is not a zombie (first process started on Linux at boot)
Taken over by linux process 1.
31
Need a way to get rid of (kill) the zombies!
This is called
reaping!
How do we control processes?
So, how do we “reap” a child process programmatically?
wait() waitpid()
Zombie?
Create & Terminate Processes
kill -s SIGCHLD pid
int wait(int *child_status)
void main()
{
pid_t pid[N]; int i;
int child_status;
for (i = 0; i < N; i++) {
if ((pid[i] = fork()) == 0) { // Child exit(100+i);
} }
for (i = 0; i < N; i++) {
pid_t wpid = wait(&child_status); if (WIFEXITED(child_status)) {
printf("Child %d terminated with exit status %d\n", wpid, WEXITSTATUS(child_status));
} else {
printf("Child %d terminated abnormally\n", wpid);
} } }
fork a child processes
Create & Terminate Processes
int wait(int *child_status)
void main()
{
pid_t pid[N]; int i;
int child_status;
for (i = 0; i < N; i++) {
if ((pid[i] = fork()) == 0) { // Child exit(100+i);
} }
for (i = 0; i < N; i++) {
pid_t wpid = wait(&child_status);
if (WIFEXITED(child_status)) {
printf("Child %d terminated with exit status %d\n", wpid, WEXITSTATUS(child_status));
} else {
printf("Child %d terminated abnormally\n", wpid);
} } }
wait for each to terminate
Create & Terminate Processes
int wait(int *child_status)
void main()
{
pid_t pid[N]; int i;
int child_status;
for (i = 0; i < N; i++) {
if ((pid[i] = fork()) == 0) { // Child exit(100+i);
} }
for (i = 0; i < N; i++) {
pid_t wpid = wait(&child_status);
if (WIFEXITED(child_status)) {
printf("Child %d terminated with exit status %d\n", wpid, WEXITSTATUS(child_status));
} else {
printf("Child %d terminated abnormally\n", wpid);
} } }
wait for each to terminate
Get Info on Status
Create & Terminate Processes
int waitpid(pid, &status, options
void main()
{
pid_t pid[N]; int i;
int child_status;
for (i = 0; i < N; i++) {
if ((pid[i] = fork()) == 0) { // Child exit(100+i);
} }
for (i = N-1; i >= 0; i–) {
pid_t wpid = waitpid(pid[i], &child_status, 0);
if (WIFEXITED(child_status)) {
printf(“Child %d terminated with exit status %d\n”, wpid, WEXITSTATUS(child_status));
} else {
printf(“Child %d terminated abnormally\n”, wpid);
} } }
wait for specific child to terminate
Create & Terminate Processes
int waitpid(-1, &status, 0)
is the same as…
int wait(&status)
Create & Terminate Processes
void main()
{
if (fork() == 0) {
printf(“a”);
}
else {
printf(“b”);
waitpid(-1, NULL, 0);
}
printf(“c”); exit(0);
}
What is the output of the program on the left?
acbc
bcac
abcc
bacc
A or C or D
Activity
void main()
{
if (fork() == 0) {
printf(“a”);
}
else {
printf(“b”);
waitpid(-1, NULL, 0);
}
printf(“c”); exit(0);
}
What is the output of the program on the left?
acbc
bcac
abcc
bacc
A or C or D
Activity
Putting a process to sleep: zzzzz…
unsigned int sleep(unsigned int seconds);
41
#include
{
int amt = sleep(5);
}
Returns the number of seconds left to sleep if a signal woke up the process.
Returns 0 if time has elapsed.
Putting a process to sleep: zzzzz…
int pause(void);
42
#include
{
int amt = pause();
}
pause() returns only when a signal was caught and the signal-catching function returned. In this case, pause() returns -1
Loading and Running Programs
43
What does a shell program do?
A shell program is a special kind of program whose primary responsibility is to run other programs.
ls
ps
emacs
vim cd
int execve(const char* filename,
const
const char*
char* argv[],
envp[]);
Loading and Running Programs
In computing, exec is a functionality of an operating system that runs an executable file in the context of an already existing process, replacing the previous executable. This act is also referred to as an overlay. It is especially important in Unix-like systems, although exists elsewhere. As a new process is not created, the process identifier (PID) does not change, but the machine code, data, heap, and stack of the process are replaced by those of the new program.
https://en.wikipedia.org/wiki/Exec_(system_call)
We know what these are…
Loading and Running Programs
int execve(const char* filename,
const
const char*
char* argv[],
envp[]);
We know what these are…
But, what is this?
Loading and Running Programs
int execve(const char* filename,
const
const char*
char* argv[],
envp[]);
Every program runs in an “environment”. The environment is customized using environment variables: PATH, EDITOR, …
envp[0]
envp[1]
…
envp[n-‐1]
NULL
envp
“USER=richards”
“SHELL=/bin/bash”
“EDITOR=emacs”
Loading and Running Programs
int execve(const char* filename,
const
const char*
char* argv[],
envp[]);
It turns out that the more general form for main is:
int main(int argc,
const char* argv[], const char* envp[]);
Loading and Running Programs
int execve(const char* filename,
const
const char*
char* argv[],
envp[]);
Loading and Running Programs
49
Where are argv and envp in memory when a process executes?
50
Loading and Running Programs
Where are argv and envp In memory when a process executes?
Null-‐terminated environment variable strings
Null-‐terminated command-‐line argument strings
unused space
envp[n] == NULL
envp[n-‐1]
…
envp[0]
argv[argc] == NULL
argv[argc-‐1]
…
argv[0]
(Dynamic Linker Variables)
envp
argv argc
Stack frame for main
Bottom of stack
Top of stack
51
Loading and Running Programs
Where are argv and envp In memory when a process executes?
So, how do we access the environment variables?
Null-‐terminated environment variable strings
Null-‐terminated command-‐line argument strings
unused space
envp[n] == NULL
envp[n-‐1]
…
envp[0]
argv[argc] == NULL
argv[argc-‐1]
…
argv[0]
(Dynamic Linker Variables)
envp
argv argc
Stack frame for main
Bottom of stack
Top of stack
int* getenv(const char* name);
Returns pointer to value if there is an entry of the form “name=value”, and NULL if there is not
int setenv(const char* name,
const char* newval, int overwrite);
Returns 0 on success, -1 on error.
void unsetenv(const char* name);
Removing the name of the environmental variable. Upon successful completion, zero shall be returned. Otherwise, -1 shall be returned, errno set to indicate the error, and the environment shall be unchanged.
Loading and Running Programs
How do we communicate to processes?
We send them messages called signals
A signal is an event of some type that has occurred in the system.
It allows the OS to communicate to a process AND
user processes to communicate to each other
Signals
How do we communicate to processes?
We send them messages called signals
A signal is an event of some type that has occurred in the system.
It allows the OS to communicate to a process AND
user processes to communicate to each other
If a process tries to divide by 0: OS sends it a SIGFPE signal
If a process executes an illegal instruction:
OS sends it a SIGILL signal
If a process makes illegal memory reference: OS sends it a SIGSEGV signal
Signals
How do we communicate to processes?
We send them messages called signals
A signal is an event of some type that has occurred in the system.
It allows the OS to communicate to a process AND
user processes to communicate to each other
If you type ctrl-‐c:during process exec OS sends it a SIGINT signal
A process can kill another process: P1 sends P2 a SIGKILL signal
When a child terminates:
OS sends the parent a SIGCHLD signal
Signals
How do we communicate to processes?
The transfer of a signal to a destination occurs in 2 steps
Sending a Signal
Receiving a Signal
Signals
How do we communicate to processes?
Sending a Signal
The kernel sends a signal to a destination process by
updating some state in the context of that process.
This occurs when the kernel detects a system event
(div by 0) or when a process invokes the kill system call to explicitly request the kernel to send a signal to the destination process.
Signals
How do we communicate to processes?
Receiving a Signal
A destination process receives a signal when it is forced by the kernel to react in some way.
The process can either ignore the signal, terminate, or catch the signal by executing user‐level functions called signal handlers.
Signals
How do we communicate to processes?
A signal is pending if sent but not yet received
There can be at most 1 pending signal for a type
Important: signals are not queued
If a process has a pending signal of type k, then subsequent signals of type k that are sent to that process are discarded.
A process can block the receipt of certain signals
Blocked signals can be delivered, but will not be received until the signal is unblocked.
Signals
Windows is not POSIX. It does not have signals.
59
So, how are they implemented?
Process 1
0 0 0 0 … 0
Pending
0 0 0 0 … 0
Blocked
Process 2
0 0 0 0 … 0
Pending
0 0 0 0 … 0
Blocked
Kernel
Signals
So, how are they implemented?
Process 1
0 0 0 0 … 0
Pending
0 0 0 0 … 0
Blocked
Process 2
0 0 0 0 … 0
Pending
0 0 0 0 … 0
Blocked
Kernel
Each process structure contains a pending and blocked bit vector.
Each entry corresponds to a specific signal.
Signals
So, how are they implemented?
Process 1
0 0 0 0 … 0
Pending
0
0
1
0
…
0
Blocked
Process 2
0 0 0 0 … 0
Pending
1
0
0
0
…
0
Blocked
Kernel
A process can decide to block a signal by setting the corresponding bit in the blocked bit vector.
Signals
So, how are they implemented?
Process 1
0 0 0 0 … 0
Pending
0
0
1
0
…
0
Blocked
Process 2
0 0 0 0 … 0
Pending
1
0
0
0
…
0
Blocked
Kernel
Not all signals can be blocked:
SIGKILL can’t be blocked or handled by the process.
Signals
So, how are they implemented?
Process 1
0 0 0 0 … 0
Pending
0
0
1
0
…
0
Blocked
Process 2
0 0 0 0 … 0
Pending
1
0
0
0
…
0
Blocked
Kernel
Imagine the processor encounters an illegal instruction during the execution of Process 1
SIGILL
Signals
So, how are they implemented?
Process 1
0
0
0
1
…
0
Pending
0
0
1
0
…
0
Blocked
Process 2
0 0 0 0 … 0
Pending
1
0
0
0
…
0
Blocked
Kernel
The kernel will set the corresponding bit in the Pending bit vector
SIGILL
SIGILL
Signals
So, how are they implemented?
Process 1
0
0
0
1
…
0
Pending
0
0
1
0
…
0
Blocked
Process 2
0 0 0 0 … 0
Pending
1
0
0
0
…
0
Blocked
Kernel
Just before Process 1 resumes execution the kernel will check to see if the process has any pending signals that are not blocked
SIGILL
?
Signals
Default Signal handler: terminate the process
66
So, how are they implemented?
Process 1
0
0
0
1
…
0
Pending
0
0
1
0
…
0
Blocked
Process 2
0 0 0 0 … 0
Pending
1
0
0
0
…
0
Blocked
Kernel
If it does it will check if the process has defined a signal handler
for the signal or execute default behavior
SIGILL
?
Signals
So, how are they implemented?
Process 1
0
0
0
1
…
0
Pending
0
0
1
0
…
0
Blocked
Process 2
0 0 0 0 … 0
Pending
1
0
0
0
…
0
Blocked
Kernel
The default behavior for SIGILL is to terminate the process
SIGILL
Signals
0 0 0 0 … 0
0
0
1
0
…
0
Blocked
0 0 0 0 … 0
1
0
0
0
…
0
Blocked
Process 1 Process 2
Kernel
Pending Pending
A process can send another process a signal using the kill system call
So, how are they implemented?
#include
int kill(pid_t pid, int sig);
Signals
0
0
0
0
…
1
0
0
1
0
…
0
Blocked
0 0 0 0 … 0
1
0
0
0
…
0
Blocked
Process 1 Process 2
Kernel
Pending Pending
A process can send another process a signal using the kill system call
So, how are they implemented?
#include
int kill(pid_t pid, int sig);
SIGINT
Signals
0
0
0
0
…
1
0
0
1
0
…
0
Blocked
0 0 0 0 … 0
1
0
0
0
…
0
Blocked
Process 1 Process 2
Kernel
Pending Pending
Again, the kernel will check for pending signals, check for a handler or execute default behavior
So, how are they implemented?
#include
int kill(pid_t pid, int sig);
SIGINT
Signals
0
0
0
0
…
1
0
0
1
0
…
0
Blocked
0 0 0 0 … 0
1
0
0
0
…
0
Blocked
Process 1 Process 2
Kernel
Pending Pending
Again, the kernel will check for pending signals, check for a handler or execute default behavior
So, how are they implemented?
#include
int kill(pid_t pid, int sig);
SIGINT
Signals
We can send signals from the keyboard
ctrl-c
Typing ctrl-c from the keyboard sends a SIGINT signal to the shell.
The shell catches the signal and then sends a SIGINT to every process in the foreground process group.
ctrl-z
Typing ctrl-‐z from the keyboard sends a SIGTSTP signal to the shell.
The shell catches the signal and then sends a SIGTSTP to every process in the foreground process group.
Signals
Ok, so how do we send signals programmatically?
#include
#include
int kill(pid_t pid, int sig);
Signals
And how do we catch a signal?
#include
int secs);
Generates a SIGALRM signal to the calling process after secs seconds.
Need a handler to “catch” the signal and do something interesting.
Signals
Register signal handler
#include
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
The signal
Sets the disposition of the signal signum to handler
Signals
Signals
77
#include
#include
#include
#include
void sig_alarm(int signal)
{
printf(“Receive alarm signal = %d\n”,
signal);
}
int main(int argc, char *argv[])
{
signal(SIGALRM /*14*/, sig_alarm);
alarm(1);
printf(“program end!\n”);
return 0;
}
Output:
Receive alarm signal = 14
program end!
Tell the OS to send a signal to a process with a certain delay.
Tell OS to send a signal
77
Clean Sharing Model
Global variables (address spaces) are not shared
File tables (file descriptors) are shared
Simple and straightforward
Forking a child process is easy
Reaping child processes is simple
Processes are isolated for protection
Process Pros
Forking a process is expensive
Need to clone parent’s memory space
Need to setup a separate context for child
Process Cons
Note: the concept of context switch and why it has high overhead: https://en.wikipedia.org/wiki/Context_switch#:~:text=In%20computing%2C%20a%20context%20switch,of%20a%20multitasking%20operating%20system.
Linux numbers:
~20K cycles to create and reap a process
~10K cycles (or less) to create and reap a thread
Context switch concept
79
Forking a process is expensive
Need to clone parent’s memory space
Need to setup a separate context for child
Non-trivial to share data between processes
Need to use files/pipes to share data
Can use shared memory
Lots of overhead!
Process Cons
/docProps/thumbnail.jpeg