softIRQ tasklet work_queue
来源:互联网 发布:幸运牛抽奖软件注册码 编辑:程序博客网 时间:2024/05/16 11:14
Soft Irq、tasklet和work queue
Soft Irq、tasklet和work queue并不是Linux内核中一直存在的机制,而是由更早版本的内核中的“下半部”(bottom half)演变而来。下半部的机制实际上包括五种,但2.6版本的内核中,下半部和任务队列的函数都消失了,只剩下了前三者。本文重点在于介绍这三者之间的关系。(函数细节将不会在本文中出现,可以参考文献,点这里)
(1)上半部和下半部的区别
上半部指的是中断处理程序,下半部则指的是一些虽然与中断有相关性但是可以延后执行的任务。举个例子:在网络传输中,网卡接收到数据包这个事件不一定需要马上被处理,适合用下半部去实现;但是用户敲击键盘这样的事件就必须马上被响应,应该用中断实现。
两者的主要区别在于:中断不能被相同类型的中断打断,而下半部依然可以被中断打断;中断对于时间非常敏感,而下半部基本上都是一些可以延迟的工作。由于二者的这种区别,所以对于一个工作是放在上半部还是放在下半部去执行,可以参考下面四条:
a)如果一个任务对时间非常敏感,将其放在中断处理程序中执行。
b)如果一个任务和硬件相关,将其放在中断处理程序中执行。
c)如果一个任务要保证不被其他中断(特别是相同的中断)打断,将其放在中断处理程序中执行。
d)其他所有任务,考虑放在下半部去执行。
(2)为什么要使用软中断?
软中断作为下半部机制的代表,是随着SMP(share memory processor)的出现应运而生的,它也是tasklet实现的基础(tasklet实际上只是在软中断的基础上添加了一定的机制)。软中断一般是“可延迟函数”的总称,有时候也包括了tasklet(请读者在遇到的时候根据上下文推断是否包含tasklet)。它的出现就是因为要满足上面所提出的上半部和下半部的区别,使得对时间不敏感的任务延后执行,而且可以在多个CPU上并行执行,使得总的系统效率可以更高。它的特性包括:
a)产生后并不是马上可以执行,必须要等待内核的调度才能执行。软中断不能被自己打断,只能被硬件中断打断(上半部)。
b)可以并发运行在多个CPU上(即使同一类型的也可以)。所以软中断必须设计为可重入的函数(允许多个CPU同时操作),因此也需要使用自旋锁来保护其数据结构。
(3)为什么要使用tasklet?(tasklet和软中断的区别)
由于软中断必须使用可重入函数,这就导致设计上的复杂度变高,作为设备驱动程序的开发者来说,增加了负担。而如果某种应用并不需要在多个CPU上并行执行,那么软中断其实是没有必要的。因此诞生了弥补以上两个要求的tasklet。它具有以下特性:
a)一种特定类型的tasklet只能运行在一个CPU上,不能并行,只能串行执行。
b)多个不同类型的tasklet可以并行在多个CPU上。
c)软中断是静态分配的,在内核编译好之后,就不能改变。但tasklet就灵活许多,可以在运行时改变(比如添加模块时)。
tasklet是在两种软中断类型的基础上实现的,但是由于其特殊的实现机制(将在4.3节详细介绍),所以具有了这样不同于软中断的特性。而由于这种特性,所以降低了设备驱动程序开发者的负担,因此如果不需要软中断的并行特性,tasklet就是最好的选择。
(4)可延迟函数(软中断及tasklet)的使用
一般而言,在可延迟函数上可以执行四种操作:初始化/激活/执行/屏蔽。屏蔽我们这里不再叙述,前三个则比较重要。下面将软中断和tasklet的三个步骤分别进行对比介绍。
(4.1)初始化
初始化是指在可延迟函数准备就绪之前所做的所有工作。一般包括两个大步骤:首先是向内核声明这个可延迟函数,以备内核在需要的时候调用;然后就是调用相应的初始化函数,用函数指针等初始化相应的描述符。
如果是软中断则在内核初始化时进行,其描述符定义如下:
在\kernel\softirq.c文件中包括了32个描述符的数组static struct softirq_action softirq_vec[32];但实际上只有前6个已经被内核注册使用(包括tasklet使用的HI_SOFTIRQ/TASKLET_SOFTIRQ和网络协议栈使用的NET_TX_SOFTIRQ/NET_RX_SOFTIRQ,还有SCSI存储和系统计时器使用的两个),剩下的可以由内核开发者使用。需要使用函数:
初始化数组中索引为nr的那个元素。需要的参数当然就是action函数指针以及data。例如网络子系统就通过以下两个函数初始化软中断(net_tx_action/net_rx_action是两个函数):
这样初始化完成后实际上就完成了一个一一对应的关系:当内核中产生到NET_TX_SOFTIRQ软中断之后,就会调用net_tx_action这个函数。
tasklet则可以在运行时定义,例如加载模块时。定义方式有两种:
静态声明
DECLARE_TASKLET_DISABLED(name, func, data)
动态声明
其参数分别为描述符,需要调用的函数和此函数的参数—必须是unsigned long类型。也需要用户自己写一个类似net_tx_action的函数指针func。初始化最终生成的结果就是一个实际的描述符,假设为my_tasklet(将在下面用到)。
(4.2)激活
激活标记一个可延迟函数为挂起(pending)状态,表示内核可以调用这个可延迟函数(即使在中断过程中也可以激活可延迟函数,只不过函数不会被马上执行);这种情况可以类比处于TASK_RUNNING状态的进程,处在这个状态的进程只是准备好了被CPU调度,但并不一定马上就会被调度。
软中断使用raise_softirq()函数激活,接收的参数就是上面初始化时用到的数组索引nr。
tasklet使用tasklet_schedule()激活,该函数接受tasklet的描述符作为参数,例如上面生成的my_tasklet:
(4.3)执行
执行就是内核运行可延迟函数的过程,但是执行只发生在某些特定的时刻(叫做检查点,具体有哪些检查点?详见《深入》p.177)。
每个CPU上都有一个32位的掩码__softirq_pending,表明此CPU上有哪些挂起(已被激活)的软中断。此掩码可以用local_softirq_pending()宏获得。所有的挂起的软中断需要用do_softirq()函数的一个循环来处理。
而对于tasklet,由于软中断初始化时,就已经通过下面的语句初始化了当遇到TASKLET_SOFTIRQ/HI_SOFTIRQ这两个软中断所需要执行的函数:
因此,这两个软中断是要被区别对待的。tasklet_action和tasklet_hi_action内部实现就是为什么软中断和tasklet有不同的特性的原因(当然也因为二者的描述符不同,tasklet的描述符要比软中断的复杂,也就是说内核设计者自己多做了一部分限制的工作而减少了驱动程序开发者的工作)。
(5)为什么要使用工作队列work queue?(work queue和软中断的区别)
上面我们介绍的可延迟函数运行在中断上下文中(软中断的一个检查点就是do_IRQ退出的时候),于是导致了一些问题:软中断不能睡眠、不能阻塞。由于中断上下文出于内核态,没有进程切换,所以如果软中断一旦睡眠或者阻塞,将无法退出这种状态,导致内核会整个僵死。但可阻塞函数不能用在中断上下文中实现,必须要运行在进程上下文中,例如访问磁盘数据块的函数。因此,可阻塞函数不能用软中断来实现。但是它们往往又具有可延迟的特性。
因此在2.6版的内核中出现了在内核态运行的work queue(替代了2.4内核中的任务队列)。它也具有一些可延迟函数的特点(需要被激活和延后执行),但是能够能够在不同的进程间切换,以完成不同的工作。
什么是workqueue?
Linux中的Workqueue机制就是为了简化内核线程的创建。通过调用workqueue的接口就能创建内核线程。并且可以根据当前系统CPU的个数创建线程的数量,使得线程处理的事务能够并行化。
workqueue是内核中实现简单而有效的机制,他显然简化了内核daemon的创建,方便了用户的编程,
Workqueue机制的实现
Workqueue机制中定义了两个重要的数据结构,分析如下:
1、
cpu_workqueue_struct结构。该结构将CPU和内核线程进行了绑定。在创建workqueue的过程中,Linux根据当前系统CPU的个数创建cpu_workqueue_struct。在该结构主要维护了一个任务队列,以及内核线程需要睡眠的等待队列,另外还维护了一个任务上下文,即task_struct。
2、
work_struct结构是对任务的抽象。在该结构中需要维护具体的任务方法,需要处理的数据,以及任务处理的时间。该结构定义如下:
struct work_struct {
unsigned
long pending;
struct
list_head entry; /* 将任务挂载到queue的挂载点 */
void
(*func)(void *); /* 任务方法 */
void
*data; /*
任务处理的数据*/
void
*wq_data; /*
work的属主 */
strut
timer_list timer; /* 任务延时处理定时器 */
};
当用户调用workqueue的初始化接口create_workqueue或者create_singlethread_workqueue对workqueue队列进行初始化时,内核就开始为用户分配一个workqueue对象,并且将其链到一个全局的workqueue队列中。然后Linux根据当前CPU的情况,为workqueue对象分配与CPU个数相同的cpu_workqueue_struct对象,每个cpu_workqueue_struct对象都会存在一条任务队列。紧接着,Linux为每个cpu_workqueue_struct对象分配一个内核thread,即内核daemon去处理每个队列中的任务。至此,用户调用初始化接口将workqueue初始化完毕,返回workqueue的指针。
在初始化workqueue过程中,内核需要初始化内核线程,注册的内核线程工作比较简单,就是不断的扫描对应cpu_workqueue_struct中的任务队列,从中获取一个有效任务,然后执行该任务。所以如果任务队列为空,那么内核daemon就在cpu_workqueue_struct中的等待队列上睡眠,直到有人唤醒daemon去处理任务队列。
Workqueue初始化完毕之后,将任务运行的上下文环境构建起来了,但是具体还没有可执行的任务,所以,需要定义具体的work_struct对象。然后将work_struct加入到任务队列中,Linux会唤醒daemon去处理任务。
上述描述的workqueue内核实现原理可以描述如下:
在Workqueue机制中,提供了一个系统默认的workqueue队列——keventd_wq,这个队列是Linux系统在初始化的时候就创建的。用户可以直接初始化一个work_struct对象,然后在该队列中进行调度,使用更加方便。
Workqueue编程接口
序号
接口函数
说明
1
create_workqueue
用于创建一个workqueue队列,为系统中的每个CPU都创建一个内核线程。输入参数:
@name:workqueue的名称
2
create_singlethread_workqueue
用于创建workqueue,只创建一个内核线程。输入参数:
@name:workqueue名称
3
destroy_workqueue
释放workqueue队列。输入参数:
@ workqueue_struct:需要释放的workqueue队列指针
4
schedule_work
调度执行一个具体的任务,执行的任务将会被挂入Linux系统提供的workqueue——keventd_wq输入参数:
@ work_struct:具体任务对象指针
5
schedule_delayed_work
延迟一定时间去执行一个具体的任务,功能与schedule_work类似,多了一个延迟时间,输入参数:
@work_struct:具体任务对象指针
@delay:延迟时间
6
queue_work
调度执行一个指定workqueue中的任务。输入参数:
@ workqueue_struct:指定的workqueue指针
@work_struct:具体任务对象指针
7
queue_delayed_work
延迟调度执行一个指定workqueue中的任务,功能与queue_work类似,输入参数多了一个delay。
workqueue 工作队列
workqueue,中文称其为工作队列,是一个用于创建内核线程的接口,通过它创建的内核线程来执行内核其他模块排列到队列里的工作,创建的内核线程被称为工作者线程。要理解工作队列的实现,重点在于理解相关的三个数据结构的含义及关系。
- /*
- * The externally visible workqueue abstraction is an array of
- * per-CPU workqueues:
- */
- struct workqueue_struct {
- struct cpu_workqueue_struct *cpu_wq; /*工作者线程数组*/
- struct list_head list; /*连接工作队列类型的链表*/
- const char *name; /*工作者线程的名称*/
- int singlethread; /*是否创建新的工作者线程,0表示采用默认的工作者线程event/n*/
- int freezeable; /* Freeze threads during suspend */
- int rt;
- #ifdef CONFIG_LOCKDEP
- struct lockdep_map lockdep_map;
- #endif
- };
内核中默认的工作队列为:
- static struct workqueue_struct *keventd_wq __read_mostly;
其对应的工作者线程为:event/n 其中,n代表当前cpu中processor的个数。
2. 表示工作者线程的数据结构:struct cpu_workqueue_struct
- /*
- * The per-CPU workqueue (if single thread, we always use the first
- * possible cpu).
- */
- struct cpu_workqueue_struct {
- spinlock_t lock; /*因为工作者线程需要频繁的处理连接到其上的工作,所以需要枷锁保护*/
- struct list_head worklist;
- wait_queue_head_t more_work;
- struct work_struct *current_work; /*当前工作线程需要处理的工作*/
- struct workqueue_struct *wq; /*该工作者线程属于那种类型的工作者队列*/
- struct task_struct *thread; /*指向工作者线程的任务结构体*/
- } ____cacheline_aligned;
3. 表示工作的数据结构,即工作者线程处理的对象:struct work_struct
- struct work_struct {
- atomic_long_t data; /*工作处理函数func的参数*/
- #define WORK_STRUCT_PENDING 0 /* T if work item pending execution */
- #define WORK_STRUCT_STATIC 1 /* static initializer (debugobjects) */
- #define WORK_STRUCT_FLAG_MASK (3UL)
- #define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)
- struct list_head entry; /*连接工作的指针*/
- work_func_t func; /*工作处理函数*/
- #ifdef CONFIG_LOCKDEP
- struct lockdep_map lockdep_map;
- #endif
- };
再分析了以上三个对象后,重点掌握三者之间的关系。工作队列类型,工作者线程以及工作三个数据对象之间的关系如图所示。
workqueue的执行非常简单,即在每次运行工作者线程的时候,去遍历工作者线程对应的工作链表上的工作,逐一进行处理即可,从这里我们也可以猜到,工作队列是没有优先级的,基本按照FIFO的方式进行处理。
tasklet and workqueue example
#include<linux/kernel.h>#include<linux/init.h>#include<linux/module.h>#include<linux/interrupt.h>static struct tasklet_struct my_tasklet;static void tasklet_handler(unsigned long data){ printk("<0>this is tasklet working !!!!!!!!!!!!1%ld",data);}struct workqueue_struct* my_queue=NULL;static struct work_struct work;static void work_handler(struct work_struct *data){ printk("<0>this is work working !!!!!!!!!!!\n");}static int work_init(void){ tasklet_init(&my_tasklet,tasklet_handler,200); tasklet_schedule(&my_tasklet); my_queue=create_workqueue("queue"); INIT_WORK(&work,work_handler); //schedule_delayed_work(&work,10); queue_work(my_queue,&work); return 0;}static void work_exit(void){ tasklet_kill(&my_tasklet); destroy_workqueue(my_queue);}MODULE_LICENSE("GPL");MODULE_AUTHOR("ANYWIND");module_init(work_init);module_exit(work_exit);
tasklet原理和源码分析
原文地址:tasklet原理和源码分析 作者:liujunwei1234
- struct tasklet_struct
- {
- struct tasklet_struct *next; /*构成tasklet的链表*/
- unsigned long state; /*tasklet的状态*/
- atomic_t count; /*使能计数*/
- void (*func)(unsigned long); /*tasklet处理函数*/
- unsigned long data; /*处理函数的参数*/
- };
每个成员的含义参见注释。这里详细介绍下state成员,它表示tasklet的状态,当前Linux内核中支持两种状态:
(1)TASKLET_STATE_SCHED:表示tasklet已经准备好了,只要被选择到就可以运行了,类似于进程的就绪态。
(2)TASKLET_STATE_RUN:表示tasklet正在执行,类似于进程的RUN态,需要注意的是,TASKLET_STATE_RUN态仅仅在Linux支持SMP的情况下才有定义,因为在UP上处理器能够明确知道当前执行的tasklet是哪个。
另外,只有count=0的时候,tasklet才能被执行,因为对某个tasklet,内核或驱动开发者希望能够显示的使能/禁止其执行,所以才在tasklet结构体中增加了此成员。可以使得count成员变化的接口如下:
- static inline void tasklet_disable_nosync(struct tasklet_struct *t)
- {
- atomic_inc(&t->count);
- smp_mb__after_atomic_inc();
- }
- static inline void tasklet_disable(struct tasklet_struct *t)
- {
- tasklet_disable_nosync(t);
- tasklet_unlock_wait(t);
- smp_mb();
- }
- static inline void tasklet_enable(struct tasklet_struct *t)
- {
- smp_mb__before_atomic_dec();
- atomic_dec(&t->count);
- }
- static inline void tasklet_hi_enable(struct tasklet_struct *t)
- {
- smp_mb__before_atomic_dec();
- atomic_dec(&t->count);
- }
从这些接口的名称也可以看出count成员的主要功能,而不能简单的将count理解为引用计数,容易操作误解,所以理解为使能计数更贴切些!
二、tasklet声明和定义
1、静态和动态创建tasklet的两种方式
(1)静态创建方式
- #define DECLARE_TASKLET(name, func, data) \
- struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
- #define DECLARE_TASKLET_DISABLED(name, func, data) \
- struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }
从代码中可以清楚的看到,两个宏的区别在于tasklet的count不同,前面已经讲过count表示tasklet的使能计数,只有当count=0,表示tasklet创建后处于使能状态;count=1,表示tasklet创建后处于禁止状态。
(2)动态创建
- struct tasklet_struct t;
- void (*func)(unsigned long);
- tasklet_init(t,func,NULL);
2. tasklet处理函数
与softirq一样,tasklet不能睡眠和阻塞,因此在设计tasklet处理函数时必须格外小心,不能使用信号或其他引起阻塞的函数。另外,同一个tasklet不可能同时在两个processor上运行,如果两个不同的tasklet之间有数据共享,需要注意采用合适的锁机制。
三、tasklet的执行过程
Linux内核中采用两个每CPU变量来存储属于当前CPU的tasklet:
(1)tasklet_vec:普通的tasklet,即基于TASKLET_SOFTIRQ实现的tasklet
(2)tasklet_hi_vec:高优先级的tasklet,即基于HI_SOFTIRQ实现的tasklet
在Linux内核中通过显示的调用tasklet_schedule()和tasklet_hi_schedule()函数来实现tasklet的调度,然后将在之后的某个时间,该tasklet将会被执行。需要注意一点:tasklet一定是在调度它的CPU上执行。
- static inline void tasklet_schedule(struct tasklet_struct *t)
- {
- if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
- __tasklet_schedule(t);
- }
- extern void __tasklet_hi_schedule(struct tasklet_struct *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);
- }
- static inline int test_and_set_bit(int nr, volatile unsigned long *addr)
- {
- int oldbit;
- asm volatile(LOCK_PREFIX "bts %2,%1\n\t"
- "sbb %0,%0" : "=r" (oldbit), ADDR : "Ir" (nr) : "memory");
- return oldbit;
- }
这里首先介绍两个比较少用的汇编指令:(内联汇编的知识就不多讲了,不理解的同学可以专门去查阅这方面的知识)
(1)bts %2,%1
bts汇编指令的原型为:bts dest,src 表示将目的操作数dest的第src位赋值给CF,然后再职位dest的第src位。在这里2%表示是输入操作数nr,值为0。1%表示的是输出操作数ADDR,即&t->state。那么,这句指令的意思即是将t->state的第0位先赋值给CF,然后将t->state的第0位置1。如果之前t->state = 0,那么此时CF=0,如果此前CF指向的第0位为1,那么CF=1。
(2)sbb %0,%0
指令格式:sbb 操作对象1,操作对象2 功能:操作对象1=操作对象1-操作对象2-CF这里0%表示是输出操作数oldbit,即oldbit = oldbit - oldbit - CF,因此返回值只于CF有关,如果为0,返回值就为0,CF不为0,返回值就不为0。
根据bts的讲解,当CF=t->state的第nr位的值,即如果原来这个tasklet被设置为TASKLET_STATE_SCHED状态,那么CF=1,那么test_and_set_bit(TASKLET_STATE_SCHED, &t->state)的返回值就非0,即不能调度执行,如果原来这个tasklet没有被设置为TASKLET_STATE_SCHED状态,那么CF=0,那么那么test_and_set_bit(TASKLET_STATE_SCHED, &t->state)的返回值就为0,这个tasklet就可以调度执行。(不知道有没有讲清楚???)
下面来分析函数__tasklet_schedule()
- void __tasklet_schedule(struct tasklet_struct *t)
- {
- unsigned long flags;
- local_irq_save(flags);
- t->next = NULL;
- *__get_cpu_var(tasklet_vec).tail = t;
- __get_cpu_var(tasklet_vec).tail = &(t->next);
- raise_softirq_irqoff(TASKLET_SOFTIRQ);
- local_irq_restore(flags);
- }
- /*
- * This function must run with irqs
- */
- inline void raise_softirq_irqoff(unsigned int nr)
- {
- __raise_softirq_irqoff(nr);
- /*
- * If we're in an interrupt or softirq, we're done
- * (this also catches softirq-disabled code). We will
- * actually run the softirq once we return from
- * the irq or softirq.
- *
- * Otherwise we wake up ksoftirqd to make sure we
- * schedule the softirq soon.
- */
- if (!in_interrupt())
- wakeup_softirqd();
- }
- #define __raise_softirq_irqoff(nr) do { or_softirq_pending(1UL << (nr)); } while (0)
首先在__raise_softirq_irqoff(nr)中激活这个softirq,然后,激活软中断处理内核线程。其中in_interrupt在上一篇文章中已经详细分析过了。(in_interrupt判断当有硬件中断嵌套,其他软中断以及不可屏蔽中断的情况下,返回非0值)
再往下分析就是softirq相关的知识了。
四、用于实现tasklet的softirq的处理函数
- void __init softirq_init(void)
- {
- int cpu;
- for_each_possible_cpu(cpu) {
- int i;
- 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;
- for (i = 0; i < NR_SOFTIRQS; i++)
- INIT_LIST_HEAD(&per_cpu(softirq_work_list[i], cpu));
- }
- register_hotcpu_notifier(&remote_softirq_cpu_notifier);
- open_softirq(TASKLET_SOFTIRQ, tasklet_action);
- open_softirq(HI_SOFTIRQ, tasklet_hi_action);
- }
- static void tasklet_action(struct softirq_action *a)
- {
- struct tasklet_struct *list;
- local_irq_disable();
- list = __get_cpu_var(tasklet_vec).head;
- __get_cpu_var(tasklet_vec).head = NULL;
- __get_cpu_var(tasklet_vec).tail = &__get_cpu_var(tasklet_vec).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;
- *__get_cpu_var(tasklet_vec).tail = t;
- __get_cpu_var(tasklet_vec).tail = &(t->next);
- __raise_softirq_irqoff(TASKLET_SOFTIRQ);
- local_irq_enable();
- }
- }
原文地址:softirq原理以及源码分析 作者:liujunwei1234
- /* 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;
- 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
- };
- /*
- * Exit an interrupt context. Process softirqs if needed and possible:
- */
- void irq_exit(void)
- {
- account_system_vtime(current);
- trace_hardirq_exit();
- sub_preempt_count(IRQ_EXIT_OFFSET);
- if (!in_interrupt() && local_softirq_pending())
- invoke_softirq();
- rcu_irq_exit();
- #ifdef CONFIG_NO_HZ
- /* Make sure that timer wheel updates are propagated */
- if (idle_cpu(smp_processor_id()) && !in_interrupt() && !need_resched())
- tick_nohz_stop_sched_tick(0);
- #endif
- preempt_enable_no_resched();
- }
- static int run_ksoftirqd(void * __bind_cpu)
- {
- ... ...
- while (local_softirq_pending()) {
- /* Preempt disable stops cpu going offline.
- If already offline, we'll be on wrong CPU:
- don't process */
- if (cpu_is_offline((long)__bind_cpu))
- goto wait_to_die;
- do_softirq();
- preempt_enable_no_resched();
- cond_resched();
- preempt_disable();
- rcu_note_context_switch((long)__bind_cpu);
- }
- preempt_enable();
- set_current_state(TASK_INTERRUPTIBLE);
- }
- __set_current_state(TASK_RUNNING);
- return 0;
- ... ...
- }
- int netif_rx_ni(struct sk_buff *skb)
- {
- int err;
- preempt_disable();
- err = netif_rx(skb);
- if (local_softirq_pending())
- do_softirq();
- preempt_enable();
- return err;
- }
- asmlinkage void do_softirq(void)
- {
- unsigned long flags;
- struct thread_info *curctx;
- union irq_ctx *irqctx;
- u32 *isp;
- if (in_interrupt()) /*这个函数需要仔细理解???*/
- return;
- local_irq_save(flags);
- if (local_softirq_pending()) {
- curctx = current_thread_info();
- irqctx = __get_cpu_var(softirq_ctx);
- irqctx->tinfo.task = curctx->task;
- irqctx->tinfo.previous_esp = current_stack_pointer;
- /* build the stack frame on the softirq stack */
- isp = (u32 *) ((char *)irqctx + sizeof(*irqctx));
- call_on_stack(__do_softirq, isp);
- /*
- * Shouldnt happen, we returned above if in_interrupt():
- */
- WARN_ON_ONCE(softirq_count());
- }
- local_irq_restore(flags);
- }
- #define hardirq_count() (preempt_count() & HARDIRQ_MASK)
- #define softirq_count() (preempt_count() & SOFTIRQ_MASK)
- #define irq_count() (preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK \
- | NMI_MASK))
- /*
- * Are we doing bottom half or hardware interrupt processing?
- * Are we in a softirq context? Interrupt context?
- */
- #define in_irq() (hardirq_count())
- #define in_softirq() (softirq_count())
- #define in_interrupt() (irq_count())
- /*
- * Are we in NMI context?
- */
- #define in_nmi() (preempt_count() & NMI_MASK)
- /*
- * We put the hardirq and softirq counter into the preemption
- * counter. The bitmask has the following meaning:
- *
- * - bits 0-7 are the preemption count (max preemption depth: 256)
- * - bits 8-15 are the softirq count (max # of softirqs: 256)
- *
- * The hardirq count can in theory reach the same as NR_IRQS.
- * In reality, the number of nested IRQS is limited to the stack
- * size as well. For archs with over 1000 IRQS it is not practical
- * to expect that they will all nest. We give a max of 10 bits for
- * hardirq nesting. An arch may choose to give less than 10 bits.
- * m68k expects it to be 8.
- *
- * - bits 16-25 are the hardirq count (max # of nested hardirqs: 1024)
- * - bit 26 is the NMI_MASK
- * - bit 28 is the PREEMPT_ACTIVE flag
- *
- * PREEMPT_MASK: 0x000000ff
- * SOFTIRQ_MASK: 0x0000ff00
- * HARDIRQ_MASK: 0x03ff0000
- * NMI_MASK: 0x04000000
- */
- #define in_interrupt() (irq_count())
- #define irq_count() (preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK \
- | NMI_MASK))
- __local_bh_disable((unsigned long)__builtin_return_address(0));
- static inline void __local_bh_disable(unsigned long ip)
- {
- add_preempt_count(SOFTIRQ_OFFSET);
- barrier();
- }
- # define add_preempt_count(val) do { preempt_count() += (val); } while (0)
- asmlinkage void __do_softirq(void)
- {
- struct softirq_action *h;
- __u32 pending;
- int max_restart = MAX_SOFTIRQ_RESTART; /*不启动ksoftirqd之前,最大的处理softirq的次数,经验值*/
- int cpu;
- /*取得当前被挂起的softirq,同时这里也解释了为什么Linux内核最多支持32个softirq,因为pending只有32bit*/
- pending = local_softirq_pending();
- account_system_vtime(current);
- __local_bh_disable((unsigned long)__builtin_return_address(0));
- lockdep_softirq_enter();
- cpu = smp_processor_id();
- restart:
- /* Reset the pending bitmask before enabling irqs */
- set_softirq_pending(0);/*获取了pending的softirq之后,清空所有pending的softirq的标志*/
- local_irq_enable();
- h = softirq_vec;
- do {
- if (pending & 1) { /*从最低位开始,循环右移逐位处理pending的softirq*/
- int prev_count = preempt_count();
- kstat_incr_softirqs_this_cpu(h - softirq_vec);
- trace_softirq_entry(h, softirq_vec);
- h->action(h); /*执行softirq的处理函数*/
- trace_softirq_exit(h, softirq_vec);
- if (unlikely(prev_count != preempt_count())) {
- printk(KERN_ERR "huh, entered softirq %td %s %p"
- "with preempt_count %08x,"
- " exited with %08x?\n", h - softirq_vec,
- softirq_to_name[h - softirq_vec],
- h->action, prev_count, preempt_count());
- preempt_count() = prev_count;
- }
- rcu_bh_qs(cpu);
- }
- h++;
- pending >>= 1; /*循环右移*/
- } while (pending);
- local_irq_disable();
- pending = local_softirq_pending();
- if (pending && --max_restart) /*启动ksoftirqd的阈值*/
- goto restart;
- if (pending) /*启动ksoftirqd去处理softirq,此时说明pending的softirq比较多,比较频繁,上面的处理过程中,又不断有softirq被pending*/
- wakeup_softirqd();
- lockdep_softirq_exit();
- account_system_vtime(current);
- _local_bh_enable();
- }
- open_softirq(NET_TX_SOFTIRQ, net_tx_action);
- open_softirq(NET_RX_SOFTIRQ, net_rx_action);
- /* Called with irq disabled */
- static inline void ____napi_schedule(struct softnet_data *sd,
- struct napi_struct *napi)
- {
- list_add_tail(&napi->poll_list, &sd->poll_list);
- __raise_softirq_irqoff(NET_RX_SOFTIRQ);
- }
softirq/tasklet/workqueue的区别
原文地址:softirq/tasklet/workqueue的区别 作者:cainiao413
softirq和tasklet都属于软中断,tasklet是softirq的特殊实现;
workqueue是普通的工作队列。
1、softirq
软中断支持SMP,同一个softirq可以在不同的CPU上同时运行,softirq必须是可重入的。软中断是在编译期间静态分配的,它不像tasklet那样能被动态的注册或去除。kernel/softirq.c中定义了一个包含32个softirq_action结构体的数组。每个被注册的软中断都占据该数组的一项。因此最多可能有32个软中断。2.6版本的内核中定义了六个软中断:HI_SOFTIRQ、TIMER_SOFTIRQ、NET_TX_SOFTIRQ、NET_RX_SOFTIRQ、 SCSI_SOFTIRQ、TASKLET_SOFTIRQ。
一般情况下,在硬件中断处理程序后都会试图调用do_softirq()函数,每个CPU都是通过执行这个函数来执行软中断服务的。由于软中断不能进入硬中断部分,且同一个CPU上软中断的执行是串行的,即不允许嵌套,因此,do_softirq()函数一开始就检查当前CPU是否已经正出在中断服务中,如果是则 do_softirq()函数立即返回。这是由do_softirq()函数中的 if (in_interrupt()) return; 保证的。
2、tasklet
引入tasklet,最主要的是考虑支持SMP,提高SMP多个cpu的利用率;不同的tasklet可以在不同的cpu上运行。tasklet可以理解为softirq的派生,所以它的调度时机和软中断一样。对于内核中需要延迟执行的多数任务都可以用tasklet来完成,由于同类tasklet本身已经进行了同步保护,所以使用tasklet比软中断要简单的多,而且效率也不错。 tasklet把任务延迟到安全时间执行的一种方式,在中断期间运行,即使被调度多次,tasklet也只运行一次,不过tasklet可以在SMP系统上和其他不同的tasklet并行运行。在SMP系统上,tasklet还被确保在第一个调度它的CPU上运行,因为这样可以提供更好的高速缓存行为,从而提高性能。
与一般的软中断不同,某一段tasklet代码在某个时刻只能在一个CPU上运行,但不同的tasklet代码在同一时刻可以在多个CPU上并发地执行。 Kernel/softirq.c中用tasklet_trylock()宏试图对当前要执行的tasklet(由指针t所指向)进行加锁,如果加锁成功(当前没有任何其他CPU正在执行这个tasklet),则用原子读函数atomic_read()进一步判断count成员的值。如果count为0,说明这个tasklet是允许执行的。如果tasklet_trylock()宏加锁不成功,或者因为当前tasklet的count值非0而不允许执行时,我们必须将这个tasklet重新放回到当前CPU的tasklet队列中,以留待这个CPU下次服务软中断向量TASKLET_SOFTIRQ时再执行。为此进行这样几步操作:
(1)先关 CPU中断,以保证下面操作的原子性。
(2)把这个tasklet重新放回到当前CPU的tasklet队列的首部;
(3)调用 __cpu_raise_softirq()函数在当前CPU上再触发一次软中断请求TASKLET_SOFTIRQ;
(4)开中断。
软中断和tasklet都是运行在中断上下文中,它们与任一进程无关,没有支持的进程完成重新调度。所以软中断和tasklet不能睡眠、不能阻塞,它们的代码中不能含有导致睡眠的动作,如减少信号量、从用户空间拷贝数据或手工分配内存等。也正是由于它们运行在中断上下文中,所以它们在同一个CPU上的执行是串行的,这样就不利于实时多媒体任务的优先处理。
3、workqueue
什么情况下使用工作队列,什么情况下使用tasklet。如果推后执行的任务需要睡眠,那么就选择工作队列。如果推后执行的任务不需要睡眠,那么就选择tasklet。另外,如果需要用一个可以重新调度的实体来执行你的下半部处理,也应该使用工作队列。它是唯一能在进程上下文运行的下半部实现的机制,也只有它才可以睡眠。这意味着在需要获得大量的内存时、在需要获取信号量时,在需要执行阻塞式的I/O操作时,它都会非常有用。如果不需要用一个内核线程来推后执行工作,那么就考虑使用tasklet。
*******************************************************************************************************
当前的2.6版内核中,有三种可能的选择:softirq、tasklet和work queue。 tasklet基于softirq实现,所以两者很相近。work queue与它们完全不同,它靠内核线程实现。
1、softirq
软中断支持SMP,同一个softirq可以在不同的CPU上同时运行,softirq必须是可重入的。软中断是在编译期间静态分配的,它不像tasklet那样能被动态的注册或去除。kernel/softirq.c中定义了一个包含32个 softirq_action结构体的数组。每个被注册的软中断都占据该数组的一项。因此最多可能有32个软中断。2.6版本的内核中定义了六个软中断:HI_SOFTIRQ、TIMER_SOFTIRQ、NET_TX_SOFTIRQ、NET_RX_SOFTIRQ、SCSI_SOFTIRQ、 TASKLET_SOFTIRQ。
软中断的特性:
1).一个软中断不会抢占另外一个软中断。
2).唯一可以抢占软中断的是中断处理程序。
3).其他软中断(包括相同类型的)可以在其他的处理其上同时执行。
4).一个注册的软中断必须在被标记后才能执行。
5).软中断不可以自己休眠(即调用可阻塞的函数或sleep等)。
6).索引号小的软中断在索引号大的软中断之前执行。
2、tasklet
引入tasklet,最主要的是考虑支持SMP,提高SMP多个cpu的利用率;两个相同的tasklet决不会同时执行。tasklet可以理解为softirq的派生,所以它的调度时机和软中断一样。对于内核中需要延迟执行的多数任务都可以用tasklet来完成,由于同类tasklet本身已经进行了同步保护,所以使用tasklet比软中断要简单的多,而且效率也不错。tasklet把任务延迟到安全时间执行的一种方式,在中断期间运行,即使被调度多次,tasklet也只运行一次,不过tasklet可以在SMP系统上和其他不同的tasklet并行运行。在SMP系统上,tasklet还被确保在第一个调度它的CPU上运行,因为这样可以提供更好的高速缓存行为,从而提高性能。
tasklet的特性:.不允许两个两个相同类型的tasklet同时执行,即使在不同的处理器上。
3、work queue
如果推后执行的任务需要睡眠,那么就选择工作队列。另外,如果需要用一个可以重新调度的实体来执行你的下半部处理,也应该使用工作队列。它是唯一能在进程上下文运行的下半部实现的机制,也只有它才可以睡眠。这意味着在需要获得大量的内存时、在需要获取信号量时,在需要执行阻塞式的I/O操作时,它都会非常有用。work queue造成的开销最大,因为它要涉及到内核线程甚至是上下文切换。这并不是说work queue的低效,但每秒钟有数千次中断,就像网络子系统时常经历的那样,那么采用其他的机制可能更合适一些。 尽管如此,针对大部分情况工作队列都能提供足够的支持。
工作队列特性:
1).工作队列会在进程上下文中执行!
2).可以阻塞。(前两种机制是不可以阻塞的)
3).可以被重新调度。(前两种只可以被中断处理程序打断)
4).使用工作队列的两种形式:
1>缺省工作者线程(works threads)
2>自建的工作者线程
5).在工作队列和内核其他部分之间使用锁机制就像在其他的进程上下文一样。
6).默认允许响应中断。
7).默认不持有任何锁。
4、softirq和tasklet共同点
软中断和tasklet都是运行在中断上下文中,它们与任一进程无关,没有支持的进程完成重新调度。所以软中断和tasklet不能睡眠、不能阻塞,它们的代码中不能含有导致睡眠的动作,如减少信号量、从用户空间拷贝数据或手工分配内存等。也正是由于它们运行在中断上下文中,所以它们在同一个CPU上的执行是串行的,这样就不利于实时多媒体任务的优先处理。
tasklet source code
/* 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 excecution 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;/* 可以是 */atomic_t count;void (*func)(unsigned long);unsigned long data;};
state 可以是
{
TASKLET_STATE_SCHED,/* Tasklet is scheduled for execution */
TASKLET_STATE_RUN/* Tasklet is running (SMP only) */
};
state 防止多次调度
防止多CPU执行, 在中断期间运行,即使被调度多次,tasklet也只运行一次
count 防止多次运行
count 为0才可以执行,保证本cpu只执行一份, 两个相同的tasklet决不会同时执行。
static void tasklet_action(struct softirq_action *a)
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;*__get_cpu_var(tasklet_vec).tail = t;__get_cpu_var(tasklet_vec).tail = &(t->next);__raise_softirq_irqoff(TASKLET_SOFTIRQ);local_irq_enable();}
- softirq, tasklet, work_queue
- softIRQ tasklet work_queue
- softirq于tasklet
- softirq,tasklet,workqueue
- 关于softirq/tasklet/workqueue
- tasklet 与 softirq
- tasklet、wait_queue、completion、work_queue用法总结
- softirq/tasklet/workqueue的区别
- softirq/tasklet/workqueue的区别
- softirq/tasklet/workqueue的区别
- softirq/tasklet/workqueue的区别
- softirq/tasklet/workqueue的区别
- softirq/tasklet/workqueue的区别
- softirq/tasklet/workqueue的区别
- softirq/tasklet/workqueue的区别
- softirq/tasklet/workqueue的区别
- softirq/tasklet/workqueue的区别
- linux中断子系统 - softirq/tasklet
- C++字符串输入流的问题
- HDU 3187 HP Problem(欧拉函数)
- 恢复Cygwin快捷方式
- Linux命令总结
- 编译报错
- softIRQ tasklet work_queue
- USACO 3.2.1 Factorials
- 从零开始学GO语言(1)——hellow world
- BOOST 宏定义标记
- 简单01背包裸题——饭卡
- Android-Recovery Mode(recover模式详解)【1】
- linux下hadoop与hive的单机开发配置
- JS中如何定义全局变量
- confluence的安装&破解&汉化