Cover image for Linux Interrupts

Linux Interrupts

Words 15.6k
Views
Visitors

Timeline

Timeline

2025-11-14

init

This article introduces the core concepts and implementation framework of the Linux interrupt mechanism. It first explains the basic principle of interrupts: during normal CPU operation, an external or internal event triggers the CPU to pause the current program and execute the interrupt handler instead; after the handler completes, the CPU returns to the original execution point, thereby improving the system's concurrent processing capability and resource utilization. Subsequently, the article discusses in detail the necessity of dividing the interrupt handler into upper and lower halves: the upper half is responsible for urgent and fast-response tasks such as saving register states, while the lower half handles time-consuming operations such as complex calculations and external device access, ensuring system real-time performance. Regarding the interrupt subsystem framework, the article divides it into four layers: user layer, generic layer, hardware-related layer, and hardware layer, corresponding to device drivers, hardware-independent generic management interfaces, specific processor architecture and interrupt controller drivers, and the physical connection between peripherals and the SoC. Finally, the article briefly introduces the version evolution of the interrupt controller GIC, including key features of GICv1 and GICv2, such as the number of supported cores, the number of interrupt IDs, and virtualization capabilities. Overall, this article systematically summarizes the design philosophy and layered architecture of Linux interrupt handling, providing a reference for understanding interrupt applications in driver development.

Linux Driver Notes

Table of ContentsLinks
1. Linux Driver Framework
2. Linux Driver Loading Logic
3. Character Device Basics
4. Concurrency and Race Conditions
5. Advanced Character Device Topics
6. Interrupts
7. Platform Bus
8. Device Tree
9. Device Model
10. Hotplug
11. pinctrl Subsystem
12. GPIO subsystem
13. Input subsystem
14. 1-Wire
15. I2C
16. SPI
17. UART
18. PWM
19. RTC
20. Watchdog
21. CAN
22. Network devices
23. ADC
24. IIO
25. USB
26. LCD

Interrupt Concept

An interrupt is a mechanism caused by external or internal events while the CPU is running normally. When an interrupt occurs, the CPU stops the currently executing program and instead executes the interrupt handler that triggered the interrupt. After the interrupt handler completes, the CPU returns to the point where the interrupt occurred and continues executing the interrupted program. The interrupt mechanism allows the CPU to respond to external or internal events in real time while maintaining its ability to handle other tasks.

The interrupt mechanism gives us the ability to handle unexpected situations, and if we can make full use of this mechanism, we can complete multiple tasks simultaneously.

The interrupt mechanism enables us to handle multiple tasks simultaneously in an orderly manner, thereby improving concurrent processing capability. Similarly, computer systems also use interrupt mechanisms to respond to various external events. For example, when keyboard input occurs, an interrupt signal is sent to the CPU so that the user’s operation can be responded to promptly. In this way, the CPU does not have to keep polling the keyboard status and can focus on other tasks. The interrupt mechanism can also be used to handle events such as hard disk read/write completion and network packet reception, improving system resource utilization and concurrent processing capability.

Top and Bottom Halves of Interrupts

**Interrupt execution requires fast response.**but not all interrupts can be completed quickly. If interrupt handling takes too long, it will cause problems.

To enable the system to better handle interrupt events and improve real-time performance and responsiveness, the interrupt service routine is divided into two parts:

  • Interrupt top half is the first part of the interrupt service routine, and it mainlyhandles urgent tasks that require quick response.
    • It is characterized by short execution time and aims to complete interrupt handling as quickly as possible. These tasks may include saving register states, updating counters, etc., so that after interrupt handling is completed, the execution can correctly return to the position before the interrupt.
  • Interrupt bottom half is the second part of the interrupt service routine, and it mainlyhandles relatively time-consuming tasks.
    • Since the interrupt top half needs to complete as quickly as possible, the interrupt bottom half is responsible for handling tasks that cannot be completed immediately and require more time. These tasks may include complex calculations, accessing external devices, or performing long-duration data processing.

Interrupt Subsystem Framework

A complete interrupt subsystem framework can be divided into four layers, from top to bottom: user layer, generic layer, hardware-related layer, and hardware layer. The relevant introduction for each layer is as follows:

  • User layer: The user layer is the user of interrupts, mainly including various device drivers.

    These drivers apply for and register interrupts through interrupt-related interfaces. When a peripheral triggers an interrupt, the user-layer driver performs corresponding callback processing and executes specific operations.

  • Generic layer: The generic layer, also called the framework layer, is a hardware-independent layer.
    The generic layer code is common across all hardware platforms and does not depend on a specific hardware architecture or interrupt controller. The generic layer provides unified interfaces and functions for managing and handling interrupts, allowing drivers to be reused on different hardware platforms.

  • Hardware-dependent layer: The hardware-dependent layer contains two parts of code.
    One part is code related to a specific processor architecture, such as the interrupt handling code for ARM64 processors. This code is responsible for handling the interrupt mechanism of a specific architecture, including the interrupt vector table, interrupt handlers, etc. The other part is the interrupt controller driver code, used to communicate with and configure the interrupt controller. This code is related to the specific interrupt controller hardware.

  • Hardware layer: The hardware layer is at the bottom and is related to specific hardware connections.

    It includes the physical connection part between peripherals and the SoC (System on Chip). Interrupt signals are transmitted from peripherals to the interrupt controller, which manages them uniformly and routes them to the processor. The design and implementation of the hardware layer determine the transmission mode of interrupt signals and the hardware’s interrupt handling capability.

Four layers of the interrupt subsystem framework
Four layers of the interrupt subsystem framework

Interrupt Controller GIC

Reference:

VersionKey featuresCommon cores
GICv1Supports up to 8 processor cores (PE) - Supports up to 1020 interrupt IDsARM Cortex-A5 MPCore, ARM Cortex-A9 MPCore, ARM Cortex-R7 MPCore
GICv2Includes all GICv1 features - Supports virtualizationARM Cortex-A7 MPCore, ARM Cortex-A15 MPCore, ARM Cortex-A53 MPCore, ARM Cortex-A57 MPCore
GICv3Includes all GICv2 features - Supports more than 8 processor cores - Supports message-based interrupts - Supports more than 1020 interrupt IDs - System register access to CPU interface registers - Enhanced security model, separating secure and non-secure Group 1 interruptsARM Cortex-A53 MPCore, ARM Cortex-A57 MPCore, ARM Cortex-A72 MPCore
GICv4Includes all GICv3 features - Supports direct injection of virtual interruptsARM Cortex-A53 MPCore, ARM Cortex-A57 MPCore, ARM Cortex-A72 MPCore

Interrupt number

In the Linux kernel, weuse two IDs, IRQ number and HW interrupt ID, to identify an interrupt from a peripheral

  • IRQ number: The CPU needs to number each peripheral interrupt, which we call the IRQ Number. This The IRQ number is a virtual interrupt ID, independent of hardware, and is only used by the CPU to identify a peripheral interrupt.
  • HW interrupt ID: For the GIC interrupt controller, it collects interrupt request lines from multiple peripherals and passes them upward. Therefore, the GIC interrupt controller needs to encode peripheral interrupts.The GIC interrupt controller uses HW interrupt IDs to identify peripheral interrupts.If there is only one GIC interrupt controller, then IRQ numbers and HW interrupt IDs can correspond one-to-one.

But in the case of cascaded GIC interrupt controllers, using only the HW interrupt ID cannot uniquely identify a peripheral interrupt. It is also necessary to know which GIC interrupt controller the HW interrupt ID belongs to (HW interrupt IDs may be encoded repeatedly on different interrupt controllers).

For driver engineers, our perspective is the same as the CPU’s. We only want to get an IRQ number and do not care which HW interrupt ID on which GIC interrupt controller it is. The benefit of this is that when interrupt-related hardware changes, the driver software does not need to be modified. Therefore,The interrupt subsystem in the Linux kernel needs to provide a mechanism to map HW interrupt IDs to IRQ numbers, namely the irq domain.

Interrupt request

request_irq()

include/linux/interrupt.h

1234567891011121314151617181920
/** * request_irq - Add a handler for an interrupt line * @irq:	The interrupt line to allocate * @handler:	Function to be called when the IRQ occurs. *		Primary handler for threaded interrupts *		If NULL, the default primary handler is installed * @flags:	Handling flags * @name:	Name of the device generating this interrupt * @dev:	A cookie passed to the handler function * * This call allocates an interrupt and establishes a handler; see * the documentation for request_threaded_irq() for details. */static inline int __must_checkrequest_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,	    const char *name, void *dev){	return request_threaded_irq(irq, handler, NULL, flags, name, dev);}
ItemDescription
Function definitionint request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev);
Header file#include <linux/interrupt.h>
Parameter irqThe interrupt line (IRQ) number to allocate
Parameter handlerThe handler function called when the interrupt occurs (for threaded interrupts, the main handler)
parameter flagsFlags used when handling the interrupt
Parameter nameThe name of the device that generates the interrupt
Parameter devContext information passed to the handler function (usually the device context)
FunctionAllocate a handler for the specified interrupt line. For threaded interrupts, userequest_threaded_irq()implementation.
Return valueReturns 0 on success; returns a negative error code on failure.
  • irq The parameter is used to specify the interrupt number to request. For example, the GPIO interrupt number needs to be obtained through the gpio_to_irq function maps GPIO pins to obtain it.

  • irq_handler_t handler The parameter is a function pointer pointing to the interrupt handler function. The interrupt handler is a function called when an interrupt event occurs, used to handle the interrupt event.

    1
    typedef irqreturn_t (*irq_handler_t)(int, void *);
  • unsigned long flags: Flags for the interrupt handler

    • IRQF_TRIGGER_NONE: No trigger mode, meaning the interrupt will not be triggered.
    • IRQF_TRIGGER_RISING: Rising edge trigger mode, meaning the interrupt is triggered on the rising edge of the signal.
    • IRQF_TRIGGER_FALLING: Falling edge trigger mode, meaning the interrupt is triggered on the falling edge of the signal.
    • IRQF_TRIGGER_HIGH: High-level trigger mode, meaning the interrupt is triggered when the signal is high.
    • IRQF_TRIGGER_LOW: Low-level trigger mode, meaning the interrupt is triggered when the signal is low.
    • IRQF_SHARED: Interrupt sharing mode, meaning the interrupt can be shared by multiple devices, used for interrupt lines shared by two or more devices. All devices sharing this interrupt line must set this flag. If ignored, only one handler can be registered for this interrupt line.
    • IRQF_TIMER: Notify the kernel that this handler is triggered by the system timer interrupt.
    • IRQ_ONESHOT: Mainly used in threaded interrupts. It requires the kernel not to re-enable the interrupt until the hard interrupt handler has completed. The interrupt remains disabled until the threaded handler runs.
12345678
#define IRQF_TRIGGER_NONE	0x00000000#define IRQF_TRIGGER_RISING	0x00000001#define IRQF_TRIGGER_FALLING	0x00000002#define IRQF_TRIGGER_HIGH	0x00000004#define IRQF_TRIGGER_LOW	0x00000008#define IRQF_TRIGGER_MASK	(IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \				 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)#define IRQF_TRIGGER_PROBE	0x00000010
  • name: Used by the kernel to identify/proc/interruptsand/proc/irqThe driver in.
  • devIts main purpose is to be passed as a parameter to the interrupt handler, which is unique for each interrupt handler because it is used to identify this device. For non-shared interrupts, it can be NULL, but for shared interrupts it cannot be NULL. A common way to use it is to provide the device structure, because it is both unique and potentially useful to the handler. That is, having a pointer to the device data structure is sufficient.

Example:

123456789101112131415161718192021222324
struct my_data {	struct input_dev *idev;	struct i2c_client *client;	char name[64];	char phys[32];};static irqreturn_t my_irq_handler(int irq, void *dev_id){	struct my_data *md = dev_id;	unsigned char nextstate = read_state(lp);	/* Check whether my device raised the irq or no */	[...]	return IRQ_HANDLED;}	/* At certain points in the probe function */	int ret;	struct my_data *md;	md = kzalloc(sizeof(*md), GFP_KERNEL);	ret = request_irq(client->irq, my_irq_handler, IRQF_TRIGGER_LOW | IRQF_ONESHOT, DRV_NAME, md);	/* In the release function*/	free_irq(client->irq, md);

When writing an interrupt handler, there is no need to worry about reentrancy. To avoid interrupt nesting, the interrupt lines serviced by interrupt handlers on all processors are disabled by the kernel.

gpio_to_irq()

include/linux/gpio.h

