Linux內(nèi)核中的軟中斷、tasklet和工作隊(duì)列詳解

軟中斷、tasklet和工作隊(duì)列并不是Linux內(nèi)核中一直存在的機(jī)制傀广,而是由更早版本的內(nèi)核中的“下半部”(bottom half)演變而來(lái)。下半部的機(jī)制實(shí)際上包括五種彩届,但2.6版本的內(nèi)核中伪冰,下半部和任務(wù)隊(duì)列的函數(shù)都消失了,只剩下了前三者樟蠕。 介紹這三種下半部實(shí)現(xiàn)之前贮聂,有必要說(shuō)一下上半部與下半部的區(qū)別。 上半部指的是中斷處理程序寨辩,下半部則指的是一些雖然與中斷有相關(guān)性但是可以延后執(zhí)行的任務(wù)吓懈。舉個(gè)例子:在網(wǎng)絡(luò)傳輸中,網(wǎng)卡接收到數(shù)據(jù)包這個(gè)事件不一定需要馬上被處理靡狞,適合用下半部去實(shí)現(xiàn)耻警;但是用戶敲擊鍵盤這樣的事件就必須馬上被響應(yīng),應(yīng)該用中斷實(shí)現(xiàn)甸怕。 兩者的主要區(qū)別在于:中斷不能被相同類型的中斷打斷甘穿,而下半部依然可以被中斷打斷;中斷對(duì)于時(shí)間非常敏感蕾各,而下半部基本上都是一些可以延遲的工作扒磁。由于二者的這種區(qū)別,所以對(duì)于一個(gè)工作是放在上半部還是放在下半部去執(zhí)行式曲,可以參考下面4條:

如果一個(gè)任務(wù)對(duì)時(shí)間非常敏感妨托,將其放在中斷處理程序中執(zhí)行。

如果一個(gè)任務(wù)和硬件相關(guān)吝羞,將其放在中斷處理程序中執(zhí)行兰伤。

如果一個(gè)任務(wù)要保證不被其他中斷(特別是相同的中斷)打斷,將其放在中斷處理程序中執(zhí)行钧排。

其他所有任務(wù)敦腔,考慮放在下半部去執(zhí)行。 有寫內(nèi)核任務(wù)需要延后執(zhí)行恨溜,因此才有的下半部符衔,進(jìn)而實(shí)現(xiàn)了三種實(shí)現(xiàn)下半部的方法找前。這就是本文要討論的軟中斷tasklet工作隊(duì)列判族。

下表可以更直觀的看到它們之間的關(guān)系躺盛。

軟中斷

軟中斷作為下半部機(jī)制的代表,是隨著SMP(share memory processor)的出現(xiàn)應(yīng)運(yùn)而生的形帮,它也是tasklet實(shí)現(xiàn)的基礎(chǔ)(tasklet實(shí)際上只是在軟中斷的基礎(chǔ)上添加了一定的機(jī)制)槽惫。軟中斷一般是“可延遲函數(shù)”的總稱,有時(shí)候也包括了tasklet(請(qǐng)讀者在遇到的時(shí)候根據(jù)上下文推斷是否包含tasklet)辩撑。它的出現(xiàn)就是因?yàn)橐獫M足上面所提出的上半部和下半部的區(qū)別界斜,使得對(duì)時(shí)間不敏感的任務(wù)延后執(zhí)行,而且可以在多個(gè)CPU上并行執(zhí)行合冀,使得總的系統(tǒng)效率可以更高各薇。它的特性包括:

產(chǎn)生后并不是馬上可以執(zhí)行,必須要等待內(nèi)核的調(diào)度才能執(zhí)行水慨。軟中斷不能被自己打斷(即單個(gè)cpu上軟中斷不能嵌套執(zhí)行)得糜,只能被硬件中斷打斷(上半部)敬扛。

可以并發(fā)運(yùn)行在多個(gè)CPU上(即使同一類型的也可以)晰洒。所以軟中斷必須設(shè)計(jì)為可重入的函數(shù)(允許多個(gè)CPU同時(shí)操作),因此也需要使用自旋鎖來(lái)保其數(shù)據(jù)結(jié)構(gòu)啥箭。

相關(guān)數(shù)據(jù)結(jié)構(gòu)

軟中斷描述符?struct softirq_action{ void (*action)(struct softirq_action *);};?描述每一種類型的軟中斷谍珊,其中void(*action)是軟中斷觸發(fā)時(shí)的執(zhí)行函數(shù)。

