中断及其处理

硬件中断(中断请求— IRQ)是来自硬件的外部异步事件。它暂停程序流,并将控制权传递给处理器以处理事件。
硬件中断的处理以下列方式发生:

  1. 当前的控制线程已暂停;返回到线程的上下文信息被保存。
  2. 处理程序功能(ISR –中断服务程序)在禁用的硬件中断的上下文中执行。处理程序应执行当前中断所需的操作。
  3. 通知设备已处理了中断。因此,现在它可以生成新的中断。
  4. 恢复上下文以从中断返回。

处理程序功能可能会很长,考虑到它是在禁用的硬件中断的上下文中执行的事实,这是不合适的。因此,决定将中断处理分为两部分(在Linux中,它们被称为上半部分和下半部分):
所谓的 上半部分是实际响应中断的例程,即您在request_irq中注册的例程 。在下半区是例程,由上半部分安排,以在更安全的时间稍后执行。上半部处理程序和下半部处理程序之间的最大区别是,在下半部执行期间将启用所有中断,这就是为什么它在更安全的时间运行。这种中断的处理方式典型的应用是,当网络接口报告新数据包的到来时,处理程序仅检索数据并将其上推到协议层。数据包的实际处理在下半部分执行。Linux内核有两种不同的机制可用于实现下半处理。分别是
1.WorkQueue
2.Tasklet

可睡眠 相应速度 原子操作
tasklet
不可
WorkQueue 可以 有延迟

首选使用Tasklet,因为Tasklet的操作都是原子的,动作相当快。

2-Linux内核中的多任务处理。中断和任务集 - 图1

tasklet

需要明确的是tasklet 是原子操作,因此我们可以将它放在中断上下文里面操作。还有一件事,即使内核多次请求运行 tasklet 操作,这个计数是不累加的。换句话说,请求多次也只能运行一次。同时还有与原子性的问题,单核的CPU同一时间只能有一个tasklet 是运行状态的,其他的tasklet 都是处于未激活状态。
tasklet就像一个很小的线程,既没有堆栈,也没有自己的上下文。这样的“线程”可以快速,完整地工作。Tasklet的主要功能如下:

  1. tasklet是原子的,因此我们不能使用sleep()以及它们中的同步原语,例如互斥体信号量)等。但是我们可以使用自旋锁
  2. 它们在比ISR更“软”的上下文中被调用。在这种情况下,允许硬件中断。它们会在ISR执行期间替换小任务。在Linux内核中,此上下文称为softirq,除了运行tasklet外,它还被多个子系统使用。
  3. 一个tasklet在计划它的同一内核上运行。或者更确切地说,它是第一个通过调用softirq进行调度的调度程序,该调度程序的处理程序始终绑定到调用内核。
  4. 不同的tasklet可以并行运行(SMP架构上面)。但是同时,一个tasklet不能与其自身并发调用,因为它仅在一个内核上运行:调度了执行的内核;
  5. 根据非抢占式调度的原则依次执行tasklet。我们可以为它们安排两个不同的优先级:normalhigh

2-Linux内核中的多任务处理。中断和任务集 - 图2

使用步骤

  • 声明使用小任务
  • 编写小任务的回调函数
    • 不能睡眠,不能使用长时间的函数
  • 调度或者杀死小任务

    对应的API

    首先应该将其初始化 ```c struct tasklet_struct { struct tasklet_struct next; /指向链表的下一个结构/ unsigned long state; /小人物的状态/ atomic_t count; /引用计数器/ void (func)(unsigned long); /要调用的函数/ unsigned long data; /传递给函数的参数/ };

define DECLARE_TASKLET(name, func, data) \

struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }

/ By default, the tasklet is activated / void tasklet_init(struct tasklet_struct t, void (func)(unsigned long), unsigned long data); DECLARE_TASKLET(name, func, data); DECLARE_TASKLET_DISABLED(name, func, data); / the deactivated tasklet /

  1. 根据优先将Tasklet放入两个队列之中的一个。<br />上面,我们定义了一个struct tasklet_struct 结构体数据。现在我们需要将这tasklet 运行起来。
  2. ```c
  3. void tasklet_schedule(struct tasklet_struct *t); /* with normal priority */
  4. void tasklet_hi_schedule(struct tasklet_struct *t); /* with high priority */
  5. void tasklet_hi_schedule_first(struct tasklet_struct *t); /* out of the queue */

既然有运行,那肯定就有停止的操作,

  1. extern void tasklet_kill(struct tasklet_struct *t);
  2. extern void tasklet_kill_immediate(struct tasklet_struct *t, unsigned int cpu);

tasklet_1.c

  1. #include <linux/module.h>
  2. #include <linux/init.h>
  3. #include <linux/fs.h>
  4. #include <linux/kdev_t.h>
  5. #include <linux/cdev.h>
  6. #include <linux/kernel.h>
  7. #include <linux/interrupt.h>
  8. static struct tasklet_struct my_tasklet;
  9. static void tasklet_handler(unsigned long data) {
  10. printk(KERN_ALERT "tasklet_handler is running.\n");
  11. }
  12. static int __init test_init(void) {
  13. tasklet_init(&my_tasklet, tasklet_handler, 0);
  14. tasklet_schedule(&my_tasklet);
  15. return 0;
  16. }
  17. static void __exit test_exit(void) {
  18. tasklet_kill(&my_tasklet);
  19. printk(KERN_ALERT "test_exit running.\n");
  20. }
  21. module_init(test_init);
  22. module_exit(test_exit);
  23. MODULE_LICENSE("GPL");
  24. MODULE_AUTHOR("zhouchengzhu <1073355312@qq.com>");
  25. MODULE_DESCRIPTION("A simple hello world driver");
  26. MODULE_VERSION("2:1.0");