1234
static inline int gpio_to_irq(unsigned int gpio){	return __gpio_to_irq(gpio);}

gpio_to_The irq function is used toConvert the GPIO pin number to the corresponding interrupt request number

ItemDescription
Function definitionint gpio_to_irq(unsigned int gpio);
Header file#include <linux/gpio.h>
Parameter gpioGPIO (General Purpose Input/Output) number
FunctionConvert a GPIO pin to the corresponding interrupt line number. This function depends on the underlying implementation__gpio_to_irq()to perform the conversion.
Return valueThe corresponding interrupt line number, or a negative value if the conversion fails.

free_irq()

include/linux/interrupt.h

1
extern const void *free_irq(unsigned int, void *);
ItemDescription
Function definitionvoid free_irq(unsigned int irq, void *dev_id);
Header file#include <linux/interrupt.h>
Parameter irqThe interrupt line number to release
Parameter dev_idThe device context associated with this interrupt (usually a device pointer or identifier)
FunctionRelease an allocated interrupt line and remove the interrupt handler associated with that interrupt line.
Return valueNo return value.
  • If the specified IRQ is not shared, thenfree_irqit will remove the interrupt handler and disable the interrupt line.

  • If the IRQ is shared, then only the interrupt handler determined by dev_id (which should be the same asrequest_irqthe one used in) is removed, but the interrupt line remains enabled until the last interrupt handler is removed, after which the interrupt line is disabled.

free_irqIt will block until all executing interrupts for the specified IRQ complete. Avoid using it in interrupt contextrequest_irqandfree_irq

Interrupt handler function

include/linux/interrupt.h

1
typedef irqreturn_t (*irq_handler_t)(int, void *);
  • Function description:

    The handler function is an interrupt service function used to handle specific interrupt events. It is called by the operating system or hardware when an interrupt event occurs, and performs necessary operations to respond to and process the interrupt request.

  • Parameter description:

    • irq: Indicates the identifier of the interrupt number or interrupt source. It indicates the hardware device or interrupt controller that triggered the interrupt.
    • dev_id: It is a pointer of type void, used to pass device-specific data or identifiers. It is typically used to distinguish between different devices or resources in interrupt handlers.
  • Return value:

    • irqreturn_t is an enumeration value of a specific type,**Used to indicate the return status of an interrupt service function.**It can have the following values:
      • IRQ_NONE: Indicates that the interrupt service function did not handle this interrupt, and the interrupt controller can continue processing other interrupt requests.
      • IRQ_HANDLED: Indicates that the interrupt service function has successfully handled the interrupt, and the interrupt controller does not need further processing.
      • IRQ_WAKE_THREAD: Indicates that the interrupt service function has handled the interrupt and requests to wake up a kernel thread to continue further processing. This is used in some interrupt cases that require long-time processing.

Interrupt Handlers and Locks

Interrupt handlers run in atomic context and can only use spinlocks to control concurrency. Whenever there is global data accessible to user code (user tasks, i.e., system calls) and interrupt code, this shared data should be protected in user code.spin_lock_irqsave()protection of.

The priority of interrupt handlers is always higher than user tasks; even if the task holds a spinlock, merely disabling IRQ is not enough, because interrupts may occur on another CPU. If updating
A user task accessing data being interrupted by an interrupt handler that attempts to access the same data would be a disaster. Usespin_lock_irqsave()will disable all interrupts on the local CPU, preventing system calls from being interrupted by any type of interrupt:

1234567891011121314151617181920212223
ssize_t my_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos){	unsigned long flags;	/* some code */	[...]	spin_lock_irqsave(&my_lock, flags); 	data++;	spin_unlock_irqrestore(&my_lock, flags);	[...]}static irqreturn_t my_interrupt_handler(int irq, void *p){	/*	 * Disable preemption while running interrupt handlers	 * serviceIRQThe line is disabled.,until the handler completes	 * No need to disable all othersIRQ,only use spin_lock and spin_unlock	 */	unsigned long flags;	spin_lock_irqsave(&my_lock, flags);	/* Process data */	[...]	spin_unlock_irqrestore(&my_lock, flags);	return IRQ_HANDLED;}

When sharing data between two different interrupt handlers (that is, when the same driver manages two or more devices, each with its own interrupt line), in these handlers one should also usespin_lock_irqsave()to protect shared data, preventing other IRQ triggers and useless spinning.

example

The iTOP-RK3568 has 5 GPIO banks: GPIO0 ~ GPIO4. Each bank is further distinguished by numbering A0 ~ A7, B0 ~ B7, C0 ~ C7, D0 ~ D7. The following formula is commonly used to calculate the pin:
The interrupt pin label corresponding to the LCD touch screen is TP_INT_L_GPIO3_A5, and the corresponding calculation process is as follows:

GPIO pin calculation formula

pin=bank32+numberpin = bank * 32 + number

GPIO group number calculation formula:

number=group8+Xnumber = group * 8 + X

For GPIO3_A5, 3 means bank=3, A means group=0, 5 means X=5

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
#include <linux/module.h>#include <linux/init.h>#include <linux/gpio.h>#include <linux/interrupt.h>#define GPIO_PIN 101// Interrupt handler functionstatic irqreturn_t gpio_irq_handler(int irq, void *dev_id){    printk(KERN_INFO "Interrupt occurred on GPIO %d\n", GPIO_PIN);    printk(KERN_INFO "This is irq_handler\n");    return IRQ_HANDLED;}static int __init interrupt_init(void){    int irq_num;    printk(KERN_INFO "Initializing GPIO Interrupt Driver\n");    // Map GPIO pin to interrupt number    irq_num = gpio_to_irq(GPIO_PIN);    printk(KERN_INFO "GPIO %d mapped to IRQ %d\n", GPIO_PIN, irq_num);    if (irq_num < 0){        return -ENODEV;    }    // Request interrupt    if (request_irq(irq_num, gpio_irq_handler, IRQF_TRIGGER_RISING, "irq_test", NULL) != 0) {        printk(KERN_ERR "Failed to request IRQ %d\n", irq_num);        // Failed to request interrupt, release GPIO pin        gpio_free(GPIO_PIN);        return -ENODEV;    }    return 0;}static void __exit interrupt_exit(void){    int irq_num = gpio_to_irq(GPIO_PIN);    // Release interrupt    free_irq(irq_num, NULL);    printk(KERN_INFO "GPIO Interrupt Driver exited successfully\n");}module_init(interrupt_init);module_exit(interrupt_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("topeet");

Interrupt request function and data structure

request_irq()

include/linux/irq.h

123456
static inline int __must_checkrequest_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,	    const char *name, void *dev){	return request_threaded_irq(irq, handler, NULL, flags, name, dev);}

request_threaded_irq()

kernel/irq/manage.c

request_threaded_irqThe function is a powerful function provided by the Linux kernel, used to request allocation of an interrupt and associate the interrupt handler with that interrupt. The main function of this function is to register an interrupt handler in the system to respond to the occurrence of the corresponding interrupt.

The following arerequest_threaded_irqA detailed introduction to the function’s functionality and purpose:

  • Interrupt requestrequest_threaded_irqThe function is used to request an interrupt. It registers the interrupt handler for the corresponding interrupt number with the kernel and allocates necessary resources for the interrupt. The interrupt number is a unique identifier for a specific hardware interrupt.
  • Interrupt handler association: viahandlerThe parameter associates the interrupt handler with the interrupt number. The interrupt handler is a predefined function used to handle interrupt events. When an interrupt occurs, the kernel calls this function to handle the interrupt event.
  • Threaded interrupt handlingrequest_threaded_irqThe function also supports using threaded interrupt handlers. By specifyingthread_fnparameter, it can asynchronously execute longer interrupt processing or delay-sensitive work in a kernel thread context. This helps avoid blocking for too long in interrupt context.
  • Interrupt attribute settings: viairqflagsparameter, you can set various attributes and flags for interrupt handling. For example, you can specify the interrupt trigger mode (rising edge, falling edge, edge-triggered, etc.), interrupt type (edge-triggered interrupt, level-triggered interrupt, etc.), and other specific interrupt behaviors.
  • Device identifier association: viadev_idparameter, you can associate interrupt handling with a specific device. This allows access to device-related data in the interrupt handler. The device identifier can be a pointer to a device structure or other device-related data.
  • Error handlingrequest_threaded_irqThe function returns an integer value indicating the result of the interrupt request. If the interrupt request succeeds, the return value is 0; if the interrupt request fails, it returns a negative error code indicating the reason for the failure.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
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){	struct irqaction *action;// Interrupt action structure pointer	struct irq_desc *desc;// Interrupt descriptor pointer	int retval; // Return value// Check whether the interrupt number is in the unconnected state	if (irq == IRQ_NOTCONNECTED)		return -ENOTCONN;	/*	 * Sanity-check: shared interrupts must pass in a real dev-ID,	 * otherwise we'll have trouble later trying to figure out	 * which interrupt is which (messes up the interrupt freeing	 * logic etc).	 *	 * Also shared interrupts do not go well with disabling auto enable.	 * The sharing interrupt might request it while it's still disabled	 * and then wait for interrupts forever.	 *	 * Also IRQF_COND_SUSPEND only makes sense for shared interrupts and	 * it cannot be set along with IRQF_NO_SUSPEND.	 */        // Check the validity of the interrupt flags	if (((irqflags & IRQF_SHARED) && !dev_id) ||	    ((irqflags & IRQF_SHARED) && (irqflags & IRQF_NO_AUTOEN)) ||	    (!(irqflags & IRQF_SHARED) && (irqflags & IRQF_COND_SUSPEND)) ||	    ((irqflags & IRQF_NO_SUSPEND) && (irqflags & IRQF_COND_SUSPEND)))		return -EINVAL;        // Get the interrupt descriptor for the interrupt number	desc = irq_to_desc(irq);	if (!desc)		return -EINVAL;        // Check whether the interrupt setting can make an interrupt request, and whether a unique device ID is allocated for each CPU	if (!irq_settings_can_request(desc) ||	    WARN_ON(irq_settings_is_per_cpu_devid(desc)))		return -EINVAL;        // If no interrupt handler is specified, use the default main handler	if (!handler) {		if (!thread_fn)			return -EINVAL;		handler = irq_default_primary_handler;	}        // Allocate and initialize the interrupt action data structure	action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);	if (!action)		return -ENOMEM;	action->handler = handler;// Interrupt handler function	action->thread_fn = thread_fn;// Thread handler	action->flags = irqflags;// Interrupt flags	action->name = devname;// device name	action->dev_id = dev_id;// Device ID        // Get the power management reference count of the interrupt	retval = irq_chip_pm_get(&desc->irq_data);	if (retval < 0) {		kfree(action);		return retval;	}        // Set up the interrupt and associate the interrupt action with the interrupt descriptor	retval = __setup_irq(irq, desc, action);        // Handle the case of interrupt setup failure	if (retval) {		irq_chip_pm_put(&desc->irq_data);//Call irq_chip_The pm_put function releases the power management reference count of the interrupt		kfree(action->secondary);//Free the memory space of the secondary interrupt action		kfree(action);//Free the memory space of the interrupt action	}#ifdef CONFIG_DEBUG_SHIRQ_FIXME	if (!retval && (irqflags & IRQF_SHARED)) {		/*		 * It's a shared IRQ -- the driver ought to be prepared for it		 * to happen immediately, so let's make sure....		 * We disable the irq to make sure that a 'real' IRQ doesn't		 * run in parallel with our fake.		 */		unsigned long flags;		disable_irq(irq);		local_irq_save(flags);		handler(irq, dev_id);		local_irq_restore(flags);		enable_irq(irq);	}#endif	return retval;}EXPORT_SYMBOL(request_threaded_irq);

struct irq_desc

include/linux/irqdesc.h

irq_The desc structure is one of the data structures used in the Linux kernel to describe interrupts. Each hardware interrupt has a corresponding irq_desc instance, which is used to record various information and status related to the interrupt. The main function of this structure is to manage interrupt handlers, interrupt behavior, and other data related to interrupt handling.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
struct irq_desc {    struct irq_common_data irq_common_data;  // Common interrupt data, including the interrupt identifier, type, and other information.    struct irq_data irq_data;                // Specific interrupt data, including the interrupt status, trigger mode, and so on.    unsigned int __percpu *kstat_irqs;       // Statistics for this interrupt, statistical data on each CPU.    irq_flow_handler_t handle_irq;           // Pointer to the interrupt handler, the actual function that handles this interrupt.    struct irqaction *action;                // Interrupt handling action linked list, used to store associated interrupt handlers.    unsigned int status_use_accessors;       // Status field indicating whether the status is read/modified through accessors.    unsigned int core_internal_state__do_not_mess_with_it;  // Internal status field; direct manipulation may cause instability.    unsigned int depth;                      // Nested interrupt disable counter.    unsigned int wake_depth;                 // Nested wake-up enable counter.    unsigned int tot_count;                  // Total interrupt count.    unsigned int irq_count;                  // Used to detect problematic interrupt counts.    unsigned long last_unhandled;            // Timer for handling unhandled interrupts.    unsigned int irqs_unhandled;             // Records the number of unhandled interrupts.    atomic_t threads_handled;                // Atomic count representing the number of threads being processed.    int threads_handled_last;                // Last processed thread count (for debugging/statistics).    raw_spinlock_t lock;                     // Spinlock used to protect this interrupt descriptor.    struct cpumask *percpu_enabled;          // Per-CPU enabled CPU mask.    const struct cpumask *percpu_affinity;   // Per-CPU interrupt affinity mask.#ifdef CONFIG_SMP    const struct cpumask *affinity_hint;     // Interrupt affinity hint.    struct irq_affinity_notify *affinity_notify;  // Notification structure for handling affinity changes.#ifdef CONFIG_GENERIC_PENDING_IRQ    cpumask_var_t pending_mask;              // Indicates the mask of currently pending interrupts.#endif#endif    unsigned long threads_oneshot;           // Used to record the state of one-shot threads.    atomic_t threads_active;                 // Current number of active threads (atomic operation).    wait_queue_head_t wait_for_threads;      // Wait queue head for thread processing.#ifdef CONFIG_PM_SLEEP    unsigned int nr_actions;                // Number of interrupt handling actions (related to power management).    unsigned int no_suspend_depth;           // Depth at which interrupts cannot be suspended.    unsigned int cond_suspend_depth;         // Depth for conditional suspension.    unsigned int force_resume_depth;         // Depth for forced resume.#endif#ifdef CONFIG_PROC_FS    struct proc_dir_entry *dir;             // Interrupt proc filesystem entry for debugging and monitoring.#endif#ifdef CONFIG_GENERIC_IRQ_DEBUGFS    struct dentry *debugfs_file;            // Interrupt debugfs file entry for debugging.    const char *dev_name;                   // Device name, usually used for debugging.#endif#ifdef CONFIG_SPARSE_IRQ    struct rcu_head rcu;                    // RCU head used for deferred release of interrupt resources.    struct kobject kobj;                    // Kernel object used to associate and manage this interrupt descriptor.#endif    struct mutex request_mutex;             // Mutex for requesting interrupts, protecting concurrent access to interrupt requests.    int parent_irq;                          // If this interrupt is a child interrupt, its parent interrupt ID.    struct module *owner;                   // Associated module pointer, indicating which module is responsible for this interrupt.    const char *name;                       // Interrupt name for debugging and identification.} ____cacheline_internodealigned_in_smp;  // Used to ensure cache alignment of this structure in SMP (Symmetric Multiprocessing) systems.

irq_descthe main functions and features of the structure:

  • Interrupt handler function managementirq_descin the structurehandle_irqThe field stores a pointer to the interrupt handler function. When hardware triggers an interrupt, the kernel calls this function to handle the interrupt event.
  • Interrupt action managementirq_descin the structureactionThe field is a pointer to a list of interrupt actions. An interrupt action is a set of callback functions used to register, unregister, and handle interrupt-related events.
  • Interrupt statisticsirq_descin the structurekstat_irqsThe field is a pointer to interrupt statistics. This information records the number of occurrences and handling of interrupt events, helping to analyze the performance and behavior of interrupts.
  • Interrupt data managementirq_descin the structureirq_dataThe field stores interrupt-related data, such as interrupt number and interrupt type. This data is used to identify and manage interrupts.
  • Generic interrupt data managementirq_descin the structureirq_common_dataThe field stores generic data related to interrupt handling, such as interrupt controller and interrupt masking. This data is used to handle and control interrupt behavior.
  • Interrupt state managementirq_descOther fields in the structure are used to manage interrupt state, such as nested interrupt disable count and wake-up enable count. This state information helps the kernel track and manage interrupt state changes. By usingirq_descthe structure, the kernel can effectively manage and handle hardware interrupts in the system. It provides a unified interface for registering and handling interrupt handlers, managing interrupt actions, and provides the necessary information and data structures to monitor and control interrupt behavior and state.

Beforeirq_descThe most important thing in the structure isactionfield.

struct irqaction

include/linux/interrupt.h

The irqaction structure is one of the data structures in the Linux kernel used to describe interrupt actions. It is used to define callback functions and related attributes during interrupt handling. The main function of the irqaction structure is to manage actions and handler functions associated with a specific interrupt.

123456789101112131415161718192021222324252627
struct irqaction {    irq_handler_t handler;                 // Pointer to the interrupt handler function. A callback function used to handle interrupts, usually a common interrupt service routine (ISR).    void *dev_id;                           // Device ID. Usually a pointer to a device structure (or other type of identifier), used to uniquely identify the device associated with the interrupt.    void __percpu *percpu_dev_id;           // Device ID on each CPU. Applicable to multiprocessor systems, storing the device identifier on each CPU.    struct irqaction *next;                 // Link for shared interrupts. Each interrupt number may correspond to multiple irqactions; this field points to the next irqaction.    irq_handler_t thread_fn;                // Handler function for threaded interrupts. For threaded interrupts,`thread_fn` Used to handle related work.    struct task_struct *thread;             // Thread pointer. For threaded interrupts, this field points to the thread assigned to the interrupt.    struct irqaction *secondary;            // Pointer to the secondary irqaction. Used to force certain interrupt operations to be processed in a thread.    unsigned int irq;                       // Interrupt number, identifying the interrupt associated with this irqaction.    unsigned int flags;                     // Interrupt flags.`IRQF_*` Flags control the behavior of the interrupt, such as whether sharing is allowed, whether it is handled in a kernel thread, etc.    unsigned long thread_flags;             // Thread flags, indicating settings or status related to threaded interrupts.    unsigned long thread_mask;              // Thread mask, used to track the active state of the thread.    const char *name;                       // Name of the interrupt handler action, usually the device name, used for debugging and identification.    struct proc_dir_entry *dir;             // Pointer to the proc filesystem entry, used to `/proc/irq/NN/name` display interrupt information in.} ____cacheline_internodealigned_in_smp;    // Ensure that the structure is aligned to cache lines in multi-core systems.

The following areirqactionthe main functions and features of the structure:

  • Interrupt handler function managementirqactionin the structurehandlerThe field stores the pointer to the interrupt handler function. This function is called when an interrupt occurs to handle the interrupt event.
  • Interrupt handling flag managementirqactionin the structureflagsThe field is used to specify various attributes and flags of interrupt handling. These flags control the behavior of interrupt handling, such as trigger mode, interrupt type, etc.
  • Device identifier managementirqactionin the structuredev_idThe field is used to store the device identifier associated with interrupt handling. It can be a pointer to a device structure or other device-related data, used to associate interrupt handling with a specific device.
  • Interrupt action linked list managementirqactionin the structurenextThe field is a pointer to the nextirqactionpointer to a structure, used to build a linked list of interrupt actions. This allows multiple interrupt handler functions to be linked together so that they can be called in sequence when an interrupt occurs.

Work deferral mechanism

Deferral is a method of scheduling work to be done for later execution; it postpones operations. It allows any type of function to be deferred and executed.

  • SoftIRQ: Executes in atomic context.
  • Tasklet: Executes in atomic context.
  • WorkQueue: Executes in process context.

Softirq (soft interrupt or software interrupt) is a deferral mechanism used only for fast processing, because itruns with the scheduler disabled (in interrupt context).. Softirq is rarely (almost never) used directly; only the network and block device subsystems use Softirq.

Tasklet is an instance of Softirq; for almost every situation where Softirq is needed, Tasklet is sufficient.

In most cases, Softirq is scheduled in hardware interrupts. These interrupts occur faster than the speed at which they can be serviced, so the kernel queues them for later processing. Ksoftirqd is responsible for deferred execution (process context). Ksoftirqd is a per-CPU kernel thread used to handle unserviced software interrupts. If CPU resources are heavily consumed by Ksoftirqd, it indicates that the system is overloaded or under an interrupt storm.

tasklet

In the Linux kernel,tasklet is a special soft interrupt mechanism, widely used to handle tasks related to the interrupt bottom half. The Tasklet deferral mechanism is used in most cases in DMA, network, and block device drivers.

Tasklets are essentially non-reentrant. If code can be interrupted anywhere during execution and can be safely called again afterwards, it is called reentrant. Tasklets are designed so that they can only run on one CPU (even on SMP systems), namely the CPU that scheduled them. Different tasklets can run simultaneously on different CPUs.

It is a common and effective method that can avoid concurrency problems on multi-core systems.The function bound to a Tasklet can only run on one CPU at a time, so there will be no concurrency conflicts.

Note

  • Calling on a Tasklet that has already been scheduled but has not yet started executingtasklet_schedulewill not perform any operation, and the Tasklet will ultimately execute only once.

  • It can be called within a Tasklettasklet_schedule, which means a Tasklet can reschedule itself.

  • High-priority tasklets are always executed before normal-priority tasklets. Abusing high-priority tasks increases system latency. Be sure to use them only when truly fast execution is needed.

  • The function bound to a tasklet must not call functions that may cause sleeping, otherwise it may cause a kernel exception.

This API is deprecated.

include/linux/interrupt.h

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
/* Tasklets --- multithreaded analogue of BHs.   This API is deprecated. Please consider using threaded IRQs instead:   https://lore.kernel.org/lkml/20200716081538.2sivhkj4hcyrusem@linutronix.de   Main feature differing them of generic softirqs: tasklet   is running only on one CPU simultaneously.   Main feature differing them of BHs: different tasklets   may be run simultaneously on different CPUs.   Properties:   * If tasklet_schedule() is called, then tasklet is guaranteed     to be executed on some cpu at least once after this.   * If the tasklet is already scheduled, but its execution is still not     started, it will be executed only once.   * If this tasklet is already running on another CPU (or schedule is called     from tasklet itself), it is rescheduled for later.   * Tasklet is strictly serialized wrt itself, but not     wrt another tasklets. If client needs some intertask synchronization,     he makes it with spinlocks. */struct tasklet_struct{	struct tasklet_struct *next;	unsigned long state;	atomic_t count;	bool use_callback;	union {		void (*func)(unsigned long data);		void (*callback)(struct tasklet_struct *t);	};	unsigned long data;};#define DECLARE_TASKLET(name, _callback)		\struct tasklet_struct name = {				\	.count = ATOMIC_INIT(0),			\	.callback = _callback,				\	.use_callback = true,				\}#define DECLARE_TASKLET_DISABLED(name, _callback)	\struct tasklet_struct name = {				\	.count = ATOMIC_INIT(1),			\	.callback = _callback,				\	.use_callback = true,				\}#define from_tasklet(var, callback_tasklet, tasklet_fieldname)	\	container_of(callback_tasklet, typeof(*var), tasklet_fieldname)#define DECLARE_TASKLET_OLD(name, _func)		\struct tasklet_struct name = {				\	.count = ATOMIC_INIT(0),			\	.func = _func,					\}#define DECLARE_TASKLET_DISABLED_OLD(name, _func)	\struct tasklet_struct name = {				\	.count = ATOMIC_INIT(1),			\	.func = _func,					\}

tasklet_structThe structure contains the following members:

  • next: Pointer to the next tasklet, used to form a linked list structure so that the kernel can manage multiple tasklets simultaneously.
  • state: Indicates the current state of the tasklet.
  • count: Used for reference counting to ensure correct handling when the tasklet is scheduled or unscheduled in multiple places.
  • func: Pointer to the function bound to the tasklet, which will be called when the tasklet is executed.
  • data: Parameter passed to the tasklet-bound function. In the 5.10 kernel, the callback function is used.

In addition, for convenience, the following is also definedtasklet_ttype asstruct tasklet_structan alias of.

Static initialization: DECLARE_TASKLET

123456
#define DECLARE_TASKLET(name, _callback)		\struct tasklet_struct name = {				\	.count = ATOMIC_INIT(0),			\	.callback = _callback,				\	.use_callback = true,				\}

Here, name is the name of the tasklet, func is the handler function of the tasklet, and data is the parameter passed to the handler function (there is no data in version 5.10). The initial state is enabled.

123456
#define DECLARE_TASKLET_DISABLED(name, _callback)	\struct tasklet_struct name = {				\	.count = ATOMIC_INIT(1),			\	.callback = _callback,				\	.use_callback = true,				\}

Here, name is the name of the tasklet, func is the handler function of the tasklet, and data is the parameter passed to the handler function (there is no data in version 5.10). The initial state is disabled.

example

123456789101112
#include <linux/interrupt.h>// Define the tasklet handler functionvoid my_tasklet_handler(struct tasklet_struct *t){	// Tasklet processing logic	// ...}// Statically initialize the taskletDECLARE_TASKLET(my_tasklet, my_tasklet_handler);// Other driver code

In the above example,my_taskletis the name of the tasklet,my_tasklet_handleris the handler function of the tasklet

However, it should be noted that,A tasklet statically initialized with DECLARE_TASKLET cannot be dynamically destroyed at runtime, therefore, when the tasklet is no longer needed, this method should be avoided. If you need to destroy the tasklet at runtime, you should usetasklet_initandtasklet_killfunction for dynamic initialization and destruction.

Dynamic initialization task_init

12
extern void tasklet_init(struct tasklet_struct *t,			 void (*func)(unsigned long), unsigned long data);

Here, t is a pointer to the tasklet structure, func is the tasklet’s handler function, and data is the parameter passed to the handler function.

example

1234567891011121314
#include <linux/interrupt.h>// Define the tasklet handler functionvoid my_tasklet_handler(unsigned long data){	// Tasklet processing logic	// ...}// Declare the tasklet structurestatic struct tasklet_struct my_tasklet;// Initialize the tasklettasklet_init(&my_tasklet, my_tasklet_handler, 0);// Other driver code

In the example, we first definedmy_tasklet_handleras the tasklet’s handler function. Then, we declared a tasklet structure namedmy_taskletNext, by calling thetasklet_initfunction to perform dynamic initialization.

By usingtasklet_initfunction, we can dynamically create and initialize tasklets at runtime. In this way, we can flexibly manage and control the lifecycle of tasklets as needed. When tasklets are no longer needed, we can usetasklet_killfunction to destroy them, thereby releasing related resources.

Disable function tasklet_disable

123456
static inline void tasklet_disable(struct tasklet_struct *t){	tasklet_disable_nosync(t);	tasklet_unlock_wait(t);	smp_mb();}

Here, t is a pointer to the tasklet structure.

example

12345678910111213141516171819
#include <linux/interrupt.h>// Define the tasklet handler functionvoid my_tasklet_handler(unsigned long data){	// Tasklet processing logic	// ...}// Declare the tasklet structurestatic struct tasklet_struct my_tasklet;// Initialize the tasklettasklet_init(&my_tasklet, my_tasklet_handler, 0);// Disable the tasklettasklet_disable(&my_tasklet);// Other driver code

In the above example, we first definedmy_tasklet_handleras the tasklet’s handler function. Then, we declared a tasklet structure namedmy_taskletand usedtasklet_initThe function initializes it.

Finally, by callingtasklet_disablefunction, we disablemy_tasklet

After disabling the tasklet, even if callingtasklet_schedulefunction triggers the tasklet, the tasklet’s handler function will not be executed again. This canbe used to temporarily pause or stop the execution of the tasklet until it is re-enabled(by calling the tasklet_enable function).

It should be noted that disabling the tasklet does not destroy the tasklet structure, so you can at any time by calling tasklet_enable function to re-enable the tasklet, or call tasklet_kill function to destroy the tasklet

Enable function tasklet_enable

12345
static inline void tasklet_enable(struct tasklet_struct *t){	smp_mb__before_atomic();	atomic_dec(&t->count);}

Here, t is a pointer to the tasklet structure

example

1234567891011121314151617
#include <linux/interrupt.h>// Define the tasklet handler functionvoid my_tasklet_handler(unsigned long data){	// Tasklet processing logic	// ...}// Declare the tasklet structurestatic struct tasklet_struct my_tasklet;// Initialize the tasklettasklet_init(&my_tasklet, my_tasklet_handler, 0);// Enable the tasklettasklet_enable(&my_tasklet);

After enabling the tasklet, if you calltasklet_schedulefunction to trigger the tasklet, the tasklet’s handler function will be executed. In this way, the tasklet will begin to execute its processing logic as planned.

It should be noted that enabling the tasklet does not automatically trigger the execution of the tasklet; instead, it is triggered by callingtasklet_schedulefunction. At the same time, you can usetasklet_disablefunction to temporarily pause or stop the execution of the tasklet.

If you need to permanently stop the execution of the tasklet and release related resources, you should calltasklet_killfunction to destroy the tasklet.

Scheduling function tasklet_schedule

123456789101112131415
extern void __tasklet_schedule(struct tasklet_struct *t);static inline void tasklet_schedule(struct tasklet_struct *t){	if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))		__tasklet_schedule(t);}extern void __tasklet_hi_schedule(struct tasklet_struct *t);static inline void tasklet_hi_schedule(struct tasklet_struct *t){	if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))		__tasklet_hi_schedule(t);}

