☰
10-中断与时钟
2026/10/19 2:10:10 网站建设 项目流程

10.1 中断与定时器

中断是设备主动通知 CPU 的机制;定时器是内核在未来某时刻执行某段代码的机制。二者共同点:回调在原子上下文(软中断上下文)执行,不能睡眠。

7.2.5 下中断子系统的层次:

关键概念:

概念说明
IRQ numberLinux 逻辑中断号,由 irqchip 的 IRQ domain 映射得到
hwirq硬件中断号(GIC/APIC 的编号)
irq_desc每个 Linux IRQ 的描述符(include/linux/irqdesc.h)
irqaction每个注册的 handler(kernel/irq/manage.c)
顶半部(top half)硬中断上下文,极短、不能睡眠
底半部(bottom half)延后执行的重活,见 10.3.3

10.2 Linux 中断处理程序架构

7.2.5 的处理流程(kernel/irq/handle.c):

  1. 硬件中断发生,CPU 进入arch_irq_enter()。
  2. generic_handle_domain_irq()/handle_irq_event()找到irq_desc。
  3. 依次调用该 IRQ 上注册的所有irqaction->handler(顶半部)。
  4. 如果 handler 返回IRQ_WAKE_THREAD且注册了 threaded handler,唤醒对应的irq/%d-%s内核线程执行thread_fn。
  5. 若注册了IRQF_ONESHOT,线程执行完毕后才重新使能该中断。
  6. 触发软中断(raise_softirq())处理tasklet/网络收包等。
  7. arch_irq_exit()。

为什么必须拆成两半:

  • 硬中断期间该 IRQ 线被屏蔽(非IRQF_PERCPU),处理越久,设备越可能丢中断。
  • 硬中断上下文不能睡眠、不能获取 mutex、不能调用可能睡眠的分配。
  • 因此硬中断只做"取走硬件数据、确认中断、唤醒后续处理"。

10.3 Linux 中断编程

10.3.1 申请和释放中断

/* include/linux/interrupt.h */ typedef irqreturn_t (*irq_handler_t)(int irq, void *dev_id); /* 最常用:devm 版本,卸载时自动释放 */ int devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id); /* 带线程化的 devm 版本 */ int devm_request_threaded_irq(struct device *dev, unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn, unsigned long irqflags, const char *devname, void *dev_id); /* 非 devm 版本(需要显式 free_irq) */ int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev); int request_threaded_irq(unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn, unsigned long flags, const char *name, void *dev); void free_irq(unsigned int irq, void *dev_id); /* 探测式获取(不确定是否有 IRQ 时) */ int request_any_context_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev_id);

返回值:

IRQ_HANDLED /* 是本设备的中断,已处理 */ IRQ_NONE /* 不是本设备的中断(共享中断必须正确返回) */ IRQ_WAKE_THREAD /* 请内核唤醒 thread_fn */

irqflags取值表(7.2.5):

标志含义
IRQF_SHARED允许多个设备共享该 IRQ
IRQF_TRIGGER_RISING/FALLING/HIGH/LOW触发方式
IRQF_ONESHOT线程处理完前不复位中断(threaded IRQ 常用)
IRQF_NO_THREAD禁止线程化(RT 内核中用于不可线程化的中断)
IRQF_PERCPU每 CPU 中断
IRQF_NO_AUTOEN注册时不自动使能(需显式enable_irq())
IRQF_NO_SUSPEND挂起时不屏蔽
IRQF_COND_ONESHOT共享中断时同意ONESHOT语义
IRQF_PROBE_SHARED探测共享中断

典型实现:

