中断是指CPU处理在进行运作时,突发某种情况,使CPU暂停当前处理的事件,转向处理突发的事件,当突发事件处理完成以后,再回到暂停处继续执行未完的操作。
中断可分为外部中断和内部中断:
1.外部中断:指CPU外部器件发生变化时所触发的操作,典型的外部中断就是按键,当按键按下时,去执行相应的操作
2.内部中断:指CPU内部发生某种异常所触发的操作,如溢出、软中断指令等
中断机制将中断处理分为上半部和下半部
上半部:一般为不可打断的,处理紧急事件,要求处理时间短的
下半部:可被打断,处理不太紧急的事件,时间长的也在下半部处理,下半部的中断事件主要是在中断触发时,做一个登记,等到一个适当的时刻再来处理。
linux中中断的处理过程被封装成了接口函数,一般使用这些接口函数即可完成开发,当然也有例外。
下面介绍中断的接口函数
1.int gpio_to_irq(int gpio)//将gpio号转换为中断号并返回
2.int irq_to_gpio(int gpio)//将中断号转换为gpio号并返回
//申请一个中断
3.int __must_check request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
参数介绍
A.unsigned int irq -->中断号,比如IRQ_GPIO_A_START+1,代表的是GPIOA1
#define IRQ_PHY_MAX_COUNT (74 + 32)
#define IRQ_GPIO_START IRQ_PHY_MAX_COUNT
#define IRQ_GPIO_A_START (IRQ_GPIO_START + PAD_GPIO_A)
#define IRQ_GPIO_B_START (IRQ_GPIO_START + PAD_GPIO_B)
#define IRQ_GPIO_C_START (IRQ_GPIO_START + PAD_GPIO_C)
#define IRQ_GPIO_D_START (IRQ_GPIO_START + PAD_GPIO_D)
#define IRQ_GPIO_E_START (IRQ_GPIO_START + PAD_GPIO_E)
B.irq_handler_t handler -->中断服务函数
irqreturn_t keys_isr(int irq, void *dev)
{
......
return IRQ_HANDLED;
}
C.unsigned long flags -->中断标志,一般为上升沿、下降沿、双边沿、高电平、低电平
#define IRQF_TRIGGER_NONE 0x00000000
#define IRQF_TRIGGER_RISING 0x00000001
#define IRQF_TRIGGER_FALLING 0x00000002
#define IRQF_TRIGGER_HIGH 0x00000004
#define IRQF_TRIGGER_LOW 0x00000008
#define IRQF_TRIGGER_MASK (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)
#define IRQF_TRIGGER_PROBE 0x00000010
D.const char *name -->中断的名字,可以自定义,可以使用命令 #cat /proc/interrupts 查看到
E.void *dev -->传递给中断服务函数的参数
4.void free_irq(unsigned int irq, void *dev_id)
参数介绍
unsigned int irq -->中断号
void *dev_id -->传递给中的参数
5.void disable_irq(unsigned int irq) -->关闭中断,使用request_irq申请的中断属于激活状态,可以使用该函数关闭
6.void enable_irq(unsigned int irq) -->开启中断
7.local_irq_save(flags) -->保存当前中断状态,并关闭所有中断
8.local_irq_restore(flags) -->还原被关闭的中断
9.local_irq_disable() -->不保存中断状态,关闭所有中断
10.local_irq_enable() -->开启所有中断
等待队列:主要是为了实现同步
比如在食堂吃饭,你要先打饭,想打饭要先排队,等待队列就是为了完成这个事情,你要吃饭就得先排队打到饭才可以
1.定义一个等待队列
wait_queue_head_t key_wait_queue;
2.初始化等待队列
init_waitqueue_head(key_wait_queue)
以上步骤可以合并为一个
DECLARE_WAIT_QUEUE_HEAD(key_wait_queue);//定义并初始化等待队列
3.定义一个等待队列标志位
int key_wait_queue_pressed_flag = 0;
4.设置等待队列判断,阻塞在这里,这里的区别不太明白,需要继续研究,如果哪位明白,请告诉我,谢谢
wait_event(wq, condition) ; //不可被wake_up唤醒
wait_event_interruptible(key_wait_queue, key_wait_queue_pressed_flag); //可被wake_up唤醒
5.设置等待队列标志位并唤醒等待队列
key_wait_queue_pressed_flag = 1;
wake_up(&key_wait_queue);
下面代码为按键驱动,linux驱动需要从下向上看
#include <linux/module.h>
#include <linux/init.h>
#include <linux/err.h>
#include <linux/miscdevice.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <mach/devices.h>
#include <linux/wait.h>
#define MISCDEVICE_NAME "interrupt_drv"
typedef struct keys_irq_info{
int key_irq;
int key_dev;
char *key_name;
}KEYS_IRQ_INFO;
char pressed_flag[4] = {0, 0, 0, 0}; //保存按键状态
static wait_queue_head_t key_wait_queue; //定义等待队列
static int key_wait_queue_pressed_flag = 0; //等待队列标志位
static KEYS_IRQ_INFO keys_irq[4] = {
{
.key_irq = IRQ_GPIO_A_START+28,
.key_dev = 28,
.key_name = "KEY_GPIOA_28",
},{
.key_irq = IRQ_GPIO_B_START+30,
.key_dev = 30,
.key_name = "KEY_GPIOB_30",
},{
.key_irq = IRQ_GPIO_B_START+31,
.key_dev = 31,
.key_name = "KEY_GPIOB_31",
},{
.key_irq = IRQ_GPIO_B_START+9,
.key_dev = 9,
.key_name = "KEY_GPIOB_9",
}
};
irqreturn_t keys_isr(int irq, void *dev) //中断服务函数
{
int i = 0;
for(i = 0; i < 4; i++){
if(irq == keys_irq[i].key_irq){ //此处通过中断号判断是哪个按键触发的中断,可以通过传递的参数来判断
pressed_flag[i] = 1; //判断后将按键状态保存的标志位置1
printk("<4>" "%s is pressed\n", keys_irq[i].key_name);
}
}
key_wait_queue_pressed_flag = 1; //将等到队列的唤醒标志置1
wake_up(&key_wait_queue); //通过这里唤醒read处阻塞的等待队列,唤醒后将read函数执行完
return IRQ_HANDLED;
}
int interrupt_drv_open(struct inode *inode, struct file *filp)
{
printk("<4>" "interrupt drv open\n");
return 0;
}
ssize_t interrupt_drv_read(struct file *filp, char __user *usr_buf, size_t size, loff_t *offset)
{
int i = 0;
int ret = 0;
if(size != 4)
return -EINVAL;
wait_event_interruptible(key_wait_queue, key_wait_queue_pressed_flag); //当应用软件使用read读取按键状态时,会阻塞在 //这里,直到按键按下后才会继续运行
ret = copy_to_user(usr_buf, pressed_flag, size); //等待队列在中断服务函数被唤醒后执行这里,将按键状态copy到应用层
if(ret != 0)
return -EINVAL;
for(i = 0; i < 4; i++){ //将按键状态重置
pressed_flag[i] = 0;
}
key_wait_queue_pressed_flag = 0;
printk("<4>" "interrupt drv read\n");
return ret;
}
int interrupt_drv_release(struct inode *inode, struct file *filp)
{
printk("<4>" "interrupt drv close\n");
return 0;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = interrupt_drv_open,
.read = interrupt_drv_read,
.release = interrupt_drv_release,
};
static struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR,
.fops = &fops,
.name = MISCDEVICE_NAME,
};
static int __init interrupt_drv_init(void)
{
int ret = 0;
int i = 0;
init_waitqueue_head(&key_wait_queue); //初始化等待队列
ret = misc_register(&misc); //注册混杂设备
if(ret)
return -EBUSY;
for(i = 0; i < 4; i++){
ret = request_irq(keys_irq[i].key_irq, //申请按键中断为下降沿触发
keys_isr, //多个中断源共享一个中断服务函数,需要在函数中进行判断
IRQF_TRIGGER_FALLING, //下降沿触发
keys_irq[i].key_name, //中断名
&keys_irq[i].key_dev //传递到中断服务函数中的参数
);
if(ret < 0)
goto request_irq_err; //申请失败,进入失败处理
}
printk("<4>" "interrupt drv init\n");
return 0;
request_irq_err:
misc_deregister(&misc); //取消注册混杂设备
while(i--)
free_irq(keys_irq[i].key_irq, &keys_irq[i].key_dev); //释放中断
return ret;
}
static void __exit interrupt_drv_exit(void) //驱动退出函数,rmmod命令使用时会运行此处,这里要重点关注,因为很多时候在驱 //动初始化时做了操作,但是在退出时没有做相应操作,会造成段错误,导致驱动退出失败或者下次安装失败
{
int i = 4;
misc_deregister(&misc); //取消注册混杂设备
while(i--)
free_irq(keys_irq[i].key_irq, &keys_irq[i].key_dev); //取消注册混杂设备
printk(KERN_WARNING "interrupt drv exit\n");
}
module_init(interrupt_drv_init);
module_exit(interrupt_drv_exit);
MODULE_DESCRIPTION("interrupt drv");
MODULE_LICENSE("GPL");