最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

Linux內(nèi)核中斷處理“下半部”機(jī)制(超詳細(xì)~)

2022-05-19 16:05 作者:大方老師單片機(jī)課堂  | 我要投稿

Linux內(nèi)核中斷處下半機(jī)制(超詳細(xì)~



///插播一條:我自己在今年年初錄制了一套還比較系統(tǒng)的入門單片機(jī)教程,想要的同學(xué)找我拿就行了免費(fèi)的,私信我就可以~點(diǎn)我頭像黑色字體加我地球呺也能領(lǐng)取哦。最近比較閑,帶做畢設(shè),帶學(xué)生參加省級(jí)或以上比///


1.中斷處下半機(jī)制

·中斷服務(wù)程序一般都是在中斷請(qǐng)求關(guān)閉的條件下執(zhí)行的,以避免嵌套而使中斷控制復(fù)雜化。但是,中斷是一個(gè)隨機(jī)事件,它隨時(shí)會(huì)到來(lái),如果關(guān)中斷的時(shí)間太長(zhǎng),CPU就不能及時(shí)響應(yīng)其他的中斷請(qǐng)求,從而造成中斷的丟失。

·因此,Linux內(nèi)核的目標(biāo)就是盡可能快的處理完中斷請(qǐng)求,盡其所能把更多的處理向后推遲。例如,假設(shè)一個(gè)數(shù)據(jù)塊已經(jīng)達(dá)到了網(wǎng)線,當(dāng)中斷控制器接受到這個(gè)中斷請(qǐng)求信號(hào)時(shí),Linux內(nèi)核只是簡(jiǎn)單地標(biāo)志數(shù)據(jù)到來(lái)了,然后讓處理器恢復(fù)到它以前運(yùn)行的狀態(tài),其余的處理稍后再進(jìn)(如把數(shù)據(jù)移入一個(gè)緩沖區(qū),接受數(shù)據(jù)的進(jìn)程就可以在緩沖區(qū)找到數(shù)據(jù))。

·因此,內(nèi)核把中斷處理分為兩部分:上半(top-half)和下半(bottom-half),上半(就是中斷服務(wù)程)內(nèi)核立即執(zhí)行,而下半(就是一些內(nèi)核函數(shù))留著稍后處理。

·首先:一個(gè)快速上半來(lái)處理硬件發(fā)出的請(qǐng)求,它必須在一個(gè)新的中斷產(chǎn)生之前終止。通常,除了在設(shè)備和一些內(nèi)存緩沖區(qū)(如果你的設(shè)備用到DMA,就不止這)之間移動(dòng)或傳送數(shù)據(jù),確定硬件是否處于健全的狀態(tài)之外,這一部分做的工作很少。

·第二下半運(yùn)行時(shí)是允許中斷請(qǐng)求的,而上半部運(yùn)行時(shí)是關(guān)中斷的,這是二者之間的主要區(qū)別。

·內(nèi)核到底什么時(shí)候執(zhí)行下半部,以何種方式組織下半部?

·這就是我們要討論的下半部實(shí)現(xiàn)機(jī)制,這種機(jī)制在內(nèi)核的演變過(guò)程中不斷得到改進(jìn),在以前的內(nèi)核中,這個(gè)機(jī)制叫bottom-half(以下簡(jiǎn)BH)。但是,Linux的這bottom-half機(jī)制有兩個(gè)缺點(diǎn):

.在任意一時(shí)刻,系統(tǒng)只能有一個(gè)CPU可以執(zhí)BH代碼,以防止兩個(gè)或多個(gè)CPU同時(shí)來(lái)執(zhí)BH函數(shù)而相互干擾。因BH代碼的執(zhí)行是嚴(yán)串行的。

.BH函數(shù)不允許嵌套。

·這兩個(gè)缺點(diǎn)在CPU系統(tǒng)中是無(wú)關(guān)緊要的,但SMP系統(tǒng)中卻是非常致命的。因BH機(jī)制的嚴(yán)格串行化執(zhí)行顯然沒(méi)有充分利SMP系統(tǒng)的CPU特點(diǎn)。為此,2.4以后的版本中有了新的發(fā)展和改進(jìn),改進(jìn)的目標(biāo)使下半部可以在多處理機(jī)上并行執(zhí)行,并有助于驅(qū)動(dòng)程序的開(kāi)發(fā)者進(jìn)行驅(qū)動(dòng)程序的開(kāi)發(fā)。下面主要介32.6內(nèi)核中下半處理機(jī)制:

.軟中斷請(qǐng)(softirq)機(jī)制

.小任務(wù)(tasklet)機(jī)制

.工作隊(duì)列機(jī)制

·以上三種機(jī)制的比較如下圖所示:


2.軟中斷請(qǐng)(softirq)機(jī)制

·Linuxsoftirq機(jī)制是SMP緊密不可分的。為此,整個(gè)softirq機(jī)制的設(shè)計(jì)與實(shí)現(xiàn)中自始自終都貫徹了一個(gè)思想誰(shuí)觸發(fā),誰(shuí)執(zhí)Who marks,Who runs),也即觸發(fā)軟中斷的那個(gè)CPU負(fù)責(zé)執(zhí)行它所觸發(fā)的軟中斷,而且每個(gè)CPU都有它自己的軟中斷觸發(fā)與控制機(jī)制。這個(gè)設(shè)計(jì)思想也使softirq機(jī)制充分利用SMP系統(tǒng)的性能和特點(diǎn)。

