第三十八期-ARM Linux内核的中断(8)

作者:罗宇哲,中国科学院软件研究所智能软件研究中心

上一期中我们介绍了ARM Linux内核中的底半机制和软中断的处理流程,这一期我们将介绍ARM Linux内核中小任务的工作流程和与工作队列相关的关键数据结构。

一、ARM Linux内核中小任务的工作流程

在ARM Linux内核中,小任务分为高优先级的小任务和低优先级的小任务,它们是基于软中断实现的,ARM Linux内核通过softirq_init()函数将高优先级的小任务和低优先级的小任务分别注册为HI_SOFTIRQ类型的软中断和TASKLET_SOFTIRQ类型的软中断。softirq_init()函数的源码可以在openEuler源码仓库的openeuler/kernel/blob/kernel-4.19/kernel/softirq.c文件中可以找到(下文中没有特别说明的源码都在softirq.c文件中):
第三十八期-ARM Linux内核的中断(8)_第1张图片
该函数将为低优先级和高优先级的小任务注册了处理函数。低优先级的小任务的处理函数为tasklet_action,高优先级小任务的处理函数为tasklet_hi_action:
第三十八期-ARM Linux内核的中断(8)_第2张图片
这两个函数调用了tasklet_action_common函数,并分别使用了tasklet_vec和tasklet_hi_vec,这两个参数定义如下:
第三十八期-ARM Linux内核的中断(8)_第3张图片
从参数的定义可以看出,每个处理器有一个高优先级小任务链表和一个低优先级小任务链表。tasklet_struct结构体是小任务的关键数据结构,其定义可以在openeuler/kernel/blob/kernel-4.19/include/linux/interrupt.h 文件中找到:
第三十八期-ARM Linux内核的中断(8)_第4张图片
该数据结构各字段的含义如下[1,3]:

  • next:tasklet单向链表中指向下一项的指针;
  • state:小任务的状态。当该值为0时代表小任务没有被调度;当该值的TASKLET_STATE_SCHED位为1时表示小任务被调度,即将被执行;当该值的TASKLET_STATE_RUN位为1时表示小任务正在被执行,这种情况只在对称多处理系统(SMP)[2]中发生;
  • count:计数变量,该值为0表示允许小任务执行,该值为非零值则表示禁止小任务被执行;
  • func:tasklet处理函数;
  • data:传给处理函数的参数。这是一个32位的无符号整数,其具体含义可供func函数自行解释,比如将其解释成一个指向某个用户自定义数据结构的地址值;

TASKLET_STATE_SCHED和TASKLET_STATE_RUN位的定义在interrupt.h文件中也可以找到:
第三十八期-ARM Linux内核的中断(8)_第5张图片
其值分别为0和1。

tasklet_action_common()函数在关中断的情况下遍历输入的小任务链表,调用小任务处理函数,并调用__raise_softirq_irqoff()函数触发小任务类型对应的软中断:

static void tasklet_action_common(struct softirq_action *a,

struct tasklet_head *tl_head,

unsigned int softirq_nr)

{

struct tasklet_struct *list;

local_irq_disable();

list = tl_head->head;

tl_head->head = NULL;

tl_head->tail = &tl_head->head;

local_irq_enable();

while (list) {

struct tasklet_struct *t = list;

list = list->next;

if (tasklet_trylock(t)) { //临界区

if (!atomic_read(&t->count)) {

if (!test_and_clear_bit(TASKLET_STATE_SCHED,

&t->state))

BUG();

t->func(t->data); //调用小任务处理函数

tasklet_unlock(t);

continue;

}

tasklet_unlock(t);

}

local_irq_disable();

t->next = NULL;

*tl_head->tail = t;

tl_head->tail = &t->next;

__raise_softirq_irqoff(softirq_nr); //触发小任务类型对应的软中断

local_irq_enable();

}

}

临界区的出现表明在SMP多处理器系统中,同一种小任务同一时刻只能在一个处理器上运行,但不同种的小任务可以同时在不同的处理器上运行,tasklet_trylock()函数设置tasklet_struct结构中state变量的TASKLET_STATE_RUN位并返回它的非值。也就是说当TASKLET_STATE_RUN位为1时(表示该小任务正在处理器上运行),那么tasklet_trylock()返回0表示加锁不成功,否则返回1表示加锁成功。tasklet_unlock()清除TASKLET_STATE_RUN位。这两个函数的源码在interrupt.h文件中可以找到:
第三十八期-ARM Linux内核的中断(8)_第6张图片

