中断处理是外围设备操作的基石,大致的中断流程图如下文,关于中断控制器的文章可以看我之前发过的贴子。本文侧重阐述中断时上下半部和内核调度部分的关键点

一 、上半部
为了提高系统的实时性,linux中把中断分为上下半部,其中上半部的处理需要很快,否则会影响系统的实时性。
上半部采用轮询机制,每个中断的上半部是平等的,无法人为设置优先级,先来先处理(但受硬件顺序影响,响应是立即的(不可抢占),所以一旦某个中断的上半部十分繁杂,CPU就会一直卡在这里,严重影响整体的实时性。

二、 下半部
对于在中断中相对耗时的操作,可以从上半部移到下半部,如将指令放在TASKLET_SOFTIRQ中
也可以在下半部进行延时的操作或者进行其它中断的屏蔽
延时一秒:

屏蔽:

最重要的一点,由于中断中没有PCB(Process Control Block,进程控制块)来控制跳转,所以在上下半部中不可以使用msleep()这类函数,否则会造成内核崩溃。简单理解就是中断一旦被切换到别处就没法回来,中断上下文不是线程,不能“睡”或“切出去”等待再回来。
三、内核调度
更复杂的操作则常放在内核调度的WORK_QUEUE中,在普通进程/线程上下文(kernel thread)中运行。优先级低于 Tasklet;属于内核工作线程,按调度策略(SCHED_NORMAL 或 SCHED_FIFO)执行。可以进行睡眠、阻塞、访问用户空间、耗时较长的操作。
再次记住这张结构图:

下面是一个关于中断的驱动代码,使用外部按键作为中断触发信号,可以参考
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/gpio.h>
#include <linux/platform_device.h>
#include <asm/io.h>
#include <linux/interrupt.h>
#ifdef CONFIG_OF
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#endif
struct key_dev{ //定义设备的类
struct device *dev;
const char *name;
unsigned int gpio_num;
unsigned int irq_num;
};
irqreturn_t key_interrupt(int irqno, void *devid)
{
pr_info("do interrupt irqno = %d\n", irqno);
return IRQ_HANDLED;
}
int keys_probe(struct platform_device *pdev)
{
int ret = -1;
struct key_dev *key; //创建设备对象
enum of_gpio_flags flags;
key = devm_kzalloc(&pdev->dev, sizeof(*key), GFP_KERNEL); /*为设备对象分配空间
函数 devm_kzalloc() 和kzalloc()一样都是内核内存分配函数,但是devm_kzalloc()是跟设备(device)有关的,当设备(device)被detached或者驱动(driver)卸载(unloaded)时,内存会被自动释放。
当然,当内存不再使用时,可以使用函数devm_kfree()释放。
而kzalloc() 必须手动释放(配对使用kfree()),但如果工程师检查不仔细,则有可能造成内存泄漏
devm_kzalloc 有在统一设备模型的设备树记录空间,有自动释放的第二道防线,更安全。
如 在keys_remove中 不加 devm_kfree,但再probe中途异常要加
*/
if (!key){
pr_err("devm_kzalloc fail \n");
ret = -ENOMEM;
goto err_devm_kzalloc;
}
key->dev = &pdev->dev;
key->name ="lan key";
key->gpio_num = of_get_named_gpio_flags(pdev->dev.of_node, "key_gpio", 0, &flags); //获取设备树节点的属性key_gpio
if(!gpio_is_valid(key->gpio_num)){
pr_err("gpio is not valid \n");
goto err_of_get_named_gpio_flags;
}
ret = devm_gpio_request(&pdev->dev,key->gpio_num, "gpio_num"); //申请gpio的使用
//devm是为统一设备模型里 提供的api,尽量用这一套,更统一安全
if (ret) {
pr_err("devm_gpio_request err\n");
goto err_devm_gpio_request;
}
pr_info("gpio num = %d\r\n", key->gpio_num);
key->irq_num=gpio_to_irq(key->gpio_num); //根据IO编号获取其中断号(注:这里是软件编号只是为了区分,不是硬件芯片手册上的编号)
pr_info("irq num = %d \n", key->irq_num);
ret = devm_request_irq(&pdev->dev,
key->irq_num, //中断号(用于区分不同中断)
key_interrupt, //中断处理函数
IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, //中断标志 上升沿或下降沿触发
key->name, //中断名称 在cat /proc/interrupts 克看到
key); // 共享中断时才用(共享一中断线, 用于标识中断处理程序,中断标志需设为IRQF_SHARED)
if (ret < 0) {
pr_err("devm_request_irq fail\n");
goto err_devm_request_irq;
}
pr_notice("keys_probe ok \n");
return 0;
//异常处理
err_devm_request_irq:
devm_gpio_free(&pdev->dev,key->gpio_num);
err_devm_gpio_request:
err_of_get_named_gpio_flags:
devm_kfree(&pdev->dev,key); //释放设备空间
err_devm_kzalloc:
return ret;
}
int keys_remove(struct platform_device *pdev)
{
//因devm分配的资源,在rmmod时会自动释放,故不用再加下面的释放函数
//devm_free_irq(&pdev->dev,irq_num,NULL); //释放中断
//devm_gpio_free(&pdev->dev,gpio_num); //释放gpio管脚
printk("keys_remove ok 1\n");
return 0;
}
static const struct of_device_id of_key_match[] = {
{ .compatible = "lan_key_irq", }, //匹配设备树的ID
{},
};
MODULE_DEVICE_TABLE(of, of_key_match);
struct platform_driver keys_drv = {
.driver = {
.owner = THIS_MODULE,
.name = "lan_key_irq driver" ,
.of_match_table = of_key_match,
},
.probe = keys_probe,
.remove = keys_remove,
};
module_platform_driver(keys_drv);
MODULE_LICENSE("GPL");
2749

被折叠的 条评论
为什么被折叠?



