快速连接
👉👉👉【精选】ARMv8/ARMv9架构入门到精通-目录 👈👈👈
目前有三种中断的三种机制:
- 软中断
- tasklet
- 工作队列
1、软中断
软中断是一组静态定义的下半部接口,有 32 个,可以在所有处理器上同时执行,类型相同也可以;在编译时静态注册。
软中断的相关函数:
- 注册软中断 open_softirq
- 触发软中断 raise_softirq
- 执行软中断 do_softirq
Linux Kernel中定义的软中断:
(linux/include/linux/interrupt.h)
enum
{
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
IRQ_POLL_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
NR_SOFTIRQS
};
软中断执行函数如下:
(linux/kernel/softirq.c)
asmlinkage __visible void do_softirq(void)
{
__u32 pending;
unsigned long flags;
if (in_interrupt())
return;
local_irq_save(flags);
pending = local_softirq_pending();
if (pending && !ksoftirqd_running(pending))
do_softirq_own_stack();
local_irq_restore(flags);
}
代码一上来就判断是否在中断处理中,如果在立刻退出函数。这说明如果有其他软中断触发,则立即返回。所以,软中断不能被另外一个软中断抢占!唯一可以抢占软中断的是中断处理程序,所以软中断允许响应中断。虽然不能在本处理器上抢占,但是其他的软中断甚至同类型可以再其他处理器上同时执行。由于这点,所以对临界区需要加锁保护。
软中断给对时间要求最严格的下半部使用。目前只有网络,内核定时器和 tasklet 建立在软中断上。
2、tasklet
Tasklet是建立在软中断之上的下半部机制,tasklet和软中断很类似,但是tasklet的接口更简单,也不需要严格的锁机制。因为tasklet是使用软中断来实现的,所以tasklet本身就是软中断。
tasklet使用两种软中断来实现:HI_SOFTIRQ和TASKLET_SOFTIRQ。两者的唯一区别在于优先级,前者优先级更高,总是先于后者执行。
tasklet使用tasklet_struct结构来表示,每个结构体表示一个唯一的tasklet,定义在<linux/interrupt.h>中
(linux/include/linux/interrupt.h)
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
bool use_callback;
union {
void (*func)(unsigned long data);
void (*callback)(struct tasklet_struct *t);
};
unsigned long data;
};
Tasklet的使用:
(1)、声明一个新的tasklet
struct tasklet_struct my_tasklet = { NULL, 0, ATOMIC_INIT(0), my_tasklet_handler, dev };
或者
tasklet_init(t, tasklet_handler, dev); /* dynamically as opposed to statically */
(2)、tasklet的处理程序
void tasklet_handler(unsigned long data)
注意:和软中断类似,tasklet不能睡眠(阻塞),因为软中断是运行在中断上下文中的,而tasklet是使用软中断来实现的。
(3)、tasklet的调度
使用tasklet_schedule()
进行调度(类似软中断的触发),传入参数为指向tasklet_struct的指针。tasklet被调度后,内核会在合适的时机执行该taskelt。(详见前面的tasklet调度的实现)。如果一个tasklet在执行前被调度了多次,还是只会执行一次(tasklet链表中不会有重复的tasklet)。如果一个tasklet在运行中被调度了(比如被另一个处理器上执行的代码调度了),那么这个tasklet会被重新调度并在下次内核处理tasklet的时候再次执行。
3、工作队列
工作队列是和软中断或者tasklet不同的一种下半部机制。工作队列将工作推迟,交给内核线程执行(所以工作队列总是运行在进程上下文中)。工作队列的这种实现可以很好的利用进程上下文的优势,最重要的就是可以睡眠也可以被调度(抢占)。与之相反的是,软中断和tasklet是不能睡眠和被调度的。
可以自己创建工作队列,但是大部分驱动都会使用系统提供的缺省的工作队列类型events,该类型的工作队列的内核线程名字为 events/n,n为处理器编号,每个处理器对应一个内核线程。如果下半部的工作是处理器密集型并且对性能敏感的,可以考虑创建自己的内核线程。比如XFS文件系统就自己创建了两种内核线程。
工作队列的使用:--(缺省工作队列events)
(1)、创建工作(Creaing Work)
DECLARE_WORK(name, void (*func)(void *), void *data); //静态
INIT_WORK(struct work_struct *work, void (*func)(void *), void *data); //动态
(2)、定义工作队列处理函数
void work_handler(void *data)
(3)、对工作(work)进行调度
内核提供了两个函数对使用缺省工作队列events的工作进行调度
schedule_work(&work);
schedule_delayed_work(&work, delay);
- schedule_work()会立刻对工作(work)进行调度,一旦其所在的处理器上的events内核线程被唤醒,该工作就会被执行。
- schedule_delayed_work()会延后一定数量的(由dealy指定)的timer tick后再进行调度。
创建自己的内核线程工作队列
(1)、创建内核线程工作队列
如果需要利用单独的内核线程的(不用events的内核线程)的性能优势,可以通过函数struct workqueue_struct create_workqueue(const char name)创建一个新的工作队列,参数是工作队列的名字。比如缺省的events工作队列的创建:
struct workqueue_struct *keventd_wq;
keventd_wq = create_workqueue(“events”);
这个函数会创建所有的内核线程(每个处理器一个),并且做些准备好让这些内核线程可以处理工作
(2)、工作队列调度
int queue_work(struct workqueue_struct *wq, struct work_struct *work)
int queue_delayed_work(struct workqueue_struct *wq,
struct work_struct *work,
unsigned long delay)
总结
下半部 | 上下文 | 说明 | |
---|---|---|---|
软中断 | 中断上下文 | 不能睡眠、不能被抢占 | |
tasklet | 中断上下文 | 不能睡眠、不能抢占、同类型tasklet不能并行 | |
工作队列 | 进程上下文 | 可以睡眠、可以被抢占 |
《ARMv8/ARMv9架构学习系列课程》全系列,共计51节课,超15h的视频课程
关注"Arm精选"公众号,备注进ARM交流讨论区。