中断及其处理
硬件中断(中断请求— IRQ)是来自硬件的外部异步事件。它暂停程序流,并将控制权传递给处理器以处理事件。
硬件中断的处理以下列方式发生:
- 当前的控制线程已暂停;返回到线程的上下文信息被保存。
- 处理程序功能(ISR –中断服务程序)在禁用的硬件中断的上下文中执行。处理程序应执行当前中断所需的操作。
- 通知设备已处理了中断。因此,现在它可以生成新的中断。
- 恢复上下文以从中断返回。
处理程序功能可能会很长,考虑到它是在禁用的硬件中断的上下文中执行的事实,这是不合适的。因此,决定将中断处理分为两部分(在Linux中,它们被称为上半部分和下半部分):
所谓的 上半部分是实际响应中断的例程,即您在request_irq中注册的例程 。在下半区是例程,由上半部分安排,以在更安全的时间稍后执行。上半部处理程序和下半部处理程序之间的最大区别是,在下半部执行期间将启用所有中断,这就是为什么它在更安全的时间运行。这种中断的处理方式典型的应用是,当网络接口报告新数据包的到来时,处理程序仅检索数据并将其上推到协议层。数据包的实际处理在下半部分执行。Linux内核有两种不同的机制可用于实现下半处理。分别是
1.WorkQueue
2.Tasklet
可睡眠 | 相应速度 | 原子操作 | |
---|---|---|---|
tasklet |
不可 | 快 | 是 |
WorkQueue | 可以 | 有延迟 | 否 |
首选使用Tasklet,因为Tasklet的操作都是原子的,动作相当快。
tasklet
需要明确的是tasklet 是原子操作,因此我们可以将它放在中断上下文里面操作。还有一件事,即使内核多次请求运行 tasklet 操作,这个计数是不累加的。换句话说,请求多次也只能运行一次。同时还有与原子性的问题,单核的CPU同一时间只能有一个tasklet 是运行状态的,其他的tasklet 都是处于未激活状态。
tasklet就像一个很小的线程,既没有堆栈,也没有自己的上下文。这样的“线程”可以快速,完整地工作。Tasklet的主要功能如下:
- tasklet是原子的,因此我们不能使用sleep()以及它们中的同步原语,例如互斥体,信号量)等。但是我们可以使用自旋锁;
- 它们在比ISR更“软”的上下文中被调用。在这种情况下,允许硬件中断。它们会在ISR执行期间替换小任务。在Linux内核中,此上下文称为softirq,除了运行tasklet外,它还被多个子系统使用。
- 一个tasklet在计划它的同一内核上运行。或者更确切地说,它是第一个通过调用softirq进行调度的调度程序,该调度程序的处理程序始终绑定到调用内核。
- 不同的tasklet可以并行运行(SMP架构上面)。但是同时,一个tasklet不能与其自身并发调用,因为它仅在一个内核上运行:调度了执行的内核;
- 根据非抢占式调度的原则依次执行tasklet。我们可以为它们安排两个不同的优先级:normal和high。
使用步骤
- 声明使用小任务
- 编写小任务的回调函数
- 不能睡眠,不能使用长时间的函数
- 调度或者杀死小任务
对应的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 /
根据优先将Tasklet放入两个队列之中的一个。<br />上面,我们定义了一个struct tasklet_struct 结构体数据。现在我们需要将这tasklet 运行起来。
```c
void tasklet_schedule(struct tasklet_struct *t); /* with normal priority */
void tasklet_hi_schedule(struct tasklet_struct *t); /* with high priority */
void tasklet_hi_schedule_first(struct tasklet_struct *t); /* out of the queue */
既然有运行,那肯定就有停止的操作,
extern void tasklet_kill(struct tasklet_struct *t);
extern void tasklet_kill_immediate(struct tasklet_struct *t, unsigned int cpu);
tasklet_1.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
static struct tasklet_struct my_tasklet;
static void tasklet_handler(unsigned long data) {
printk(KERN_ALERT "tasklet_handler is running.\n");
}
static int __init test_init(void) {
tasklet_init(&my_tasklet, tasklet_handler, 0);
tasklet_schedule(&my_tasklet);
return 0;
}
static void __exit test_exit(void) {
tasklet_kill(&my_tasklet);
printk(KERN_ALERT "test_exit running.\n");
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zhouchengzhu <1073355312@qq.com>");
MODULE_DESCRIPTION("A simple hello world driver");
MODULE_VERSION("2:1.0");
执行结果
[root@imx6ull:~/NFS]# insmod tasklet_1.ko
[ 70.300580] tasklet_handler is running.
tasklet_2.c
下面,我们展示一下怎样使用 tasklet 的优先级,来控制输出顺序。
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
static struct tasklet_struct my_tasklet_normal;
static struct tasklet_struct my_tasklet_higth;
static void tasklet_handler_normal(unsigned long data) {
printk(KERN_ALERT "tasklet_handler_normal is running.\n");
}
static void tasklet_handler_higth(unsigned long data) {
printk(KERN_ALERT "tasklet_handler_higth is running.\n");
}
static int __init test_init(void) {
tasklet_init(&my_tasklet_normal, tasklet_handler_normal, 0);
tasklet_init(&my_tasklet_higth, tasklet_handler_higth, 0);
/*首先调用 my_tasklet_normal*/
tasklet_schedule(&my_tasklet_normal);
tasklet_hi_schedule(&my_tasklet_higth);
return 0;
}
static void __exit test_exit(void) {
tasklet_kill(&my_tasklet_normal);
tasklet_kill(&my_tasklet_higth);
printk(KERN_ALERT "test_exit running.\n");
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zhouchengzhu <1073355312@qq.com>");
MODULE_DESCRIPTION("A simple hello world driver");
MODULE_VERSION("2:1.0");
这一此我们使用了两个tasklet ,除了schedule 调度器不同其他都是一样的。可以看到的是my_tasklet_normal
先被schedule 初测,结果还是 my_tasklet_higth
先执行。
[root@imx6ull:~/NFS]# insmod tasklet_2.ko
[ 1033.419892] tasklet_handler_higth is running.
[ 1033.424535] tasklet_handler_normal is running.
tasklet 的调用过程
开启软中断
tasklet_schedule
kernel/softirq.c
void __tasklet_schedule(struct tasklet_struct *t)
raise_softirq_irqoff(TASKLET_SOFTIRQ);
void __tasklet_hi_schedule(struct tasklet_struct *t)
raise_softirq_irqoff(HI_SOFTIRQ);
首先,调用tasklet调度的时候,其实是开启了软中断。TASKLET_SOFTIRQ 以及 HI_SOFTIRQ 。分别对应着两种模式。既然是在软中断实现的调度。那么软中断中是怎么处理的了?
调用过程
前面说开启了中断,这个中断对应的函数
kernel/softirq.c
open_softirq(TASKLET_SOFTIRQ, tasklet_action);
open_softirq(HI_SOFTIRQ, tasklet_hi_action);
static void tasklet_action(struct softirq_action *a)
static void tasklet_hi_action(struct softirq_action *a)
显然TASKLET_SOFTIRQ 软中断对应的是 tasklet_action 函数,以此函数为例
static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);
static void tasklet_action(struct softirq_action *a)
{
struct tasklet_struct *list;
// 获取链表
list = __this_cpu_read(tasklet_vec.head);
/* 前面我们使用tasklet_init 就已经将对应的对应的结构体插入链表了
现在开始遍历链表*/
while (list) {
struct tasklet_struct *t = list;
list = list->next;
/* 前面提到了 tasklet 只能在一个CPU核上面单独执行,就是因为这里的操作
tasklet_trylock 函数,将其上锁了。只会有一个核心从头到尾处理整件事。
*/
if (tasklet_trylock(t)) {
if (!atomic_read(&t->count)) {
if (!test_and_clear_bit(TASKLET_STATE_SCHED,
&t->state))
BUG();
// 在这里执行回调函数
// 请注意回调函数尽量不要调用 printk等函数
t->func(t->data);
tasklet_unlock(t);
continue;
}
tasklet_unlock(t);
}
local_irq_disable();
t->next = NULL;
*__this_cpu_read(tasklet_vec.tail) = t;
__this_cpu_write(tasklet_vec.tail, &(t->next));
__raise_softirq_irqoff(TASKLET_SOFTIRQ);
local_irq_enable();
}
}