写点什么

Linux 设备驱动系列 (13) —— 系统中断编程

  • 2024-05-26
    浙江
  • 本文字数:3589 字

    阅读完需:约 12 分钟

Linux设备驱动系列(13) —— 系统中断编程

关注微信公众号:Linux 内核拾遗

文章来源:https://mp.weixin.qq.com/s/K_I_zKtvdTE4UeShy10KUw


前文已经介绍了系统中断的基础知识,本文立刻马上进入系统中断的编程实战。


Linux设备驱动系列(12) —— 系统中断概述

1 中断编程特点

和普通的内核编程不同,中断代码的编写有如下的特点(或者需要注意的地方):


  1. 避免睡眠:中断处理程序不能调用会导致睡眠的函数。

  2. 使用自旋锁:进入临界区时,用自旋锁代替互斥锁,因为互斥锁会导致睡眠。

  3. 禁止与用户空间交换数据:中断处理程序不应直接与用户空间交互。

  4. 快速处理并延迟工作:中断处理程序应快速执行,复杂任务应放在下半部分,通过软中断(softirq)、任务队列(tasklet)或工作队列(workqueue)处理。

  5. 防止重入:确保处理中断时禁用相应的 IRQ,防止重复调用。

  6. 合理设置优先级:中断处理程序可以被更高优先级的中断打断,必要时标记为快速处理,但过多会影响性能。

2 中断相关函数

下面介绍一下中断相关的基本内核函数。

2.1 request_irq

函数 request_irq()用于向内核中注册一个中断处理函数:


request_irq(unsigned int irq, irq_handler_t handler,            unsigned long flags, const char *name, void *dev_id);
复制代码


当内核接收到中断号为 irq 的中断时就会调用 handler 函数进行处理。


flags 的相关定义在头文件linux/interrupt.h中:


  1. IRQF_DISABLED:指示内核在执行中断处理程序时禁用所有中断,大部分情况下不应该设置此 flag,必须时候保留给执行速度快且性能敏感的中断。

  2. IRQF_SAMPLE_RANDOM:指定由此设备生成的中断应贡献到内核熵池,其中内核熵池提供由各种随机事件派生的真正随机数。不要在设备以可预测的速率发出中断(例如系统计时器)或可能被外部攻击者影响(例如网络设备)时设置此标志。

  3. IRQF_SHARED:指定中断线可以在多个中断处理程序之间共享,否则每条中断线上只能存在一个处理程序。

  4. IRQF_TIMER:指定此处理程序处理系统计时器的中断。


name 指定的是使用该 IRQ 的设备名称,它会显示在/proc/interrupts 中。


dev_id:如果中断线未共享,可以传递 NULL;如果中断线是共享的,必须传递一个唯一标识符,用于在共享中断线上唯一标识中断处理程序,确保内核能够正确移除特定的处理程序。


需要注意的是,request_irq()不能在中断上下文(或者其他不应该阻塞的代码)中调用,因为该函数调用会导致阻塞。

2.2 free_irq

函数 free_irq()用于释放由 request_irq()注册的中断处理函数:


free_irq(unsigned int irq, void *dev_id);
复制代码


如果指定的中断线未共享,此函数会移除处理程序并禁用该中断线。如果中断线是共享的,通过 dev_id 标识的处理程序会被移除,但只有在最后一个处理程序被移除时才会禁用中断线。


在共享中断线的情况下,需要一个唯一的标识符来区分同一条线上可能存在的多个处理程序,并使 free_irq() 能够只移除正确的处理程序。无论中断线是否共享,如果 dev_id 非空,它必须与所需的处理程序匹配。


调用 free_irq() 必须在进程上下文中进行。

2.3 其他函数

  1. enable_irq:启用指定的中断线,允许相应的中断请求(IRQ)触发中断处理程序。

  2. disable_irq:禁用指定的中断线,阻止该中断线触发中断处理程序。此函数等待所有正在处理的中断处理程序完成后再返回。

  3. disable_irq_nosync:禁用指定的中断线,但不等待当前正在处理的中断处理程序完成。这可以提高性能,但需要确保不会导致竞态条件。

  4. in_irq:用于确定代码是否在硬中断处理程序(Interrupt handler)中运行。

  5. in_interrupt:检查当前是否在硬中断(Interrupt handler)或者软中断处理程序(Bottom half)中运行。

2.4 使用示例

// 1. 注册中断处理函数#define IRQ_NO 11if (request_irq(IRQ_NO, irq_handler, IRQF_SHARED, "my_device", (void *)(irq_handler))) {    pr_info("my_device: cannot register IRQ ");    return -EINVAL;}
// 2. 释放中断处理函数free_irq(IRQ_NO,(void *)(irq_handler));
// 3. 中断处理函数代码static irqreturn_t irq_handler(int irq,void *dev_id) { pr_info("Shared IRQ: Interrupt Occurred"); return IRQ_HANDLED;}
复制代码

3 中断模拟

下面介绍一下如何通过软件方式来模拟触发硬件中断。


