-
Notifications
You must be signed in to change notification settings - Fork 2
OS note
MeadowView edited this page Feb 3, 2019
·
2 revisions
- Usenix conferences: https://www.usenix.org/conferences
- Reading material: https://pdos.csail.mit.edu/6.828/2016/reference.html
- Install/try XV6
- prerequisites:
- GMP: GNU MP bignum library
- MPFR: GNU multi-precison floating-point computation with correct rounding
- MPC:GNU C library for complex numbers with arbitrary high precision and correct rounding,
-
nm kernel
: list symbols from object files
$ nm kernel | grep _start # find the entry point of kernel
8010a48c D _binary_entryother_start
8010a460 D _binary_initcode_start
0010000c T _start
- Phil Storrs PC Hardware book
- PC Assembly book: https://github.com/modrpc/info/blob/master/docs/pcasm-book.pdf
qemu-system-i386 \
-drive file=obj/kern/kernel.img,index=0,media=disk,format=raw \
-serial mon:stdio \
-gdb tcp::26000 \
-D qemu.log
-
-serial mon:stdio
: Connect "mon:stdio" to serial port so that XTERM terminal can MIRROR the console of QEMU VM -
-gdb tcp::26000
: QEMU will create a GDB server which GDB client can connect and control execution
-
Compile:
- gcc -pipe -nostdinc -O1 -fno_builtin -I. -MD -fno_omit_frame_pointer -Wno_format -gstabs -m32 -fno_stack_protector -DJOS_KERNEL -c -o boot.o boot.S
- gcc -pipe -nostdinc -O1 -fno_builtin -I. -MD -fno_omit_frame_pointer -Wno_format -gstabs -m32 -fno_stack_protector -DJOS_KERNEL -Os -c -o main.o main.c
- -pipe: faster compilation (w/o temp file)
- -nostdinc: don't include standard C include (no. stdlibc in kernel)
- -fno_omit_frame_pointer: auto ON for x86_64 when -On given; expclitly if x86 (.386)
-
Load:
- ld -m elf_i386 -N -e start -Ttext 0x7c00 -o boot.out boot.o main.o
- -N: set text/data sector RW
- -e start: entry point
- -Ttext 0x7c00: text sector start at 0x7c00
- objdump -S boot.out > boot.asm
- objcopy -S -o binary -j .text boot.out boot
- -j .text: copy only text sector
- ld -m elf_i386 -N -e start -Ttext 0x7c00 -o boot.out boot.o main.o
- Sanity check
-
text section:
objdump -h obj/boot/boot.out
obj/boot/boot.out: file format elf32-i386
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000186 00007c00 00007c00 00000074 2**2
CONTENTS, ALLOC, LOAD, CODE
1 .eh_frame 000000a8 00007d88 00007d88 000001fc 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .stab 00000720 00000000 00000000 000002a4 2**2
CONTENTS, READONLY, DEBUGGING
3 .stabstr 0000088f 00000000 00000000 000009c4 2**0
CONTENTS, READONLY, DEBUGGING
4 .comment 00000034 00000000 00000000 00001253 2**0
CONTENTS, READONLY
-
program headers:
objdump -x obj/boot/boot.out
obj/boot/boot.out: file format elf32-i386
obj/boot/boot.out
architecture: i386, flags 0x00000012:
EXEC_P, HAS_SYMS
start address 0x00007c00
Program Header:
LOAD off 0x00000074 vaddr 0x00007c00 paddr 0x00007c00 align 2**2
filesz 0x00000230 memsz 0x00000230 flags rwx
STACK off 0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**4
filesz 0x00000000 memsz 0x00000000 flags rwx
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000186 00007c00 00007c00 00000074 2**2
CONTENTS, ALLOC, LOAD, CODE
1 .eh_frame 000000a8 00007d88 00007d88 000001fc 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .stab 00000720 00000000 00000000 000002a4 2**2
CONTENTS, READONLY, DEBUGGING
3 .stabstr 0000088f 00000000 00000000 000009c4 2**0
CONTENTS, READONLY, DEBUGGING
4 .comment 00000034 00000000 00000000 00001253 2**0
CONTENTS, READONLY
SYMBOL TABLE:
00007c00 l d .text 00000000 .text
00007d88 l d .eh_frame 00000000 .eh_frame
00000000 l d .stab 00000000 .stab
00000000 l d .stabstr 00000000 .stabstr
00000000 l d .comment 00000000 .comment
00000000 l df *ABS* 00000000 main.c
00000000 l df *ABS* 00000000 obj/boot/boot.o
00000008 l *ABS* 00000000 PROT_MODE_CSEG
00000010 l *ABS* 00000000 PROT_MODE_DSEG
00000001 l *ABS* 00000000 CR0_PE_ON
00007c0a l .text 00000000 seta20.1
00007c14 l .text 00000000 seta20.2
00007c64 l .text 00000000 gdtdesc
00007c32 l .text 00000000 protcseg
00007c4a l .text 00000000 spin
00007c4c l .text 00000000 gdt
00007c6a g F .text 00000012 waitdisk
00007d15 g F .text 00000071 bootmain
00007cdc g F .text 00000039 readseg
00007e30 g .eh_frame 00000000 __bss_start
00007c7c g F .text 00000060 readsect
00007e30 g .eh_frame 00000000 _edata
00007e30 g .eh_frame 00000000 _end
00007c00 g .text 00000000 start
- Some memory address range mapps to BIOS ROM.
- On boot, PC points to (hardwired) an address in BIOS ROM. BIOS contains code which does:
- set up IDT (interrupt descriptor table)
- initalizes PCI bus
- initalizes devices that BIOS knows (console -- VGA display, keyboard, etc.)
- search for bootable device -- floppy, HDD, CD-ROM
- from a bootable device, reads the boot loader (e.g. the "boot" sector 0 in HDD) into memory
- jump to the memory (i.e. execute boot loader) -- bootloader will read the kernel image into the memory.
- starts with fixed-length ELF header, followed by variable-length program header
- program header lists each of program sections to be loaded
- program sections are:
- .text: The program's executable instructions.
- .rodata: Read-only data, such as ASCII string constants produced by the C compiler. (We will not bother setting up the hardware to prohibit writing, however.)
- .data: The data section holds the program's initialized data, such as global variables declared with initializers like int x = 5;.
- When linker computes a memory layout, it reserved space for uninitialized global vars (i.e. .bss) that follows .data in memory.
-
LMA (load address) vs VMA (link address) in .text section
- LMA: memory address at which the section should be loaded into memory
- VMA: the memory address from which the section expects to execute
- unless PIC (position-independent code). which does not contain any absolute addresses, LMA should be respected
- typically, VMA and LMA are the same
void
bootmain(void)
{
struct Proghdr *ph, *eph;
// read 1st page off disk
readseg((uint32_t) ELFHDR, SECTSIZE*8, 0);
// is this a valid ELF?
if (ELFHDR->e_magic != ELF_MAGIC)
goto bad;
// load each program segment (ignores ph flags)
ph = (struct Proghdr *) ((uint8_t *) ELFHDR + ELFHDR->e_phoff);
eph = ph + ELFHDR->e_phnum;
for (; ph < eph; ph++)
// p_pa is the load address of this segment (as well
// as the physical address)
readseg(ph->p_pa, ph->p_memsz, ph->p_offset);
// call the entry point from the ELF header
// note: does not return!
((void (*)(void)) (ELFHDR->e_entry))();
bad:
outw(0x8A00, 0x8A00);
outw(0x8A00, 0x8E00);
while (1)
/* do nothing */
-
load addresses and link addresses are not the same in kernel
- kernel tells the boot lader to load it into memory at a low address (1MB) but it expects to execute from a high address
objdump -h obj/kern/kernel
obj/kern/kernel: file format elf32-i386
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00001d81 f0100000 00100000 00001000 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .rodata 000009a8 f0101da0 00101da0 00002da0 2**5
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .stab 00004555 f0102748 00102748 00003748 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
3 .stabstr 00001cbb f0106c9d 00106c9d 00007c9d 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .data 0000a300 f0109000 00109000 0000a000 2**12
CONTENTS, ALLOC, LOAD, DATA
5 .bss 00000650 f0113300 00113300 00014300 2**5
ALLOC
6 .comment 00000034 00000000 00000000 00014300 2**0
CONTENTS, READONLY
-
entry point in ELF header:
objdump -x obj/kern/kernel
obj/kern/kernel: file format elf32-i386
architecture: i386, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x0010000c
-
kern/entrypgdir.c: page table setup
- For now, this only maps first 4MB of physical memory
- "mapping physical memory" means: creating a page table for some "virtual addres space" which will be mapped to first 4MB
- i.e. we have 4MB physical memory to use; now we make a plan how to use the 4MB physical memory
- The mapping is not necessarily 1-to-1; many-to-1 is ok.
- maps virtual addresses [KERNBASE,]KERNBASE+4MB) to physical addresses [0,]4MB)
- KERNBASE = 0xF000-0000
- WHY 4MG?: 4MB is enough to be mapped using "one" page table
-
kern/entry.S:
- Load the physical address of
entry_pgdir
into cr3movl $(RELOC(entry_pgdir)), %eax
movl %eax, %cr3
- Turn on paging
movl %cr0, %eax
movl $(CR0_PE&#124;CR0_PG&#124;CR0_WP), %eax
movl %eax, %cr0
- Load the physical address of
__attribute__((__aligned__(PGSIZE)))
pde_t entry_pgdir[NPDENTRIES] = {
// Map VA's [0, 4MB) to PA's [0, 4MB)
[0]
= ((uintptr_t)entry_pgtable - KERNBASE) + PTE_P,
// Map VA's [KERNBASE, KERNBASE+4MB) to PA's [0, 4MB)
[KERNBASE>>PDXSHIFT]
= ((uintptr_t)entry_pgtable - KERNBASE) + PTE_P + PTE_W
};
- Page table entry consists of page table entries
- Entry 0 of the page table maps to physical page 0, entry 1 to physical page 1, etc.
pte_t entry_pgtable[NPTENTRIES] = {
0x000000 | PTE_P | PTE_W,
0x001000 | PTE_P | PTE_W
};
- kern/console.c: cga, keybard, serial
- LECTURE 2: https://pdos.csail.mit.edu/6.828/2016/lec/l-x86.html
- x86 calling conventions: https://en.wikipedia.org/wiki/X86_calling_conventions
- cdecl calling convention: http://unixwiz.net/techtips/win32-callconv-asm.html
- Before call: during execution of a function; caller needs to call another function
| |
+-----------------+
| saved %ebp | <---- %ebp
+-----------------+
| local var |
+-----------------+
| local var |
+-----------------+
| callee-save Rs | <---- %esp STACK FRAME for current function
+-----------------+-------------------------------------------------------
| |
+-----------------+
- Caller push arguments & caller-save registers: Frst argument comes last
caller:
pushl %ebp # make new call frame
movl %esp, %ebp
pushl 3 # push arguments
pushl 2
pushl 1
call callee # call callee
add %esp, 12 # remove arguments from frame
add %eax, 5 # use result (%eax contains return value)
popl %ebp # restore old call frame
ret # return
| |
+-----------------+
| saved %ebp | <---- %ebp
+-----------------+
| local var |
+-----------------+
| local var |
+-----------------+
| callee-save Rs | STACK FRAME for current function
+-----------------+ - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| arg n | STACK FRAME for current function
+-----------------+ extended for call
| ... |
+-----------------+
| arg 0 | <---- %esp STACK FRAME for current function
+-----------------+--------------------------------------------------------
- CALL instruction is executed: x86 will push the %eip (i.e. address of next instruction after "call foo") into stack and then jump to CALLEE
| |
+-----------------+
| saved %ebp | <---- %ebp
+-----------------+
| local var |
+-----------------+
| local var |
+-----------------+
| callee-save Rs | STACK FRAME for current function
+-----------------+ - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| arg n | STACK FRAME for current function
+-----------------+ extended for call
| ... |
+-----------------+
| arg 0 |
+-----------------+
| return %eip | <---- %esp STACK FRAME for current function
+-----------------+-------------------------------------------------------
- Call executed at text section: at this point following contract between CALLER and CALLEE
- at entry to a function (i.e. just after call):
- %eip points at first instruction of function
- %esp+4 points at first argument
- %esp points at return address
- after ret instruction:
- %eip contains return address
- %esp points at arguments pushed by caller
- called function may have trashed arguments
- %eax (and %edx, if return type is 64-bit) contains return value (or trash if function is void)
- %eax, %edx (above), and %ecx may be trashed
- %ebp, %ebx, %esi, %edi must contain contents from time of call
- Terminology:
- %eax, %ecx, %edx are "caller save" registers
- %ebp, %ebx, %esi, %edi are "callee save" registers
- at entry to a function (i.e. just after call):
- Function prologue: caller do this upon entry
pushl %ebp # save frame pointer into stack
# so that new value can be set to %ebp
movl %esp, %ebp # set new frame pointer;
# current esp becomes ebp
# above two instructions = "enter $0, $0"
subl $80, %esp # allocate stack space (local vars)
pushl %edi # save callee-save registers
pushl %esi # save callee-save registers
pushl %ebx # (in case they are used in function)
- Callee executes prologue; prologue updates the stack as follows
| |
+-----------------+
| saved %ebp | <---- [OLD %ebp]
+-----------------+
| local var |
+-----------------+
| local var |
+-----------------+
| callee-save Rs | STACK FRAME for current function
+-----------------+ - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| arg n | STACK FRAME for current function
+-----------------+ extended for call
| ... |
+-----------------+
| arg 0 |
+-----------------+
| return %eip | <---- [OLD %esp] STACK FRAME for current function
+-----------------+-------------------------------------------------------
| OLD %ebp | <---- %ebp (pushl OLD %ebp; and then curr %esp becomes %ebp)
+-----------------+
| local var |
+-----------------+
| local var |
+-----------------+
| callee-save Rs | <---- %esp
+-----------------+
- Function epilogue: callee do this before return
movl %edi, %eax # set up return value
popl %ebx # restore callee-save registers
popl %esi # restore callee-save registers
popl %edi # restore callee-save registers
movl %ebp, %esp # restore stack pointer
popl %ebp # restore frame pointer
# above two instructions = "leave"
return # pop return address
-
Callee executes prologue; prologue updates the stack as follows
- pop calle--save registers
| |
+-----------------+
| saved %ebp | <---- [OLD %ebp]
+-----------------+
| local var |
+-----------------+
| local var | STACK FRAME for current function
+-----------------+ - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| arg n | STACK FRAME for current function
+-----------------+ extended
| ... |
+-----------------+
| arg 0 |
+-----------------+
| return %eip | <---- [OLD %esp] STACK FRAME for current function
+-----------------+-------------------------------------------------------
| OLD %ebp | <---- %ebp (pushl OLD %ebp; and then curr %esp becomes %ebp)
+-----------------+
| local var |
+-----------------+
| local var | <---- %esp
+-----------------+
- "leave": restore stack poitner and frame pointer
| |
+-----------------+
| saved %ebp | <---- %ebp
+-----------------+
| local var |
+-----------------+
| local var | STACK FRAME for current function
+-----------------+ - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| arg n | STACK FRAME for current function
+-----------------+ extended
| ... |
+-----------------+
| arg 0 |
+-----------------+
| return %eip | <---- %esp
+-----------------+
- return: pop return $eip
| |
+-----------------+
| saved %ebp | <---- %ebp
+-----------------+
| local var |
+-----------------+
| local var | STACK FRAME for current function
+-----------------+ - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| arg n | STACK FRAME for current function
+-----------------+ extended
| ... |
+-----------------+
| arg 0 | <---- %esp
+-----------------+
- adjust %esp for arguments
- use return value inside %eax
- C code
int main(void) { return f(8)+1; }
int f(int x) { return g(x); }
int g(int x) { return x+3; }
- Assembler
_main :
prologue
pushl %ebp
movl %esp, %ebp
body
pushl $8
call _f
addl $1, %eax
epilogue
movl %ebp, %esp
popl %ebp
ret
_f :
prologue
pushl %ebp
movl %esp, %ebp
body
pushl 8(%esp)
call _g
epilogue
movl %ebp, %esp
popl %ebp
ret
_g :
prologue
pushl %ebp
movl %esp, %ebp
save %ebx
pushl %ebx
body
movl 8(%ebp), %ebx
addl $3, %ebx
movl %ebx, %eax
restore %ebx
popl %ebx
epilogue
movl %ebp, %esp
popl %ebp
ret
- Two things to do for memory management: 1) physical memory allocation and 2) virtual memory
-
physical memory allocator: allows kernel to allocate/free memory (in 4KB-pages)
- need to maintain table to see which physical pages are free/allocated
-
virtual memory: maps virtual addresses used by kernel and user to physical memory
- x86 MMU performs address translation, based on "page directory and page tables"
- so, supporting virtual memory essentially means populating the page dir/tables
- Two memory allocators are needed:
-
boot_alloc()
to be used right AFTER booting and BEFORE setting up virtual memory -
page_alloc()/page_free()
to be used after setting up virtual memory
-
-
i386_detect_memory()
- determine basemem, extmem, npages
- Create initial page directory, insert kern_pgdir into page directory itself (at PDX(UVPT))
- kern_pgdir = (pde_t *) boot_alloc(PGSIZE)
- kern_pgdir[PDX(UVPT)] = PADDR(kern_pgdir) | PTE_U | PTE_P
- Create pages = boot_alloc(npages * sizeof(struct PageInfo))
- Will allocate two stuffs: kern_pgdir, PageInfo array
- kern_pgdir: initial page directory (size=PGSiZE=4096); so that the PDX(UVPT) entry recursively points to itself.
kern_pgdir[PDX(UVPT)] = PADDR(kern_pgdir) | PTE_U | PTE_P
- <npages></npages> PageInfo objects: i.e. there are 4096 * npages pages; npages are the number of pages in physical memory
(computed during bootup)
K> kerninfo
_start 0010000c (phys)
entry f010000c (virt) 0010000c (phys)
etext f0101e11 (virt) 00101e11 (phys)
edata f0113300 (virt) 00113300 (phys)
end f0113950 (virt) 00113950 (phys)
- Address types:
-
uintptr_t: opaque virtual address; only virtual addresses can be dereferenced
- this is because, "dereferencing" means going through MMU for address translation; physical addresses are OUTPUT of MMU not INPUT of MMU
- physaddr_t: physical address; physical addresses can never be dereferenced
-
uintptr_t: opaque virtual address; only virtual addresses can be dereferenced
- 32-bit address can have up-to-4GB byte-addressable memory.
- let the size of a page be 4KB
- i.e. there are 4GB/4KB = 1M pages in 4GB memory
-
simple 1-dimensional page table:
- for each of 2^20 = 1M pages, create one table entry.
- there are 1M page table entry, where each entry occupies 4B (total 4MB) -- 4B points the physical address where the page starts
- 2-level paging scheme
- fragment the giant 1M-entry page table into many page tables (1024 tables) and one page directory (1024 entries).
- We'd like to get the giant conceptual page-table back in some way -- processes in JOS are going to look at it to figure out what's going on in their address space. But how?
- Luckily, the paging hardware is great for precisely this -- putting together a set of fragmented pages into a contiguous address space. And it turns out we already have a table with pointers to all of our fragmented page tables: it's the page directory!
- So, we can use the page directory as a page table to map our conceptual giant 2^22-byte page table (represented by 1024 pages) at some contiguous 2^22-byte range in the virtual address space. And we can ensure user processes can't modify their page tables by marking the PDE entry as read-only.
- Puzzle: do we need to create a separate UVPD mapping too?

