第8章 下半部和推后執(zhí)行的工作

操作系統(tǒng)必須有一個快速逞带、異步欺矫、簡單的機(jī)制負(fù)責(zé)對硬件做出迅速響應(yīng)并完成那些時間要求很嚴(yán)格的操作,中斷處理程序很適合用于實(shí)現(xiàn)這些功能展氓;然而對于其他對時間要求相對寬松的任務(wù)穆趴,就應(yīng)該推后到中斷被激活以后再去運(yùn)行。

一遇汞、下半部

下半部的任務(wù)就是執(zhí)行與中斷處理密切相關(guān)但中斷處理程序本身不執(zhí)行的工作未妹,這對系統(tǒng)的響應(yīng)能力和性能都至關(guān)重要。并沒有規(guī)定如何在上半部和下半部劃分工作空入,但有以下經(jīng)驗(yàn):

  • 若任務(wù)對時間非常敏感络它,將其放在中斷處理程序中執(zhí)行
  • 若任務(wù)和硬件相關(guān),將其放在中斷處理程序中執(zhí)行
  • 若任務(wù)要保證不被其他中斷(特別是相同的中斷)中斷歪赢,將其放在中斷處理程序中執(zhí)行
  • 其他所有任務(wù)化戳,考慮放在下半部執(zhí)行

通常下半部在中斷處理程序一返回就會馬上執(zhí)行,下半部執(zhí)行的關(guān)鍵在于當(dāng)它們運(yùn)行時允許響應(yīng)所有的中斷埋凯。

當(dāng)前点楼,有三種機(jī)制可以用來實(shí)現(xiàn)將工作推后執(zhí)行:

  • 軟中斷
  • tasklet
  • 工作隊列

二、軟中斷

2.1 軟中斷的實(shí)現(xiàn)

軟中斷是在編譯期間靜態(tài)分配的白对,最多有32個軟中斷掠廓,其結(jié)構(gòu):

struct softirq_action {
  void (*action) (struct softirq_action *);
};

static struct softirq_action softirq_vec[NR_SOFTIRQS]; //NR_SOFTIRQS=32

軟中斷處理程序:

void softirq_handler(struct softirq_action *);

一個軟中斷不會搶占另一個軟中斷。唯一可以搶占軟中斷的是中斷處理程序甩恼。不過蟀瞧,其他軟中斷(包括相同類型的軟中斷)可以在其他處理器上同時執(zhí)行。


執(zhí)行軟中斷:一個注冊的軟中斷必須在被標(biāo)記后才會執(zhí)行媳拴。這被稱為觸發(fā)軟中斷黄橘。通常中斷處理程序在返回前標(biāo)記它的軟中斷,使其在稍后被執(zhí)行屈溉。于是在合適的時刻塞关,該軟中斷就會被運(yùn)行:

  • 從一個硬件中斷代碼返回時
  • 在ksoftirqd內(nèi)核線程中
  • 在那些顯示檢查和執(zhí)行待處理的軟中斷的代碼中,如網(wǎng)絡(luò)子系統(tǒng)中

軟中斷最后都在do_softirq()中執(zhí)行子巾,其會循環(huán)遍歷每一個待處理的軟中斷帆赢,調(diào)用它們的處理程序小压。

2.2 使用軟中斷

軟中斷保留給系統(tǒng)中對時間要求最嚴(yán)格以及最重要的下半部使用。目前只有兩個子系統(tǒng)(網(wǎng)絡(luò)和SCSI)直接使用軟中斷椰于。此外怠益,內(nèi)核定時器和tasklet都是建立在軟中斷上。

在編譯期間瘾婿,通過一個枚舉類型來靜態(tài)聲明軟中斷蜻牢,并通過索引值來表示相對優(yōu)先級,索引號小的軟中斷在索引號大的軟中斷之前執(zhí)行偏陪。


image.png

注冊軟中斷處理程序:

open_softirq(軟中斷索引號抢呆,處理函數(shù))

例如:
open_softirq(NET_TX_SOFTIRQ, next_tx_action);
open_softirq(NET_RX_SOFTIRQ, next_rx_action);