static irqreturn_t my_hardirq(int irq, void *dev_id) { struct my_dev *dev = dev_id; if (!my_is_my_irq(dev)) return IRQ_NONE; /* 共享中断必须判断 */ /* 只做最小工作:读状态、清中断、唤醒线程 */ return IRQ_WAKE_THREAD; } static irqreturn_t my_threadfn(int irq, void *dev_id) { struct my_dev *dev = dev_id; /* 可以睡眠、可以用 mutex、可以 copy_to_user */ process(dev); return IRQ_HANDLED; } static int my_probe(struct platform_device *pdev) { struct my_dev *dev = ...; int irq = platform_get_irq(pdev, 0); if (irq < 0) return irq; return devm_request_threaded_irq(&pdev->dev, irq, my_hardirq, my_threadfn, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, dev_name(&pdev->dev), dev); }

7.2.5 的关键变化:

  • IRQF_DISABLED已被删除(4.0 时代已废弃)。所有 handler 现在默认在关中断状态下运行。
  • IRQF_ONESHOT与IRQF_COND_ONESHOT对线程化中断很重要。
  • 7.2.5 推荐优先用devm_request_irq()/devm_request_threaded_irq(),卸载时自动释放。
  • request_threaded_irq()支持"handler 为 NULL 时使用默认主处理函数"的用法(返回IRQ_WAKE_THREAD)。
  • 7.2.5 中free_irq()会等待正在运行的 handler 返回;devm版本在devres回滚时自动调用。
  • 中断处理函数的dev_id在共享中断中必须唯一且非 NULL。

10.3.2 使能和屏蔽中断

disable_irq(irq); /* 屏蔽并等待正在执行的 handler 结束 */ disable_irq_nosync(irq); /* 屏蔽但不等待(可在中断上下文调用) */ enable_irq(irq); /* 使能,必须与 disable 配对 */

7.2.5 新增:scope-based 守卫

#include <linux/interrupt.h> scoped_guard(disable_irq, irq) { /* 离开作用域自动 enable_irq(irq) */ }

include/linux/interrupt.h中有DEFINE_LOCK_GUARD_1(disable_irq, int, disable_irq(*_T->lock), enable_irq(*_T->lock))。

重要规则:

  • disable_irq()不可在中断上下文调用(会死锁,因为它要等待 handler 返回);中断上下文用disable_irq_nosync()。
  • enable_irq()与disable_irq()必须严格配对,计数不平衡会触发Unbalanced enable for IRQ警告。
  • 不要在 handler 里disable_irq()自己,用IRQF_ONESHOT或返回IRQ_WAKE_THREAD让内核管理。

10.3.3 底半部机制

7.2.5 可用的底半部机制:

机制上下文可睡眠并发性状态
threaded IRQ内核线程是每个 IRQ 一个线程首选
workqueue内核线程是可并行首选
tasklet软中断否同一 tasklet 不并发仅维护遗留代码
softirq软中断否同类型可多核并发子系统专用
timer软中断否每 CPU见 10.5
threaded IRQ(推荐)

见 10.3.1 的devm_request_threaded_irq()。优点:可在thread_fn中睡眠、获取 mutex、调用copy_to_user()。

workqueue
/* include/linux/workqueue.h */ struct work_struct work; INIT_WORK(&work, my_work_fn); schedule_work(&work); /* 提交到系统工作队列 */ queue_work(my_wq, &work); /* 提交到自定义队列 */ struct delayed_work dwork; INIT_DELAYED_WORK(&dwork, my_delayed_fn); schedule_delayed_work(&dwork, HZ); /* 1 秒后执行 */ queue_delayed_work(my_wq, &dwork, msecs_to_jiffies(100)); mod_delayed_work(my_wq, &dwork, HZ); /* 修改已排队的延时 */ /* 自定义工作队列 */ struct workqueue_struct *wq; wq = alloc_workqueue("mywq", WQ_UNBOUND | WQ_MEM_RECLAIM, 0); /* 7.2.5 资源管理版本 */ wq = devm_alloc_workqueue(dev, "mywq", WQ_MEM_RECLAIM, 0); /* 取消 */ cancel_work_sync(&work); cancel_delayed_work_sync(&dwork); flush_workqueue(wq); destroy_workqueue(wq);

alloc_workqueue()的 flags(include/linux/workqueue.h):

flag含义
WQ_UNBOUND不绑定 CPU,适合可能长时间运行的 work
WQ_MEM_RECLAIM内存回收路径可用(设备驱动几乎总要加)
WQ_HIGHPRI高优先级
WQ_CPU_INTENSIVECPU 密集型
WQ_FREEZABLE可冻结(系统挂起时暂停)
WQ_POWER_EFFICIENT省电模式
WQ_SYSFS在 sysfs 中暴露
max_active最大并发 work 数,0 表示默认

7.2.5 变化:

  • alloc_workqueue()现在展开为alloc_hooks(alloc_workqueue_noprof(...)),带内存分配 profiling 支持。
  • 新增devm_alloc_workqueue(),设备卸载时自动destroy_workqueue()。
  • WQ_POWER_EFFICIENT成为省电默认(CONFIG_WQ_POWER_EFFICIENT_DEFAULT)。
tasklet(遗留)
struct tasklet_struct t; tasklet_setup(&t, my_tasklet_fn); /* 7.2.5 推荐,回调带 tasklet 参数 */ /* 或 */ tasklet_init(&t, my_tasklet_fn_old, data); tasklet_schedule(&t); tasklet_disable(&t); tasklet_enable(&t); tasklet_kill(&t); /* 模块卸载前必须调用 */

7.2.5 建议:tasklet 只用于维护遗留代码。原因:

  • 软中断上下文不能睡眠;
  • 与现代PREEMPT_RT的模型冲突(RT 上 tasklet 被转成线程);
  • 并发模型难以推理。

新代码一律用 threaded IRQ 或 workqueue。

中断上下文的资源限制(必须遵守)
操作硬中断软中断/taskletthreaded IRQ/workqueue
kmalloc(GFP_KERNEL)否否是
kmalloc(GFP_ATOMIC)是是是
mutex_lock()否否是
spin_lock()是是是
copy_to_user()否否是
msleep()否否是
printk()是是是
synchronize_rcu()否否是

10.3.4 实例:GPIO 按键的中断

QEMU 下没有真实 GPIO 按键,但有两条替代路径:

  1. gpio-sim + gpio-keys:用 configfs 创建虚拟 GPIO 线,用gpio-keys驱动绑定。
  2. 自定义 platform 驱动 + gpio-sim:自己写驱动,从 gpio-sim 获取 GPIO 与 IRQ。
路径一:gpio-keys(推荐,无需写代码)

内核配置:

CONFIG_GPIO_SIM=m CONFIG_KEYBOARD_GPIO=m CONFIG_CONFIGFS_FS=y CONFIG_IRQ_SIM=y

guest 中:

mount -t configfs none /sys/kernel/config mkdir /sys/kernel/config/gpio-sim/button mkdir /sys/kernel/config/gpio-sim/button/bank0 echo 1 > /sys/kernel/config/gpio-sim/button/bank0/num_lines echo "button" > /sys/kernel/config/gpio-sim/button/bank0/line0/name echo 1 > /sys/kernel/config/gpio-sim/button/bank0/line0/consumer echo 1 > /sys/kernel/config/gpio-sim/button/live # 激活 ls /sys/bus/gpio/devices/ gpiodetect # libgpiod 工具 gpioinfo # 查看线状态

用gpioset/gpioget(libgpiod)操作虚拟线,配合gpio-keys即可在evtest/input-events中看到按键事件。

路径二:写一个带中断的 GPIO 驱动
#include <linux/module.h> #include <linux/platform_device.h> #include <linux/gpio/consumer.h> #include <linux/interrupt.h> #include <linux/of.h> struct btn_dev { struct gpio_desc *gpio; int irq; struct work_struct work; atomic_t count; }; static irqreturn_t btn_irq(int irq, void *data) { struct btn_dev *dev = data; atomic_inc(&dev->count); schedule_work(&dev->work); /* 延后处理 */ return IRQ_HANDLED; } static void btn_work(struct work_struct *work) { struct btn_dev *dev = container_of(work, struct btn_dev, work); pr_info("button pressed %d times\n", atomic_read(&dev->count)); } static int btn_probe(struct platform_device *pdev) { struct btn_dev *dev; int ret; dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); if (!dev) return -ENOMEM; dev->gpio = devm_gpiod_get(&pdev->dev, "button", GPIOD_IN); if (IS_ERR(dev->gpio)) return PTR_ERR(dev->gpio); dev->irq = gpiod_to_irq(dev->gpio); if (dev->irq < 0) return dev->irq; INIT_WORK(&dev->work, btn_work); atomic_set(&dev->count, 0); ret = devm_request_threaded_irq(&pdev->dev, dev->irq, NULL, btn_irq, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "btn", dev); if (ret) return ret; platform_set_drvdata(pdev, dev); return 0; } static void btn_remove(struct platform_device *pdev) { struct btn_dev *dev = platform_get_drvdata(pdev); cancel_work_sync(&dev->work); } static const struct of_device_id btn_of_match[] = { { .compatible = "qemu,sim-button" }, { } }; MODULE_DEVICE_TABLE(of, btn_of_match); static struct platform_driver btn_driver = { .probe = btn_probe, .remove = btn_remove, .driver = { .name = "sim-button", .of_match_table = btn_of_match, }, }; module_platform_driver(btn_driver); MODULE_LICENSE("GPL");

QEMU 中测试:通过 gpio-sim 触发虚拟线变化,观察dmesg输出。

10.4 中断共享

多个设备共用一个 IRQ 线时需要:

  1. 注册时加IRQF_SHARED。
  2. 每个 handler 必须判断中断是否来自自己,不是则返回IRQ_NONE。
  3. dev_id必须唯一且非 NULL(用于free_irq定位)。
  4. 所有共享者都注册了IRQF_SHARED才能共享。
static irqreturn_t shared_handler(int irq, void *dev_id) { struct my_dev *dev = dev_id; u32 status = readl(dev->base + IRQ_STATUS); if (!(status & MY_IRQ_BIT)) return IRQ_NONE; /* 不是我的中断 */ writel(status & MY_IRQ_BIT, dev->base + IRQ_CLEAR); return IRQ_HANDLED; }

为什么 QEMU 中值得练习:virtio、qemu-xhci等设备都会共享 PCI 中断(INTx),可以观察/proc/interrupts中同一 IRQ 上的多个 handler 名称。

10.5 内核定时器

10.5.1 内核定时器编程

/* include/linux/timer.h */ struct timer_list my_timer; /* 7.2.5 唯一推荐方式 */ timer_setup(&my_timer, my_timer_cb, 0); /* 栈上定时器 */ timer_setup_on_stack(&my_timer, my_timer_cb, 0); destroy_timer_on_stack(&my_timer); /* 回调原型(7.2.5 起) */ static void my_timer_cb(struct timer_list *t) { struct my_dev *dev = from_timer(dev, t, timer); ... } /* 启动/修改 */ mod_timer(&my_timer, jiffies + HZ); /* 1 秒后 */ mod_timer(&my_timer, jiffies + msecs_to_jiffies(500)); add_timer(&my_timer); /* 等价于 mod_timer(t, t->expires) */ del_timer(&my_timer); del_timer_sync(&my_timer); /* 等待回调结束,模块卸载必须用 */ /* 关闭定时器(7.2.5 新增,替代 del_timer_sync 的更安全版本) */ timer_shutdown_sync(&my_timer); timer_shutdown(&my_timer);

