What is a Process and Process Abstraction
A process is an abstraction that supports running programs
- Different processes may run several instances of the same program
- In most systems, processes form a tree, with the root being the first process to be created
- At a minimum, processes require the following resources:
- Memory to contain the program code and data
- A set of CPU registers to support execution of the program code
- The process may require the other extra resources, like:
- I/O Access
The process abstraction controls the resource access for programs.
The first process in UNIX is init
.
The init
process
As the name suggests, initializes things on the machine for other processes to work, and for the OS to function properly
Programs
A program consists of:
code
— machine instructionsdata
— variables stored and manipulated in memory, classified into- initialized variables (globals)
- dynamically allocated variables (
malloc
,new
, etc.) - stack variables (C automatic variables, function arguments)
- dynamically linked libraries (
DLL
’s) — libraries that were not compiled or linked with the program (contained code & data, possibly shared with other programs that use them) - mapped files — memory segments containing variables (
mmap()
), used frequently in database programs- similar to DLL’s, these are also not part of the program itself
mmap()
takes data from the disk and maps it into memory
This is explained in further detail in Object Modules and Linking.
Preparing Programs
Static vs. Dynamic Linking
Why use one over the other? There are many tradeoffs, including file size, memory usage, program runtime speed, portability, etc.
Static | Dynamic | |
---|---|---|
Performance | ✅ no lookup needed to find library (negligible) | library needs to be looked up to execute in runtime |
Storage Use | libraries are included in the program file | ✅ library doesn’t need to be included with file |
Memory Use | larger program files must be loaded into main memory | ✅ library added to process only when needed |
Portability | ✅ machine does not have to have library as it is included | machine must have the library to run the program |
Patching | program has to be recompiled to include patch | ✅ program does not need to be recompiled |
Running a Program
This is a multi-step process.
- The OS creates a process with some memory allocated for it
- The loader
- reads and interprets the header of the executable file
- sets up the process’ memory to contain the code & data from the executable
- pushes
argc
,argv
,envp
onto the runtime stack - sets the CPU registers properly and calls
__start()
(part of CRT0)
- Program starts running at
__start
, which in turn callsmain()
We now say that the ‘process’ is running, no longer a ‘program’ - When
main()
returns, CRT0 callsexit()
which destroys the process and returns all resources to the OS
Anatomy of a Process
Process Life Cycle
Process Context
The process context consists of its address space and the CPU registers
We say that we are running the context of process if its address space is in memory and the CPU registers are used to run .
Context Switching
The OS saves and evicts the data of the currently running process and switches to another process, setting up registers as necessary.
The TLB
has to be flushed every time there is a context switch, which massively slows down performance.
The process implementing a program must have its own set of ‘private’ CPU registers
- stored in main memory
Process Manager
The process manager has three parts:
Scheduler
The scheduler:
- decides who gets to run, when, and on what resources
- must:
- improve CPU utilization (resource efficiency)
- improve user response time (end user satisfaction)
- be fair
- Measured by two parameters:
First Come First Served (FCFS)
Scheduler implements a FIFO queue (ready queue or run queue)
When a process becomes ready, it enters the queue
Process at the head of the queue is allowed to run to completion before bringing in the next process
Advantages:
- Simple
Disadvantage:
- Cannot give priority to more important processes
- Slower response time
- Non-preemptive FCFS can waste cycles (when waiting for I/O)
Shortest Job First (SJF)
Scheduler implements a queue sorted by amount of time to run the process
Process at the head of the queue is allowed to run to completion before bringing in the next process
Advantages:
- Optimal response time
Disadvantages:
- Undecidable problem to know the shortest job
- Must ask the user predicted times
- User has to guess time
- Important processes are not necessarily given priority
- Always a shorter process, long processes will be starved
Preemptive vs. Non Preemptive
In non preemptive, once a process starts, it is allowed to run until it finishes
- Simple, efficient
- Creates problems - Infinite loops can cause queue to never move
In preemptive scheduling, a process is switched back and forth between the ‘ready’ and ‘running’ states
- More sophisticated with lots of capabilities
- Less efficient (context switching)
Priority-Based Scheduling
Scheduler implements a queue sorted by process’ priority
Process at the head of the queue is allowed to run until:
- a time quantum expires, in which case the process re-enters queue, possibly getting to the queue head if it has the highest priority
- process enters the wait state, in which process is out of the queue and re-enters when the wait condition is no longer relevant
Advantages:
- Reasonable response time for high priority
Disadvantages:
- Context switching
- Subject to starvation
Round Robin Scheduling (RR)
Scheduler implements a FIFO queue
Process at the head of the queue runs until:
- a time quantum expires, in which case the process re-enters the queue
- the process enters the wait state, in which the process is out of the queue and re-enters when the wait condition is no longer relevant
Advantages:
- No process starvation
- Fair
Disadvantages:
- context switching
Multi-Feedback Queues (MFQ) (UNIX)
The scheduler implements several Round Robin queues such that the processes in one queue all have the same priority.
The process at the head of the queue with the highest priority is allowed to run until:
- a time quantum expires, in which case the process re-enters the queue
- process enters the wait state in which the process is out of the queue and re-enters when the wait condition is no longer relevant
After running for a while, a process is relegated to the next queue in priority order (its priority is decreased).
After spending time in the I/O wait, a process is promoted to the highest priority queue (its priority is set to max).
Process Creation
The system creates the first process (sysproc
in UNIX).
The first process creates other process such that:
- the creator is called the parent process
- the created is called the child process
- the parent/child relationships can be expressed by a process tree
Ex. in UNIX, the second process is called init
- it creates all the gettys (login processes) and daemons
- it should never die
- it controls the system configuration (num of processes, priorities, etc.)
The system interface includes a call to create processes.
- in UNIX, this is called
fork()
- This creates an exact copy of the current process, including all of the memory (heap, stack, etc.) and pointers (this is copying the page tables for the process)
- In order to run a different process, you must use
exec()
- in Windows, this is called
createProcess()
Because of address spaces, pointers and heaps retain the same values but in reality have different contexts and do not affect each other.
fork
Parent and child’s code must be in the same program
- limits the usefulness for general purpose process creation
- gets out of control quickly if many children are to be created
The child inherits all the open files and network connections
- having parent and child share the I/O and network is very tricky
- behavior with respect to the network connections varies
- if the child does not use the inherited open files, then the work to set up the sharing is useless and the resources that are consumed within the OS are for no reason
Using fork
is expensive.
There is an alternative that does not fully copy the process memory, called vfork
that does not copy the page tables for the parent process.
exec
The exec()
call allows a process to ‘load’ a different program and start execution at __start
It allows a process to specify the number of arguments (argc
) and the string argument array (argv
)
If the call is successful:
- it is the same process
- it runs a different process
Two implementation options:
- overwrite current memory segments with the new values
- allocate new memory segments, load them with the new values, and deallocate old segments
If we are overwriting current memory and there is an error in the exec
and overwriting, we have a problem, because the memory is now partially overwritten. As such, it is better to go with the 2nd option and ensure that the new program is able to run.
exec
replaces the entire memory of the current process with a new program that is provided by pathname. A call to exec
should not return if it is successful.
This system call may cause confusion because it may be fair to ask “how is the parent getting the return value of the process if exec
isn’t supposed to return anything?”
This works because exec
replaces the process with a different program. Then, this process is still the child of whatever process called fork
to create it, but there is no longer the old program running, it is a new program, which means that the process return value will be the return value that the parent is able to read.
Example of exec
pid = fork();
if (pid) {
// Parent stuff
rval = wait(pid);
printf("%i", rval)
} else {
err = exec("/path/to/some/file");
printf("%i", err);
}
Let’s say that the file /path/to/some/file
exists and exec
successfully executes. The print statement is never called. Now, the parent process is in the first block of the if
statement, and is waiting for the result of the child process. When the child process exits, the parent will print the return value (exit code).
Lets say that the file /path/to/some/file
does not exist and exec
fails. exec
will return a value and err
will have a value. There will be something printed to the console.
The fork
/exec
sequence
Example
Say you have a process with process ID (PID) 0. The following code segment is executed:
int i = 3;
pid = fork();
if (pid) {
// Parent process
i++;
} else {
// Child process
i++;
exec("Path name of code to be run")
}
i = 4
in both copies when the process is ended.
The reason the if
statement works above is because when the process is forked, the new PID is assigned to the PID variable only in the parent process because it only assigns the variable after the fork, so the copy that the child process has is uninitialized and equal to 0
.
Example with Files
Files connections are copied over along with the rest of the data when using fork
, so in the following code segment, both the child and parent are accessing and editing the same file:
fopen(...)
pid = fork();
if (pid) {
// Parent process
i++;
fwrite(...)
} else {
// Child process
i++;
fwrite(...)
}
This will lead to a race condition, because we don’t know which process will edit the file first. This is not deterministic.
Process Termination
Orderly Termination: exit()
After the program finishes execution, it calls exit()
The system call does the following:
- takes the “result” of the program as an argument (CRT0 calls
exit()
with the value returned by the main program) - closes all open files, connections, etc.
- deallocates memory
- deallocates most of the OS data structures supporting the process
- checks if the parent is alive:
- if so, it holds the result value until the parent requests it, process doesn’t really die, but enters the zombie/defunct state
- if not, deallocates all data structures, the process is now dead
- cleans up all waiting zombies
wait
The wait
system call allows a parent process to wait for a child process’s return value. The thread waits until the child process is exited.
Process Control
The OS must include calls to enable special control of a process:
- Priority manipulation
nice()
which specifies the base process priority (the initial priority)- recall that UNIX priority decays as the process consumes CPU
- Debugging support:
ptrace()
, allows a process to be put under control of another- the other process can set breakpoints, examine registers, etc.
- Alarms and time:
- sleep puts a process on a timer queue waiting for some number of seconds, supporting an alarm functionality
Signaling
the OS translates some events into asynchronous signals:
- input/output
- alarms
The OS also translates exceptions into signals:
- e.g. memory access violations, illegal instructions, overflow, etc.
Signals also used for process control:
- e.g.
SIGKILL
to kill a process,SIGSTOP
to stop a process, etc.
Rudimentary process communications:
- e.g. process may use the
kill()
system call to send signals to each other (SIGUSR1
,SIGUSR2
)
Signal Handling
A process can:
- rely on the default signal handler (e.g. core dump in UNIX)
- ignore signal (temporarily or permanently)
Basic Process Implementation
Each process has a process control block (PCB) that: