Here's a high-level view of the low-level processing. I'm describing a simple typical architecture, real architectures can be more complex or differ in ways that don't matter at this level of detail.
When an interrupt occurs, the processor looks if interrupts are masked. If they are, nothing happens until they are unmasked. When interrupts become unmasked, if there are any pending interrupts, the processor picks one.
Then the processor executes the interrupt by branching to a particular address in memory. The code at that address is called the interrupt handler. When the processor branches there, it masks interrupts (so the interrupt handler has exclusive control) and saves the contents of some registers in some place (typically other registers).
The interrupt handler does what it must do, typically by communicating with the peripheral that triggered the interrupt to send or receive data. If the interrupt was raised by the timer, the handler might trigger the OS scheduler, to switch to a different thread. When the handler finishes executing, it executes a special return-from-interrupt instruction that restores the saved registers and unmasks interrupts.
The interrupt handler must run quickly, because it's preventing any other interrupt from running. In the Linux kernel, interrupt processing is divided in two parts:
- The “top half” is the interrupt handler. It does the minimum necessary, typically communicate with the hardware and set a flag somewhere in kernel memory.
- The “bottom half” does any other necessary processing, for example copying data into process memory, updating kernel data structures, etc. It can take its time and even block waiting for some other part of the system since it runs with interrupts enabled.
As usual on this topic, for more information, read Linux Device Drivers; chapter 10 is about interrupts.
Answer from Gilles 'SO- stop being evil' on Stack Exchangekernel - How is an Interrupt handled in Linux? - Unix & Linux Stack Exchange
Linux - Interrupt Handling: Overview (Part 1)
c - Kernel Module Programming ( Interrupt handler ) - Stack Overflow
Do interrupts actual interrupt or do they wait for a 'natural' context switch and jump the queue?
Videos
Here's a high-level view of the low-level processing. I'm describing a simple typical architecture, real architectures can be more complex or differ in ways that don't matter at this level of detail.
When an interrupt occurs, the processor looks if interrupts are masked. If they are, nothing happens until they are unmasked. When interrupts become unmasked, if there are any pending interrupts, the processor picks one.
Then the processor executes the interrupt by branching to a particular address in memory. The code at that address is called the interrupt handler. When the processor branches there, it masks interrupts (so the interrupt handler has exclusive control) and saves the contents of some registers in some place (typically other registers).
The interrupt handler does what it must do, typically by communicating with the peripheral that triggered the interrupt to send or receive data. If the interrupt was raised by the timer, the handler might trigger the OS scheduler, to switch to a different thread. When the handler finishes executing, it executes a special return-from-interrupt instruction that restores the saved registers and unmasks interrupts.
The interrupt handler must run quickly, because it's preventing any other interrupt from running. In the Linux kernel, interrupt processing is divided in two parts:
- The “top half” is the interrupt handler. It does the minimum necessary, typically communicate with the hardware and set a flag somewhere in kernel memory.
- The “bottom half” does any other necessary processing, for example copying data into process memory, updating kernel data structures, etc. It can take its time and even block waiting for some other part of the system since it runs with interrupts enabled.
As usual on this topic, for more information, read Linux Device Drivers; chapter 10 is about interrupts.
Gilles already described the general case of an interrupt, the following applies specifically to Linux 2.6 on an Intel architecture (part of this is also based on Intel's specifications).
An interrupt is an event that changes the sequence of instructions executed by the processor.
There are two different kinds of interrupts:
- Synchronous interrupt (Exception) produced by the CPU while processing instructions
- Asynchronous interrupt (Interrupt) issued by other hardware devices
Exceptions are caused by programming errors (f.e. Divide error, Page Fault, Overflow) that must be handled by the kernel. He sends a signal to the program and tries to recover from the error.
The following two exceptions are classified:
- Processor-detected exception generated by the CPU while detecting a anomalous condition; divided into three groups: Faults can generally be corrected, Traps report an execution, Aborts are serious errors.
- Programmed exception requested by the programmer, handled like a trap.
Interrupts can be issued by I/O devices (keyboard, network adapter, ..), interval timers and (on multiprocessor systems) other CPUs. When an interrupt occures, the CPU must stop his current instruction and execute the newly arrived interrupt. He needs to save the old interrupted process state to (probably) resume it after the interrupt is handled.
Handling interrupts is a sensitive task:
- Interrupts can occur at any time, the kernel tries to get it out of the way as soon as possible
- An interrupt can be interrupted by another interrupt
- There are regions in the kernel which must not be interrupted at all
Two different interrupt levels are defined:
- Maskable interrupts issued by I/O devices; can be in two states, masked or unmasked. Only unmasked interrupts are getting processed.
- Nonmaskable interrupts; critical malfunctions (f.e. hardware failure); always processed by the CPU.
Every hardware device has it's own Interrupt Request (IRQ) line. The IRQs are numbered starting from 0. All IRQ lines are connected to a Programmable Interrupt Controller (PIC). The PIC listens on IRQs and assigns them to the CPU. It is also possible to disable a specific IRQ line.
Modern multiprocessing Linux systems generally include the newer Advanced PIC (APIC), which distributes IRQ requests equally between the CPUs.
The mid-step between an interrupt or exception and the handling of it is the Interrupt Descriptor Table (IDT). This table associates each interrupt or exception vector (a number) with a specified handler (f.e. Divide error gets handled by the function divide_error()).
Through the IDT, the kernel knows exactly how to handle the occurred interrupt or exception.
So, what does the kernel when an interrupt occurres?
- The CPU checks after each instruction if there's a IRQ from the (A)PIC
- If so, consults the IDT to map the received vector to a function
- Checks if the interrupt was issued by a authorized source
- Saves the registers of the interrupted process
- Call the according function to handle the interrupt
- Load the recently saved registers of the interrupted process and try to resume it
After talking in general about the concept of interrupts (https://medium.com/@boutnaru/computer-architecture-interrupts-9d0dc849c1e5) now we are going to deep dive into how they are handled under Linux.
In order to handle an interrupt we first need to register it using a call to “request_irq” which adds the handler (https://elixir.bootlin.com/linux/v6.1/source/include/linux/interrupt.h#L165). In order to remove a handler we call “free_irq”. Fun fact, “request_irq” is referenced in 1280 files in the source code of the Linux kernel (version 6.1).
Overall, we can think about two major use cases when we are handling interrupts. First, interrupts that are designed to execute quickly. Second, interrupts which have to do several operations to complete (meaning they won’t execute quickly).
Due to that, interrupt handling has two phases (by the way it is true not only for Linux). We have “Top Half” and “Bottom Half”. The main reason for the split is the fact that we can’t perform large operations in interrupt-context (when interrupts are disable). So, what “Bottom Half” allows us is to defer work for later. For more information I suggest reading https://0xax.gitbooks.io/linux-insides/content/Interrupts/linux-interrupts-9.html.
We can sum up the phases of a “Top Half” as follows: ensure that the interrupt was triggered by the correct hardware (in case of line sharing), clearing interrupt pending bit on the hardware (if needed), does everything that needs to be immediate (like reading/writing to/from the device) and schedule the “Bottom Half” (if needed). As an example we can see the interrupt handler for a floppy (https://elixir.bootlin.com/linux/v6.1/source/drivers/block/floppy.c#L1712) that calls the deferred work in the following line https://elixir.bootlin.com/linux/v6.1/source/drivers/block/floppy.c#L1765.
Thus, “Top Half” runs in an interrupt context, which is used for quick responses to external events and it is responsible for creating the deferred work for later (the “Bottom Half”) - as we can see in the timeline diagram shown below.
Lastly, We have three types for the category of “Bottom Half”: workques, tasklets and softirq (https://www.oreilly.com/library/view/understanding-the-linux/0596002130/ch04s07.html). We will dive into each one of them in future writeups. By the way, there are also timers and normal regular kernel threads that can be used for deferred work but they are less natural for interrupt handling so I won’t go over them in this series.
Next we are going to start diving into workques, tasklets and softirq. See you in the next writeup :-).
https://hugh712.gitbooks.io/embeddedsystem/content/Images/RealTime/RT006.png
Your example does not handle some random IRQ. It handles an interrupt critical for working with the machine.
At the beginning of your registration you remove the previous interrupt handler. Your problem is that you failed to re-install it once you remove your own.
The result is that, when you rmmod your module, no one is handling the keyboard interrupt.
Please change the code:
static void __exitrq_ex_exit(void)
{
free_irq(1, NULL);
}
into:
static void __exitrq_ex_exit(void)
{
free_irq(1, (void*)irq_handler);
}
You need to let the kernel know which handler you want to remove. Since you use the function irq_handler() as the dev_id, you need to use it again to remove the mode without remove the original keyboard interrupt handler.