1 00:00:00,539 --> 00:00:05,890 A key technology for timesharing is the periodic interrupt from the external timer device. 2 00:00:05,890 --> 00:00:11,450 Let's remind ourselves how the interrupt hardware in the Beta works. 3 00:00:11,450 --> 00:00:16,840 External devices request an interrupt by asserting the Beta's interrupt request (IRQ) input. 4 00:00:16,840 --> 00:00:22,570 If the Beta is running in user mode, i.e., the supervisor bit stored in the PC is 0, 5 00:00:22,570 --> 00:00:29,579 asserting IRQ will trigger the following actions on the clock cycle the interrupt is recognized. 6 00:00:29,579 --> 00:00:35,230 The goal is to save the current PC+4 value in the XP register and force the program counter 7 00:00:35,230 --> 00:00:40,160 (PC) to a particular kernel-mode instruction, which starts the execution of the interrupt 8 00:00:40,160 --> 00:00:42,080 handler code. 9 00:00:42,080 --> 00:00:46,780 The normal process of generating control signals based on the current instruction is superseded 10 00:00:46,780 --> 00:00:51,800 by forcing particular values for some of the control signals. 11 00:00:51,800 --> 00:00:57,110 PCSEL is set to 4, which selects a specified kernel-mode address as the next value of the 12 00:00:57,110 --> 00:00:59,030 program counter. 13 00:00:59,030 --> 00:01:02,400 The address chosen depends on the type of external interrupt. 14 00:01:02,400 --> 00:01:09,430 In the case of the timer interrupt, the address is 0x80000008. 15 00:01:09,430 --> 00:01:15,070 Note that PC[31], the supervisor bit, is being set to 1 and the CPU will be in kernel-mode 16 00:01:15,070 --> 00:01:19,680 as it starts executing the code of the interrupt handler. 17 00:01:19,680 --> 00:01:26,200 The WASEL, WDSEL, and WERF control signals are set so that PC+4 is written into the XP 18 00:01:26,200 --> 00:01:30,370 register (i.e., R30) in the register file. 19 00:01:30,370 --> 00:01:36,950 And, finally, MWR is set to 0 to ensure that if we're interrupting a ST instruction that 20 00:01:36,950 --> 00:01:39,500 its execution is aborted correctly. 21 00:01:39,500 --> 00:01:44,789 So in the next clock cycle, execution starts with the first instruction of the kernel-mode 22 00:01:44,789 --> 00:01:48,780 interrupt handler, which can find the PC+4 of the interrupted 23 00:01:48,780 --> 00:01:53,330 instruction in the XP register of the CPU. 24 00:01:53,330 --> 00:01:56,860 As we can see the interrupt hardware is pretty minimal: 25 00:01:56,860 --> 00:02:02,830 it saves the PC+4 of the interrupted user-mode program in the XP register and sets the program 26 00:02:02,830 --> 00:02:09,369 counter to some predetermined value that depends on which external interrupt happened. 27 00:02:09,369 --> 00:02:13,569 The remainder of the work to handle the interrupt request is performed in software. 28 00:02:13,569 --> 00:02:19,870 The state of the interrupted process, e.g., the values in the CPU registers R0 through 29 00:02:19,870 --> 00:02:24,829 R30, is stored in main memory in an OS data structure called UserMState. 30 00:02:24,829 --> 00:02:30,209 Then the appropriate handler code, usually a procedure written in C, is invoked to do 31 00:02:30,209 --> 00:02:32,049 the heavy lifting. 32 00:02:32,049 --> 00:02:36,849 When that procedure returns, the process state is reloaded from UserMState. 33 00:02:36,849 --> 00:02:44,239 The OS subtracts 4 from the value in XP, making it point to the interrupted instruction and 34 00:02:44,239 --> 00:02:49,430 then resumes user-mode execution with a JMP(XP). 35 00:02:49,430 --> 00:02:54,099 Note that in our simple Beta implementation the first instructions for the various interrupt 36 00:02:54,099 --> 00:02:57,930 handlers occupy consecutive locations in main memory. 37 00:02:57,930 --> 00:03:03,489 Since interrupt handlers are longer than one instruction, this first instruction is invariably 38 00:03:03,489 --> 00:03:06,959 a branch to the actual interrupt code. 39 00:03:06,959 --> 00:03:12,809 Here we see that the reset interrupt (asserted when the CPU first starts running) sets the 40 00:03:12,809 --> 00:03:18,999 PC to 0, the illegal instruction interrupt sets the PC to 4, the timer interrupt sets 41 00:03:18,999 --> 00:03:22,370 the PC to 8, and so on. 42 00:03:22,370 --> 00:03:29,939 In all cases, bit 31 of the new PC value is set to 1 so that handlers execute in supervisor 43 00:03:29,939 --> 00:03:35,239 or kernel mode, giving them access to the kernel context. 44 00:03:35,239 --> 00:03:41,019 A common alternative is provide a table of new PC values at a known location and have 45 00:03:41,019 --> 00:03:46,889 the interrupt hardware access that table to fetch the PC for the appropriate handler routine. 46 00:03:46,889 --> 00:03:51,150 This provides the same functionality as our simple Beta implementation. 47 00:03:51,150 --> 00:03:57,689 Since the process state is saved and restored during an interrupt, interrupts are transparent 48 00:03:57,689 --> 00:04:00,239 to the running user-mode program. 49 00:04:00,239 --> 00:04:05,450 In essence, we borrow a few CPU cycles to deal with the interrupt, then it's back to 50 00:04:05,450 --> 00:04:08,310 normal program execution. 51 00:04:08,310 --> 00:04:11,219 Here's how the timer interrupt handler would work. 52 00:04:11,219 --> 00:04:16,108 Our initial goal is to use the timer interrupt to update a data value in the OS that records 53 00:04:16,108 --> 00:04:18,250 the current time of day (TOD). 54 00:04:18,250 --> 00:04:22,880 Let's assume the timer interrupt is triggered every 1/60th of a second. 55 00:04:22,880 --> 00:04:27,730 A user-mode program executes normally, not needing to make any special provision to deal 56 00:04:27,730 --> 00:04:30,260 with timer interrupts. 57 00:04:30,260 --> 00:04:34,540 Periodically the timer interrupts the user-mode program to run the clock interrupt handler 58 00:04:34,540 --> 00:04:39,400 code in the OS, then resumes execution of the user-mode program. 59 00:04:39,400 --> 00:04:43,600 The program continues execution just as if the interrupt had not occurred. 60 00:04:43,600 --> 00:04:49,500 If the program needs access to the TOD, it makes the appropriate service request to the 61 00:04:49,500 --> 00:04:51,880 OS. 62 00:04:51,880 --> 00:04:56,200 The clock handler code in the OS starts and ends with a small amount of assembly-language 63 00:04:56,200 --> 00:04:58,570 code to save and restore the state. 64 00:04:58,570 --> 00:05:05,660 In the middle, the assembly code makes a C procedure call to actually handle the interrupt. 65 00:05:05,660 --> 00:05:07,770 Here's what the handler code might look like. 66 00:05:07,770 --> 00:05:14,610 In C, we find the declarations for the TOD data value and the structure, called UserMState, 67 00:05:14,610 --> 00:05:17,850 that temporarily holds the saved process state. 68 00:05:17,850 --> 00:05:22,900 There's also the C procedure for incrementing the TOD value. 69 00:05:22,900 --> 00:05:28,500 A timer interrupt executes the BR() instruction at location 8, which branches to the actual 70 00:05:28,500 --> 00:05:32,010 interrupt handler code at CLOCK_H. 71 00:05:32,010 --> 00:05:38,330 The code first saves the values of all the CPU registers into the UserMState data structure. 72 00:05:38,330 --> 00:05:43,580 Note that we don't save the value of R31 since its value is always 0. 73 00:05:43,580 --> 00:05:47,950 After setting up the kernel-mode stack, the assembly-language stub calls the C procedure 74 00:05:47,950 --> 00:05:50,470 above to do the hard work. 75 00:05:50,470 --> 00:05:55,870 When the procedure returns, the CPU registers are reloaded from the saved process state 76 00:05:55,870 --> 00:06:01,680 and the XP register value decremented by 4 so that it will point to the interrupted instruction. 77 00:06:01,680 --> 00:06:06,310 Then a JMP(XP) resumes user-mode execution. 78 00:06:06,310 --> 00:06:08,960 Okay, that was simple enough. 79 00:06:08,960 --> 00:06:12,500 But what does this all have to do with timesharing? 80 00:06:12,500 --> 00:06:17,650 Wasn't our goal to arrange to periodically switch which process was running? 81 00:06:17,650 --> 00:06:18,760 Aha! 82 00:06:18,760 --> 00:06:24,140 We have code that runs on every timer interrupt, so let's modify it so that every so often 83 00:06:24,140 --> 00:06:28,280 we arrange to call the OS' Scheduler() routine. 84 00:06:28,280 --> 00:06:33,420 In this example, we'd set the constant QUANTUM to 2 if we wanted to call Scheduler() every 85 00:06:33,420 --> 00:06:36,210 second timer interrupt. 86 00:06:36,210 --> 00:06:41,620 The Scheduler() subroutine is where the time sharing magic happens! 87 00:06:41,620 --> 00:06:46,080 Here we see the UserMState data structure from the previous slide where the user-mode 88 00:06:46,080 --> 00:06:49,570 process state is stored during interrupts. 89 00:06:49,570 --> 00:06:54,780 And here's an array of process control block (PCB) data structures, one for each process 90 00:06:54,780 --> 00:06:56,639 in the system. 91 00:06:56,639 --> 00:07:01,840 The PCB holds the complete state of a process when some other process is currently executing 92 00:07:01,840 --> 00:07:06,389 - it's the long-term storage for processor state! 93 00:07:06,389 --> 00:07:11,980 As you can see, it includes a copy of MState with the process' register values, the MMU 94 00:07:11,980 --> 00:07:17,460 state, and various state associated with the process' input/output activities, 95 00:07:17,460 --> 00:07:22,950 represented here by a number indicating which virtual user-interface console is attached 96 00:07:22,950 --> 00:07:25,210 to the process. 97 00:07:25,210 --> 00:07:27,780 There are N processes altogether. 98 00:07:27,780 --> 00:07:35,400 The variable CUR gives the index into ProcTable for the currently running process. 99 00:07:35,400 --> 00:07:40,080 And here's the surprisingly simple code for implementing timesharing. 100 00:07:40,080 --> 00:07:45,360 Whenever the Scheduler() routine is called, it starts by moving the temporary saved state 101 00:07:45,360 --> 00:07:49,510 into the PCB for the current process. 102 00:07:49,510 --> 00:07:54,560 It then increments CUR to move to the next process, making sure it wraps back around 103 00:07:54,560 --> 00:07:59,340 to 0 when we've just finished running the last of the N processes. 104 00:07:59,340 --> 00:08:05,770 It then loads reloads the temporary state from the PCB of the new process and sets up 105 00:08:05,770 --> 00:08:08,730 the MMU appropriately. 106 00:08:08,730 --> 00:08:13,290 At this point Scheduler() returns and the clock interrupt handler reloads the CPU registers 107 00:08:13,290 --> 00:08:18,230 from the updated temporary saved state and resumes execution. 108 00:08:18,230 --> 00:08:19,600 Voila! 109 00:08:19,600 --> 00:08:22,960 We're now running a new process. 110 00:08:22,960 --> 00:08:26,880 Let's use this diagram to once again walk through how time sharing works. 111 00:08:26,880 --> 00:08:32,080 At the top of the diagram you'll see the code for the user-mode processes, and below the 112 00:08:32,080 --> 00:08:35,190 OS code along with its data structures. 113 00:08:35,190 --> 00:08:40,379 The timer interrupts the currently running user-mode program and starts execution of 114 00:08:40,379 --> 00:08:43,250 the OS' clock handler code. 115 00:08:43,250 --> 00:08:49,910 The first thing the handler does is save all the registers into the UserMState data structure. 116 00:08:49,910 --> 00:08:56,610 If the Scheduler() routine is called, it moves the temporarily saved state into the PCB, 117 00:08:56,610 --> 00:09:01,009 which provides the long-term storage for a process' state. 118 00:09:01,009 --> 00:09:05,749 Next, Scheduler() copies the saved state for the next process into the temporary holding 119 00:09:05,749 --> 00:09:06,999 area. 120 00:09:06,999 --> 00:09:13,319 Then the clock handler reloads the updated state into the CPU registers and resumes execution, 121 00:09:13,319 --> 00:09:17,029 this time running code in the new process. 122 00:09:17,029 --> 00:09:21,499 While we're looking at the OS, note that since its code runs with the supervisor mode bit 123 00:09:21,499 --> 00:09:26,700 set to 1, interrupts are disabled while in the OS. 124 00:09:26,700 --> 00:09:30,860 This prevents the awkward problem of getting a second interrupt while still in the middle 125 00:09:30,860 --> 00:09:36,089 of handling a first interrupt, a situation that might accidentally overwrite the state 126 00:09:36,089 --> 00:09:38,449 in UserMState. 127 00:09:38,449 --> 00:09:43,440 But that means one has to be very careful when writing OS code. 128 00:09:43,440 --> 00:09:47,110 Any sort of infinite loop can never be interrupted. 129 00:09:47,110 --> 00:09:51,829 You may have experienced this when your machine appears to freeze, accepting no inputs and 130 00:09:51,829 --> 00:09:54,379 just sitting there like a lump. 131 00:09:54,379 --> 00:09:59,709 At this point, your only choice is to power-cycle the hardware (the ultimate interrupt!) and 132 00:09:59,709 --> 00:10:02,089 start afresh. 133 00:10:02,089 --> 00:10:06,260 Interrupts are allowed during execution of user-mode programs, so if they run amok and 134 00:10:06,260 --> 00:10:10,990 need to be interrupted, that's always possible since the OS is still responding to, say, 135 00:10:10,990 --> 00:10:12,889 keyboard interrupts. 136 00:10:12,889 --> 00:10:17,410 Every OS has a magic combination of keystrokes that is guaranteed to suspend execution of 137 00:10:17,410 --> 00:10:22,410 the current process, sometimes arranging to make a copy of the process state for later 138 00:10:22,410 --> 00:10:23,970 debugging. 139 00:10:23,970 --> 00:10:24,420 Very handy!