欢迎光临散文网 会员登陆 & 注册

Linux内核中断处理“下半部”机制(超详细~)

2022-05-19 16:05 作者:大方老师单片机课堂  | 我要投稿

Linux内核中断处下半机制(超详~



///插播一条:我自己在今年年初录制了一套还比较系统的入门单片机教程,想要的同学找我拿就行了免費的,私信我就可以~点我头像黑色字体加我地球呺也能领取哦。最近比较闲,带做毕设,带学生参加省级或以上比///


1.中断处下半机制

·中断服务程序一般都是在中断请求关闭的条件下执行的,以避免嵌套而使中断控制复杂化。但是,中断是一个随机事件,它随时会到来,如果关中断的时间太长CPU就不能及时响应其他的中断请求,从而造成中断的丢失。

·因此Linux内核的目标就是尽可能快的处理完中断请求,尽其所能把更多的处理向后推迟。例如,假设一个数据块已经达到了网线,当中断控制器接受到这个中断请求信号时Linux内核只是简单地标志数据到来了,然后让处理器恢复到它以前运行的状态,其余的处理稍后再进(如把数据移入一个缓冲区,接受数据的进程就可以在缓冲区找到数)

·因此,内核把中断处理分为两部分:上半(top-half)和下半(bottom-half),上半(就是中断服务程)内核立即执行,而下半(就是一些内核函)留着稍后处理。

·首先:一个快速上半来处理硬件发出的请求,它必须在一个新的中断产生之前终止。通常,除了在设备和一些内存缓冲(如果你的设备用到DMA,就不止这)之间移动或传送数据,确定硬件是否处于健全的状态之外,这一部分做的工作很少。

·第二下半运行时是允许中断请求的,而上半部运行时是关中断的,这是二者之间的主要区别。

·内核到底什么时候执行下半部,以何种方式组织下半部?

·这就是我们要讨论的下半部实现机制,这种机制在内核的演变过程中不断得到改进,在以前的内核中,这个机制叫bottom-half(以下简BH)。但是Linux的这bottom-half机制有两个缺点:

.在任意一时刻,系统只能有一CPU可以执BH代码,以防止两个或多CPU同时来执BH函数而相互干扰。因BH代码的执行是严串行的。

.BH函数不允许嵌套。

·这两个缺点在CPU系统中是无关紧要的,但SMP系统中却是非常致命的。因BH机制的严格串行化执行显然没有充分利SMP系统的CPU特点。为此,2.4以后的版本中有了新的发展和改进,改进的目标使下半部可以在多处理机上并行执行,并有助于驱动程序的开发者进行驱动程序的开发。下面主要介32.6内核中下半处理机制:

.软中断请(softirq)机制

.小任(tasklet)机制

.工作队列机制

·以上三种机制的比较如下图所示:


2.软中断请(softirq)机制

·Linuxsoftirq机制是SMP紧密不可分的。为此,整softirq机制的设计与实现中自始自终都贯彻了一个思想谁触发,谁执Who marksWho runs),也即触发软中断的那CPU负责执行它所触发的软中断,而且每CPU都有它自己的软中断触发与控制机制。这个设计思想也使softirq机制充分利用SMP系统的性能和特点。

2.1软中断描述符

·Linuxinclude/linux/interrupt.h头文件中定义了数据结softirq_action,来描述一个软中断请求,如下所示:

/* 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, //用于实tasklet软中

SCHED_SOFTIRQ,

HRTIMER_SOFTIRQ,

RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */

NR_SOFTIRQS

};

/* map softirq index to softirq name. update 'softirq_to_name' in

* kernel/softirq.c when adding a new softirq.

*/

extern char *softirq_to_name[NR_SOFTIRQS];

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

};

asmlinkage void do_softirq(void);

asmlinkage void __do_softirq(void);

·其中,函数指action指向软中断请求的服务函数。基于上述软中断描述符Linuxkernel/softirq.c文件中定义了一个全局softirq_vec数组:

