您的位置 首页 开关

Linux中止(interrupt)子系统之一:软件中止(softIRQ)

Linux中断(interrupt)子系统之一:软件中断(softIRQ)-软件中断(softIRQ)是内核提供的一种延迟执行机制,它完全由软件触发,虽然说是延迟机制,实际上,在大多数情况下,它与普通进程相比,能得到更快的响应时间。软中断也是其他一些内核机制的基础,比如tasklet,高分辨率timer等。

软件中止(softIRQ)是内核供给的一种推迟履行机制,它完全由软件触发,尽管说是推迟机制,实际上,在大多数状况下,它与一般进程比较,能得到更快的呼应时刻。软中止也是其他一些内核机制的根底,比方tasklet,高分辨率TImer等。

1.  软件中止的数据结构

1.1  struct sofTIrq_acTIon

内核用sofTIrq_action结构办理软件中止的注册和激活等操作,它的界说如下:

[cpp] view plain copy

struct softirq_action  

{  

void    (*action)(struct softirq_action *);  

};  

十分简略,只需一个用于回调的函数指针。软件中止的资源是有限的,内核现在只完成了10种类型的软件中止,它们是:

[cpp] view plain copy

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  

};  

内核的开发者们不主张咱们私行增加软件中止的数量,假如需求新的软件中止,尽或许把它们完成为根据软件中止的tasklet办法。与上面的枚举值相对应,内核界说了一个softirq_action的结构数组,每种软中止对应数组中的一项:

[cpp] view plain copy

static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;  

1.2  irq_cpustat_t

多个软中止能够一起在多个cpu运转,就算是同一种软中止,也有或许一起在多个cpu上运转。内核为每个cpu都办理着一个待决软中止变量(pending),它便是irq_cpustat_t:

[cpp] view plain copy

typedef struct {  

unsigned int __softirq_pending;  

} ____cacheline_aligned irq_cpustat_t;  

[cpp] view plain copy

irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned;  

__softirq_pending字段中的每一个bit,对应着某一个软中止,某个bit被置位,阐明有相应的软中止等候处理。

1.3  软中止的看护进程ksoftirqd

在cpu的热插拔阶段,内核为每个cpu创建了一个用于履行软件中止的看护进程ksoftirqd,一起界说了一个per_cpu变量用于保存每个看护进程的task_struct结构指针:

[cpp] view plain copy

DEFINE_PER_CPU(struct task_struct *, ksoftirqd);  

大多数状况下,软中止都会在irq_exit阶段被履行,在irq_exit阶段没有处理完的软中止才有或许会在看护进程中履行。

2.  触发软中止

要触发一个软中止,只需调用api:raise_softirq即可,它的完成很简略,先是封闭本地cpu中止,然后调用:raise_softirq_irqoff

[cpp] view plain copy

void raise_softirq(unsigned int nr)  

{  

unsigned long flags;  

local_irq_save(flags);  

raise_softirq_irqoff(nr);  

local_irq_restore(flags);  

}  

再看看raise_softirq_irqoff:

[cpp] view plain copy

inline void raise_softirq_irqoff(unsigned int nr)  

{  

__raise_softirq_irqoff(nr);  

……  

if (!in_interrupt())  

wakeup_softirqd();  

}  

先是经过__raise_softirq_irqoff设置cpu的软中止pending标志位(irq_stat[NR_CPUS] ),然后经过in_interrupt判别现在是否在中止上下文中,或许软中止是否被制止,假如都不建立,则唤醒软中止的看护进程,在看护进程中履行软中止的回调函数。不然什么也不做,软中止将会在中止的退出阶段被履行。

3.  软中止的履行

根据上面所说,软中止的履行既能够看护进程中履行,也能够在中止的退出阶段履行。实际上,软中止更多的是在中止的退出阶段履行(irq_exit),以便到达更快的呼应,参加看护进程机制,仅仅忧虑一旦有很多的软中止等候履行,会使得内核过长地留在中止上下文中。

3.1  在irq_exit中履行

看看irq_exit的部分:

[cpp] view plain copy

void irq_exit(void)  

{  

……  

sub_preempt_count(IRQ_EXIT_OFFSET);  

if (!in_interrupt() && local_softirq_pending())  

invoke_softirq();  

……  

}  

