程序代写CS代考 UNIS Template

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 void main()
{
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 void main()
{
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 unsigned int alarm(unsigned
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