tasklet

我们从软中断的初始化说起:

软中断的初始化是在star_kernel中调用softirq_init完成的。

void __init softirq_init(void)
{
	int cpu;

	for_each_possible_cpu(cpu) {  //初始化每个CPU的tasklet_vec队列、tasklet_hi_vec队列以及软中断工作列表。
		int i;

		per_cpu(tasklet_vec, cpu).tail =
			&per_cpu(tasklet_vec, cpu).head;
/*
struct tasklet_head
{
    struct tasklet_struct *head;
    struct tasklet_struct **tail;
};
*/
		per_cpu(tasklet_hi_vec, cpu).tail =
			&per_cpu(tasklet_hi_vec, cpu).head;

		for (i = 0; i < NR_SOFTIRQS; i++)
			INIT_LIST_HEAD(&per_cpu(softirq_work_list[i], cpu));
/*
DECLARE_PER_CPU(struct list_head [NR_SOFTIRQS], softirq_work_list);
*/
         }

	register_hotcpu_notifier(&remote_softirq_cpu_notifier);

	open_softirq(TASKLET_SOFTIRQ, tasklet_action);
	open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}
初始化中最重要的莫过于最后两个操作,用来初始化softirq_vec[TASKLET_SOFTIRQ]和softirq_vec[HI_SOFTIRQ]。

我们首先要知道softirq_vec是干嘛的。.


/* softirq mask and active fields moved to irq_cpustat_t in
 * asm/hardirq.h to get better cache usage.  KAO
 */

struct softirq_action
{
	void	(*action)(struct softirq_action *);
};

static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

softirq_vec是个全局量,系统中的各个CPU所看到的是同一个数组。但是每个CPU有自己的“软中断控制/状况结构”,这些控制结构形成以CPU编号为下标的数组irq_stat[],这个数组也是全局变量,但是各个CPU可以按其自身的编号访问相应的数据结构:

typedef struct {
	unsigned int __softirq_pending;
	unsigned int __nmi_count;	/* arch dependent */
	unsigned int irq0_irqs;
#ifdef CONFIG_X86_LOCAL_APIC
	unsigned int apic_timer_irqs;	/* arch dependent */
	unsigned int irq_spurious_count;
#endif
	unsigned int x86_platform_ipis;	/* arch dependent */
	unsigned int apic_perf_irqs;
	unsigned int apic_irq_work_irqs;
#ifdef CONFIG_SMP
	unsigned int irq_resched_count;
	unsigned int irq_call_count;
	unsigned int irq_tlb_count;
#endif
#ifdef CONFIG_X86_THERMAL_VECTOR
	unsigned int irq_thermal_count;
#endif
#ifdef CONFIG_X86_MCE_THRESHOLD
	unsigned int irq_threshold_count;
#endif
} ____cacheline_aligned irq_cpustat_t;

DEFINE_PER_CPU_SHARED_ALIGNED(irq_cpustat_t, irq_stat);

irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned;



至于NR_SOFTIRQS:

/* PLEASE, avoid to allocate new softirqs, if you need not _really_ high
   frequency threaded job scheduling. For almost all the purposes
   tasklets are more than enough. F.e. all serial device BHs et
   al. should be converted to tasklets, not to softirqs.
 */

enum
{
	HI_SOFTIRQ=0,
	TIMER_SOFTIRQ,
	NET_TX_SOFTIRQ,
	NET_RX_SOFTIRQ,
	BLOCK_SOFTIRQ,
	BLOCK_IOPOLL_SOFTIRQ,
	TASKLET_SOFTIRQ,
	SCHED_SOFTIRQ,
	HRTIMER_SOFTIRQ,
	RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */

	NR_SOFTIRQS
};

因此softirq_vec[HI_SOFTIRQ]对应于HI_SOFTIRQ类型软中断所对应的服务程序,即初始化时的tasklet_hi_action;

而softirq_vec[TASKLET_SOFTIRQ]对应于TASKLET_IRQ类型的软中断所对应的服务程序,即初始化为tasklet_action。
至于其他类型软中断的服务是在其它的函数中进行初始化的,如TIMER_SOFTIRQ是在init_timers中初始化的。