軟中斷處理程序執(zhí)行的時候,允許響應(yīng)中斷笛谦,但它自己不能休眠抱虐。在一個處理程序運(yùn)行的時候,當(dāng)前處理器上的軟中斷被終止饥脑。但其他的處理器仍可以執(zhí)行別的軟中斷恳邀。實(shí)際上,如果同一個軟中斷在被執(zhí)行的同時再被觸發(fā)了灶轰,那么另一個處理器可以同時處理其處理程序谣沸,這意味著任何共享數(shù)據(jù)都需要嚴(yán)格的鎖保護(hù)。


觸發(fā)軟中斷:將一個軟中斷設(shè)置為掛起狀態(tài)框往,讓它在下次調(diào)用do_softirq()函數(shù)時投入運(yùn)行鳄抒。

raise_softirq(NET_TX_SOFTIRQ);

若中斷被禁止了:
raise_softirq_irqoff(NET_TX_SOFTIRQ);

在中斷處理程序中觸發(fā)軟中斷是最常見的形式。在這種情況下椰弊,中斷處理程序執(zhí)行硬件設(shè)備的相關(guān)操作许溅,然后觸發(fā)相應(yīng)的軟中斷,最后退出秉版。內(nèi)核在執(zhí)行完中斷處理程序后贤重,馬上會調(diào)用do_softirq函數(shù)。于是軟中斷開始執(zhí)行中斷處理程序留給它去完成的剩余任務(wù)清焕。

三并蝗、tasklet

3.1 tasklet實(shí)現(xiàn)

tasklet是通過軟中斷實(shí)現(xiàn)的,由兩類軟中斷代表:HI_SOFTIRQ和TASKLET_SOFTIRQ秸妥。兩者的區(qū)別在于優(yōu)先級滚停。

tasklet的結(jié)構(gòu)體如下:

struct tasklet_struct {
  struct tasklet_struct *next;   // 鏈表中的下一個tasklet,每個tasklet_struct代表一個不同的tasklet
  unsigned long state;           // tasklet的狀態(tài)粥惧,0键畴、TASKLET_STATE_SCHED、TASKLET_STATE_RUN
  atomic_t count;                // 引用計數(shù)器,若不為0起惕,則tasklet被禁止涡贱,不允許運(yùn)行;為0時惹想,tasklet才被激活问词,并設(shè)置為掛起狀態(tài),才能被執(zhí)行
  void (*func)(unsigned long);   // tasklet處理函數(shù)
  unsigned long data;            // 給tasklet處理函數(shù)的參數(shù) 
};

tasklet由tasklet_schedule()和tasklet_hi_schedule()函數(shù)進(jìn)行調(diào)度嘀粱,接收一個指向tasklet_struct結(jié)構(gòu)的指針作為參數(shù)激挪。

tasklet_schedule()會檢查tasklet的狀態(tài),若可執(zhí)行則調(diào)用_tasklet_schedule(),把需要調(diào)度的tasklet放到tasklet_vec鏈表表頭草穆。觸發(fā)TASKLET_SOFTIRQ軟中斷灌灾,這樣在下一次調(diào)用do_softirq時就會執(zhí)行待處理的tasklet。

do_softirq執(zhí)行對應(yīng)的tasklet_action()悲柱。tasklet_action()會遍歷tasklet_vec鏈表中待處理的tasklet,判斷并標(biāo)志tasklet的狀態(tài)以及count值些己,若tasklet需要執(zhí)行則運(yùn)行其處理函數(shù)tasklet_struct#func豌鸡。

3.2 使用tasklet

聲明tasklet:

靜態(tài)創(chuàng)建tasklet:
DECLARE_TASKLET(name, func, data);//count初始值為0,tasklet處于激活狀態(tài)
DECLARE_TASKLET_DISABLE(name, func, data);//count初始值為1段标,tasklet處于禁止?fàn)顟B(tài)

//創(chuàng)建一個名為my_tasklet涯冠,處理程序?yàn)閙y_tasklet_handler并且被激活的tasklet
//當(dāng)my_tasklet_handler被調(diào)用時dev會被傳遞給它
DECLARE_TASKLET(my_tasklet, my_tasklet_handler, dev);