軟中斷全局?jǐn)?shù)據(jù)和類型

staticstructsoftirq_actionsoftirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;enum{HI_SOFTIRQ=0,/*用于高優(yōu)先級(jí)的tasklet*/TIMER_SOFTIRQ,/*用于定時(shí)器的下半部*/NET_TX_SOFTIRQ,/*用于網(wǎng)絡(luò)層發(fā)包*/NET_RX_SOFTIRQ,/*用于網(wǎng)絡(luò)層收?qǐng)?bào)*/BLOCK_SOFTIRQ,? ? ? ? BLOCK_IOPOLL_SOFTIRQ,? ? ? ? TASKLET_SOFTIRQ,/*用于低優(yōu)先級(jí)的tasklet*/SCHED_SOFTIRQ,? ? ? ? HRTIMER_SOFTIRQ,? ? ? ? RCU_SOFTIRQ,/* Preferable RCU should always be the last softirq */NR_SOFTIRQS? ? };

相關(guān)API

注冊(cè)軟中斷

void open_softirq(int nr, void (*action)(struct softirq_action *))

即注冊(cè)對(duì)應(yīng)類型的處理函數(shù)到全局?jǐn)?shù)組softirq_vec中急侥。例如網(wǎng)絡(luò)發(fā)包對(duì)應(yīng)類型為NET_TX_SOFTIRQ的處理函數(shù)net_tx_action.

觸發(fā)軟中斷

void raise_softirq(unsigned int nr)

實(shí)際上即以軟中斷類型nr作為偏移量置位每cpu變量irq_stat[cpu_id]的成員變量__softirq_pending砌滞,這也是同一類型軟中斷可以在多個(gè)cpu上并行運(yùn)行的根本原因。

軟中斷執(zhí)行函數(shù)

do_softirq-->__do_softirq

執(zhí)行軟中斷處理函數(shù)__do_softirq前首先要滿足兩個(gè)條件: (1)不在中斷中(硬中斷坏怪、軟中斷和NMI) 贝润。1 (2)有軟中斷處于pending狀態(tài)。 系統(tǒng)這么設(shè)計(jì)是為了避免軟件中斷在中斷嵌套中被調(diào)用铝宵,并且達(dá)到在單個(gè)CPU上軟件中斷不能被重入的目的打掘。對(duì)于ARM架構(gòu)的CPU不存在中斷嵌套中調(diào)用軟件中斷的問(wèn)題,因?yàn)锳RM架構(gòu)的CPU在處理硬件中斷的過(guò)程中是關(guān)閉掉中斷的鹏秋。只有在進(jìn)入了軟中斷處理過(guò)程中之后才會(huì)開(kāi)啟硬件中斷尊蚁,如果在軟件中斷處理過(guò)程中有硬件中斷嵌套,也不會(huì)再次調(diào)用軟中斷侣夷,because硬件中斷是軟件中斷處理過(guò)程中再次進(jìn)入的横朋,此時(shí)preempt_count已經(jīng)記錄了軟件中斷!對(duì)于其它架構(gòu)的CPU百拓,有可能在觸發(fā)調(diào)用軟件中斷前琴锭,也就是還在處理硬件中斷的時(shí)候晰甚,就已經(jīng)開(kāi)啟了硬件中斷,可能會(huì)發(fā)生中斷嵌套决帖,在中斷嵌套中是不允許調(diào)用軟件中斷處理的压汪。Why?我的理解是古瓤,在發(fā)生中斷嵌套的時(shí)候止剖,表明這個(gè)時(shí)候是系統(tǒng)突發(fā)繁忙的時(shí)候,內(nèi)核第一要?jiǎng)?wù)就是趕緊把中斷中的事情處理完成落君,退出中斷嵌套穿香。避免多次嵌套,哪里有時(shí)間處理軟件中斷绎速,所以把軟件中斷推遲到了所有中斷處理完成的時(shí)候才能觸發(fā)軟件中斷皮获。

需要C/C++ Linux服務(wù)器架構(gòu)師學(xué)習(xí)資料加群563998835(資料包括C/C++,Linux纹冤,golang技術(shù)洒宝,Nginx,ZeroMQ萌京,MySQL雁歌,Redis,fastdfs知残,MongoDB靠瞎,ZK,流媒體求妹,CDN乏盐,P2P,K8S制恍,Docker父能,TCP/IP,協(xié)程净神,DPDK何吝,ffmpeg等),免費(fèi)分享