tasklet_hi_vec队列和tasklet_vec队列也是一CPU编号为下标的全局变量,它们将同一种类型的不同的软中断服务程序以队列的形式组织起来,逐个执行。


现在,回想一下在“软中断”的博文中,我们是如何调用到具体的软中断服务程序的:

    asmlinkage void __do_softirq(void)  
    {  
        struct softirq_action *h;  
        __u32 pending;  
        int max_restart = MAX_SOFTIRQ_RESTART;  
        int cpu;  
      
        pending = local_softirq_pending();    
        account_system_vtime(current);  
      
        __local_bh_disable((unsigned long)__builtin_return_address(0),  // 这里加上软中断计数,这样在本函数中开中断后,发生中断后不会再重入本函数  
                    SOFTIRQ_OFFSET);  
        lockdep_softirq_enter();  //调试用  
      
        cpu = smp_processor_id();  
    restart:  
        /* Reset the pending bitmask before enabling irqs */  
        set_softirq_pending(0);  //将软中断对应的掩码清0  
      
        local_irq_enable();      //开中断,避免长时间不能中断  
      
        h = softirq_vec;      
      
        do {  
            if (pending & 1) {  
                unsigned int vec_nr = h - softirq_vec;   //获取对应的软中断的编号  
                int prev_count = preempt_count();        //保存抢占计数preempt_count,避免其受到破坏  
      
                kstat_incr_softirqs_this_cpu(vec_nr);  
      
                trace_softirq_entry(vec_nr);  
                h->action(h);                        //执行相应的软中断服务程序  ****************************************************
                trace_softirq_exit(vec_nr);  
                if (unlikely(prev_count != preempt_count())) {  //如果软中断破坏了抢占计数器,那么恢复原来的preempt_count  
                    printk(KERN_ERR "huh, entered softirq %u %s %p"  
                           "with preempt_count %08x,"  
                           " exited with %08x?\n", vec_nr,  
                           softirq_to_name[vec_nr], h->action,  
                           prev_count, preempt_count());  
                    preempt_count() = prev_count;  
                }  
      
                rcu_bh_qs(cpu);  
            }  
            h++;              //处理下一个软中断  
            pending >>= 1;    //右移pending  
        } while (pending);        //如果没有挂起的软中断,那么结束循环  
       
        local_irq_disable();      //关中断  
       
        pending = local_softirq_pending();  
        if (pending && --max_restart) //max_restart = 10;因此,最多轮询10次。以防止执行处理软中断的时间过长,而将应用程序饿死。  
            goto restart;  
      
        if (pending)                  //如果还是没有将所有软中断处理完,那么唤醒softirqd,放弃CPU占用权,让其它应用程序进来。  
            wakeup_softirqd();  
      
        lockdep_softirq_exit();  
      
        account_system_vtime(current);  
        __local_bh_enable(SOFTIRQ_OFFSET);  
    }  


好,我们现在来看tasklet_action。

static void tasklet_action(struct softirq_action *a)
{
	struct tasklet_struct *list;

//由于中断可能注册tasklet,因此,在获取待处理的tasklet链表时,需要关闭中断。
	local_irq_disable();

    /**

     * 将本CPU上的任务链表头加载到局部变量中,并将任务链表头置空,这样可以快速获取整个链表。

     * 在后续的处理中,不必长时间的关闭中断。

     */
	list = __this_cpu_read(tasklet_vec.head);
	__this_cpu_write(tasklet_vec.head, NULL);
	__this_cpu_write(tasklet_vec.tail, &__get_cpu_var(tasklet_vec).head);
	local_irq_enable();

    /**

     * 遍历处理局部变量中保存的任务链表。

     */

	while (list) {
		struct tasklet_struct *t = list;

		list = list->next;

             /**

              * 其他核上的中断可能会调度一个tasklet开始运行。因此这里试图获得它的锁再执行其他回调。

              */

if (tasklet_trylock(t)) {/*没有禁止该tasklet */ if (!atomic_read(&t->count)) { if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state)) /* 任务没有被禁止,又没有被调度,但是又在链表中,是不正常的 */ BUG(); t->func(t->data);/* 可以安全的调用tasklet的回调函数了 */ tasklet_unlock(t);/* 执行完回调函数后释放任务锁并继续处理下一个任务 */ continue; }

                       /**

                        * 运行到这里,说明其他核在操作该任务,解除锁并将任务加回链表待下次处理任务。

                        */

tasklet_unlock(t); }

             /**

              * 运行到里,说明不能获得任务锁,或者其他核在操作任务,因此需要将任务放回链表。

              * 在放回前,需要关中断以保护链表。

              */

   local_irq_disable();

           /**

              * 将任务加到链表尾部。

              */