Here, t is a pointer to the tasklet structure.

The kernel maintains normal-priority and high-priority tasklets in two different linked lists.tasklet_scheduleTo add a tasklet to the normal-priority linked list, useTASKLET_SOFTIRQflag the associated softirq for scheduling.

tasklet_hi_scheduleTo add a tasklet to the high-priority linked list, and useHI_SOFTIRQflag the associated softirq for scheduling. High-priority tasklets are intended for softirq handlers with low-latency requirements.

example

123456789101112131415
#include <linux/interrupt.h>// Define the tasklet handler functionvoid my_tasklet_handler(unsigned long data){	// Tasklet processing logic	// ...}// Declare the tasklet structurestatic struct tasklet_struct my_tasklet;// Initialize the tasklettasklet_init(&my_tasklet, my_tasklet_handler, 0);// Schedule the tasklet for executiontasklet_schedule(&my_tasklet);// Other driver code

Note that scheduling a tasklet only marks the tasklet as needing to be executedIt does not immediately execute the tasklet’s handler function. The actual execution time depends on the kernel’s scheduling and processing mechanism.

The destruction function tasklet_kill

12
extern void tasklet_kill(struct tasklet_struct *t);extern void tasklet_kill_immediate(struct tasklet_struct *t, unsigned int cpu);

Here, t is a pointer to the tasklet structure

example

12345678910111213141516
#include <linux/interrupt.h>// Define the tasklet handler functionvoid my_tasklet_handler(unsigned long data){	// Tasklet processing logic	// ...}// Declare the tasklet structurestatic struct tasklet_struct my_tasklet;// Initialize the tasklettasklet_init(&my_tasklet, my_tasklet_handler, 0);tasklet_disable(&my_tasklet);// Destroy the tasklettasklet_kill(&my_tasklet);// Other driver code

Calltasklet_killThe function releases the resources occupied by the tasklet and marks the tasklet as invalid. Therefore, a destroyed tasklet can no longer be used.

It should be noted that,Before destroying a tasklet, you should ensure that the tasklet has been disabled.(by calling tasklet_disable function). Otherwise, destroying a running tasklet may cause a kernel crash or other errors.

Once a tasklet is destroyed, if you need to use it again, you must reinitialize it (by callingtasklet_initfunction)

example

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
#include <linux/init.h>#include <linux/module.h>#include <linux/interrupt.h>#include <linux/gpio.h>#define TEST_GPIO_PIN 101static int irq;static struct tasklet_struct mytasklet;irqreturn_t test_irq_handler(int irq, void *dev_id){        pr_info("test_irq_handler is called\n");        tasklet_schedule(&mytasklet);        return IRQ_RETVAL(IRQ_HANDLED);}void mytasklet_func(unsigned long data){        pr_info("data is %lu\n", data);}static int __init tasklet_test_init(void){        int ret;        irq = gpio_to_irq(TEST_GPIO_PIN);        pr_info("irq is %d\n", irq);        if (irq < 0)                return -ENODEV;        ret = request_irq(irq, test_irq_handler, IRQF_TRIGGER_RISING, "test", NULL);        if (ret < 0)                return -ENODEV;        tasklet_init(&mytasklet, mytasklet_func, 1);        tasklet_enable(&mytasklet);// optional: after init, it is enabled by default        return 0;}static void __exit tasklet_test_exit(void){        tasklet_disable(&mytasklet);        tasklet_kill(&mytasklet);        free_irq(irq, NULL);        printk("bye\n");}module_init(tasklet_test_init);module_exit(tasklet_test_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@outlook.com>");MODULE_DESCRIPTION("This is a test sample for tasklet");

Softirq

Software interrupt = an exception/interrupt mechanism triggered by software, used to enter the kernel.

Softirq = a “bottom half” execution mechanism inside the Linux kernel, used to handle high-frequency short tasks.

Softirq is a type ofinterrupt bottom-half mechanism, used to execute high-frequency, deferrable tasks.

It is not a CPU interrupt, nor an exception, but the kernel’s own scheduling mechanism.

12345678910111213141516
// include/linux/interrupt.henum{	HI_SOFTIRQ=0,	TIMER_SOFTIRQ,	NET_TX_SOFTIRQ,	NET_RX_SOFTIRQ,	BLOCK_SOFTIRQ,	IRQ_POLL_SOFTIRQ,	TASKLET_SOFTIRQ, // Tasklet softirq	SCHED_SOFTIRQ,	HRTIMER_SOFTIRQ,	RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */	NR_SOFTIRQS};

The above code defines an enum type used to identify different types or priorities of softirqs. Each enum constant corresponds to a specific softirq type.

Meaning of the enum constants:

  • HI_SOFTIRQ: high-priority softirq
  • TIMER_SOFTIRQ: timer softirq
  • NET_TX_SOFTIRQ: network transmit softirq
  • NET_RX_SOFTIRQ: network receive softirq
  • BLOCK_SOFTIRQ: block device softirq
  • IRQ_POLL_SOFTIRQ: interrupt polling softirq
  • TASKLET_SOFTIRQtasklet softirq
  • SCHED_SOFTIRQ: scheduler softirq
  • HRTIMER_SOFTIRQ: Unused, but kept as tools rely on the numbering. Sigh!
  • RCU_SOFTIRQ: Preferable RCU should always be the last softirq
  • NR_SOFTIRQS: represents the total number of softirqs, used to indicate the number of softirq types

The smaller the priority number of an interrupt, the higher the priority. In driver code, we can use the softirqs mentioned above in the Linux driver code, and of course we can also add our own softirqs. We add a custom softirq as shown below, where TEST_SOFTIRQ is the custom-added softirq.

1234567891011121314151617
// include/linux/interrupt.henum{	HI_SOFTIRQ=0,	TIMER_SOFTIRQ,	NET_TX_SOFTIRQ,	NET_RX_SOFTIRQ,	BLOCK_SOFTIRQ,	IRQ_POLL_SOFTIRQ,	TASKLET_SOFTIRQ,	SCHED_SOFTIRQ,	HRTIMER_SOFTIRQ, /* Unused, but kept as tools rely on thenumbering. Sigh! */	TEST_SOFTIRQ, // Added custom softirq	RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */	NR_SOFTIRQS};

Although adding a custom softirq is very simple,Linux kernel developers do not want us to do this. If we need to use softirqs, it is recommended to use tasklets.

After adding a softirq, you need to recompile the Linux source code. Moreover, the softirq interface functions do not have an exported symbol table; if you want to use them, you need tokernel/softirq.c

123456
// kernel/softirq.cvoid open_softirq(int nr, void (*action)(struct softirq_action *)){	softirq_vec[nr].action = action;}EXPORT_SYMBOL(open_softirq)

Softirq interface functions

open_softirq

To register a softirq, use the open_softirq function, whose prototype is as follows:

1
void open_softirq(int nr, void (*action)(struct softirq_action *));
  • nr: The number or priority of the softirq. It is an integer representing the identifier of the softirq to be registered.
  • action: A pointer to a function that will serve as the softirq handler. This function accepts a struct
  • softirq_actiontype parameter.

raise_softirq

To trigger a softirq, useraise_softirqfunction, whose prototype is as follows:

1
void raise_softirq(unsigned int nr);
  • nr: The number or priority of the softirq. It is an integer representing the identifier of the softirq to be registered.

raise_softirq_irqoff

When hardware interrupts are disabled, trigger a softirq using raise_softirq_irqoff function, whose prototype is as follows:

1
void raise_softirq_irqoff(unsigned int nr);
  • nr: The number or priority of the softirq. It is an integer representing the identifier of the softirq to be registered.