假如中止产生嵌套,in_interrupt()确保了只需在最外层的中止的irq_exit阶段,invoke_interrupt才会被调用,当然,local_softirq_pending也会完成判别当时cpu有无待决的软中止。代码终究会进入__do_softirq中,内核会确保调用__do_softirq时,本地cpu的中止处于封闭状况,进入__do_softirq:

[cpp] view plain copy

asmlinkage void __do_softirq(void)  

{  

……  

pending = local_softirq_pending();  

__local_bh_disable((unsigned long)__builtin_return_address(0),  

SOFTIRQ_OFFSET);  

restart:  

/* Reset the pending bitmask before enabling irqs */  

set_softirq_pending(0);  

local_irq_enable();  

h = softirq_vec;  

do {  

if (pending & 1) {  

……  

trace_softirq_entry(vec_nr);  

h->action(h);  

trace_softirq_exit(vec_nr);  

……  

}  

h++;  

pending >>= 1;  

} while (pending);  

local_irq_disable();  

pending = local_softirq_pending();  

if (pending && –max_restart)  

goto restart;  

if (pending)  

wakeup_softirqd();  

lockdep_softirq_exit();  

__local_bh_enable(SOFTIRQ_OFFSET);  

}  

首要取出pending的状况;

制止软中止,首要是为了避免和软中止看护进程产生竞赛;

铲除一切的软中止待决标志;

翻开本地cpu中止;

循环履行待决软中止的回调函数;

假如循环结束,发现新的软中止被触发,则从头启动循环,直到以下条件满意,才退出:

没有新的软中止等候履行;

循环现已到达最大的循环次数MAX_SOFTIRQ_RESTART,现在的设定值时10次;

假如经过MAX_SOFTIRQ_RESTART次循环后还未处理完,则激活看护进程,处理剩余的软中止;

推出前康复软中止;

3.2  在ksoftirqd进程中履行

从前面几节的评论咱们能够看出,软中止也或许由ksoftirqd看护进程履行,这要产生在以下两种状况下:

在irq_exit中履行软中止,可是在经过MAX_SOFTIRQ_RESTART次循环后,软中止还未处理完,这种状况尽管很少产生,但毕竟有或许;

内核的其它代码自动调用raise_softirq,而这时正好不是在中止上下文中,看护进程将被唤醒;

看护进程终究也会调用__do_softirq履行软中止的回调,详细的代码坐落run_ksoftirqd函数中,内核会封闭抢占的状况下履行__do_softirq,详细的进程这儿不做评论。

4.  tasklet

由于内核现已界说好了10种软中止类型,并且不主张咱们自行增加额定的软中止,所以对软中止的完成办法,咱们首要是做一个简略的了解,关于驱动程序的开发者来说,无需完成自己的软中止。可是,关于某些状况下,咱们不期望一些操作直接在中止的handler中履行,可是又期望在稍后的时刻里得到快速地处理,这就需求运用tasklet机制。 tasklet是建立在软中止上的一种推迟履行机制,它的完成根据TASKLET_SOFTIRQ和HI_SOFTIRQ这两个软中止类型。

4.1  tasklet_struct        

在软中止的初始化函数softirq_init的最终,内核注册了TASKLET_SOFTIRQ和HI_SOFTIRQ这两个软中止:

[cpp] view plain copy

void __init softirq_init(void)  

{  

……  

open_softirq(TASKLET_SOFTIRQ, tasklet_action);  

open_softirq(HI_SOFTIRQ, tasklet_hi_action);  

}  

内核用一个tasklet_struct来表明一个tasklet,它的界说如下:

[cpp] view plain copy

struct tasklet_struct  

{  

struct tasklet_struct *next;  

unsigned long state;  

atomic_t count;  

void (*func)(unsigned long);  

unsigned long data;  

};  

next用于把同一个cpu的tasklet链接成一个链表,state用于表明该tasklet的当时状况,现在仅仅用了最低的两个bit,别离用于表明现已预备被调度履行和现已在另一个cpu上履行:

[cpp] view plain copy

enum  

{  

TASKLET_STATE_SCHED,    /* Tasklet is scheduled for execution */  

TASKLET_STATE_RUN   /* Tasklet is running (SMP only) */  

};  

原子变量count用于tasklet对tasklet_disable和tasklet_enable的计数,count为0时表明答应tasklet履行,不然不答应履行,每次tasklet_disable时,该值加1,tasklet_enable时该值减1。func是tasklet被履行时的回调函数指针,data则用作回调函数func的参数。

4.2  初始化一个tasklet

