进程、线程、中断的核心:栈
- ARM 芯片属于精简指令集计算机(RISC:Reduced Instruction Set Computing),它所用的指令比较简单,有如下特点:
① 对内存只有读、写指令
② 对于数据的运算是在 CPU 内部实现
③ 使用 RISC 指令的 CPU 复杂度小一点,易于设计
- CPU 内部的寄存器很重要,如果要暂停一个程序,中断一个程序,就需要把这些寄存器的值保存下来:这就称为保存现场
- 保存在哪里?内存,这块内存就称之为栈
- 序要继续执行,就先从栈中恢复那些 CPU 内部寄存器的值。
- 程序 A、B 的切换过程,栈的作用是类似的
- 函数调用
- 中断处理
- 进程切换
- 进程是资源分配的最小单位;线程的CPU调度的最小单位
- 在 Linux 中:资源分配的单位是进程,调度的单位是线程
-
Linux 系统对中断处理的演进
Linux中中断的重要原则:
- 中断不能嵌套(中断任务执行中,即使有更高优先级的中断发生也不能中断当前中断任务)
- 中断处理越快越好
- Linux 系统把中断的意义扩展了,对于按键中断等硬件产生的中断,称之为“硬件中断”(hard irq)
- 还可以人为地制造中断:软件中断(soft irq)
a. 软件中断何时生产?
由软件决定,对于 X 号软件中断,只需要把它的 flag 设置为 1 就表示发生了该中断。
b. 软件中断何时处理?
软件中断嘛,并不是那么十万火急,有空再处理它好了。
什么时候有空?不能让它一直等吧?
Linux 系统中,各种硬件中断频繁发生,至少定时器中断每 10ms 发生一次,那取个巧?
在处理完硬件中断后,再去处理软件中断?就这么办!
- 在中断下半部的执行过程中,虽然是开中断的,期间可以处理各类中断。但是毕竟整个中断的处理还没走完,这期间 APP 是无法执行的。
- 如果中断要做的事情实在太耗时,那就不能用软件中断来做,而应该用内核线程来做:在中断上半部唤醒内核线程。内核线程和 APP 都一样竞争执行,APP 有机会执行,系统不会卡顿
- 这个内核线程是系统帮我们创建的,一般是 kworker 线程,内核中有很多这样的线程
- kworker 线程要去“工作队列”(work queue)上取出一个一个“工作”(work),来执行它里面的函数。
- 新技术:threaded irq (中断的线程化处理)
- 使用线程来处理中断,并不是什么新鲜事。使用 work 就可以实现,但是需要定义 work、调用schedule_work
- 以前用 work 来线程化地处理中断,一个 worker 线程只能由一个 CPU 执行,多个中断的 work 都由同一个 worker 线程来处理,在单 CPU 系统中也只能忍着了
- 但是在 SMP 系统中,明明有那么多 CPU 空着,你偏偏让多个中断挤在这个 CPU 上?
- 新技术 threaded irq,为每一个中断都创建一个内核线程;多个中断的内核线程可以分配到多个 CPU上执行,这提高了效率。
Linux 中断系统中的重要数据结构
- 可以从 request_irq(include/linux/interrupt.h)函数一路分析得到
- 最核心的结构体是 irq_desc
- irq_desc 结构体在 include/linux/irqdesc.h 中定义
- 中断的处理函数来源有三
① GIC 的处理函数
② 模块的中断处理函数
③ 外部设备提供的处理函数
- irqaction 结构体
- irqaction 结构体在 include/linux/interrupt.h 中定义
- 当调用 request_irq、request_threaded_irq 注册中断处理函数时,内核就会构造一个 irqaction 结构体。在里面保存 name、dev_id 等,最重要的是 handler、thread_fn、thread
可以提供 handler 而不提供 thread_fn,就退化为一般的 request_irq 函数。
可以不提供 handler 只提供 thread_fn,完全由内核线程来处理中断。
也可以既提供 handler 也提供 thread_fn,这就是中断上半部、下半部。
- irq_data 结构体
- irq_data 结构体在 include/linux/irq.h 中定义
- 它就是个中转站,里面有 irq_chip 指针 irq_domain 指针,都是指向别的结构体
- irq 是软件中断号,hwirq 是硬件中断号
- rq_domain 会把本地的 hwirq 映射为全局的 irq,什么意思?比如 GPIO 控制器里有第 1 号中断,UART 模块里也有第 1 号中断,这两个“第 1 号中断”是不一样的,它们属于不同的“域”──irq_domain。
- irq_domain 结构体
- irq_domain 结构体在 include/linux/irqdomain.h 中定义
- 如何在设备树中指定中断,设备树的中断如何被转换为 irq 时,irq_domain将会起到极大的作为
- irq_chip 结构体
- irq_chip 结构体在 include/linux/irq.h 中定义
- 这个结构体跟“chip”即芯片相关,里面各成员的作用在头文件中也列得很清楚
- 我们在 request_irq 后,并不需要手工去使能中断,原因就是系统调用对应的 irq_chip 里的函数帮我们使能了中断
在设备树中指定中断_在代码中获得中断
- 在设备树中,中断控制器节点中必须有一个属性:interrupt-controller,表明它是“中断控制器”
- 还必须有一个属性:#interrupt-cells,表明引用这个中断控制器的话需要多少个 cell
- 如果中断控制器有级联关系,下级的中断控制器还需要表明它的“interrupt-parent”是谁,用了“interrupt-parent”中的哪一个“interrupts”
- 新写法:interrupts-extended
- 一个“interrupts-extended”属性就可以既指定“interrupt-parent”,也指定“interrupts”,比如:
interrupts-extended = <&intc1 5 1>, <&intc2 1 0>;
- 设备树中的节点有些能被转换为内核里的 platform_device,有些不能
- 一 个 节 点 能 被 转 换 为 platform_device , 如 果 它 的 设 备 树 里 指 定 了 中 断 属 性 , 那 么 可 以 从platform_device 中获得“中断资源”
- 对于 I2C 设备节点,I2C 总线驱动在处理设备树里的 I2C 子节点时,也会处理其中的中断信息。一个I2C 设备会被转换为一个 i2c_client 结构体,中断号会保存在 i2c_client 的 irq 成员里
- 对于 SPI 设备节点,SPI 总线驱动在处理设备树里的 SPI 子节点时,也会处理其中的中断信息。一个SPI 设备会被转换为一个 spi_device 结构体,中断号会保存在 spi_device 的 irq 成员里
- 如果你的设备节点既不能转换为 platform_device,它也不是 I2C 设备,不是 SPI 设备,那么在驱动程序中可以自行调用 of_irq_get 函数去解析设备树,得到中断号
- 对应GPIO,可以使用 gpio_to_irq 或 gpiod_to_irq 获得中断号
编写使用中断的按键驱动程序
- 对于 GPIO 按键,我们并不需要去写驱动程序,使用内核自带的驱动程序drivers/input/keyboard/gpio_keys.c 就可以,然后你需要做的只是修改设备树指定引脚及键值
在设备树中添加节点并指定信息
gpio_keys_100ask {
compatible = "100ask,gpio_key";
gpios = <&gpio5 1 GPIO_ACTIVE_HIGH
&gpio4 14 GPIO_ACTIVE_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&key1_pinctrl
&key2_pinctrl>;
};
编写驱动程序 ```c 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
- gpio=>irq
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;
printk(“%s %s line %d\n”, FILE, FUNCTION, LINE);
count = ofgpiocount(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; gpio_keys_100ask[i].irq = gpio_to_irq(gpio_keys_100ask[i].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. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数
卸载platformdriver */ 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”); ```