實(shí)現(xiàn)原理和實(shí)例

軟中斷的調(diào)度時(shí)機(jī):

do_irq完成I/O中斷時(shí)調(diào)用irq_exit强挫。

系統(tǒng)使用I/O APIC,在處理完本地時(shí)鐘中斷時(shí)岔霸。

local_bh_enable,即開(kāi)啟本地軟中斷時(shí)俯渤。

SMP系統(tǒng)中呆细,cpu處理完被CALL_FUNCTION_VECTOR處理器間中斷所觸發(fā)的函數(shù)時(shí)。

ksoftirqd/n線程被喚醒時(shí)。 下面以從中斷處理返回函數(shù)irq_exit中調(diào)用軟中斷為例詳細(xì)說(shuō)明絮爷。 觸發(fā)和初始化的的流程如圖所示:

軟中斷處理流程

asmlinkage void __do_softirq(void){? ? struct softirq_action *h;? ? __u32 pending;intmax_restart = MAX_SOFTIRQ_RESTART;intcpu;? ? pending = local_softirq_pending();? ? account_system_vtime(current);? ? __local_bh_disable((unsigned long)__builtin_return_address(0));? ? lockdep_softirq_enter();? ? cpu = smp_processor_id();restart:/* Reset the pending bitmask before enabling irqs */set_softirq_pending(0);? ? local_irq_enable();? ? h = softirq_vec;do{if(pending &1) {intprev_count = preempt_count();? ? ? ? ? ? kstat_incr_softirqs_this_cpu(h - softirq_vec);? ? ? ? ? ? trace_softirq_entry(h, softirq_vec);? ? ? ? ? ? h->action(h);? ? ? ? ? ? trace_softirq_exit(h, softirq_vec);if(unlikely(prev_count != preempt_count())) {? ? ? ? ? ? ? ? printk(KERN_ERR"huh, entered softirq %td %s %p""with preempt_count %08x,"" exited with %08x?\n", h - softirq_vec,? ? ? ? ? ? ? ? ? ? ? softirq_to_name[h - softirq_vec],? ? ? ? ? ? ? ? ? ? ? h->action, prev_count, preempt_count());? ? ? ? ? ? ? ? preempt_count() = prev_count;? ? ? ? ? ? }? ? ? ? ? ? rcu_bh_qs(cpu);? ? ? ? }? ? ? ? h++;? ? ? ? pending >>=1;? ? }while(pending);? ? local_irq_disable();? ? pending = local_softirq_pending();if(pending && --max_restart)gotorestart;if(pending)? ? ? ? wakeup_softirqd();? ? lockdep_softirq_exit();? ? account_system_vtime(current);? ? _local_bh_enable();}

首先調(diào)用local_softirq_pending函數(shù)取得目前有哪些位存在軟件中斷趴酣。

調(diào)用__local_bh_disable關(guān)閉軟中斷,其實(shí)就是設(shè)置正在處理軟件中斷標(biāo)記坑夯,在同一個(gè)CPU上使得不能重入__do_softirq函數(shù)岖寞。

重新設(shè)置軟中斷標(biāo)記為0,set_softirq_pending重新設(shè)置軟中斷標(biāo)記為0柜蜈,這樣在之后重新開(kāi)啟中斷之后硬件中斷中又可以設(shè)置軟件中斷位仗谆。

調(diào)用local_irq_enable,開(kāi)啟硬件中斷淑履。

之后在一個(gè)循環(huán)中隶垮,遍歷pending標(biāo)志的每一位,如果這一位設(shè)置就會(huì)調(diào)用軟件中斷的處理函數(shù)秘噪。在這個(gè)過(guò)程中硬件中斷是開(kāi)啟的狸吞,隨時(shí)可以打斷軟件中斷。這樣保證硬件中斷不會(huì)丟失指煎。

之后關(guān)閉硬件中斷(local_irq_disable)蹋偏,查看是否又有軟件中斷處于pending狀態(tài),如果是至壤,并且在本次調(diào)用__do_softirq函數(shù)過(guò)程中沒(méi)有累計(jì)重復(fù)進(jìn)入軟件中斷處理的次數(shù)超過(guò)max_restart=10次威始,就可以重新調(diào)用軟件中斷處理。如果超過(guò)了10次崇渗,就調(diào)用wakeup_softirqd()喚醒內(nèi)核的一個(gè)進(jìn)程來(lái)處理軟件中斷字逗。設(shè)立10次的限制京郑,也是為了避免影響系統(tǒng)響應(yīng)時(shí)間宅广。