動態(tài)創(chuàng)建tasklet并初始化:
tasklet_init(t, tasklet_handler, dev)

tasklet處理程序原型如下:

void tasklet_handler(unsigned long data);

tasklet不能休眠。兩個相同的tasklet不會同時運(yùn)行逼庞,但tasklet運(yùn)行時允許響應(yīng)中斷蛇更,所以tasklet與中斷處理程序之間共享數(shù)據(jù)需要做好并發(fā)保護(hù);兩個不通的tasklet可能在兩個處理器同時執(zhí)行赛糟,因此它們之間的共享數(shù)據(jù)也需要做好并發(fā)保護(hù)派任。


調(diào)度tasklet:

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

tasklet被調(diào)度后,只要有機(jī)會就會盡可能早地運(yùn)行:

  • 如果它沒得到運(yùn)行機(jī)會之前璧南,有個相同的tasklet被調(diào)度了掌逛,那么它只會運(yùn)行一次;
  • 如果它以及開始運(yùn)行了司倚,在另一個處理器上又被調(diào)度了豆混,那么會再運(yùn)行一次。
tasklet_disable(&my_tasklet);  //禁止tasklet
tasklet_enable(&my_tasklet);  //激活tasklet

tasklet_kill(&my_tasklet);  //從掛起的隊列中去掉my_tasklet动知,可能會休眠皿伺,禁止在中斷上下文中使用

當(dāng)大量軟中斷出現(xiàn)時,內(nèi)核會喚醒一組內(nèi)核線程來處理這些負(fù)載盒粮。這些線程以最低優(yōu)先級運(yùn)行(nice值19)鸵鸥,避免跟其他重要任務(wù)搶奪資源。但它們最后肯定會被執(zhí)行拆讯,因此能夠保證在軟中斷負(fù)載重時用戶程序不會因?yàn)榈貌坏教幚砥鲿r間而處于饑餓狀態(tài)脂男,也能保證空閑系統(tǒng)上軟中斷迅速得到處理养叛。

每個處理器都有一個這樣的線程,稱為ksoftirqd/n宰翅,n表示處理器編號弃甥。該線程一旦被初始化,就會陷入循環(huán):若沒有待處理軟中斷汁讼,則調(diào)用schedule讓出時間淆攻;若有則調(diào)用do_softirq來處理。

四嘿架、工作隊列

工作隊列把工作推后瓶珊,交由一個內(nèi)核線程執(zhí)行,這個下半部總是會在進(jìn)程上下文中執(zhí)行耸彪,因此工作隊列執(zhí)行的代碼擁有進(jìn)程上下文的所有優(yōu)勢伞芹。最重要的是工作隊列運(yùn)行重新調(diào)度甚至休眠。

4.1 工作隊列的實(shí)現(xiàn)

工作隊列子系統(tǒng)是一個用于創(chuàng)建內(nèi)核線程的接口蝉娜,通過它創(chuàng)建的進(jìn)程負(fù)責(zé)執(zhí)行由內(nèi)核其他部分放入隊列里的任務(wù)唱较。這些內(nèi)核線程稱為工作線程events/n,n表示處理器編號召川,每個處理器對應(yīng)一個線程南缓。

//工作線程結(jié)構(gòu):
struct workqueue_struct {
  struct cpu_workqueue_struct cpu_wq[NR_CPUS]; //每個元素對應(yīng)一個處理器
  struct list_head list;
  const char *name;
  int singlethread;
  int freezeable;
  int rt;
};

//處理器的結(jié)構(gòu):
struct cpu_workqueue_struct{
  spinlock_t lock; //鎖保護(hù)這種結(jié)構(gòu)
  struct list_head worklist; //工作列表
  wait_queue_head_t more_work;
  struct work_struct *current_struct; //工作鏈表
  struct workqueue_struct *wq; // 關(guān)聯(lián)工作隊列結(jié)構(gòu)
  task_t *thread;   //關(guān)聯(lián)線程
};

