ESC
输入关键词搜索文章标题和内容

Linux驱动开发:中断处理与并发控制,从上半部到锁机制一次讲透

本文由 linuxROS 整理发布,首发于 linuxros.cn,转载请注明出处。

Linux驱动开发:中断处理与并发控制,从上半部到锁机制一次讲透

中断是外设与CPU沟通的桥梁,并发控制是多核场景下数据安全的底线。这两块搞不清楚,写出来的驱动迟早翻车。

一、中断是什么?

CPU跑得快,外设跑得慢,如果CPU一直轮询外设状态,大部分时间都在空转——这跟等快递时每分钟打一次电话问到了没一个道理。
中断就是让外设"到了叫我",CPU该干嘛干嘛,外设就绪后主动发信号通知CPU。CPU收到信号后暂停当前任务,去处理外设请求,处理完再回来接着干。

中断 vs 轮询

维度 中断方式 轮询方式
CPU利用率 高,空闲时做别的 低,反复查询浪费周期
响应延迟 低,信号到达立即响应 取决于轮询间隔
实现复杂度 较高,需注册handler、处理并发 简单,循环读状态
适用场景 事件驱动、低频外设 高频数据流、无中断线设备
功耗 低,CPU可进入低功耗模式 高,CPU持续运行

中断类型

Linux把中断分成两大类。
- 硬件中断(IRQ):外设通过中断控制器发来的电信号,CPU必须立即响应,比如按键按下、网卡收包、定时器到期。- 软件中断(softirq):内核模拟的中断机制,用于推迟执行耗时操作。softirq、tasklet、workqueue都属于这一类。
硬件中断优先级最高,会打断一切;软件中断在硬中断处理完毕后、进程调度之前执行。

二、中断处理流。

中断来了,CPU不是随便跳转就完事,有一套严格的流程。