·static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

·在这里系统一共定义10个软中断请求描述符。软中断向i0i9)所对应的软中断请求描述符就softirq_vec[i]。这个数组是个系统全局数组,即它被所有CPU所共享。这里需要注意的一点是:每CPU虽然都有它自己的触发和控制机制,并且只执行他自己所触发的软中断请求,但是各CPU所执行的软中断服务例程却是相同的,也即都是执softirq_vec[ ]数组中定义的软中断服务函数Linuxkernel/softirq.c中的相关代码如下:

/*

- No shared variables, all the data are CPU local.

- If a softirq needs serialization, let it serialize itself

by its own spinlocks.

- Even if softirq is serialized, only local cpu is marked for

execution. Hence, we get something sort of weak cpu binding.

Though it is still not clear, will it result in better locality

or will not.

Examples:

- NET RX softirq. It is multithreaded and does not require

any global serialization.

- NET TX softirq. It kicks software netdevice queues, hence

it is logically serialized per device, but this serialization

is invisible to common code.

- Tasklets: serialized wrt itself.

*/

#ifndef __ARCH_IRQ_STAT

irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned;

EXPORT_SYMBOL(irq_stat);

#endif

static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

DEFINE_PER_CPU(struct task_struct *, ksoftirqd);

char *softirq_to_name[NR_SOFTIRQS] = {

"HI", "TIMER", "NET_TX", "NET_RX", "BLOCK", "BLOCK_IOPOLL",

"TASKLET", "SCHED", "HRTIMER", "RCU"

};

【文章福利】小编推荐自己Linux内核技术交流:891587639】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!100名进群领取,额外赠送大厂面试题。


学习直通车:

Linux内核源/内存调/文件系/进程管/设备驱/网络协议-学习视频教-腾讯课ke.qq.com/course/4032547?flowToken=1040236ke.qq.com/course/4032547?flowToken=1040236ke.qq.com/course/4032547?flowToken=1040236ke.qq.com/course/4032547?flowToken=1041712ke.qq.com/course/4032547?flowToken=1041712ke.qq.com/course/4032547?flowToken=1041712ke.qq.com/course/4032547?flowToken=1041712ke.qq.com/course/4032547?flowToken=1041712ke.qq.com/course/4032547?flowToken=1041712ke.qq.com/course/4032547?flowToken=1041712ke.qq.com/course/4032547?flowToken=1041712ke.qq.com/course/4032547?flowToken=1041712ke.qq.com/course/4032547?flowToken=1041712ke.qq.com/course/4032547?flowToken=1041712ke.qq.com/course/4032547?flowToken=1041712ke.qq.com/course/4032547?flowToken=1041712ke.qq.com/course/4032547?flowToken=1041712ke.qq.com/course/4032547?flowToken=1041712ke.qq.com/course/4032547?flowToken=1041712ke.qq.com/course/4032547?flowToken=1041712ke.qq.com/course/4032547?flowToken=1041712ke.qq.com/course/4032547?flowToken=1041712ke.qq.com/course/4032547?flowToken=1041712ke.qq.com/course/4032547?flowToken=1041712ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639ke.qq.com/course/4032547?flowToken=1042639

内核资料直通车:

嵌入式开发Linux内核开发学习路线+完整视+完整资docs.qq.com/doc/DYXlud2FKT1REWFRCdocs.qq.com/doc/DYWhwd2pvcm96VXJB

2.2软中断触发机制

·要实谁触发,谁执的思想,就必须为每CPU都定义它自己的触发和控制变量。为此Linuxinclude/asm-i386/hardirq.h头文件中定义了数据结irq_cpustat_t来描述一CPU的中断统计信息,其中就有用于触发和控制软中断的成员变量。数据结irq_cpustat_t的定义如下:

·IPI:处理器间的中(Inter-Processor Interrupts)

#define NR_IPI6