2.1軟中斷描述符

·Linuxinclude/linux/interrupt.h頭文件中定義了數(shù)據(jù)結(jié)構(gòu)softirq_action,來(lái)描述一個(gè)軟中斷請(qǐng)求,如下所示:

/* 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, //用于實(shí)現(xiàn)高優(yōu)先級(jí)的軟中斷

TIMER_SOFTIRQ,

NET_TX_SOFTIRQ, //用于網(wǎng)絡(luò)數(shù)據(jù)的發(fā)送

NET_RX_SOFTIRQ, //用于網(wǎng)絡(luò)數(shù)據(jù)的接收

BLOCK_SOFTIRQ,

BLOCK_IOPOLL_SOFTIRQ,

TASKLET_SOFTIRQ, //用于實(shí)現(xiàn)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);

·其中,函數(shù)指action指向軟中斷請(qǐng)求的服務(wù)函數(shù)?;谏鲜鲕浿袛嗝枋龇?/span>Linuxkernel/softirq.c文件中定義了一個(gè)全局softirq_vec數(shù)組:

·static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

·在這里系統(tǒng)一共定義10個(gè)軟中斷請(qǐng)求描述符。軟中斷向i0i9)所對(duì)應(yīng)的軟中斷請(qǐng)求描述符就softirq_vec[i]。這個(gè)數(shù)組是個(gè)系統(tǒng)全局?jǐn)?shù)組,即它被所有CPU所共享。這里需要注意的一點(diǎn)是:每個(gè)CPU雖然都有它自己的觸發(fā)和控制機(jī)制,并且只執(zhí)行他自己所觸發(fā)的軟中斷請(qǐng)求,但是各個(gè)CPU所執(zhí)行的軟中斷服務(wù)例程卻是相同的,也即都是執(zhí)softirq_vec[ ]數(shù)組中定義的軟中斷服務(wù)函數(shù)。Linuxkernel/softirq.c中的相關(guān)代碼如下:

/*

- 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內(nèi)核技術(shù)交流:891587639】整理了一些個(gè)人覺(jué)得比較好的學(xué)習(xí)書(shū)籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。?/span>100名進(jìn)群領(lǐng)取,額外贈(zèng)送大廠面試題。


學(xué)習(xí)直通車:

Linux內(nèi)核源/內(nèi)存調(diào)優(yōu)/文件系統(tǒng)/進(jìn)程管/設(shè)備驅(qū)動(dòng)/網(wǎng)絡(luò)協(xié)議-學(xué)習(xí)視頻教-騰訊課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

內(nèi)核資料直通車:

嵌入式開(kāi)發(fā)Linux內(nèi)核開(kāi)發(fā)學(xué)習(xí)路線+完整視+完整資docs.qq.com/doc/DYXlud2FKT1REWFRCdocs.qq.com/doc/DYWhwd2pvcm96VXJB

2.2軟中斷觸發(fā)機(jī)制

·要實(shí)現(xiàn)誰(shuí)觸發(fā),誰(shuí)執(zhí)的思想,就必須為每個(gè)CPU都定義它自己的觸發(fā)和控制變量。為此Linuxinclude/asm-i386/hardirq.h頭文件中定義了數(shù)據(jù)結(jié)構(gòu)irq_cpustat_t來(lái)描述一個(gè)CPU的中斷統(tǒng)計(jì)信息,其中就有用于觸發(fā)和控制軟中斷的成員變量。數(shù)據(jù)結(jié)構(gòu)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;

·中斷處理的相關(guān)宏如下:

#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:為系統(tǒng)CPU個(gè)數(shù)。

.這樣,每個(gè)CPU都只操作它自己的中斷統(tǒng)計(jì)信息結(jié)構(gòu)。假設(shè)有一個(gè)編號(hào)idCPU,那么它只能操作它自己的中斷統(tǒng)計(jì)信息結(jié)構(gòu)irq_statid0idNR_CPUS-1),從而使CPU之間互不影響。

1)觸發(fā)軟中斷函數(shù):

void raise_softirq(unsigned int nr)// nr為中斷號(hào)

2)設(shè)置軟中斷服務(wù)函數(shù):

void open_softirq(int nr, void (*action)(struct softirq_action *)); // nr為中斷號(hào), action為中斷處理函數(shù)

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); //設(shè)置軟中斷服務(wù)函數(shù)

open_softirq(HI_SOFTIRQ, tasklet_hi_action); //設(shè)置軟中斷服務(wù)函數(shù)

}

2.4軟中斷服務(wù)的執(zhí)行函數(shù)do_softirq

·數(shù)do_softirq()負(fù)責(zé)執(zhí)行數(shù)softirq_veci]中設(shè)置的軟中斷服務(wù)函數(shù)。每個(gè)CPU都是通過(guò)執(zhí)行這個(gè)函數(shù)來(lái)執(zhí)行軟中斷服務(wù)的。由于同一個(gè)CPU上的軟中斷服務(wù)例程不允許嵌套,因此,do_softirq()函數(shù)一開(kāi)始就檢查當(dāng)CPU是否已經(jīng)正出在中斷服務(wù)中,如果是do_softirq()函數(shù)立即返回。舉個(gè)例子,假設(shè)CPU0正在執(zhí)do_softirq()函數(shù),執(zhí)行過(guò)程產(chǎn)生了一個(gè)高優(yōu)先級(jí)的硬件中斷,于CPU0轉(zhuǎn)去執(zhí)行這個(gè)高優(yōu)先級(jí)中斷所對(duì)應(yīng)的中斷服務(wù)程序。眾所周知,所有的中斷服務(wù)程序最后都要跳轉(zhuǎn)do_IRQ()函數(shù)并由它來(lái)依次執(zhí)行中斷服務(wù)隊(duì)列中ISR,這里我們假定這個(gè)高優(yōu)先級(jí)中斷ISR請(qǐng)求觸發(fā)了一次軟中斷,于do_IRQ()函數(shù)在退出之前看到有軟中斷請(qǐng)求,從而調(diào)do_softirq()函數(shù)來(lái)服務(wù)軟中斷請(qǐng)求。因此,CPU0再次進(jìn)do_softirq()函數(shù)(也do_softirq()函數(shù)CPU0上被重入了)。但是在這一次進(jìn)do_softirq()函數(shù)時(shí),它馬上發(fā)現(xiàn)CPU0此前已經(jīng)處在中斷服務(wù)狀態(tài)中了,因此這一do_softirq()函數(shù)立即返回。于是CPU0回到該開(kāi)始時(shí)do_softirq()函數(shù)繼續(xù)執(zhí)行,并為高優(yōu)先級(jí)中斷ISR所觸發(fā)的軟中斷請(qǐng)求補(bǔ)上一次服務(wù)。從這里可以看出,do_softirq()函數(shù)在同一個(gè)CPU上的執(zhí)行是串行的。

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.小任務(wù)( tasklet)機(jī)制

·tasklet機(jī)制是一種較為特殊的軟中斷。

·tasklet一詞的原意小片任務(wù)的意思,這里是指一小段可執(zhí)行的代碼,且通常以函數(shù)的形式出現(xiàn)。軟中斷向HI_SOFTIRQTASKLET_SOFTIRQ均是tasklet機(jī)制來(lái)實(shí)現(xiàn)的。

·從某種程度上講,tasklet機(jī)制Linux內(nèi)核對(duì)BH機(jī)制的一種擴(kuò)展。2.4內(nèi)核引入softirq機(jī)制后,原有BH機(jī)制正是通過(guò)tasklet機(jī)制這個(gè)橋梁來(lái)softirq機(jī)制納入整體框架中的。正是由于這種歷史的延伸關(guān)系,使tasklet機(jī)制與一般意義上的軟中斷有所不同,而呈現(xiàn)出以下兩個(gè)顯著的特點(diǎn):

.與一般的軟中斷不同,某一tasklet代碼在某個(gè)時(shí)刻只能在一個(gè)CPU上運(yùn)行,而不像一般的軟中斷服務(wù)函數(shù)(softirq_action結(jié)構(gòu)中action函數(shù)指)在同一時(shí)刻可以被多個(gè)CPU并發(fā)地執(zhí)行。

.BH機(jī)制不同,不同tasklet代碼在同一時(shí)刻可以在多個(gè)CPU上并發(fā)地執(zhí)行,而不BH機(jī)制那樣必須嚴(yán)格地串行化執(zhí)(也即在同一時(shí)刻系統(tǒng)中只能有一個(gè)CPU執(zhí)BH數(shù))

3.1 tasklet描述符

·Linux用數(shù)據(jù)結(jié)構(gòu)tasklet_struct來(lái)描述一個(gè)tasklet,每個(gè)結(jié)構(gòu)代表一個(gè)獨(dú)立的小任務(wù)。該數(shù)據(jù)結(jié)構(gòu)定義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:指向下一個(gè)tasklet的指針

·state:定義了這個(gè)tasklet的當(dāng)前狀態(tài)。這一個(gè)32位的無(wú)符號(hào)長(zhǎng)整數(shù),當(dāng)前只使用bit[1]bit[0]兩個(gè)狀態(tài)位。其中,bit[1]1表示這個(gè)tasklet當(dāng)前正在某個(gè)CPU上被執(zhí)行,它僅對(duì)SMP系統(tǒng)才有意義,其作用就是為了防止多個(gè)CPU同時(shí)執(zhí)行一個(gè)tasklet的情形出現(xiàn);bit[0]1表示這個(gè)tasklet已經(jīng)被調(diào)度去等待執(zhí)行了。

·對(duì)這兩個(gè)狀態(tài)位的宏定義如下所示interrupt.h):

enum

{

TASKLET_STATE_SCHED,/* Tasklet is scheduled for execution */

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

};

