trap( struct trapframe *tf) write syscall in xv6: interrupt table setup ... meaning: run in kernel mode (yes, code segments specifjes more than that — nothing we care about) vectors[T_SYSCALL] — OS function for processor to run set to pointer to assembly function vector64 trap returns to alltraps alltraps restores registers from tf , then returns to user-mode vector64: pushl $0 pushl $64 jmp alltraps vectors.S (otherwise: triggers fault like privileged instruction) alltraps: ... call trap ... iret trapasm.S void { ... trap.c set it to use the kernel “code segment” be callable from user mode via int instruction ... set the T_SYSCALL (= 0x40 ) interrupt to lidt(idt, sizeof (idt)); ... ... trap.c (run on boot) lidt — function (in x86.h) wrapping lidt instruction sets the interrupt descriptor table table of handler functions for each interrupt type (from mmu.h): // Set up a normal interrupt/trap gate descriptor. // - istrap: 1 for a trap gate, 0 for an interrupt gate. // interrupt gate clears FL_IF, trap gate leaves FL_IF alone // - sel: Code segment selector for interrupt/trap handler // - off: Offset in code segment for interrupt/trap handler // - dpl: Descriptor Privilege Level - // the privilege level required for software to invoke // this interrupt/trap gate explicitly using an int instruction. #define SETGATE(gate, istrap, sel, off, d) \ 7 SETGATE(idt[T_SYSCALL], 1, SEG_KCODE<<3, vectors[T_SYSCALL], DPL_USER);
trap( struct trapframe *tf) write syscall in xv6: interrupt table setup ... meaning: run in kernel mode (yes, code segments specifjes more than that — nothing we care about) vectors[T_SYSCALL] — OS function for processor to run set to pointer to assembly function vector64 trap returns to alltraps alltraps restores registers from tf , then returns to user-mode vector64: pushl $0 pushl $64 jmp alltraps vectors.S (otherwise: triggers fault like privileged instruction) alltraps: ... call trap ... iret trapasm.S void { ... trap.c set it to use the kernel “code segment” be callable from user mode via int instruction ... set the T_SYSCALL (= 0x40 ) interrupt to lidt(idt, sizeof (idt)); ... ... trap.c (run on boot) lidt — function (in x86.h) wrapping lidt instruction sets the interrupt descriptor table table of handler functions for each interrupt type (from mmu.h): // Set up a normal interrupt/trap gate descriptor. // - istrap: 1 for a trap gate, 0 for an interrupt gate. // interrupt gate clears FL_IF, trap gate leaves FL_IF alone // - sel: Code segment selector for interrupt/trap handler // - off: Offset in code segment for interrupt/trap handler // - dpl: Descriptor Privilege Level - // the privilege level required for software to invoke // this interrupt/trap gate explicitly using an int instruction. #define SETGATE(gate, istrap, sel, off, d) \ 7 SETGATE(idt[T_SYSCALL], 1, SEG_KCODE<<3, vectors[T_SYSCALL], DPL_USER);
write syscall in xv6: interrupt table setup ... meaning: run in kernel mode (yes, code segments specifjes more than that — nothing we care about) vectors[T_SYSCALL] — OS function for processor to run set to pointer to assembly function vector64 trap returns to alltraps alltraps restores registers from tf , then returns to user-mode vector64: pushl $0 pushl $64 jmp alltraps vectors.S (otherwise: triggers fault like privileged instruction) alltraps: ... call trap ... iret trapasm.S void { ... trap.c set it to use the kernel “code segment” be callable from user mode via int instruction ... // Set up a normal interrupt/trap gate descriptor. lidt(idt, sizeof (idt)); ... SETGATE(idt[T_SYSCALL], 1, SEG_KCODE<<3, vectors[T_SYSCALL], DPL_USER); ... trap.c (run on boot) lidt — function (in x86.h) wrapping lidt instruction sets the interrupt descriptor table table of handler functions for each interrupt type (from mmu.h): // - istrap: 1 for a trap gate, 0 for an interrupt gate. set the T_SYSCALL (= 0x40 ) interrupt to // interrupt gate clears FL_IF, trap gate leaves FL_IF alone // - sel: Code segment selector for interrupt/trap handler // - off: Offset in code segment for interrupt/trap handler // - dpl: Descriptor Privilege Level - // the privilege level required for software to invoke // this interrupt/trap gate explicitly using an int instruction. #define SETGATE(gate, istrap, sel, off, d) \ 7 trap( struct trapframe *tf)
interrupt type, application registers, … write syscall in xv6: the trap function } what operation to do for program uses myproc()->tf to determine syscall() — actual implementations much more on this later in semester represents currently running process myproc() — pseudo-global variable example: tf >eax = old value of eax struct trapframe — set by assembly trap.c ... void } return ; exit(); syscall(); exit(); { 8 trap( struct trapframe *tf) if (tf − >trapno == T_SYSCALL){ if (myproc() − >killed) myproc() − >tf = tf; if (myproc() − >killed)
write syscall in xv6: the trap function void what operation to do for program uses myproc()->tf to determine syscall() — actual implementations much more on this later in semester represents currently running process myproc() — pseudo-global variable struct trapframe — set by assembly trap.c } ... } return ; exit(); syscall(); exit(); { 8 trap( struct trapframe *tf) interrupt type, application registers, … if (tf − >trapno == T_SYSCALL){ example: tf − >eax = old value of eax if (myproc() − >killed) myproc() − >tf = tf; if (myproc() − >killed)
interrupt type, application registers, … write syscall in xv6: the trap function } what operation to do for program uses myproc()->tf to determine syscall() — actual implementations much more on this later in semester represents currently running process myproc() — pseudo-global variable example: tf >eax = old value of eax struct trapframe — set by assembly trap.c ... void } return ; exit(); syscall(); exit(); { 8 trap( struct trapframe *tf) if (tf − >trapno == T_SYSCALL){ if (myproc() − >killed) myproc() − >tf = tf; if (myproc() − >killed)
interrupt type, application registers, … write syscall in xv6: the trap function } what operation to do for program uses myproc()->tf to determine syscall() — actual implementations much more on this later in semester represents currently running process myproc() — pseudo-global variable example: tf >eax = old value of eax struct trapframe — set by assembly trap.c ... void } return ; exit(); syscall(); exit(); { 8 trap( struct trapframe *tf) if (tf − >trapno == T_SYSCALL){ if (myproc() − >killed) myproc() − >tf = tf; if (myproc() − >killed)
write syscall in xv6: the syscall function } else { copies tf >eax into %eax ) (assembly code this returns to result assigned to eax store result in user’s eax register call sys_…function from table (if system call number in range) ‘ [number] value ’: syscalls[number] = value array of functions — one for syscall syscall.c ... if (num > 0 && num < NELEM(syscalls) && syscalls[num]) { static int (*syscalls[])( void ) = { ... { syscall( void ) void ... }; ... sys_write, [SYS_write] ... 9 num = curproc − >tf − >eax; curproc − >tf − >eax = syscalls[num]();
write syscall in xv6: the syscall function } else { copies tf >eax into %eax ) (assembly code this returns to result assigned to eax store result in user’s eax register call sys_…function from table (if system call number in range) ‘ [number] value ’: syscalls[number] = value array of functions — one for syscall syscall.c ... if (num > 0 && num < NELEM(syscalls) && syscalls[num]) { static int (*syscalls[])( void ) = { ... { syscall( void ) void ... }; ... sys_write, [SYS_write] ... 9 num = curproc − >tf − >eax; curproc − >tf − >eax = syscalls[num]();
write syscall in xv6: the syscall function } else { copies tf >eax into %eax ) (assembly code this returns to result assigned to eax store result in user’s eax register call sys_…function from table (if system call number in range) ‘ [number] value ’: syscalls[number] = value array of functions — one for syscall syscall.c ... if (num > 0 && num < NELEM(syscalls) && syscalls[num]) { static int (*syscalls[])( void ) = { ... { syscall( void ) void ... }; ... sys_write, [SYS_write] ... 9 num = curproc − >tf − >eax; curproc − >tf − >eax = syscalls[num]();
write syscall in xv6: the syscall function static int (*syscalls[])( void ) = { (assembly code this returns to result assigned to eax store result in user’s eax register call sys_…function from table (if system call number in range) ‘ [number] value ’: syscalls[number] = value array of functions — one for syscall syscall.c ... } else { if (num > 0 && num < NELEM(syscalls) && syscalls[num]) { ... { syscall( void ) void ... }; ... sys_write, [SYS_write] ... 9 copies tf − >eax into %eax ) num = curproc − >tf − >eax; curproc − >tf − >eax = syscalls[num]();
write syscall in xv6: sys_write } (the terminal counts as a fjle) actual internal function that implements writing to a fjle (note: 32-bit x86 calling convention puts all args on stack) (more on this later) returns -1 on error (e.g. stack pointer invalid) utility functions that read arguments from user’s stack sysfjle.c return filewrite(f, p, n); int if (argfd(0, 0, &f) < 0 || argint(2, &n) < 0 || argptr(1, &p, n) < 0) int n; { sys_write( void ) 10 struct file *f; char *p; return − 1;
write syscall in xv6: sys_write } (the terminal counts as a fjle) actual internal function that implements writing to a fjle (note: 32-bit x86 calling convention puts all args on stack) (more on this later) returns -1 on error (e.g. stack pointer invalid) utility functions that read arguments from user’s stack sysfjle.c return filewrite(f, p, n); int if (argfd(0, 0, &f) < 0 || argint(2, &n) < 0 || argptr(1, &p, n) < 0) int n; { sys_write( void ) 10 struct file *f; char *p; return − 1;
write syscall in xv6: sys_write } (the terminal counts as a fjle) actual internal function that implements writing to a fjle (note: 32-bit x86 calling convention puts all args on stack) (more on this later) returns -1 on error (e.g. stack pointer invalid) utility functions that read arguments from user’s stack sysfjle.c return filewrite(f, p, n); int if (argfd(0, 0, &f) < 0 || argint(2, &n) < 0 || argptr(1, &p, n) < 0) int n; { sys_write( void ) 10 struct file *f; char *p; return − 1;
write syscall in xv6: interrupt table setup ... meaning: run in kernel mode (yes, code segments specifjes more than that — nothing we care about) vectors[T_SYSCALL] — OS function for processor to run set to pointer to assembly function vector64 trap returns to alltraps alltraps restores registers from tf , then returns to user-mode vector64: pushl $0 pushl $64 jmp alltraps vectors.S (otherwise: triggers fault like privileged instruction) alltraps: ... call trap ... iret trapasm.S void { ... trap.c set it to use the kernel “code segment” be callable from user mode via int instruction ... // Set up a normal interrupt/trap gate descriptor. lidt(idt, sizeof (idt)); ... SETGATE(idt[T_SYSCALL], 1, SEG_KCODE<<3, vectors[T_SYSCALL], DPL_USER); ... trap.c (run on boot) lidt — function (in x86.h) wrapping lidt instruction sets the interrupt descriptor table table of handler functions for each interrupt type (from mmu.h): // - istrap: 1 for a trap gate, 0 for an interrupt gate. set the T_SYSCALL (= 0x40 ) interrupt to // interrupt gate clears FL_IF, trap gate leaves FL_IF alone // - sel: Code segment selector for interrupt/trap handler // - off: Offset in code segment for interrupt/trap handler // - dpl: Descriptor Privilege Level - // the privilege level required for software to invoke // this interrupt/trap gate explicitly using an int instruction. #define SETGATE(gate, istrap, sel, off, d) \ 11 trap( struct trapframe *tf)
write syscall in xv6: summary write function — syscall wrapper uses int $0x40 interrupt table entry setup points to assembly function vector64 (and switches to kernel stack) …which calls trap() with trap number set to 64 ( T_SYSCALL ) (after saving all registers into struct trapframe ) …which checks trap number, then calls syscall() …which checks syscall number (from eax) …and uses it to call sys_write …which reads arguments from the stack and does the write …then registers restored, return to user space 13
write syscall in xv6: summary write function — syscall wrapper uses int $0x40 interrupt table entry setup points to assembly function vector64 (and switches to kernel stack) …which calls trap() with trap number set to 64 ( T_SYSCALL ) (after saving all registers into struct trapframe ) …which checks trap number, then calls syscall() …which checks syscall number (from eax) …and uses it to call sys_write …then registers restored, return to user space 14 …which reads arguments from the stack and does the write
recall: address translation Program B code = kernel-mode only trigger error real memory … OS data Program B data Program A data Program A code Program A (set by OS) mapping (set by OS) mapping addresses Program B addresses 16
xv6 memory layout larger addresses are for kernel is active on a processor as part of switching which processes change which one exceptions use one kernel stack per process in special “task state selector” location of stack stored when execption/interrupt/…happens processor switches stacks kernel stack allocated here smaller addresses are for applications (accessible in kernel mode only ) 17 Virtual 4 Gig Device memory RW- 0xFE000000 Unused if less than 2 Gig of physical memory Free memory RW-- end Kernel data RW- Kernel text R-- + 0x100000 Physical RW- 4 Gig KERNBASE Memory-mapped 32-bit I/O devices PHYSTOP Unused if less than 2 Gig of physical memory Program data & heap At most 2 Gig Extended memory PAGESIZE User stack RWU 0x100000 User data RWU I/O space 640K User text RWU Base memory 0 0
xv6 memory layout larger addresses are for kernel is active on a processor as part of switching which processes change which one exceptions use one kernel stack per process in special “task state selector” location of stack stored when execption/interrupt/…happens processor switches stacks kernel stack allocated here smaller addresses are for applications (accessible in kernel mode only ) 17 Virtual 4 Gig Device memory RW- 0xFE000000 Unused if less than 2 Gig of physical memory Free memory RW-- end Kernel data RW- Kernel text R-- + 0x100000 Physical RW- 4 Gig KERNBASE Memory-mapped 32-bit I/O devices PHYSTOP Unused if less than 2 Gig of physical memory Program data & heap At most 2 Gig Extended memory PAGESIZE User stack RWU 0x100000 User data RWU I/O space 640K User text RWU Base memory 0 0
xv6 memory layout larger addresses are for kernel is active on a processor as part of switching which processes change which one exceptions use one kernel stack per process in special “task state selector” location of stack stored when execption/interrupt/…happens processor switches stacks kernel stack allocated here smaller addresses are for applications (accessible in kernel mode only ) 17 Virtual 4 Gig Device memory RW- 0xFE000000 Unused if less than 2 Gig of physical memory Free memory RW-- end Kernel data RW- Kernel text R-- + 0x100000 Physical RW- 4 Gig KERNBASE Memory-mapped 32-bit I/O devices PHYSTOP Unused if less than 2 Gig of physical memory Program data & heap At most 2 Gig Extended memory PAGESIZE User stack RWU 0x100000 User data RWU I/O space 640K User text RWU Base memory 0 0
xv6 memory layout larger addresses are for kernel is active on a processor as part of switching which processes change which one exceptions use one kernel stack per process in special “task state selector” location of stack stored when execption/interrupt/…happens processor switches stacks kernel stack allocated here smaller addresses are for applications (accessible in kernel mode only ) 17 Virtual 4 Gig Device memory RW- 0xFE000000 Unused if less than 2 Gig of physical memory Free memory RW-- end Kernel data RW- Kernel text R-- + 0x100000 Physical RW- 4 Gig KERNBASE Memory-mapped 32-bit I/O devices PHYSTOP Unused if less than 2 Gig of physical memory Program data & heap At most 2 Gig Extended memory PAGESIZE User stack RWU 0x100000 User data RWU I/O space 640K User text RWU Base memory 0 0
write syscall in xv6: summary write function — syscall wrapper uses int $0x40 interrupt table entry setup points to assembly function vector64 …which calls trap() with trap number set to 64 ( T_SYSCALL ) (after saving all registers into struct trapframe ) …which checks trap number, then calls syscall() …which checks syscall number (from eax) …and uses it to call sys_write …which reads arguments from the stack and does the write …then registers restored, return to user space 18 (and switches to kernel stack)
non-system call exceptions xv6: there are traps other than system calls trap() timer interrupt — every hardware “tick” action: schedule new process faults — e.g. access invalid memory I/O — handle I/O 19
non-system call exceptions xv6: there are traps other than system calls trap() timer interrupt — every hardware “tick” action: schedule new process faults — e.g. access invalid memory I/O — handle I/O 20
xv6: timer interrupt void process was just about to stop running check state == RUNNING in case myproc() retrieves running process acquire/release — related to synchronization (later) (needed for all interrupts from ‘external’ devices) lapiceoi — tell hardware we have handled this interrupt wakeup — handle processes waiting a certain amount of time yield — maybe context switch } ... yield(); ... // Force process to give up CPU on clock tick. ... break ; lapiceoi(); } release(&tickslock); wakeup(&ticks); ticks++; acquire(&tickslock); if (cpuid() == 0){ case T_IRQ0 + IRQ_TIMER: { 21 trap( struct trapframe *tf) switch (tf − >trapno){ if (myproc() && myproc() − >state == RUNNING && tf − >trapno == T_IRQ0+IRQ_TIMER)
xv6: timer interrupt void process was just about to stop running check state == RUNNING in case myproc() retrieves running process acquire/release — related to synchronization (later) (needed for all interrupts from ‘external’ devices) lapiceoi — tell hardware we have handled this interrupt wakeup — handle processes waiting a certain amount of time yield — maybe context switch } ... yield(); ... // Force process to give up CPU on clock tick. ... break ; lapiceoi(); } release(&tickslock); wakeup(&ticks); ticks++; acquire(&tickslock); if (cpuid() == 0){ case T_IRQ0 + IRQ_TIMER: { 21 trap( struct trapframe *tf) switch (tf − >trapno){ if (myproc() && myproc() − >state == RUNNING && tf − >trapno == T_IRQ0+IRQ_TIMER)
xv6: timer interrupt void process was just about to stop running check state == RUNNING in case myproc() retrieves running process acquire/release — related to synchronization (later) (needed for all interrupts from ‘external’ devices) lapiceoi — tell hardware we have handled this interrupt wakeup — handle processes waiting a certain amount of time yield — maybe context switch } ... yield(); ... // Force process to give up CPU on clock tick. ... break ; lapiceoi(); } release(&tickslock); wakeup(&ticks); ticks++; acquire(&tickslock); if (cpuid() == 0){ case T_IRQ0 + IRQ_TIMER: { 21 trap( struct trapframe *tf) switch (tf − >trapno){ if (myproc() && myproc() − >state == RUNNING && tf − >trapno == T_IRQ0+IRQ_TIMER)
xv6: timer interrupt void process was just about to stop running check state == RUNNING in case myproc() retrieves running process acquire/release — related to synchronization (later) (needed for all interrupts from ‘external’ devices) lapiceoi — tell hardware we have handled this interrupt wakeup — handle processes waiting a certain amount of time yield — maybe context switch } ... yield(); ... // Force process to give up CPU on clock tick. ... break ; lapiceoi(); } release(&tickslock); wakeup(&ticks); ticks++; acquire(&tickslock); if (cpuid() == 0){ case T_IRQ0 + IRQ_TIMER: { 21 trap( struct trapframe *tf) switch (tf − >trapno){ if (myproc() && myproc() − >state == RUNNING && tf − >trapno == T_IRQ0+IRQ_TIMER)
xv6: timer interrupt void process was just about to stop running check state == RUNNING in case myproc() retrieves running process acquire/release — related to synchronization (later) (needed for all interrupts from ‘external’ devices) lapiceoi — tell hardware we have handled this interrupt wakeup — handle processes waiting a certain amount of time yield — maybe context switch } ... yield(); ... // Force process to give up CPU on clock tick. ... break ; lapiceoi(); } release(&tickslock); wakeup(&ticks); ticks++; acquire(&tickslock); if (cpuid() == 0){ case T_IRQ0 + IRQ_TIMER: { 21 trap( struct trapframe *tf) switch (tf − >trapno){ if (myproc() && myproc() − >state == RUNNING && tf − >trapno == T_IRQ0+IRQ_TIMER)
xv6: timer interrupt void process was just about to stop running check state == RUNNING in case myproc() retrieves running process acquire/release — related to synchronization (later) (needed for all interrupts from ‘external’ devices) lapiceoi — tell hardware we have handled this interrupt wakeup — handle processes waiting a certain amount of time yield — maybe context switch } ... yield(); ... // Force process to give up CPU on clock tick. ... break ; lapiceoi(); } release(&tickslock); wakeup(&ticks); ticks++; acquire(&tickslock); if (cpuid() == 0){ case T_IRQ0 + IRQ_TIMER: { 21 trap( struct trapframe *tf) switch (tf − >trapno){ if (myproc() && myproc() − >state == RUNNING && tf − >trapno == T_IRQ0+IRQ_TIMER)
non-system call exceptions xv6: there are traps other than system calls trap() timer interrupt — every hardware “tick” action: schedule new process faults — e.g. access invalid memory I/O — handle I/O 22
xv6: faults 0x%x on void cpu %d " "eip addr err 0x%x--kill proc\n", } } unknown exception print message and kill running program assume it screwed up %d 23 ... %s: ... cprintf("pid ... %d default : { trap %d trap( struct trapframe *tf) switch (tf − >trapno) { ␣ ␣ ␣ ␣ ␣ ␣ ␣ ␣ ␣ ␣ ␣ ␣ ␣ ␣ myproc() − >pid, myproc() − >name, tf − >trapno, tf − >err, cpuid(), tf − >eip, rcr2()); myproc() − >killed = 1;
non-system call exceptions xv6: there are traps other than system calls trap() timer interrupt — every hardware “tick” action: schedule new process faults — e.g. access invalid memory I/O — handle I/O 24
xv6: I/O kbdintr(); uart = serial port (external terminal) kbd = keyboard ide = disk interface break ; lapiceoi(); uartintr(); case T_IRQ0 + IRQ_COM1: break ; lapiceoi(); case T_IRQ0 + IRQ_KBD: void ... break ; lapiceoi(); ideintr(); case T_IRQ0 + IRQ_IDE: ... ... { 25 trap( struct trapframe *tf) switch (tf − >trapno) {
xv6: keyboard I/O void kbdintr( void ) { consoleintr(kbdgetc); } ... void consoleintr(...) { ... wakeup(&input.r); ... } fjnds process waiting on consle make it run soon 26
xv6: keyboard I/O void kbdintr( void ) { consoleintr(kbdgetc); } ... void consoleintr(...) { ... wakeup(&input.r); ... } fjnds process waiting on consle make it run soon 26
timing nothing long times[NUM_TIMINGS]; int main( void ) { for ( int i = 0; i < N; ++i) { long start, end; /* do nothing */ end = get_time(); } output_timings(times); } 27 start = get_time(); times[i] = end - start; same instructions — same difgerence each time?
doing nothing on a busy system 28 time for empty loop body 10 8 10 7 10 6 time (ns) 10 5 10 4 10 3 10 2 10 1 0 200000 400000 600000 800000 1000000 sample #
doing nothing on a busy system 29 time for empty loop body 10 8 10 7 10 6 time (ns) 10 5 10 4 10 3 10 2 10 1 0 200000 400000 600000 800000 1000000 sample #
time multiplexing // whatever get_time does ... subq %rbp, %rax // whatever get_time does call get_time million cycle delay movq %rax, %rbp call get_time loop.exe ... time CPU: ssh.exe loop.exe firefox.exe ssh.exe 30
time multiplexing // whatever get_time does ... subq %rbp, %rax // whatever get_time does call get_time million cycle delay movq %rax, %rbp call get_time loop.exe ... time CPU: ssh.exe loop.exe firefox.exe ssh.exe 30
time multiplexing // whatever get_time does ... subq %rbp, %rax // whatever get_time does call get_time million cycle delay movq %rax, %rbp call get_time loop.exe ... time CPU: ssh.exe loop.exe firefox.exe ssh.exe 30
time multiplexing really loop.exe ssh.exe firefox.exe loop.exe ssh.exe = operating system exception happens return from exception 31
time multiplexing really loop.exe ssh.exe firefox.exe loop.exe ssh.exe = operating system exception happens return from exception 31
OS and time multiplexing starts running instead of normal program saves old program counter, registers somewhere sets new registers, jumps to new program counter saved information called context 32 mechanism for this: exceptions (later) called context switch
context all registers values condition codes program counter address space = page table base pointer 33 %rax %rbx , …, %rsp , …
contexts (A running) Process B memory: in Memory … … %rcxPC %rbxZF %raxSF OS memory: code, stack, etc. code, stack, etc. %rax Process A memory: in CPU PC ZF SF … %rsp %rcx %rbx 34
contexts (B running) OS memory: on B’s kernel stack into “trapframe” exception handler xv6: saved by in Memory … … %rcxPC %rbxZF %raxSF code, stack, etc. %rax Process B memory: code, stack, etc. Process A memory: in CPU PC ZF SF … %rsp %rcx %rbx 35
contexts (B running) OS memory: on B’s kernel stack into “trapframe” exception handler xv6: saved by in Memory … … %rcxPC %rbxZF %raxSF code, stack, etc. %rax Process B memory: code, stack, etc. Process A memory: in CPU PC ZF SF … %rsp %rcx %rbx 35
context switch in xv6 xv6 context switch has two parts switching threads switching user address spaces + kernel stack to use for exception kernel part of address space same for every process (simplifjes address space switching) each process has its own kernel stack 36
context switch in xv6 xv6 context switch has two parts switching threads kernel part of address space same for every process (simplifjes address space switching) each process has its own kernel stack 36 switching user address spaces + kernel stack to use for exception
context switch in xv6 xv6 context switch has two parts switching threads kernel part of address space same for every process (simplifjes address space switching) each process has its own kernel stack 36 switching user address spaces + kernel stack to use for exception
thread switching yes, it looks like we’re missing switch to context new set old to point to it allocate space for context on top of stack function to switch contexts eip = saved program counter some registers we need… structure to save context in struct context { } uint eip; uint ebp; uint ebx; uint esi; uint edi; 37 void swtch( struct context **old, struct context * new );
thread switching yes, it looks like we’re missing switch to context new set old to point to it allocate space for context on top of stack function to switch contexts eip = saved program counter some registers we need… structure to save context in struct context { } uint eip; uint ebp; uint ebx; uint esi; uint edi; 37 void swtch( struct context **old, struct context * new );
thread switching yes, it looks like we’re missing switch to context new set old to point to it allocate space for context on top of stack function to switch contexts eip = saved program counter some registers we need… structure to save context in struct context { } uint eip; uint ebp; uint ebx; uint esi; uint edi; 37 void swtch( struct context **old, struct context * new );
thread switching yes, it looks like we’re missing switch to context new set old to point to it allocate space for context on top of stack function to switch contexts eip = saved program counter some registers we need… structure to save context in struct context { } uint eip; uint ebp; uint ebx; uint esi; uint edi; 37 void swtch( struct context **old, struct context * new );
thread switching in xv6: C in thread A: ... // (1) ... // (4) in thread B: ... ... // (3) ... 38 /* switch from A to B */ swtch(&(a − >context), b − >context); /* returns to (2) */ ... // (2) [just after another swtch() call?] /* later on switch back to A */ swtch(&(b − >context), a − >context) /* returns to (4) */
thread switching in xv6: C in thread A: ... // (1) ... // (4) in thread B: ... ... // (3) ... 38 /* switch from A to B */ swtch(&(a − >context), b − >context); /* returns to (2) */ ... // (2) [just after another swtch() call?] /* later on switch back to A */ swtch(&(b − >context), a − >context) /* returns to (4) */
thread switching in xv6: C in thread A: ... // (1) ... // (4) in thread B: ... // (2) [just after another swtch() call?] ... ... // (3) ... 38 /* switch from A to B */ swtch(&(a − >context), b − >context); /* returns to (2) */ /* later on switch back to A */ swtch(&(b − >context), a − >context) /* returns to (4) */
thread switching in xv6: C in thread A: ... // (1) ... // (4) in thread B: ... ... // (3) ... 38 /* switch from A to B */ swtch(&(a − >context), b − >context); /* returns to (2) */ ... // (2) [just after another swtch() call?] /* later on switch back to A */ swtch(&(b − >context), a − >context) /* returns to (4) */
struct context **from_context struct context *to_context thread switching in xv6: assembly = where to fjnd new context context stored on thread’s stack context address = top of stack saved: ebp, ebx, esi, edi what about other parts of context? eax, ecx, …: saved by swtch’s caller esp: same as address of context program counter: set by call of swtch save stack pointer to fjrst argument (stack pointer now has all info) restore stack pointer from second argument restore program counter (and other saved registers) from new context = where to save current context two arguments: .globl swtch pushl %edi swtch: movl 4(%esp), %eax movl 8(%esp), %edx # Save old callee-save registers pushl %ebp pushl %ebx pushl %esi # Switch stacks ret movl %esp, (%eax) movl %edx, %esp # Load new callee-save registers popl %edi popl %esi popl %ebx popl %ebp 39
thread switching in xv6: assembly esp: same as address of context = where to fjnd new context context stored on thread’s stack context address = top of stack saved: ebp, ebx, esi, edi what about other parts of context? eax, ecx, …: saved by swtch’s caller program counter: set by call of swtch .globl swtch save stack pointer to fjrst argument (stack pointer now has all info) restore stack pointer from second argument restore program counter (and other saved registers) from new context = where to save current context two arguments: ret pushl %edi swtch: movl 4(%esp), %eax movl 8(%esp), %edx # Save old callee-save registers pushl %ebp pushl %ebx pushl %esi # Switch stacks popl %ebp movl %esp, (%eax) movl %edx, %esp # Load new callee-save registers popl %edi popl %esi popl %ebx 39 struct context **from_context struct context *to_context
struct context **from_context struct context *to_context thread switching in xv6: assembly = where to fjnd new context context stored on thread’s stack context address = top of stack saved: ebp, ebx, esi, edi what about other parts of context? eax, ecx, …: saved by swtch’s caller esp: same as address of context program counter: set by call of swtch save stack pointer to fjrst argument (stack pointer now has all info) restore stack pointer from second argument restore program counter (and other saved registers) from new context = where to save current context two arguments: .globl swtch pushl %edi swtch: movl 4(%esp), %eax movl 8(%esp), %edx # Save old callee-save registers pushl %ebp pushl %ebx pushl %esi # Switch stacks ret movl %esp, (%eax) movl %edx, %esp # Load new callee-save registers popl %edi popl %esi popl %ebx popl %ebp 39
struct context **from_context struct context *to_context thread switching in xv6: assembly = where to fjnd new context context stored on thread’s stack context address = top of stack saved: ebp, ebx, esi, edi what about other parts of context? eax, ecx, …: saved by swtch’s caller esp: same as address of context program counter: set by call of swtch save stack pointer to fjrst argument (stack pointer now has all info) restore stack pointer from second argument restore program counter (and other saved registers) from new context = where to save current context two arguments: .globl swtch pushl %edi swtch: movl 4(%esp), %eax movl 8(%esp), %edx # Save old callee-save registers pushl %ebp pushl %ebx pushl %esi # Switch stacks ret movl %esp, (%eax) movl %edx, %esp # Load new callee-save registers popl %edi popl %esi popl %ebx popl %ebp 39
struct context **from_context struct context *to_context thread switching in xv6: assembly = where to fjnd new context context stored on thread’s stack context address = top of stack saved: ebp, ebx, esi, edi what about other parts of context? eax, ecx, …: saved by swtch’s caller esp: same as address of context program counter: set by call of swtch save stack pointer to fjrst argument (stack pointer now has all info) restore stack pointer from second argument restore program counter (and other saved registers) from new context = where to save current context two arguments: .globl swtch pushl %edi swtch: movl 4(%esp), %eax movl 8(%esp), %edx # Save old callee-save registers pushl %ebp pushl %ebx pushl %esi # Switch stacks ret movl %esp, (%eax) movl %edx, %esp # Load new callee-save registers popl %edi popl %esi popl %ebx popl %ebp 39
struct context **from_context struct context *to_context thread switching in xv6: assembly = where to fjnd new context context stored on thread’s stack context address = top of stack saved: ebp, ebx, esi, edi what about other parts of context? eax, ecx, …: saved by swtch’s caller esp: same as address of context program counter: set by call of swtch save stack pointer to fjrst argument (stack pointer now has all info) restore stack pointer from second argument restore program counter (and other saved registers) from new context = where to save current context two arguments: .globl swtch pushl %edi swtch: movl 4(%esp), %eax movl 8(%esp), %edx # Save old callee-save registers pushl %ebp pushl %ebx pushl %esi # Switch stacks ret movl %esp, (%eax) movl %edx, %esp # Load new callee-save registers popl %edi popl %esi popl %ebx popl %ebp 39
juggling stacks %esp swtch return addr. saved ebp saved ebx saved esi saved edi to stack %esp %esp %esp %esp %esp caller-saved registers fjrst instruction executed by new thread bottom of new kernel stack saved user regs … from stack saved user regs … to stack swtch arguments from stack .globl swtch movl %edx, %esp swtch: movl 4(%esp), %eax movl 8(%esp), %edx # Save old callee-save registers pushl %ebp pushl %ebx pushl %esi pushl %edi # Switch stacks movl %esp, (%eax) # Load new callee-save registers saved edi popl %edi popl %esi popl %ebx popl %ebp ret caller-saved registers swtch arguments swtch return addr. saved ebp saved ebx saved esi 40
juggling stacks %esp swtch arguments swtch return addr. saved ebp saved ebx saved esi saved edi to stack %esp %esp %esp %esp .globl swtch fjrst instruction executed by new thread bottom of new kernel stack saved user regs … from stack saved user regs … to stack caller-saved registers from stack saved edi movl %edx, %esp swtch: movl 4(%esp), %eax movl 8(%esp), %edx # Save old callee-save registers pushl %ebp pushl %ebx pushl %esi pushl %edi # Switch stacks movl %esp, (%eax) # Load new callee-save registers saved esi popl %edi popl %esi popl %ebx popl %ebp ret caller-saved registers swtch arguments swtch return addr. saved ebp saved ebx 40 %esp →
juggling stacks %esp swtch arguments swtch return addr. saved ebp saved ebx saved esi saved edi to stack %esp %esp %esp %esp .globl swtch fjrst instruction executed by new thread bottom of new kernel stack saved user regs … from stack saved user regs … to stack caller-saved registers from stack saved edi movl %edx, %esp swtch: movl 4(%esp), %eax movl 8(%esp), %edx # Save old callee-save registers pushl %ebp pushl %ebx pushl %esi pushl %edi # Switch stacks movl %esp, (%eax) # Load new callee-save registers saved esi popl %edi popl %esi popl %ebx popl %ebp ret caller-saved registers swtch arguments swtch return addr. saved ebp saved ebx 40 %esp →
juggling stacks %esp swtch arguments swtch return addr. saved ebp saved ebx saved esi saved edi to stack %esp %esp %esp %esp .globl swtch fjrst instruction executed by new thread bottom of new kernel stack saved user regs … from stack saved user regs … to stack caller-saved registers from stack saved edi movl %edx, %esp swtch: movl 4(%esp), %eax movl 8(%esp), %edx # Save old callee-save registers pushl %ebp pushl %ebx pushl %esi pushl %edi # Switch stacks movl %esp, (%eax) # Load new callee-save registers saved esi popl %edi popl %esi popl %ebx popl %ebp ret caller-saved registers swtch arguments swtch return addr. saved ebp saved ebx 40 ← %esp
juggling stacks %esp swtch arguments swtch return addr. saved ebp saved ebx saved esi saved edi to stack %esp %esp %esp %esp .globl swtch fjrst instruction executed by new thread bottom of new kernel stack saved user regs … from stack saved user regs … to stack caller-saved registers from stack saved edi movl %edx, %esp swtch: movl 4(%esp), %eax movl 8(%esp), %edx # Save old callee-save registers pushl %ebp pushl %ebx pushl %esi pushl %edi # Switch stacks movl %esp, (%eax) # Load new callee-save registers saved esi popl %edi popl %esi popl %ebx popl %ebp ret caller-saved registers swtch arguments swtch return addr. saved ebp saved ebx 40 ← %esp
juggling stacks %esp swtch arguments swtch return addr. saved ebp saved ebx saved esi saved edi to stack %esp %esp %esp %esp .globl swtch fjrst instruction executed by new thread bottom of new kernel stack saved user regs … from stack saved user regs … to stack caller-saved registers from stack saved edi movl %edx, %esp swtch: movl 4(%esp), %eax movl 8(%esp), %edx # Save old callee-save registers pushl %ebp pushl %ebx pushl %esi pushl %edi # Switch stacks movl %esp, (%eax) 40 saved esi # Load new callee-save registers popl %edi popl %esi popl %ebx popl %ebp ret caller-saved registers swtch arguments swtch return addr. saved ebp saved ebx ← %esp
fjrst call to swtch? one thread calls swtch and …return from another thread’s call to swtch 41 what about switching to a new thread?
creating a new thread (for swtch) ‘trapframe’ (saved userspace registers as if there was an interrupt) return address = trapret (for forkret) return address = forkret saved kernel registers static struct proc* (for swtch) new kernel stack assembly code to return to user mode same code as for syscall returns new stack says: this thread is in middle of calling swtch in the middle of a system call (for the kernel only) p is new struct proc 42 // Set up new context to start executing at forkret, allocproc( void ) { ... // Leave room for trap frame. // which returns to trapret. ... *(uint*)sp = (uint)trapret; struct proc ≈ process p − >kstack is its new stack sp = p − >kstack + KSTACKSIZE; sp − = sizeof *p − >tf; p − >tf = ( struct trapframe*)sp; sp − = 4; sp − = sizeof *p − >context; p − >context = ( struct context*)sp; memset(p − >context, 0, sizeof *p − >context); p − >context − >eip = (uint)forkret;
creating a new thread (for swtch) (for the kernel only) ‘trapframe’ (saved userspace registers as if there was an interrupt) return address = trapret (for forkret) return address = forkret saved kernel registers p is new struct proc (for swtch) new kernel stack assembly code to return to user mode same code as for syscall returns new stack says: this thread is in middle of calling swtch in the middle of a system call p >kstack is its new stack process static struct proc* struct proc allocproc( void ) { ... // Leave room for trap frame. // Set up new context to start executing at forkret, // which returns to trapret. *(uint*)sp = (uint)trapret; ... 42 sp = p − >kstack + KSTACKSIZE; sp − = sizeof *p − >tf; p − >tf = ( struct trapframe*)sp; sp − = 4; sp − = sizeof *p − >context; p − >context = ( struct context*)sp; memset(p − >context, 0, sizeof *p − >context); p − >context − >eip = (uint)forkret;
creating a new thread (for swtch) (for the kernel only) ‘trapframe’ (saved userspace registers as if there was an interrupt) return address = trapret (for forkret) return address = forkret saved kernel registers p is new struct proc (for swtch) new kernel stack assembly code to return to user mode same code as for syscall returns new stack says: this thread is in middle of calling swtch in the middle of a system call p >kstack is its new stack process static struct proc* struct proc allocproc( void ) { ... // Leave room for trap frame. // Set up new context to start executing at forkret, // which returns to trapret. *(uint*)sp = (uint)trapret; ... 42 sp = p − >kstack + KSTACKSIZE; sp − = sizeof *p − >tf; p − >tf = ( struct trapframe*)sp; sp − = 4; sp − = sizeof *p − >context; p − >context = ( struct context*)sp; memset(p − >context, 0, sizeof *p − >context); p − >context − >eip = (uint)forkret;
creating a new thread (for swtch) (for the kernel only) ‘trapframe’ (saved userspace registers as if there was an interrupt) return address = trapret (for forkret) return address = forkret saved kernel registers p is new struct proc (for swtch) new kernel stack assembly code to return to user mode same code as for syscall returns new stack says: this thread is in middle of calling swtch in the middle of a system call p >kstack is its new stack process static struct proc* struct proc allocproc( void ) { ... // Leave room for trap frame. // Set up new context to start executing at forkret, // which returns to trapret. *(uint*)sp = (uint)trapret; ... 42 sp = p − >kstack + KSTACKSIZE; sp − = sizeof *p − >tf; p − >tf = ( struct trapframe*)sp; sp − = 4; sp − = sizeof *p − >context; p − >context = ( struct context*)sp; memset(p − >context, 0, sizeof *p − >context); p − >context − >eip = (uint)forkret;
creating a new thread (for swtch) (for the kernel only) ‘trapframe’ (saved userspace registers as if there was an interrupt) return address = trapret (for forkret) return address = forkret saved kernel registers p is new struct proc (for swtch) new kernel stack assembly code to return to user mode same code as for syscall returns new stack says: this thread is in middle of calling swtch in the middle of a system call p >kstack is its new stack process static struct proc* struct proc allocproc( void ) { ... // Leave room for trap frame. // Set up new context to start executing at forkret, // which returns to trapret. *(uint*)sp = (uint)trapret; ... 42 sp = p − >kstack + KSTACKSIZE; sp − = sizeof *p − >tf; p − >tf = ( struct trapframe*)sp; sp − = 4; sp − = sizeof *p − >context; p − >context = ( struct context*)sp; memset(p − >context, 0, sizeof *p − >context); p − >context − >eip = (uint)forkret;
creating a new thread (for swtch) (for the kernel only) ‘trapframe’ (saved userspace registers as if there was an interrupt) return address = trapret (for forkret) return address = forkret saved kernel registers p is new struct proc (for swtch) new kernel stack assembly code to return to user mode same code as for syscall returns new stack says: this thread is in middle of calling swtch in the middle of a system call p >kstack is its new stack process static struct proc* struct proc allocproc( void ) { ... // Leave room for trap frame. // Set up new context to start executing at forkret, // which returns to trapret. *(uint*)sp = (uint)trapret; ... 42 sp = p − >kstack + KSTACKSIZE; sp − = sizeof *p − >tf; p − >tf = ( struct trapframe*)sp; sp − = 4; sp − = sizeof *p − >context; p − >context = ( struct context*)sp; memset(p − >context, 0, sizeof *p − >context); p − >context − >eip = (uint)forkret;
juggling stacks %esp swtch arguments swtch return addr. saved ebp saved ebx saved esi saved edi to stack %esp %esp %esp %esp .globl swtch fjrst instruction executed by new thread bottom of new kernel stack saved user regs … from stack saved user regs … to stack caller-saved registers from stack saved edi saved esi swtch: movl 4(%esp), %eax movl 8(%esp), %edx # Save old callee-save registers pushl %ebp pushl %ebx pushl %esi pushl %edi # Switch stacks movl %esp, (%eax) movl %edx, %esp # Load new callee-save registers popl %edi popl %esi popl %ebx popl %ebp ret caller-saved registers swtch arguments swtch return addr. saved ebp saved ebx 43 ← %esp
kernel-space context switch summary swtch function saves registers on current kernel stack switches to new kernel stack and restores its registers initial setup — manually construct stack values 44
juggling stacks %esp swtch return addr. saved ebp saved ebx saved esi saved edi to stack %esp %esp %esp %esp %esp caller-saved registers fjrst instruction executed by new thread bottom of new kernel stack saved user regs … from stack saved user regs … to stack swtch arguments from stack .globl swtch movl %edx, %esp swtch: movl 4(%esp), %eax movl 8(%esp), %edx # Save old callee-save registers pushl %ebp pushl %ebx pushl %esi pushl %edi # Switch stacks movl %esp, (%eax) # Load new callee-save registers saved edi popl %edi popl %esi popl %ebx popl %ebp ret caller-saved registers swtch arguments swtch return addr. saved ebp saved ebx saved esi 45
the userspace part? user registers stored in ‘trapframe’ struct created on kernel stack when interrupt/trap happens restored before using iret to switch to user mode initial user registers created manually on stack (as if saved by system call) other code (not shown) handles setting address space 46
the userspace part? user registers stored in ‘trapframe’ struct created on kernel stack when interrupt/trap happens restored before using iret to switch to user mode initial user registers created manually on stack (as if saved by system call) other code (not shown) handles setting address space 46
exercise loop.exe’s eax stored? D. the kernel stack for the program switched to G. elsewhere C. the user stack of the program switched to F. a special register B. loop.exe’s kernel stack E. loop.exe’s heap A. loop.exe’s user stack when xv6 switches away from this program, where is the value of suppose xv6 is running this loop.exe : // goto start_loop jmp start_loop add $1, %eax start_loop: mov $0, %eax main: 47 // eax ← 0 // eax ← eax + 1
where things go in context switch ‘to’ kernel stack caller-saved registers swtch arguments swtch return addr. saved ebp saved ebx saved esi saved edi fjrst %esp value trap return addr. for ‘to’ process (argument to swtch ) main ’s return addr. main ’s vars … ‘to’ user stack %esp value after return-from-exception … saved user registers saved user registers saved edi trap return addr. … caller-saved registers swtch arguments swtch return addr. saved ebp saved ebx saved esi ‘from’ kernel stack just before exception last %esp value for ‘from’ process (saved by swtch ) main ’s return addr. main ’s vars … ‘from’ user stack %esp value 48
where things go in context switch ‘to’ kernel stack caller-saved registers swtch arguments swtch return addr. saved ebp saved ebx saved esi saved edi fjrst %esp value trap return addr. for ‘to’ process (argument to swtch ) main ’s return addr. main ’s vars … ‘to’ user stack %esp value after return-from-exception … saved user registers saved user registers saved edi trap return addr. … caller-saved registers swtch arguments swtch return addr. saved ebp saved ebx saved esi ‘from’ kernel stack just before exception last %esp value for ‘from’ process (saved by swtch ) main ’s return addr. main ’s vars … ‘from’ user stack %esp value 48
Recommend
More recommend