- CR3 points at the page directory.
- The PDX part of the address indexes into the page directory to give you a page table.
- The PTX part indexes into the page table to give you a page, and then you add the low bits in.
- But the processor has no concept of page directories, page tables, and pages being anything other than plain memory.
- So there's nothing that says a particular page in memory can't serve as two or three of these at once.
- The processor just follows pointers: pd = lcr3(); pt = *(pd+4*PDX); page = *(pt+4*PTX);
- Diagramatically, it starts at CR3, follows three arrows, and then stops.
- If we put a pointer into the page directory that points back to itself at index V, as in

- then when we try to translate a virtual address with PDX and PTX equal to V, following three arrows leaves us at the page directory. So that virtual page translates to the page holding the page directory. In Jos, V is 0x3BD, so the virtual address of the UVPD is (0x3BD<<22)|(0x3BD<<12).
- Now, if we try to translate a virtual address with PDX = V but an arbitrary PTX != V, then following three arrows from CR3 ends one level up from usual (instead of two as in the last case), which is to say in the page tables. So the set of virtual pages with PDX=V form a 4MB region whose page contents, as far as the processor is concerned, are the page tables themselves. In Jos, V is 0x3BD so the virtual address of the UVPT is (0x3BD<<22).
- So because of the "no-op" arrow we've cleverly inserted into the page directory, we've mapped the pages being used as the page directory and page table (which are normally virtually invisible) into the virtual address space.
/*
* Virtual memory map: Permissions
* kernel/user
*
* 4 Gig --------> +------------------------------+
* | | RW/--
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* : . :
* : . :
* : . :
* |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| RW/--
* | | RW/--
* | Remapped Physical Memory | RW/--
* | | RW/--
* KERNBASE, ----> +------------------------------+ 0xf0000000 --+
* KSTACKTOP | CPU0's Kernel Stack | RW/-- KSTKSIZE |
* | - - - - - - - - - - - - - - -| |
* | Invalid Memory (*) | --/-- KSTKGAP |
* +------------------------------+ |
* | CPU1's Kernel Stack | RW/-- KSTKSIZE |
* | - - - - - - - - - - - - - - -| PTSIZE
* | Invalid Memory (*) | --/-- KSTKGAP |
* +------------------------------+ |
* : . : |
* : . : |
* MMIOLIM ------> +------------------------------+ 0xefc00000 --+
* | Memory-mapped I/O | RW/-- PTSIZE
* ULIM, MMIOBASE --> +------------------------------+ 0xef800000
* | Cur. Page Table (User R-) | R-/R- PTSIZE
* UVPT ----> +------------------------------+ 0xef400000
* | RO PAGES | R-/R- PTSIZE
* UPAGES ----> +------------------------------+ 0xef000000
* | RO ENVS | R-/R- PTSIZE
* UTOP,UENVS ------> +------------------------------+ 0xeec00000
* UXSTACKTOP -/ | User Exception Stack | RW/RW PGSIZE
* +------------------------------+ 0xeebff000
* | Empty Memory (*) | --/-- PGSIZE
* USTACKTOP ---> +------------------------------+ 0xeebfe000
* | Normal User Stack | RW/RW PGSIZE
* +------------------------------+ 0xeebfd000
* | |
* | |
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* . .
* . .
* . .
* |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|
* | Program Data & Heap |
* UTEXT --------> +------------------------------+ 0x00800000
* PFTEMP -------> | Empty Memory (*) | PTSIZE
* | |
* UTEMP --------> +------------------------------+ 0x00400000 --+
* | Empty Memory (*) | |
* | - - - - - - - - - - - - - - -| |
* | User STAB Data (optional) | PTSIZE
* USTABDATA ----> +------------------------------+ 0x00200000 |
* | Empty Memory (*) | |
* 0 ------------> +------------------------------+ --+
*
* (*) Note: The kernel ensures that "Invalid Memory" is *never* mapped.
* "Empty Memory" is normally unmapped, but user programs may map pages
* there if desired. JOS user programs map pages temporarily at UTEMP.
*/
- Two views: one for linking and the other for execution