typedef struct {

unsigned int __softirq_pending;

#ifdef CONFIG_LOCAL_TIMERS

unsigned int local_timer_irqs;

#endif

#ifdef CONFIG_SMP

unsigned int ipi_irqs[NR_IPI];

#endif

} ____cacheline_aligned irq_cpustat_t;

·中断处理的相关宏如下:

#define __inc_irq_stat(cpu, member)__IRQ_STAT(cpu, member)++

#define __get_irq_stat(cpu, member)__IRQ_STAT(cpu, member)

#define __IRQ_STAT(cpu, member) (irq_stat[cpu].member)

·irq_cpustat_tirq_stat[NR_CPUS] ____cacheline_aligned;

.NR_CPUS:为系统CPU个数。

.这样,每CPU都只操作它自己的中断统计信息结构。假设有一个编号idCPU,那么它只能操作它自己的中断统计信息结irq_statid0idNR_CPUS-1),从而使CPU之间互不影响。

1)触发软中断函:

void raise_softirq(unsigned int nr)// nr为中断号

2)设置软中断服务函数:

void open_softirq(int nr, void (*action)(struct softirq_action *)); // nr为中断, action为中断处理函数

2.3初始化软中(softirq_init)

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); //设置软中断服务函数

}

2.4软中断服务的执行函do_softirq

·do_softirq()负责执行数softirq_veci]中设置的软中断服务函数。每CPU都是通过执行这个函数来执行软中断服务的。由于同一CPU上的软中断服务例程不允许嵌套,因此do_softirq()函数一开始就检查当CPU是否已经正出在中断服务中,如果是do_softirq()函数立即返回。举个例子,假CPU0正在执do_softirq()函数,执行过程产生了一个高优先级的硬件中断,于CPU0转去执行这个高优先级中断所对应的中断服务程序。众所周知,所有的中断服务程序最后都要跳转do_IRQ()函数并由它来依次执行中断服务队列中ISR,这里我们假定这个高优先级中断ISR请求触发了一次软中断,于do_IRQ()函数在退出之前看到有软中断请求,从而调do_softirq()函数来服务软中断请求。因此CPU0再次进do_softirq()函数(也do_softirq()函数CPU0上被重入了)。但是在这一次进do_softirq()函数时,它马上发CPU0此前已经处在中断服务状态中了,因此这一do_softirq()函数立即返回。于是CPU0回到该开始时do_softirq()函数继续执行,并为高优先级中断ISR所触发的软中断请求补上一次服务。从这里可以看出do_softirq()函数在同一CPU上的执行是串行的。

asmlinkage void do_softirq(void)

{

__u32 pending;

unsigned long flags;

if (in_interrupt())

return;

local_irq_save(flags);

pending = local_softirq_pending();

if (pending)

__do_softirq();

local_irq_restore(flags);

}

3.小任( tasklet)机制

·tasklet机制是一种较为特殊的软中断。

·tasklet一词的原意小片任的意思,这里是指一小段可执行的代码,且通常以函数的形式出现。软中断向HI_SOFTIRQTASKLET_SOFTIRQ均是tasklet机制来实现的。

·从某种程度上讲tasklet机制Linux内核BH机制的一种扩展。2.4内核引入softirq机制后,原有BH机制正是通tasklet机制这个桥梁来softirq机制纳入整体框架中的。正是由于这种历史的延伸关系,使tasklet机制与一般意义上的软中断有所不同,而呈现出以下两个显著的特点:

.与一般的软中断不同,某一tasklet代码在某个时刻只能在一CPU上运行,而不像一般的软中断服务函(softirq_action结构中action函数指)在同一时刻可以被多CPU并发地执行。

.BH机制不同,不同tasklet代码在同一时刻可以在多CPU上并发地执行,而不BH机制那样必须严格地串行化执(也即在同一时刻系统中只能有一CPUBH)

3.1 tasklet描述符

