linux驱动中的上半部和下半部_linux驱动分为哪几类

[TOC]

问题描述:

在 Linux 驱动中,中断处理的上半部(硬中断)和下半部分离是核心机制,核心目的是上半部快速响应中断(禁止中断嵌套),下半部延迟处理耗时任务(可调度、允许中断)。常用实现方式有 3 种:tasklet、工作队列(workqueue)、软中断,其中 tasklet 和工作队列最常用

日志

分析步骤

第1步:
第2步:
...

代码片段

  1. tasklet 方式(软中断上下文,不可睡眠) tasklet 基于软中断实现,特点是同一 tasklet 不会并行执行,不同 tasklet 可在不同 CPU 并行,适合短任务。
/*
 * @Author: your name
 * @Date: 2025-09-01 19:31:14
 * @LastEditTime: 2025-09-01 19:35:48
 * @LastEditors: Please set LastEditors
 * @Description: In User Settings Edit
 * @FilePath: \linux-4.14.143\drivers\gpu\drm\test\tasklet.c
 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/timer.h>

// 1. 定义Tasklet和共享数据
static struct tasklet_struct my_tasklet;
static int tasklet_count = 0;  // 计数:下半部处理次数
static struct timer_list irq_timer;  // 模拟中断的定时器

// 2. 下半部:Tasklet处理函数
static void tasklet_handler(unsigned long data) {
    // 模拟耗时处理,不可调用sleep类阻塞函数
    tasklet_count++;
    printk(KERN_INFO "[下半部-Tasklet] 处理完成!总次数: %d (CPU: %d)\n",
           tasklet_count, smp_processor_id());
}

// 3. 上半部:定时器回调(模拟中断,触发Tasklet)
static void timer_irq_handler(struct timer_list *t) {
    printk(KERN_INFO "[上半部] 收到中断,触发Tasklet...\n");
    
    // 触发Tasklet(上半部核心工作)
    tasklet_schedule(&my_tasklet);
    
    // 重新启动定时器
    mod_timer(t, jiffies + msecs_to_jiffies(1000));
}

// 4. 驱动初始化
static int __init tasklet_demo_init(void) {
    // 初始化Tasklet
    tasklet_init(&my_tasklet, tasklet_handler, 0);
    
    // 初始化定时器(1秒触发一次)
    timer_setup(&irq_timer, timer_irq_handler, 0);
    mod_timer(&irq_timer, jiffies + msecs_to_jiffies(1000));
    
    printk(KERN_INFO "Tasklet demo初始化完成\n");
    return 0;
}

// 5. 驱动卸载
static void __exit tasklet_demo_exit(void) {
    del_timer(&irq_timer);
    tasklet_kill(&my_tasklet);  // 确保Tasklet已执行完毕
    printk(KERN_INFO "Tasklet demo卸载完成,总处理次数: %d\n", tasklet_count);
}

module_init(tasklet_demo_init);
module_exit(tasklet_demo_exit);
MODULE_LICENSE("GPL");
  1. 工作队列方式(内核线程上下文,可睡眠) 工作队列将任务交给内核线程(如kworker)执行,支持睡眠(可调用kmalloc、msleep等阻塞函数),适合长耗时任务。
关键结论
工作队列的任务不在调用者的线程中执行,而是由内核专门创建的 kworker 线程执行。
这种设计的优势是:任务执行与调用者线程解耦,调用者无需等待任务完成,实现了真正的异步处理;同时,内核线程运行在进程上下文,允许睡眠(如 msleep()),适合处理耗时操作。
因此,工作队列本质上是 “基于内核线程的异步任务调度机制”,通过独立的内核线程实现任务的异步执行。
//普通的工作队列
/*
 * @Author: your name
 * @Date: 2025-09-01 19:45:06
 * @LastEditTime: 2025-09-01 19:53:44
 * @LastEditors: Please set LastEditors
 * @Description: In User Settings Edit
 * @FilePath: \linux-4.14.143\drivers\gpu\drm\test\workqueue.c
 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/workqueue.h>
#include <linux/timer.h>
#include <linux/delay.h>

/*
    工作队列使用步骤:
    1. 定义工作队列和工作队列处理函数
        INIT_WORK
    2. 触发工作队列,使其能够调用回调函数
        schedule_work
*/      
// 1. 定义工作队列和共享数据
static struct work_struct my_work;
static int work_count = 0;  // 计数:下半部处理次数
static struct timer_list irq_timer;  // 模拟中断的定时器