·count:子計(jì)數(shù)count,對(duì)這個(gè)tasklet的引用計(jì)數(shù)值。

·注:只有當(dāng)count0時(shí)tasklet代碼段才能執(zhí)行,也即此時(shí)tasklet是被使能的;如count非零,則這個(gè)tasklet是被禁止的。任何想要執(zhí)行一個(gè)tasklet代碼段的人都首先必須先檢查count成員是否0。

·func:指向以函數(shù)形式表現(xiàn)的可執(zhí)tasklet代碼段。

·data:函數(shù)func的參數(shù)。這是一個(gè)32位的無(wú)符號(hào)整數(shù),其具體含義可func函數(shù)自行解釋,比如將其解釋成一個(gè)指向某個(gè)用戶自定義數(shù)據(jù)結(jié)構(gòu)的地址值。

·Linuxinterrupt.h頭文件中又定義了兩個(gè)用來(lái)定tasklet_struct結(jié)構(gòu)變量的輔助宏:

#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在初始化時(shí)是被使能的enabled),因?yàn)?/span>count成員0。而DECLARE_TASKLET_DISABLED宏定義tasklet在初始時(shí)是被禁止的disabled),因?yàn)?/span>count1。

3.2改變一個(gè)tasklet狀態(tài)的操作

·在這里tasklet狀態(tài)指兩個(gè)方面:

