这篇文章以按键为例子讲解linux的中断原理,中断的架构,中断的实现。
在我们的开发中,检测按键是否触发,无非就两种方法—轮询和中断。作者认为两种方法最大的区别就是CPU的利用率。
轮询方法:一直占用CPU资源,CPU利用率不高。
中断方法:当产生中断时,占用CPU资源;否则将释放CPU资源。CPU利用率高。
Linux的中断原理:
中断:处理器异常的一种,它是一种特殊的电信号,用硬件发给处理器,处理器接收到中断后,会马上向操作系统反映此信号的到来,然后就由操作系统负责处理这些新到来的数据。
中断服务程序:相应特定中断时,内核会执行的函数。其中:1、中断处理程序是被内核调用来响应中断的。2、而它们运行于我们称之为中断上下文的特殊上下文中。
中断上下部:为了是程序运行得快和完成的工作量多,将中断处理分为两部分,上半部和下半部,其中,上半部(中断处理程序):接收到中断,立马要执行的程序,只做有严格时限的工作。下半部:允许稍后完成的工作。Linux中断的相关函数:
1、注册中断函数:
中断处理函数结构:
typedef irqreturn_t (*irq_handler_t)(int, void*);
注册中断函数:int request_irq(unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *name,
void *dev);
其中: irq:要申请的中断号,CPU为每一个外设的中断编号,是一个虚拟的interrupt ID,与硬件无关,给CPU用来标识一个外设中断。
handler:指向该中断的中断处理程序
flags:中断标志
name:设备名
dev,用于共享中断线
2、释放中断函数:void free_irq(unsigned int irq,void* dev );
其中:irq:处理程序要相应的中断处理程序。
dev:与request_irq的参数dev必须一致,将request_irq指定的dev传递给这个参数;
Linux中断处理分析:
第一种方式:应用层的读,不管什么情况下,都会返回。
第二种方式:应用层的读,只有按键按下的时候,才会返回。
Linux中断实现:
这里例子采用等待队列的方式实现(也就是上述的第二种方法)。
等待队列的方法:
1,定义等待队列:wait_queue_head_t my_queue
2,初始化等待队列:init_waitqueue_head ( &my_queue )
3,定义并初始化等待队列:DECLARE_WAIT_QUEUE_HEAD ( my_queue )
定义等待队列并进行初始化(button_wait_queue):
static DECLARE_WAIT_QUEUE_HEAD(button_wait_queue);
请求中断的实现:
open函数的实现:通过reques_irq函数,请求中断。
其中:IRQ_EINT0:按键对应引脚的中断号。
button_irq:该中断指向中断函数。
IRQT_BOTHEDGE:中断类型标志,这里采用双边沿出发。
1. IRQT_NOEDGE
2. IRQT_RISING//上升沿触发
3. IRQT_FALLING //下降沿触发
4. IRQT_BOTHEDGE//双边沿触发
5. IRQT_LOW//低电平触发
6. IRQT_HIGH//高电平触发
7. IRQT_PROBE
"KEY1":设备名。
&pins_desc[0]:用于共享中断线,请求中断和卸载中断会用到。
static int button_open(struct inode *inode, struct file *file)
{
printk("button irq driver: open\r\n");
request_irq(IRQ_EINT0, button_irq, IRQT_BOTHEDGE, "KEY1", &pins_desc[0]);
return 0;
}
其中pins_desc的定义:
struct pin_desc {
unsigned int pin;
unsigned int key_val;
};
struct pin_desc pins_desc[4] = {
{ GPF0,0xF0},
};
卸载中断的实现:
close函数的实现:通过free_irq卸载中断。
其中:IRQ_EINT0:按键对应引脚的中断号。与加载的中断号一致。
&pins_desc[0]:用于共享中断线,与加载时保持一致
static int button_close(struct inode *inode, struct file *file)
{
free_irq(IRQ_EINT0, &pins_desc[0]);
return 0;
}
中断函数的实现:
button_irq函数的实现:该函数与请求中断注册的中断函数名保持一致。
其中函数参数:irq:中断对应的中断号。
Dev_id:与请求中断函数的第五个参数保持一致。
通过wake_up_interruptible()函数实现等待队列的唤醒。
static irqreturn_t button_irq(int irq, void *dev_id)
{
struct pin_desc *pindesc = (struct pin_desc *)dev_id;
unsigned int pin_val;
pin_val = GPIO_GET_PIN(pindesc->pin);
if(pin_val)
key_val = 0x08 | pindesc->key_val;
else
key_val = pindesc->key_val;
ev_press = 1;
wake_up_interruptible(&button_wait_queue);
return IRQ_RETVAL(IRQ_HANDLED);
}
按键值返回用户层的实现:
read函数的实现:通过检测wait_event_interruptible()等待是否被唤醒,其中当ev_press等于1的时候,程序仅需往下运行,如果等于0,则进入睡眠。
static ssize_t button_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
wait_event_interruptible(button_wait_queue, ev_press);
copy_to_user(buf, &key_val, 1);
ev_press = 0;
return sizeof(key_val);
}
file_operations结构体的实现:
static struct file_operations buttons_fops = {
.owner = THIS_MODULE,
.open = button_open,
.read = button_read,
.release = button_close,
};
模块初始化函数:
其中:register_chrdev():申请设备号。
class_create():创建一个设备类。
device_create():创建一个设备节点,路径:/dev/xxx
static int __init button_init(void)
{
major = register_chrdev(0, DEVICE_NAME, &buttons_fops);
button_class = class_create(THIS_MODULE, "button_driver");
if (IS_ERR(button_class))
return PTR_ERR(button_class);
button_class_device = class_device_create(button_class, NULL, MKDEV(major, 0), NULL, "button");
if (unlikely(IS_ERR(button_class_device)))
return PTR_ERR(button_class_device);
printk("button driver: init\r\n");
return 0;
}
模块卸载函数:
其中:unregister_chrdev():通过主设备号,注销设备号
device_destroy():注销设备节点
class_destroy():注销设备类
static void __exit button_exit(void)
{
unregister_chrdev(major, DEVICE_NAME);
class_device_unregister(button_class_device);
class_destroy(button_class);
printk("button drive: exit\r\n");
}
驱动编写好,写个应用程序进行测试:
当有按键按下时,则输出一句话。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#define file_name "/dev/button"
int main(int argc, char **argv)
{
int fd;
char key_val;
fd = open(file_name, O_RDWR);
if(fd < 0) {
printf("error: can`t open %s", file_name);
return 0;
}
for(;;) {
read(fd, &key_val, 1);
printf("button input: %d\r\n", key_val);
}
return 0;
}
本文分享自 Rice 嵌入式开发技术分享 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!