TASK机制
tasklet是软中断实现的一种下半部机制,用于处理较为复杂耗时的操作,体现形式为将任务向后推迟,让它们在系统不繁忙且中断恢复后执行,在运行的同时允许响应所有中断。简单来说就是缩短task的工作时间。类型代表HI_SOFTIRQ
和TASKLET_SOFTIRQ
,前者优先级是高于后者优先级的,面向的对象也有所不同,HI_SOFTIRQ
高优先软中断处理一些需要快速响应的任务,TASKLET_SOFTIRQ
用于处理tasklet机制触发的延迟任务。
本次只分析普通tasklet情况tasklet_schedule
,高优先级tasklet_hi_schedule
处理情况类似。
tasklet_struct
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;//tasklet的状态
atomic_t count;//tasklet计数器
bool use_callback;//标记是否使用回调函数
union {//处理函数
void (*func)(unsigned long data);
void (*callback)(struct tasklet_struct *t);
};
unsigned long data;//传递给tasklet处理函数的参数
};
每个结构体都单独代表一个单独的tasklet,tasklet状态取值为0/TASKLET_ATATE_SCHED
(tasklet已被调度,准备投入运行)/TASKLET_STATE_RUN
(tasklet正在运行,用于多处理器);tasklet计数器为0表明当前tasklet被激活并且将其设置为挂起状态否则处于被禁止状态。
//已调度的tasklet存放在tasklet_vec和tasklet_hi_vec数据结构中,都是由tasklet组成的链表
static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);//普通tasklet
static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);//高优先级tasklet
tasklet_schedule调度函数
static inline void tasklet_schedule(struct tasklet_struct *t)
{//普通tasklet调度
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))//判断当前tasklet是否已经被调度0则未被调度,1则被调度
__tasklet_schedule(t);//执行调度
}
static inline void tasklet_hi_schedule(struct tasklet_struct *t)
{//高优先级
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
__tasklet_hi_schedule(t);
}
tasklet_schedule()调度执行标记当前tasklet挂起状态
void __tasklet_schedule(struct tasklet_struct *t)
{
//将需要调度的tasklet加入到tasklet_vec链表头
__tasklet_schedule_common(t, &tasklet_vec,
TASKLET_SOFTIRQ);//软中断
}
__tasklet_schedule_common
static void __tasklet_schedule_common(struct tasklet_struct *t,
struct tasklet_head __percpu *headp,
unsigned int softirq_nr)
{
struct tasklet_head *head;
unsigned long flags;
local_irq_save(flags);//保存当前cpu中断状态
head = this_cpu_ptr(headp);//获取当前cpu的tasklet头指针
t->next = NULL;//tasklet成为链表的最后一个元素
*head->tail = t;//将调度的tasklet添加到tasklet链表末尾
head->tail = &(t->next);//将链表的末尾指针指向要调度的tasklet
raise_softirq_irqoff(softirq_nr);//触发软中断
local_irq_restore(flags);//恢复之前保存的中断状态
}
tasklet执行时候在中断处理程序中置为挂起即设置成待处理状态,此时do_softirq()
会执行相对应的软中断处理程序处理所有类型的软中断,而 tasklet_action()
则专门用于处理由 tasklet
触发的软中断。
//处理软中断
asmlinkage __visible void do_softirq(void)
{
__u32 pending; // 待处理的软中断标志
unsigned long flags; // 中断状态寄存器的值
if (in_interrupt())
return; // 如果当前处于中断上下文,则直接返回
local_irq_save(flags); // 禁止中断,并保存中断状态寄存器的值到flags变量
pending = local_softirq_pending(); // 获取待处理的软中断标志
if (pending && !ksoftirqd_running(pending))
do_softirq_own_stack(); // 如果存在待处理的软中断标志且没有正在运行的内核线程来处理软中断,则在当前线程的栈上处理软中断
local_irq_restore(flags); // 恢复中断状态,重新开启中断
}
在tasklet调度的时候触发了软中断所以进入软中断初始化函数。
void __init softirq_init(void)
{
int cpu;
//遍历每个可能的cpu
for_each_possible_cpu(cpu) {
// 初始化 tasklet_vec 和 tasklet_hi_vec
per_cpu(tasklet_vec, cpu).tail =
&per_cpu(tasklet_vec, cpu).head;
per_cpu(tasklet_hi_vec, cpu).tail =
&per_cpu(tasklet_hi_vec, cpu).head;
}
// 注册 TASKLET_SOFTIRQ 和 HI_SOFTIRQ 的处理函数
open_softirq(TASKLET_SOFTIRQ, tasklet_action);
open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}
static __latent_entropy void tasklet_action(struct softirq_action *a)
{
tasklet_action_common(a, this_cpu_ptr(&tasklet_vec), TASKLET_SOFTIRQ);
//参数:指针指向特定中断处理程序,获取当前 CPU 上的 tasklet_vec(存储 Tasklet 的数组,用于保存各个 CPU 上的 Tasklet 队列),tasklet软中断
}
tasklet_action_common即tasklet核心处理函数
//处理tasklet类型软中断
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)) { //为什么tasklet被禁止了后面又重新加入链表重新触发软中断,那不就一直存在软中断,为什么不在禁止状态的时候将其移除链表,使能的时候加入?
if (tasklet_clear_sched(t)) { // 检查任务是否需要调度
if (t->use_callback)
t->callback(t); // 调用任务的回调函数
else
t->func(t->data); // 调用任务的处理函数
}
tasklet_unlock(t); // 清除TASKLET_STATE_RUN
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(); // 启用本地中断
}
}
//如果t->count的值等于0,说明这个tasklet在调度之后,被disable掉了,所以会将tasklet结构体重新放回到tasklet_vec链表,并重新调度TASKLET_SOFTIRQ软中断,在之后enable这个tasklet之后重新再执行它
所有的tasklet都通过反复运行HI_SOFIRQ
和TASKLET_AOFIRQ
两个软中断进行实现,当一个tasklet被调度的时候,内核将会唤起其中一个软中断,此时唤醒的是TASKLET_AOFIRQ
,该软中断会被特定的函数进行处理,执行所有的已经调度的tasklet,tasklet核心处理过程就是循环遍历获得链表上的每一个待处理的tasklet,进行一系列检查最终判定tasklet处于TASKLET_STATE_RUN
状态没有在其他处理系统上运行,并且任务引用次数count为0就可以执行tasklet的处理程序。
那么在内核中tasklet是如何关闭软中断的锁机制呢?
static inline void local_bh_enable(void)
{//启用本地中断
__local_bh_enable_ip(_THIS_IP_, SOFTIRQ_DISABLE_OFFSET);//THIS_IP 表示当前代码所在的地址,而 SOFTIRQ_DISABLE_OFFSET 表示当前代码所在的偏移量。
}
static inline void local_bh_disable(void)
{//关闭软中断
__local_bh_disable_ip(_THIS_IP_, SOFTIRQ_DISABLE_OFFSET);//将当前 CPU 上所有的软中断都设置为不可用状态
}
实际上local_bh_enable()和local_bh_disable()都是对BH接口函数的封装。
实验部分
实验目的:
熟悉和了解tasklet机制的使用
实验要求:
(1)编写一个简单的内核模块,初始化一个tasklet,在write()函数中使用tasklet回调函数,在tasklet回调函数输出用户程序写入的字符串。
(2)写一个应用程序,测试以上功能。
实验步骤:
本实验是在Qemu环境下完成。
内核态代码:
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/kfifo.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/cdev.h>
#include <linux/poll.h>
#include <linux/interrupt.h>
#define DEMO_NAME "mydemo_dev"
#define MYDEMO_FIFO_SIZE 64
static dev_t dev;//设备号
static struct cdev *demo_cdev;//字符设备对象
static struct class *mydemo_class;//设备类
struct mydemo_device {
char name[64];
struct device *dev;
wait_queue_head_t read_queue;
wait_queue_head_t write_queue;
struct kfifo mydemo_fifo;
struct fasync_struct *fasync;//异步通知
struct mutex lock;//互斥锁
};
//私有数据结构体
struct mydemo_private_data {
struct mydemo_device *device;
char name[64];
struct tasklet_struct tasklet;//tasklet队列
};
#define MYDEMO_MAX_DEVICES 8
static struct mydemo_device *mydemo_device[MYDEMO_MAX_DEVICES]; //存储所有设备信息
//处理异部任务函数
static void do_tasklet(unsigned long data)
{
struct mydemo_device *device = (struct mydemo_device *)data;
dev_info(device->dev, "%s: trigger a tasklet\n", __func__);
}
//打开设备文件
static int demodrv_open(struct inode *inode, struct file *file)
{
unsigned int minor = iminor(inode);
struct mydemo_private_data *data;
struct mydemo_device *device = mydemo_device[minor];//从指针数组中获取对应的设备信息
dev_info(device->dev, "%s: major=%d, minor=%d, device=%s\n", __func__,
MAJOR(inode->i_rdev), MINOR(inode->i_rdev), device->name);
// 申请一个 mydemo_private_data 结构体类型的空间,用于存储打开设备的私有数据
data = kmalloc(sizeof(struct mydemo_private_data), GFP_KERNEL);
if (!data)
return -ENOMEM;
sprintf(data->name, "private_data_%d", minor);
tasklet_init(&data->tasklet, do_tasklet, (unsigned long)device);
//参数:将软中断对象的指针地址存储在设备数据结构体 、软中断被触发后执行的回调函数,将设备指针转换为无符号长整型,并将其作为参数传递给处理函数 do_tasklet()
//将设备对象或设备数据的指针存储在文件的私有数据字段
data->device = device;
file->private_data = data;
return 0;
}
//释放设备
static int demodrv_release(struct inode *inode, struct file *file)
{
// 从文件结构体的私有数据字段中获取设备数据结构体指针
struct mydemo_private_data *data = file->private_data;
// 停止并销毁与设备相关的软中断任务队列
tasklet_kill(&data->tasklet);
//释放设备结构体的内存
kfree(data);
return 0;
}
//读取设备文件数据
static ssize_t
demodrv_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
struct mydemo_private_data *data = file->private_data;
struct mydemo_device *device = data->device;
int actual_readed;
int ret;
if (kfifo_is_empty(&device->mydemo_fifo)) {//fifo缓冲区为空
if (file->f_flags & O_NONBLOCK)//文件标志包含I/O非阻塞模式
return -EAGAIN;
//打印日志进入睡眠状态等待数据
dev_info(device->dev, "%s:%s pid=%d, going to sleep, %s\n", __func__, device->name, current->pid, data->name);
ret = wait_event_interruptible(device->read_queue,
!kfifo_is_empty(&device->mydemo_fifo));
if (ret)
return ret;
}
//互斥锁防止并发访问
mutex_lock(&device->lock);
//将fifo缓冲区数据读出并复制到用户buf中,期望读取字节大小为count,actual_readed实际读取字节数
ret = kfifo_to_user(&device->mydemo_fifo, buf, count, &actual_readed);
if (ret)
return -EIO;
//调度一个延迟的任务,一旦延迟tasklet_schedule就会立即将该任务添加到内核的任务队列里面
tasklet_schedule(&data->tasklet);
//释放设备互斥锁
mutex_unlock(&device->lock);
//fifo缓冲区数据未满则唤醒写进程
if (!kfifo_is_full(&device->mydemo_fifo)){
wake_up_interruptible(&device->write_queue);
//发送异步通知,发送信号SIGIO,POLL_OUT此数据可供读取
kill_fasync(&device->fasync, SIGIO, POLL_OUT);
}
dev_info(device->dev, "%s:%s, pid=%d, actual_readed=%d, pos=%lld\n",__func__,
device->name, current->pid, actual_readed, *ppos);
return actual_readed;
}
static ssize_t
demodrv_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
struct mydemo_private_data *data = file->private_data;
struct mydemo_device *device = data->device;
unsigned int actual_write;
int ret;
if (kfifo_is_full(&device->mydemo_fifo)){
if (file->f_flags & O_NONBLOCK)
return -EAGAIN;
dev_info(device->dev, "%s:%s pid=%d, going to sleep\n", __func__, device->name, current->pid);
ret = wait_event_interruptible(device->write_queue,
!kfifo_is_full(&device->mydemo_fifo));
if (ret)
return ret;
}
mutex_lock(&device->lock);
ret = kfifo_from_user(&device->mydemo_fifo, buf, count, &actual_write);
if (ret)
return -EIO;
mutex_unlock(&device->lock);
if (!kfifo_is_empty(&device->mydemo_fifo)) {
wake_up_interruptible(&device->read_queue);
kill_fasync(&device->fasync, SIGIO, POLL_IN);
printk("%s kill fasync\n", __func__);
}
dev_info(device->dev, "%s:%s pid=%d, actual_write =%d, ppos=%lld, ret=%d\n", __func__,
device->name, current->pid, actual_write, *ppos, ret);
return actual_write;
}
//设备驱动程序的poll函数,用于检测设备是否可以进行读写操作,以及读写操作是否存在异常情况,主要完成的功能是将当前进程添加到相对应的等待队列中
static unsigned int demodrv_poll(struct file *file, poll_table *wait)
{
int mask = 0;
struct mydemo_private_data *data = file->private_data;
struct mydemo_device *device = data->device;
mutex_lock(&device->lock);
poll_wait(file, &device->read_queue, wait);//将当前进程加入到读等待队列
poll_wait(file, &device->write_queue, wait);
if (!kfifo_is_empty(&device->mydemo_fifo))
mask |= POLLIN | POLLRDNORM;
if (!kfifo_is_full(&device->mydemo_fifo))
mask |= POLLOUT | POLLWRNORM;
mutex_unlock(&device->lock);
return mask;//掩码会告诉设备当前是否可以进行读写、读写是否存在异常情况
}
//异步通知机制
static int demodrv_fasync(int fd, struct file *file, int on)
{
struct mydemo_private_data *data = file->private_data;
struct mydemo_device *device = data->device;
int ret;
mutex_lock(&device->lock);
dev_info(device->dev, "%s send SIGIO\n", __func__);
ret = fasync_helper(fd, file, on, &device->fasync);//设置异步通知处理函数并返回结果,将异步通知处理函数与文件描述符和文件对象关联起来,从而启用或禁用异步通知
mutex_unlock(&device->lock);
return ret;//表示异步通知的启用或禁用是否成功
}
//设备初始化
static const struct file_operations demodrv_fops = {
.owner = THIS_MODULE,
.open = demodrv_open,
.release = demodrv_release,
.read = demodrv_read,
.write = demodrv_write,
.poll = demodrv_poll,//轮询函数
.fasync = demodrv_fasync,//异步通知
};
static int __init simple_char_init(void)
{
int ret;
int i;
struct mydemo_device *device;
//分配字符设备号范围0- MYDEMO_MAX_DEVICES
ret = alloc_chrdev_region(&dev, 0, MYDEMO_MAX_DEVICES, DEMO_NAME);
if (ret) {
printk("failed to allocate char device region");
return ret;
}
demo_cdev = cdev_alloc();//分配cdev结构体对象
if (!demo_cdev) {
printk("cdev_alloc failed\n");
goto unregister_chrdev;
}
//cdev结构体对象进行初始化,并将设备文件操作函数集合关联起来
cdev_init(demo_cdev, &demodrv_fops);
//将字符设备添加到内核中
ret = cdev_add(demo_cdev, dev, MYDEMO_MAX_DEVICES);
if (ret) {
printk("cdev_add failed\n");
goto cdev_fail;
}
//创建设备类,在/sys/class目录下创建并且与当前模块进行关联
mydemo_class = class_create(THIS_MODULE, "my_class");
for (i = 0; i < MYDEMO_MAX_DEVICES; i++) {
device = kzalloc(sizeof(struct mydemo_device), GFP_KERNEL);
if (!device) {
ret = -ENOMEM;
goto free_device;
}
sprintf(device->name, "%s%d", DEMO_NAME, i);
mutex_init(&device->lock);//初始化设备锁
//创建字符设备,将字符设备与设备类mydemo_class进行关联
device->dev = device_create(mydemo_class, NULL, MKDEV(dev, i), NULL, "mydemo:%d:%d", MAJOR(dev), i);
dev_info(device->dev, "create device: %d:%d\n", MAJOR(dev), MINOR(i));
mydemo_device[i] = device;
init_waitqueue_head(&device->read_queue);
init_waitqueue_head(&device->write_queue);
//为设备分配fifo环形缓冲区
ret = kfifo_alloc(&device->mydemo_fifo,
MYDEMO_FIFO_SIZE,
GFP_KERNEL);
if (ret) {
ret = -ENOMEM;
goto free_kfifo;
}
printk("mydemo_fifo=%p\n", &device->mydemo_fifo);
}
printk("succeeded register char device: %s\n", DEMO_NAME);
return 0;
//释放环形缓冲区内存
free_kfifo:
for (i =0; i < MYDEMO_MAX_DEVICES; i++)
if (&device->mydemo_fifo)
kfifo_free(&device->mydemo_fifo);
//释放设备结构体对象内存
free_device:
for (i =0; i < MYDEMO_MAX_DEVICES; i++)
if (mydemo_device[i])
kfree(mydemo_device[i]);
//删除字符设备对象
cdev_fail:
cdev_del(demo_cdev);
//注销设备号
unregister_chrdev:
unregister_chrdev_region(dev, MYDEMO_MAX_DEVICES);
return ret;
}
static void __exit simple_char_exit(void)
{
int i;
printk("removing device\n");
if (demo_cdev)
cdev_del(demo_cdev);
unregister_chrdev_region(dev, MYDEMO_MAX_DEVICES);
//释放设备对象以及设备结构体对象内存
for (i =0; i < MYDEMO_MAX_DEVICES; i++) {
if (mydemo_device[i]) {
device_destroy(mydemo_class, MKDEV(dev, i));
kfree(mydemo_device[i]);
}
}
//销毁设备类
class_destroy(mydemo_class);
}
module_init(simple_char_init);
module_exit(simple_char_exit);
MODULE_AUTHOR("Benshushu");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("simpe character device");
用户态代码:
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>
#include <poll.h>
#include <signal.h>
//通过使用异步I/O和信号处理来实现对设备文件的事件驱动操作。当设备文件的I/O状态发生变化时,会触发SIGIO信号,并通过注册的信号处理函数my_signal_fun来处理相应的事件。
static int fd;
//信号处理
void my_signal_fun(int signum, siginfo_t *siginfo, void *act)
{
int ret;
char buf[64];
//异步错误通知信号
if (signum == SIGIO) {
if (siginfo->si_band & POLLIN) {//有数据可读
printf("FIFO is not empty\n");
if ((ret = read(fd, buf, sizeof(buf))) != -1) {//读取数据
buf[ret] = '\0';
puts(buf);
}
}
if (siginfo->si_band & POLLOUT)//有空间可写
printf("FIFO is not full\n");
}
}
int main(int argc, char *argv[])
{
int ret;
int flag;
struct sigaction act, oldact;
sigemptyset(&act.sa_mask);//清空信号集act.sa_mask
sigaddset(&act.sa_mask, SIGIO);//SIGIO添加到信号集
act.sa_flags = SA_SIGINFO;//使用sa_sigaction字段指定的函数作为信号处理函数
act.sa_sigaction = my_signal_fun;//SIGIO信号的处理方式设置为my_signal_fun函数
if (sigaction(SIGIO, &act, &oldact) == -1)
goto fail;
fd = open("/dev/mydemo0", O_RDWR);
if (fd < 0)
goto fail;
/*设置异步IO所有权*/
if (fcntl(fd, F_SETOWN, getpid()) == -1)
goto fail;
/*将当前进程PID设置为fd文件所对应驱动程序将要发送SIGIO,SIGUSR信号进程PID*/
if (fcntl(fd, F_SETSIG, SIGIO) == -1)
goto fail;
/*获取文件flags*/
if ((flag = fcntl(fd, F_GETFL)) == -1)
goto fail;
/*设置文件flags, 设置FASYNC,支持异步通知*/
if (fcntl(fd, F_SETFL, flag | FASYNC) == -1)
goto fail;
while (1)//等待异步I/O事件
sleep(1);
fail:
perror("fasync test");
exit(EXIT_FAILURE);
}
插入模块后显示当前创建了8个设备,每个设备的设备名、主设备号、次设备号以及每个设备在缓冲区的地址。
查看创建的8个设备,可以看到显示的信息是设备名、主设备号以及次设备号,在/dev/目录下并没有发现主设备为247的,故需要手动去mknod一个。
mknod创建一个字符设备mydemo,指定主设备号为247,次设备号为0。
mknod /dev/mydemo0 c 247 0
gcc编译测试程序并让其在后台运行,echo命令在终端向mydemo设备中写入字符串,首先进入demodrv_open
函数打开字符设备,打印当前进程信息(主设备号、次设备号、设备名),调用demodrv_write
函数向设备文件中进行写操作,首先将写队列处于睡眠状态,等当前fifo缓冲区并不空可以用wake_up_interruptible
读进程唤醒写进程,打印日志信息,写入完毕进行读取进入demodrv_read
函数,当前缓冲区并不为空所以将读队列置于睡眠状态,给设备上互斥锁,并且将fifo缓冲区数据读出并复制到用户buf中,调用tasklet_schedule()
加入tasklet队列将的延迟的任务加入到该队列中,释放掉互斥锁,如果此时fifo缓冲区数据并未满那么调用wake_up_interruptible
写进程唤醒读进程,发送异步信号通知打印日志信息,读取信息为“i am student” 已经成功,进入tasklet的回调函数do_tasklet
打印信息 trigger a tasklet 即当前程序运行成功。
问题:
1、为什么在读写操作的时候要加入互斥锁?
在demodrv_poll
中加入互斥锁可以确保在同一个时间只有一个进程可以访问设备,避免在此过程中存在多个进程同时对一个设备进行读写操作,在demodrv_write
和demodrv_read
中加入互斥锁可以避免在读取数据的时候受到写操作干扰,在向设备中写入数据的时候受到读操作的干扰,加入互斥锁可以确保对设备状态的读取以及判断都是在临界区域内完成,避免读写并发的冲突。
2、tasklet任务机制起到什么作用?它是什么时候被触发的?
tasklet任务机制主要用于处理一些需要快速执行但是又不宜立即去执行的任务,在此实验中tasklet主要用于触发异步通知,执行需要等待延迟处理的任务。tasklet的触发是在demodrv_read()
函数中fifo缓冲区有数据可读时调用tasklet_schedule()
去触发tasklet任务,将需要延迟处理的工作放在tasklet中。
3、为什么在tasklet调度的时候也要加锁?
tasklet
是依靠软中断完成的,可能需要实时处理一些需要快速响应的任务,所以tasklet
不能处于睡眠状态,所以在此过程中不能使用阻塞型函数,如果在此过程不上锁那么就会存在当前的tasklet和中断处理程序共享某些数据或者两个不同的tasklet共享数据,可能导致多个tasklet访问数据存在不一致行,所以要对taskle
t调度上锁保护共享资源,让一个 tasklet 只能在一个处理器上执行一次。
4、为什么不在禁止状态的时候将tasklet
移除链表而是保留在链表中?
tasklet的禁用可启用可能存在于不同的上下文,有可能在中断上下文中禁用tasklet但是在其他上下文中会重新启用tasklet,如果被禁用后在链表中将其移除,那么可能在重新启用它时会导致丢失某些需要延迟处理的任务,所以通常情况下会通常会保留禁用状态的 tasklet
在链表中,在适当的时候重新启用它。这样,就会在下一次调用 tasklet_action()
函数时,禁用状态的 tasklet
将被跳过,而只有启用状态的 tasklet
才会被执行。