软中断

软中断的分配时静态的(即在编译时定义),而tasklet的分配和初始化能够在执行时进行。

软中断(即便是同一种类型的软中断)能够并发地运行在多个CPU上。

因此,软中断是可重入函数并且必须明白地使用自旋锁保护其数据结构。

tasklet不必操心这些问题。由于内核对tasklet的运行进行了更加严格的控制。

同样类型的tasklet总是被串行运行。

换句话说就是:不能在两个CPU上同一时候执行同样类型的tasklet。可是,类型不同的tasklet能够在几个CPU上并发执行。

tasklet的串行化使tasklet函数不必是可重入的

软中断           下标    说明

HI_SOFTIRQ      0      处理高优先级的tasklet

TIMER_SOFTIRQ   1      和时钟中断相关的tasklet

NET_TX_SOFTIRQ  2      把数据包传送到网卡

NET_RX_SOFTIRQ  3      从网卡接收数据包

SCSI_SOFTIRQ    4      SCSI命令的后台中断处理

TASKLET_SOFTIRQ 5      处理常规tasklet

thread_info->preempt_count

preempt_count字段

位              描写叙述

0~7             抢占计数器(max value=255)

8~15            软中断计数器(max value=255)

16~27           硬中断计数器(max value=255)

28              PREEMPT_ACTIVE标志

第一个计数器记录显式禁用本地CPU内核抢占的次数。值等于0表示同意内核抢占。

第二个计数器表示可延迟函数被禁用的程度(值为0表示可延迟函数处于激活状态)。

第三个计数器表示在本地CPU上中断处理程序的嵌套数。

处理软中断

open_softirq()     函数处理软中断的初始化

raise_softirq()    函数用来激活软中断

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();
} void raise_softirq(unsigned int nr)
{
unsigned long flags; local_irq_save(flags);
raise_softirq_irqoff(nr);
local_irq_restore(flags);
}

raise_softirq()函数运行以下的操作:

1、运行local_irq_save宏以保存eflags寄存器IF标志状态并禁用本地CPU上的中断。

2、把软中断标记为挂起状态。这是通过设置本地CPU的软中断掩码中与下标nr相关的位来实现的

3、假设in_interrupt()产生为1的值,则跳转到5。

这样的情况说明:要么已经在中断上下文中调用了raise_softirq()。要么当前禁用了软中断。

4、否则,就在须要的时候去调用wakeup_softirqd()以唤醒本地CPU的ksoftirqd内核线程。

5、运行local_irq_restore宏。恢复在第1步保存的IF标志的状态。

在下面几种情况周期性地检查是否有软中断要运行:

a、当内核调用local_bh_enable()函数激活本地CPU的软中断时

b、当do_IRQ()完毕了I/O中断的处理时或调用irq_exit()宏时

c、假设系统使用I/O APIC。则当smp_apic_timer_interrupt()函数处理完本地定时器中断时

d、在多处理器系统中。当CPU处理完被CALL_FUNCTION_VECTOR处理器间中断所触发的函数时

e、当一个特殊的ksoftirqd/n内核线程被唤醒时

ksoftirqd内核线程

每一个ksoftirqd/n内核线程都执行ksoftirqd()函数,该函数实际上执行下列的循环:

static int ksoftirqd(void * __bind_cpu)
{
set_user_nice(current, 19);
current->flags |= PF_NOFREEZE; set_current_state(TASK_INTERRUPTIBLE); while (!kthread_should_stop()) {
if (!local_softirq_pending())
schedule(); __set_current_state(TASK_RUNNING); while (local_softirq_pending()) {
/* Preempt disable stops cpu going offline.
If already offline, we'll be on wrong CPU:
don't process */
preempt_disable();
if (cpu_is_offline((long)__bind_cpu))
goto wait_to_die;
do_softirq();
preempt_enable();
cond_resched();
} set_current_state(TASK_INTERRUPTIBLE);
}
__set_current_state(TASK_RUNNING);
return 0; wait_to_die:
preempt_enable();
/* Wait for kthread_stop */
set_current_state(TASK_INTERRUPTIBLE);
while (!kthread_should_stop()) {
schedule();
set_current_state(TASK_INTERRUPTIBLE);
}
__set_current_state(TASK_RUNNING);
return 0;
}

当内核线程被唤醒时,就检查local_softirq_pending()中的软中断位掩码并在必要时调用do_softirq()。假设没有挂起的软中断。函数把当前进程状态置为TASK_INTERRUPTIBLE,随后。假设当前进程须要(当前thread_info的TIF_NEED_RESCHED标志被设置)就调用con_resched()函数来实现进程切换。

tasklet

tasklet是I/O驱动程序中实现可延迟函数的首选方法。

tasklet建立在两个叫做HI_SOFTIRQ和TASKLET_SOFTIRQ的软中断之上。

几个tasklet能够与同一个软中断相关联,每一个tasklet运行自己的函数。

struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
struct tasklet_head
{
struct tasklet_struct *head;
struct tasklet_struct **tail;
};
static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);
static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);

tasklet描写叙述符的字段

next     指向链表中下一个描写叙述符的指针

state    tasklet的状态

count    锁计数器

func     指向tasklet函数的指针

data     一个无符号长整数。能够由tasklet函数使用

tasklet描写叙述的state字段含有两个标志:

TASKLET_STATE_SCHED

该标志被设置时,表示tasklet是挂起的。也意味着tasklet描写叙述符被插入到tasklet_vec和tasklet_hi_vec数组的当中一个链表

TASKLET_STATE_RUN

该标志被设置时。表示tasklet正在被执行;在单处理器系统上不使用这个标志,由于没有必要检查特定的tasklet是否在执行

为了激活tasklet,依据tasklet的优先级,调用tasklet_schedule()或tasklet_hi_schedule()函数。

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();
} 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);
}
static inline void tasklet_schedule(struct tasklet_struct *t)
{
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
__tasklet_schedule(t);
}

这两个函数很类似,当中每一个运行下列操作:

1、检查TASKLET_STATE_SCHED标志;假设设置则返回

2、调用local_irq_save保存IF标志的状态并禁用本地中断

3、在tasklet_vec或者tasklet_hi_vec指向的链表的起始处添加tasklet描写叙述符

4、调用raise_softirq_irqoff()激活TASKLET_SOFTIRQ或HI_SOFTIRQ类型的软中断

5、调用local_irq_restore恢复IF标志的状态

工作队列

可延迟函数和工作队列很相似。它们的差别在于:可延迟函数执行在中断上下文,而工作队列中的函数执行在进程上下文中。执行可堵塞函数(比如:须要訪问磁盘数据块的函数)的唯一方式是在进程上下文中执行。由于,在中断上下文中不可能发生进程切换。可延迟函数和工作队列中的函数都不能訪问进程的用户态地址空间。

struct cpu_workqueue_struct {

	spinlock_t lock;

	long remove_sequence;	/* Least-recently added (next to run) */
long insert_sequence; /* Next to add */ struct list_head worklist;
wait_queue_head_t more_work;
wait_queue_head_t work_done; struct workqueue_struct *wq;
task_t *thread; int run_depth; /* Detect run_workqueue() recursion depth */
} ____cacheline_aligned; /*
* The externally visible workqueue abstraction is an array of
* per-CPU workqueues:
*/
struct workqueue_struct {
struct cpu_workqueue_struct cpu_wq[NR_CPUS];
const char *name;
struct list_head list; /* Empty if single thread */
};

cpu_workqueue_struct类型的描写叙述符,字段描写叙述例如以下:

lock             保护该数据结构的自旋锁

remove_sequence  flush_workqueue()使用的序列号

insert_sequence  flush_workqueue()使用的序列号

worklist         挂起链表的头节点

more_work        等待队列。当中的工作者线程因等待很多其它的工作而处于睡眠状态

work_done        等待队列,当中的进程因为等待工作队列被刷新而处于睡眠状态

wq               指向workqueue_struct结构的指针,当中包括该描写叙述符

thread           指向结构中工作线程的进程描写叙述符指针

run_depth        run_workqueue()当前的运行深度

struct work_struct {
unsigned long pending;
struct list_head entry;
void (*func)(void *);
void *data;
void *wq_data;
struct timer_list timer;
};

pending       假设函数已经在工作队列链表中。该字段值设为1,否则设为0

entry         指向挂起函数链表前一个或后一个元素的指针

func          挂起函数的地址

data          传递给挂起函数的參数,是一个指针

wq_data       一般是指向cpu_workqueue_struct描写叙述符的父节点的指针

timer         用于延迟挂起函数运行的软定时器

工作队列函数

create_workqueue()  函数接收一个字符串作为參数,返回新创建工作队列的workqueue_struct描写叙述符的地址。该函数还创建n个工作者线程,并根                     据传递给函数的字符串为工作者线程命名,如foo/0,foo/1等等

destory_workqueue() 函数撤销工作队列

queue_work()        把函数插入工作队列,该函数主要运行以下步骤:

1、检查要插入的函数是否已经在工作队列中,假设是就结束

2、把work_struct描写叙述符加到工作队列链表中。然后把work->pending置为1

3、假设工作者线程在本地CPU的cpu_workqueue_struct描写叙述符的more_work等待队列上睡眠。该函数唤醒这个线程

每一个工作线程在worker_thread()函数内部不断地运行循环操作,因而。线程在绝大多数时间里处于睡眠状态并等待某些工作被插入队列。

工作线程一旦被唤醒就调用run_workqueue()函数,该函数从工作者线程的工作队列链表中删除全部work_struct描写叙述符并运行对应的挂起函数。

因为工作队列函数能够堵塞。因此,能够让工作者线程睡眠,甚至能够让它迁移到还有一个CPU上恢复运行

flush_workqueue()函数接收workqueue_struct描写叙述符的地址,而且在工作队列中的全部挂起函数结束之前使调用进程一直处于堵塞状态。