example

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
#include <linux/module.h>#include <linux/init.h>#include <linux/interrupt.h>#include <linux/gpio.h>int irq;// Softirq handlervoid testsoft_func(struct softirq_action *softirq_action){  printk("This is testsoft_func\n");}irqreturn_t test_interrupt(int irq, void *args){  printk("This is test_interrupt\n");  raise_softirq(TEST_SOFTIRQ); // Trigger a softirq.  return IRQ_RETVAL(IRQ_HANDLED);}static int interrupt_irq_init(void){  int ret;  irq = gpio_to_irq(101); // Map GPIO to interrupt number  printk("irq is %d\n", irq);  // Request interrupt  ret = request_irq(irq, test_interrupt, IRQF_TRIGGER_RISING, "test", NULL);  if (ret < 0)  {    printk("request_irq is error\n");    return -1;  }  // Register softirq handler function  open_softirq(TEST_SOFTIRQ, testsoft_func);  return 0;}static void interrupt_irq_exit(void){  free_irq(irq, NULL); // Release interrupt  printk("bye bye\n");}module_init(interrupt_irq_init);module_exit(interrupt_irq_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@outlook.com>");MODULE_DESCRIPTION("Tis is a test sample for softirq");

Tasklet analysis

A tasklet is a softirq mechanism in the Linux kernel. It can be regarded as a lightweight deferred processing mechanism. It is implemented through the softirq control structure, so it is also called a softirq.

1234567891011121314151617
// kernel/softirq.cvoid __init softirq_init(void){	int cpu;// Initialize the tasklet for each possible CPU_vec and tasklet_hi_vec// Set the tail pointer to the initial position of the corresponding head pointer.	for_each_possible_cpu(cpu) {		per_cpu(tasklet_vec, cpu).tail =			&per_cpu(tasklet_vec, cpu).head;		per_cpu(tasklet_hi_vec, cpu).tail =			&per_cpu(tasklet_hi_vec, cpu).head;	}// Register TASKLET_SOFTIRQ soft interrupt, and specify the corresponding handler function as tasklet._action	open_softirq(TASKLET_SOFTIRQ, tasklet_action);// Register HI_SOFTIRQ soft interrupt, and specify the corresponding handler function as tasklet._hi_action	open_softirq(HI_SOFTIRQ, tasklet_hi_action);}
  • for_each_possible_cpu(cpu): Iterate over each possible CPU. In a multi-core system, this loop is used to initialize each CPU’stasklet_vecandtasklet_hi_vec
  • per_cpu(tasklet_vec, cpu).tail = &per_cpu(tasklet_vec, cpu).head;: Set each CPU’stasklet_vectail pointer to the initial position of the corresponding head pointer. This is done toensure that the initial state of tasklet_vec is empty.
  • per_cpu(tasklet_hi_vec, cpu).tail = &per_cpu(tasklet_hi_vec, cpu).head;: Set each CPU’stasklet_hi_vectail pointer to the initial position of the corresponding head pointer. This is done toensure that the initial state of tasklet_hi_vec is empty.
  • open_softirq(TASKLET_SOFTIRQ, tasklet_action);Register the TASKLET_SOFTIRQ soft interrupt, and specify the corresponding handler function as tasklet_action. In this way, when TASKLET_SOFTIRQ is triggered, the tasklet_action function will be called to handle the corresponding tasks.
  • open_softirq(HI_SOFTIRQ, tasklet_hi_action);: RegisterHI_SOFTIRQsoft interrupt, and specify the corresponding handler function astasklet_hi_action. In this way, whenHI_SOFTIRQis triggered, it will calltasklet_hi_actionfunction to handle the corresponding tasks.

When executing__init softirq_init the function, it will triggerTASKLET_SOFTIRQ, and then it will calltasklet_actionfunction,tasklet_actionThe function is as follows:

12345678910
// kernel/softirq.cstatic __latent_entropy void tasklet_action(struct softirq_action *a){	tasklet_action_common(a, this_cpu_ptr(&tasklet_vec), TASKLET_SOFTIRQ);}static __latent_entropy void tasklet_hi_action(struct softirq_action *a){	tasklet_action_common(a, this_cpu_ptr(&tasklet_hi_vec), HI_SOFTIRQ);}

The above function callstasklet_action_commonfunction

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
// kernel/softirq.cstatic void tasklet_action_common(struct softirq_action *a,				  struct tasklet_head *tl_head,				  unsigned int softirq_nr){	struct tasklet_struct *list;    // Disable local interrupts	local_irq_disable();    // Get the task list in tasklet_head	list = tl_head->head;    // Clear the task list in tasklet_head	tl_head->head = NULL;    // Point the tail pointer back to the head pointer.	tl_head->tail = &tl_head->head;    // Enable local interrupts.	local_irq_enable();    // Traverse the task list and process each tasklet.	while (list) {		struct tasklet_struct *t = list;        // Get the next tasklet and update the list.		list = list->next;		if (tasklet_trylock(t)) {// Try to acquire the tasklet's lock.			if (!atomic_read(&t->count)) {// Check whether the count counter is 0.				if (!test_and_clear_bit(TASKLET_STATE_SCHED,							&t->state))					BUG();// If the state flag is not correct, an error occurs.				if (t->use_callback)					t->callback(t);				else					t->func(t->data);// Execute the tasklet's handler function.				tasklet_unlock(t);// Unlock the tasklet.				continue;			}			tasklet_unlock(t);// Unlock the tasklet.		}    	// Disable local interrupts		local_irq_disable();        // Add the current tasklet to the tail of tasklet_head.		t->next = NULL;        // Update the tail pointer.		*tl_head->tail = t;		tl_head->tail = &t->next;        // Trigger a softirq.		__raise_softirq_irqoff(softirq_nr);        // Enable local interrupts.		local_irq_enable();	}}

In the above code,tasklet_action_common()The function processes each tasklet in the task list. It first disables local interrupts, obtains the head pointer of the task list, clears the task list, and resets the tail pointer. Then it iterates through the task list to process each tasklet. If the tasklet’s lock is acquired successfully and the counter is 0, it executes the tasklet’s handler function and clears the state flag. If the lock acquisition fails or the counter is not 0, it adds the tasklet to the tail of the task list and triggers the specified softirq. Finally, it enables local interrupts and completes the task processing.

The tasklet is added to the list through the__tasklet_schedule_common()function.

12345678910111213141516171819202122232425262728293031323334353637
// kernel/softirq.cstatic void __tasklet_schedule_common(struct tasklet_struct *t,				      struct tasklet_head __percpu *headp,				      unsigned int softirq_nr){	struct tasklet_head *head;	unsigned long flags;// Save the current interrupt state and disable local interrupts.	local_irq_save(flags);    // Get the tasklet_head pointer of the current CPU.	head = this_cpu_ptr(headp);    // Add the current tasklet to the tail of tasklet_head.	t->next = NULL;    // Update the tail pointer of tasklet_head.	*head->tail = t;	head->tail = &(t->next);    // Trigger the specified softirq.	raise_softirq_irqoff(softirq_nr);    // Restore the interrupt state.	local_irq_restore(flags);}void __tasklet_schedule(struct tasklet_struct *t){	__tasklet_schedule_common(t, &tasklet_vec,				  TASKLET_SOFTIRQ);}EXPORT_SYMBOL(__tasklet_schedule);void __tasklet_hi_schedule(struct tasklet_struct *t){	__tasklet_schedule_common(t, &tasklet_hi_vec,				  HI_SOFTIRQ);}EXPORT_SYMBOL(__tasklet_hi_schedule);

Through the above code,__tasklet_schedule_common()The function successfully adds the tasklet to the end of the linked list.

When a softirq is triggered, the system traverses the linked list and processes each tasklet. Therefore, after being added to the linked list, the tasklet will be scheduled and executed by the system at an appropriate time.

Based on the above analysis, it can be said that a tasklet is a special kind of softirq.

Advantages of tasklet

  1. Simplified interface and programming model: Tasklet provides a simple interface and programming model, making it easier to handle deferred work in the kernel. Compared to adding softirqs yourself, Tasklet offers a higher-level abstraction.

  2. Low latency: Tasklet executes in the softirq context, avoiding the context-switch overhead of kernel threads, thus providing lower latency. This is very important for latency-sensitive tasks that require fast response.

  3. Adaptive scheduling: Tasklet has the feature of adaptive scheduling. When multiple tasklets are in the waiting state, the kernel merges them to reduce unnecessary context switches. This scheduling mechanism can improve system efficiency.

Disadvantages of tasklet

  1. Cannot handle long-running tasks:Tasklet is suitable for deferred work that runs for a short time, if you need to handle long-running tasks, it may block the execution of other tasks. For longer operations, you may need to use work queues or kernel threads to handle them.
  2. Lack of flexibility: The execution of Taskletis limited to the softirq context, so it is not suitable for all types of deferred work. In some cases, a more flexible scheduling and execution mechanism may be needed, and custom softirqs may be more appropriate at that time.
  3. Resource limitation: The number of Tasklets is limited,the number of Tasklets available in the system depends on the architecture and kernel configuration. If a large amount of deferred work needs to be processed, it may be limited by the number of Tasklets

Work queue

The work queue is one of the mechanisms for implementing the bottom half of interrupts, and is a data structure or mechanism used to manage tasks.

The basic principle of a work queue is to arrange the tasks to be executed in order in a queue, and provide a set of worker threads or worker processes to handle the tasks in the queue.

When new tasks arrive, they are added to the end of the queue, and worker threads or worker processes take tasks from the head of the queue and perform the corresponding processing operations.

Tasklet is also one of the mechanisms for implementing the bottom half of interrupts. If you need to sleep in the bottom half of an interrupt, the work queue is the only choice, because tasklets cannot sleep, while work queues can sleep, so tasklet can be used to handle relatively time-consuming things, whileWork queues can handle more time-consuming tasks.

After the work queue defers work, it will be handed over to kernel threads for execution.During startup, Linux creates a worker kernel thread, which is in a sleep state after creation. When there is work to be processed, the thread is awakened to handle the work.

In the kernel, work queues includeShared work queueandCustom work queueThese two types. These two types of work queues have different characteristics and uses.

  • Shared work queueIt is handled by a group of kernel threads, each running on a CPU. Once a work task needs to be scheduled, the work is queued in the global work queue and will be executed at an appropriate time.
  • Custom work queueIt runs the work queue within dedicated kernel threads. This means that whenever a work queue handler needs to be executed, a dedicated kernel thread is awakened to handle it, rather than one of the default predefined threads.

Shared work queue

The shared queue is a global work queue managed by the kernel.It is used to handle some system-level tasks in the kernel.. Unless there is no alternative, or critical performance is required, or control
over every detail from work queue initialization to work scheduling; otherwise, if you only submit tasks occasionally, you should use the shared work queue provided by the kernel. This queue is shared by the entire system, and it can be used but should not be exclusively occupied for a long time.

The shared work queue is a default work queue in the kernel that can be shared by multiple kernel components and drivers.

Since tasks pending on the queue are executed serially on each CPU, tasks should not sleep for a long time. Because before it wakes up, other tasks on the queue cannot run, and a task does not even know which tasks it shares the work queue with, so a task may take a long time to get the CPU.

The work in the shared work queue is executed by kernel-createdevents/nthreads on each CPU.

work_struct

1234567891011
// include/linux/workqueue.hstruct work_struct {	atomic_long_t data;	struct list_head entry;	work_func_t func;/* Work queue handler function */#ifdef CONFIG_LOCKDEP	struct lockdep_map lockdep_map;#endif};typedef void (*work_func_t)(struct work_struct *work);//Work function

INIT_WORK()

ItemDescription
Macro definitionINIT_WORK(_work, _func)
Header file#include <linux/workqueue.h>
Parameter workThe work item to be initialized (work_structstructure)
Parameter funcThe handler function corresponding to the work item
FunctionInitialize a work item, binding it to the specified execution function, in preparation for subsequent scheduling.
Return valueNo return value

DECLARE_WORK()

ItemDescription
Macro definitionDECLARE_WORK(name, func);
Header file#include <linux/workqueue.h>
Parameter nameWork item (work_structtype) variable name
Parameter funcThe handler function corresponding to the work item
FunctionDefine and initialize a work item, equivalent to definingstruct work_structand usingINIT_WORK()to initialize.
Return valueNo return value

schedule_work()

ItemDescription
Function definitionbool schedule_work(struct work_struct *work);
Header file#include <linux/workqueue.h>
Parameter workPointer to the work item to be scheduled
FunctionAdds the work item to the system work queue, requesting the scheduler to execute the work at an appropriate time.
Return valuetrue: successfully submitted to the work queue;
false: submission failed or the work item is already in the queue

