《Linux内核深度解析》--中断、异常、系统调用
[复制链接]
本篇阅读梳理“中断、异常和系统调用”章节,以书本为纲,以实际应用代码作小结进行。
异常
广义的异常指中断、系统调用和其他打断程序正常执行流的事件。狭义的异常专指执行指令时触发的异常。
异常分为同步异常和异步异常。同步异常是试图执行指令时生成的异常,或是作为指令的执行结果生成的异常。同步异常包括:系统调用;数据中止;指令中止;栈指针或指令地址没有对齐;没有定义的指令;调试异常。异步异常不是由正在执行的指令生成的,和正在执行的指令没有关联。异步异常包括以下:中断;快速中断;系统错误(System Error,SError),是由硬件错误触发的异常。
当异常发生的时候,处理器需要执行异常的处理程序。存储异常处理程序的内存位置称为异常向量,通常把所有异常向量存放在一张表中,称为异常向量表。
中断
中断是外围设备通知处理器的一种机制。中断有以下4种状态:
●Inactive:中断源没有发送中断。
●Pending:中断源已经发送中断,等待处理器处理。
●Active:处理器已经确认中断,正在处理。
●Active and pending:处理器正在处理中断,相同的中断源又发送了一个中断。
中断的状态转换过程如下:
●lnactive->Pending:外围设备发送了中断。
●Pending->Active:处理器确认了中断。
●Active->Inactive:处理器处理完中断。
为了把每个中断控制器的硬件中断号映射到全局唯一的Linux中断号(也称为虚拟中断号),内核定义了中断域irq_domain,每个中断控制器有自己的中断域。
Linux 系统把中断处理程序分为两部分,上半部(top half,th)在关闭中断的情况下执行,只做对时间非常敏感、与硬件相关或者不能被其他中断打断的工作;下半部(bottomhalf,bh)在开启中断的情况下执行,可以被其他中断打断。
上半部称为硬中断(hardirq),下半部有3种:软中断(softirq)、小任务(tasklet)和工作队列(workqueue)。
系统调用
系统调用是内核给用户程序提供的编程接口,用户程序调用系统调用,通常使用glibc库针对单个系统调用封装的函数。如果glibc库没有针对某个系统调用封装函数,用户程序可以使用通用的封装函数 syscall():
中断应用
下面以i.MX6ULL开发板为例,其中断控制器节点内容如下:
intc:interrupt-controller @00a01000
{
compatible = "arm,cortex-a7-gic";
#interrupt - cells = < 3>;
interrupt - controller;
reg = <0x00a01000 0x1000>,
<0x00a02000 0x100>;
};
compatible 属性值为“arm,cortex-a7-gic” 在 Linux 内核源码中搜索“arm,cortex-a7- gic” 即可找到 GIC中断控制器驱动文件。
#interrupt-cells 和#address-cells、 #size-cells 一样。 表示此中断控制器下设备的 cells 大小, 对于设备而言, 会使用 interrupts 属性描述中断信息, #interrupt-cells 描述了 interrupts 属性的 cells 大小, 也就是一条信息有几个 cells。
interrupt-controller 节点为空, 表示当前节点是中断控制器。
gpio 节点也可以作为中断控制器, 比如 imx6ull.dtsi 文件中的 gpio5 节点内容如下:
1 gpio5 : gpio @020ac000{
2 compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
3 reg = <0x020ac000 0x4000>;
4 interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH>;
5 gpio-controller;
6 #gpio-cells = <2>;
7 interrupt-controller;
8 #interrupt-cells = <2>;
9 };
第 4 行, interrupts 描述中断源信息, 对于 gpio5 来说一共有两条信息, 中断类型都是 SPI, 触发电平都是 IRQ_TYPE_LEVEL_HIGH。 不同之处在于中断源.
下面摘录实现一个按键中断, 按一下按键就让在终端上打印一句话, 这个程序没有涉及到中断下文的编写, 只有中断上文。修改设备树文件 imx6ull-14x14-evk.dts, 路径在源码目录 arch/arm/boot/dts/目录下, 添加节点如下:
完整的驱动代码如下:
/*
* @Description: 使用 gpio_to_irq 函数来获取中断号
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/irq.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
//定义结构体表示我们的节点
struct device_node *test_device_node;
struct property *test_node_property;
//要申请的中断号
int irq;
// GPIO 编号
int gpio_nu;
/**
* @description: 中断处理函数 test_key
* @param {int} irq : 要申请的中断号
* @param {void} *args :
* [url=home.php?mod=space&uid=784970]@return[/url] {*}IRQ_HANDLED
*/
irqreturn_t test_key(int irq, void *args)
{
printk("test_key \n");
return IRQ_RETVAL(IRQ_HANDLED);
} /
*******************************************************************************
* [url=home.php?mod=space&uid=159083]@brief[/url] beep_probe : 与设备信息层(设备树) 匹配成功后自动执行此函数,
* @param inode : 文件索引
* @param file : 文件
* @return 成功返回 0
*******************************************************************************/
int beep_probe(struct platform_device *pdev)
{
int ret = 0;
// 打印匹配成功进入 probe 函数
printk("beep_probe\n");
// of_find_node_by_path 函数通过路径查找节点, /test_key 是设备树下的节点路径
test_device_node = of_find_node_by_path("/test_key");
if (test_device_node == NULL)
{
//查找节点失败则打印信息
printk("of_find_node_by_path is error \n");
return -1;
}
//of_get_named_gpio 函数获取 GPIO 编号
gpio_nu = of_get_named_gpio(test_device_node, "gpios", 0);
if (gpio_nu < 0)
{
printk("of_get_namd_gpio is error \n");
return -1;
}
//设置 GPIO 为输入模式
gpio_direction_input(gpio_nu);
//获取 GPIO 对应的中断号
irq = gpio_to_irq(gpio_nu);
printk("irq is %d \n", irq);
/*申请中断, irq:中断号名字
test_key: 中断处理函数
IRQF_TRIGGER_RISING: 中断标志, 意为上升沿触发
"test_key": 中断的名字
*/
ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING, "test_key", NULL);
if (ret < 0)
{
printk("request_irq is error \n");
return -1;
}
return 0;
}
int beep_remove(struct platform_device *pdev)
{
printk("beep_remove\n");
return 0;
}
const struct platform_device_id beep_idtable = {
.name = "keys",
};
const struct of_device_id of_match_table_test[] = {
{.compatible = "keys"},
{},
};
struct platform_driver beep_driver = {
//3. 在 beep_driver 结构体中完成了 beep_probe 和 beep_remove
.probe = beep_probe,
.remove = beep_remove,
.driver = {
.owner = THIS_MODULE,
.name = "beep_test",
.of_match_table = of_match_table_test},
//4 .id_table 的优先级要比 driver.name 的优先级要高, 优先与.id_table 进行匹配
.id_table = &beep_idtable};
/**
* @description: 模块初始化函数
* @param {*}
* @return {*}
*/
static int beep_driver_init(void)
{
//1.我们看驱动文件要从 init 函数开始看
int ret = 0;
//2.在 init 函数里面注册了 platform_driver
ret = platform_driver_register(&beep_driver);
if (ret < 0)
{
printk("platform_driver_register error \n");
}
printk("platform_driver_register ok \n");
return 0;
}
/**
* @description: 模块卸载函数
* @param {*}
* @return {*}
*/
static void beep_driver_exit(void)
{
free_irq(irq, NULL);
platform_driver_unregister(&beep_driver);
printk("gooodbye! \n");
}
module_init(beep_driver_init);
module_exit(beep_driver_exit);
MODULE_LICENSE("GPL");
至此,对中断、异常、系统调用进行了概览梳理,对常用到的中断示例应用,两者相互结合,相辅相成。
|