轮询
Linux 的版本在持续更新,对中断的处理方式也在不停的发生变化。下面几张图,是以前在学习时画的思维导图。这几张图比较清晰地描述了在Linux操作系统中,关于中断的一些基本概念。
这张图的结构还是比较清晰的,基本上概括了Linux系统中的中断分类。
另外,在很多关于中断的书籍中,大部分都是从基础的 PIC(可编程中断控制器)开始讲解的。
PIC/APIC
)相关CPU
相关中断
如何进行处理
。如果您了解Linux中断的相关内容,一定会看到这样的描述:中断处理分为上半部分和下半部分。2步动作
:request_irq()
,向操作系统注册,并且激活指定的中断线
:int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *devname, void *dev_id);
参数说明:
irq: 申请的硬件中断号;
handler: 中断处理函数。一旦中断发生,这个函数就被调用;
flags: 中断的属性,例如:IRQF_DISABLED,IRQF_TIMER,IRQF_SHARED;
devname: 中断驱动程序的名称,在 /proc/interrupts 文件中看到对应的内容;
dev_id: 中断程序的唯一标识,比如:在共享中断中,可以用来区分不同的中断处理程序;
驱动程序通过函数 free_irq(),向操作系统注销一个中断处理函数:
void free_irq(unsigned int irq, void *dev_id);
参数说明:
irq: 硬件中断号;
dev_id: 中断程序的唯一标识;
INIT_WORK(&mywork, mywork_handler);
schedule_work(&mywork);
static struct work_struct mywork;static void mywork_handler(struct work_struct *work)
{printk("This is n");
}
示例代码,捕获键盘的中断,在中断处理函数中,打印出按键的扫描码,如果是 ESC 键被按下,就打印出指定的信息。操作的目录位于:/linux-4.15/drivers
目录下。
$ mkdir my_driver_interrupt
$ touch driver_interrupt.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/interrupt.h>// 中断号
static int irq; // 驱动程序名称
static char * devname; // 用来接收加载驱动模块时传入的参数
module_param(irq, int, 0644);
module_param(devname, charp, 0644);// 定义驱动程序的 ID,在中断处理函数中用来判断是否需要处理
#define MY_DEV_ID 1211// 驱动程序数据结构
struct myirq
{int devid;
};// 保存驱动程序的所有信息
struct myirq mydev ={ MY_DEV_ID };// 键盘相关的 IO 端口
#define KBD_DATA_REG 0x60
#define KBD_STATUS_REG 0x64
#define KBD_SCANCODE_MASK 0x7f
#define KBD_STATUS_MASK 0x80// 中断处理函数
static irqreturn_t myirq_handler(int irq, void * dev)
{struct myirq mydev;unsigned char key_code;mydev = *(struct myirq*)dev; // 检查设备 id,只有当相等的时候才需要处理if (MY_DEV_ID == mydev.devid){// 读取键盘扫描码key_code = inb(KBD_DATA_REG);/* 这里如果放开,每次按键都会打印出很多信息printk("key_code: %x %sn",key_code & KBD_SCANCODE_MASK,key_code & KBD_STATUS_MASK ? "released" : "pressed");*/// 判断:是否为 ESC 键if (key_code == 0x01){printk("EXC key is pressed! n");}} return IRQ_HANDLED;
}// 驱动模块初始化函数
static int __init myirq_init(void)
{printk("myirq_init is called. n");// 注册中断处理函数if(request_irq(irq, myirq_handler, IRQF_SHARED, devname, &mydev)!=0){printk("register irq[%d] handler failed. n", irq);return -1;}printk("register irq[%d] handler success. n", irq);return 0;
}// 驱动模块退出函数
static void __exit myirq_exit(void)
{printk("myirq_exit is called. n");// 注销中断处理函数free_irq(irq, &mydev);
}MODULE_LICENSE("GPL");
module_init(myirq_init);
module_exit(myirq_exit);
示例代码中,在调用 request_irq
时,需要指定中断号和驱动程序的名称。这两个参数是在加载驱动模块的时候,从命令行传入的。在驱动程序中,通过下面两行代码即可实现参数的接收:
module_param(irq, int, 0644);
module_param(devname, charp, 0644);
module_param
是一个宏定义,定义在 include/linux/moduleparam.h
文件中,具体定义如下:
#define module_param(name, type, perm) module_param_named(name, name, type, perm);
name
: 存储参数的变量名;
type
: 变量的类型;
perm
: 访问参数的权限,表示此参数在sysfs文件系统中所对应的文件节点的属性;
读取 IO 外设的两种不同方式:IO内存和IO端口
IO 端口有两种编址方式:统一编址和独立编址。
把IO外设寄存器的地址映射
到这部分划出来的内存地址空间
中。统一编址的好处是:读取IO外设的时候,就好像读取普通的内存地址空间中的数据一样。// 读写一个字节
unsigned inb(unsigned port);
void outb(unsigned char byte, unsigned port);// 读写一个字
unsigned inw(unsigned port);
void outw(unsigned short word, unsigned port);
编译驱动模块:
$ make
输出文件:driver_interrupt.ko
因为我们捕获的是键盘中断(中断号:1),先看一下在加载驱动模块之前的中断驱动程序 head /proc/interrupts
:
可以把 demsg
的输出也清理一下:dmesg -c
执行下面指令来加载驱动模块(传递2个参数):insmod driver_interrupt.ko irq=1 devname=myirq
再次执行一下指令 head /proc/interrupts
查看驱动程序:
在中断号 1
的右侧,是不是看到了我们的驱动程序:my_irq
再来看一下 dmesg
的输出信息:
成功注册了中断号1
的处理函数!
此时,按几次键盘左上角的 ESC 键,然后再查看 dmesg 的输出信息:
本文发布于:2024-02-01 18:28:34,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170678331638602.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |