Linux Kernel中断下半部分实现的三种方式

个人博客笔记导读目录(全部)
.
说明:
在默认情况下,本文讲述的都是ARMV8-aarch64架构,linux kernel 5.14

目录

        • 1、软中断
        • 2、tasklet
        • 3、工作队列
        • 总结

目前有三种中断的三种机制:

  • 软中断
  • 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/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的视频课程

你可能感兴趣的:(linux,kernel,tasklet,工作队列,中断下半部分,中断,linux)