cancel_work_sync()

ItemDescription
Function definitionbool cancel_work_sync(struct work_struct *work);
Header file#include <linux/workqueue.h>
Parameter workPointer to the work item to be canceled
FunctionSynchronously cancels work item scheduling: if the work item is in the queue, removes it; if it is running, waits for execution to complete before returning.
Return valuetrue: Successfully canceled;
false: the work is not in a waiting or running state

flush_work()

ItemDescription
Function definitionbool flush_work(struct work_struct *work);
Header file#include <linux/workqueue.h>
Parameter workPointer to the work item to wait for completion
FunctionWaits for the specified work item to finish execution (if it is running or in the queue), ensuring it is no longer pending or running. Often used for synchronous cleanup before module unload.
Return valuetrue: The work item was pending or executing, and has now completed;
false: The work item has not been scheduled or has finished execution (no need to wait)

example

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
#include <linux/module.h>#include <linux/init.h>#include <linux/gpio.h>#include <linux/interrupt.h>#include <linux/workqueue.h>#include <linux/delay.h>#define TEST_GPIO_PIN 101static int irq;struct work_struct test_work;void test_work_func(struct work_struct *work){        msleep(1000);        pr_info("test_work_func is called\n");}irqreturn_t test_irq_handler(int irq, void *dev_id){        pr_info("test_irq_handler is called\n");        // Interrupt bottom half        // Submit work item to work queue        schedule_work(&test_work);        return IRQ_RETVAL(IRQ_HANDLED);}static int __init workqueue_test_init(void){        int ret;        irq = gpio_to_irq(TEST_GPIO_PIN);        if (irq < 0)                return -ENODEV;        pr_info("irq is %d\n", irq);        ret = request_irq(irq, test_irq_handler, IRQF_TRIGGER_RISING, "test", NULL);        if (ret < 0) {                free_irq(irq, NULL);                return -ENODEV;        }        INIT_WORK(&test_work, test_work_func);        return 0;}static void __exit workqueue_test_exit(void){        free_irq(irq, NULL);        // Before module exit, it must be ensured that all submitted work items have completed and are removed from the work queue.        cancel_work_sync(&test_work);}module_init(workqueue_test_init);module_exit(workqueue_test_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@outlook.com>");MODULE_DESCRIPTION("This is a test for workqueue");

Custom work queue

A custom work queue is a specific work queue created by the kernel or a driver to handle specific tasks.

Custom work queues are usually associated with a specific kernel module or driver and are used to perform tasks related to that module or driver.

workqueue_struct

The kernel usesstruct workqueue_structStructure describing a work queue

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
// kernel/workqueue.c/* * The externally visible workqueue.  It relays the issued work items to * the appropriate worker_pool through its pool_workqueues. */struct workqueue_struct {	struct list_head	pwqs;		/* WR: all pwqs of this wq */	struct list_head	list;		/* PR: list of all workqueues */	struct mutex		mutex;		/* protects this wq */	int			work_color;	/* WQ: current work color */	int			flush_color;	/* WQ: current flush color */	atomic_t		nr_pwqs_to_flush; /* flush in progress */	struct wq_flusher	*first_flusher;	/* WQ: first flusher */	struct list_head	flusher_queue;	/* WQ: flush waiters */	struct list_head	flusher_overflow; /* WQ: flush overflow list */	struct list_head	maydays;	/* MD: pwqs requesting rescue */	struct worker		*rescuer;	/* MD: rescue worker */	int			nr_drainers;	/* WQ: drain in progress */	int			saved_max_active; /* WQ: saved pwq max_active */	struct workqueue_attrs	*unbound_attrs;	/* PW: only for unbound wqs */	struct pool_workqueue	*dfl_pwq;	/* PW: only for unbound wqs */#ifdef CONFIG_SYSFS	struct wq_device	*wq_dev;	/* I: for sysfs interface */#endif#ifdef CONFIG_LOCKDEP	char			*lock_name;	struct lock_class_key	key;	struct lockdep_map	lockdep_map;#endif	char			name[WQ_NAME_LEN]; /* I: workqueue name */	/*	 * Destruction of workqueue_struct is RCU protected to allow walking	 * the workqueues list without grabbing wq_pool_mutex.	 * This is used to dump all workqueues from sysrq.	 */	struct rcu_head		rcu;	/* hot fields used during command issue, aligned to cacheline */	unsigned int		flags ____cacheline_aligned; /* WQ: WQ_* flags */	struct pool_workqueue __percpu *cpu_pwqs; /* I: per-cpu pwqs */	struct pool_workqueue __rcu *numa_pwq_tbl[]; /* PWR: unbound pwqs indexed by node */};

create_workqueue()

ItemDescription
Function definitionstruct workqueue_struct *create_workqueue(const char *name);
Header file#include <linux/workqueue.h>
Parameter nameThe name of the created work queue
FunctionCreate a per-CPU work queue (one worker thread per CPU).
Return valueSuccess:workqueue_struct*pointer;
Failure:NULL

create_singlethread_workqueue()

ItemDescription
Macro definitioncreate_singlethread_workqueue(name)
The macro expands to:alloc_workqueue("%s", WQ_SINGLE_THREAD, 1, name)
Header file#include <linux/workqueue.h>
Parameter nameWork queue name
FunctionCreate a work queue bound to a single CPU with a single worker thread.
Return valueSuccess:workqueue_struct*pointer;
Failure:NULL

alloc_workqueue()

ItemDescription
Function definitionstruct workqueue_struct *alloc_workqueue(const char *fmt, unsigned int flags, int max_active, …);
Header file#include <linux/workqueue.h>
Parameter fmtThe name format string of the work queue (similar toprintf), used to identify the work queue in the kernel (such as appearing in/proc/workqueueor logs).
parameter flagsFlags that control the behavior of the work queue, commonly including:
WQ_UNBOUND: not bound to a specific CPU, dynamically allocated by the scheduler;
WQ_MEM_RECLAIM: allows execution in the memory reclaim path to avoid deadlocks;
WQ_HIGHPRI: high-priority workqueue;
WQ_CPU_INTENSIVE: marked as CPU-intensive, affecting concurrent scheduling.
Parameter max_activeSpecifies the maximum number of work items that can be executed concurrently on each CPU for this work queue.
• Set to1means serial execution (at most one work runs at a time);
• Set to0means using the default value (usuallyWQ_MAX_ACTIVE = 512); (forWQ_UNBOUNDqueue, it indicates the global maximum concurrency.)
Variable arguments …Used in conjunction withfmtformat string (such asalloc_workqueue("my_wq_%d", ..., id)), iffmtThe absence of a format specifier can be ignored.
FunctionDynamically create and initialize a dedicated workqueue, returning its pointer. Compared to the deprecatedcreate_workqueue(), it provides finer-grained control and better scalability.
Return valueOn success, returns a pointer tostruct workqueue_structpointer;
On failure, returnsNULL(e.g., out of memory).
Associated operations• Submit work:queue_work(wq, &work)
• Destruction:destroy_workqueue(wq)
• Synchronous wait:flush_work(),flush_workqueue()

Typical usage:

12
// Create a custom work queue that can reclaim memory, has no CPU limit, and executes serially.wq = alloc_workqueue("my_custom_wq", WQ_MEM_RECLAIM | WQ_UNBOUND, 1);

The following macro functions in the kernel are all by callingalloc_workqueueImplemented:

  • alloc_ordered_workqueue
    • Create a strictly serial (ordered) work queue.
    • All work submitted to this queue will be executed in the order of submission, never concurrently.
    • Using WQ_UNBOUND: not limited to running on a specific CPU; the kernel scheduler freely chooses the CPU.
    • max_active = 1: Ensure that at most one work is executing at the same time.
    • __WQ_ORDERED | __WQ_ORDERED_EXPLICIT: Internal flag that forces the ‘ordered’ semantics to be enabled.
123
#define alloc_ordered_workqueue(fmt, flags, args...)			\	alloc_workqueue(fmt, WQ_UNBOUND | __WQ_ORDERED |		\			__WQ_ORDERED_EXPLICIT | (flags), 1, ##args)
  • create_workqueue
    • This is a macro replacement for the deprecated function create_workqueue() (deprecated since Linux 2.6.36).
    • What is actually created is a bound work queue with one worker thread per CPU (but through__WQ_LEGACYSimulate old behavior).
    • WQ_MEM_RECLAIM: Allows scheduling in the memory reclamation path to avoid deadlocks.
    • max_active = 1: Note! This is inconsistent with earlier kernel behavior.

⚠️ In very old kernels, create_workqueue() is multi-threaded and concurrent by default (one thread per CPU, can execute in parallel). However, modern kernels map it to max for simplicity._In the form of active=1, the actual behavior has become serial!

12
#define create_workqueue(name)						\	alloc_workqueue("%s", __WQ_LEGACY | WQ_MEM_RECLAIM, 1, (name))
  • create_freezable_workqueue
    • Create a work queue that can be frozen when the system is suspended (freeze).
    • WQ_FREEZABLE: When the system enterssuspend/hibernateAt that time, the work in this queue will be paused until the system resumes.
    • WQ_UNBOUND: Not limited to CPU.
    • max_active = 1: Serial execution.
    • Also has__WQ_LEGACY, which is a compatibility interface.
123
#define create_freezable_workqueue(name)				\	alloc_workqueue("%s", __WQ_LEGACY | WQ_FREEZABLE | WQ_UNBOUND |	\			WQ_MEM_RECLAIM, 1, (name))
  • create_singlethread_workqueue
    • Create a single-threaded workqueue that executes serially.
    • It is essentiallyalloc_ordered_workqueue(..., max_active=1)a wrapper for.
    • Has__WQ_LEGACY, indicating this is a replacement for the old API.
    • Guarantees that all work is completed sequentially in the same execution context.
12
#define create_singlethread_workqueue(name)				\	alloc_ordered_workqueue("%s", __WQ_LEGACY | WQ_MEM_RECLAIM, name)

queue_work()

ItemDescription
Function definitionbool queue_work(struct workqueue_struct *wq, struct work_struct *work);
Header file#include <linux/workqueue.h>
Parameter wqPointer to the target work queue
Parameter workThe work item to be added to the work queue
FunctionAdd the work item to the specified work queue, and the kernel scheduler selects an appropriate CPU to execute it.
Return valuetrue: successfully added to the queue;
false: failed to add (for example, the work item is already pending in the queue)

queue_work_on()

ItemDescription
Function definitionbool queue_work_on(int cpu, struct workqueue_struct *wq, struct work_struct *work);
Header file#include <linux/workqueue.h>
Parameter cpuSpecifies the CPU on which the work item is to be executed
Parameter wqPointer to the target work queue
Parameter workThe work item to be added to the work queue
FunctionAdd the work item to the specified work queue on the specified CPU, waiting for execution.
Return valuetrue: successfully added to the queue;
false: failed to add (for example, already in the queue)

cancel_work_sync()

ItemDescription
Function definitionbool cancel_work_sync(struct work_struct *work);
Header file#include <linux/workqueue.h>
Parameter workPointer to the work item to cancel
FunctionSynchronously cancel the work item; if the work is currently executing, wait for it to complete before returning.
Return valuetrue: Successfully canceled;
false: Work is not in the queue or cannot be canceled

flush_work_queue()

ItemDescription
Function definitionvoid flush_workqueue(struct workqueue_struct *wq);
Header file#include <linux/workqueue.h>
Parameter wqThe work queue to flush
FunctionFlush the work queue, waiting for all submitted but not yet executed work to complete.
Return valueNo return value

destroy_work_queue()

ItemDescription
Function definitionvoid destroy_workqueue(struct workqueue_struct *wq);
Header file#include <linux/workqueue.h>
Parameter wqThe work queue to destroy
FunctionDestroy the work queue and release its resources. Before calling, you must ensure that there are no unfinished work items in the queue (can be used in combination withflush_workqueue()use).
Return valueNo return value

