# 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 #include #include 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 #include #include 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**.