·Linux用数据结tasklet_struct来描述一tasklet,每个结构代表一个独立的小任务。该数据结构定义include/linux/interrupt.h头文件中。如下所示:

/* 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;

atomic_t count;

void (*func)(unsigned long);

unsigned long data;

};

·next:指向下一tasklet的指针

·state:定义了这tasklet的当前状态。这一32位的无符号长整数,当前只使用bit[1]bit[0]两个状态位。其中bit[1]1表示这tasklet当前正在某CPU上被执行,它仅SMP系统才有意义,其作用就是为了防止多CPU同时执行一tasklet的情形出现bit[0]1表示这tasklet已经被调度去等待执行了。

·对这两个状态位的宏定义如下所示interrupt.h):

enum

{

TASKLET_STATE_SCHED,/* Tasklet is scheduled for execution */

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

};

·count:子计count,对这tasklet的引用计数值。

·注:只有count0tasklet代码段才能执行,也即此tasklet是被使能的;如count非零,则这tasklet是被禁止的。任何想要执行一tasklet代码段的人都首先必须先检查count成员是否0

·func:指向以函数形式表现的可执tasklet代码段。

·data:函func的参数。这是一32位的无符号整数,其具体含义可func函数自行解释,比如将其解释成一个指向某个用户自定义数据结构的地址值。

·Linuxinterrupt.h头文件中又定义了两个用来定tasklet_struct结构变量的辅助宏:

#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 }

·显然,从上述源代码可以看出,DECLARE_TASKLET宏定义tasklet在初始化时是被使能的enabled),因为count成员0。而DECLARE_TASKLET_DISABLED宏定义tasklet在初始时是被禁止的disabled),因为count1

3.2改变一tasklet状态的操作

·在这里tasklet状态指两个方面:

.state:成员所表示的运行状态;

.count:成员决定的使能/禁止状态。

3.2.1改变一tasklet的运行状态

·state成员中bit[0]表示一tasklet是否已被调度去等待执行bit[1]表示一tasklet是否正在某CPU上执行。对state变量中某位的改变必须是一个原子操作,因此可以用定义include/asm/bitops.h头文件中的位操作来进行。

·bit[1]这一位(TASKLET_STATE_RUN)仅仅对SMP系统才有意义,因LinuxInterrupt.h头文件中显示地定义了TASKLET_STATE_RUN位的操作。如下所示:

#ifdef CONFIG_SMP

static inline int tasklet_trylock(struct tasklet_struct *t)

{

return !test_and_set_bit(TASKLET_STATE_RUN, &(t)->state);

}

static inline void tasklet_unlock(struct tasklet_struct *t)

{

smp_mb__before_clear_bit();

clear_bit(TASKLET_STATE_RUN, &(t)->state);

}

static inline void tasklet_unlock_wait(struct tasklet_struct *t)

{

while (test_bit(TASKLET_STATE_RUN, &(t)->state)) { barrier(); }

}

#else

#define tasklet_trylock(t) 1

#define tasklet_unlock_wait(t) do { } while (0)

#define tasklet_unlock(t) do { } while (0)

#endif

·显然,SMP系统同tasklet_trylock()宏将把一tasklet_struct结构变量中state成员中bit[1]位设置1,同时还返bit[1]位的非。因此,如bit[1]位原有值1(表示另外一CPU正在执行这tasklet代码),那tasklet_trylock()宏将返回0,也就表示上锁不成功。如bit[1]位的原有值0,那tasklet_trylock()宏将返回1,表示加锁成功。而在CPU系统中tasklet_trylock()宏总是返回1

·任何想要执行某tasklet代码的程序都必须首先调用tasklet_trylock()来试图对这tasklet进行上锁(即设TASKLET_STATE_RUN位),且只能在上锁成功的情况下才能执行这tasklet。建议!即使你的程序只CPU系统上运行,你也要在执tasklet之前调tasklet_trylock()宏,以便使你的代码获得良好可移植性。

