6.S081/6.828 2019 Lecture 7: Interrupts, System calls, and Exceptions the general topic: hardware wants attention now! software must set aside current work and respond why does hw want attention now? MMU cannot translate address User program divides by zero User program wants to execute kernel code (ecall) Network hardware wants to deliver a packet Timer hardware wants to deliver a "tick" kernel CPU-to-CPU communication, e.g. to flush TLB (IPI) these "traps" fall broadly speaking in 3 classes: Exceptions (page fault, divide by zero) System calls (ecall, intended exception) Interrupts (devices want attention) (Warning: terminology isn't used consistently) On RISC-V all three handled with same mechanism (and called traps) where do device interrupts come from? diagram: Fig 1 of doc/FU540-C000-v1.0.pdf in repo CPUs, CLINT, PLIC, devices Fig 3 has more detail for interrupts UART (universal asynchrnonous receiver/transmitter) the interrupt tells the kernel the device hardware wants attention the driver (in the kernel) knows how to tell the device to do things often the interrupt handler calls the relevant driver but other arrangements are possible (schedule a thread; poll) [diagram: top-half/bottom-half] case study: keyboard input $ ls how do the characters l and s show up on the console? the simulated keyboard sends a UART packet to board's UART when UART receives packet, it generates interrupt driver removes charter from UART driver echos character to console by sending characters from UART how does kernel know which device interrupted? each device has a unique source/IRQ (interrupt request) number defined by hardware platform UART0 is 10 on qemu (see kernel/memlayout.h) different on SiFive board RISC-V interrupt-related registers sie --- supervisor interrupt enabled register one bit to enable interrupts sstatus --- supervisor status register one bit per software interrupt, external interrupt, timer interrupt sip --- supervisor interrupt pending register scause --- supervisor cause register stvec --- supervisor trap vector register mdeleg --- machine delegate register let's look at how xv6 sets up the interrupt machinery main(): consoleinit(); uartinit() plicinit(); scheduler(); intr_on(); w_sie(r_sie() | SIE_SEIE | SIE_STIE | SIE_SSIE); w_sstatus(r_sstatus() | SSTATUS_SIE); $ shell is in read system call to get input from console usertrap() for system call w_stvec((uint64)kernelvec); consoleread() sleep() scheduler() intr_on() $ l user hits l, which causes UART interrupt PLIC passes interrupt on to processor the processor performs the following steps on interrupt: - If the trap is a device interrupt, and the SIE bit is clear, don't do any of the following. - Disable interrupts by clearing SIE. - Copy the pc to sepc - Save the current mode (user or supervisor) in the SPP bit in sstatus. - Set scause to reflect the interrupt's cause. - Set the mode to supervisor. - Copy stvec to the pc. (which contains kernelvec in this example) - Start executing at the new pc. kernelvec: save space on current stack; which stack? save registers on the current stack in our example, the scheduler thread's stack kerneltrap() devintr() uartintr() c = uartgetc() consoleintr(c) handle ctrl characters echo c put c in buffer wakeup reader return from devintr() return from kerneltrap() load registers back sret Q: where does sret return too where ever the interrupt happened (in scheduler loop in this case) scheduler runs shell so that it can collect 'l' Interrupts in user space e.g., $ usertests& $ ls usertrapret(): intr_off(); ... // send syscalls, interrupts, and exceptions to trampoline.S w_stvec(TRAMPOLINE + (uservec - trampoline)); ... // set S Previous Privilege mode to User. unsigned long x = r_sstatus(); x &= ~SSTATUS_SPP; // clear SPP to 0 for user mode x |= SSTATUS_SPIE; // enable interrupts in user mode w_sstatus(x); ... usertrapret/uservec same code as we studied for system calls! interrupts/system calls use exact mechanism same for exceptions usertrap() ... w_stvec((uint64)kernelvec); ... if(r_scause() == 8){ ... } else if((which_dev = devintr()) != 0){ // ok } ... usertrapret() Interrupts introduces concurrency Other code runs between my code For example, my code is 1. addi sp,sp,-48 2. sd ra,40(sp) Q: Might other code run between 1 and 2? Yes! Interrupt may happen between 1 and 2 e.g., timer interrupt or uart interrupt For user code maybe not that bad Kernel will resume user code in in the same state For kernel code could be difficult Interrupt handler may update state that is observable by my code my code: interrupt: x = 0 if x = 0 then x = 1 f() f() may be executed or may not be executed! To make a block of code "atomic", turn off interrupts intr_off(): w_sstatus(r_sstatus() & ~SSTATUS_SIE); RISC-V turns of interrupt on a trap (interrupt/exception) Can kernel handle interrupt in trampoline.S? Our first glimps of "concurrency" We'll get back to this when discussing locking Interrupt evolution Interrupts used to be relatively fast; now they are slow old approach: every event causes an interrupt, simple h/w, smart s/w new approach: h/w completes lots of work before interrupting Some devices generate events faster than one per microsecond e.g. gigabit ethernet can deliver 1.5 million small packets / second An interrupt takes on the order of a microsecond save/restore state cache misses what to do if interrupt comes in faster than 1 per microsecond? Polling: another way of interacting with devices Processor spins until device wants attention Wastes processor cycles if device is slow One example in xv6: uartputc() But inexpensive if device is fast No saving of registers etc. If events are always waiting, no need to keep alerting the software Polling versus interrupts Polling rather than interrupting, for high-rate devices Interrupt for low-rate devices, e.g. keyboard constant polling would waste CPU Switch between polling and interrupting automatically interrupt when rate is low (and polling would waste CPU cycles) poll when rate is high (and interrupting would waste CPU cycles) Faster forwarding of interrupts to user space for page faults and user-handled devices h/w delivers directly to user, w/o kernel intervention? faster forwarding path through kernel? We will be seeing many of these topics later in the course