flowchart TB A(["中断信号到达"]) --> B["保存当前上下<br/>(寄存器入栈"] B --> C["查找IRQ<br/>(中断控制器映射"] C --> D["执行中断handler<br/>(上半部:快进快出)"] D --> E{"有下半部任务?"} E -->|"| F["标记下半<br/>(tasklet/workqueue"] E -->|"| G["恢复上下"] F --> G G --> H["开中断<br/>恢复被中断的执行"] H --> I{"下半部已标记?"} I -->|"| J["执行下半部处<br/>(softirq/tasklet/workqueue"] I -->|"| K(["继续正常执行"]) J --> K

上半部 vs 下半部

这是Linux中断处理的核心设计哲学:

维度 上半部(硬中断) 下半部(软中断)
执行上下文 中断上下文 中断上下文或进程上下文
中断状态 关中断或部分分支 开中断
执行时间 必须极短 可以较长
能否睡眠 绝对不行 workqueue可以
典型操作 读取硬件状态、ACK 数据处理、协议解析
机制 irq_handler softirq/tasklet/workqueue

一句话上半部只做最紧急的事,剩下的推给下半部 你在中断handler里磨蹭,整个系统的实时性都得跟着遭殃。

三、中断注册与处理

request_irq / free_irq

Linux 6.8中注册中断的标准接口。

#include <linux/interrupt.h>

// 注册中断
int request_irq(unsigned int irq,
                irq_handler_t handler,
                unsigned long flags,
                const char *name,
                void *dev);

// 释放中断
void free_irq(unsigned int irq, void *dev);

参数说明。
- irq:IRQ编号,从设备树或平台资源获取
- handler:中断处理函数- flags:中断标志位
- name。proc/interrupts中显示的名称
- dev:传递给handler的私有数据,共享中断时用于区。

irq_handler_t 签名与返回调

typedef irqreturn_t (*irq_handler_t)(int irq, void *dev_id);

返回值:

  • IRQ_HANDLED:中断已处理
  • IRQ_NONE:不是我的中断(共享中断时用。

共享中断:IRQF_SHARED

多个设备共用一条中断线时,必须设置IRQF_SHARED标志,且dev_id不能为NULL(内核靠它区分是谁的中断):

// 共享中断注册
ret = request_irq(irq, my_handler, IRQF_SHARED, "my_device", &my_dev);

所有共享同一个IRQ的handler都会被调用,每个handler检查dev_id判断是不是自己的设备产生的中断,不是就返回IRQ_NONE。

线程化中断:request_threaded_irq

Linux 6.8推荐使用线程化中断,把耗时的下半部工作放到内核线程中执行:

int request_threaded_irq(unsigned int irq,
                         irq_handler_t handler,    // 上半部,可为NULL
                         irq_handler_t thread_fn,   // 线程化下半部
                         unsigned long flags,
                         const char *name,
                         void *dev);

handler返回IRQ_WAKE_THREAD时,内核会唤醒thread_fn对应的内核线程来执行,线程化中断的好处:下半部运行在进程上下文,可以睡眠,RT-Linux友好。

完整代码示例:按键中断驱动

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/platform_device.h>

#define DRIVER_NAME "btn_irq_demo"

struct btn_priv {
    int gpio;
    int irq;
    struct device *dev;
};

// 上半部:读取GPIO状态,决定是否唤醒线程
static irqreturn_t btn_hard_handler(int irq, void *dev_id)
{
    struct btn_priv *priv = dev_id;
    int val = gpio_get_value(priv->gpio);
    dev_info(priv->dev, "hard IRQ: GPIO=%d val=%d\n", priv->gpio, val);
    // 唤醒线程化下半部
    return IRQ_WAKE_THREAD;
}

// 线程化下半部:可以睡眠,做耗时操作
static irqreturn_t btn_thread_fn(int irq, void *dev_id)
{
    struct btn_priv *priv = dev_id;
    int val = gpio_get_value(priv->gpio);
    dev_info(priv->dev, "thread: GPIO=%d val=%d\n", priv->gpio, val);
    // 这里可以做msleep、kmalloc(GFP_KERNEL)等操。    return IRQ_HANDLED;
}

static int btn_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct btn_priv *priv;
    struct device_node *np = dev->of_node;
    int ret;

    priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;

    priv->dev = dev;

    // 从设备树获取GPIO
    priv->gpio = of_get_named_gpio(np, "btn-gpio", 0);
    if (!gpio_is_valid(priv->gpio))
        return dev_err_probe(dev, -EINVAL, "invalid btn-gpio\n");

    ret = devm_gpio_request(dev, priv->gpio, "btn_gpio");
    if (ret)
        return dev_err_probe(dev, ret, "gpio request failed\n");

    gpio_direction_input(priv->gpio);

    // 获取IRQ。    priv->irq = gpio_to_irq(priv->gpio);
    if (priv->irq < 0)
        return dev_err_probe(dev, priv->irq, "gpio_to_irq failed\n");

    // 注册线程化中断,下降沿触。    ret = devm_request_threaded_irq(dev, priv->irq,
                                     btn_hard_handler,
                                     btn_thread_fn,
                                     IRQF_TRIGGER_FALLING,
                                     DRIVER_NAME, priv);
    if (ret)
        return dev_err_probe(dev, ret, "request threaded irq failed\n");

    platform_set_drvdata(pdev, priv);
    dev_info(dev, "button irq driver probed, gpio=%d irq=%d\n",
             priv->gpio, priv->irq);
    return 0;
}

static const struct of_device_id btn_of_match[] = {
    { .compatible = "linuxros,btn-irq" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, btn_of_match);

static struct platform_driver btn_driver = {
    .probe  = btn_probe,
    .driver = {
        .name           = DRIVER_NAME,
        .of_match_table = btn_of_match,
    },
};
module_platform_driver(btn_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("linuxros.cn");
MODULE_DESCRIPTION("Button IRQ driver demo (Linux 6.8)");

对应的设备树节点。

btn_irq {
    compatible = "linuxros,btn-irq";
    btn-gpio = <&gpio0 5 GPIO_ACTIVE_LOW>;
};

四、下半部机制

上半部必须快,那耗时操作往哪推?三种机制各有适用场景:

三种机制对比

维度 softirq tasklet workqueue
执行上下文 中断上下文 中断上下文 进程上下文
能否睡眠 不能 不能 可以
并发度 同类型可多CPU并行 同类型严格串行 每个worker独立
使用途 内核核心(网络、块设备等) 驱动开发 驱动开发
灵活度 低,需编译时注册 中 高
开销 最低 无 较大(线程调度)

softirq

最底层的软中断机制。6.8内核中只有网络收发(NET_RX/NET_TX)、块设备、定时器等核心子系统在用。驱动开发者一般不直接碰softirq,内核也不建议导出相关API给模块使用。
softirq在__do_softirq()中执行,同一个softirq可以在多个CPU上同时运行,所以softirq的handler必须自己处理多核并发。

tasklet

基于softirq实现的轻量级延迟机制,是驱动开发中最常用的下半部方案。

// 定义并初始化
DECLARE_TASKLET(my_tasklet, my_tasklet_func, (unsigned long)&my_data);

// 或者动态初始化
struct tasklet_struct my_tasklet;
tasklet_init(&my_tasklet, my_tasklet_func, (unsigned long)&my_data);

// 在中断handler中调试tasklet_schedule(&my_tasklet);

// 不用了要销毁
tasklet_kill(&my_tasklet);

tasklet的关键特性:同一个tasklet不会在多个CPU上同时执行,但不同tasklet可以并行。这比softirq安全得多,代价是同一tasklet的执行是串行的。

workqueue

运行在进程上下文的延迟执行机制,可以睡眠、可以调度、可以执行耗时操作。集

// 静态定义
DECLARE_WORK(my_work, my_work_func);
DECLARE_DELAYED_WORK(my_dwork, my_dwork_func);

// 动态初始化
INIT_WORK(&my_work, my_work_func);
INIT_DELAYED_WORK(&my_dwork, my_dwork_func);

// 调度执行
schedule_work(&my_work);
schedule_delayed_work(&my_dwork, msecs_to_jiffies(100));

// 等待完成
flush_work(&my_work);
cancel_delayed_work_sync(&my_dwork);

workqueue的handler函数签名。

来自 linuxros.cn · linuxROS
void my_work_func(struct work_struct *work);

6.8内核默认使用system_wq(per-CPU的worker pool),也可以用alloc_workqueue()创建自定义workqueue。

选择指南

场景 推荐机制 理由
简单延迟操作,不需睡眠 tasklet 轻量、安全、够用
需要睡眠(kmalloc、msleep等) workqueue 进程上下文,允许睡眠
需要定时延迟执行 delayed_work workqueue的定时版本
高频网络包处理 softirq(NAPI) 内核网络栈已实现
需要指定CPU执行 自定义workqueue 设置WQ_CPU_INTENSIVE

经验法则:拿不准就用workqueue,它最安全。tasklet虽然轻量,但在中断上下文执行的约束太多。

五、并发控制原。

中断随时可能打断当前执行流,多核CPU上多个handler可能同时跑——共享数据不加保护,数据竞争只是时间问题。

自旋锁(spinlock。

自旋锁是最基本的中断上下文同步原语。拿不到锁就原地自旋(忙等),不会让出CPU。

#include <linux/spinlock.h>

// 定义
spinlock_t my_lock;

// 初始化spin_lock_init(&my_lock);

// 基本用法
spin_lock(&my_lock);
// 临界区
spin_unlock(&my_lock);

关键问题:如果进程拿着spinlock,中断handler也要拿同一个锁——死锁。因为中断会打断进程,handler发现锁被自己打断的进程拿着,永远等不到释放。
解决方案——中断安全版本:

// 关本地中断版本
spin_lock_irq(&my_lock);
// 临界区
spin_unlock_irq(&my_lock);

// 保存/恢复中断标志版本(推荐)
unsigned long flags;
spin_lock_irqsave(&my_lock, flags);
// 临界区
spin_lock_restore(&my_lock, flags);

spin_lock_irqsave把当前CPU的中断状态保存到flags,然后关中断再拿锁。spin_unlock_irqrestore恢复之前的中断状态。这比spin_lock_irq安全,因为你不知道调用时中断是开还是关。

互斥锁(mutex。

mutex用于进程上下文的同步,拿不到锁就睡眠让出CPU。

#include <linux/mutex.h>

// 定义和初始化
DEFINE_MUTEX(my_mutex);
// 或动态初始化
struct mutex my_mutex;
mutex_init(&my_mutex);

// 基本用法
mutex_lock(&my_mutex);
// 临界区(可以睡眠)
mutex_unlock(&my_mutex);

// 可中断版本(推荐用于用户空间可触发的路径)
if (mutex_lock_interruptible(&my_mutex))
    return -ERESTARTSYS;  // 被信号打断
// 临界区
mutex_unlock(&my_mutex);

*铁律:中断上下文绝对不能用mutex mutex拿不到会睡眠,中断上下文不允许睡眠。

原子操作

不需要锁的轻量级并发控制,适合简单的计数器操作:

#include <linux/atomic.h>

// 定义和初始化
atomic_t counter = ATOMIC_INIT(0);

// 操作
atomic_inc(&counter);           // +1
atomic_dec(&counter);           // -1
atomic_add(5, &counter);        // +5
atomic_sub(3, &counter);        // -3
int val = atomic_read(&counter); // 。
// 带返回调int new_val = atomic_inc_return(&counter);  // +1并返回新。
// 条件操作
atomic_dec_and_test(&counter);  // -1,结果为0返回true
atomic_cmpxchg(&counter, old, new); // CAS操作

6.8内核还提供了atomic64_t和atomic_long_t,用法相同。

读写锁(rwlock)与 RCU

读多写少场景的优化方案:

rwlock——读写自旋锁。

#include <linux/rwlock.h>

rwlock_t my_rwlock;
rwlock_init(&my_rwlock);

// 读端(可多读者并行)
read_lock(&my_rwlock);
// 只读临界区
read_unlock(&my_rwlock);

// 写端(独占)
write_lock_irqsave(&my_rwlock, flags);
// 写临界区
write_unlock_irqrestore(&my_rwlock, flags);

RCU——读端几乎零开销。

#include <linux/rcupdate.h>

// 读端
rcu_read_lock();
ptr = rcu_dereference(global_ptr);
// 使用ptr,不能睡眠
rcu_read_unlock();

// 写端
new_ptr = kmalloc(sizeof(*new_ptr), GFP_KERNEL);
*new_ptr = *old_ptr;       // 拷贝
new_ptr->field = new_val;  // 修改
rcu_assign_pointer(global_ptr, new_ptr); // 原子更新指针
synchronize_rcu();          // 等待所有读者退出
kfree(old_ptr);            // 安全释放旧数```

RCU的读端不加锁不写内存屏障,开销极低。代价是写端要等所有读者退出才能释放旧数据,适合读远多于写的场景:
### 完成量(completion。
用于"一个执行流通知另一个执行流完成"的场景:

```c
#include <linux/completion.h>

// 定义和初始化
DECLARE_COMPLETION(my_comp);
// 静态定义
struct completion my_comp;
init_completion(&my_comp);

// 等待
wait_for_completion(&my_comp);           // 不可中断等待
wait_for_completion_interruptible(&my_comp); // 可中断
wait_for_completion_timeout(&my_comp, timeout); // 超时

// 完成
complete(&my_comp);       // 唤醒一个等待者
complete_all(&my_comp);   // 唤醒所有等待者

典型场景:中断handler中complete(),进程上下文wait_for_completion()。

各原语对。

原语 适用上下文 可否睡眠 开销 典型场景
spinlock 中断+进程 不能 中断handler与进程共享数据
spin_lock_irqsave 中断+进程 不能 防止中断打断导致的死锁
mutex 仅进程 可以 中 进程间互斥
atomic 任意 不能 最低 简单计数器
rwlock 中断+进程 不能 读多写少
RCU 读:任意;写:进程 读不能,写可以 读极低 读远多于写
completion 进程等待 等待时可以 低 中断通知进程

六、完整示例:中断+并发控制

把前面的知识点串起来:按键中断上半部读取GPIO状态,tasklet做延迟处理,spinlock保护共享数据。

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/spinlock.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/fs.h>

#define DRIVER_NAME "btn_concurrent"
#define BUF_SIZE 64

struct btn_priv {
    int gpio;
    int irq;
    struct device *dev;
    dev_t devt;
    struct cdev cdev;
    struct class *class;

    // 共享数据:自旋锁保护
    spinlock_t lock;
    int event_count;
    int last_gpio_val;
    char buf[BUF_SIZE];
    int buf_len;

    // 下半。    struct tasklet_struct tlet;
};

// tasklet处理函数:在中断上下文执行,不能睡眠
static void btn_tasklet_func(unsigned long data)
{
    struct btn_priv *priv = (struct btn_priv *)data;
    unsigned long flags;
    int count, val;
    char local_buf[BUF_SIZE];
    int local_len;

    // 拷贝共享数据到本地,减少临界区时。    spin_lock_irqsave(&priv->lock, flags);
    count = priv->event_count;
    val = priv->last_gpio_val;
    local_len = priv->buf_len;
    if (local_len > 0)
        memcpy(local_buf, priv->buf, local_len);
    spin_unlock_irqrestore(&priv->lock, flags);

    // 在本地处理数据,不持。    dev_info(priv->dev, "tasklet: count=%d val=%d\n", count, val);
}

// 中断上半部:只做最紧急的操作
static irqreturn_t btn_handler(int irq, void *dev_id)
{
    struct btn_priv *priv = dev_id;
    unsigned long flags;
    int val;

    val = gpio_get_value(priv->gpio);

    // 快速更新共享数。    spin_lock(&priv->lock);  // handler中中断已关,用spin_lock即可
    priv->event_count++;
    priv->last_gpio_val = val;
    priv->buf_len = snprintf(priv->buf, BUF_SIZE, "evt:%d val:%d\n",
                             priv->event_count, val);
    spin_unlock(&priv->lock);

    // 调度下半。    tasklet_schedule(&priv->tlet);

    return IRQ_HANDLED;
}

// 用户空间读取中断事件计数
static ssize_t btn_read(struct file *filp, char __user *ubuf,
                        size_t count, loff_t *ppos)
{
    struct btn_priv *priv = filp->private_data;
    unsigned long flags;
    char kbuf[BUF_SIZE];
    int len;

    spin_lock_irqsave(&priv->lock, flags);
    len = snprintf(kbuf, BUF_SIZE, "events=%d last_val=%d\n",
                   priv->event_count, priv->last_gpio_val);
    spin_unlock_irqrestore(&priv->lock, flags);

    if (*ppos >= len)
        return 0;

    if (count > len - *ppos)
        count = len - *ppos;

    if (copy_to_user(ubuf, kbuf + *ppos, count))
        return -EFAULT;

    *ppos += count;
    return count;
}

static int btn_open(struct inode *inode, struct file *filp)
{
    struct btn_priv *priv = container_of(inode->i_cdev,
                                         struct btn_priv, cdev);
    filp->private_data = priv;
    return 0;
}

static const struct file_operations btn_fops = {
    .owner   = THIS_MODULE,
    .open    = btn_open,
    .read    = btn_read,
};

static int btn_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct btn_priv *priv;
    struct device_node *np = dev->of_node;
    int ret;

    priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;

    priv->dev = dev;
    spin_lock_init(&priv->lock);

    // GPIO和IRQ初始化    priv->gpio = of_get_named_gpio(np, "btn-gpio", 0);
    if (!gpio_is_valid(priv->gpio))
        return dev_err_probe(dev, -EINVAL, "invalid btn-gpio\n");

    ret = devm_gpio_request(dev, priv->gpio, "btn_gpio");
    if (ret)
        return dev_err_probe(dev, ret, "gpio request failed\n");

    gpio_direction_input(priv->gpio);
    priv->irq = gpio_to_irq(priv->gpio);
    if (priv->irq < 0)
        return dev_err_probe(dev, priv->irq, "gpio_to_irq failed\n");

    // 初始化tasklet
    tasklet_init(&priv->tlet, btn_tasklet_func, (unsigned long)priv);

    // 注册中断
    ret = devm_request_irq(dev, priv->irq, btn_handler,
                           IRQF_TRIGGER_FALLING,
                           DRIVER_NAME, priv);
    if (ret)
        return dev_err_probe(dev, ret, "request irq failed\n");

    // 字符设备注册
    ret = alloc_chrdev_region(&priv->devt, 0, 1, DRIVER_NAME);
    if (ret)
        return dev_err_probe(dev, ret, "alloc chrdev failed\n");

    cdev_init(&priv->cdev, &btn_fops);
    priv->cdev.owner = THIS_MODULE;
    ret = cdev_add(&priv->cdev, priv->devt, 1);
    if (ret)
        goto err_region;

    priv->class = class_create(DRIVER_NAME);
    if (IS_ERR(priv->class)) {
        ret = PTR_ERR(priv->class);
        goto err_cdev;
    }

    if (IS_ERR(device_create(priv->class, NULL, priv->devt,
                              NULL, DRIVER_NAME))) {
        ret = -ENOMEM;
        goto err_class;
    }

    platform_set_drvdata(pdev, priv);
    dev_info(dev, "probed: gpio=%d irq=%d dev=%d:%d\n",
             priv->gpio, priv->irq, MAJOR(priv->devt), MINOR(priv->devt));
    return 0;

err_class:
    class_destroy(priv->class);
err_cdev:
    cdev_del(&priv->cdev);
err_region:
    unregister_chrdev_region(priv->devt, 1);
    return ret;
}

static void btn_remove(struct platform_device *pdev)
{
    struct btn_priv *priv = platform_get_drvdata(pdev);

    tasklet_kill(&priv->tlet);
    device_destroy(priv->class, priv->devt);
    class_destroy(priv->class);
    cdev_del(&priv->cdev);
    unregister_chrdev_region(priv->devt, 1);
    dev_info(&pdev->dev, "removed\n");
}

static const struct of_device_id btn_of_match[] = {
    { .compatible = "linuxros,btn-concurrent" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, btn_of_match);

static struct platform_driver btn_driver = {
    .probe   = btn_probe,
    .remove  = btn_remove,
    .driver  = {
        .name           = DRIVER_NAME,
        .of_match_table = btn_of_match,
    },
};
module_platform_driver(btn_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("linuxros.cn");
MODULE_DESCRIPTION("Button IRQ + concurrency control demo (Linux 6.8)");

这个例子覆盖了几个关键点。
1. spinlock保护共享数据:event_count、last_gpio_val、buf被中断handler和read函数共享,必须加锁。2. handler中用spin_lock:硬中断handler执行时本地CPU中断已关,用spin_lock就够。3. 进程上下文用spin_lock_irqsave:read函数可能被中断打断,必须保存中断标志
4. tasklet做延迟处理:handler只更新数据、调度tasklet,tasklet做后续处理。5. 临界区尽量短**:tasklet中先持锁拷贝数据到本地,释放锁后再处理。

七、常见问题

*Q1:中断handler里调用kmalloc(GFP_KERNEL)会怎样

会死。GFP_KERNEL意味着允许睡眠(内核可能需要换页、刷磁盘),中断上下文不允许睡眠。必须用GFP_ATOMIC,它不会睡眠,代价是分配成功率略低。同理,中断handler里也不能调用msleep()、mutex_lock()、copy_from_user()等可能阻塞的函数。
Q2:spin_lock在中断handler里导致死锁怎么破?

进程拿着spinlock时被中断打断,handler也要拿同一个锁——死锁。解法:进程上下文用spin_lock_irqsave(),拿锁前先关本地CPU中断。handler里用spin_lock()即可(因为中断进来时本地CPU中断已关)。记住一条:谁怕被打断谁关中断。
Q3:tasklet和workqueue怎么选?

需要睡眠(调kmalloc(GFP_KERNEL)、msleep、等IO)就用workqueue。不需要睡眠且对延迟敏感就用tasklet,它在中断上下文执行,调度开销更小。拿不准就workqueue,它最安全,性能差距在大多数场景下可以忽略。

八、总结

知识 关键结论
中断本质 外设主动通知CPU,避免轮询空转
上半部/下半部 上半部快进快出,耗时操作推迟到下半部
request_threaded_irq 6.8推荐,下半部运行在内核线程可睡眠
tasklet 中断上下文,不能睡眠,同类型串行
workqueue 进程上下文,可以睡眠,最安全
spin_lock_irqsave 进程上下文防死锁的标准写法
mutex 仅进程上下文,可睡眠,中断中禁用
atomic 最轻量,适合简单计数器
GFP_ATOMIC 中断上下文唯一的内存分配标志

本文首发于linuxros.cn,转载请注明出处。

版权声明

作者linuxROS
协议本作品采用 CC BY-NC-SA 4.0 许可协议:署名-非商业性使用-相同方式共享
关注欢迎关注微信公众号 linuxROS,获取更多机器人 / 嵌入式 / Linux 干货
返回首页