// 工作結(jié)構(gòu):
struct work_struct {
  atomic_long_t data;
  struct list_head entry;
  work_fun_t func;
};

工作線程會執(zhí)行worker_thread()函數(shù),在其初始化后會執(zhí)行一個死循環(huán)并開始休眠荧呐,當(dāng)有操作被插入到隊列current_struct時汉形,線程就會被喚醒,以便執(zhí)行這些操作work->func倍阐;當(dāng)沒有剩余操作時概疆,又繼續(xù)休眠。

image.png

4.2 使用工作隊列

創(chuàng)建工作:

// 靜態(tài)創(chuàng)建名稱為name收捣,處理函數(shù)為func届案,參數(shù)為data的work_struct結(jié)構(gòu)體:
DECLARE_WORK(name, void (*func)(void *), void *data);

//運(yùn)行時創(chuàng)建work_struct結(jié)構(gòu)體:
INIT_WORK(struct work_struct *work, void (*func)(void *), void *data);

工作隊列處理函數(shù)運(yùn)行在進(jìn)程上下文,原型如下:

void work_handle(void *data);

該函數(shù)默認(rèn)允許響應(yīng)中斷罢艾,并且不持有任何鎖楣颠。如有需要,可以休眠咐蚯。但不能訪問用戶空間童漩。


工作調(diào)度,一旦工作所在處理器上的工作者線程被喚醒春锋,工作就會被執(zhí)行:

schedule_work(&work);

schedule_delayed_work(&work, delay); //延遲執(zhí)行工作
int cancel_delayed_work(struct work_struct *work); //取消延遲執(zhí)行的工作

刷新指定工作隊列矫膨,函數(shù)會一直等待直到隊列中所有對象都被執(zhí)行以后才返回。在等待所有待處理的工作執(zhí)行時,該函數(shù)會進(jìn)入休眠狀態(tài)侧馅,所以只能在進(jìn)程上下文中使用它:

void flush_scheduled_work(void);

創(chuàng)建新的工作隊列和與之相關(guān)的工作者線程:

struct workqueue_struct *create_workqueue(const char *name);

調(diào)度工作到指定的工作隊列:

int queue_work(struct workqueue_struct *wq,  
               struct work_struct *work);

int queue_delayed_work(struct workqueue_struct *wq,  
               struct work_struct *work, 
               unsigned long delay);

刷新指定工作隊列:

flush_workqueue(struct workqueue_struct *wq);

五危尿、下半部機(jī)制的選擇

image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市馁痴,隨后出現(xiàn)的幾起案子谊娇,更是在濱河造成了極大的恐慌,老刑警劉巖罗晕,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件济欢,死亡現(xiàn)場離奇詭異,居然都是意外死亡小渊,警方通過查閱死者的電腦和手機(jī)法褥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來酬屉,“玉大人半等,你說我怎么就攤上這事“鸸撸” “怎么了酱鸭?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長垛吗。 經(jīng)常有香客問我,道長烁登,這世上最難降的妖魔是什么怯屉? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮饵沧,結(jié)果婚禮上锨络,老公的妹妹穿的比我還像新娘。我一直安慰自己狼牺,他們只是感情好羡儿,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著是钥,像睡著了一般掠归。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上悄泥,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天虏冻,我揣著相機(jī)與錄音,去河邊找鬼弹囚。 笑死厨相,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蛮穿,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼庶骄,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了践磅?” 一聲冷哼從身側(cè)響起单刁,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎音诈,沒想到半個月后幻碱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡细溅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年褥傍,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片喇聊。...
    茶點(diǎn)故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡恍风,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出誓篱,到底是詐尸還是另有隱情朋贬,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布窜骄,位于F島的核電站锦募,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏邻遏。R本人自食惡果不足惜糠亩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望准验。 院中可真熱鬧赎线,春花似錦、人聲如沸糊饱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽另锋。三九已至滞项,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間砰蠢,已是汗流浹背蓖扑。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留台舱,地道東北人律杠。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓潭流,卻偏偏與公主長得像,于是被迫代替她去往敵國和親柜去。 傳聞我的和親對象是個殘疾皇子灰嫉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容