·SMP系统中tasklet_unlock_wait()宏将一直不停地测TASKLET_STATE_RUN位的值,直到该位的值变0(即一直等待到解锁),假如CPU0正在执tasklet A的代码,在此期间CPU1也想执tasklet A的代码,CPU1tasklet ATASKLET_STATE_RUN1,于是它就可以通tasklet_unlock_wait()宏等tasklet A被解锁(也TASKLET_STATE_RUN位被清零)。在CPU系统中,这是一个空操作。

·tasklet_unlock()用来对一tasklet进行解锁操作,也即TASKLET_STATE_RUN位清零。在CPU系统中,这是一个空操作。

3.2.2使能/禁止一tasklet

·使能与禁止操作往往总是成对地被调用的tasklet_disable()函数如下interrupt.h):

static inline void tasklet_disable(struct tasklet_struct *t)

{

tasklet_disable_nosync(t);

tasklet_unlock_wait(t);

smp_mb();

}

·tasklet_disable_nosync()也是一个静inline函数,它简单地通过原子操作count成员变量的值1。如下所示interrupt.h):

static inline void tasklet_disable_nosync(struct tasklet_struct *t)

{

atomic_inc(&t->count);

smp_mb__after_atomic_inc();

}

·tasklet_enable()用于使能一tasklet,如下所示interrupt.h):

static inline void tasklet_enable(struct tasklet_struct *t)

{

smp_mb__before_atomic_dec();

atomic_dec(&t->count);

}

3.3 tasklet描述符的初始化与杀死

·tasklet_init()用来初始化一个指定tasklet描述符,其源码如下所示kernel/softirq.c):

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_kill()用来将一个已经被调度了tasklet杀死,即将其恢复到未调度的状态。其源码如下所示kernel/softirq.c):

void tasklet_kill(struct tasklet_struct *t)

{

if (in_interrupt())

printk("Attempt to kill tasklet from interrupt\n");

while (test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {

do {

yield();

} while (test_bit(TASKLET_STATE_SCHED, &t->state));

}

tasklet_unlock_wait(t);

clear_bit(TASKLET_STATE_SCHED, &t->state);

}

3.4 tasklet对列

·tasklet可以通tasklet描述符中next成员指针链接成一个单向对列。为此Linux专门在头文include/linux/interrupt.h中定义了数据结tasklet_head来描述一tasklet对列的头部指针。如下所示:

/*

* Tasklets

*/

struct tasklet_head

{

struct tasklet_struct *head;

struct tasklet_struct **tail;

};

·tasklet机制是特定于软中断向HI_SOFTIRQTASKLET_SOFTIRQ的一种实现,但tasklet机制仍然属softirq机制的整体框架范围内的,因此,它的设计与实现仍然必须坚谁触发,谁执的思想。为此Linux为系统中的每一CPU都定义了一tasklet对列头部,来表示应该有各CPU负责执行tasklet对列。如下所示kernel/softirq.c):

#define DEFINE_PER_CPU_SECTION(type, name, sec)\

__PCPU_ATTRS(sec) PER_CPU_DEF_ATTRIBUTES\

__typeof__(type) name

#define DEFINE_PER_CPU(type, name) \

DEFINE_PER_CPU_SECTION(type, name, "")

static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);

static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);

struct tasklet_head tasklet_vec[NR_CPUS] __cacheline_aligned;
struct tasklet_head tasklet_hi_vec[NR_CPUS] __cacheline_aligned;

·其中tasklet_vec[]数组用于软中断向TASKLET_SOFTIRQtasklet_hi_vec[]数组则用于软中断向HI_SOFTIRQ。也即,如CPUi0iNR_CPUS-1)触发了软中断向TASKLET_SOFTIRQ,那么对tasklet_veci]中的每一tasklet都将CPUi服务于软中断向TASKLET_SOFTIRQCPUi所执行。同样地,如CPUi0iNR_CPUS-1)触发了软中断向HI_SOFTIRQ,那么队tasklet_hi_veci]中的每一taskletCPUi在对软中断向HI_SOFTIRQ进行服务时CPUi所执行。