有两种办法初始化一个tasklet,第一种是静态初始化,运用以下两个宏,这两个宏界说一个tasklet_struct结构,并用相应的参数对结构中的字段进行初始化:

DECLARE_TASKLET(name, func, data);界说姓名为name的tasklet,默以为enable状况,也便是count字段等于0。

DECLARE_TASKLET_DISABLED(name, func, data);界说姓名为name的tasklet,默以为enable状况,也便是count字段等于1。

第二个是动态初始化办法:先界说一个tasklet_struct,然后用tasklet_init函数进行初始化,该办法默许tasklet处于enable状况:

[cpp] view plain copy

struct tasklet_struct tasklet_xxx;  

……  

tasklet_init(&tasklet_xxx, func, data);  

4.3  tasklet的运用办法

使能和制止tasklet,运用以下函数:

tasklet_disable()  经过给count字段加1来制止一个tasklet,假如tasklet正在运转中,则等候运转结束才回来(经过TASKLET_STATE_RUN标志)。

tasklet_disable_nosync()  tasklet_disable的异步版别,它不会等候tasklet运转结束。

tasklet_enable()  使能tasklet,仅仅简略地给count字段减1。

调度tasklet的履行,运用以下函数:

tasklet_schedule(struct tasklet_struct *t)  假如TASKLET_STATE_SCHED标志为0,则置位TASKLET_STATE_SCHED,然后把tasklet挂到该cpu等候履行的tasklet链表上,接着宣布TASKLET_SOFTIRQ软件中止请求。

tasklet_hi_schedule(struct tasklet_struct *t)  作用同上,区别是它宣布的是HI_SOFTIRQ软件中止请求。

毁掉tasklet,运用以下函数:

tasklet_kill(struct tasklet_struct *t)  假如tasklet处于TASKLET_STATE_SCHED状况,或许tasklet正在履行,则会等候tasklet履行结束,然后铲除TASKLET_STATE_SCHED状况。

4.4  tasklet的内部履行机制

内核为每个cpu用界说了一个tasklet_head结构,用于办理每个cpu上的tasklet的调度和履行:

[cpp] view plain copy

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);  

回到4.1节,咱们知道,tasklet是使用TASKLET_SOFTIRQ和HI_SOFTIRQ这两个软中止来完成的,两个软中止仅仅有优先级的不同,所以咱们只评论TASKLET_SOFTIRQ的完成,TASKLET_SOFTIRQ的中止回调函数是tasklet_action,咱们看看它的代码:

[cpp] view plain copy

static void tasklet_action(struct softirq_action *a)  

{  

struct tasklet_struct *list;  

local_irq_disable();  

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;  

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;  

*__this_cpu_read(tasklet_vec.tail) = t;  

__this_cpu_write(tasklet_vec.tail, &(t->next));  

__raise_softirq_irqoff(TASKLET_SOFTIRQ);  

local_irq_enable();  

}  

}  

解析如下:

封闭本地中止的前提下,移出当时cpu的待处理tasklet链表到一个暂时链表后,铲除当时cpu的tasklet链表,之所以这样处理,是为了处理当时tasklet链表的时分,答应新的tasklet被调度进待处理链表中。

遍历暂时链表,用tasklet_trylock判别当时tasklet是否现已在其他cpu上运转,并且tasklet没有被制止:

假如没有运转,也没有制止,则铲除TASKLET_STATE_SCHED状况位,履行tasklet的回调函数。

假如现已在运转,或许被制止,则把该tasklet从头增加会当时cpu的待处理tasklet链表上,然后触发TASKLET_SOFTIRQ软中止,等候下一次软中止时再次履行。

剖析到这了我有个疑问,看了上面的代码,假如一个tasklet被tasklet_schedule后,在没有被履行前被tasklet_disable了,岂不是会无穷无尽地引发TASKLET_SOFTIRQ软中止?

经过以上的剖析,咱们需求留意的是,tasklet有以下几个特征:

同一个tasklet只能一起在一个cpu上履行,但不同的tasklet能够一起在不同的cpu上履行;

一旦tasklet_schedule被调用,内核会确保tasklet必定会在某个cpu上履行一次;

假如tasklet_schedule被调用时,tasklet不是出于正在履行状况,则它只会履行一次;

假如tasklet_schedule被调用时,tasklet现已正在履行,则它会在稍后被调度再次被履行;

两个tasklet之间假如有资源抵触,应该要用自旋进行同步维护;

 

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/dianyuan/kaiguan/90437.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部