软中断&tasklet&工作队列的更多相关文章

  1. Linux内核中的软中断、tasklet和工作队列具体解释

    [TOC] 本文基于Linux2.6.32内核版本号. 引言 软中断.tasklet和工作队列并非Linux内核中一直存在的机制,而是由更早版本号的内核中的"下半部"(bottom ...

  2. Linux软中断、tasklet和工作队列

    Linux内核中的软中断.tasklet和工作队列详解 引言 软中断.tasklet和工作队列并不是Linux内核中一直存在的机制,而是由更早版本的内核中的“下半部”(bottom half)演变而来 ...

  3. [Linux内核]软中断、tasklet、工作队列

    转自:http://www.cnblogs.com/li-hao/archive/2012/01/12/2321084.html 软中断.tasklet和工作队列并不是Linux内核中一直存在的机制, ...

  4. Linux中断管理 (2)软中断和tasklet

    目录: <Linux中断管理> <Linux中断管理 (1)Linux中断管理机制> <Linux中断管理 (2)软中断和tasklet> <Linux中断管 ...

  5. linux内核--软中断与tasklet

    硬件中断通常都需要在最短的时间内执行完毕,如果将所有硬件中断相关的处理都放在硬件中断处理程序中,那么就达不到这个目的. 通过linux提供的软中断和tasklet,可以将硬件中断处理程序中可以延迟处理 ...

  6. 软中断和tasklet介绍

    今天看了下tasklet,重点分析了其和软中断的关系,特此记录 关于软中断,在之前的中断文章中已经有所介绍,这里就不多说了,只是说明下,系统中默认支持32种软中断,而实际上系统定义的软中断仅有以下几种 ...

  7. 内核软中断之tasklet机制

    1. 软中断IRQ简介 软中断(SoftIRQ)是内核提供的一种基于中断的延时机制, Linux内核定义的软中断有以下几种: enum { HI_SOFTIRQ=0, /*高优先级的tasklet*/ ...

  8. Linux中断分层--软中断和tasklet

    1. Linux中断分层 (1)上半部:当中断发生时,它进行相应的硬件读写,并“登记”该中断.通常由中断处理程序充当上半部.(一般情况下,上半部不可被打断) (2)下半部:在系统空闲的时候,对上半部“ ...

  9. tasklet和工作队列

    tasklet机制和工作队列 http://blog.chinaunix.net/uid-28236237-id-3450753.html tasklet原理 http://www.kuqin.com ...

随机推荐

  1. 关于size_t与size_type

    整理自关于size_t与size_type 问题起源于这样一段代码: #include <algorithm> #include <stdio.h> int main() { ...

  2. Hadoop新版本中map任务待处理split大小的计算方法

    1. split大小的计算公式 minSize=max{minSplitSize,mapred.min.split.size} (minSplitSize大小默认为1B) maxSize=mapred ...

  3. linux 性能分析常规逻辑和手段总结

    一. 追查cpu占用较高的进程(线程)  1 . 如何查找出当前系统中占用cpu或者内存最高的进程? ps aux |sort -rn -k 3 |head -n3 查找出当前系统中cpu资源占用前三 ...

  4. 李洪强漫谈iOS开发[C语言-018]-scanf函数

  5. [jobdu]二进制中1的个数

    做法是n&(n-1).据说还有变态的查表法:http://www.cnblogs.com/graphics/archive/2010/06/21/1752421.html.最后,居然必须用sc ...

  6. ANDROID_MARS学习笔记_S01原始版_017_绑定SERVICE

    一.流程 1.编写service,重写onBind(Intent intent),返回自定义的Binder 2.自写义Binder,提供一个可访问的方法,以传递数据 3.点击界面按钮会开启servic ...

  7. QStringRef可以提高性能,下次注意使用;QPair方便了语法,函数可以一次返回多个返回值,方便使用

    而且QPair从此与各种QMap容器联系了起来,会非常好用.甚至QList也可以成双成对的使用元素了: QList<QPair<int, double> > list;list ...

  8. 创建安卓app的30个经验教训

    在添加任何第三方party之前,请三思:这真的是一个成熟的项目吗? 如果一个东西用户看不到,就不要绘制它! 除非真的需要,否则别使用数据库: 达到65k方法数限制来的非常快,真的,非常快!不过 mul ...

  9. Android 性能优化之使用MAT分析内存泄露问题

    我们平常在开发Android应用程序的时候,稍有不慎就有可能产生OOM,虽然JAVA有垃圾回收机,但也不能杜绝内存泄露,内存溢出等问题,随着科技的进步,移动设备的内存也越来越大了,但由于Android ...

  10. 如何查看自己运行ubuntu是32位还是64位

    当安装ubuntu在pc上,不推荐在32位pc安装64位操作系统,64位pc安装32位操作系统 方法/步骤 按ctrl+shift+t 快捷键,打开终端,输入sudo uname --m ,按下ent ...