执行结果

  1. [root@imx6ull:~/NFS]# insmod tasklet_1.ko
  2. [ 70.300580] tasklet_handler is running.

上面的例子展示了怎样将一个小任务加入队列的。

tasklet_2.c

下面,我们展示一下怎样使用 tasklet 的优先级,来控制输出顺序。

  1. #include <linux/module.h>
  2. #include <linux/init.h>
  3. #include <linux/fs.h>
  4. #include <linux/kdev_t.h>
  5. #include <linux/cdev.h>
  6. #include <linux/kernel.h>
  7. #include <linux/interrupt.h>
  8. static struct tasklet_struct my_tasklet_normal;
  9. static struct tasklet_struct my_tasklet_higth;
  10. static void tasklet_handler_normal(unsigned long data) {
  11. printk(KERN_ALERT "tasklet_handler_normal is running.\n");
  12. }
  13. static void tasklet_handler_higth(unsigned long data) {
  14. printk(KERN_ALERT "tasklet_handler_higth is running.\n");
  15. }
  16. static int __init test_init(void) {
  17. tasklet_init(&my_tasklet_normal, tasklet_handler_normal, 0);
  18. tasklet_init(&my_tasklet_higth, tasklet_handler_higth, 0);
  19. /*首先调用 my_tasklet_normal*/
  20. tasklet_schedule(&my_tasklet_normal);
  21. tasklet_hi_schedule(&my_tasklet_higth);
  22. return 0;
  23. }
  24. static void __exit test_exit(void) {
  25. tasklet_kill(&my_tasklet_normal);
  26. tasklet_kill(&my_tasklet_higth);
  27. printk(KERN_ALERT "test_exit running.\n");
  28. }
  29. module_init(test_init);
  30. module_exit(test_exit);
  31. MODULE_LICENSE("GPL");
  32. MODULE_AUTHOR("zhouchengzhu <1073355312@qq.com>");
  33. MODULE_DESCRIPTION("A simple hello world driver");
  34. MODULE_VERSION("2:1.0");

这一此我们使用了两个tasklet ,除了schedule 调度器不同其他都是一样的。可以看到的是my_tasklet_normal
先被schedule 初测,结果还是 my_tasklet_higth 先执行。

  1. [root@imx6ull:~/NFS]# insmod tasklet_2.ko
  2. [ 1033.419892] tasklet_handler_higth is running.
  3. [ 1033.424535] tasklet_handler_normal is running.

tasklet 的调用过程

开启软中断

  1. tasklet_schedule
  2. kernel/softirq.c
  3. void __tasklet_schedule(struct tasklet_struct *t)
  4. raise_softirq_irqoff(TASKLET_SOFTIRQ);
  5. void __tasklet_hi_schedule(struct tasklet_struct *t)
  6. raise_softirq_irqoff(HI_SOFTIRQ);

首先,调用tasklet调度的时候,其实是开启了软中断。TASKLET_SOFTIRQ 以及 HI_SOFTIRQ 。分别对应着两种模式。既然是在软中断实现的调度。那么软中断中是怎么处理的了?

调用过程

前面说开启了中断,这个中断对应的函数

  1. kernel/softirq.c
  2. open_softirq(TASKLET_SOFTIRQ, tasklet_action);
  3. open_softirq(HI_SOFTIRQ, tasklet_hi_action);
  4. static void tasklet_action(struct softirq_action *a)
  5. static void tasklet_hi_action(struct softirq_action *a)

显然TASKLET_SOFTIRQ 软中断对应的是 tasklet_action 函数,以此函数为例

  1. static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);
  2. static void tasklet_action(struct softirq_action *a)
  3. {
  4. struct tasklet_struct *list;
  5. // 获取链表
  6. list = __this_cpu_read(tasklet_vec.head);
  7. /* 前面我们使用tasklet_init 就已经将对应的对应的结构体插入链表了
  8. 现在开始遍历链表*/
  9. while (list) {
  10. struct tasklet_struct *t = list;
  11. list = list->next;
  12. /* 前面提到了 tasklet 只能在一个CPU核上面单独执行,就是因为这里的操作
  13. tasklet_trylock 函数,将其上锁了。只会有一个核心从头到尾处理整件事。
  14. */
  15. if (tasklet_trylock(t)) {
  16. if (!atomic_read(&t->count)) {
  17. if (!test_and_clear_bit(TASKLET_STATE_SCHED,
  18. &t->state))
  19. BUG();
  20. // 在这里执行回调函数
  21. // 请注意回调函数尽量不要调用 printk等函数
  22. t->func(t->data);
  23. tasklet_unlock(t);
  24. continue;
  25. }
  26. tasklet_unlock(t);
  27. }
  28. local_irq_disable();
  29. t->next = NULL;
  30. *__this_cpu_read(tasklet_vec.tail) = t;
  31. __this_cpu_write(tasklet_vec.tail, &(t->next));
  32. __raise_softirq_irqoff(TASKLET_SOFTIRQ);
  33. local_irq_enable();
  34. }
  35. }

参考资料

Top and Bottom Halves
Tasklet | Static Method