.state:成員所表示的運(yùn)行狀態(tài);

.count:成員決定的使能/禁止?fàn)顟B(tài)。

3.2.1改變一個(gè)tasklet的運(yùn)行狀態(tài)

·state成員中bit[0]表示一個(gè)tasklet是否已被調(diào)度去等待執(zhí)行,bit[1]表示一個(gè)tasklet是否正在某個(gè)CPU上執(zhí)行。對(duì)state變量中某位的改變必須是一個(gè)原子操作,因此可以用定義include/asm/bitops.h頭文件中的位操作來(lái)進(jìn)行。

·bit[1]這一位(TASKLET_STATE_RUN)僅僅對(duì)SMP系統(tǒng)才有意義,因LinuxInterrupt.h頭文件中顯示地定義了對(duì)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系統(tǒng)同,tasklet_trylock()宏將把一個(gè)tasklet_struct結(jié)構(gòu)變量中state成員中bit[1]位設(shè)置1,同時(shí)還返bit[1]位的非。因此,如bit[1]位原有值1(表示另外一個(gè)CPU正在執(zhí)行這個(gè)tasklet代碼),那tasklet_trylock()宏將返回0,也就表示上鎖不成功。如bit[1]位的原有值0,那tasklet_trylock()宏將返回1,表示加鎖成功。而在CPU系統(tǒng)中,tasklet_trylock()宏總是返回1。

