Linux驱动开发之Linux内核中的中断处理与等待队列以及相关API和例程分析
2023-12-30 13:09:19
????????
目录
????????中断是计算机中实现异步事件处理的一种关键机制。当中断发生时,CPU会暂停当前的任务,转去运行中断服务例程。中断处理完成后,CPU再返回到原来的任务。这使得中断处理具有很高的实时性和响应速度。在Linux内核中,充分利用了中断机制来响应各种硬件和软件事件。
在Linux操作系统中,中断的本质就是一个数字,要想使用中断,就是对这个数字进行操作,但是内核不允许直接操作这个数字,可以简介的通过函数进行操作,这个函数对中断进行一个转换,转换的对象就是引脚的编号,引脚的编号转换完成之后就可以得到一个中断号,用来进行操作。
中断的特点
- 快进快出:中断处理必须很快地完成,避免长时间阻塞正常任务。
- 随机发生:中断可能在任何时候发生,需要能够保存和恢复处理前的上下文。
- 高优先级:中断处理具有很高的优先级,可以打断正在运行的进程。
- 准确识别:需要正确区分不同的中断源,调用对应 的服务程序。
Linux中的中断类型
Linux将中断分为以下三类:
- PPI - 私有外设中断,特定的外设专用中断。
- SPI - 共享外设中断,可以服务多个外设,数量有限。
- SGI - 软件生成中断,通过编程的方式触发。
中断号唯一标识一个中断源,用于注册和处理中断。
相关API函数
gpio_to_irq()
功能
通过gpio_to_irq函数将引脚编号转换为中断号,用于后续的操作
头文件
#include<linux/gpio.h>
原型
int gpio_to_irq(unsigned int gpio)
参数
unsigned int gpio 引脚编号
返回值
成功 中断号
失败 负数
enable_irq()
功能
使用enable_irq函数使能中断号,使得该中断可以被触发和处理
头文件
#include<linux/interrupt.h>
原型
void enable_irq(unsigned int irq);
参数
unsigned int irq 中断号
返回值
无
disable_irq()
功能
失能中断号
头文件
#include<linux/interrupt.h>
原型
void disable_irq(unsigned int irq);
参数
unsigned int irq 中断号
返回值
无
request_irq()
功能
使用request_irq函数向内核注册中断号,指定中断服务函数、中断的触发方式、中断的名字等参数
头文件
#include<linux/interrupt.h>
原型
static inline int __must_check request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char * name, void * dev)
参数
unsigned int irq, 中断号
irq_handler_t handler, typedefirqreturn_t (*irq_handler_t)(int, void *);
中断服务函数
unsigned long flags, 中断的触发的方式
IRQF_TRIGGER_RISING 上升沿
IRQF_TRIGGER_FALLING 下降沿
IRQF_TRIGGER_HIGH 高电平
IRQF_TRIGGER_LOW 低电平
const char *name, 中断的名字
void *dev 传给中断服务函数的参数 一般写 NULL
返回值
成功 0
失败 负数
free_irq()
功能
使用free_irq函数取消中断的注册,释放中断号和中断服务函数
头文件
#include<linux/interrupt.h>
原型
void free_irq(unsigned int irq, void *dev_id)
参数
undigned int irq 中断号
void *dev_id 跟注册中断的函数的最后一位保持一致
返回值
无
中断的使用
在Linux中使用中断的一般流程是:
- 将GPIO引脚号转换为中断号。
- 注册中断服务函数。
- 使能中断号。
- 在中断服务函数中编写处理逻辑。
- 根据需要禁用或重新启用中断。
- 当不再需要时,取消中断号注册。
采用这种方式,可以很好地响应异步外部事件,提高系统实时性。
等待队列
????????由于中断具有异步性和随机性,有时需要利用等待队列使内核进入睡眠,等待某事件发生后再被唤醒继续执行。常用的等待队列相关函数有:
DECLARE_WAIT_QUEUE_HEAD()
功能
定义等待队列的结构体
原型
#define DECLARE_WAIT_QUEUE_HEAD(name) \
wait_queue_head_tname = __WAIT_QUEUE_HEAD_INITIALIZER(name)
参数
name: 等待队列的结构体的名字
wait_event_interruptible()
功能
阻塞内核,直到满足指定的条件才解除阻塞
头文件
#include <linux/sched.h>
原型
#define wait_event_interruptible(wq,condition) \
({ \
int__ret = 0; \
if(!(condition)) \
__wait_event_interruptible(wq,condition, __ret); \
__ret; \
})
参数
wq 等待队列的头
condition 条件 0 休眠 1 唤醒
返回值
无
wake_up_interruptible()
功能
解除等待队列的阻塞,唤醒等待队列中的进程
头文件
#include<linux/sched.h>
原型
#define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
参数
x 等待队列的结构体指针
返回值
无
等待队列可以防止空轮询,减少CPU资源占用,是Linux内核编程中非常重要的机制。
中断相关例程
例程分析
????????该例程使用了中断来监控按键状态变化,通过定时器和等待队列在按键按下时解除阻塞并通知用户空间。同时,还提供了对应的文件操作函数供用户空间使用。
全局变量
- ?
?dev_t mydev?
?: 设备号。 - ?
?struct cdev mycdev?
?: 字符设备结构体。 - ?
?struct file_operations myfops?
?: 文件操作结构体。 - ?
?struct class *myclass?
?: 类结构体。 - ?
?unsigned int irq_num[4], myirq?
?: 中断号和当前中断号。 - ?
?int cond?
?: 条件变量,用于在读取函数中阻塞解除。
函数声明
- ?
?myfunc?
?: 中断服务函数,当中断触发时调用。 - ?
?myopen?
??,???myclose?
??,???myread?
??,???mypoll?
?: 对应打开、关闭、读取和轮询操作的函数。
myopen
- 将GPIO引脚转换为中断号。
- 使能中断号。
- 注册中断号并设置中断服务函数。
- 打印日志表示设备已打开。
myclose
- 取消中断的注册并禁用中断。
myread
- 读取GPIO引脚的状态,如果按键按下,则将按键号传递给用户空间。
- 当条件符合时解除阻塞并唤醒等待队列。
定时器函数?my_timer_fun
- 当定时器激活时,检查中断号,如果对应的按键按下,则设置条件变量为1,唤醒等待队列。
mypoll
- 使用?
?poll?
?系统调用进行轮询等待。 - 当条件满足时,返回?
?POLLIN?
?。
模块初始化和退出
- ?
?mykey_init?
?: 初始化函数,在模块加载时执行,包括设备号分配、字符设备注册、类结构体创建以及设备文件创建等操作。 - ?
?mykey_exit?
?: 退出函数,在模块卸载时执行,注销设备文件、类结构体,删除字符设备等操作。
源码分享
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/uaccess.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/poll.h>
dev_t mydev;
struct cdev mycdev;
struct file_operations myfops;
struct class *myclass;
unsigned int irq_num[4], myirq;
int cond;
struct timer_list mytimer;
//创建一个等待队列
DECLARE_WAIT_QUEUE_HEAD(mywait);
irqreturn_t myfunc(int num, void *args)
{
myirq = num;
//更新激活定时器
mod_timer(&mytimer, jiffies + msecs_to_jiffies(15));
return 0;
}
int myopen (struct inode *inode, struct file *file)
{
int ret, i;
//gpio转中断号
for(i = 2; i < 6; i++)
{
irq_num[i - 2] = gpio_to_irq(EXYNOS4_GPX3(i));
if(irq_num[i - 2] < 0)
{
printk("中断号转换失败\n");
return -1;
}
}
//使能中断号
for(i = 0; i < 4; i++)
{
enable_irq(irq_num[i]);
}
//向内核注册一个中断号
for(i = 0; i < 4; i++)
{
ret = request_irq(irq_num[i], myfunc, IRQF_TRIGGER_FALLING, "keytest", NULL);
if(ret < 0)
{
printk("中断号注册失败\n");
return -1;
}
}
printk("open执行完毕\n");
return 0;
}
int myclose (struct inode *inode, struct file *file)
{
int i;
//取消中断的注册
for(i = 0; i < 4; i++)
{
free_irq(irq_num[i], NULL);
disable_irq(irq_num[i]);
}
return 0;
}
ssize_t myread (struct file *file, char __user *buf, size_t size, loff_t *loff)
{
int value, ret, i;
// wait_event_interruptible(mywait, cond);
printk("阻塞解除\n");
for(i = 2; i < 6; i++)
{
value = gpio_get_value(EXYNOS4_GPX3(i));
if(value == 0)
{
i = i - 1;
ret = copy_to_user(buf, &i, 4);
break;
}
}
cond = 0;
return 0;
}
//定时器回调函数
void my_timer_fun(unsigned long data){
int i;
for(i = 0; i < 4; i++)
{
if(myirq == irq_num[i])
{
if(gpio_get_value(EXYNOS4_GPX3(i + 2)) == 0){
printk("按键 %d 按下\n", i + 1);
cond = 1;
wake_up_interruptible(&mywait);
break;
}
}
}
}
//poll轮询函数
unsigned int mypoll (struct file *file, struct poll_table_struct *p){
poll_wait(file, &mywait, p);
if(cond == 1){
cond = 0;
return POLLIN;
}
return 0;
}
static int __init mykey_init(void)
{
//初始化定时器
mytimer.expires = jiffies + 2 * HZ;
mytimer.function = my_timer_fun;
init_timer(&mytimer);
//申请设备号
alloc_chrdev_region(&mydev, 0, 1, "mykey");
printk("申请到的设备号 %d\n", mydev);
printk("主设备号 %d\n", MAJOR(mydev));
printk(
"次设备号 %d\n", MINOR(mydev));
//初始化核心结构体
myfops.owner = THIS_MODULE;
myfops.open = myopen;
myfops.release = myclose;
myfops.read = myread;
myfops.poll = mypoll;
cdev_init(&mycdev, &myfops);
//向设备注册核心结构体
cdev_add(&mycdev, mydev, 1);
//创建类结构体
myclass = class_create(THIS_MODULE, "mykey");
if(myclass == NULL)
{
printk("创建类结构体失败\n");
return -1;
}
//创建设备文件
device_create(myclass, NULL, mydev, NULL, "mykey");
return 0;
}
static void __exit mykey_exit(void)
{
//注销设备文件
device_destroy(myclass, mydev);
//注销类结构体
class_destroy(myclass);
//取消注册
cdev_del(&mycdev);
//释放设备好
unregister_chrdev_region(mydev, 1);
}
module_init(mykey_init);
module_exit(mykey_exit);
MODULE_LICENSE("GPL");
总结
????????中断机制和等待队列在Linux内核中发挥着重要作用,它们的合理利用可以构建出高实时性和高效性的系统。中断编程涉及内核低层操作,需要谨慎处理,但掌握后可以大大提升系统的异步处理能力。
文章来源:https://blog.csdn.net/qq_58288010/article/details/135303246
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!