Intel 处理器使用中断描述符表 (IDT) 来处理中断。IDT 由 256 个条目组成,每个条目对应一个向量,并且每个条目占 8 个字节。所有条目都是指向中断处理函数的指针。IDT 的地址存储在 IDTR(IDT 寄存器)中。它们之间的关系可以如下图所示:



代码中可以通过指令"int"来触发一个中断,例如"int $0x80"将触发一个 Linux 系统调用。


Linux 内核的中断向量表映射关系定义在arch/x86/include/asm/irq_vectors.h头文件中:



其中外部中断源可用的 IDT 向量是从 0x20 开始的,并且 0x80 分配给系统调用向量,0x30~0x3f 预留给 ISA。


/* * IDT vectors usable for external interrupt sources start at 0x20. * (0x80 is the syscall vector, 0x30-0x3f are for ISA) */#define FIRST_EXTERNAL_VECTOR    0x20
#define IA32_SYSCALL_VECTOR 0x80
/* * Vectors 0x30-0x3f are used for ISA interrupts. * round up to the next 16-vector boundary */#define ISA_IRQ_VECTOR(irq) (((FIRST_EXTERNAL_VECTOR + 16) & ~15) + irq)
复制代码


因此执行asm("int $0x3B")将会往 IRQ 11 发起中断。

4 完整示例代码

kernel_driver.c


#include <linux/kernel.h>#include <linux/init.h>#include <linux/module.h>#include <linux/kdev_t.h>#include <linux/fs.h>#include <linux/cdev.h>#include <linux/device.h>#include <linux/slab.h>#include <linux/uaccess.h>#include <linux/sysfs.h>#include <linux/kobject.h>#include <linux/interrupt.h>#include <asm/io.h>#include <linux/err.h>
#define IRQ_NO 12static irqreturn_t irq_handler(int irq, void *dev_id){ pr_info("Shared IRQ[%d]: Interrupt Occurred\n", irq); return IRQ_HANDLED;}volatile int my_value = 0;
dev_t dev = 0;static struct class *dev_class;static struct cdev my_cdev;struct kobject *kobj_ref;
static ssize_t sysfs_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf){ return sprintf(buf, "%d", my_value);}static ssize_t sysfs_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count){ sscanf(buf, "%d", &my_value); return count;}
struct kobj_attribute my_attr = __ATTR(my_value, 0660, sysfs_show, sysfs_store);
static ssize_t my_read(struct file *filp, char __user *buf, size_t len, loff_t *off){ asm("int $0x3C"); return 0;}
static struct file_operations fops = { .owner = THIS_MODULE, .read = my_read,};
static int __init my_driver_init(void){ if ((alloc_chrdev_region(&dev, 0, 1, "my_dev")) < 0) return -1;
cdev_init(&my_cdev, &fops); my_cdev.owner = THIS_MODULE; my_cdev.ops = &fops;
if ((cdev_add(&my_cdev, dev, 1)) < 0) goto r_class;
if (IS_ERR(dev_class = class_create(THIS_MODULE, "my_class"))) goto r_class;
if (IS_ERR(device_create(dev_class, NULL, dev, NULL, "my_device"))) goto r_device;
kobj_ref = kobject_create_and_add("my_sysfs", kernel_kobj); if (sysfs_create_file(kobj_ref, &my_attr.attr)) goto r_sysfs;
if (request_irq(IRQ_NO, irq_handler, IRQF_SHARED, "my_device", (void *)(irq_handler))) goto irq;
return 0;irq: free_irq(IRQ_NO, (void *)(irq_handler));r_sysfs: kobject_put(kobj_ref); sysfs_remove_file(kernel_kobj, &my_attr.attr);
r_device: class_destroy(dev_class);r_class: unregister_chrdev_region(dev, 1); cdev_del(&my_cdev); return -1;}
static void __exit my_driver_exit(void){ free_irq(IRQ_NO, (void *)(irq_handler)); kobject_put(kobj_ref); sysfs_remove_file(kernel_kobj, &my_attr.attr); device_destroy(dev_class, dev); class_destroy(dev_class); cdev_del(&my_cdev); unregister_chrdev_region(dev, 1);}
module_init(my_driver_init);module_exit(my_driver_exit);
MODULE_LICENSE("GPL");MODULE_AUTHOR("feifei <feifei@gmail.com>");MODULE_DESCRIPTION("A simple device driver");MODULE_VERSION("1.9");
复制代码


关注微信公众号:Linux 内核拾遗

文章来源:https://mp.weixin.qq.com/s/K_I_zKtvdTE4UeShy10KUw


发布于: 刚刚阅读数: 3
用户头像

聚沙成塔 2023-01-12 加入

分享Linux内核开发相关的编程语言、开发调试工具链、计算机组成及操作系统内核知识、Linux社区最新资讯等

评论

发布
暂无评论
Linux设备驱动系列(13) —— 系统中断编程_Linux内核_Linux内核拾遗_InfoQ写作社区