// 2. 下半部:工作队列处理函数(进程上下文,可睡眠)
static void work_handler(struct work_struct *work) {
    // 模拟耗时处理(可睡眠)
    msleep(500);  // 睡眠500ms(Tasklet/软中断中禁止此操作)
    work_count++;
    printk(KERN_INFO "[下半部-工作队列] 处理完成!总次数: %d (CPU: %d)\n",
           work_count, smp_processor_id());
}

// 3. 上半部:定时器回调(模拟中断,触发工作队列)
static void timer_irq_handler(struct timer_list *t) {
    printk(KERN_INFO "[上半部] 收到中断,触发工作队列...\n");
    
    // 调度工作队列(上半部核心工作)
    schedule_work(&my_work);
    
    // 重新启动定时器
    mod_timer(t, jiffies + msecs_to_jiffies(1000));
}

// 4. 驱动初始化
static int __init workqueue_demo_init(void) {
    // 初始化工作队列
    INIT_WORK(&my_work, work_handler);
    
    // 初始化定时器(1秒触发一次)
    timer_setup(&irq_timer, timer_irq_handler, 0);
    mod_timer(&irq_timer, jiffies + msecs_to_jiffies(1000));
    
    printk(KERN_INFO "工作队列demo初始化完成\n");
    return 0;
}

// 5. 驱动卸载
static void __exit workqueue_demo_exit(void) {
    del_timer(&irq_timer);
    flush_work(&my_work);  // 等待工作队列执行完毕
    printk(KERN_INFO "工作队列demo卸载完成,总处理次数: %d\n", work_count);
}

module_init(workqueue_demo_init);
module_exit(workqueue_demo_exit);
MODULE_LICENSE("GPL");
//常用延时工作队列
/*
 * @Author: your name
 * @Date: 2025-09-02 11:01:32
 * @LastEditTime: 2025-09-02 11:10:51
 * @LastEditors: Please set LastEditors
 * @Description: In User Settings Edit
 * @FilePath: \linux-4.14.143\drivers\gpu\drm\test\delay_work.c
 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/workqueue.h>
#include <linux/jiffies.h>
#include <linux/delay.h>

/*
schedule_delayed_work与普通延时队列的区别:
schedule_delayed_work() 是内核推荐的延迟任务实现方式,通过封装 delayed_work。
与手动实现的 “延时队列” 相比,它减少了代码量和出错风险,适合大多数需要延迟执行异步任务的驱动场景(如设备超时处理、周期性状态刷新等)

schedule_delayed_work 使用步骤如下:
1. 定义延时工作项
2. 初始化延时工作项
3. 调度延时工作项
4. 取消延时工作项
5. 驱动初始化:创建自定义工作队列并提交任务
6. 驱动卸载:清理工作队列
*/
// 定义延时工作项
static struct delayed_work my_delayed_work;
// 任务计数器
static int work_count = 0;
// 延迟时间(500ms)
#define DELAY_MS 500

// 工作项处理函数(进程上下文,可睡眠)
static void work_handler(struct work_struct *work)
{
    struct delayed_work *dwork = to_delayed_work(work);
    work_count++;
    
    printk(KERN_INFO "[%d] 延迟工作执行,CPU:%d\n", 
           work_count, smp_processor_id());
    
    // 模拟耗时操作
    msleep(1000);
    
    // 重新调度,形成周期性执行(间隔 DELAY_MS)
    schedule_delayed_work(dwork, msecs_to_jiffies(DELAY_MS));
}

static int __init delayed_work_init(void)
{
    printk(KERN_INFO "延迟工作示例初始化...\n");
    
    // 初始化延时工作项
    INIT_DELAYED_WORK(&my_delayed_work, work_handler);
    
    // 首次调度:延迟 DELAY_MS 后执行
    schedule_delayed_work(&my_delayed_work, msecs_to_jiffies(DELAY_MS));
    
    printk(KERN_INFO "首次工作将在 %d ms 后执行\n", DELAY_MS);
    return 0;
}

static void __exit delayed_work_exit(void)
{
    // 取消未执行的工作项
    cancel_delayed_work_sync(&my_delayed_work);
    printk(KERN_INFO "驱动卸载,共执行 %d 次工作\n", work_count);
}