7.2.5 的重大变化:

  • init_timer()已被彻底删除,只能用timer_setup()。这是 4.0 代码移植时的第一大坑。
  • 回调原型从void (*)(unsigned long)变成void (*)(struct timer_list *),用from_timer()反查所属结构体。
  • del_timer_sync()在模块卸载路径应优先替换为timer_shutdown_sync(),因为后者保证回调不会再次自我重启,避免卸载后回调重新激活定时器导致的 UAF。
  • 定时器回调在软中断上下文运行,不能睡眠。
  • 自重启定时器:在回调里再次mod_timer(),或用timer_reduce()。

jiffies 与时间换算:

#include <linux/jiffies.h> #include <linux/ktime.h> HZ /* 每秒 tick 数(CONFIG_HZ,常见 250/1000) */ jiffies /* 全局 tick 计数 */ msecs_to_jiffies(ms) usecs_to_jiffies(us) jiffies_to_msecs(j) time_after(a, b) / time_before(a, b) / time_after_eq(a, b) /* 必须用这些比较 */ ktime_get() / ktime_get_ns() / ktime_get_boottime() ktime_set(secs, nsecs)

注意:jiffies比较必须用time_after()/time_before(),直接用>在回绕时会出错。

10.5.2 内核中延迟的工作 delayed_work

struct delayed_work dwork; INIT_DELAYED_WORK(&dwork, my_delayed_fn); schedule_delayed_work(&dwork, HZ); /* 延时 1 秒执行一次 */ schedule_delayed_work_on(cpu, &dwork, HZ); mod_delayed_work(system_wq, &dwork, HZ); cancel_delayed_work(&dwork); /* 不等待 */ cancel_delayed_work_sync(&dwork); /* 等待,卸载必须用 */

定时器 vs delayed_work:

维度timer_listdelayed_work
上下文软中断内核线程
能否睡眠否是
适用极短、高频、无阻塞需要睡眠或耗时的延迟任务
7.2.5 推荐硬件相关的精确短延时绝大多数延时任务

10.5.3 实例:秒字符设备

原书的"second"设备:写入后 1 秒才可读,或在 1 秒后产生事件。7.2.5 版本:

#include <linux/module.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/timer.h> #include <linux/wait.h> #include <linux/jiffies.h> #define SECOND_MAJOR 0 #define SECOND_MAGIC 's' struct second_dev { struct cdev cdev; struct device *device; struct timer_list timer; struct wait_queue_head wq; atomic_t counter; /* 秒计数 */ atomic_t has_data; /* 是否有新数据 */ }; static struct class *second_class; static struct second_dev *second_devp; static void second_timer_fn(struct timer_list *t) { struct second_dev *dev = from_timer(dev, t, timer); atomic_inc(&dev->counter); atomic_set(&dev->has_data, 1); wake_up_interruptible(&dev->wq); /* 唤醒阻塞读者 */ mod_timer(&dev->timer, jiffies + HZ); /* 重新定时 */ } static ssize_t second_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) { struct second_dev *dev = filp->private_data; int counter; char kbuf[32]; int len; if (!atomic_read(&dev->has_data)) { if (filp->f_flags & O_NONBLOCK) return -EAGAIN; if (wait_event_interruptible(dev->wq, atomic_read(&dev->has_data))) return -ERESTARTSYS; } atomic_set(&dev->has_data, 0); counter = atomic_read(&dev->counter); len = scnprintf(kbuf, sizeof(kbuf), "%d\n", counter); if (copy_to_user(buf, kbuf, len)) return -EFAULT; return len; } static int second_open(struct inode *inode, struct file *filp) { struct second_dev *dev = container_of(inode->i_cdev, struct second_dev, cdev); filp->private_data = dev; /* 每次打开重新定时,避免长期空转 */ mod_timer(&dev->timer, jiffies + HZ); return 0; } static int second_release(struct inode *inode, struct file *filp) { return 0; } static const struct file_operations second_fops = { .owner = THIS_MODULE, .open = second_open, .read = second_read, .release = second_release, }; static int __init second_init(void) { dev_t devno; int ret; ret = alloc_chrdev_region(&devno, 0, 1, "second"); if (ret) return ret; second_devp = kzalloc(sizeof(*second_devp), GFP_KERNEL); if (!second_devp) { ret = -ENOMEM; goto err_region; } init_waitqueue_head(&second_devp->wq); atomic_set(&second_devp->counter, 0); atomic_set(&second_devp->has_data, 0); timer_setup(&second_devp->timer, second_timer_fn, 0); cdev_init(&second_devp->cdev, &second_fops); second_devp->cdev.owner = THIS_MODULE; ret = cdev_add(&second_devp->cdev, devno, 1); if (ret) goto err_free; second_class = class_create("second"); if (IS_ERR(second_class)) { ret = PTR_ERR(second_class); goto err_cdev; } second_devp->device = device_create(second_class, NULL, devno, NULL, "second"); if (IS_ERR(second_devp->device)) { ret = PTR_ERR(second_devp->device); goto err_class; } mod_timer(&second_devp->timer, jiffies + HZ); pr_info("second: loaded\n"); return 0; err_class: class_destroy(second_class); err_cdev: cdev_del(&second_devp->cdev); err_free: kfree(second_devp); err_region: unregister_chrdev_region(devno, 1); return ret; } static void __exit second_exit(void) { dev_t devno = MKDEV(MAJOR(second_devp->cdev.dev), 0); timer_shutdown_sync(&second_devp->timer); /* 7.2.5:阻止回调重启定时器 */ device_destroy(second_class, devno); class_destroy(second_class); cdev_del(&second_devp->cdev); kfree(second_devp); unregister_chrdev_region(devno, 1); pr_info("second: unloaded\n"); } module_init(second_init); module_exit(second_exit); MODULE_LICENSE("GPL");