調(diào)用_local_bh_enable開(kāi)啟軟中斷。

軟中斷內(nèi)核線程

之前我們分析的觸發(fā)軟件中斷的位置其實(shí)是中斷上下文中些举,而在軟中斷的內(nèi)核線程中實(shí)際已經(jīng)是進(jìn)程的上下文跟狱。 這里說(shuō)的軟中斷上下文指的就是系統(tǒng)為每個(gè)CPU建立的ksoftirqd進(jìn)程。 軟中斷的內(nèi)核進(jìn)程中主要有兩個(gè)大循環(huán)户魏,外層的循環(huán)處理有軟件中斷就處理驶臊,沒(méi)有軟件中斷就休眠。內(nèi)層的循環(huán)處理軟件中斷叼丑,每循環(huán)一次都試探一次是否過(guò)長(zhǎng)時(shí)間占據(jù)了CPU关翎,需要調(diào)度就釋放CPU給其它進(jìn)程。具體的操作在注釋中做了解釋鸠信。

set_current_state(TASK_INTERRUPTIBLE);//外層大循環(huán)纵寝。while(!kthread_should_stop()) {preempt_disable();//禁止內(nèi)核搶占,自己掌握cpuif(!local_softirq_pending()) {preempt_enable_no_resched();//如果沒(méi)有軟中斷在pending中就讓出cpuschedule();//調(diào)度之后重新掌握cpupreempt_disable();? ? ? ? }__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 */if(cpu_is_offline((long)__bind_cpu))gotowait_to_die;//有軟中斷則開(kāi)始軟中斷調(diào)度do_softirq();//查看是否需要調(diào)度星立,避免一直占用cpupreempt_enable_no_resched();cond_resched();preempt_disable();rcu_sched_qs((long)__bind_cpu);? ? ? ? }preempt_enable();set_current_state(TASK_INTERRUPTIBLE);? ? }__set_current_state(TASK_RUNNING);return0;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);return0;

tasklet

由于軟中斷必須使用可重入函數(shù)爽茴,這就導(dǎo)致設(shè)計(jì)上的復(fù)雜度變高葬凳,作為設(shè)備驅(qū)動(dòng)程序的開(kāi)發(fā)者來(lái)說(shuō),增加了負(fù)擔(dān)室奏。而如果某種應(yīng)用并不需要在多個(gè)CPU上并行執(zhí)行火焰,那么軟中斷其實(shí)是沒(méi)有必要的。因此誕生了彌補(bǔ)以上兩個(gè)要求的tasklet胧沫。它具有以下特性: a)一種特定類型的tasklet只能運(yùn)行在一個(gè)CPU上昌简,不能并行,只能串行執(zhí)行绒怨。 b)多個(gè)不同類型的tasklet可以并行在多個(gè)CPU上江场。 c)軟中斷是靜態(tài)分配的,在內(nèi)核編譯好之后窖逗,就不能改變址否。但tasklet就靈活許多,可以在運(yùn)行時(shí)改變(比如添加模塊時(shí))碎紊。 tasklet是在兩種軟中斷類型的基礎(chǔ)上實(shí)現(xiàn)的佑附,因此如果不需要軟中斷的并行特性,tasklet就是最好的選擇仗考。也就是說(shuō)tasklet是軟中斷的一種特殊用法音同,即延遲情況下的串行執(zhí)行

相關(guān)數(shù)據(jù)結(jié)構(gòu)

tasklet描述符