·tasklet_vecItasklet_hi_vecI]中的各tasklet是怎样被CPUi所执行的呢?其关键就是软中断向TASKLET_SOFTIRQHI_SOFTIRQ的软中断服务程tasklet_action()函数tasklet_hi_action()函数。下面我们就来分析这两个函数。

3.5软中断向TASKLET_SOFTIRQHI_SOFTIRQ

·Linux为软中断向TASKLET_SOFTIRQHI_SOFTIRQ实现了专用的触发函数和软中断服务函数。

1.专用的触发函数

tasklet_schedule()函数tasklet_hi_schedule()函数分别用来在当CPU上触发软中断向TASKLET_SOFTIRQHI_SOFTIRQ,并把指定tasklet加入当CPU所对应tasklet队列中去等待执行。

2.专用的软中断服务函数

tasklet_action()函数tasklet_hi_action()函数则分别是软中断向TASKLET_SOFTIRQHI_SOFTIRQ的软中断服务函数。在初始化函softirq_init()中,这两个软中断向量对应的描述softirq_vec[0]softirq_vec[6]action函数指针就被分别初始化成指向函tasklet_hi_action()和函tasklet_action()

3.5.1软中断向TASKLET_SOFTIRQ的触发函tasklet_schedule

·该函数实现include/linux/interrupt.h头文件中,是一inline函数。其源码如下所示:

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); //触发软中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);

}

test_and_set_bit()函数将待调度taskletstate成员变量bit0]位(也TASKLET_STATE_SCHED位)设置1,该函数同时还返TASKLET_STATE_SCHED位的原有值。因此如bit0]为的原有值已经1,那就说明这tasklet已经被调度到另一CPU上去等待执行了。由于一tasklet在某一个时刻只能由一CPU来执行,因tasklet_schedule()函数什么也不做就直接返回了。否则,就继续下面的调度操作。

.首先,调local_irq_save()函数来关闭当CPU的中断,以保证下面的步骤在当CPU上原子地被执行。

.然后,将待调度tasklet添加到当CPU对应tasklet队列的尾部。

.接着,调raise_softirq_irqoff函数在当CPU上触发软中断请TASKLET_SOFTIRQ

.最后,调local_irq_restore()函数来开当CPU的中断。

3.5.2软中断向TASKLET_SOFTIRQ的服务程tasklet_action

·tasklet_action()tasklet机制与软中断向TASKLET_SOFTIRQ的联系纽带。正是该函数将当CPUtasklet队列中的各tasklet放到当CPU上来执行的。该函数实现kernel/softirq.c文件中,其源代码如下:

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关中断的情况下地读取当CPUtasklet队列头部指针,将其保存到局部变list指针中,然后将当CPUtasklet队列头部指针设置NULL,以表示理论上当CPU将不再tasklet需要执行(但最后的实际结果却并不一定如此,下面将会看到)。

.然后,用一while{}循环来遍历list所指向tasklet队列,队列中的各个元素就是将在当CPU上执行tasklet。循环体的执行步骤如下:

.用指t来表示当前队列元素,即当前需要执行tasklet

.list指针list->next,使它指向下一个要执行tasklet

.tasklet_trylock()宏试图对当前要执行tasklet(由指t所指向)进行加锁,如果加锁成功(当前没有任何其CPU正在执行这tasklet),则用原子读函atomic_read()进一步判count成员的值。如count0,说明这tasklet是允许执行的,于是:

(1)先清TASKLET_STATE_SCHED位;

(2)然后,调用这tasklet的可执行函func

(3)调用tasklet_unlock()来清TASKLET_STATE_RUN

(4)最后,执continue语句跳过下面的步骤,回while循环继续遍历队列中的下一个元素。如count0,说明这tasklet是禁止运行的,于是调tasklet_unlock()清除前面tasklet_trylock()设置TASKLET_STATE_RUN位。

