tasklet内核源代码分析

tasklet的使用方法在之前也有讲过,但是不够全面,而且也仅仅知道怎么使用而已,现在看看被人的总结:

//初始化tasklet_struct结构体
void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data);

//使能一个之前被disable的tasklet;若这个tasklet已经被调度, 它会很快运行。
void tasklet_enable(struct tasklet_struct *t); 

//函数暂时禁止给定的tasklet被tasklet_schedule调度,直到这个tasklet被再次被enable;若这个tasklet当前在运行, 这个函数忙等待直到这个tasklet退出
void tasklet_disable(struct tasklet_struct *t); 

//和tasklet_disable类似,但是tasklet可能仍然运行在另一个 CPU
void tasklet_disable_nosync(struct tasklet_struct *t); 

//调度 tasklet 执行,如果tasklet在运行中被调度, 它在完成后会再次运行; 这保证了在其他事件被处理当中发生的事件受到应有的注意. 这个做法也允许一个 tasklet 重新调度它自己
void tasklet_schedule(struct tasklet_struct *t); 

//和tasklet_schedule类似,只是在更高优先级执行。
void tasklet_hi_schedule(struct tasklet_struct *t); 

//确保了 tasklet 不会被再次调度来运行,通常当一个设备正被关闭或者模块卸载时被调用。如果 tasklet 正在运行, 这个函数等待直到它执行完毕。
void tasklet_kill(struct tasklet_struct *t); 

这里就深入的看看其实现方法,现在先来一个tasklet的使用案例:

#include
#include
#include
#include 
    
static void my_tasklet_function(unsigned long data);
        
char my_tasklet_data[]="my_tasklet_function was  called.";
        
   
 //1.1)动态定义结构体变量,用于动态初始化
struct tasklet_struct my_tasklet;
    
 //2)定义tasklet工作函数
static void my_tasklet_function(unsigned long data)
{
       	printk("%s\r\n",(char *)data);
      	udelay(2000);	  //延时2ms,而不是休眠
}
        
static int __init tasklet_module_init(void)
{
	printk("mondule insmod\r\n");
	
	//1.2)动态初始化
    tasklet_init(&my_tasklet, my_tasklet_function,(unsigned long) my_tasklet_data);
    
   	//3)调度tasklet
    tasklet_schedule(&my_tasklet);
    return 0;
}
    
static void __exit tasklet_cleanup(void)
{
	 printk("mondule remove\r\n");
	//4)销毁tasklet 
	tasklet_kill(&my_tasklet);
}

module_init(tasklet_module_init);   
module_exit(tasklet_cleanup);  
MODULE_LICENSE("GPL");

所以我们只要弄明白下面的几个函数是怎么实现的就可以了:

  1. struct tasklet_struct结构体
struct tasklet_struct
{
	struct tasklet_struct *next;//指针,用于建立tasklet_struct实例的链表,允许多个任务在排队
	
	//表示任务当前的状态,一共有两种状态:
	//TASKLET_STATE_SCHED:表示等待调度,注册tasklet的时候会初始化为该状态
	//TASKLET_STATE_RUN:表示tasklet当前正在执行
	unsigned long state;
	atomic_t count;//原子锁计数器,用于同步tasklet_struct的修改
	void (*func)(unsigned long);//最重要的成员:回调函数
	unsigned long data;//回调函数的执行参数
};

主要成员就这么几个,next指针主要是把所有的tasklet_struct连起来,state表示tasklet_struct的状态是在排队还是在执行,count是原子锁计数器,他的作用是保护tasklet_struct不会被同时修改,func是回调的执行函数,data就是执行函数的参数。

  1. 初始化函数:void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data)
void tasklet_init(struct tasklet_struct *t,
		  void (*func)(unsigned long), unsigned long data)
{
	//初始化tasklet_struct结构体成员
	t->next = NULL;
	t->state = 0;
	atomic_set(&t->count, 0);
	t->func = func;
	t->data = data;
}
EXPORT_SYMBOL(tasklet_init);

可以看出tasklet_init的作用就是初始化tasklet_struct 结构体。

  1. 调度函数:tasklet_schedule (struct tasklet_struct *t)
static inline void tasklet_schedule(struct tasklet_struct *t)
{
	//设置tasklet的状态为TASKLET_STATE_SCHED,使用__tasklet_schedule进行调度
	if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
		__tasklet_schedule(t);
}