structtasklet_struct{structtasklet_struct*next;//將多個(gè)tasklet鏈接成單向循環(huán)鏈表unsignedlongstate;//TASKLET_STATE_SCHED(Tasklet is scheduled for execution)? TASKLET_STATE_RUN(Tasklet is running (SMP only))atomic_tcount;//0:激活tasklet 非0:禁用taskletvoid(*func)(unsignedlong);//用戶自定義函數(shù)unsignedlongdata;//函數(shù)入?yún);

tasklet鏈表

staticDEFINE_PER_CPU(structtasklet_head, tasklet_vec);//低優(yōu)先級(jí)staticDEFINE_PER_CPU(structtasklet_head, tasklet_hi_vec);//高優(yōu)先級(jí)

相關(guān)API

定義tasklet

#define DECLARE_TASKLET(name,func,data) \structtasklet_struct name = { NULL,0, ATOMIC_INIT(0),func,data}//定義名字為name的非激活tasklet#define DECLARE_TASKLET_DISABLED(name,func,data) \structtasklet_struct name = { NULL,0, ATOMIC_INIT(1),func,data}//定義名字為name的激活taskletvoid tasklet_init(structtasklet_struct *t,void (*func)(unsigned long),unsignedlongdata)//動(dòng)態(tài)初始化tasklet

tasklet操作

staticinlinevoidtasklet_disable(structtasklet_struct *t)//函數(shù)暫時(shí)禁止給定的tasklet被tasklet_schedule調(diào)度秃嗜,直到這個(gè)tasklet被再次被enable权均;若這個(gè)tasklet當(dāng)前在運(yùn)行, 這個(gè)函數(shù)忙等待直到這個(gè)tasklet退出staticinlinevoidtasklet_enable(structtasklet_struct *t)//使能一個(gè)之前被disable的tasklet;若這個(gè)tasklet已經(jīng)被調(diào)度, 它會(huì)很快運(yùn)行锅锨。tasklet_enable和tasklet_disable必須匹配調(diào)用, 因?yàn)閮?nèi)核跟蹤每個(gè)tasklet的"禁止次數(shù)"staticinlinevoidtasklet_schedule(structtasklet_struct *t)//調(diào)度 tasklet 執(zhí)行叽赊,如果tasklet在運(yùn)行中被調(diào)度, 它在完成后會(huì)再次運(yùn)行; 這保證了在其他事件被處理當(dāng)中發(fā)生的事件受到應(yīng)有的注意. 這個(gè)做法也允許一個(gè) tasklet 重新調(diào)度它自己tasklet_hi_schedule(structtasklet_struct *t)//和tasklet_schedule類似,只是在更高優(yōu)先級(jí)執(zhí)行必搞。當(dāng)軟中斷處理運(yùn)行時(shí), 它處理高優(yōu)先級(jí) tasklet 在其他軟中斷之前必指,只有具有低響應(yīng)周期要求的驅(qū)動(dòng)才應(yīng)使用這個(gè)函數(shù), 可避免其他軟件中斷處理引入的附加周期.tasklet_kill(structtasklet_struct *t)//確保了 tasklet 不會(huì)被再次調(diào)度來(lái)運(yùn)行,通常當(dāng)一個(gè)設(shè)備正被關(guān)閉或者模塊卸載時(shí)被調(diào)用恕洲。如果 tasklet 正在運(yùn)行, 這個(gè)函數(shù)等待直到它執(zhí)行完畢塔橡。若 tasklet 重新調(diào)度它自己,則必須阻止在調(diào)用 tasklet_kill 前它重新調(diào)度它自己霜第,如同使用 del_timer_sync

實(shí)現(xiàn)原理

調(diào)度原理

staticinlinevoidtasklet_schedule(structtasklet_struct *t){if(!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))? ? ? ? __tasklet_schedule(t);}void__tasklet_schedule(structtasklet_struct *t){unsignedlongflags;? ? local_irq_save(flags);? ? t->next =NULL;? ? *__get_cpu_var(tasklet_vec).tail = t;? ? __get_cpu_var(tasklet_vec).tail = &(t->next);//加入低優(yōu)先級(jí)列表raise_softirq_irqoff(TASKLET_SOFTIRQ);//觸發(fā)軟中斷l(xiāng)ocal_irq_restore(flags);}

tasklet執(zhí)行過(guò)程 TASKLET_SOFTIRQ對(duì)應(yīng)執(zhí)行函數(shù)為tasklet_action葛家,HI_SOFTIRQ為tasklet_hi_action,以tasklet_action為例說(shuō)明泌类,tasklet_hi_action大同小異癞谒。

staticvoid tasklet_action(struct softirq_action *a){? ? struct tasklet_struct *list;? ? local_irq_disable();list= __get_cpu_var(tasklet_vec).head;? ? __get_cpu_var(tasklet_vec).head =NULL;? ? __get_cpu_var(tasklet_vec).tail = &__get_cpu_var(tasklet_vec).head;//取得tasklet鏈表local_irq_enable();while(list) {? ? ? ? struct tasklet_struct *t =list;list=list->next;if(tasklet_trylock(t)) {if(!atomic_read(&t->count)) {//執(zhí)行taskletif(!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))? ? ? ? ? ? ? ? ? ? BUG();? ? ? ? ? ? ? ? t->func(t->data);? ? ? ? ? ? ? ? tasklet_unlock(t);continue;? ? ? ? ? ? }? ? ? ? ? ? tasklet_unlock(t);? ? ? ? }//如果t->count的值不等于0,說(shuō)明這個(gè)tasklet在調(diào)度之后,被disable掉了扯俱,所以會(huì)將tasklet結(jié)構(gòu)體重新放回到tasklet_vec鏈表书蚪,并重新調(diào)度TASKLET_SOFTIRQ軟中斷,在之后enable這個(gè)tasklet之后重新再執(zhí)行它local_irq_disable();? ? ? ? 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_enable();? ? }}

工作隊(duì)列

從上面的介紹看以看出迅栅,軟中斷運(yùn)行在中斷上下文中殊校,因此不能阻塞和睡眠,而tasklet使用軟中斷實(shí)現(xiàn)读存,當(dāng)然也不能阻塞和睡眠为流。但如果某延遲處理函數(shù)需要睡眠或者阻塞呢?沒(méi)關(guān)系工作隊(duì)列就可以如您所愿了让簿。 把推后執(zhí)行的任務(wù)叫做工作(work)敬察,描述它的數(shù)據(jù)結(jié)構(gòu)為work_struct ,這些工作以隊(duì)列結(jié)構(gòu)組織成工作隊(duì)列(workqueue)尔当,其數(shù)據(jù)結(jié)構(gòu)為workqueue_struct 莲祸,而工作線程就是負(fù)責(zé)執(zhí)行工作隊(duì)列中的工作。系統(tǒng)默認(rèn)的工作者線程為events椭迎。 工作隊(duì)列(work queue)是另外一種將工作推后執(zhí)行的形式锐帜。工作隊(duì)列可以把工作推后,交由一個(gè)內(nèi)核線程去執(zhí)行—這個(gè)下半部分總是會(huì)在進(jìn)程上下文執(zhí)行畜号,但由于是內(nèi)核線程缴阎,其不能訪問(wèn)用戶空間。最重要特點(diǎn)的就是工作隊(duì)列允許重新調(diào)度甚至是睡眠简软。 通常蛮拔,在工作隊(duì)列和軟中斷/tasklet中作出選擇非常容易”陨可使用以下規(guī)則: - 如果推后執(zhí)行的任務(wù)需要睡眠建炫,那么只能選擇工作隊(duì)列。 - 如果推后執(zhí)行的任務(wù)需要延時(shí)指定的時(shí)間再觸發(fā)视卢,那么使用工作隊(duì)列踱卵,因?yàn)槠淇梢岳胻imer延時(shí)(內(nèi)核定時(shí)器實(shí)現(xiàn))。 - 如果推后執(zhí)行的任務(wù)需要在一個(gè)tick之內(nèi)處理据过,則使用軟中斷或tasklet,因?yàn)槠淇梢該屨计胀ㄟM(jìn)程和內(nèi)核線程妒挎,同時(shí)不可睡眠绳锅。 - 如果推后執(zhí)行的任務(wù)對(duì)延遲的時(shí)間沒(méi)有任何要求,則使用工作隊(duì)列酝掩,此時(shí)通常為無(wú)關(guān)緊要的任務(wù)鳞芙。 實(shí)際上,工作隊(duì)列的本質(zhì)就是將工作交給內(nèi)核線程處理,因此其可以用內(nèi)核線程替換原朝。但是內(nèi)核線程的創(chuàng)建和銷毀對(duì)編程者的要求較高驯嘱,而工作隊(duì)列實(shí)現(xiàn)了內(nèi)核線程的封裝,不易出錯(cuò)喳坠,所以我們也推薦使用工作隊(duì)列鞠评。

