XV6 as most OSs is monolithic, preemptive and time-sharing multiplexing. Isolation comes from the CPU which provides machine, supervisor and user modes. - machine: just for the boot time - supervisor: each time the kernel is active including when calling a syscall (with `ecall` in RISC-V) - user: for all user code Minix, L4 (seL4) and QNX operating systems have been mentioned as micro-kernel OSs. Memory layout of a process: [text; data; user stack; heap (large) ; trapframe; trampoline] 0 ↑------------------------------------------------------------↑ 2^38-1 text: instructions data: global variables heap: explicitly allocated memory trapframe: saved process registers when switching in and out of the kernel trampoline (4 KiB): code to transition in and out of the kernel trapframe & trampoline: explained in chapter 4 kernel `proc` structure contains: - (kstack) kernel stack used when the process calls for syscalls - (pagetable) pointers to physical memory pages actually used by the process, provided to the hardware for translation (virtual @ ←→ physical @) - (state) UNUSED, USED, SLEEPING, RUNNABLE, RUNNING or ZOMBIE - (trapframe) saved process registers when switching in and out of the kernel - (pid) process identification number - (ofile) list of opened files - (name) process name - (cwd) current directory - (context) kernel registers used to enter the process - (sz) size of process's memory - (parent) pointer to the process's parent proc structure - (xstate) exit status (given to its parent when it "waits" for it) - (killed) non-zero when the process has been killed - (chan) TODO: not currently explained - (lock) TODO: not currently explained RISC-V instructions - ecall: raise hardware privilege level program counter change to a kernel-defined entry point which then switches to the process's kernel stack and executes kernel instructions for this syscall once done, the kernel calls sret - sret: lower hardware privilege level A process is the abstraction of memory and CPU for a running program, giving it the illusion of being alone on the hardware. A process is: - an address space to give a running program the illusion of owning the entire memory - a thread to give a running program the illusion of having a CPU for himself starting xv6, the different phases context: booting up the machine => paging hardware disabled (virtual memory == physical memory) => booting at phy@ 0x80000000 because 0-0x80000000 contains IO devices => FYI: stack grows DOWN boot loader loads xv6 into memory then jumps to _entry (kernel/entry.S:7) phases for _entry: 1. sets up a 4096-byte stack for each hardware thread (HART) (hart = "hardware thread" as opposed to software-managed thread context) these stacks start at the address "start0" defined in C code (kernel/start.c:11) 2. loads stack0+4096 in sp (stack pointer) (which is the top of the first stack because stacks grow DOWN) 3. jumps to C function "start" (kernel/start.c:14) phases for "start": => main idea: "start" performs machine-mode configuration then jumps to "main" ex: interruptions, exceptions and Physical Memory Protection configuration 1. configures supervisor mode (related to the `mret` RISC-V instruction) mret enables to "return" from a mode to previous one mret in this case is first _configured_ to jump to supervisor mode a. mstatus (previous mode) is set to "supervisor" b. mepc (return address) is set to the address of "main" (kernel/main.c:10) c. satp (page-table register) is set to 0 => disables virtual address translation in supervisor mode d. delegates all interruptions and exceptions to supervisor mode 2. sets a timer interrupts on the clock chip 3. changes to supervisor mode with `mret` while jumping to "main" (kernel/main.c:10) phases for "main": 1. initializes devices, subsystems and a lot of stuff in general 2. calls "userinit" (kernel/proc.c:233) to set up the first "user process" => it is just the creation of the process from a kernel point of view ≠ execution => the process is then in "RUNNABLE" state => the program for this process is in initcode.S (kernel/initcode.S:3) (but compiled for some reason into the kernel/proc:221 char array) 3. calls the scheduler executes the only "RUNNABLE" process in the list, made by "userinit" => this "initcode" executes the /init application here is the code: 1. a0 = address to the "/init" string 2. a1 = argv for the future process 3. EXEC syscall to run the init program with provided parameters (a0 & a1) phases for /init: 1. creates a console device (if needed) 2. opens file descriptors 3. starts the shell on the newly created console system is up and running, yay!