linux kernel的中斷子系統(tǒng)之(八):softirq作者:linuxer 發(fā)布于:2014-10-24 11:53 分類:中斷子系統(tǒng) 一、前言 對于中斷處理而言,linux將其分成了兩個(gè)部分,一個(gè)叫做中斷handler(top half),是全程關(guān)閉中斷的,另外一部分是deferable task(bottom half),屬于不那么緊急需要處理的事情。在執(zhí)行bottom half的時(shí)候,是開中斷的。有多種bottom half的機(jī)制,例如:softirq、tasklet、workqueue或是直接創(chuàng)建一個(gè)kernel thread來執(zhí)行bottom half(這在舊的kernel驅(qū)動(dòng)中常見,現(xiàn)在,一個(gè)理智的driver廠商是不會(huì)這么做的)。本文主要討論softirq機(jī)制。由于tasklet是基于softirq的,因此本文也會(huì)提及tasklet,但主要是從需求層面考慮,不會(huì)涉及其具體的代碼實(shí)現(xiàn)。 在普通的驅(qū)動(dòng)中一般是不會(huì)用到softirq,但是由于驅(qū)動(dòng)經(jīng)常使用的tasklet是基于softirq的,因此,了解softirq機(jī)制有助于撰寫更優(yōu)雅的driver。softirq不能動(dòng)態(tài)分配,都是靜態(tài)定義的。內(nèi)核已經(jīng)定義了若干種softirq number,例如網(wǎng)絡(luò)數(shù)據(jù)的收發(fā)、block設(shè)備的數(shù)據(jù)訪問(數(shù)據(jù)量大,通信帶寬高),timer的deferable task(時(shí)間方面要求高)。本文的第二章討論了softirq和tasklet這兩種機(jī)制有何不同,分別適用于什么樣的場景。第三章描述了一些context的概念,這是要理解后續(xù)內(nèi)容的基礎(chǔ)。第四章是進(jìn)入softirq的實(shí)現(xiàn),對比hard irq來解析soft irq的注冊、觸發(fā),調(diào)度的過程。 注:本文中的linux kernel的版本是3.14
二、為何有softirq和tasklet 1、為何有top half和bottom half 中斷處理模塊是任何OS中最重要的一個(gè)模塊,對系統(tǒng)的性能會(huì)有直接的影響。想像一下:如果在通過U盤進(jìn)行大量數(shù)據(jù)拷貝的時(shí)候,你按下一個(gè)key,需要半秒的時(shí)間才顯示出來,這個(gè)場景是否讓你崩潰?因此,對于那些復(fù)雜的、需要大量數(shù)據(jù)處理的硬件中斷,我們不能讓handler中處理完一切再恢復(fù)現(xiàn)場(handler是全程關(guān)閉中斷的),而是僅僅在handler中處理一部分,具體包括: (1)有實(shí)時(shí)性要求的 (2)和硬件相關(guān)的。例如ack中斷,read HW FIFO to ram等 (3)如果是共享中斷,那么獲取硬件中斷狀態(tài)以便判斷是否是本中斷發(fā)生 除此之外,其他的內(nèi)容都是放到bottom half中處理。在把中斷處理過程劃分成top half和bottom half之后,關(guān)中斷的top half被瘦身,可以非??焖俚膱?zhí)行完畢,大大減少了系統(tǒng)關(guān)中斷的時(shí)間,提高了系統(tǒng)的性能。 我們可以基于下面的系統(tǒng)進(jìn)一步的進(jìn)行討論: 當(dāng)網(wǎng)卡控制器的FIFO收到的來自以太網(wǎng)的數(shù)據(jù)的時(shí)候(例如半滿的時(shí)候,可以軟件設(shè)定),可以將該事件通過irq signal送達(dá)Interrupt Controller。Interrupt Controller可以把中斷分發(fā)給系統(tǒng)中的Processor A or B。 NIC的中斷處理過程大概包括:mask and ack interrupt controller-------->ack NIC-------->copy FIFO to ram------>handle Data in the ram----------->unmask interrupt controller 我們先假設(shè)Processor A處理了這個(gè)網(wǎng)卡中斷事件,于是NIC的中斷handler在Processor A上歡快的執(zhí)行,這時(shí)候,Processor A的本地中斷是disable的。NIC的中斷handler在執(zhí)行的過程中,網(wǎng)絡(luò)數(shù)據(jù)仍然源源不斷的到來,但是,如果NIC的中斷handler不操作NIC的寄存器來ack這個(gè)中斷的話,NIC是不會(huì)觸發(fā)下一次中斷的。還好,我們的NIC interrupt handler總是在最開始就會(huì)ack,因此,這不會(huì)導(dǎo)致性能問題。ack之后,NIC已經(jīng)具體再次trigger中斷的能力。當(dāng)Processor A上的handler 在處理接收來自網(wǎng)絡(luò)的數(shù)據(jù)的時(shí)候,NIC的FIFO很可能又收到新的數(shù)據(jù),并trigger了中斷,這時(shí)候,Interrupt controller還沒有umask,因此,即便還有Processor B(也就是說有處理器資源),中斷控制器也無法把這個(gè)中斷送達(dá)處理器系統(tǒng)。因此,只能眼睜睜的看著NIC FIFO填滿數(shù)據(jù),數(shù)據(jù)溢出,或者向?qū)Χ税l(fā)出擁塞信號,無論如何,整體的系統(tǒng)性能是受到嚴(yán)重的影響。 注意:對于新的interrupt controller,可能沒有mask和umask操作,但是原理是一樣的,只不過NIC的handler執(zhí)行完畢要發(fā)生EOI而已。 要解決上面的問題,最重要的是盡快的執(zhí)行完中斷handler,打開中斷,unmask IRQ(或者發(fā)送EOI),方法就是把耗時(shí)的handle Data in the ram這個(gè)步驟踢出handler,讓其在bottom half中執(zhí)行。
2、為何有softirq和tasklet OK,linux kernel已經(jīng)把中斷處理分成了top half和bottom half,看起來已經(jīng)不錯(cuò)了,那為何還要提供softirq、tasklet和workqueue這些bottom half機(jī)制,linux kernel本來就夠復(fù)雜了,bottom half還來添亂。實(shí)際上,在早期的linux kernel還真是只有一個(gè)bottom half機(jī)制,簡稱BH,簡單好用,但是性能不佳。后來,linux kernel的開發(fā)者開發(fā)了task queue機(jī)制,試圖來替代BH,當(dāng)然,最后task queue也消失在內(nèi)核代碼中了?,F(xiàn)在的linux kernel提供了三種bottom half的機(jī)制,來應(yīng)對不同的需求。 workqueue和softirq、tasklet有本質(zhì)的區(qū)別:workqueue運(yùn)行在process context,而softirq和tasklet運(yùn)行在interrupt context。因此,出現(xiàn)workqueue是不奇怪的,在有sleep需求的場景中,defering task必須延遲到kernel thread中執(zhí)行,也就是說必須使用workqueue機(jī)制。softirq和tasklet是怎么回事呢?從本質(zhì)上將,bottom half機(jī)制的設(shè)計(jì)有兩方面的需求,一個(gè)是性能,一個(gè)是易用性。設(shè)計(jì)一個(gè)通用的bottom half機(jī)制來滿足這兩個(gè)需求非常的困難,因此,內(nèi)核提供了softirq和tasklet兩種機(jī)制。softirq更傾向于性能,而tasklet更傾向于易用性。 我們還是進(jìn)入實(shí)際的例子吧,還是使用上一節(jié)的系統(tǒng)圖。在引入softirq之后,網(wǎng)絡(luò)數(shù)據(jù)的處理如下: 關(guān)中斷:mask and ack interrupt controller-------->ack NIC-------->copy FIFO to ram------>raise softirq------>unmask interrupt controller 開中斷:在softirq上下文中進(jìn)行handle Data in the ram的動(dòng)作 同樣的,我們先假設(shè)Processor A處理了這個(gè)網(wǎng)卡中斷事件,很快的完成了基本的HW操作后,raise softirq。在返回中斷現(xiàn)場前,會(huì)檢查softirq的觸發(fā)情況,因此,后續(xù)網(wǎng)絡(luò)數(shù)據(jù)處理的softirq在processor A上執(zhí)行。在執(zhí)行過程中,NIC硬件再次觸發(fā)中斷,Interrupt controller將該中斷分發(fā)給processor B,執(zhí)行動(dòng)作和Processor A是類似的,因此,最后,網(wǎng)絡(luò)數(shù)據(jù)處理的softirq在processor B上執(zhí)行。 為了性能,同一類型的softirq有可能在不同的CPU上并發(fā)執(zhí)行,這給使用者帶來了極大的痛苦,因?yàn)轵?qū)動(dòng)工程師在撰寫softirq的回調(diào)函數(shù)的時(shí)候要考慮重入,考慮并發(fā),要引入同步機(jī)制。但是,為了性能,我們必須如此。 當(dāng)網(wǎng)絡(luò)數(shù)據(jù)處理的softirq同時(shí)在Processor A和B上運(yùn)行的時(shí)候,網(wǎng)卡中斷又來了(可能是10G的網(wǎng)卡吧)。這時(shí)候,中斷分發(fā)給processor A,這時(shí)候,processor A上的handler仍然會(huì)raise softirq,但是并不會(huì)調(diào)度該softirq。也就是說,softirq在一個(gè)CPU上是串行執(zhí)行的。這種情況下,系統(tǒng)性能瓶頸是CPU資源,需要增加更多的CPU來解決該問題。 如果是tasklet的情況會(huì)如何呢?為何tasklet性能不如softirq呢?如果一個(gè)tasklet在processor A上被調(diào)度執(zhí)行,那么它永遠(yuǎn)也不會(huì)同時(shí)在processor B上執(zhí)行,也就是說,tasklet是串行執(zhí)行的(注:不同的tasklet還是會(huì)并發(fā)的),不需要考慮重入的問題。我們還是用網(wǎng)卡這個(gè)例子吧(注意:這個(gè)例子僅僅是用來對比,實(shí)際上,網(wǎng)絡(luò)數(shù)據(jù)是使用softirq機(jī)制的),同樣是上面的系統(tǒng)結(jié)構(gòu)圖。假設(shè)使用tasklet,網(wǎng)絡(luò)數(shù)據(jù)的處理如下: 關(guān)中斷:mask and ack interrupt controller-------->ack NIC-------->copy FIFO to ram------>schedule tasklet------>unmask interrupt controller 開中斷:在softirq上下文中(一般使用TASKLET_SOFTIRQ這個(gè)softirq)進(jìn)行handle Data in the ram的動(dòng)作 同樣的,我們先假設(shè)Processor A處理了這個(gè)網(wǎng)卡中斷事件,很快的完成了基本的HW操作后,schedule tasklet(同時(shí)也就raise TASKLET_SOFTIRQ softirq)。在返回中斷現(xiàn)場前,會(huì)檢查softirq的觸發(fā)情況,因此,在TASKLET_SOFTIRQ softirq的handler中,獲取tasklet相關(guān)信息并在processor A上執(zhí)行該tasklet的handler。在執(zhí)行過程中,NIC硬件再次觸發(fā)中斷,Interrupt controller將該中斷分發(fā)給processor B,執(zhí)行動(dòng)作和Processor A是類似的,雖然TASKLET_SOFTIRQ softirq在processor B上可以執(zhí)行,但是,在檢查tasklet的狀態(tài)的時(shí)候,如果發(fā)現(xiàn)該tasklet在其他processor上已經(jīng)正在運(yùn)行,那么該tasklet不會(huì)被處理,一直等到在processor A上的tasklet處理完,在processor B上的這個(gè)tasklet才能被執(zhí)行。這樣的串行化操作雖然對驅(qū)動(dòng)工程師是一個(gè)福利,但是對性能而言是極大的損傷。
三、理解softirq需要的基礎(chǔ)知識(各種context) 1、preempt_count 為了更好的理解下面的內(nèi)容,我們需要先看看一些基礎(chǔ)知識:一個(gè)task的thread info數(shù)據(jù)結(jié)構(gòu)定義如下(只保留和本場景相關(guān)的內(nèi)容):
preempt_count這個(gè)成員被用來判斷當(dāng)前進(jìn)程是否可以被搶占。如果preempt_count不等于0(可能是代碼調(diào)用preempt_disable顯式的禁止了搶占,也可能是處于中斷上下文等),說明當(dāng)前不能進(jìn)行搶占,如果preempt_count等于0,說明已經(jīng)具備了搶占的條件(當(dāng)然具體是否要搶占當(dāng)前進(jìn)程還是要看看thread info中的flag成員是否設(shè)定了_TIF_NEED_RESCHED這個(gè)標(biāo)記,可能是當(dāng)前的進(jìn)程的時(shí)間片用完了,也可能是由于中斷喚醒了優(yōu)先級更高的進(jìn)程)。 具體preempt_count的數(shù)據(jù)格式可以參考下圖: preemption count用來記錄當(dāng)前被顯式的禁止搶占的次數(shù),也就是說,每調(diào)用一次preempt_disable,preemption count就會(huì)加一,調(diào)用preempt_enable,該區(qū)域的數(shù)值會(huì)減去一。preempt_disable和preempt_enable必須成對出現(xiàn),可以嵌套,最大嵌套的深度是255。 hardirq count描述當(dāng)前中斷handler嵌套的深度。對于ARM平臺(tái)的linux kernel,其中斷部分的代碼如下:
通用的IRQ handler被irq_enter和irq_exit這兩個(gè)函數(shù)包圍。irq_enter說明進(jìn)入到IRQ context,而irq_exit則說明退出IRQ context。在irq_enter函數(shù)中會(huì)調(diào)用preempt_count_add(HARDIRQ_OFFSET),為hardirq count的bit field增加1。在irq_exit函數(shù)中,會(huì)調(diào)用preempt_count_sub(HARDIRQ_OFFSET),為hardirq count的bit field減去1。hardirq count占用了4個(gè)bit,說明硬件中斷handler最大可以嵌套15層。在舊的內(nèi)核中,hardirq count占用了12個(gè)bit,支持4096個(gè)嵌套。當(dāng)然,在舊的kernel中還區(qū)分fast interrupt handler和slow interrupt handler,中斷handler最大可以嵌套的次數(shù)理論上等于系統(tǒng)IRQ的個(gè)數(shù)。在實(shí)際中,這個(gè)數(shù)目不可能那么大(內(nèi)核棧就受不了),因此,即使系統(tǒng)支持了非常大的中斷個(gè)數(shù),也不可能各個(gè)中斷依次嵌套,達(dá)到理論的上限?;谶@樣的考慮,后來內(nèi)核減少了hardirq count占用bit數(shù)目,改成了10個(gè)bit(在general arch的代碼中修改為10,實(shí)際上,各個(gè)arch可以redefine自己的hardirq count的bit數(shù))。但是,當(dāng)內(nèi)核大佬們決定廢棄slow interrupt handler的時(shí)候,實(shí)際上,中斷的嵌套已經(jīng)不會(huì)發(fā)生了。因此,理論上,hardirq count要么是0,要么是1。不過呢,不能總拿理論說事,實(shí)際上,萬一有寫奇葩或者老古董driver在handler中打開中斷,那么這時(shí)候中斷嵌套還是會(huì)發(fā)生的,但是,應(yīng)該不會(huì)太多(一個(gè)系統(tǒng)中怎么可能有那么多奇葩呢?呵呵),因此,目前hardirq count占用了4個(gè)bit,應(yīng)付15個(gè)奇葩driver是妥妥的。 對softirq count進(jìn)行操作有兩個(gè)場景: (1)也是在進(jìn)入soft irq handler之前給 softirq count加一,退出soft irq handler之后給 softirq count減去一。由于soft irq handler在一個(gè)CPU上是不會(huì)并發(fā)的,總是串行執(zhí)行,因此,這個(gè)場景下只需要一個(gè)bit就夠了,也就是上圖中的bit 8。通過該bit可以知道當(dāng)前task是否在sofirq context。 (2)由于內(nèi)核同步的需求,進(jìn)程上下文需要禁止softirq。這時(shí)候,kernel提供了local_bf_enable和local_bf_disable這樣的接口函數(shù)。這部分的概念是和preempt disable/enable類似的,占用了bit9~15,最大可以支持127次嵌套。
2、一個(gè)task的各種上下文 看完了preempt_count之后,我們來介紹各種context:
這里首先要介紹的是一個(gè)叫做IRQ context的術(shù)語。這里的IRQ context其實(shí)就是hard irq context,也就是說明當(dāng)前正在執(zhí)行中斷handler(top half),只要preempt_count中的hardirq count大于0(=1是沒有中斷嵌套,如果大于1,說明有中斷嵌套),那么就是IRQ context。 softirq context并沒有那么的直接,一般人會(huì)認(rèn)為當(dāng)sofirq handler正在執(zhí)行的時(shí)候就是softirq context。這樣說當(dāng)然沒有錯(cuò),sofirq handler正在執(zhí)行的時(shí)候,會(huì)增加softirq count,當(dāng)然是softirq context。不過,在其他context的情況下,例如進(jìn)程上下文中,有有可能因?yàn)橥降囊蠖{(diào)用local_bh_disable,這時(shí)候,通過local_bh_disable/enable保護(hù)起來的代碼也是執(zhí)行在softirq context中。當(dāng)然,這時(shí)候其實(shí)并沒有正在執(zhí)行softirq handler。如果你確實(shí)想知道當(dāng)前是否正在執(zhí)行softirq handler,in_serving_softirq可以完成這個(gè)使命,這是通過操作preempt_count的bit 8來完成的。 所謂中斷上下文,就是IRQ context + softirq context+NMI context。
四、softirq機(jī)制 softirq和hardirq(就是硬件中斷啦)是對應(yīng)的,因此softirq的機(jī)制可以參考hardirq對應(yīng)理解,當(dāng)然softirq是純軟件的,不需要硬件參與。 1、softirq number 和IRQ number一樣,對于軟中斷,linux kernel也是用一個(gè)softirq number唯一標(biāo)識一個(gè)softirq,具體定義如下:
HI_SOFTIRQ用于高優(yōu)先級的tasklet,TASKLET_SOFTIRQ用于普通的tasklet。TIMER_SOFTIRQ是for software timer的(所謂software timer就是說該timer是基于系統(tǒng)tick的)。NET_TX_SOFTIRQ和NET_RX_SOFTIRQ是用于網(wǎng)卡數(shù)據(jù)收發(fā)的。BLOCK_SOFTIRQ和BLOCK_IOPOLL_SOFTIRQ是用于block device的。SCHED_SOFTIRQ用于多CPU之間的負(fù)載均衡的。HRTIMER_SOFTIRQ用于高精度timer的。RCU_SOFTIRQ是處理RCU的。這些具體使用情景分析會(huì)在各自的子系統(tǒng)中分析,本文只是描述softirq的工作原理。 2、softirq描述符 我們前面已經(jīng)說了,softirq是靜態(tài)定義的,也就是說系統(tǒng)中有一個(gè)定義softirq描述符的數(shù)組,而softirq number就是這個(gè)數(shù)組的index。這個(gè)概念和早期的靜態(tài)分配的中斷描述符概念是類似的。具體定義如下:
系統(tǒng)支持多少個(gè)軟中斷,靜態(tài)定義的數(shù)組就會(huì)有多少個(gè)entry。____cacheline_aligned保證了在SMP的情況下,softirq_vec是對齊到cache line的。softirq描述符非常簡單,只有一個(gè)action成員,表示如果觸發(fā)了該softirq,那么應(yīng)該調(diào)用action回調(diào)函數(shù)來處理這個(gè)soft irq。對于硬件中斷而言,其mask、ack等都是和硬件寄存器相關(guān)并封裝在irq chip函數(shù)中,對于softirq,沒有硬件寄存器,只有“軟件寄存器”,定義如下:
ipi_irqs這個(gè)成員用于處理器之間的中斷,我們留到下一個(gè)專題來描述。__softirq_pending就是這個(gè)“軟件寄存器”。softirq采用誰觸發(fā),誰負(fù)責(zé)處理的。例如:當(dāng)一個(gè)驅(qū)動(dòng)的硬件中斷被分發(fā)給了指定的CPU,并且在該中斷handler中觸發(fā)了一個(gè)softirq,那么該CPU負(fù)責(zé)調(diào)用該softirq number對應(yīng)的action callback來處理該軟中斷。因此,這個(gè)“軟件寄存器”應(yīng)該是每個(gè)CPU擁有一個(gè)(專業(yè)術(shù)語叫做banked register)。為了性能,irq_stat中的每一個(gè)entry被定義對齊到cache line。 3、如何注冊一個(gè)softirq 通過調(diào)用open_softirq接口函數(shù)可以注冊softirq的action callback函數(shù),具體如下:
softirq_vec是一個(gè)多CPU之間共享的數(shù)據(jù),不過,由于所有的注冊都是在系統(tǒng)初始化的時(shí)候完成的,那時(shí)候,系統(tǒng)是串行執(zhí)行的。此外,softirq是靜態(tài)定義的,每個(gè)entry(或者說每個(gè)softirq number)都是固定分配的,因此,不需要保護(hù)。 4、如何觸發(fā)softirq? 在linux kernel中,可以調(diào)用raise_softirq這個(gè)接口函數(shù)來觸發(fā)本地CPU上的softirq,具體如下:
雖然大部分的使用場景都是在中斷handler中(也就是說關(guān)閉本地CPU中斷)來執(zhí)行softirq的觸發(fā)動(dòng)作,但是,這不是全部,在其他的上下文中也可以調(diào)用raise_softirq。因此,觸發(fā)softirq的接口函數(shù)有兩個(gè)版本,一個(gè)是raise_softirq,有關(guān)中斷的保護(hù),另外一個(gè)是raise_softirq_irqoff,調(diào)用者已經(jīng)關(guān)閉了中斷,不需要關(guān)中斷來保護(hù)“soft irq status register”。 所謂trigger softirq,就是在__softirq_pending(也就是上面說的soft irq status register)的某個(gè)bit置一。從上面的定義可知,__softirq_pending是per cpu的,因此不需要考慮多個(gè)CPU的并發(fā),只要disable本地中斷,就可以確保對,__softirq_pending操作的原子性。 具體raise_softirq_irqoff的代碼如下:
(1)__raise_softirq_irqoff函數(shù)設(shè)定本CPU上的__softirq_pending的某個(gè)bit等于1,具體的bit是由soft irq number(nr參數(shù))指定的。 (2)如果在中斷上下文,我們只要set __softirq_pending的某個(gè)bit就OK了,在中斷返回的時(shí)候自然會(huì)進(jìn)行軟中斷的處理。但是,如果在context上下文調(diào)用這個(gè)函數(shù)的時(shí)候,我們必須要調(diào)用wakeup_softirqd函數(shù)用來喚醒本CPU上的softirqd這個(gè)內(nèi)核線程。具體softirqd的內(nèi)容請參考下一個(gè)章節(jié)。
5、disable/enable softirq 在linux kernel中,可以使用local_irq_disable和local_irq_enable來disable和enable本CPU中斷。和硬件中斷一樣,軟中斷也可以disable,接口函數(shù)是local_bh_disable和local_bh_enable。雖然和想像的local_softirq_enable/disable有些出入,不過bh這個(gè)名字更準(zhǔn)確反應(yīng)了該接口函數(shù)的意涵,因?yàn)閘ocal_bh_disable/enable函數(shù)就是用來disable/enable bottom half的,這里就包括softirq和tasklet。 先看disable吧,畢竟禁止bottom half比較簡單:
看起來disable bottom half比較簡單,就是講current thread info上的preempt_count成員中的softirq count的bit field9~15加上一就OK了。barrier是優(yōu)化屏障(Optimization barrier),會(huì)在內(nèi)核同步系列文章中描述。 enable函數(shù)比較復(fù)雜,如下:
(1)disable/enable bottom half是一種內(nèi)核同步機(jī)制。在硬件中斷的handler(top half)中,不應(yīng)該調(diào)用disable/enable bottom half函數(shù)來保護(hù)共享數(shù)據(jù),因?yàn)閎ottom half其實(shí)是不可能搶占top half的。同樣的,soft irq也不會(huì)搶占另外一個(gè)soft irq的執(zhí)行,也就是說,一旦一個(gè)softirq handler被調(diào)度執(zhí)行(無論在哪一個(gè)processor上),那么,本地的softirq handler都無法搶占其運(yùn)行,要等到當(dāng)前的softirq handler運(yùn)行完畢后,才能執(zhí)行下一個(gè)soft irq handler。注意:上面我們說的是本地,是local,softirq handler是可以在多個(gè)CPU上同時(shí)運(yùn)行的,但是,linux kernel中沒有disable all softirq的接口函數(shù)(就好像沒有disable all CPU interrupt的接口一樣,注意體會(huì)local_bh_enable/disable中的local的含義)。 說了這么多,一言以蔽之,local_bh_enable/disable是給進(jìn)程上下文使用的,用于防止softirq handler搶占local_bh_enable/disable之間的臨界區(qū)的。 irqs_disabled接口函數(shù)可以獲知當(dāng)前本地CPU中斷是否是disable的,如果返回1,那么當(dāng)前是disable 本地CPU的中斷的。如果irqs_disabled返回1,有可能是下面這樣的代碼造成的:
本質(zhì)上,關(guān)本地中斷是一種比關(guān)本地bottom half更強(qiáng)勁的鎖,關(guān)本地中斷實(shí)際上是禁止了top half和bottom half搶占當(dāng)前進(jìn)程上下文的運(yùn)行。也許你會(huì)說:這也沒有什么,就是有些浪費(fèi),至少代碼邏輯沒有問題。但事情沒有這么簡單,在local_bh_enable--->do_softirq--->__do_softirq中,有一條無條件打開當(dāng)前中斷的操作,也就是說,原本想通過local_irq_disable/local_irq_enable保護(hù)的臨界區(qū)被破壞了,其他的中斷handler可以插入執(zhí)行,從而無法保證local_irq_disable/local_irq_enable保護(hù)的臨界區(qū)的原子性,從而破壞了代碼邏輯。 in_irq()這個(gè)函數(shù)如果不等于0的話,說明local_bh_enable被irq_enter和irq_exit包圍,也就是說在中斷handler中調(diào)用了local_bh_enable/disable。這道理是和上面類似的,這里就不再詳細(xì)描述了。 (2)在local_bh_disable中我們?yōu)閜reempt_count增加了SOFTIRQ_DISABLE_OFFSET,在local_bh_enable函數(shù)中應(yīng)該減掉同樣的數(shù)值。這一步,我們首先減去了(SOFTIRQ_DISABLE_OFFSET-1),為何不一次性的減去SOFTIRQ_DISABLE_OFFSET呢?考慮下面運(yùn)行在進(jìn)程上下文的代碼場景:
在臨界區(qū)內(nèi),有進(jìn)程context 和softirq共享的數(shù)據(jù),因此,在進(jìn)程上下文中使用local_bh_enable/disable進(jìn)行保護(hù)。假設(shè)在臨界區(qū)代碼執(zhí)行的時(shí)候,發(fā)生了中斷,由于代碼并沒有阻止top half的搶占,因此中斷handler會(huì)搶占當(dāng)前正在執(zhí)行的thread。在中斷handler中,我們r(jià)aise了softirq,在返回中斷現(xiàn)場的時(shí)候,由于disable了bottom half,因此雖然觸發(fā)了softirq,但是不會(huì)調(diào)度執(zhí)行。因此,代碼返回臨界區(qū)繼續(xù)執(zhí)行,直到local_bh_enable。一旦enable了bottom half,那么之前raise的softirq就需要調(diào)度執(zhí)行了,因此,這也是為什么在local_bh_enable會(huì)調(diào)用do_softirq函數(shù)。 調(diào)用do_softirq函數(shù)來處理pending的softirq的時(shí)候,當(dāng)前的task是不能被搶占的,因?yàn)橐坏┍粨屨?,下一次該task被調(diào)度運(yùn)行的時(shí)候很可能在其他的CPU上去了(還記得嗎?softirq的pending 寄存器是per cpu的)。因此,我們不能一次性的全部減掉,那樣的話有可能preempt_count等于0,那樣就允許搶占了。因此,這里減去了(SOFTIRQ_DISABLE_OFFSET-1),既保證了softirq count的bit field9~15被減去了1,又保持了preempt disable的狀態(tài)。 (3)如果當(dāng)前不是interrupt context的話,并且有pending的softirq,那么調(diào)用do_softirq函數(shù)來處理軟中斷。 (4)該來的總會(huì)來,在step 2中我們少減了1,這里補(bǔ)上,其實(shí)也就是preempt count-1。 (5)在softirq handler中很可能wakeup了高優(yōu)先級的任務(wù),這里最好要檢查一下,看看是否需要進(jìn)行調(diào)度,確保高優(yōu)先級的任務(wù)得以調(diào)度執(zhí)行。
5、如何處理一個(gè)被觸發(fā)的soft irq 我們說softirq是一種defering task的機(jī)制,也就是說top half沒有做的事情,需要延遲到bottom half中來執(zhí)行。那么具體延遲到什么時(shí)候呢?這是本節(jié)需要講述的內(nèi)容,也就是說soft irq是如何調(diào)度執(zhí)行的。 在上一節(jié)已經(jīng)描述一個(gè)softirq被調(diào)度執(zhí)行的場景,本節(jié)主要關(guān)注在中斷返回現(xiàn)場時(shí)候調(diào)度softirq的場景。我們來看中斷退出的代碼,具體如下:
代碼中“!in_interrupt()”這個(gè)條件可以確保下面的場景不會(huì)觸發(fā)sotfirq的調(diào)度: (1)中斷handler是嵌套的。也就是說本次irq_exit是退出到上一個(gè)中斷handler。當(dāng)然,在新的內(nèi)核中,這種情況一般不會(huì)發(fā)生,因?yàn)橹袛鄅andler都是關(guān)中斷執(zhí)行的。 (2)本次中斷是中斷了softirq handler的執(zhí)行。也就是說本次irq_exit是不是退出到進(jìn)程上下文,而是退出到上一個(gè)softirq context。這一點(diǎn)也保證了在一個(gè)CPU上的softirq是串行執(zhí)行的(注意:多個(gè)CPU上還是有可能并發(fā)的) 我們繼續(xù)看invoke_softirq的代碼:
force_irqthreads是和強(qiáng)制線程化相關(guān)的,主要用于interrupt handler的調(diào)試(一般而言,在線程環(huán)境下比在中斷上下文中更容易收集調(diào)試數(shù)據(jù))。如果系統(tǒng)選擇了對所有的interrupt handler進(jìn)行線程化處理,那么softirq也沒有理由在中斷上下文中處理(中斷handler都在線程中執(zhí)行了,softirq怎么可能在中斷上下文中執(zhí)行)。本身invoke_softirq這個(gè)函數(shù)是在中斷上下文中被調(diào)用的,如果強(qiáng)制線程化,那么系統(tǒng)中所有的軟中斷都在sofirq的daemon進(jìn)程中被調(diào)度執(zhí)行。 如果沒有強(qiáng)制線程化,softirq的處理也分成兩種情況,主要是和softirq執(zhí)行的時(shí)候使用的stack相關(guān)。如果arch支持單獨(dú)的IRQ STACK,這時(shí)候,由于要退出中斷,因此irq stack已經(jīng)接近全空了(不考慮中斷棧嵌套的情況,因此新內(nèi)核下,中斷不會(huì)嵌套),因此直接調(diào)用__do_softirq()處理軟中斷就OK了,否則就調(diào)用do_softirq_own_stack函數(shù)在softirq自己的stack上執(zhí)行。當(dāng)然對ARM而言,softirq的處理就是在當(dāng)前的內(nèi)核棧上執(zhí)行的,因此do_softirq_own_stack的調(diào)用就是調(diào)用__do_softirq(),代碼如下(刪除了部分無關(guān)代碼):
(注1)再次檢查softirq pending,有可能上面的softirq handler在執(zhí)行過程中,發(fā)生了中斷,又raise了softirq。如果的確如此,那么我們需要跳轉(zhuǎn)到restart那里重新處理soft irq。當(dāng)然,也不能總是在這里不斷的loop,因此linux kernel設(shè)定了下面的條件: (1)softirq的處理時(shí)間沒有超過2個(gè)ms (2)上次的softirq中沒有設(shè)定TIF_NEED_RESCHED,也就是說沒有有高優(yōu)先級任務(wù)需要調(diào)度 (3)loop的次數(shù)小于 10次 因此,只有同時(shí)滿足上面三個(gè)條件,程序才會(huì)跳轉(zhuǎn)到restart那里重新處理soft irq。否則wakeup_softirqd就OK了。這樣的設(shè)計(jì)也是一個(gè)平衡的方案。一方面照顧了調(diào)度延遲:本來,發(fā)生一個(gè)中斷,系統(tǒng)期望在限定的時(shí)間內(nèi)調(diào)度某個(gè)進(jìn)程來處理這個(gè)中斷,如果softirq handler不斷觸發(fā),其實(shí)linux kernel是無法保證調(diào)度延遲時(shí)間的。另外一方面,也照顧了硬件的thoughput:已經(jīng)預(yù)留了一定的時(shí)間來處理softirq。
原創(chuàng)文章,轉(zhuǎn)發(fā)請注明出處。蝸窩科技 http://www./linux_kenrel/soft-irq.html 評論:
azureming
2014-11-10 13:24 Linuxer,你好,又來請教個(gè)問題;
既然可以采用threaded_irq,那么所謂的bottom機(jī)制【比如softirq】是不是可以不使用了,或者他們在使用的時(shí)候各有應(yīng)用場景來著? 謝謝
linuxer
2014-11-10 17:36 @azureming:中斷線程化是從linux的rt tree導(dǎo)入的一個(gè)特性,因此,threaded irq更多的是從系統(tǒng)實(shí)時(shí)性的角度來看待問題。
如果把threaded irq handler和bottom half對比的話,可能workqueue(而不是softirq)更合適一些,畢竟work是在進(jìn)程上下文中執(zhí)行的,和threaded irq handler是類似的。不過雖然都是在進(jìn)程上下文執(zhí)行,但是,還是有不一樣的,對于線程化的irq handler,其執(zhí)行在一個(gè)實(shí)時(shí)進(jìn)程上,也就是說本質(zhì)上原來在handler在中斷上下文中執(zhí)行,但是現(xiàn)在修改為在系統(tǒng)中的一個(gè)rt進(jìn)程中執(zhí)行,所有threaded irq handler都是先于系統(tǒng)中的normal進(jìn)程被調(diào)度執(zhí)行。 對于workqueue,其進(jìn)程是一個(gè)普通進(jìn)程,需要和很多系統(tǒng)中的普通的進(jìn)程共同爭搶cpu資源。 我們來看一個(gè)場景:如果某個(gè)外設(shè)是慢速總線設(shè)備,例如I2C(呵呵,I2C總是在各種場合下出現(xiàn)),讀取I2C上的數(shù)據(jù)不適合在interrupt handler中執(zhí)行,因此,我們考慮使用workqueue。但是,如果I2C外設(shè)的FIFO很小,而系統(tǒng)又很繁忙,那么這種場景下使用workqueue就會(huì)丟失來自外設(shè)的數(shù)據(jù)。這時(shí)候,使用threaded irq handler會(huì)改善。當(dāng)然,是否保證不丟失數(shù)據(jù)是一個(gè)系統(tǒng)問題,你需要仔細(xì)的設(shè)計(jì)系統(tǒng)中所有task的優(yōu)先級 soft irq是一定需要的,它是在中斷上下文執(zhí)行的,只要是中斷上下文,其優(yōu)先級總是高過進(jìn)程上下文
tigger
2014-11-10 18:40 @linuxer:這里可能還要補(bǔ)充一下linux調(diào)度策略以及調(diào)度器類的概念。但是我的功力還不夠正確的描述出來。所以可以通俗的理解rt進(jìn)程會(huì)優(yōu)先于normal進(jìn)程被調(diào)度執(zhí)行就可以了。其實(shí)我的理解是rt進(jìn)程對應(yīng)的調(diào)度器類的優(yōu)先級高于完全公平進(jìn)程。不知道這樣表述是否可以。
azureming
2014-11-11 09:18 @linuxer:我可不可以這樣理解:
workqueue這個(gè)下半部和普通進(jìn)程的優(yōu)先級一樣,所以有時(shí)候這個(gè)下半部得不到及時(shí)的執(zhí)行; threaded irq優(yōu)先級高于普通進(jìn)程,這點(diǎn)和soft irq一樣(高的優(yōu)先級),可以讓待處理的任務(wù)得到較早的執(zhí)行,相對于soft irq不能被進(jìn)程搶占,threaded irq的機(jī)制則可以通過設(shè)置某些進(jìn)程更高的優(yōu)先級,從而獲得比threaded irq更早的執(zhí)行機(jī)會(huì),從而滿足特定的實(shí)時(shí)性要求。 soft irq的存在主要是為了保證這個(gè)下半部的優(yōu)先執(zhí)行權(quán)限,不能被進(jìn)程打斷?
forion
2014-10-30 16:30 hi linuxer 有一處沒看懂的地方:
(4)該來的總會(huì)來,在step 2中我們少減了1,這里補(bǔ)上,其實(shí)也就是enable preempt。 preempt_count_dec(); ---------------------(4) 這條指令之后,并不一定使能了preempt吧?這個(gè)只是preempt count -1; preempt count=0 才是使能了preempt吧? |
|