void __tasklet_schedule(struct tasklet_struct *t)
{
	__tasklet_schedule_common(t, &tasklet_vec,
				  TASKLET_SOFTIRQ);
}
EXPORT_SYMBOL(__tasklet_schedule);

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);//获取per cpu变量的线性地址
	t->next = NULL;//初始化tasklet_struct的next指针
	*head->tail = t;//把tasklet_struct放到cpu调度链表中
	head->tail = &(t->next);//设置head->tail的地址,用于存放下一个tasklet_struct
	raise_softirq_irqoff(softirq_nr);//唤醒第softirq_nr个softirq准备执行
	local_irq_restore(flags);//恢复中断状态寄存器
}

调度函数的首先设置tasklet_struct的状态,然后最后调用__tasklet_schedule_common关闭本地cpu中断,把tasklet_struct 放到cpu的tasklet_head调度链表中,让cpu自己找到tasklet_struct并且调度其中的回调函数。

  1. 销毁函数:void tasklet_kill(struct tasklet_struct *t)
void tasklet_kill(struct tasklet_struct *t)
{
	//如果在中断状态,则提醒一下
	if (in_interrupt())
		pr_notice("Attempt to kill tasklet from interrupt\n");

	//等待调度完毕
	while (test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {
		do {
			yield();//让出cpu
		} while (test_bit(TASKLET_STATE_SCHED, &t->state));
	}
	tasklet_unlock_wait(t);//等待解锁
	//清除tasklet_struct的状态
	clear_bit(TASKLET_STATE_SCHED, &t->state);
}
EXPORT_SYMBOL(tasklet_kill);

销毁函数就是在等待调度完毕后让出cpu,最后清除tasklet_struct 的状态。

看到这里有人就奇怪了,那到底是怎么调用到func函数的呢?
这就要说到linux中的软中断了,因为tasklet是通过软中断实现的,我们下面看看软中断的初始化函数把,函数在kernel/softirq.c文件中:

void __init softirq_init(void)
{
	int cpu;
	
	//初始化每个cpu的tasklet_vec链表
	for_each_possible_cpu(cpu) {
		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的软中断,中断回调函数为tasklet_action
	open_softirq(TASKLET_SOFTIRQ, tasklet_action);
	//打开HI_SOFTIRQ的软中断,中断回调函数为tasklet_hi_action
	open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}

软中断无非就是初始化每一个cpu的tasklet_vec链表,然后打开TASKLET_SOFTIRQ和HI_SOFTIRQ的软中断,我们先看看open_softirq是怎么打开软中断的:

void open_softirq(int nr, void (*action)(struct softirq_action *))
{
	softirq_vec[nr].action = action;
}

其实只是把中断向量数组的action成员赋值上该中断的回调函数而已,那么每次该中断产生,就会去到回调函数执行了,是不是很简单呢。
我们再看看回调函数tasklet_action是怎么找到我们的tasklet_struct的:

static __latent_entropy void tasklet_action(struct softirq_action *a)
{
	tasklet_action_common(a, this_cpu_ptr(&tasklet_vec), TASKLET_SOFTIRQ);
}

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;//记录tasklet_head指针
	tl_head->head = NULL;//赋值head,让链表为空
	tl_head->tail = &tl_head->head;//赋值tail,让链表为空
	local_irq_enable();//开启中断

	//遍历tasklet链表,让链表上挂入的函数全部执行完成
	while (list) {
		//从链表上摘下每一个tasklet_struct结构体
		struct tasklet_struct *t = list;

		list = list->next;

		//根据tasklet_struct的state成员,判断是否RUNING
		if (tasklet_trylock(t)) {
			if (!atomic_read(&t->count)) {//如果原子锁计数器为0,则可以执行
				//执行前需要先改变state为RUNING
				if (!test_and_clear_bit(TASKLET_STATE_SCHED,
							&t->state))
					BUG();
				t->func(t->data);//真正运行user注册的tasklet函数的地方
				tasklet_unlock(t);//运行完毕,清除RUNING状态
				continue;
			}
			tasklet_unlock(t);//如果原子锁计数器不为0,清除RUNING状态
		}

		local_irq_disable();//关闭本地cpu中断
		//把以上没有处理完的tasklet重新挂到tasklt_vec数组中对应的cpu上的链表
		t->next = NULL;
		*tl_head->tail = t;
		tl_head->tail = &t->next;
		__raise_softirq_irqoff(softirq_nr);//把本地CPU的TASKLET_SOFTIRQ标记挂起
		local_irq_enable();//使能中断
	}
}

在中断回调函数tasklet_action主要是调用tasklet_action_common来完成tasklet_struct的fun实现,首先还是关闭本地中断,禁止内核抢占,再取出tasklet_head链表并且清空tasklet_head链表,最后把中断开回来,然后再遍历tasklet链表,在while循环中执行fun函数,并且把没有处理的tasklet重新挂到tasklt_vec中。

如果还想知道软中断是怎么通知cpu执行的,请看下集。

你可能感兴趣的:(Linux,kernel,linux,tasklet)