·任何想要執(zhí)行某個(gè)tasklet代碼的程序都必須首先調(diào)用tasklet_trylock()來(lái)試圖對(duì)這個(gè)tasklet進(jìn)行上鎖(即設(shè)TASKLET_STATE_RUN位),且只能在上鎖成功的情況下才能執(zhí)行這個(gè)tasklet。建議!即使你的程序只CPU系統(tǒng)上運(yùn)行,你也要在執(zhí)tasklet之前調(diào)tasklet_trylock()宏,以便使你的代碼獲得良好可移植性。

·SMP系統(tǒng)中,tasklet_unlock_wait()宏將一直不停地測(cè)TASKLET_STATE_RUN位的值,直到該位的值變0(即一直等待到解鎖),假如CPU0正在執(zhí)tasklet A的代碼,在此期間CPU1也想執(zhí)tasklet A的代碼,CPU1發(fā)現(xiàn)tasklet ATASKLET_STATE_RUN1,于是它就可以通過(guò)tasklet_unlock_wait()宏等tasklet A被解鎖(也TASKLET_STATE_RUN位被清零)。在CPU系統(tǒng)中,這是一個(gè)空操作。

·tasklet_unlock()用來(lái)對(duì)一個(gè)tasklet進(jìn)行解鎖操作,也即TASKLET_STATE_RUN位清零。在CPU系統(tǒng)中,這是一個(gè)空操作。

3.2.2使能/禁止一個(gè)tasklet

·使能與禁止操作往往總是成對(duì)地被調(diào)用的,tasklet_disable()函數(shù)如下interrupt.h):

static inline void tasklet_disable(struct tasklet_struct *t)

{

tasklet_disable_nosync(t);

tasklet_unlock_wait(t);

smp_mb();

}

·數(shù)tasklet_disable_nosync()也是一個(gè)靜態(tài)inline函數(shù),它簡(jiǎn)單地通過(guò)原子操作count成員變量的值1。如下所示interrupt.h):

static inline void tasklet_disable_nosync(struct tasklet_struct *t)

{

atomic_inc(&t->count);

smp_mb__after_atomic_inc();

}

·數(shù)tasklet_enable()用于使能一個(gè)tasklet,如下所示interrupt.h):

static inline void tasklet_enable(struct tasklet_struct *t)

{

smp_mb__before_atomic_dec();

atomic_dec(&t->count);

}

3.3 tasklet描述符的初始化與殺死

·數(shù)tasklet_init()用來(lái)初始化一個(gè)指定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;

}

·數(shù)tasklet_kill()用來(lái)將一個(gè)已經(jīng)被調(diào)度了tasklet殺死,即將其恢復(fù)到未調(diào)度的狀態(tài)。其源碼如下所示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對(duì)列

·個(gè)tasklet可以通過(guò)tasklet描述符中next成員指針鏈接成一個(gè)單向?qū)α?。為?/span>Linux專門在頭文include/linux/interrupt.h中定義了數(shù)據(jù)結(jié)構(gòu)tasklet_head來(lái)描述一個(gè)tasklet對(duì)列的頭部指針。如下所示:

/*

* Tasklets

*/

struct tasklet_head

{

struct tasklet_struct *head;

struct tasklet_struct **tail;

};