3.6 tasklet使用总结

1.声明和使用小任务大多数情况下,为了控制一个常用的硬件设备,小任务机制是实现下半部的最佳选择。小任务可以动态创建,使用方便,执行起来也比较快。我们既可以静态地创建小任务,也可以动态地创建它。选择那种方式取决于到底是想要对小任务进行直接引用还是一个间接引用。如果准备静态地创建一个小任务(也就是对它直接引用),使用下面两个宏中的一个:

DECLARE_TASKLET(name,func, data)
DECLARE_TASKLET_DISABLED(name,func, data)

·这两个宏都能根据给定的名字静态地创建一tasklet_struct结构。当该小任务被调度以后,给定的函func会被执行,它的参数data给出。这两个宏之间的区别在于引用计数器的初始值设置不同。第一个宏把创建的小任务的引用计数器设置0,因此,该小任务处于激活状态。另一个把引用计数器设置1,所以该小任务处于禁止状态。例如:

DECLARE_TASKLET(my_tasklet,my_tasklet_handler, dev);
这行代码其实等价于
struct tasklet_struct my_tasklet = { NULL, 0, ATOMIC_INIT(0),
tasklet_handler,dev};

·这样就创建了一个名my_tasklet的小任务,其处理程序tasklet_handler,并且已被激活。当处理程序被调用的时候dev就会被传递给它。

2.编写自己的小任务处理程序小任务处理程序必须符合如下的函数类型:

void tasklet_handler(unsigned long data)

·由于小任务不能睡眠,因此不能在小任务中使用信号量或者其它产生阻塞的函数。但是小任务运行时可以响应中断。

3.调度自己的小任务通过调tasklet_schedule()函数并传递给它相应tasklt_struct指针,该小任务就会被调度以便适当的时候执行:

tasklet_schedule(&my_tasklet); /*my_tasklet标记为挂 */

·在小任务被调度以后,只要有机会它就会尽可能早的运行。在它还没有得到运行机会之前,如果一个相同的小任务又被调度了,那么它仍然只会运行一次。

·可以调tasklet_disable()函数来禁止某个指定的小任务。如果该小任务当前正在执行,这个函数会等到它执行完毕再返回。调tasklet_enable()函数可以激活一个小任务,如果希望把DECLARE_TASKLET_DISABLED()创建的小任务激活,也得调用这个函数,如:

tasklet_disable(&my_tasklet); /*小任务现在被禁,这个小任务不能运*/
tasklet_enable(&my_tasklet); /*小任务现在被激*/

·也可以调tasklet_kill()函数从挂起的队列中去掉一个小任务。该函数的参数是一个指向某个小任务tasklet_struct的长指针。在小任务重新调度它自身的时候,从挂起的队列中移去已调度的小任务会很有用。这个函数首先等待该小任务执行完毕,然后再将它移去。

4.tasklet的简单用法

·下面tasklet的一个简单应,以模块的形成加载。

#include

#include

#include

#include

#include

#include

#include

static struct t asklet_struct my_tasklet;

static void tasklet_handler (unsigned long d ata)

{

printk(KERN_ALERT,"tasklet_handler is running./n");

}

static int __init test_init(void)

{

tasklet_init(&my_tasklet,tasklet_handler,0);

tasklet_schedule(&my_tasklet);

return0;

}

static void __exit test_exit(void)

{

tasklet_kill(&tasklet);

printk(KERN_ALERT,"test_exit is running./n");

}

MODULE_LICENSE("GPL");

module_init(test_init);

module_exit(test_exit);

·从这个例子可以看出,所谓的小任务机制是为下半部函数的执行提供了一种执行机制,也就是说,推迟处理的事情是tasklet_handler实现,何时执行,经由小任务机制封装后交给内核去处理。

Linux内核中断处理“下半部”机制(超详细~)的评论 (共 条)

分享到微博请遵守国家法律