验证:

insmod second.ko cat /dev/second # 立即返回,每秒打印一个递增计数 timeout 5 cat /dev/second dmesg | tail

10.6 内核延时

10.6.1 短延迟(忙等,不睡眠)

/* include/linux/delay.h */ ndelay(ns); /* 纳秒,实际按 us 粒度向上取整 */ udelay(us); /* 微秒,通常 < 1000us */ mdelay(ms); /* 毫秒,内部循环 udelay */

规则:

  • udelay()依赖__udelay()的忙等循环,不能在长延时中使用(会占用 CPU)。
  • mdelay()大于MAX_UDELAY_MS时会拆成多次udelay(1000)。
  • 忙等期间不释放 CPU,只能用于寄存器复位等待等极短场景。

10.6.2 长延迟(可睡眠)

msleep(ms); /* 不可中断睡眠 */ msleep_interruptible(ms); /* 可中断,返回剩余 ms */ ssleep(sec); /* 秒 */ usleep_range(min_us, max_us); /* 推荐:给调度器选择空间,更省电 */ usleep_range_idle(min, max); /* 7.2.5 新增,IDLE 状态,不计入负载 */

7.2.5 推荐:需要几十微秒以上延时且可以睡眠时,用usleep_range()而非udelay(),因为它让高分辨率定时器有合并窗口,显著降低功耗。

10.6.3 睡着延迟(等待事件)

真正的"睡着延迟"应使用等待队列:

/* 等待某个条件,最多等待 100ms */ ret = wait_event_interruptible_timeout(wq, condition, msecs_to_jiffies(100)); /* 精确睡眠 */ ktime_t timeout = ktime_set(0, 100 * NSEC_PER_MSEC); set_current_state(TASK_UNINTERRUPTIBLE); schedule_hrtimeout(&timeout, HRTIMER_MODE_REL);
需求推荐 API
< 10us 且不可睡眠ndelay()/udelay()
10us ~ 20ms,可睡眠usleep_range()
> 20ms,可睡眠msleep()/ssleep()
等待事件wait_event_*()
精确高分辨率延时hrtimer+schedule_hrtimeout()