- 80386 Interrupts Chapter: https://pdos.csail.mit.edu/6.828/2017/readings/i386/c09.htm
- 8259A PIC: https://en.wikibooks.org/wiki/X86_Assembly/Programmable_Interrupt_Controller
- x86 interrupts, exceptions, traps
- /proc/interrupts
CPU0 CPU1 CPU2 CPU3
0: 24 0 0 0 IR-IO-APIC 2-edge timer
1: 5 3 1 2 IR-IO-APIC 1-edge i8042
7: 14 0 0 0 IR-IO-APIC 7-edge
8: 0 0 0 1 IR-IO-APIC 8-edge rtc0
9: 0 0 0 0 IR-IO-APIC 9-fasteoi acpi
12: 12 0 0 1 IR-IO-APIC 12-edge i8042
16: 0 0 0 0 IR-IO-APIC 16-fasteoi rtl_pci
17: 31 557 67 333 IR-IO-APIC 17-fasteoi snd_hda_intel
120: 0 0 0 0 DMAR-MSI 0-edge dmar0
121: 0 0 0 0 IR-PCI-MSI 16384-edge PCIe PME
122: 0 0 0 0 IR-PCI-MSI 458752-edge PCIe PME
123: 0 0 0 0 IR-PCI-MSI 466944-edge PCIe PME
124: 0 0 0 0 IR-PCI-MSI 471040-edge PCIe PME
125: 6623 1147264 14748 3021727 IR-PCI-MSI 327680-edge xhci_hcd
126: 9400 24084 32778 558090 IR-PCI-MSI 376832-edge 0000:00:17.0
127: 0 8978004 24983 5453710 IR-PCI-MSI 2097152-edge enp4s0
128: 16 0 9 1 IR-PCI-MSI 360448-edge mei_me
129: 130 272 151 59 IR-PCI-MSI 514048-edge snd_hda_intel
130: 106 6627 3223903 43 IR-PCI-MSI 524288-edge nvidia
NMI: 340 352 347 357 Non-maskable interrupts
LOC: 9377514 8925961 8306858 9074815 Local timer interrupts
SPU: 0 0 0 0 Spurious interrupts
PMI: 340 352 347 357 Performance monitoring interrupts
IWI: 0 0 0 1 IRQ work interrupts
RTR: 3 0 0 0 APIC ICR read retries
RES: 2368415 1729579 1839443 1729691 Rescheduling interrupts
CAL: 56806 10619 11076 9979 Function call interrupts
TLB: 736667 747816 709980 730346 TLB shootdowns
TRM: 1 1 1 1 Thermal event interrupts
THR: 0 0 0 0 Threshold APIC interrupts
DFR: 0 0 0 0 Deferred Error APIC interrupts
MCE: 0 0 0 0 Machine check exceptions
MCP: 271 271 271 271 Machine check polls
ERR: 14
MIS: 0
PIN: 0 0 0 0 Posted-interrupt notification event
PIW: 0 0 0 0 Posted-interrupt wakeup event