module_init(delayed_work_init);
module_exit(delayed_work_exit);
MODULE_LICENSE("GPL");
//标准延时队列用法
/*
 * @Author: your name
 * @Date: 2025-09-02 10:41:49
 * @LastEditTime: 2025-09-02 10:55:43
 * @LastEditors: Please set LastEditors
 * @Description: In User Settings Edit
 * @FilePath: \linux-4.14.143\drivers\gpu\drm\test\delayed_work.c
 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/workqueue.h>
#include <linux/jiffies.h>
#include <linux/delay.h>  // 内核中msleep的头文件

/*
    延时工作队列步骤:
    1. 定义延时工作队列和工作项
    2. 定义工作项的处理函数
    3. 初始化工作项
    4. 创建自定义工作队列
    5. 提交工作项到自定义工作队列
    6. 驱动初始化:创建自定义工作队列并提交任务
    7. 驱动卸载:清理工作队列
*/
// 定义延时工作项(包含工作队列和内置定时器)
static struct delayed_work my_delayed_work;
// 任务计数器
static int task_counter = 0;
// 延迟时间(毫秒,可修改)
#define DELAY_MS 2000  // 每次任务延迟2秒执行

// 延时队列的任务处理函数(进程上下文,可睡眠)
static void delayed_work_handler(struct work_struct *work)
{
    // 转换为delayed_work结构体(通过container_of获取完整结构)
    struct delayed_work *dwork = to_delayed_work(work);
    
    task_counter++;
    printk(KERN_INFO "[延时队列] 第%d次执行任务当前CPU:%d\n",
           task_counter, smp_processor_id());
    
    // 模拟耗时操作(如设备数据处理、状态上报等)
    msleep(500);  // 进程上下文允许睡眠
    
    // 重新调度延时任务(形成循环执行)
    queue_delayed_work(system_wq, dwork, msecs_to_jiffies(DELAY_MS));
}

// 驱动初始化函数
static int __init delayed_queue_init(void)
{
    printk(KERN_INFO "延时队列驱动初始化...\n");
    
    // 初始化延时工作项,绑定处理函数
    INIT_DELAYED_WORK(&my_delayed_work, delayed_work_handler);
    
    // 首次调度延时任务(延迟DELAY_MS后执行)
    if (!queue_delayed_work(system_wq, &my_delayed_work, msecs_to_jiffies(DELAY_MS))) {
        printk(KERN_ERR "首次调度延时任务失败!\n");
        return -EFAULT;
    }
    
    printk(KERN_INFO "延时队列启动成功,将在%d毫秒后执行首次任务\n", DELAY_MS);
    return 0;
}

// 驱动卸载函数
static void __exit delayed_queue_exit(void)
{
    // 取消所有未执行的延时任务,等待当前任务完成
    cancel_delayed_work_sync(&my_delayed_work);
    printk(KERN_INFO "延时队列驱动卸载,共执行%d次任务\n", task_counter);
}