10.6.4 hrtimer(高分辨率定时器)

/* include/linux/hrtimer.h */ struct hrtimer hr; /* 7.2.5 新式初始化 */ hrtimer_setup(&hr, my_hr_cb, CLOCK_MONOTONIC, HRTIMER_MODE_REL); static enum hrtimer_restart my_hr_cb(struct hrtimer *t) { struct my_dev *dev = from_timer(dev, t, hr); ... hrtimer_forward_now(t, ms_to_ktime(10)); /* 每 10ms 一次 */ return HRTIMER_RESTART; /* 或 HRTIMER_NORESTART */ } hrtimer_start(&hr, ms_to_ktime(100), HRTIMER_MODE_REL); hrtimer_cancel(&hr);

7.2.5 变化:

  • 新增hrtimer_setup()/hrtimer_setup_on_stack()/hrtimer_setup_sleeper_on_stack(),替代hrtimer_init()(hrtimer_init()仍存在但已标注为旧接口)。
  • hrtimer回调在硬中断上下文(HRTIMER_MODE_HARD)或软中断上下文执行,不能睡眠。
  • hrtimer的精度取决于CONFIG_HIGH_RES_TIMERS与硬件时钟源。

QEMU 中的定时器实验观察点:

cat /proc/timer_list # 需要 CONFIG_TIMER_STATS? 实际为 /proc/timer_list cat /proc/interrupts | grep -i timer cat /sys/devices/system/clocksource/clocksource0/current_clocksource

10.7 常见错误

错误现象修正
用init_timer()编译失败(7.2.5 已删除)改用timer_setup()
定时器回调原型仍是void (*)(unsigned long)编译错误/类型不匹配改成void (*)(struct timer_list *)+from_timer()
卸载时只用del_timer()UAF:回调可能还在跑用del_timer_sync()或timer_shutdown_sync()
在定时器回调中睡眠BUG: scheduling while atomic改用 delayed_work
在硬中断中disable_irq()死锁用disable_irq_nosync()
enable_irq()与disable_irq()不配对Unbalanced enable警告严格计数配对
共享中断不判断来源别的设备中断被误处理返回IRQ_NONE
忘记tasklet_kill()卸载后软中断执行已释放内存卸载前tasklet_kill()
用>比较 jiffies回绕后判断错误用time_after()/time_before()
在硬中断里kmalloc(GFP_KERNEL)睡眠警告用GFP_ATOMIC或延后到线程
IRQF_ONESHOT缺失导致中断风暴中断线反复触发加IRQF_ONESHOT

10.8 本章实验清单

实验目的关键观察
gpio-sim + gpio-keys无硬件模拟按键中断/proc/interrupts计数增长
自定义 GPIO 中断驱动理解gpiod_to_irq()+ threaded IRQdmesg打印按键次数
second 秒字符设备理解 timer + 等待队列每秒一个递增数字
delayed_work 版本对比 timer 与 workqueuework 中可睡眠
hrtimer 高频定时理解高分辨率定时器/proc/timer_list可见
共享中断观察理解IRQF_SHARED/proc/interrupts同一 IRQ 多 handler
usleep_range功耗对比理解长延时选择与udelay的 CPU 占用差异

10.9 总结

  1. 中断必须分两半:硬中断只做最少工作,重活放 threaded IRQ 或 workqueue。
  2. 7.2.5 下init_timer()已删除,必须用timer_setup();回调签名已改变。
  3. 卸载定时器必须用timer_shutdown_sync()(或del_timer_sync()),防止回调重启定时器。
  4. tasklet 只用于遗留代码;新代码用 threaded IRQ / workqueue。
  5. 短延时用udelay,长延时用usleep_range/msleep,等待事件用wait_event_*。
  6. QEMU 中可用gpio-sim+gpio-keys完整复现原书的按键中断实验,无需任何真实硬件。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询