t->next = NULL; *__this_cpu_read(tasklet_vec.tail) = t; //触发TASKLET_SOFTIRQ,这样,在do_softirq的下一轮将会处理该任务。

__raise_softirq_irqoff(TASKLET_SOFTIRQ); local_irq_enable(); } } 



看一下tasklet_struct:

/* Tasklets --- multithreaded analogue of BHs.

   Main feature differing them of generic softirqs: tasklet
   is running only on one CPU simultaneously.

   Main feature differing them of BHs: different tasklets
   may be run simultaneously on different CPUs.

   Properties:
   * If tasklet_schedule() is called, then tasklet is guaranteed
     to be executed on some cpu at least once after this.
   * If the tasklet is already scheduled, but its execution is still not
     started, it will be executed only once.
   * If this tasklet is already running on another CPU (or schedule is called
     from tasklet itself), it is rescheduled for later.
   * Tasklet is strictly serialized wrt itself, but not
     wrt another tasklets. If client needs some intertask synchronization,
     he makes it with spinlocks.
 */

struct tasklet_struct
{
	struct tasklet_struct *next;  //链表指针
	unsigned long state;          //任务当前的状态,只有两个状态(1)在tasklet注册到内核,等待调度执行的时候,将其设置为TASKLET_STATE_SCHED
                                      //(2)TASKLET_STATE_RUN表示tasklet当前正在执行。这个状态只在SMP系统上有用,用于保护tasklet在多个处理器上并行进行。
	atomic_t count;               //用于禁用已调度的tasklet,如果非0,则在接下来执行有待决的tasklet时,将忽略对应的tasklet。
	void (*func)(unsigned long);  //特定的服务程序
	unsigned long data;           //服务程序执行时的参数
};
对这个数据结构的解释是很重要的,开头列出了tasklet和普通的softirqs和BHS的区别:tasklet是“多序”的(不是多进程或者多线程的!),因为同一时刻tasklet只能在某个确定的CPU上运行,但是多个CPU上可以同时运行不同的tasklet(对BHS改进)。


分析到这里,我们大概知道了tasklet从初始化到调用的过程,那么如何用tasklet机制?也就是说给一个驱动程序,内核是如何将其加载并且执行的呢?

驱动程序在初始化时,通过函数tasklet_init建立一个tasklet:

void tasklet_init(struct tasklet_struct *t,
		  void (*func)(unsigned long), unsigned long data)
{
	t->next = NULL;
	t->state = 0;
	atomic_set(&t->count, 0);
	t->func = func;
	t->data = data;
}

然后调用函数tasklet_schedule将这个tasklet放在 tasklet_vec链表的头部,并唤醒后台线程ksoftirqd:

static inline void tasklet_schedule(struct tasklet_struct *t)
{
	if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
		__tasklet_schedule(t);
}

void __tasklet_schedule(struct tasklet_struct *t)
{
	unsigned long flags;

	local_irq_save(flags);
	t->next = NULL;
	*__this_cpu_read(tasklet_vec.tail) = t;
	__this_cpu_write(tasklet_vec.tail, &(t->next));
	raise_softirq_irqoff(TASKLET_SOFTIRQ);
	local_irq_restore(flags);
}
当后台线程ksoftirqd运行调用__do_softirq时,会执行在中断向量表softirq_vec里中断号TASKLET_SOFTIRQ对应的tasklet_action函数,然后tasklet_action遍历 tasklet_vec链表,调用每个tasklet的函数完成软中断操作。



参考:《情景分析》

           《深入Linux内核架构》
   http://blog.chinaunix.net/space.php?uid=25845340&do=blog&id=2983385

   http://blog.csdn.net/chengqianyun2002/article/details/1607005

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值