Handling Interrupts

Handling interrupts

In my last article, we explored the concept of interrupts and how they catch the CPU’s attention. In this article, we will look at what happens after an interrupt occurs.

Let’s start by defining what a context switch is. When an interrupt occurs, it causes the CPU to stop executing the current program. The control then passes to a special piece of code called an Interrupt Handler or Interrupt Service Routine. The interrupt handler will process the interrupt and resume the interrupted program. But have you ever wondered how the initial program is resumed? How does the CPU know where to continue from, and what values were being computed when the interrupt occurred? This happens by means of a context switch, which forms a vital part of interrupt handling.

A context switch is the process of storing and restoring state (the context) of a CPU, so that execution can be resumed from the same point at a later time. When an interrupt is generated, the processor saves its execution state via a context switch, and begins executing the interrupt handler at the interrupt vector. In a switch, the state of the first process must be saved, so that when the scheduler gets back to executing the first process, it can restore this state and continue. The state of the process includes all registers that the process may be using, including the program counter (PC).

Saving and restoring registers

Since an interrupt causes control to transfer to another function, we need to save the registers, so as to preserve their content. For example, let’s suppose that while executing a particular function F, an interrupt occurs:

void F() 
{
........ 
........ 
........
(Interrupt Occurs)
........ 
........
........
}

Let’s suppose we have a function INTR that is invoked when the interrupt occurs:

static void INTR() 
{ 
........ 
........ 
........
}

It is possible that the value of some of the registers being used by F() are also used by INTR(). So, if we don’t preserve their content, they will be corrupted by INTR(). So, we take a backup of the registers inside the interrupt function (while entering the interrupt function), and restore them while exiting the function, as follows:

static void INTR()
{
    // Take backup of registers used
    ........
    ........
    ........    
    // Restore the registers
}
Note: Usually, the value of the PC is preserved by the underlying hardware, so you might not bother about it.

We backup the registers to memory. The memory stack could look like what is shown in Figure 1.

Memory stack

Figure 1: Memory stack

Now, let us look at an example to see how the registers will be saved in memory. To understand this example, let’s assume that we have a processor that supports:

  • Data registers — R0, R1, R2
  • Address registers — AR0
  • Instructions in which the first operand forms the destination operand. And the remaining operands form the source operands.

Please note that we need to save those registers that are used by the interrupt function. Let’s look at the following example for the interrupt routine INTR():

static void INTR()
{
    int a, b, c ;
    a = b + c;
}

The saving and restoring of registers is done as shown below:

   _intr:
    // Prologue Code 
    ...
    ...
    // Taking Backup of Data Registers 
    // used in the interrupt routine
        MOVE    [AR0]++,R0;
        MOVE    [AR0]++,R1;
        MOVE    [AR0]++,R2;

    //  a = b + c;

        MOVE    AR0,    SP ;  //SP is the stack pointer
        MOVE    R2,     [AR0]++ ;
        MOVE    R1,     [AR0]++ ;
        ADD     R0,     R2,    R1 ;
        MOVE    [AR0]++,    R0 ;

    //Restore ARO by the stack pointer   
    // Restoring Data Registers 
    // used in the interrupt routine

        MOVE    R2,   [AR0]--;    
        MOVE    R1,   [AR0]--;    
        MOVE    R0,   [AR0]  ;      

    // Epilogue Code 
    ...
    ...

        RETINT    ;  //Return from interrupt routine
    END

As you can see above, since we know what registers are used in the interrupt routine, we need to preserve only (the values of) those registers. A better (but suboptimal) way would be to blindly save/restore all the physical registers, so that in future, if the interrupt routine is modified, you need not bother about additional code for saving/restoring the new set of registers used in the function. However, this may be subject to requirements.

All published articles are released under Creative Commons Attribution-NonCommercial 3.0 Unported License, unless otherwise noted.
Open Source For You is powered by WordPress, which gladly sits on top of a CentOS-based LEMP stack.

Creative Commons License.