example

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
#include <linux/module.h>#include <linux/init.h>#include <linux/gpio.h>#include <linux/irq.h>#include <linux/interrupt.h>#include <linux/workqueue.h>#include <linux/delay.h>#define TEST_GPIO_PIN 101static int irq;static struct workqueue_struct *test_wq;static struct work_struct test_work;// Interrupt bottom half, work handler functionirqreturn_t test_irq_handler(int irq, void *dev_id){        pr_info("test_irq_handler is called\n");        queue_work(test_wq, &test_work);        // queue_work_on(0, test_wq, &test_work);        return IRQ_RETVAL(IRQ_HANDLED);}// Interrupt top half, interrupt handler functionvoid test_work_func(struct work_struct *work){        msleep(1000);        pr_info("test_work_func is called\n");}static int __init custom_workqueue_test_init(void){        int ret;        irq = gpio_to_irq(TEST_GPIO_PIN);        if (irq < 0) {                ret = -ENODEV;                goto get_irq_fail;        }        ret = request_irq(irq, test_irq_handler, IRQF_TRIGGER_RISING, "test", NULL);        if (ret < 0)                goto request_irq_fail;        // Create work queue        test_wq = create_workqueue("test");        if (IS_ERR_OR_NULL(test_wq))                goto create_workqueue_fail;        // Initialize work item        INIT_WORK(&test_work, test_work_func);        return 0;create_workqueue_fail:        free_irq(irq, NULL);request_irq_fail:get_irq_fail:        return ret;}static void __exit custom_workqueue_test_exit(void){        free_irq(irq, NULL);// Disable new interrupts        // Cancel work item               // flush_work(&test_work);        // Flush the work queue, wait for all submitted but not yet executed work to complete        flush_workqueue(test_wq);        // Destroy the work queue and release resources        destroy_workqueue(test_wq);        }module_init(custom_workqueue_test_init);module_exit(custom_workqueue_test_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@outlook.com>");MODULE_DESCRIPTION("This is a test for custom workqueue");

Delayed work

Delayed work is atechnique that delays the execution of work to a later point in time for processingtechnique.

In general,**When a task takes a long time, does not need to be executed immediately, or needs to be executed on time,**delayed work comes in handy.

The basic idea of delayed work is to put tasks into a queue, and then a background worker process or task scheduler handles the tasks in the queue. Tasks can be executed after a specified delay, or they can be sorted and processed based on priority, task type, or other conditions.

  1. Delayed work is often used to handle tasks that take a long time, such as sending emails, processing images, etc. By putting these tasks into a queue and delaying their execution, it can avoid blocking the application’s main thread and improve the system’s responsiveness.
  2. Delayed work can be used to execute scheduled tasks, such as scheduled database backups. By setting tasks to execute at a certain point in the future, it improves the reliability and efficiency of the system.

Button debouncing

Ideally, after pressing the button:

Voltage change after pressing the button in the ideal case
Voltage change after pressing the button in the ideal case

In practice:

In practice, the voltage will bounce
In practice, the voltage will bounce

  • At time t1, the button is pressed, but due to bouncing, it does not stabilize until time t2. The period from t1 to t2 is the bouncing.
  • Generally, this period is about ten-odd milliseconds. As can be seen from the figure above, there will be multiple triggers during the bouncing period. If this bouncing is not eliminated, the software will misjudge: the button was pressed only once, but the software reads the IO value and sees multiple level changes, thinking it was pressed multiple times.
  • SoWe need to skip this bouncing period before reading the button’s IO value., that is, we should read the IO value only after at least time t2.

Timer debouncing:

Using a timer for debouncing
Using a timer for debouncing

In the button interrupt, start a timer with a period of 10ms. When the timer expires, it triggers a timer interrupt. Finally, in the timer interrupt handler, read the button value. If the button is still in the pressed state, it indicates a valid button press.

In the figure above, the period from t1 to t3 is the button bouncing, which needs to be eliminated. Set the button to trigger on the falling edge, so the button interrupt will be triggered at t1, t2, and t3. Each time the interrupt handler is entered, the timer interrupt is restarted, so the timer interrupt is started at t1, t2, and t3.

However, the two periods t1~t2 and t2~t3 are shorter than the timer interrupt period we set (i.e., the debounce time, e.g., 10ms). So although the timer is started at t1, the timer is reset at t2 before the timer period expires. In the end, only the timer started at t3 can complete the full timer period and trigger the interrupt. Then we can handle the button in the interrupt handler. This is the principle of timer-based button debouncing. The button driver in Linux uses this principle!

Besides using a timer for debouncing, delayed work can also be used. In the interrupt, delay the work by 3 ms before reading the GPIO level state.

delayed_work

123456789
// include/linux/workqueue.hstruct delayed_work {	struct work_struct work;	struct timer_list timer;// Timer for delaying work execution	/* target workqueue and CPU ->timer uses to queue ->work */	struct workqueue_struct *wq;	int cpu;};

DECLARE_DELAYED_WORK()