相關(guān)數(shù)據(jù)結(jié)構(gòu)

正常工作結(jié)構(gòu)體

structwork_struct{atomic_long_tdata;//傳遞給工作函數(shù)的參數(shù)#defineWORK_STRUCT_PENDING 0/* T if work item pending execution */#defineWORK_STRUCT_FLAG_MASK (3UL)#defineWORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)structlist_headentry;//鏈表結(jié)構(gòu),鏈接同一工作隊(duì)列上的工作壕鹉。work_func_tfunc;//工作函數(shù)剃幌,用戶自定義實(shí)現(xiàn)#ifdefCONFIG_LOCKDEPstructlockdep_maplockdep_map;#endif};//工作隊(duì)列執(zhí)行函數(shù)的原型:void(*work_func_t)(struct work_struct *work);//該函數(shù)會(huì)由一個(gè)工作者線程執(zhí)行,因此其在進(jìn)程上下文中晾浴,可以睡眠也可以中斷负乡。但只能在內(nèi)核中運(yùn)行,無(wú)法訪問(wèn)用戶空間脊凰。

延遲工作結(jié)構(gòu)體(延遲的實(shí)現(xiàn)是在調(diào)度時(shí)延遲插入相應(yīng)的工作隊(duì)列)

structdelayed_work{structwork_structwork;structtimer_listtimer;//定時(shí)器抖棘,用于實(shí)現(xiàn)延遲處理};

工作隊(duì)列結(jié)構(gòu)體

structworkqueue_struct{structcpu_workqueue_struct*cpu_wq;//指針數(shù)組,其每個(gè)元素為per-cpu的工作隊(duì)列structlist_headlist;constchar*name;intsinglethread;//標(biāo)記是否只創(chuàng)建一個(gè)工作者線程intfreezeable;/* Freeze threads during suspend */intrt;#ifdefCONFIG_LOCKDEPstructlockdep_maplockdep_map;#endif};

每cpu工作隊(duì)列(每cpu都對(duì)應(yīng)一個(gè)工作者線程worker_thread)

structcpu_workqueue_struct{spinlock_tlock;structlist_headworklist;wait_queue_head_tmore_work;structwork_struct*current_work;structworkqueue_struct*wq;structtask_struct*thread;} ____cacheline_aligned;

相關(guān)API

缺省工作隊(duì)列

靜態(tài)創(chuàng)建 DECLARE_WORK(name,function);//定義正常執(zhí)行的工作項(xiàng)DECLARE_DELAYED_WORK(name,function);//定義延后執(zhí)行的工作項(xiàng)動(dòng)態(tài)創(chuàng)建INIT_WORK(_work, _func)//創(chuàng)建正常執(zhí)行的工作項(xiàng)INIT_DELAYED_WORK(_work, _func)//創(chuàng)建延后執(zhí)行的工作項(xiàng)調(diào)度默認(rèn)工作隊(duì)列intschedule_work(structwork_struct *work)//對(duì)正常執(zhí)行的工作進(jìn)行調(diào)度狸涌,即把給定工作的處理函數(shù)提交給缺省的工作隊(duì)列和工作者線程钉答。工作者線程本質(zhì)上是一個(gè)普通的內(nèi)核線程,在默認(rèn)情況下杈抢,每個(gè)CPU均有一個(gè)類型為“events”的工作者線程数尿,當(dāng)調(diào)用schedule_work時(shí),這個(gè)工作者線程會(huì)被喚醒去執(zhí)行工作鏈表上的所有工作惶楼。系統(tǒng)默認(rèn)的工作隊(duì)列名稱是:keventd_wq,默認(rèn)的工作者線程叫:events/n右蹦,這里的n是處理器的編號(hào),每個(gè)處理器對(duì)應(yīng)一個(gè)線程。比如歼捐,單處理器的系統(tǒng)只有events/0這樣一個(gè)線程何陆。而雙處理器的系統(tǒng)就會(huì)多一個(gè)events/1線程。默認(rèn)的工作隊(duì)列和工作者線程由內(nèi)核初始化時(shí)創(chuàng)建:start_kernel()-->rest_init-->do_basic_setup-->init_workqueues調(diào)度延遲工作intschedule_delayed_work(structdelayed_work *dwork,unsignedlongdelay)刷新缺省工作隊(duì)列voidflush_scheduled_work(void)//此函數(shù)會(huì)一直等待豹储,直到隊(duì)列中的所有工作都被執(zhí)行贷盲。取消延遲工作staticinlineintcancel_delayed_work(structdelayed_work *work)//flush_scheduled_work并不取消任何延遲執(zhí)行的工作,因此剥扣,如果要取消延遲工作巩剖,應(yīng)該調(diào)用cancel_delayed_work。

以上均是采用缺省工作者線程來(lái)實(shí)現(xiàn)工作隊(duì)列钠怯,其優(yōu)點(diǎn)是簡(jiǎn)單易用佳魔,缺點(diǎn)是如果缺省工作隊(duì)列負(fù)載太重,執(zhí)行效率會(huì)很低晦炊,這就需要我們創(chuàng)建自己的工作者線程和工作隊(duì)列鞠鲜。

自定義工作隊(duì)列

create_workqueue(name)//宏定義 返回值為工作隊(duì)列宁脊,name為工作線程名稱。創(chuàng)建新的工作隊(duì)列和相應(yīng)的工作者線程贤姆,name用于該內(nèi)核線程的命名榆苞。intqueue_work(structworkqueue_struct *wq,structwork_struct *work)//類似于schedule_work,區(qū)別在于queue_work把給定工作提交給創(chuàng)建的工作隊(duì)列wq而不是缺省隊(duì)列霞捡。intqueue_delayed_work(structworkqueue_struct *wq,structdelayed_work *dwork,unsignedlongdelay)//調(diào)度延遲工作坐漏。voidflush_workqueue(structworkqueue_struct *wq)//刷新指定工作隊(duì)列。voiddestroy_workqueue(structworkqueue_struct *wq)//釋放創(chuàng)建的工作隊(duì)列弄砍。

實(shí)現(xiàn)原理

工作隊(duì)列的組織結(jié)構(gòu) 即workqueue_struct仙畦、cpu_workqueue_struct與work_struct的關(guān)系。 一個(gè)工作隊(duì)列對(duì)應(yīng)一個(gè)work_queue_struct音婶,工作隊(duì)列中每cpu的工作隊(duì)列由cpu_workqueue_struct表示慨畸,而work_struct為其上的具體工作。 關(guān)系如下圖所示:

2.工作隊(duì)列的工作過(guò)程

應(yīng)用實(shí)例 linux各個(gè)接口的狀態(tài)(up/down)的消息需要通知netdev_chain上感興趣的模塊同時(shí)上報(bào)用戶空間消息衣式。這里使用的就是工作隊(duì)列寸士。 具體流程圖如下所示:

是否處于中斷中在Linux中是通過(guò)preempt_count來(lái)判斷的,具體如下: 在linux系統(tǒng)的進(jìn)程數(shù)據(jù)結(jié)構(gòu)里,有這么一個(gè)數(shù)據(jù)結(jié)構(gòu): #define preempt_count() (current_thread_info()->preempt_count) 利用preempt_count可以表示是否處于中斷處理或者軟件中斷處理過(guò)程中,如下所示: # define hardirq_count() (preempt_count() & HARDIRQ_MASK) #define softirq_count() (preempt_count() & SOFTIRQ_MASK) #define irq_count() (preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK | NMI_MASK)) #define in_irq() (hardirq_count()) #define in_softirq() (softirq_count()) #define in_interrupt() (irq_count())

preempt_count的8~23位記錄中斷處理和軟件中斷處理過(guò)程的計(jì)數(shù)碴卧。如果有計(jì)數(shù)弱卡,表示系統(tǒng)在硬件中斷或者軟件中斷處理過(guò)程中。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末住册,一起剝皮案震驚了整個(gè)濱河市婶博,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌荧飞,老刑警劉巖凡人,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異叹阔,居然都是意外死亡挠轴,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門耳幢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)岸晦,“玉大人,你說(shuō)我怎么就攤上這事睛藻∑羯希” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵修档,是天一觀的道長(zhǎng)碧绞。 經(jīng)常有香客問(wèn)我,道長(zhǎng)吱窝,這世上最難降的妖魔是什么讥邻? 我笑而不...
    開(kāi)封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮院峡,結(jié)果婚禮上兴使,老公的妹妹穿的比我還像新娘。我一直安慰自己照激,他們只是感情好发魄,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著俩垃,像睡著了一般励幼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上口柳,一...
    開(kāi)封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天苹粟,我揣著相機(jī)與錄音,去河邊找鬼跃闹。 笑死嵌削,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的望艺。 我是一名探鬼主播苛秕,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼找默!你這毒婦竟也來(lái)了艇劫?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤惩激,失蹤者是張志新(化名)和其女友劉穎店煞,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體咧欣,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡浅缸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了魄咕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片衩椒。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖哮兰,靈堂內(nèi)的尸體忽然破棺而出毛萌,到底是詐尸還是另有隱情,我是刑警寧澤喝滞,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布阁将,位于F島的核電站,受9級(jí)特大地震影響右遭,放射性物質(zhì)發(fā)生泄漏做盅。R本人自食惡果不足惜缤削,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望吹榴。 院中可真熱鬧亭敢,春花似錦、人聲如沸图筹。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)远剩。三九已至扣溺,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間瓜晤,已是汗流浹背锥余。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留活鹰,地道東北人哈恰。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像志群,于是被迫代替她去往敵國(guó)和親着绷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355