Files
2025-11-30 21:19:04 +09:00

255 lines
7.6 KiB
Markdown

# 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
```c
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
```c
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:
```c
void unix_error(char *msg) {
fprintf(stderr, "%s: %s\n", msg, strerror(errno));
exit(0);
}
```
Error handling wrappers:
```c
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 process
* `pid_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
1. `return` from `main`
2. call `exit(status)` function
3. Receive a termination signal
```c
void exit(int status);
```
### Creating Process
Parent process can creates a new running child process by calling `fork()` system call.
```c
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.
```c {cmd=gcc, args=[-O2 -x c $input_file -o 9_1.out]}
#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);
}
```
```sh {cmd hide}
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.
```c {cmd=gcc, args=[-O2 -x c $input_file -o 9_2.out]}
#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");
}
}
```
```sh {cmd hide}
while ! [ -r 9_2.out ]; do sleep .1; done; ./9_2.out & ps -ef | grep "9_2.out" | grep -v grep;
```
```c
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`
## execve
```c
int execve(const char *filename, char *const argv[], char *envp[]);
```
Load and runs in the current process.
* `filename`: path of the executable file
* `argv`: argument list
* `envp`: 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**.