一.中断简介
1.1中断介绍
指CPU在执行过程之中,出现某些突发事件需要进行紧急处理,CPU此时就要暂停当前运行的程序去处理中断事件,处理完成之后CPU就需要返回原程序被中断的位置继续执行。
1.2中断分类
内部中断:来自CPU内部(cpu溢出、软件中断、触发错误等)
外部中断:来自CPU外部(外设触发:按键等)
1.3屏蔽中断和不可屏蔽中断
可屏蔽中断:可以通过屏蔽字屏蔽,屏蔽之后该中断不再触发响应
不可屏蔽中断:中断无法被屏蔽
1.4向量中断和非向量中断
向量中断:CPU通常会为不同中断分配不同中断号,当检测到某中断号的中断到来之后,就自动跳转到对应地址执行(根据中断类型,存放中断处理程序的地址)
例子:假设发生了IRQ中断,会跳转到中断向量表中IRQ处执行ldr pc, _irq指令,从而跳转到中断处理函数执行
非向量中断:多个中断共享一个向量地址,进入该入口地址后再通过判断中断标志识别是哪一个中断,也就是说向量中断的软件提供中断服务入口地址,非向量中断提供中断入口地址。
1.5 CPU被中断之后原来执行的程序被存放到哪
如果CPU在执行程序时被中断,那么当前运行程序的信息以及各种寄存器的值就会被存放到栈中,中断处理结束之后再将栈的值取出然后继续执行程序。
二.linux中断处理程序架构
2.1中断分层
设备的中断会打断内核进程的调度和运行,所以系统希望中断处理程序尽量短小精悍,但在实际中,中断处理程序的执行时间可能会很长,为了减少耗时,将中断分为中断上半部和中断下半部。
中断上半部:完成尽可能的比较紧急的任务,它往往只是简单的读取寄存器中的中断状态并清除中断标志后就进行,效应速度比较快,执行时间比较短,中断上半部不可以被其它中断打断。
中断下半部:处理繁琐,需要时间长的任务,中断下半部可以被其它中断打断。
注:在linux系统中,中断是不允许嵌套的
如果我在执行中断A的过程中又发生了中断A,那么可以说这是中断的重入不属于中断嵌套。
在早期的Linux系统中中断可以嵌套,但是在现在的Linux系统中中断不可以嵌套。(哪怕优先级高也不可以嵌套)
什么是中断嵌套:指中断系统正在执行一个中断服务L时,有另一个更高优先级的中断H触发,这是中断系统会暂时中止当前正在执行低优先级中断服务程序L,而去处理更高优先的中断H,待处理完毕之后,再返回被中断了的中断服务程序L继续执行。(指在不同的中断之间)
为什么现在不允许中断嵌套了呢?
如果嵌套的中断处理太多,栈就会溢出,因此禁用中断嵌套。
对于一个中断,如何划分上半部呢?哪些处理放在上半部,哪些又放在下半部呢?
1.任务对时间十分敏感,放在上半部
2.任务和硬件相关,放在上半部
3.任务需要保证不被其它中断打断,放在上半部
4.其它任务放在下半部
2.2 中断下半部的几种实现方式:
2.2.1 tasklet方式(软件中断):
使用preempt_count计数方式实现,如果发生中断重入(重复进入同一个中断),若A中断发生N次走N次中断上半部,只走一次中断下半部节省程序运行效率。
2.2.2工作队列方式:
如果中断使用上边哪种tasklet方式,中断下半部需要执行1,2分钟,那么在这几分钟中APP无法响应。工作队列方式是由内核kworker线程去工作队列(work queque)上面取一个工作(work),来执行它的函数。
使用方法:
①创建work
②将work交给work queque
③谁来执行work中的函数
schedule_work函数不仅仅是将work放入队列,还会把kworker线程唤醒。此线程抢到运行时间时,他就会从队列中取出work,执行里面的函数
④谁把work交给work queque?
在中断场景中,可以在中断上半部去调用schedule_work函数。
2.2.3threaded_irq内核线程来处理
使用该函数,系统会为这个函数创建一个内核线程。发生中断时,内核就会执行这个函数。
三.中断发起以及执行过程
假设外部设备1产生中断,告诉GPIO控制器,发生了GPIO B号中断,然后GPIO控制器再告诉GIC控制器,再由GIC控制器告诉CPU
处理中断的流程和产生中断的过程恰好相反,CPU知道有中断产生,调用GIC的中断处理函数,判断是哪一个GPIO控制器发出的中断。然后由GPIO的中断控制函数去调用下面外设(外设设备1、外部设备2)的中断处理函数,判断是由哪一个外设产生的 中断。调用外设的中断处理函数的过程中,判断中断是不是这个外设产生了,如果是继续执行中断处理函数。如果不是该外设产生,停止执行中断处理函数。
软件和硬件中断号:
硬件中断号:先在设备树中声明使用哪一个中断,将硬件中断号转换为虚拟中断号来使用 ,寻找具体使用的是哪一个硬件的时候,要查看虚拟中断号对应的是硬件中断号是什么。使用硬件中断号来寻找具体的硬件。
硬件中断号映射的虚拟中断号无法找到硬件,需要使用硬件中断号来寻找硬件。
(硬件中断号用来区分硬件设备的)
四.程序
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
struct gpio_key{
int gpio;
struct gpio_desc *gpiod;
int flag;
int irq;
} ;
static struct gpio_key *gpio_keys_100ask;
static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
struct gpio_key *gpio_key = dev_id;
int val;
val = gpiod_get_value(gpio_key->gpiod);
printk("key %d %d\n", gpio_key->gpio, val);
return IRQ_HANDLED;
}
/* 1. 从platform_device获得GPIO
* 2. gpio=>irq
* 3. request_irq
*/
static int gpio_key_probe(struct platform_device *pdev)
{
int err;
struct device_node *node = pdev->dev.of_node;
int count;
int i;
enum of_gpio_flags flag;
unsigned flags = GPIOF_IN;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
count = of_gpio_count(node);
if (!count)
{
printk("%s %s line %d, there isn't any gpio available\n", __FILE__, __FUNCTION__, __LINE__);
return -1;
}
gpio_keys_100ask = kzalloc(sizeof(struct gpio_key) * count, GFP_KERNEL);
for (i = 0; i < count; i++)
{
gpio_keys_100ask[i].gpio = of_get_gpio_flags(node, i, &flag);
if (gpio_keys_100ask[i].gpio < 0)
{
printk("%s %s line %d, of_get_gpio_flags fail\n", __FILE__, __FUNCTION__, __LINE__);
return -1;
}
gpio_keys_100ask[i].gpiod = gpio_to_desc(gpio_keys_100ask[i].gpio);
gpio_keys_100ask[i].flag = flag & OF_GPIO_ACTIVE_LOW;
if (flag & OF_GPIO_ACTIVE_LOW)
flags |= GPIOF_ACTIVE_LOW;
err = devm_gpio_request_one(&pdev->dev, gpio_keys_100ask[i].gpio, flags, NULL);
gpio_keys_100ask[i].irq = gpio_to_irq(gpio_keys_100ask[i].gpio);//将GPIO引脚转换成对应中断号
}
for (i = 0; i < count; i++)
{
err = request_irq(gpio_keys_100ask[i].irq, gpio_key_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "100ask_gpio_key", &gpio_keys_100ask[i]);
}
return 0;
}
static int gpio_key_remove(struct platform_device *pdev)
{
//int err;
struct device_node *node = pdev->dev.of_node;
int count;
int i;
count = of_gpio_count(node);
for (i = 0; i < count; i++)
{
free_irq(gpio_keys_100ask[i].irq, &gpio_keys_100ask[i]);
}
kfree(gpio_keys_100ask);
return 0;
}
static const struct of_device_id ask100_keys[] = {
{ .compatible = "100ask,gpio_key" },
{ },
};
/* 1. 定义platform_driver */
static struct platform_driver gpio_keys_driver = {
.probe = gpio_key_probe,
.remove = gpio_key_remove,
.driver = {
.name = "100ask_gpio_key",
.of_match_table = ask100_keys,
},
};
/* 2. 在入口函数注册platform_driver */
static int __init gpio_key_init(void)
{
int err;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
err = platform_driver_register(&gpio_keys_driver);
return err;
}
/* 3. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数
* 卸载platform_driver
*/
static void __exit gpio_key_exit(void)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
platform_driver_unregister(&gpio_keys_driver);
}
/* 7. 其他完善:提供设备信息,自动创建设备节点 */
module_init(gpio_key_init);
module_exit(gpio_key_exit);
MODULE_LICENSE("GPL");