·tasklet機(jī)制是特定于軟中斷向HI_SOFTIRQTASKLET_SOFTIRQ的一種實(shí)現(xiàn),但tasklet機(jī)制仍然屬softirq機(jī)制的整體框架范圍內(nèi)的,因此,它的設(shè)計(jì)與實(shí)現(xiàn)仍然必須堅(jiān)誰(shuí)觸發(fā),誰(shuí)執(zhí)的思想。為此Linux為系統(tǒng)中的每一個(gè)CPU都定義了一個(gè)tasklet對(duì)列頭部,來(lái)表示應(yīng)該有各個(gè)CPU負(fù)責(zé)執(zhí)行tasklet對(duì)列。如下所示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[]數(shù)組用于軟中斷向TASKLET_SOFTIRQ,tasklet_hi_vec[]數(shù)組則用于軟中斷向HI_SOFTIRQ。也即,如CPUi0iNR_CPUS-1)觸發(fā)了軟中斷向TASKLET_SOFTIRQ,那么對(duì)tasklet_veci]中的每一個(gè)tasklet都將CPUi服務(wù)于軟中斷向TASKLET_SOFTIRQ時(shí)CPUi所執(zhí)行。同樣地,如CPUi0iNR_CPUS-1)觸發(fā)了軟中斷向HI_SOFTIRQ,那么隊(duì)tasklet_hi_veci]中的每一個(gè)taskletCPUi在對(duì)軟中斷向HI_SOFTIRQ進(jìn)行服務(wù)時(shí)CPUi所執(zhí)行。

·隊(duì)tasklet_vecItasklet_hi_vecI]中的各個(gè)tasklet是怎樣被CPUi所執(zhí)行的呢?其關(guān)鍵就是軟中斷向TASKLET_SOFTIRQHI_SOFTIRQ的軟中斷服務(wù)程tasklet_action()函數(shù)tasklet_hi_action()函數(shù)。下面我們就來(lái)分析這兩個(gè)函數(shù)。

3.5軟中斷向TASKLET_SOFTIRQHI_SOFTIRQ

·Linux為軟中斷向TASKLET_SOFTIRQHI_SOFTIRQ實(shí)現(xiàn)了專用的觸發(fā)函數(shù)和軟中斷服務(wù)函數(shù)。

1.專用的觸發(fā)函數(shù)

tasklet_schedule()函數(shù)tasklet_hi_schedule()函數(shù)分別用來(lái)在當(dāng)CPU上觸發(fā)軟中斷向TASKLET_SOFTIRQHI_SOFTIRQ,并把指定tasklet加入當(dāng)CPU所對(duì)應(yīng)tasklet隊(duì)列中去等待執(zhí)行。

2.專用的軟中斷服務(wù)函數(shù)

tasklet_action()函數(shù)tasklet_hi_action()函數(shù)則分別是軟中斷向TASKLET_SOFTIRQHI_SOFTIRQ的軟中斷服務(wù)函數(shù)。在初始化函數(shù)softirq_init()中,這兩個(gè)軟中斷向量對(duì)應(yīng)的描述softirq_vec[0]softirq_vec[6]action函數(shù)指針就被分別初始化成指向函數(shù)tasklet_hi_action()和函數(shù)tasklet_action()。

3.5.1軟中斷向TASKLET_SOFTIRQ的觸發(fā)函數(shù)tasklet_schedule

·該函數(shù)實(shí)現(xiàn)include/linux/interrupt.h頭文件中,是一個(gè)inline函數(shù)。其源碼如下所示:

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); //觸發(fā)軟中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);

}

?調(diào)test_and_set_bit()函數(shù)將待調(diào)度taskletstate成員變量bit0]位(也TASKLET_STATE_SCHED位)設(shè)置1,該函數(shù)同時(shí)還返TASKLET_STATE_SCHED位的原有值。因此如bit0]為的原有值已經(jīng)1,那就說(shuō)明這個(gè)tasklet已經(jīng)被調(diào)度到另一個(gè)CPU上去等待執(zhí)行了。由于一個(gè)tasklet在某一個(gè)時(shí)刻只能由一個(gè)CPU來(lái)執(zhí)行,因tasklet_schedule()函數(shù)什么也不做就直接返回了。否則,就繼續(xù)下面的調(diào)度操作。

.首先,調(diào)local_irq_save()函數(shù)來(lái)關(guān)閉當(dāng)CPU的中斷,以保證下面的步驟在當(dāng)CPU上原子地被執(zhí)行。

.然后,將待調(diào)度tasklet添加到當(dāng)CPU對(duì)應(yīng)tasklet隊(duì)列的尾部。

