Basic Interrupt explanation
Interrupt is a signal to the processor emitted by hardware (for example: mouse moved) or software (for example: exceptions in CPU like divide by zero, page fault) indicating an event that needs immediate attention.This is the flow of handling an interrupt:
- When an interrupt arrives, the CPU looks if it is masked, if so, it ignores it.
- There is a special location called - the interrupt vector, in which it locates, where in memory, located the interrupt handler function for the current interrupt number.
- The CPU masks interrupts and saves the contents of some registers in some place and execute the interrupt handler.
- When the handler finishes executing, it executes a special return-from-interrupt instruction that restores the saved registers and unmasks interrupts.
Interrupts in Linux
In Linux, each interrupting device gets an interrupt request number (IRQ). When the processor detects that an interrupt has been generated on an IRQ, it stops what it's doing and invokes an interrupt service routine (ISR) registered for the corresponding IRQ.To compensate for interrupting the current thread of execution, ISRs are executed in a restricted environment called interrupt context (or atomic context).
Process Context and Interrupt Context
Kernel code that services system calls issued by user applications runs
on behalf of the corresponding application processes and is said to
execute in process context. Interrupt handlers, on the other hand, run
asynchronously in interrupt context.
Kernel code running in process context is preemptive by the scheduler. An interrupt context, however, always runs to completion and is not preemptive.
Because scheduler only handle process context, if you go to sleep, nothing will resume the code later on. Thats why, you may not use mutexes or any other code that may sleep inside ISR.
Kernel code running in process context is preemptive by the scheduler. An interrupt context, however, always runs to completion and is not preemptive.
Because scheduler only handle process context, if you go to sleep, nothing will resume the code later on. Thats why, you may not use mutexes or any other code that may sleep inside ISR.
More about ISRs
- Different instances of the same ISR will not run simultaneously on multiple processors, because while ISR run, the IRQ is disabled.
- Different IRQs may be handled at the same time by different CPUs.
- Interrupt handlers can be interrupted by handlers associated with IRQs that have higher priority. To prevent this, you must declare your ISR as fast handler. Fast handlers run with all interrupts disabled on the local processor.
How to request an IRQ
int request_irq (
unsigned int irq,
irq_handler_t handler,
unsigned long irqflags,
const char * devname,
void * dev_id);
irq
- Interrupt line to allocate
handler
- Function to be called when the IRQ occursirqflags
- Interrupt type flag, for example:
- IRQF_TRIGGER_RISING - interrupt is generated on rising edge.
- IRQF_TRIGGER_HIGH - interrupt is generated if level is high.
- IRQF_SHARED - specify that this IRQ is shared among multiple devices, each such a device should specify that flag.
- SA_INTERRUPT - mark the ISR as fast, which tells the kernel to disable other interrupts on current CPU until it finished (otherwise, kernel may jump to another ISR, if its IRQ has higher priority). Other CPUs may still handle other IRQs.
devname
- Name for the claiming device, used in /proc/interrupts for identification.dev_id
- A cookie passed back to the handler function
Top half vs bottom half
ISRs must they work fast, because of the following reasons:- While ISR run, it doesn't let other interrupts to run (interrupts with higher priority will run).
- Interrupts with same type will be missed.
- Top half - the ISR part which interact with hardware and should exit as soon as possible.
- Bottom half - The relaxed part that does most of the processing with all interrupts enabled. The kernel decides when to execute them.
Bottom halves
There are 4 bottom half mechanizes available in Linux: Threaded IRQs, softirqs, tasklets, work-queue.threaded IRQs
A driver that wishes to request a threaded interrupt handler will use:int request_threaded_irq (
unsigned int irq, | |
irq_handler_t handler, | |
irq_handler_t thread_fn, | |
unsigned long irqflags, | |
const char * devname, | |
void * dev_id) ; |
So, as you may notice it is a pretty similar to request_irq() function discussed already.
handler
- Function to be called when the IRQ occurs. This is the top half and should return IRQ_WAKE_THREAD which will wake up the handler thread and run
thread_fn
. thread_fn
- This is the bottom half handler which runs in a process context.
When you use the request_threaded_irq() function passing two functions as arguments, this would create a kthread which would be visible in the 'ps ax' process listing, for example:
$ ps -ax
465 ? S 4:16 [irq/34-iwlwifi]
6334 ? S 0:00 [irq/35-mei_me]
The numbers 34,35 are irq numbers and 'iwlwifi', 'mei_me' are device name related to the IRQs.
When the thread_fn completes, the associated kthread would take itself out of the runqueue and remain in blocked state until woken up again by the top half again.
Softirqs
They are used only by a few performance-sensitive subsystems such as the networking layer, SCSI layer, and kernel timers.
Different instances of a softirq can run simultaneously on different processors, thats why their content must be protected with spinlocks.
To define a softirq, you must statically add an entry to include/linux/interrupt.h, which means compiling the kernel.
Softirq run at a high-priority in an interrupt context, with scheduler preemption being disabled, not letting CPU to handle other processes/threads until it complete. If functions registered in SoftIRQs fail to finish within a jiffy (1 to 10 milliseconds based on CONFIG_HZ of the kernel), in which case it impact the responsiveness of the kernel, any new SoftIRQs raised by ISRs would be delegated to run in process context via ksoftirqd thread, thus making SoftIRQs compete for their CPU share along with other processes and threads on the runqueue. In general, SoftIRQs are preferred for bottom-half processing that could finish consistently in few 100 microseconds (well within a jiffy).
void __init mymodule_init()
{
open_softirq(SOFT_IRQ_NUM, bottom_half_handler, NULL);
}
/* The bottom half */
void bottom_half_handler()
{
//DO SOME BOTTOM-HALF WORK HERE
}
/* The top half */
static irqreturn_t some_interrupt(int irq, void *dev_id)
{
//DO SOME TOP-HALF WORK HERE
raise_softirq(SOFT_IRQ_NUM);
return IRQ_HANDLED;
}
void __init mymodule_init() { open_softirq(SOFT_IRQ_NUM, bottom_half_handler, NULL); } /* The bottom half */ void bottom_half_handler() { //DO SOME BOTTOM-HALF WORK HERE } /* The top half */ static irqreturn_t some_interrupt(int irq, void *dev_id) { //DO SOME TOP-HALF WORK HERE raise_softirq(SOFT_IRQ_NUM); return IRQ_HANDLED; }
Tasklets
Tasklets are built on top of softirqs and are easier to use. It's recommended to use tasklets then softirqs unless you have crucial scalability or speed requirements. Tasklet can't be run in parallel on different CPUs, but different types of tasklets may be run simultaneously.
Tasklet can be statically allocated using DECLARE_TASKLET(name, func, data)
or can also be allocated dynamically and initialized at runtime using tasklet_init(name, func, data)
A tasklet can be scheduled to execute at normal priority or high priority. The latter group is always executed first.
Tasklets are a bottom-half mechanism built on top of softirqs, they are represented by two softirqs:HI_SOFTIRQ
and TASKLET_SOFTIRQ
. Tasklets are actually run from a softirq. The only real difference in these types is that the HI_SOFTIRQ
based tasklets run prior to the TASKLET_SOFTIRQ
tasklets.
struct roller_device_struct { /* Device-specific structure */ /* ... */ struct tasklet_struct tsklt; /* ... */ } void __init roller_init() { struct roller_device_struct *dev_struct; /* ... */ /* Initialize tasklet */ tasklet_init(&dev_struct->tsklt, roller_analyze, dev); } /* The bottom half */ void roller_analyze() { /* Analyze the waveforms and switch to polled mode if required */ } /* The interrupt handler */ static irqreturn_t roller_interrupt(int irq, void *dev_id) { struct roller_device_struct *dev_struct; /* Capture the wave stream */ roller_capture(); /* Mark tasklet as pending */ tasklet_schedule(&dev_struct->tsklt); return IRQ_HANDLED; }
Work queues
Summary
Threaded IRQ
(bottom-half)
|
Softirqs
|
Tasklets
|
Work Queues
| |
Execution
context
|
process context
|
interrupt context
|
interrupt context
|
process context
|
Reentrancy
|
cannot run
simultaneously on different CPUs.
|
can run simultaneously on different CPUs.
|
cannot run
simultaneously on different CPUs.
Different tasklets may run on different CPUs.
|
cannot run
simultaneously on different CPUs.
|
Sleep
semantics
|
May go to sleep.
|
Cannot go to sleep.
|
Cannot go to sleep.
|
May go to sleep.
|
Preemption
|
The IRQ thread has a higher priority than work-queue thread.
|
They run at a high-priority with scheduler preemption being disabled, so processes/threads wait for them. If softirq doesn’t release the CPU in more than 1 jiffy, and another softirq pending execution, they will be executed in a kthread.
|
Cannot be
preempted/scheduled.
|
May be
preempted/scheduled.
|
When to use
|
Threaded IRQs are preferred if interrupt processing can take consistently long periods of time (exceeding a jiffy in most cases)
|
If you must do work at real-time, without sleeps.
|
If you don’t need to sleep.
|
If you go to sleep.
|
No comments:
Post a Comment