Linux Kernel (12) – Delayed Work and Queue Parameters
Delayed Work
Problem
When analyzing work queues, the main approach is to place the work onto the work queue, which is then processed by a specific thread. However, we may encounter a situation where we do not want to place the work immediately onto the work queue but rather wait for a certain period before doing so. In such cases, we need to handle delayed work.
Concept
In the operating system kernel, not all tasks need or should be executed immediately. Some tasks may need to wait for a while before execution for efficiency or logical reasons. This is the concept of “Delayed Work”.
Scenarios:
- • Merge Operations: For example, in disk I/O, multiple small write operations can be merged into a larger write operation to reduce disk addressing times and improve performance.
- • Delayed Acknowledgment: In network protocols like TCP, to allow for “piggybacking” acknowledgments, the acknowledgment packets may be delayed.
- • Debouncing: In drivers, such as handling keyboard presses, waiting for a period to determine if it is a repeated press or a single press.
- • Scheduled Tasks: Regularly checking device status, executing certain cleanup routines, etc.
Structure
struct delayed_work {
struct work_struct work;
struct timer_list timer;
};
- • struct work_struct work;
- • This is a standard work unit that contains a pointer to the function that needs to be executed after a delay (commonly referred to as
<span>work_handler</span>) and some management data. - • You can think of it as a “work ticket” that specifies “what to do” (function pointer) and “the status of this work” (e.g., whether it is queued, whether it is executing).
- • struct timer_list timer;
- • This is a kernel timer that functions to expire at a specified time and then call its associated callback function.
- • Here, this timer is the core of the “delay” mechanism.
Static Initialization
DECLARE_DELAYED_WORK(my_delayed_work, my_work_function);
This line of code is equivalent to:
struct delayed_work my_delayed_work = __DELAYED_WORK_INITIALIZER(my_delayed_work, my_work_function, 0);
Function
- • This macro statically defines and initializes a variable named
<span>n</span>of type<span>struct delayed_work</span>at compile time. It expands into a variable definition and initialization statement during preprocessing. It creates a global (or module-static) variable<span>my_delayed_work</span>and points its internal<span>work</span>function pointer to<span>my_work_function</span>, while also initializing the internal timer.
Usage Scenario
- • When you need a
<span>delayed_work</span>structure as a global variable or a static variable within a module, you can use<span>DECLARE_DELAYED_WORK</span>. The lifecycle of this structure is determined at compile time and will exist as long as the module or kernel exists.
Dynamic Initialization
#define INIT_DELAYED_WORK(_work, _func)
Function
- • This macro initializes an already existing
<span>struct delayed_work</span><span> variable at runtime. It is typically used to initialize a delayed_work member embedded in a larger structure (such as a device private data structure).</span>
Usage Scenario
- • When your
<span>delayed_work</span>is part of dynamically allocated memory (like kmalloc), this macro must be used. This is common in device drivers where delayed_work exists as a member of the device private data structure. You need to initialize the work item after allocating the device structure.
Example
struct my_device_data {
// ... other device-related members ...
struct delayed_work ping_work;
// ... other device-related members ...
};
struct my_device_data *dev_data;
dev_data = kmalloc(sizeof(struct my_device_data), GFP_KERNEL);
// First dynamically allocate memory, at this point the internal ping_work is uninitialized
INIT_DELAYED_WORK(&dev_data->ping_work, device_ping_function);
// Now the delayed_work member in this memory is initialized
Custom Queue Delayed Scheduling Function
static inline bool queue_delayed_work(struct workqueue_struct *wq,
struct delayed_work *dwork,
unsigned long delay)
Parameters
- • static inline bool: This is an inline function that returns a boolean value indicating whether the scheduling was successful.
- • struct workqueue_struct*wq: This is the most important parameter and the fundamental difference from
<span>schedule_delayed_work</span><span>. It is a pointer to a custom work queue. Developers can fully control which queue the work task is submitted to.</span> - • struct delayed_work*dwork: A pointer to the initialized delayed work item.
- • unsigned long delay: The delay time, measured in ticks (jiffies).
Example
#include <linux/workqueue.h>
/* Define work and queue */
struct workqueue_struct *my_wq;
struct delayed_work my_dwork;
void my_delayed_function(struct work_struct *work) {
printk("This runs on my custom queue!\n");
}
/* Module initialization function */
int __init my_init(void) {
// 1. Create a custom work queue with no special attributes
my_wq = alloc_workqueue("my_wq", 0, 0);
if (!my_wq)
return -ENOMEM;
// 2. Initialize delayed work
INIT_DELAYED_WORK(&my_dwork, my_delayed_function);
// 3. Schedule work on the custom queue to execute after a 1 second delay
queue_delayed_work(my_wq, &my_dwork, msecs_to_jiffies(1000));
return 0;
}
/* Module exit function */
void __exit my_exit(void) {
// Cancel work (if still pending)
cancel_delayed_work_sync(&my_dwork);
// Destroy custom queue
destroy_workqueue(my_wq);
}
Shared Queue Delayed Work Scheduling Function
schedule_delayed_work is a convenient wrapper for queue_delayed_work
static inline bool schedule_delayed_work(struct delayed_work *dwork, unsigned long delay)
static inline bool: Like schedule_delayed_work, this is an inline function that returns a boolean value indicating whether the scheduling was successful.
struct workqueue_struct *wq: This is the key parameter and the fundamental difference from schedule_delayed_work. It is a pointer to a custom work queue. Developers can fully control which queue the work task is submitted to.
struct delayed_work *dwork: A pointer to the initialized delayed work item.
unsigned long delay: The delay time, measured in ticks (jiffies).
Parameters
- • dwork: The delayed work.
- • delay: The time to delay.
- • Measured in ticks (jiffies): This is the most basic time unit in the kernel.
<span>jiffies</span>is a global variable that increments once for each clock interrupt after the system boots.<span>HZ</span>indicates the number of ticks per second, defining the update frequency of<span>jiffies</span>. For example: - •
<span>HZ = 1000</span>: Indicates 1000 clock interrupts per second, making each tick (jiffy) 1 millisecond. - • To delay
<span>n</span>milliseconds, you can use the formula:<span>delay = msecs_to_jiffies(n)</span>. - • The kernel provides convenient helper functions for time conversion:
- •
<span>msecs_to_jiffies()</span><span>: Converts milliseconds to ticks.</span> - •
<span>usecs_to_jiffies()</span><span>: Converts microseconds to ticks.</span> - •
<span>nsecs_to_jiffies()</span><span>: Converts nanoseconds to ticks.</span>
Example
/* Define a delayed work item and its handler */
struct delayed_work my_work;
void my_work_function(struct work_struct *work) {
printk("Work executed after delay!\n");
}
/* In driver initialization code */
void my_init(void) {
// 1. Initialize work item
INIT_DELAYED_WORK(&my_work, my_work_function);
// 2. Schedule it to execute after a 200 millisecond delay
schedule_delayed_work(&my_work, msecs_to_jiffies(200));
}
/* In driver exit code */
void my_exit(void) {
// Ensure to cancel delayed work to prevent it from being called after module unload
cancel_delayed_work_sync(&my_work);
}
Complete Example
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
// Global variable declaration
static int irq; // Interrupt number
static struct delayed_work test_delayed_work; // Delayed work structure
static struct workqueue_struct *test_workqueue; // Work queue pointer
// Delayed work handler function
void test_work_handler(struct work_struct *work)
{
// Simulate a time-consuming operation, delay for 1 second
msleep(1000);
printk(KERN_INFO "Delayed work executed - This runs in the work queue thread\n");
}
// Interrupt handler function
static irqreturn_t test_interrupt_handler(int irq, void *args)
{
printk(KERN_INFO "GPIO interrupt triggered - This runs in interrupt context\n");
// Submit delayed work to the work queue, delay for 2 seconds
// Note: Here we use queue_delayed_work instead of queue_work
// The third parameter is the delay time, measured in jiffies
// HZ is the number of clock ticks per second, HZ*2 indicates a 2-second delay
queue_delayed_work(test_workqueue, &test_delayed_work, HZ * 2);
// Return interrupt handling status
return IRQ_HANDLED;
}
// Module initialization function
static int __init interrupt_irq_init(void)
{
int ret;
// Get the interrupt number corresponding to GPIO 13
irq = gpio_to_irq(13);
printk(KERN_INFO "GPIO 13 corresponds to interrupt number: %d\n", irq);
// Request interrupt, set to trigger on rising edge
ret = request_irq(irq, test_interrupt_handler, IRQF_TRIGGER_RISING, "test_gpio_irq", NULL);
if (ret < 0) {
printk(KERN_ERR "Request interrupt failed, error code: %d\n", ret);
return ret;
}
// Create work queue
test_workqueue = create_workqueue("test_workqueue");
if (!test_workqueue) {
printk(KERN_ERR "Failed to create work queue\n");
free_irq(irq, NULL);
return -ENOMEM;
}
// Initialize delayed work
// Note: Use INIT_DELAYED_WORK instead of INIT_WORK
INIT_DELAYED_WORK(&test_delayed_work, test_work_handler);
printk(KERN_INFO "GPIO interrupt module loaded successfully\n");
return 0;
}
// Module exit function
static void __exit interrupt_irq_exit(void)
{
// Free interrupt
free_irq(irq, NULL);
// Cancel all pending delayed work
// Note: Use cancel_delayed_work_sync instead of cancel_work_sync
cancel_delayed_work_sync(&test_delayed_work);
// Flush work queue to ensure all work is completed
flush_workqueue(test_workqueue);
// Destroy work queue
destroy_workqueue(test_workqueue);
printk(KERN_INFO "GPIO interrupt module unloaded successfully\n");
}
// Register module initialization and exit functions
module_init(interrupt_irq_init);
module_exit(interrupt_irq_exit);
// Define module information
MODULE_LICENSE("GPL");
MODULE_AUTHOR("XSX");
MODULE_VERSION("V1.0");
MODULE_DESCRIPTION("GPIO interrupt handling example using delayed work queue");
Work Queue Parameter Passing
container_of
<span>struct work_struct</span> is a generic work structure defined by the kernel, which does not know or care what specific structure contains it. However, the prototype of the callback function is fixed:<span>void my_work_function(struct work_struct *work)</span>. Through this pointer <span>work</span>, we need to access other members in <span>struct my_work_data</span> (such as <span>parameter1</span> and <span>parameter2</span>).
To access other members in <span>struct my_work_data</span><span> through the </span><code><span>work</span> pointer, you can use the following steps:
Usage Example
struct my_work_struct {
struct work_struct work; // Generic work structure
struct my_work_data data; // Custom data
};
// Initialize my_work_struct
void initialize_my_work(struct my_work_struct *my_work, int param1, int param2) {
INIT_WORK(&my_work->work, my_work_function);
my_work->data.parameter1 = param1;
my_work->data.parameter2 = param2;
}
// Callback function
void my_work_function(struct work_struct *work) {
struct my_work_struct *my_work = container_of(work, struct my_work_struct, work);
int param1 = my_work->data.parameter1;
int param2 = my_work->data.parameter2;
// Use param1 and param2
}
In this way, you can access other members in <span>struct my_work_data</span><span> through the </span><code><span>work</span> pointer.
Work Queue Parameter Passing
Using a Structure Containing work_struct
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/workqueue.h>
// Define a structure containing work structure and parameters
struct my_work_data {
struct work_struct my_work;
int parameter1;
char parameter2[32];
void *parameter3;
};
static struct my_work_data *work_data;
void my_work_function(struct work_struct *work) {
// Use container_of to get the structure containing work_struct
struct my_work_data *data = container_of(work, struct my_work_data, my_work);
printk(KERN_INFO "Parameter 1: %d\n", data->parameter1);
printk(KERN_INFO "Parameter 2: %s\n", data->parameter2);
// Process actual work...
}
// Initialize work queue and set parameters
static int __init my_module_init(void) {
work_data = kmalloc(sizeof(struct my_work_data), GFP_KERNEL);
if (!work_data)
return -ENOMEM;
// Initialize work
INIT_WORK(&work_data->my_work, my_work_function);
// Set parameters
work_data->parameter1 = 42;
strncpy(work_data->parameter2, "Hello World", sizeof(work_data->parameter2));
work_data->parameter3 = NULL;
// Schedule work
schedule_work(&work_data->my_work);
return 0;
}
static void __exit my_module_exit(void) {
// Ensure all work is completed
flush_work(&work_data->my_work);
kfree(work_data);
}
module_init(my_module_init);
module_exit(my_module_exit);
Using Dynamic Allocation and Pointer Passing
For more complex scenarios, you can use dynamically allocated memory to pass parameters.
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/workqueue.h>
#include <linux/slab.h> // For kmalloc/kfree
// Custom parameter structure
struct my_parameters {
int data1;
char data2[64];
};
// Custom work structure containing standard work_struct and parameter pointer
struct my_work {
struct work_struct work;
struct my_parameters *params;
};
static struct my_work *my_work_item;
// Work handler function
static void my_work_function(struct work_struct *work) {
struct my_work *my_work = container_of(work, struct my_work, work);
struct my_parameters *params = my_work->params;
if (params) {
printk(KERN_INFO "Work queue executed: Data1=%d, Data2=%s\n",
params->data1, params->data2);
kfree(params); // Free parameter memory
}
kfree(my_work); // Free work structure memory
}
static int __init workqueue_example_init(void) {
int ret = 0;
printk(KERN_INFO "Initializing work queue example module\n");
// Allocate work structure
my_work_item = kmalloc(sizeof(struct my_work), GFP_KERNEL);
if (!my_work_item) {
printk(KERN_ERR "Unable to allocate work structure memory\n");
return -ENOMEM;
}
// Allocate parameter structure
my_work_item->params = kmalloc(sizeof(struct my_parameters), GFP_KERNEL);
if (!my_work_item->params) {
printk(KERN_ERR "Unable to allocate parameter memory\n");
kfree(my_work_item);
return -ENOMEM;
}
// Initialize parameters
my_work_item->params->data1 = 100;
strncpy(my_work_item->params->data2, "Dynamic Parameter Example",
sizeof(my_work_item->params->data2) - 1);
my_work_item->params->data2[sizeof(my_work_item->params->data2) - 1] = '\0';
// Initialize work queue
INIT_WORK(&my_work_item->work, my_work_function);
// Schedule work
schedule_work(&my_work_item->work);
printk(KERN_INFO "Work scheduled\n");
return ret;
}
static void __exit workqueue_example_exit(void) {
printk(KERN_INFO "Unloading work queue example module\n");
// Note: Memory has already been freed in the work function
// If work may still be in the queue, it should be canceled first
// cancel_work_sync(&my_work_item->work);
}
module_init(workqueue_example_init);
module_exit(workqueue_example_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("XSX");
MODULE_DESCRIPTION("Workqueue parameter passing example");