二、ARM Linux内核中工作队列的关键数据结构

工作队列是使用内核线程异步执行函数的通用机制,中断处理程序可以把耗时比较长且可能睡眠的函数交给工作队列执行[1]。内核为操作系统中需要内核线程执行的函数提供了通用接口,从而使得异步函数可共享内核线程,节省资源。

工作项是内核用于保存需要异步执行的函数的数据结构,在openEuler源码仓库的openeuler/kernel/blob/kernel-4.19/include/linux/workqueue.h文件中可以找到工作项work_struct:
第三十八期-ARM Linux内核的中断(8)_第7张图片
该数据结构中保存了需要内核线程异步执行的函数func,其类型定义同样在workqueue.h文件中:
在这里插入图片描述
使用INIT_WORK(_work,_func)函数可以初始化一个工作项,其中_work是工作项的地址,_func是需要异步执行的函数[1]。

工作项由工作者线程承担执行异步函数的任务,工作者的数据结构worker的源码在openeuler/kernel/blob/kernel-4.19/kernel/workqueue_internal.h文件中可以找到:

struct worker {

/* on idle list while idle, on busy hash table while busy */

union {

struct list_head entry; /* L: while idle */

struct hlist_node hentry; /* L: while busy */

};

struct work_struct *current_work; /* L: work being processed */

work_func_t current_func; /* L: current_work's fn */

struct pool_workqueue *current_pwq; /* L: current_work's pwq */

struct list_head scheduled; /* L: scheduled works */

/* 64 bytes boundary on 64bit, 32 on 32bit */

struct task_struct *task; /* I: worker task */

struct worker_pool *pool; /* A: the associated pool */

……

};

当worker处于idle状态时,它存在于worker_pool的idle_list中;当worker处于busy状态时,它存在于busy
hash table中。current_work和current_func分别指向当前正在处理的工作和其对应的异步函数,task则是工作者线程对应的线程描述符。worker_pool数据结构维护了工作者的集合,包括idle_list和busy
hash table,其代码在workqueue.c文件中可以找到:

struct worker_pool {

……

struct list_head worklist; /* L: list of pending works */

……

int nr_workers; /* L: total number of workers */

int nr_idle; /* L: currently idle workers */

struct list_head idle_list; /* X: list of idle workers */

……

/* a workers is either on busy_hash or idle_list, or the manager */

DECLARE_HASHTABLE(busy_hash, BUSY_WORKER_HASH_ORDER);

/* L: hash of busy workers */

struct worker *manager; /* L: purely informational */

struct list_head workers; /* A: attached workers */

……

} ____cacheline_aligned_in_smp;

worker_pool存放在pool_workqueue实例中,而pool_workqueue链表则在workqueue_struct数据结构中维护,这两个数据结构的代码在workqueue.c文件中可以找到:

struct pool_workqueue {

struct worker_pool *pool; /* I: the associated pool */

struct workqueue_struct *wq; /* I: the owning workqueue */

……

} __aligned(1 << WORK_STRUCT_FLAG_BITS);
truct workqueue_struct {

struct list_head pwqs; /* WR: all pwqs of this wq */

struct list_head list; /* PR: list of all workqueues */

……

};

这几个数据结构之间的关系如下图所示[4]:
第三十八期-ARM Linux内核的中断(8)_第8张图片

三、结语

本期我们介绍了ARM Linux内核中的小任务的工作流程和与工作队列相关的关键数据结构,下一期我们将介绍与工作队列相关的关键函数。

参考文献

[1] 《Linux内核深度解析》,余华兵著,2019
[2] https://baike.baidu.com/item/对称多处理/6274908?fromtitle=SMP&fromid=235768&fr=aladdin
[3] https://blog.csdn.net/kernel_learner/article/details/7331835
[4] https://www.cnblogs.com/ck1020/p/8312652.html

你可能感兴趣的:(第三十八期-ARM Linux内核的中断(8))