进程、线程、中断的核心:栈

  • ARM 芯片属于精简指令集计算机(RISC:Reduced Instruction Set Computing),它所用的指令比较简单,有如下特点:

① 对内存只有读、写指令
② 对于数据的运算是在 CPU 内部实现
③ 使用 RISC 指令的 CPU 复杂度小一点,易于设计

  • image.png
  • CPU 内部的寄存器很重要,如果要暂停一个程序,中断一个程序,就需要把这些寄存器的值保存下来:这就称为保存现场
    • 保存在哪里?内存,这块内存就称之为栈
    • 序要继续执行,就先从栈中恢复那些 CPU 内部寄存器的值。
  • 程序 A、B 的切换过程,栈的作用是类似的
    • 函数调用
    • 中断处理
    • 进程切换
  • 进程是资源分配的最小单位;线程的CPU调度的最小单位
  • 在 Linux 中:资源分配的单位是进程,调度的单位是线程
  • 每一个线程,都有自己的栈。

    Linux 系统对中断处理的演进

  • Linux中中断的重要原则:

    • 中断不能嵌套(中断任务执行中,即使有更高优先级的中断发生也不能中断当前中断任务)
    • 中断处理越快越好
  • image.png
  • Linux 系统把中断的意义扩展了,对于按键中断等硬件产生的中断,称之为“硬件中断”(hard irq)
    • image.png
  • 还可以人为地制造中断:软件中断(soft irq)
    • image.png

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)函数一路分析得到
  • image.png
  • image.png
  • 最核心的结构体是 irq_desc
    • irq_desc 结构体在 include/linux/irqdesc.h 中定义
  • 中断的处理函数来源有三

① GIC 的处理函数
② 模块的中断处理函数
③ 外部设备提供的处理函数

  • irqaction 结构体
    • irqaction 结构体在 include/linux/interrupt.h 中定义
    • image.png
    • 当调用 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 中定义
    • image.png
    • 这个结构体跟“chip”即芯片相关,里面各成员的作用在头文件中也列得很清楚
    • 我们在 request_irq 后,并不需要手工去使能中断,原因就是系统调用对应的 irq_chip 里的函数帮我们使能了中断

在设备树中指定中断_在代码中获得中断

  • 在设备树中,中断控制器节点中必须有一个属性:interrupt-controller,表明它是“中断控制器”
  • 还必须有一个属性:#interrupt-cells,表明引用这个中断控制器的话需要多少个 cell
  • 如果中断控制器有级联关系,下级的中断控制器还需要表明它的“interrupt-parent”是谁,用了“interrupt-parent”中的哪一个“interrupts”
  • image.png
  • image.png
  • 新写法:interrupts-extended
    • 一个“interrupts-extended”属性就可以既指定“interrupt-parent”,也指定“interrupts”,比如:

interrupts-extended = <&intc1 5 1>, <&intc2 1 0>;

  • image.png
  • 设备树中的节点有些能被转换为内核里的 platform_device,有些不能
  • 一 个 节 点 能 被 转 换 为 platform_device , 如 果 它 的 设 备 树 里 指 定 了 中 断 属 性 , 那 么 可 以 从platform_device 中获得“中断资源”
    • image.png
  • 对于 I2C 设备节点,I2C 总线驱动在处理设备树里的 I2C 子节点时,也会处理其中的中断信息。一个I2C 设备会被转换为一个 i2c_client 结构体,中断号会保存在 i2c_client 的 irq 成员里
    • image.png
  • 对于 SPI 设备节点,SPI 总线驱动在处理设备树里的 SPI 子节点时,也会处理其中的中断信息。一个SPI 设备会被转换为一个 spi_device 结构体,中断号会保存在 spi_device 的 irq 成员里
    • image.png
  • 如果你的设备节点既不能转换为 platform_device,它也不是 I2C 设备,不是 SPI 设备,那么在驱动程序中可以自行调用 of_irq_get 函数去解析设备树,得到中断号
  • 对应GPIO,可以使用 gpio_to_irq 或 gpiod_to_irq 获得中断号

编写使用中断的按键驱动程序

  • 对于 GPIO 按键,我们并不需要去写驱动程序,使用内核自带的驱动程序drivers/input/keyboard/gpio_keys.c 就可以,然后你需要做的只是修改设备树指定引脚及键值
  • 在设备树中添加节点并指定信息

    1. gpio_keys_100ask {
    2. compatible = "100ask,gpio_key";
    3. gpios = <&gpio5 1 GPIO_ACTIVE_HIGH
    4. &gpio4 14 GPIO_ACTIVE_HIGH>;
    5. pinctrl-names = "default";
    6. pinctrl-0 = <&key1_pinctrl
    7. &key2_pinctrl>;
    8. };
  • 编写驱动程序 ```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);

  1. printk("key %d %d\n", gpio_key->gpio, val);
  2. return IRQ_HANDLED;

}

/* 1. 从platform_device获得GPIO

    1. gpio=>irq
    1. 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) {

      1. printk("%s %s line %d, of_get_gpio_flags fail\n", __FILE__, __FUNCTION__, __LINE__);
      2. 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;

  1. count = of_gpio_count(node);
  2. for (i = 0; i < count; i++)
  3. {
  4. free_irq(gpio_keys_100ask[i].irq, &gpio_keys_100ask[i]);
  5. }
  6. kfree(gpio_keys_100ask);
  7. 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;

  1. printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  2. err = platform_driver_register(&gpio_keys_driver);
  3. 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”); ```