.接著,調(diào)raise_softirq_irqoff函數(shù)在當(dāng)CPU上觸發(fā)軟中斷請(qǐng)TASKLET_SOFTIRQ。

.最后,調(diào)local_irq_restore()函數(shù)來(lái)開(kāi)當(dāng)CPU的中斷。

3.5.2軟中斷向TASKLET_SOFTIRQ的服務(wù)程tasklet_action

·數(shù)tasklet_action()tasklet機(jī)制與軟中斷向TASKLET_SOFTIRQ的聯(lián)系紐帶。正是該函數(shù)將當(dāng)CPUtasklet隊(duì)列中的各個(gè)tasklet放到當(dāng)CPU上來(lái)執(zhí)行的。該函數(shù)實(shí)現(xiàn)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();

}

}

·首先,在當(dāng)CPU關(guān)中斷的情況下,地讀取當(dāng)CPUtasklet隊(duì)列頭部指針,將其保存到局部變list指針中,然后將當(dāng)CPUtasklet隊(duì)列頭部指針設(shè)置NULL,以表示理論上當(dāng)CPU將不再tasklet需要執(zhí)行(但最后的實(shí)際結(jié)果卻并不一定如此,下面將會(huì)看到)。

.然后,用一個(gè)while{}循環(huán)來(lái)遍歷list所指向tasklet隊(duì)列,隊(duì)列中的各個(gè)元素就是將在當(dāng)CPU上執(zhí)行tasklet。循環(huán)體的執(zhí)行步驟如下:

.用指t來(lái)表示當(dāng)前隊(duì)列元素,即當(dāng)前需要執(zhí)行tasklet。

.list指針list->next,使它指向下一個(gè)要執(zhí)行tasklet。

.tasklet_trylock()宏試圖對(duì)當(dāng)前要執(zhí)行tasklet(由指t所指向)進(jìn)行加鎖,如果加鎖成功(當(dāng)前沒(méi)有任何其CPU正在執(zhí)行這個(gè)tasklet),則用原子讀函數(shù)atomic_read()進(jìn)一步判count成員的值。如count0,說(shuō)明這個(gè)tasklet是允許執(zhí)行的,于是:

(1)先清TASKLET_STATE_SCHED位;

(2)然后,調(diào)用這個(gè)tasklet的可執(zhí)行函數(shù)func;

(3)調(diào)用tasklet_unlock()來(lái)清TASKLET_STATE_RUN

(4)最后,執(zhí)continue語(yǔ)句跳過(guò)下面的步驟,回while循環(huán)繼續(xù)遍歷隊(duì)列中的下一個(gè)元素。如count0,說(shuō)明這個(gè)tasklet是禁止運(yùn)行的,于是調(diào)tasklet_unlock()清除前面tasklet_trylock()設(shè)置TASKLET_STATE_RUN位。

3.6 tasklet使用總結(jié)

1.聲明和使用小任務(wù)大多數(shù)情況下,為了控制一個(gè)常用的硬件設(shè)備,小任務(wù)機(jī)制是實(shí)現(xiàn)下半部的最佳選擇。小任務(wù)可以動(dòng)態(tài)創(chuàng)建,使用方便,執(zhí)行起來(lái)也比較快。我們既可以靜態(tài)地創(chuàng)建小任務(wù),也可以動(dòng)態(tài)地創(chuàng)建它。選擇那種方式取決于到底是想要對(duì)小任務(wù)進(jìn)行直接引用還是一個(gè)間接引用。如果準(zhǔn)備靜態(tài)地創(chuàng)建一個(gè)小任務(wù)(也就是對(duì)它直接引用),使用下面兩個(gè)宏中的一個(gè):

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

·這兩個(gè)宏都能根據(jù)給定的名字靜態(tài)地創(chuàng)建一個(gè)tasklet_struct結(jié)構(gòu)。當(dāng)該小任務(wù)被調(diào)度以后,給定的函數(shù)func會(huì)被執(zhí)行,它的參數(shù)data給出。這兩個(gè)宏之間的區(qū)別在于引用計(jì)數(shù)器的初始值設(shè)置不同。第一個(gè)宏把創(chuàng)建的小任務(wù)的引用計(jì)數(shù)器設(shè)置0,因此,該小任務(wù)處于激活狀態(tài)。另一個(gè)把引用計(jì)數(shù)器設(shè)置1,所以該小任務(wù)處于禁止?fàn)顟B(tài)。例如:

