Interrupt Service Routines

The x86 architecture is an interrupt driven system. External events trigger an interrupt - the normal control flow is interrupted and a Interrupt Service Routine (ISR) is called.

Such events can be triggered by hardware or software. An example of a hardware interrupt is the keyboard: Every time you press a key, the keyboard triggers IRQ1 (Interrupt Request 1), and the corresponding interrupt handler is called. Timers, and disk request completion are other possible sources of hardware interrupts.

Software driven interrupts are triggered by the int opcode; e.g. the services provided by MS-DOS are called by the software triggering INT 21h and passing the applicable parameters in CPU registers.

An ISR is called directly by the CPU, and the protocol for calling an ISR differs from calling e.g. a C function. Most importantly, an ISR has to end with the iret opcode, whereas usual C functions end with ret or retf. The obvious but nevertheless wrong solution leads to one of the most "popular" triple-fault errors among OS programmers.

The Problem

Many people shun away from Assembler, and want to do as much as possible in their favorite high-level language. GCC (as well as other compilers) allow you to add inline Assembler, so many programmers are tempted to write an ISR like this:

This cannot work. The compiler doesn't understand what is going on. It doesn't understand that the registers and stack is required to be preserved between the asm statements; the optimizer will likely corrupt the function. Additionally, the compiler adds stack handling code before and after your function, which together with the iret results in Assembler code resembling this:

Compiler Specific Interrupt Directives

Some compilers for some processors have directives allowing you to declare a routine interrupt, offer a #pragma interrupt, or a dedicated macro. Borland C, Watcom C/C++, Microsoft C 6.0 and Free Pascal Compiler 1.9.* and up offer this, while GCC does not. Visual C++ and clang-llvm offer an alternative shown under Naked Functions:

Borland C

Watcom C/C++

Naked Functions

Some Compilers can be used to make interrupt routines, but requires you to manually handle the stack and return operations. Doing so requires that the function be generated without an epilogue or prologue. This is called making the function naked - this is done in Visual C++ by adding the attribute _declspec(naked) and in clang-llvm by adding the attribute __attribute__((naked)) to the function. You need to verify that you do include a return operation (such as iretd) as that is part of the epilogue that the compiler has now been instructed to not include.

If you intend to use local variables, you must set up the stack frame in the manner which the compiler expects; as ISRs are non-reentrant, however, you can simply use static variables.

Visual C++

Visual C++ also supplies the __LOCAL_SIZE assembler macro, which notifies you how much space is required by the objects on the stack for the function.

This assumes that leave is the correct end-of-function handling - you are doing the function return code "by hand", and leave the compiler-generated handling as "dead code". Needless to say, such assumptions on compiler internals are dangerous. This code can break on a different compiler, or even a different version of the same compiler. It is therefore strongly discouraged, and listed only for completeness.

Asm Goto

Having read all the above warnings and still eager to have a solution for writing ISRs in C(++) code using gcc? Consider the following: