 This time, we're going to be looking at exceptions and exception handling, looking at how MIPS will deal with situations that are unanticipated and how you can actually write code that will handle those. To begin with, an exception is any sort of unanticipated event. That could be something going wrong such as a software error. Perhaps you had some overflow in an ALU computation. Maybe you tried to access an unaligned memory address. Or you might have even tried to run an instruction that doesn't even exist. These are all the errors that might possibly occur when just running regular instructions. Exceptions also include any sort of kernel request. That could be one of your system call instructions, which asks the kernel to do something for you. But it could be something else like a memory fault. You asked for a piece of data that wasn't immediately available to the CPU. So it had to ask the operating system to go find that piece of data and put it where it was expecting to find it. The last type of exceptions are often called interrupts. Those are any sort of requests that is made from an external device. That might be a hard drive or a keyboard or a mouse. Anything that's providing input to the computer that needs to be dealt with immediately. There are two general ways of handling these exceptions. We can have a status register, in which case we write down why we're accessing the exception handler. Then we call the one generic exception handler that we have. It will read that register and it will decide what to do to handle the problem that it's got. The other alternative is that we could have vector interrupts. In this case, we don't have a status register. We're not going to write down why we're calling our exception handler. Instead, we'll have multiple exception handlers. So when one type of exception occurs, I might go to this block of code. When a different type of exception occurs, I might go to this block of code. Third type of exception occurs, maybe I've got a block of code over here that handles that. So instead of having one single block of code that figures out what went wrong and how to handle it, which would turn into a big case statement anyway, we could just have a series of different handlers that are each capable of handling one and let the CPU decide which one of those we should run based on the exception that we get. Now we're going to look at the specifics of how MIPS actually handles exceptions and how you can write code to work with that. To begin with, MIPS has a coprocessor to support exception handling, which is similar to what we had for floating point processing, except that we'll be using coprocessor 0 instead of coprocessor 1. It has a whole lot less functionality on it because it's related to exception handling and not floating point operations. We'll have some similar operations like data transfer. There are four registers that are really interesting to us for exception handling support. And you can actually see those when you're looking at QTSpib. Those include bad address, which tells us if we gave it a bad address that was unacceptable for whatever instruction we had. So if we have an unaligned address, then that would show up in our bad fee address. We have a status register, but unlike what we just looked at, we're not actually going to put the error in there. Error will go in the cost register. Instead, the status register holds a little bit of a history about what things have gone wrong. It tells us about the last three errors, including whether they happened in kernel mode or in user mode. And whether it was an interrupt or an exception. It also tells the processor which exceptions it should look for, which ones should it care about. Maybe we'd like to ignore all interrupts for the moment. We can set that in the status register. The cost register is where we're actually going to put whatever error has occurred. Whether it's an actual error or just a kernel request, we'll put that cause in the cost register. EPC is for exception program counter. This tells you where in your code the exception occurred. That will give us a way to go back to that line of code if we're interested. We might just kill the user code, but often enough, we do want to go back to that code, and the EPC will give us a way to do that. So since MIPS has this cause register, we're just going to have a single exception handler. It will be able to look at that cause register and determine what needs to be done to recover from the exception and continue on. Our exception handler though is just a block of code, like any other block of code we've written. It handles requests, displays errors if they're needed, decides whether we should kill the user process or continue running. Whatever needs to be done to handle any exception it can expect to have. But it's really just another block of code. In our case, we can find it in a very specific part of memory, which is at this address. And you can notice that this is actually above the stack. This is one reason why our memory is limited, because we've stuck other things above it. We've actually got stuff out there that we wouldn't want to overwrite with dynamic data. So when an exception occurs, MIPS will transfer control to that address and just start running the exception handler. Again, our exception handler is just this block of code, looks at the data that it's got, and does something with it to handle the issue. When it's done, we need to return. But it turns out that EPC isn't always going to be the same thing. In some cases, it's the address of the instruction where the exception occurred. In other cases, it's the address of the instruction right afterwards. Because we don't want to go back to run the same instruction we just did again, we have to be sure that we're going to get the address of the instruction after the exception every time. So these two blocks of code are slightly different. Really the only difference is this line of code, where we're incrementing K0 by 4. So the first line of code in both of these tells the processor to go copy the EPC off of co-processor 0, stick it into K0 on the regular CPU. Then if we need to, we add 4. We know we're pointing at the address of the instruction after the exception. Then we have this RFE instruction. The return from exception instruction resets all of the exception handling support so that it's ready to watch for a new exception. This will make sure it's not going to come back and just run the exception handler again. We need to make sure that it's reset all of the relevant registers so that it can detect a new exception. The last thing we've got there is that jump register instruction. And that works just the same as a regular jump register instruction. This time we have K0 because that's where we put our return address. We don't have it in the RA register because we might overwrite the return address that the previously running function had put in there. We probably still need that return address. So we use K0, which is reserved for the kernel. The processor can only really access those registers when it's running in kernel mode. So a user can't just modify K0. Thus it will be free as long as we're not using it for something else within our kernel code. So MIPS gives us a handful of registers to work with that we can use to handle any sort of exception that might occur. And the way we handle those is just with another block of code. We'll look at the cause of our exception, any relevant data, and make any decisions it needs to about what we should do in this case.