DECLARE_TASKLET(my_tasklet,my_tasklet_handler, dev);
這行代碼其實(shí)等價(jià)于
struct tasklet_struct my_tasklet = { NULL, 0, ATOMIC_INIT(0),
tasklet_handler,dev};

·這樣就創(chuàng)建了一個(gè)名my_tasklet的小任務(wù),其處理程序tasklet_handler,并且已被激活。當(dāng)處理程序被調(diào)用的時(shí)候,dev就會(huì)被傳遞給它。

2.編寫(xiě)自己的小任務(wù)處理程序小任務(wù)處理程序必須符合如下的函數(shù)類型:

void tasklet_handler(unsigned long data)

·由于小任務(wù)不能睡眠,因此不能在小任務(wù)中使用信號(hào)量或者其它產(chǎn)生阻塞的函數(shù)。但是小任務(wù)運(yùn)行時(shí)可以響應(yīng)中斷。

3.調(diào)度自己的小任務(wù)通過(guò)調(diào)tasklet_schedule()函數(shù)并傳遞給它相應(yīng)tasklt_struct指針,該小任務(wù)就會(huì)被調(diào)度以便適當(dāng)?shù)臅r(shí)候執(zhí)行:

tasklet_schedule(&my_tasklet); /*my_tasklet標(biāo)記為掛 */

·在小任務(wù)被調(diào)度以后,只要有機(jī)會(huì)它就會(huì)盡可能早的運(yùn)行。在它還沒(méi)有得到運(yùn)行機(jī)會(huì)之前,如果一個(gè)相同的小任務(wù)又被調(diào)度了,那么它仍然只會(huì)運(yùn)行一次。

·可以調(diào)tasklet_disable()函數(shù)來(lái)禁止某個(gè)指定的小任務(wù)。如果該小任務(wù)當(dāng)前正在執(zhí)行,這個(gè)函數(shù)會(huì)等到它執(zhí)行完畢再返回。調(diào)tasklet_enable()函數(shù)可以激活一個(gè)小任務(wù),如果希望把DECLARE_TASKLET_DISABLED()創(chuàng)建的小任務(wù)激活,也得調(diào)用這個(gè)函數(shù),如:

tasklet_disable(&my_tasklet); /*小任務(wù)現(xiàn)在被禁,這個(gè)小任務(wù)不能運(yùn)*/
tasklet_enable(&my_tasklet); /*小任務(wù)現(xiàn)在被激*/

·也可以調(diào)tasklet_kill()函數(shù)從掛起的隊(duì)列中去掉一個(gè)小任務(wù)。該函數(shù)的參數(shù)是一個(gè)指向某個(gè)小任務(wù)tasklet_struct的長(zhǎng)指針。在小任務(wù)重新調(diào)度它自身的時(shí)候,從掛起的隊(duì)列中移去已調(diào)度的小任務(wù)會(huì)很有用。這個(gè)函數(shù)首先等待該小任務(wù)執(zhí)行完畢,然后再將它移去。

4.tasklet的簡(jiǎn)單用法

·下面tasklet的一個(gè)簡(jiǎn)單應(yīng),以模塊的形成加載。

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

·從這個(gè)例子可以看出,所謂的小任務(wù)機(jī)制是為下半部函數(shù)的執(zhí)行提供了一種執(zhí)行機(jī)制,也就是說(shuō),推遲處理的事情是tasklet_handler實(shí)現(xiàn),何時(shí)執(zhí)行,經(jīng)由小任務(wù)機(jī)制封裝后交給內(nèi)核去處理。

Linux內(nèi)核中斷處理“下半部”機(jī)制(超詳細(xì)~)的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
祁门县| 香港 | 鹤峰县| 桃源县| 延寿县| 喀喇| 保德县| 西平县| 女性| 法库县| 福海县| 留坝县| 海安县| 金塔县| 东兴市| 北海市| 洞头县| 平定县| 庆云县| 天等县| 定陶县| 顺平县| 凤台县| 临邑县| 新乐市| 宁安市| 瓮安县| 兴安盟| 康马县| 措美县| 南江县| 小金县| 嘉善县| 江城| 忻州市| 清徐县| 苍山县| 太保市| 台湾省| 开平市| 思茅市|