ItemDescription
Macro definitionDECLARE_DELAYED_WORK(n, f);
Header file#include <linux/workqueue.h>
Parameter nDelayed work (delayed_work) variable name
Parameter fThe handler function corresponding to the delayed work (void (*f)(struct work_struct *)
FunctionStatically define and initialize a delayed work item.
Return valueNo return value

INIT_DELAYED_WORK()

ItemDescription
Macro definitionINIT_DELAYED_WORK(work, func);
Header file#include <linux/workqueue.h>
Parameter workPointer to the delayed work structure to be initialized (delayed_work
Parameter funcHandler function for the delayed work
FunctionDynamically initialize a delayed work item, and also initialize the internalwork_structandtimer
Return valueNo return value

schedule_delayed_work()

ItemDescription
Function definitionbool schedule_delayed_work(struct delayed_work *dwork, unsigned long delay);
Header file#include <linux/workqueue.h>
Parameter dworkThe delayed work item to be scheduled
Parameter delayDelay time (in jiffies)
FunctionSubmit the delayed work to the system default work queue and execute it after the specified delay.
Return valuetrue: successfully submitted;
false: submission failed

queue_delayed_work()

ItemDescription
Function definitionbool queue_delayed_work(struct workqueue_struct *wq, struct delayed_work *dwork, unsigned long delay);
Header file#include <linux/workqueue.h>
Parameter wqSpecify the target work queue for execution
Parameter dworkThe delayed work item to be scheduled
Parameter delayDelay time (in jiffies)
FunctionAdd the delayed work to a custom work queue and execute it after the specified delay.
Return valuetrue: successfully submitted;
false: submission failed

cancel_delayed_work_sync()

ItemDescription
Function definitionbool cancel_delayed_work_sync(struct delayed_work *dwork);
Header file#include <linux/workqueue.h>
Parameter dworkThe delayed work item to be canceled
FunctionSynchronously cancel delayed work scheduling: if the work has already executed or is executing, wait for it to complete.
Return valuetrue: Successfully canceled;
false: unable to cancel or work already completed
1234
if ( !cancel_delayed_work( &thework) ){	flush_workqueue(myqueue);	destroy_workqueue(myqueue);}

You must check whether the function’s return value is true. Ensure that the work does not enqueue itself again, and then you must explicitly flush the work queue:

Example

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657
#include <linux/module.h>#include <linux/init.h>#include <linux/gpio.h>#include <linux/interrupt.h>#include <linux/workqueue.h>#include <linux/delay.h>#define TEST_GPIO_PIN 101static int irq;void test_delayed_work_func(struct work_struct *work){        pr_info("test_delayed_work_func is called\n");        msleep(1000);}struct delayed_work test_delayed_work;irqreturn_t test_irq_handler(int irq, void *dev_id){        pr_info("test_irq_handler is called\n");        // Submit to the default work queue        schedule_delayed_work(&test_delayed_work, 3 * HZ);        return IRQ_RETVAL(IRQ_HANDLED);}static int __init delayed_work_test_init(void){        int ret;        irq = gpio_to_irq(TEST_GPIO_PIN);        if (irq < 0)                return -ENODEV;        ret = request_irq(irq, test_irq_handler, IRQF_TRIGGER_RISING, "test", NULL);        if (ret < 0)                return -ENODEV;        INIT_DELAYED_WORK(&test_delayed_work, test_delayed_work_func);        return 0;}static void __exit delayed_work_test_exit(void){        free_irq(irq, NULL);        // Ensure the work is completed before exiting        flush_delayed_work(&test_delayed_work);}module_init(delayed_work_test_init);module_exit(delayed_work_test_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@163.com>");MODULE_DESCRIPTION("This is a test for delayed work");

Passing parameters to the work queue

The main idea is to rely oncontainer_ofthe macro to place work_struct into a custom structure

Example:

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
#include <linux/module.h>#include <linux/init.h>#include <linux/gpio.h>#include <linux/interrupt.h>#include <linux/slab.h>#include <linux/delay.h>#define TEST_GPIO_PIN 101struct drv_data {        int irq;        struct work_struct test_work;        int arg;};static struct drv_data *drv_dat;void test_work_func(struct work_struct *work){        struct drv_data *drv_dat = container_of(work, struct drv_data, test_work);        pr_info("test_work_func is called, arg is %d\n", drv_dat->arg);        msleep(1000);}irqreturn_t test_irq_handler(int irq, void *dev_id){        pr_info("test_irq_handler is called\n");        schedule_work(&drv_dat->test_work);        return IRQ_RETVAL(IRQ_HANDLED);}static int __init test_arg_pass_init(void){        int ret;        drv_dat = kzalloc(sizeof(struct drv_data), GFP_KERNEL);        if (drv_dat == NULL) {                ret = -ENOMEM;                goto kzalloc_fail;        }        drv_dat->irq = gpio_to_irq(TEST_GPIO_PIN);        if (drv_dat->irq < 0) {                ret = -ENODEV;                goto fail;        }        ret = request_irq(drv_dat->irq, test_irq_handler, IRQF_TRIGGER_RISING, "test", NULL);        if (ret < 0) {                ret = -ENODEV;                goto fail;        }        drv_dat->arg = 0x6;        INIT_WORK(&drv_dat->test_work, test_work_func);        return 0;fail:        kfree(drv_dat);kzalloc_fail:        return ret;}static void __exit test_arg_pass_exit(void){        free_irq(drv_dat->irq, NULL);        flush_work(&drv_dat->test_work);        kfree(drv_dat);}module_init(test_arg_pass_init);module_exit(test_arg_pass_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@outlook.com>");MODULE_DESCRIPTION("This is a test for passing args to work func");

Concurrency-managed work queues

When using work queues, we first define a work structure, then add the work to the workqueue, and finally the worker thread executes the workqueue.

When new work is generated in the work queue, the worker thread executes each work item in the work queue. When execution finishes, the worker thread sleeps until a new interrupt occurs, then work is added to the work queue again, and the worker thread executes each work item, and the cycle repeats.

Single-core system

In a single-core threaded system, a worker thread is usually initialized for each CPU (core) and associated with a work queue. This default setup ensures that each CPU has a dedicated thread to handle work items on its bound work queue.

multi-core system

In a multi-core threaded system, the design of work queues differs from that in a single-core threaded system.**In a multi-core threaded system, there are usually multiple work queues, each bound to a worker thread.**This makes full use of the parallel processing capability of multiple cores.

When a new work item is generated, the system needs to decide which work queue to assign it to. A common strategy is to usea load balancing algorithm, which balances the assignment of work items based on the load of the work queues, to avoid performance degradation caused by overloading a particular work queue.

Each work queue independently manages its own work items. When a new work item is added to a work queue, the worker thread fetches the pending work item from its associated work queue and executes the corresponding processing function.

In a multi-core threaded system, multiple worker threads can simultaneously execute work items in their respective bound work queues. This enables parallel processing and improves the overall performance and response speed of the system.

Disadvantages of work queues

  1. When work item w0 is running or even sleeping, work items w1 and w2 are queued and waiting. In a busy system, the work queue may accumulate a large number of pending work items, causing delays in task scheduling, which may affect the system’s response performance and increase the processing time of work items.
  2. In a work queue, different work items may have different processing times and resource requirements. If the processing times of work items vary greatly, some worker threads may be constantly busy processing long-running work items while other worker threads remain idle, resulting in unbalanced resource utilization.
  3. In a multi-threaded environment, multiple worker threads accessing and modifying the work queue simultaneously can lead to race conditions. To ensure data consistency and correctness, appropriate synchronization mechanisms such as locks or atomic operations need to be adopted to protect shared data, but this may introduce additional synchronization overhead.
  4. Work queues typically process work items in a first-in, first-out (FIFO) manner, lacking fine-grained control over work item priority. In some scenarios, priority scheduling may be required based on the importance or urgency of work items, but the work queue itself cannot provide this level of priority control.
  5. When worker threads fetch work items from the work queue and execute them, frequent context switches may be required, switching the processor’s execution context from one thread to another. This context-switching overhead can affect system performance and efficiency.

Concurrency Managed Workqueue

CMWQ The full name is Concurrency Managed Workqueue, meaning concurrent managed work queue. A concurrency managed work queue is a concurrent programming pattern used to effectively manage and schedule tasks or work items to be executed. It is commonly used in multi-threaded or multi-process environments to achieve concurrent execution and improve system performance.

CMWQ
CMWQ

When we need to process multiple tasks or jobs simultaneously in a system, using a concurrency managed work queue is an effective approach.

alloc_workqueue()
12345678910111213141516171819
//include/linux/workqueue.h/** * alloc_workqueue - allocate a workqueue * @fmt: printf format for the name of the workqueue * @flags: WQ_* flags * @max_active: max in-flight work items, 0 for default * remaining args: args for @fmt * * Allocate a workqueue with the specified parameters.  For detailed * information on WQ_* flags, please refer to * Documentation/core-api/workqueue.rst. * * RETURNS: * Pointer to the allocated workqueue on success, %NULL on failure. */struct workqueue_struct *alloc_workqueue(const char *fmt,					 unsigned int flags,					 int max_active, ...);
ItemDescription
Function definitionstruct workqueue_struct *alloc_workqueue(const char *fmt, unsigned int flags, int max_active, …);
Header file#include <linux/workqueue.h>
Parameter fmtThe name format string of the work queue (similar toprintf), used to identify the work queue in the kernel (such as appearing in/proc/workqueueor logs).
parameter flagsFlags that control the behavior of the work queue, commonly including:
WQ_UNBOUND: not bound to a specific CPU, dynamically allocated by the scheduler;
WQ_MEM_RECLAIM: allows execution in the memory reclaim path to avoid deadlocks;
WQ_HIGHPRI: high-priority workqueue;
WQ_CPU_INTENSIVE: marked as CPU-intensive, affecting concurrent scheduling.
Parameter max_activeSpecifies the maximum number of work items that can be executed concurrently on each CPU for this work queue.
• Set to1means serial execution (at most one work runs at a time);
• Set to0means using the default value (usuallyWQ_MAX_ACTIVE = 512); (forWQ_UNBOUNDqueue, it indicates the global maximum concurrency.)
Variable arguments …Used in conjunction withfmtformat string (such asalloc_workqueue("my_wq_%d", ..., id)), iffmtThe absence of a format specifier can be ignored.
FunctionDynamically create and initialize a dedicated workqueue, returning its pointer. Compared to the deprecatedcreate_workqueue(), it provides finer-grained control and better scalability.
Return valueOn success, returns a pointer tostruct workqueue_structpointer;
On failure, returnsNULL(e.g., out of memory).
Associated operations• Submit work:queue_work(wq, &work)
• Destruction:destroy_workqueue(wq)
• Synchronous wait:flush_work(),flush_workqueue()

Typical usage:

12
// Create a custom work queue that can reclaim memory, has no CPU limit, and executes serially.wq = alloc_workqueue("my_custom_wq", WQ_MEM_RECLAIM | WQ_UNBOUND, 1);

The following macro functions in the kernel are all by callingalloc_workqueueImplemented:

  • alloc_ordered_workqueue
    • Create a strictly serial (ordered) work queue.
    • All work submitted to this queue will be executed in the order of submission, never concurrently.
    • Using WQ_UNBOUND: not limited to running on a specific CPU; the kernel scheduler freely chooses the CPU.
    • max_active = 1: Ensure that at most one work is executing at the same time.
    • __WQ_ORDERED | __WQ_ORDERED_EXPLICIT: Internal flag that forces the ‘ordered’ semantics to be enabled.
123
#define alloc_ordered_workqueue(fmt, flags, args...)			\	alloc_workqueue(fmt, WQ_UNBOUND | __WQ_ORDERED |		\			__WQ_ORDERED_EXPLICIT | (flags), 1, ##args)
  • create_workqueue
    • This is a macro replacement for the deprecated function create_workqueue() (deprecated since Linux 2.6.36).
    • What is actually created is a bound work queue with one worker thread per CPU (but through__WQ_LEGACYSimulate old behavior).
    • WQ_MEM_RECLAIM: Allows scheduling in the memory reclamation path to avoid deadlocks.
    • max_active = 1: Note! This is inconsistent with earlier kernel behavior.

⚠️ In very old kernels, create_workqueue() is multi-threaded and concurrent by default (one thread per CPU, can execute in parallel). However, modern kernels map it to max for simplicity._In the form of active=1, the actual behavior has become serial!

12
#define create_workqueue(name)						\	alloc_workqueue("%s", __WQ_LEGACY | WQ_MEM_RECLAIM, 1, (name))
  • create_freezable_workqueue
    • Create a work queue that can be frozen when the system is suspended (freeze).
    • WQ_FREEZABLE: When the system enterssuspend/hibernateAt that time, the work in this queue will be paused until the system resumes.
    • WQ_UNBOUND: Not limited to CPU.
    • max_active = 1: Serial execution.
    • Also has__WQ_LEGACY, which is a compatibility interface.
123
#define create_freezable_workqueue(name)				\	alloc_workqueue("%s", __WQ_LEGACY | WQ_FREEZABLE | WQ_UNBOUND |	\			WQ_MEM_RECLAIM, 1, (name))
  • create_singlethread_workqueue
    • Create a single-threaded workqueue that executes serially.
    • It is essentiallyalloc_ordered_workqueue(..., max_active=1)a wrapper for.
    • Has__WQ_LEGACY, indicating this is a replacement for the old API.
    • Guarantees that all work is completed sequentially in the same execution context.
12
#define create_singlethread_workqueue(name)				\	alloc_ordered_workqueue("%s", __WQ_LEGACY | WQ_MEM_RECLAIM, name)
example
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
#include <linux/init.h>#include <linux/module.h>#include <linux/interrupt.h>#include <linux/gpio.h>#include <linux/delay.h>#include <linux/workqueue.h>#define TEST_GPIO_IN 101static int irq;static struct work_struct test_work;static struct workqueue_struct *cmwq;void test_work_func(struct work_struct *work){        msleep(1000);        pr_info("test_work_func is called\n");}irqreturn_t test_irq_handler(int irq, void *dev_id){        pr_info("test_irq_handler is called\n");        queue_work(cmwq, &test_work);        return IRQ_HANDLED;}static int __init cmwq_test_init(void){        int ret;        irq = gpio_to_irq(TEST_GPIO_IN);        if (irq < 0)                return -ENODEV;        ret = request_irq(irq, test_irq_handler, IRQF_TRIGGER_RISING, "test_irq", NULL);        if (ret < 0)                return -ENODEV;        cmwq = alloc_workqueue("test_workqueue", WQ_UNBOUND | WQ_SYSFS, 0);        if (cmwq == NULL) {                free_irq(irq, NULL);                return -EFAULT;        }        INIT_WORK(&test_work, test_work_func);        return 0;}static void __exit cmwq_test_exit(void){        free_irq(irq, NULL);        flush_workqueue(cmwq);        destroy_workqueue(cmwq);}module_init(cmwq_test_init);module_exit(cmwq_test_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@outlook.com>");MODULE_DESCRIPTION("This is a test for Concurrency Managed Work Queue");

Interrupt Threading Technology

Interrupt threading (threaded IRQ) is an optimization technique used to improve the performance of multi-threaded programs. The main goal isto minimize the time interrupts are disabled.

In multi-threaded programs, sometimes hardware or other events issue interrupt signals, interrupting the currently executing thread, and it is necessary to switch to the interrupt handler to process these events. This kind offrequent interrupt switching leads to additional overhead and latency., affecting the performance of the program.

To solve this problem, interrupt threading proposes an optimization scheme. It separates the interrupt handler from the main thread and creates a dedicated thread to handle these interrupt events. In this way, the main thread is no longer disturbed by interrupts and can focus on its own work without being interrupted frequently.

The core idea of interrupt threading isSeparate interrupt handling from the main thread’s work, allowing them to execute in parallel. The interrupt thread is responsible for handling interrupt events, while the main thread handles the main work tasks. This not only reduces switching overhead but also improves the responsiveness and performance of the entire program.

Threaded interrupt handling can still be viewed as the original interrupt top half and bottom half.

The top half is still used to handle urgent matters, and the bottom half also handles relatively time-consuming operations, but the bottom half is handed over to a dedicated kernel thread for processing. This kernel thread is used only for this interrupt.

When an interrupt occurs, this kernel thread is woken up, and then this kernel thread executes the function of the interrupt bottom half.

It should be noted that interrupt threading also needs to handle synchronization and data sharing between threads. Because the interrupt thread and the main thread may simultaneously access and modify shared data, proper synchronization operations are required to ensure data consistency and correctness.

request_threaded_riq()

12345
// include/linux/interrupt.hextern int __must_checkrequest_threaded_irq(unsigned int irq, irq_handler_t handler,		     irq_handler_t thread_fn,		     unsigned long flags, const char *name, void *dev);
ItemDescription
Function definitionint request_threaded_irq(unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn, unsigned long irqflags, const char *devname, void *dev_id);
Header file#include <linux/interrupt.h>
Parameter irqInterrupt number (IRQ line number)
Parameter handlerTop-half handler: the fast handler executed first when an interrupt occurs.
If it returnsIRQ_WAKE_THREADthen the bottom-half thread is woken up.
Can be set toNULLuse the system default handler.
Parameter thread_fnThreaded interrupt handler (bottom half), executed in a kernel thread. If it isNULLthen interrupt threading is not used.
Parameter irqflagsInterrupt attribute flags, such asIRQF_TRIGGER_RISINGIRQF_SHAREDetc.
Parameter devnameInterrupt name, used to identify the device
Parameter dev_idDevice identifier passed to the handler (usually a device structure pointer)
FunctionRegister a threaded interrupt, including top-half and bottom-half thread handling, to improve the real-time performance and schedulability of interrupt handling.
Return valueSuccess: returns 0;
Failure: return a negative error code
  • handlerFunction: this is the same as usingrequest_irq()the function used when registering. It represents the top-half function, which runs in atomic context (or hard interrupt). If it can handle the interrupt faster, it may not need the bottom half at all; it should returnIRQ_HANDLED. However, if the interrupt handling requires100μsor more, as mentioned earlier, then the bottom half should be used. In this case, it should returnIRQ_WAKE_THREAD, thereby schedulingthread_fnfunction (which must be provided).
  • thread_fnFunction: this represents the bottom half, scheduled by the top half. When the hard interrupt handler (handler function) returnsIRQ_WAKE_THREAD, the kernel thread associated with this bottom half will be scheduled, and when the kernel thread runs, it calls thread_fn function. thread_fn function must return when it completesIRQ_HANDLED. After execution, the interrupt is re-triggered, and before the hard interrupt returnsIRQ_WAKE_THREADthe kernel thread will not be scheduled again.

Threaded interrupts can be used anywhere a work queue can be used to schedule the bottom half. A true threaded interrupt must define handler and thread_fn.

If handler is NULL, and thread_fn is not NULL, the kernel will install a default hard interrupt handler, which will simply return IRQ_WAKE_THREAD to schedule the bottom half.

When the interrupt handler executes, the interrupt it services is always disabled on all CPUs, and is re-enabled when the hard interrupt (top half) completes.

However, if for some reason you need to not re-enable the interrupt line after the top half and keep it disabled until the threaded handler runs, you should request a threaded interrupt with the IRQF_ONESHOT flag. This way, the interrupt line will wait until the bottom half completes before being re-enabled.

example

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
#include <linux/init.h>#include <linux/module.h>#include <linux/interrupt.h>#include <linux/gpio.h>#include <linux/delay.h>#define TEST_GPIO_PIN 101static int irq;irqreturn_t test_irq_top_half_handler(int irq, void *dev_id){        pr_info("test_irq_handler is called\n");        // Defer interrupt work to the interrupt bottom half, handled by a kernel thread        return IRQ_WAKE_THREAD;}irqreturn_t test_irq_bottom_half_handler(int irq, void *dev_id){        msleep(1000);        pr_info("threaded_irq_handler is called\n");        return IRQ_HANDLED;}static int __init threaded_irq_test_init(void){        int ret;        irq = gpio_to_irq(TEST_GPIO_PIN);        if (irq < 0)                return -ENODEV;        ret = request_threaded_irq(irq, test_irq_top_half_handler, test_irq_bottom_half_handler,                                   IRQF_TRIGGER_RISING, "test", NULL);        if (ret < 0)                return ret;        return 0;}static void __exit threaded_irq_test_exit(void){        free_irq(irq, NULL);}module_init(threaded_irq_test_init);module_exit(threaded_irq_test_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@outlook.com>");MODULE_DESCRIPTION("This is a test sample for threaded irq");
Loading comments…