// 注册驱动入口和出口
module_init(delayed_queue_init);
module_exit(delayed_queue_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Linux Delayed Workqueue Demo");
MODULE_AUTHOR("Your Name");
//自定义工作队列
/*
 * @Author: your name
 * @Date: 2025-09-02 10:50:56
 * @LastEditTime: 2025-09-02 10:57:14
 * @LastEditors: Please set LastEditors
 * @Description: In User Settings Edit
 * @FilePath: \linux-4.14.143\drivers\gpu\drm\test\custom_workqueue.c
 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/workqueue.h>
#include <linux/kthread.h>
#include <linux/delay.h>

/*
    自定义工作队列步骤:
    1. 定义自定义工作队列和工作项
    2. 定义工作项的处理函数
    3. 初始化工作项
    4. 创建自定义工作队列
    5. 提交工作项到自定义工作队列
    6. 驱动初始化:创建自定义工作队列并提交任务
    7. 驱动卸载:清理工作队列
*/
// 1. 定义自定义工作队列和工作项
static struct workqueue_struct *my_wq;  // 自定义工作队列
static struct work_struct work1, work2; // 两个工作项(任务)
static int work_count = 0;              // 任务执行计数器

// 2. 工作项1的处理函数(进程上下文,可睡眠)
static void work_func1(struct work_struct *work)
{
    work_count++;
    printk(KERN_INFO "[工作项1] 执行任务(计数:%d运行在CPU:%d\n",

           work_count, smp_processor_id());
    
    // 模拟耗时操作(如数据处理)
    msleep(100);  // 进程上下文允许睡眠
}

// 3. 工作项2的处理函数
static void work_func2(struct work_struct *work)
{
    work_count++;
    printk(KERN_INFO "[工作项2] 执行任务(计数:%d),运行在CPU:%d\n",
           work_count, smp_processor_id());
    
    // 模拟另一种耗时操作(如设备控制)
    msleep(200);
}

// 4. 驱动初始化:创建自定义工作队列并提交任务
static int __init custom_workqueue_init(void)
{
    printk(KERN_INFO "自定义工作队列初始化...\n");

    // 创建自定义工作队列(名称为"my_custom_wq",单线程模式),也可使用系统默认队列 system_wq)
    // 参数说明:
    // - 第二个参数 0 表示单线程(每个CPU一个线程可设为 WQ_UNBOUND)
    /*  当执行ps命令可以看到创建的工作队列的名称
        838 root     [my_custom_wq]
    */
    my_wq = create_workqueue("my_custom_wq");
    if (!my_wq) {
        printk(KERN_ERR "创建自定义工作队列失败!\n");
        return -ENOMEM;
    }

    // 初始化工作项,绑定处理函数
    INIT_WORK(&work1, work_func1);
    INIT_WORK(&work2, work_func2);

    // 将工作项提交到自定义工作队列(异步执行)
    queue_work(my_wq, &work1);
    queue_work(my_wq, &work2);

    printk(KERN_INFO "工作项已提交到自定义工作队列,等待执行...\n");
    return 0;
}

// 5. 驱动卸载:清理工作队列
static void __exit custom_workqueue_exit(void)
{
    // 等待队列中所有任务执行完毕,然后销毁队列
    flush_workqueue(my_wq);  // 确保所有工作项完成
    destroy_workqueue(my_wq); // 销毁自定义工作队列

    printk(KERN_INFO "自定义工作队列已卸载,总执行任务数:%d\n", work_count);
}

// 注册驱动入口和出口
module_init(custom_workqueue_init);
module_exit(custom_workqueue_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Custom Workqueue Demo");
MODULE_AUTHOR("Your Name");

3.软中断(Softirq):多核安全,并行处理

特点:
静态分配(编译时注册),可在多核 CPU 上并行执行(同类型软中断可在不同核同时运行);
适合高频、高性能场景(如网络数据包处理);
处理函数运行在中断上下文,不能睡眠。
/*
 * @Author: your name
 * @Date: 2025-09-02 11:44:06
 * @LastEditTime: 2025-09-02 11:46:58
 * @LastEditors: Please set LastEditors
 * @Description: In User Settings Edit
 * @FilePath: \linux-4.14.143\drivers\gpu\drm\test\softirq.c
 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/timer.h>
#include <linux/interrupt.h>

// 1. 定义共享数据和软中断编号(使用空闲编号,如31)
#define SOFTIRQ_NUM 31
static int softirq_count = 0;  // 计数:下半部处理次数
static struct timer_list irq_timer;  // 模拟中断的定时器

// 2. 下半部:软中断处理函数(执行耗时操作)
static void softirq_handler(struct softirq_action *action) {
    // 模拟耗时处理(打印信息+计数)
    softirq_count++;
    printk(KERN_INFO "[下半部-软中断] 处理完成!总次数: %d (CPU: %d)\n",
           softirq_count, smp_processor_id());
}

// 3. 上半部:定时器回调(模拟中断服务程序,快速触发下半部)
static void timer_irq_handler(struct timer_list *t) {
    printk(KERN_INFO "[上半部] 收到中断,触发软中断...\n");
    
    // 触发软中断(上半部核心工作:快速调度下半部)
    raise_softirq(SOFTIRQ_NUM);
    
    // 重新启动定时器(模拟周期性中断)
    mod_timer(t, jiffies + msecs_to_jiffies(1000));
}

// 4. 驱动初始化
static int __init softirq_demo_init(void) {
    // 注册软中断
    open_softirq(SOFTIRQ_NUM, softirq_handler);
    
    // 初始化定时器(模拟硬件中断,1秒触发一次)
    timer_setup(&irq_timer, timer_irq_handler, 0);
    mod_timer(&irq_timer, jiffies + msecs_to_jiffies(1000));
    
    printk(KERN_INFO "软中断demo初始化完成\n");
    return 0;
}

// 5. 驱动卸载
static void __exit softirq_demo_exit(void) {
    // 注销定时器和软中断
    del_timer(&irq_timer);
    printk(KERN_INFO "软中断demo卸载完成,总处理次数: %d\n", softirq_count);
}

module_init(softirq_demo_init);
module_exit(softirq_demo_exit);
MODULE_LICENSE("GPL");

图片

结论

待查资料问题

  • 问题 1:?
  • 问题 2:?

参考链接

  • 官方文档
原文链接:,转发请注明来源!