Linux內(nèi)核中斷處理“下半部”機(jī)制(超詳細(xì)~)
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ā)。下面主要介紹3種2.6內(nèi)核中的“下半部”處理機(jī)制:
.軟中斷請(qǐng)求(softirq)機(jī)制
.小任務(wù)(tasklet)機(jī)制
.工作隊(duì)列機(jī)制
·以上三種機(jī)制的比較如下圖所示:
2.軟中斷請(qǐng)求(softirq)機(jī)制
·Linux的softirq機(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軟中斷描述符
·Linux在include/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>,Linux在kernel/softirq.c文件中定義了一個(gè)全局的softirq_vec數(shù)組:
·static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
·在這里系統(tǒng)一共定義了10個(gè)軟中斷請(qǐng)求描述符。軟中斷向量i(0≤i≤9)所對(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ù)。Linux在kernel/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ā)和控制變量。為此,Linux在include/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)為id的CPU,那么它只能操作它自己的中斷統(tǒng)計(jì)信息結(jié)構(gòu)irq_stat[id](0≤id≤NR_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_vec[i]中設(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_SOFTIRQ和TASKLET_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)count等于0時(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)的地址值。
·Linux在interrupt.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>其count等于1。
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)才有意義,因此Linux在Interrupt.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 A的TASKLET_STATE_RUN位為1,于是它就可以通過(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_SOFTIRQ和TASKLET_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。也即,如果CPUi(0≤i≤NR_CPUS-1)觸發(fā)了軟中斷向量TASKLET_SOFTIRQ,那么對(duì)列tasklet_vec[i]中的每一個(gè)tasklet都將在CPUi服務(wù)于軟中斷向量TASKLET_SOFTIRQ時(shí)被CPUi所執(zhí)行。同樣地,如果CPUi(0≤i≤NR_CPUS-1)觸發(fā)了軟中斷向量HI_SOFTIRQ,那么隊(duì)列tasklet_hi_vec[i]中的每一個(gè)tasklet都將CPUi在對(duì)軟中斷向量HI_SOFTIRQ進(jìn)行服務(wù)時(shí)被CPUi所執(zhí)行。
·隊(duì)列tasklet_vec[I]和tasklet_hi_vec[I]中的各個(gè)tasklet是怎樣被所CPUi所執(zhí)行的呢?其關(guān)鍵就是軟中斷向量TASKLET_SOFTIRQ和HI_SOFTIRQ的軟中斷服務(wù)程序——tasklet_action()函數(shù)和tasklet_hi_action()函數(shù)。下面我們就來(lái)分析這兩個(gè)函數(shù)。
3.5軟中斷向量TASKLET_SOFTIRQ和HI_SOFTIRQ
·Linux為軟中斷向量TASKLET_SOFTIRQ和HI_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_SOFTIRQ和HI_SOFTIRQ,并把指定的tasklet加入當(dāng)前CPU所對(duì)應(yīng)的tasklet隊(duì)列中去等待執(zhí)行。
2.專用的軟中斷服務(wù)函數(shù)
tasklet_action()函數(shù)和tasklet_hi_action()函數(shù)則分別是軟中斷向量TASKLET_SOFTIRQ和HI_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)度的tasklet的state成員變量的bit[0]位(也即TASKLET_STATE_SCHED位)設(shè)置為1,該函數(shù)同時(shí)還返回TASKLET_STATE_SCHED位的原有值。因此如果bit[0]為的原有值已經(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)前CPU的tasklet隊(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)前CPU的tasklet隊(duì)列頭部指針,將其保存到局部變量list指針中,然后將當(dāng)前CPU的tasklet隊(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成員的值。如果count為0,說(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è)元素。如果count不為0,說(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)核去處理。