7.6 KiB
Exception
An Exception is a transfer of control to the OS kernel in response to some event(like div by 0, overflow, ctrl+C). that is change in processor state.
Exception Tables
Each type of event has an unique exc number k: k is index into exception table(interrupt vector table)
Handler k is called each time exception k occurs.
Asyncronous exceptions
It is caused by external events. Handler returns to next instruction.
for example: Timer interrupt, I/O interrupt.
Syncronous exceptions
It is caused by events that occur as a result of exxecuting an current instruction.
- Traps
- Intentional like procedure calls (e.g. system calls)
- Returns to next instruction
- Faults
- Unintentional but possibly recoverable (e.g. page fault(recoverable), protection fault(not recoverable), floating point exception)
- re-executes by kernel or aborts
- Aborts
- Unintentional and not recoverable (e.g. illegal instruction, parity error, machine check)
- Aborts the program
System Calls
Each x86-64 system call has a unique syscall number.
| Num | Name | Desc |
|---|---|---|
| 0 | read | read file |
| 1 | write | write file |
| 2 | open | open file |
| 3 | close | close file |
| 4 | stat | get file status |
| 57 | fork | create process |
| 59 | execve | execute program |
| 62 | kill | send signal |
Fault Example
Page Fault
int a[1000];
main() {
a[500] = 13;
}
In this situation, a page containing a[500] is currently on disk, so page fault occurs, CPU cannot find the data in physical RAM. So kernel copy page from disk to memory, and return and re-executes the instruction movl
Invalid Memory Ref
int a[1000];
main() {
a[5000] = 13;
}
In this situation, address a[5000] is invalid, so protection fault occurs, kernel terminates the program by sending SIGSEGV signal to the user process. Then user process exits with error code Segmentation Fault.
Process
An instance of a running program.
Process provides each program with two key abstractions:
- Logical control flow
- Each program seems to have exclusive use of the CPU provided by context switching of the kernel
- Private address space
- Each program seems to have exclusive use of main memory provided by virtual memory of the kernel
But in reality, computer runs multiple processes simultaneously by time-sharing CPU and multiplexing memory.
Multiprocessing
Single processor executes multiple processes concurrently. process execution interleaved by time-slicing. Address spaces managed by virtual memory system. And register values for non-executing processes saved in memory.
Multicore processor share main memory each can execute a separate process. scheduling of processors onto cores done by kernel.
Concurrent Processes
Concurrency is not at the exact same time.
Two processes are concurrent if their flows overlap in time. Otherwise, they are sequential.
Control flows for concurrent processes are pysically disjoint in time. But user think that they are logically running in parallel.
- Execution time of instruction may vary because of the Nondeterminism of the System: OS scheduling, Interrupts, Cache miss or Page fault, I/O device delays.
Context Switching
Prcess are managed by a shared chunk of memory-resident OS code called the kernel.
What is important is that the kernel is not a seprate process. It is invoked by processes when they need OS services, or when exceptions occur. That is Part of the processor.
Control flow passes via a context switching.
Syscall Error Handling
On error, Linux sys level function typically returns -1 and sets the global variable errno to indicate the specific error.
Hard and fast rule:
- You must check the return status of every system-level function
- Only exception is the handful of functions that return void
Error reporting functions:
void unix_error(char *msg) {
fprintf(stderr, "%s: %s\n", msg, strerror(errno));
exit(0);
}
Error handling wrappers:
pid_t Fork(void) {
pid_t pid;
if ((pid = fork()) < 0) unix_error("Fork error");
return pid;
}
Creating and Terminating Processes
pid_t getpid(void)returnes pid of current processpid_t getppid(void)returns pid of parent process
We can think of a process as being in one of three states:
- Running
- Executing or waiting to be executed
- Stopped
- Process execution is suspended and will not be scheduled until futher notice
- Terminated
- Stopped permanently
Terminating Processes
returnfrommain- call
exit(status)function - Receive a termination signal
void exit(int status);
Creating Process
Parent process can creates a new running child process by calling fork() system call.
int fork(void);
it returns 0 to the newly created child, and returns child's pid to the parent.
Child is almost identical to parent: child get an identical copy of the parent's virtual address space, file descriptors, and process state.
But child has its own unique pid.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
pid_t pid;
int x = 1;
pid = fork();
if (pid == 0) { /* Child */
printf("child: x=%d\n", ++x);
exit(0);
}
printf("parent: x=%d\n", --x);
exit(0);
}
while ! [ -r 9_1.out ]; do sleep .1; done; ./9_1.out
Concurrent execution of parent and child processes. fork duplicates but separates address space.
File descriptors are shared between parent and child like stdout, stderr.
Modeling fork with Process Graphs
- Each vertex represents a process state
- Directive Edges represent is ordering of execution.
- Edge can be labeled with current value of variables
Any topological sort of the graph corresponds to a feasible total ordering.
Reaping Child Processes
When process terminates, it still consumes system resources. It is called a "zombie".
"Reaping" is performed by the parent by using wait or waitpid. And then parent is given exit status information. Finally kernel then deletes zombie child process.
If any parent terminates without reaping a child, then the orphaned child will be reaped by the init process (pid 1).
However, long-running processes should reap their children to avoid accumulating zombies.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
if(fork() == 0) {
printf("Terminating child process\n");
exit(0);
} else {
printf("Running Parent\n");
sleep(1);
printf("Parent exiting\n");
}
}
while ! [ -r 9_2.out ]; do sleep .1; done; ./9_2.out & ps -ef | grep "9_2.out" | grep -v grep;
pid_t wait(int * child_status);
wait suspends current process until one of its children terminates.
- return value: pid of terminated child
- child_status: if not
NULL, the integer points will be set to the termination status(reason and exit status) of the child.- It can be checked by using macros defined in
wait.h
- It can be checked by using macros defined in
execve
int execve(const char *filename, char *const argv[], char *envp[]);
Load and runs in the current process.
filename: path of the executable fileargv: argument listenvp: environment variables "name=value"getenv,putenv,printenv
It does overwrite code, data, stack. retain only pid, file descriptors, signal context.
Called once and never returns on success.