乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      linux kernel的中斷子系統(tǒng)之(八):softirq

       老匹夫 2015-04-08

      linux kernel的中斷子系統(tǒng)之(八):softirq

      作者:linuxer 發(fā)布于:2014-10-24 11:53 分類:中斷子系統(tǒng)

      一、前言

      對(duì)于中斷處理而言,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來(lái)執(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ù)訪問(wèn)(數(shù)據(jù)量大,通信帶寬高),timer的deferable task(時(shí)間方面要求高)。本文的第二章討論了softirq和tasklet這兩種機(jī)制有何不同,分別適用于什么樣的場(chǎng)景。第三章描述了一些context的概念,這是要理解后續(xù)內(nèi)容的基礎(chǔ)。第四章是進(jìn)入softirq的實(shí)現(xiàn),對(duì)比hard irq來(lái)解析soft irq的注冊(cè)、觸發(fā),調(diào)度的過(guò)程。

      注:本文中的linux kernel的版本是3.14

       

      二、為何有softirq和tasklet

      1、為何有top half和bottom half

      中斷處理模塊是任何OS中最重要的一個(gè)模塊,對(duì)系統(tǒng)的性能會(huì)有直接的影響。想像一下:如果在通過(guò)U盤進(jìn)行大量數(shù)據(jù)拷貝的時(shí)候,你按下一個(gè)key,需要半秒的時(shí)間才顯示出來(lái),這個(gè)場(chǎng)景是否讓你崩潰?因此,對(duì)于那些復(fù)雜的、需要大量數(shù)據(jù)處理的硬件中斷,我們不能讓handler中處理完一切再恢復(fù)現(xiàn)場(chǎng)(handler是全程關(guān)閉中斷的),而是僅僅在handler中處理一部分,具體包括:

      (1)有實(shí)時(shí)性要求的

      (2)和硬件相關(guān)的。例如ack中斷,read HW FIFO to ram等

      (3)如果是共享中斷,那么獲取硬件中斷狀態(tài)以便判斷是否是本中斷發(fā)生

      除此之外,其他的內(nèi)容都是放到bottom half中處理。在把中斷處理過(guò)程劃分成top half和bottom half之后,關(guān)中斷的top half被瘦身,可以非??焖俚膱?zhí)行完畢,大大減少了系統(tǒng)關(guān)中斷的時(shí)間,提高了系統(tǒng)的性能。

      我們可以基于下面的系統(tǒng)進(jìn)一步的進(jìn)行討論:

      rrr

      當(dāng)網(wǎng)卡控制器的FIFO收到的來(lái)自以太網(wǎng)的數(shù)據(jù)的時(shí)候(例如半滿的時(shí)候,可以軟件設(shè)定),可以將該事件通過(guò)irq signal送達(dá)Interrupt Controller。Interrupt Controller可以把中斷分發(fā)給系統(tǒng)中的Processor A or B。

      NIC的中斷處理過(guò)程大概包括: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í)行的過(guò)程中,網(wǎng)絡(luò)數(shù)據(jù)仍然源源不斷的到來(lái),但是,如果NIC的中斷handler不操作NIC的寄存器來(lái)ack這個(gè)中斷的話,NIC是不會(huì)觸發(fā)下一次中斷的。還好,我們的NIC interrupt handler總是在最開始就會(huì)ack,因此,這不會(huì)導(dǎo)致性能問(wèn)題。ack之后,NIC已經(jīng)具體再次trigger中斷的能力。當(dāng)Processor A上的handler 在處理接收來(lái)自網(wǎng)絡(luò)的數(shù)據(jù)的時(shí)候,NIC的FIFO很可能又收到新的數(shù)據(jù),并trigger了中斷,這時(shí)候,Interrupt controller還沒有umask,因此,即便還有Processor B(也就是說(shuō)有處理器資源),中斷控制器也無(wú)法把這個(gè)中斷送達(dá)處理器系統(tǒng)。因此,只能眼睜睜的看著NIC FIFO填滿數(shù)據(jù),數(shù)據(jù)溢出,或者向?qū)Χ税l(fā)出擁塞信號(hào),無(wú)論如何,整體的系統(tǒng)性能是受到嚴(yán)重的影響。

      注意:對(duì)于新的interrupt controller,可能沒有mask和umask操作,但是原理是一樣的,只不過(guò)NIC的handler執(zhí)行完畢要發(fā)生EOI而已。

      要解決上面的問(wèn)題,最重要的是盡快的執(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,看起來(lái)已經(jīng)不錯(cuò)了,那為何還要提供softirq、tasklet和workqueue這些bottom half機(jī)制,linux kernel本來(lái)就夠復(fù)雜了,bottom half還來(lái)添亂。實(shí)際上,在早期的linux kernel還真是只有一個(gè)bottom half機(jī)制,簡(jiǎn)稱BH,簡(jiǎn)單好用,但是性能不佳。后來(lái),linux kernel的開發(fā)者開發(fā)了task queue機(jī)制,試圖來(lái)替代BH,當(dāng)然,最后task queue也消失在內(nèi)核代碼中了?,F(xiàn)在的linux kernel提供了三種bottom half的機(jī)制,來(lái)應(yīng)對(duì)不同的需求。

      workqueue和softirq、tasklet有本質(zhì)的區(qū)別:workqueue運(yùn)行在process context,而softirq和tasklet運(yùn)行在interrupt context。因此,出現(xiàn)workqueue是不奇怪的,在有sleep需求的場(chǎng)景中,defering task必須延遲到kernel thread中執(zhí)行,也就是說(shuō)必須使用workqueue機(jī)制。softirq和tasklet是怎么回事呢?從本質(zhì)上將,bottom half機(jī)制的設(shè)計(jì)有兩方面的需求,一個(gè)是性能,一個(gè)是易用性。設(shè)計(jì)一個(gè)通用的bottom half機(jī)制來(lái)滿足這兩個(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)場(chǎng)前,會(huì)檢查softirq的觸發(fā)情況,因此,后續(xù)網(wǎng)絡(luò)數(shù)據(jù)處理的softirq在processor A上執(zhí)行。在執(zhí)行過(guò)程中,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í)行,這給使用者帶來(lái)了極大的痛苦,因?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)卡中斷又來(lái)了(可能是10G的網(wǎng)卡吧)。這時(shí)候,中斷分發(fā)給processor A,這時(shí)候,processor A上的handler仍然會(huì)raise softirq,但是并不會(huì)調(diào)度該softirq。也就是說(shuō),softirq在一個(gè)CPU上是串行執(zhí)行的。這種情況下,系統(tǒng)性能瓶頸是CPU資源,需要增加更多的CPU來(lái)解決該問(wèn)題。

      如果是tasklet的情況會(huì)如何呢?為何tasklet性能不如softirq呢?如果一個(gè)tasklet在processor A上被調(diào)度執(zhí)行,那么它永遠(yuǎn)也不會(huì)同時(shí)在processor B上執(zhí)行,也就是說(shuō),tasklet是串行執(zhí)行的(注:不同的tasklet還是會(huì)并發(fā)的),不需要考慮重入的問(wèn)題。我們還是用網(wǎng)卡這個(gè)例子吧(注意:這個(gè)例子僅僅是用來(lái)對(duì)比,實(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)場(chǎng)前,會(huì)檢查softirq的觸發(fā)情況,因此,在TASKLET_SOFTIRQ softirq的handler中,獲取tasklet相關(guān)信息并在processor A上執(zhí)行該tasklet的handler。在執(zhí)行過(guò)程中,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í)行。這樣的串行化操作雖然對(duì)驅(qū)動(dòng)工程師是一個(gè)福利,但是對(duì)性能而言是極大的損傷。

       

      三、理解softirq需要的基礎(chǔ)知識(shí)(各種context)

      1、preempt_count

      為了更好的理解下面的內(nèi)容,我們需要先看看一些基礎(chǔ)知識(shí):一個(gè)task的thread info數(shù)據(jù)結(jié)構(gòu)定義如下(只保留和本場(chǎng)景相關(guān)的內(nèi)容):

      struct thread_info { 
          ……
          int            preempt_count;    /* 0 => preemptable, <0 => bug */
          ……
      };

      preempt_count這個(gè)成員被用來(lái)判斷當(dāng)前進(jìn)程是否可以被搶占。如果preempt_count不等于0(可能是代碼調(diào)用preempt_disable顯式的禁止了搶占,也可能是處于中斷上下文等),說(shuō)明當(dāng)前不能進(jìn)行搶占,如果preempt_count等于0,說(shuō)明已經(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í)更高的進(jìn)程)。 具體preempt_count的數(shù)據(jù)格式可以參考下圖:

      preempt-count

      preemption count用來(lái)記錄當(dāng)前被顯式的禁止搶占的次數(shù),也就是說(shuō),每調(diào)用一次preempt_disable,preemption count就會(huì)加一,調(diào)用preempt_enable,該區(qū)域的數(shù)值會(huì)減去一。preempt_disable和preempt_enable必須成對(duì)出現(xiàn),可以嵌套,最大嵌套的深度是255。

      hardirq count描述當(dāng)前中斷handler嵌套的深度。對(duì)于ARM平臺(tái)的linux kernel,其中斷部分的代碼如下:

      void handle_IRQ(unsigned int irq, struct pt_regs *regs)
      {
          struct pt_regs *old_regs = set_irq_regs(regs);

          irq_enter(); 
          generic_handle_irq(irq);

          irq_exit();
          set_irq_regs(old_regs);
      }

      通用的IRQ handler被irq_enter和irq_exit這兩個(gè)函數(shù)包圍。irq_enter說(shuō)明進(jìn)入到IRQ context,而irq_exit則說(shuō)明退出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,說(shuō)明硬件中斷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á)到理論的上限?;谶@樣的考慮,后來(lái)內(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。不過(guò)呢,不能總拿理論說(shuō)事,實(shí)際上,萬(wàn)一有寫奇葩或者老古董driver在handler中打開中斷,那么這時(shí)候中斷嵌套還是會(huì)發(fā)生的,但是,應(yīng)該不會(huì)太多(一個(gè)系統(tǒng)中怎么可能有那么多奇葩呢?呵呵),因此,目前hardirq count占用了4個(gè)bit,應(yīng)付15個(gè)奇葩driver是妥妥的。

      對(duì)softirq count進(jìn)行操作有兩個(gè)場(chǎng)景:

      (1)也是在進(jìn)入soft irq handler之前給 softirq count加一,退出soft irq handler之后給 softirq count減去一。由于soft irq handler在一個(gè)CPU上是不會(huì)并發(fā)的,總是串行執(zhí)行,因此,這個(gè)場(chǎng)景下只需要一個(gè)bit就夠了,也就是上圖中的bit 8。通過(guò)該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之后,我們來(lái)介紹各種context:

      #define in_irq()        (hardirq_count())
      #define in_softirq()        (softirq_count())
      #define in_interrupt()        (irq_count())

      #define in_serving_softirq()    (softirq_count() & SOFTIRQ_OFFSET)

      這里首先要介紹的是一個(gè)叫做IRQ context的術(shù)語(yǔ)。這里的IRQ context其實(shí)就是hard irq context,也就是說(shuō)明當(dāng)前正在執(zhí)行中斷handler(top half),只要preempt_count中的hardirq count大于0(=1是沒有中斷嵌套,如果大于1,說(shuō)明有中斷嵌套),那么就是IRQ context。

      softirq context并沒有那么的直接,一般人會(huì)認(rèn)為當(dāng)sofirq handler正在執(zhí)行的時(shí)候就是softirq context。這樣說(shuō)當(dāng)然沒有錯(cuò),sofirq handler正在執(zhí)行的時(shí)候,會(huì)增加softirq count,當(dāng)然是softirq context。不過(guò),在其他context的情況下,例如進(jìn)程上下文中,有有可能因?yàn)橥降囊蠖{(diào)用local_bh_disable,這時(shí)候,通過(guò)local_bh_disable/enable保護(hù)起來(lái)的代碼也是執(zhí)行在softirq context中。當(dāng)然,這時(shí)候其實(shí)并沒有正在執(zhí)行softirq handler。如果你確實(shí)想知道當(dāng)前是否正在執(zhí)行softirq handler,in_serving_softirq可以完成這個(gè)使命,這是通過(guò)操作preempt_count的bit 8來(lái)完成的。

      所謂中斷上下文,就是IRQ context + softirq context+NMI context。

       

      四、softirq機(jī)制

      softirq和hardirq(就是硬件中斷啦)是對(duì)應(yīng)的,因此softirq的機(jī)制可以參考hardirq對(duì)應(yīng)理解,當(dāng)然softirq是純軟件的,不需要硬件參與。

      1、softirq number

      和IRQ number一樣,對(duì)于軟中斷,linux kernel也是用一個(gè)softirq number唯一標(biāo)識(shí)一個(gè)softirq,具體定義如下:

      enum
      {
          HI_SOFTIRQ=0,
          TIMER_SOFTIRQ,
          NET_TX_SOFTIRQ,
          NET_RX_SOFTIRQ,
          BLOCK_SOFTIRQ,
          BLOCK_IOPOLL_SOFTIRQ,
          TASKLET_SOFTIRQ,
          SCHED_SOFTIRQ,
          HRTIMER_SOFTIRQ,
          RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */

          NR_SOFTIRQS
      };

      HI_SOFTIRQ用于高優(yōu)先級(jí)的tasklet,TASKLET_SOFTIRQ用于普通的tasklet。TIMER_SOFTIRQ是for software timer的(所謂software timer就是說(shuō)該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)說(shuō)了,softirq是靜態(tài)定義的,也就是說(shuō)系統(tǒng)中有一個(gè)定義softirq描述符的數(shù)組,而softirq number就是這個(gè)數(shù)組的index。這個(gè)概念和早期的靜態(tài)分配的中斷描述符概念是類似的。具體定義如下:

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

      static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

      系統(tǒng)支持多少個(gè)軟中斷,靜態(tài)定義的數(shù)組就會(huì)有多少個(gè)entry。____cacheline_aligned保證了在SMP的情況下,softirq_vec是對(duì)齊到cache line的。softirq描述符非常簡(jiǎn)單,只有一個(gè)action成員,表示如果觸發(fā)了該softirq,那么應(yīng)該調(diào)用action回調(diào)函數(shù)來(lái)處理這個(gè)soft irq。對(duì)于硬件中斷而言,其mask、ack等都是和硬件寄存器相關(guān)并封裝在irq chip函數(shù)中,對(duì)于softirq,沒有硬件寄存器,只有“軟件寄存器”,定義如下:

      typedef struct {
          unsigned int __softirq_pending;
      #ifdef CONFIG_SMP
          unsigned int ipi_irqs[NR_IPI];
      #endif
      } ____cacheline_aligned irq_cpustat_t;

      irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned;

      ipi_irqs這個(gè)成員用于處理器之間的中斷,我們留到下一個(gè)專題來(lái)描述。__softirq_pending就是這個(gè)“軟件寄存器”。softirq采用誰(shuí)觸發(fā),誰(shuí)負(fù)責(zé)處理的。例如:當(dāng)一個(gè)驅(qū)動(dòng)的硬件中斷被分發(fā)給了指定的CPU,并且在該中斷handler中觸發(fā)了一個(gè)softirq,那么該CPU負(fù)責(zé)調(diào)用該softirq number對(duì)應(yīng)的action callback來(lái)處理該軟中斷。因此,這個(gè)“軟件寄存器”應(yīng)該是每個(gè)CPU擁有一個(gè)(專業(yè)術(shù)語(yǔ)叫做banked register)。為了性能,irq_stat中的每一個(gè)entry被定義對(duì)齊到cache line。

      3、如何注冊(cè)一個(gè)softirq

      通過(guò)調(diào)用open_softirq接口函數(shù)可以注冊(cè)softirq的action callback函數(shù),具體如下:

      void open_softirq(int nr, void (*action)(struct softirq_action *))
      {
          softirq_vec[nr].action = action;
      }

      softirq_vec是一個(gè)多CPU之間共享的數(shù)據(jù),不過(guò),由于所有的注冊(cè)都是在系統(tǒng)初始化的時(shí)候完成的,那時(shí)候,系統(tǒng)是串行執(zhí)行的。此外,softirq是靜態(tài)定義的,每個(gè)entry(或者說(shuō)每個(gè)softirq number)都是固定分配的,因此,不需要保護(hù)。

      4、如何觸發(fā)softirq?

      在linux kernel中,可以調(diào)用raise_softirq這個(gè)接口函數(shù)來(lái)觸發(fā)本地CPU上的softirq,具體如下:

      void raise_softirq(unsigned int nr)
      {
          unsigned long flags;

          local_irq_save(flags);
          raise_softirq_irqoff(nr);
          local_irq_restore(flags);
      }

      雖然大部分的使用場(chǎng)景都是在中斷handler中(也就是說(shuō)關(guān)閉本地CPU中斷)來(lái)執(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)中斷來(lái)保護(hù)“soft irq status register”。

      所謂trigger softirq,就是在__softirq_pending(也就是上面說(shuō)的soft irq status register)的某個(gè)bit置一。從上面的定義可知,__softirq_pending是per cpu的,因此不需要考慮多個(gè)CPU的并發(fā),只要disable本地中斷,就可以確保對(duì),__softirq_pending操作的原子性。

      具體raise_softirq_irqoff的代碼如下:

      inline void raise_softirq_irqoff(unsigned int nr)
      {
          __raise_softirq_irqoff(nr); ----------------(1)


          if (!in_interrupt())
              wakeup_softirqd();------------------(2)
      }

      (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ù)用來(lái)喚醒本CPU上的softirqd這個(gè)內(nèi)核線程。具體softirqd的內(nèi)容請(qǐng)參考下一個(gè)章節(jié)。

       

      5、disable/enable softirq

      在linux kernel中,可以使用local_irq_disable和local_irq_enable來(lái)disable和enable本CPU中斷。和硬件中斷一樣,軟中斷也可以disable,接口函數(shù)是local_bh_disable和local_bh_enable。雖然和想像的local_softirq_enable/disable有些出入,不過(guò)bh這個(gè)名字更準(zhǔn)確反應(yīng)了該接口函數(shù)的意涵,因?yàn)閘ocal_bh_disable/enable函數(shù)就是用來(lái)disable/enable bottom half的,這里就包括softirq和tasklet。

      先看disable吧,畢竟禁止bottom half比較簡(jiǎn)單:

      static inline void local_bh_disable(void)
      {
          __local_bh_disable_ip(_THIS_IP_, SOFTIRQ_DISABLE_OFFSET);
      }

      static __always_inline void __local_bh_disable_ip(unsigned long ip, unsigned int cnt)
      {
          preempt_count_add(cnt);
          barrier();
      }

      看起來(lái)disable bottom half比較簡(jiǎn)單,就是講current thread info上的preempt_count成員中的softirq count的bit field9~15加上一就OK了。barrier是優(yōu)化屏障(Optimization barrier),會(huì)在內(nèi)核同步系列文章中描述。

      enable函數(shù)比較復(fù)雜,如下:

      static inline void local_bh_enable(void)
      {
          __local_bh_enable_ip(_THIS_IP_, SOFTIRQ_DISABLE_OFFSET);
      }

      void __local_bh_enable_ip(unsigned long ip, unsigned int cnt)
      {
          WARN_ON_ONCE(in_irq() || irqs_disabled());-----------(1)

          preempt_count_sub(cnt - 1); ------------------(2)

          if (unlikely(!in_interrupt() && local_softirq_pending())) { -------(3)
              do_softirq();
          }

          preempt_count_dec(); ---------------------(4)
          preempt_check_resched();
      }

      (1)disable/enable bottom half是一種內(nèi)核同步機(jī)制。在硬件中斷的handler(top half)中,不應(yīng)該調(diào)用disable/enable bottom half函數(shù)來(lái)保護(hù)共享數(shù)據(jù),因?yàn)閎ottom half其實(shí)是不可能搶占top half的。同樣的,soft irq也不會(huì)搶占另外一個(gè)soft irq的執(zhí)行,也就是說(shuō),一旦一個(gè)softirq handler被調(diào)度執(zhí)行(無(wú)論在哪一個(gè)processor上),那么,本地的softirq handler都無(wú)法搶占其運(yùn)行,要等到當(dāng)前的softirq handler運(yùn)行完畢后,才能執(zhí)行下一個(gè)soft irq handler。注意:上面我們說(shuō)的是本地,是local,softirq handler是可以在多個(gè)CPU上同時(shí)運(yùn)行的,但是,linux kernel中沒有disable all softirq的接口函數(shù)(就好像沒有disable all CPU interrupt的接口一樣,注意體會(huì)local_bh_enable/disable中的local的含義)。

      說(shuō)了這么多,一言以蔽之,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,有可能是下面這樣的代碼造成的:

      local_irq_disable();

      ……
      local_bh_disable();

      ……

      local_bh_enable();
      ……
      local_irq_enable();

      本質(zhì)上,關(guān)本地中斷是一種比關(guān)本地bottom half更強(qiáng)勁的鎖,關(guān)本地中斷實(shí)際上是禁止了top half和bottom half搶占當(dāng)前進(jìn)程上下文的運(yùn)行。也許你會(huì)說(shuō):這也沒有什么,就是有些浪費(fèi),至少代碼邏輯沒有問(wèn)題。但事情沒有這么簡(jiǎn)單,在local_bh_enable--->do_softirq--->__do_softirq中,有一條無(wú)條件打開當(dāng)前中斷的操作,也就是說(shuō),原本想通過(guò)local_irq_disable/local_irq_enable保護(hù)的臨界區(qū)被破壞了,其他的中斷handler可以插入執(zhí)行,從而無(wú)法保證local_irq_disable/local_irq_enable保護(hù)的臨界區(qū)的原子性,從而破壞了代碼邏輯。

      in_irq()這個(gè)函數(shù)如果不等于0的話,說(shuō)明local_bh_enable被irq_enter和irq_exit包圍,也就是說(shuō)在中斷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)程上下文的代碼場(chǎng)景:

      ……

      local_bh_disable

      ……需要被保護(hù)的臨界區(qū)……

      local_bh_enable

      ……

      在臨界區(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)場(chǎng)的時(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ù)來(lái)處理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ù)來(lái)處理軟中斷。

      (4)該來(lái)的總會(huì)來(lái),在step 2中我們少減了1,這里補(bǔ)上,其實(shí)也就是preempt count-1。

      (5)在softirq handler中很可能wakeup了高優(yōu)先級(jí)的任務(wù),這里最好要檢查一下,看看是否需要進(jìn)行調(diào)度,確保高優(yōu)先級(jí)的任務(wù)得以調(diào)度執(zhí)行。

       

      5、如何處理一個(gè)被觸發(fā)的soft irq

      我們說(shuō)softirq是一種defering task的機(jī)制,也就是說(shuō)top half沒有做的事情,需要延遲到bottom half中來(lái)執(zhí)行。那么具體延遲到什么時(shí)候呢?這是本節(jié)需要講述的內(nèi)容,也就是說(shuō)soft irq是如何調(diào)度執(zhí)行的。

      在上一節(jié)已經(jīng)描述一個(gè)softirq被調(diào)度執(zhí)行的場(chǎng)景,本節(jié)主要關(guān)注在中斷返回現(xiàn)場(chǎng)時(shí)候調(diào)度softirq的場(chǎng)景。我們來(lái)看中斷退出的代碼,具體如下:

      void irq_exit(void)
      {
      ……
          if (!in_interrupt() && local_softirq_pending())
              invoke_softirq();

      ……
      }

      代碼中“!in_interrupt()”這個(gè)條件可以確保下面的場(chǎng)景不會(huì)觸發(fā)sotfirq的調(diào)度:

      (1)中斷handler是嵌套的。也就是說(shuō)本次irq_exit是退出到上一個(gè)中斷handler。當(dāng)然,在新的內(nèi)核中,這種情況一般不會(huì)發(fā)生,因?yàn)橹袛鄅andler都是關(guān)中斷執(zhí)行的。

      (2)本次中斷是中斷了softirq handler的執(zhí)行。也就是說(shuō)本次irq_exit是不是退出到進(jìn)程上下文,而是退出到上一個(gè)softirq context。這一點(diǎn)也保證了在一個(gè)CPU上的softirq是串行執(zhí)行的(注意:多個(gè)CPU上還是有可能并發(fā)的)

      我們繼續(xù)看invoke_softirq的代碼:

      static inline void invoke_softirq(void)
      {
          if (!force_irqthreads) {
      #ifdef CONFIG_HAVE_IRQ_EXIT_ON_IRQ_STACK
              __do_softirq();
      #else
              do_softirq_own_stack();
      #endif
          } else {
              wakeup_softirqd();
          }
      }

      force_irqthreads是和強(qiáng)制線程化相關(guān)的,主要用于interrupt handler的調(diào)試(一般而言,在線程環(huán)境下比在中斷上下文中更容易收集調(diào)試數(shù)據(jù))。如果系統(tǒng)選擇了對(duì)所有的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)然對(duì)ARM而言,softirq的處理就是在當(dāng)前的內(nèi)核棧上執(zhí)行的,因此do_softirq_own_stack的調(diào)用就是調(diào)用__do_softirq(),代碼如下(刪除了部分無(wú)關(guān)代碼):

      asmlinkage void __do_softirq(void)
      {

      ……

          pending = local_softirq_pending();---------------獲取softirq pending的狀態(tài)

          __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);---標(biāo)識(shí)下面的代碼是正在處理softirq

          cpu = smp_processor_id();
      restart:
          set_softirq_pending(0); ---------清除pending標(biāo)志

          local_irq_enable(); ------打開中斷,softirq handler是開中斷執(zhí)行的

          h = softirq_vec; -------獲取軟中斷描述符指針

          while ((softirq_bit = ffs(pending))) {-------尋找pending中第一個(gè)被設(shè)定為1的bit
              unsigned int vec_nr;
              int prev_count;

              h += softirq_bit - 1; ------指向pending的那個(gè)軟中斷描述符

              vec_nr = h - softirq_vec;----獲取soft irq number

              h->action(h);---------指向softirq handler

              h++;
              pending >>= softirq_bit;
          }

          local_irq_disable(); -------打開中斷

          pending = local_softirq_pending();----------(注1)
          if (pending) {
              if (time_before(jiffies, end) && !need_resched() &&
                  --max_restart)
                  goto restart;

              wakeup_softirqd();
          }


          __local_bh_enable(SOFTIRQ_OFFSET);----------標(biāo)識(shí)softirq處理完畢

      }

      (注1)再次檢查softirq pending,有可能上面的softirq handler在執(zhí)行過(guò)程中,發(fā)生了中斷,又raise了softirq。如果的確如此,那么我們需要跳轉(zhuǎn)到restart那里重新處理soft irq。當(dāng)然,也不能總是在這里不斷的loop,因此linux kernel設(shè)定了下面的條件:

      (1)softirq的處理時(shí)間沒有超過(guò)2個(gè)ms

      (2)上次的softirq中沒有設(shè)定TIF_NEED_RESCHED,也就是說(shuō)沒有有高優(yōu)先級(jí)任務(wù)需要調(diào)度

      (3)loop的次數(shù)小于 10次

      因此,只有同時(shí)滿足上面三個(gè)條件,程序才會(huì)跳轉(zhuǎn)到restart那里重新處理soft irq。否則wakeup_softirqd就OK了。這樣的設(shè)計(jì)也是一個(gè)平衡的方案。一方面照顧了調(diào)度延遲:本來(lái),發(fā)生一個(gè)中斷,系統(tǒng)期望在限定的時(shí)間內(nèi)調(diào)度某個(gè)進(jìn)程來(lái)處理這個(gè)中斷,如果softirq handler不斷觸發(fā),其實(shí)linux kernel是無(wú)法保證調(diào)度延遲時(shí)間的。另外一方面,也照顧了硬件的thoughput:已經(jīng)預(yù)留了一定的時(shí)間來(lái)處理softirq。



      原創(chuàng)文章,轉(zhuǎn)發(fā)請(qǐng)注明出處。蝸窩科技

      http://www./linux_kenrel/soft-irq.html

      標(biāo)簽: 軟中斷 softirq

      評(píng)論:

      ayeu0425
      2015-01-19 17:49
      寫得好,一下子就全懂了
      azureming
      2014-11-10 13:24
      Linuxer,你好,又來(lái)請(qǐng)教個(gè)問(wèn)題;
      既然可以采用threaded_irq,那么所謂的bottom機(jī)制【比如softirq】是不是可以不使用了,或者他們?cè)谑褂玫臅r(shí)候各有應(yīng)用場(chǎng)景來(lái)著?
      謝謝
      linuxer
      2014-11-10 17:36
      @azureming:中斷線程化是從linux的rt tree導(dǎo)入的一個(gè)特性,因此,threaded irq更多的是從系統(tǒng)實(shí)時(shí)性的角度來(lái)看待問(wèn)題。

      如果把threaded irq handler和bottom half對(duì)比的話,可能workqueue(而不是softirq)更合適一些,畢竟work是在進(jìn)程上下文中執(zhí)行的,和threaded irq handler是類似的。不過(guò)雖然都是在進(jìn)程上下文執(zhí)行,但是,還是有不一樣的,對(duì)于線程化的irq handler,其執(zhí)行在一個(gè)實(shí)時(shí)進(jìn)程上,也就是說(shuō)本質(zhì)上原來(lái)在handler在中斷上下文中執(zhí)行,但是現(xiàn)在修改為在系統(tǒng)中的一個(gè)rt進(jìn)程中執(zhí)行,所有threaded irq handler都是先于系統(tǒng)中的normal進(jìn)程被調(diào)度執(zhí)行。

      對(duì)于workqueue,其進(jìn)程是一個(gè)普通進(jìn)程,需要和很多系統(tǒng)中的普通的進(jìn)程共同爭(zhēng)搶cpu資源。

      我們來(lái)看一個(gè)場(chǎng)景:如果某個(gè)外設(shè)是慢速總線設(shè)備,例如I2C(呵呵,I2C總是在各種場(chǎng)合下出現(xiàn)),讀取I2C上的數(shù)據(jù)不適合在interrupt handler中執(zhí)行,因此,我們考慮使用workqueue。但是,如果I2C外設(shè)的FIFO很小,而系統(tǒng)又很繁忙,那么這種場(chǎng)景下使用workqueue就會(huì)丟失來(lái)自外設(shè)的數(shù)據(jù)。這時(shí)候,使用threaded irq handler會(huì)改善。當(dāng)然,是否保證不丟失數(shù)據(jù)是一個(gè)系統(tǒng)問(wèn)題,你需要仔細(xì)的設(shè)計(jì)系統(tǒng)中所有task的優(yōu)先級(jí)

      soft irq是一定需要的,它是在中斷上下文執(zhí)行的,只要是中斷上下文,其優(yōu)先級(jí)總是高過(guò)進(jìn)程上下文
      tigger
      2014-11-10 18:40
      @linuxer:這里可能還要補(bǔ)充一下linux調(diào)度策略以及調(diào)度器類的概念。但是我的功力還不夠正確的描述出來(lái)。所以可以通俗的理解rt進(jìn)程會(huì)優(yōu)先于normal進(jìn)程被調(diào)度執(zhí)行就可以了。其實(shí)我的理解是rt進(jìn)程對(duì)應(yīng)的調(diào)度器類的優(yōu)先級(jí)高于完全公平進(jìn)程。不知道這樣表述是否可以。
      linuxer
      2014-11-10 22:42
      @tigger:那些內(nèi)容是任務(wù)管理子系統(tǒng)的內(nèi)容,這里我就偷懶一下吧,等到release任務(wù)管理子系統(tǒng)的文檔再詳細(xì)講述調(diào)度器以及調(diào)度算法的內(nèi)容吧
      azureming
      2014-11-11 09:18
      @linuxer:我可不可以這樣理解:
      workqueue這個(gè)下半部和普通進(jìn)程的優(yōu)先級(jí)一樣,所以有時(shí)候這個(gè)下半部得不到及時(shí)的執(zhí)行;
      threaded irq優(yōu)先級(jí)高于普通進(jìn)程,這點(diǎn)和soft irq一樣(高的優(yōu)先級(jí)),可以讓待處理的任務(wù)得到較早的執(zhí)行,相對(duì)于soft irq不能被進(jìn)程搶占,threaded irq的機(jī)制則可以通過(guò)設(shè)置某些進(jìn)程更高的優(yōu)先級(jí),從而獲得比threaded irq更早的執(zhí)行機(jī)會(huì),從而滿足特定的實(shí)時(shí)性要求。
      soft irq的存在主要是為了保證這個(gè)下半部的優(yōu)先執(zhí)行權(quán)限,不能被進(jìn)程打斷?
      linuxer
      2014-11-11 11:56
      @azureming:你的理解基本上是正確的,linux kernel提供了各種工具,如何正確的使用這些工具,適合自己的系統(tǒng)也是一個(gè)很有技術(shù)含量的工作,也就是傳說(shuō)中的系統(tǒng)設(shè)計(jì)過(guò)程。
      linuxer
      2014-11-06 18:46
      修改了強(qiáng)制線程化的內(nèi)容,原來(lái)的理解有偏差
      forion
      2014-10-30 16:30
      hi linuxer 有一處沒看懂的地方:
      (4)該來(lái)的總會(huì)來(lái),在step 2中我們少減了1,這里補(bǔ)上,其實(shí)也就是enable preempt。
      preempt_count_dec(); ---------------------(4)
      這條指令之后,并不一定使能了preempt吧?這個(gè)只是preempt count -1;
      preempt count=0 才是使能了preempt吧?
      linuxer
      2014-10-30 18:32
      @forion:你說(shuō)的是對(duì)的,enable preempt應(yīng)該是下面一系列動(dòng)作:
      (1)preempt count - 1
      (2)test preempt count, if 0 and really need reschedule, then trigger scheduler to preempt current process.

      preempt_count_dec僅僅是減去1而已
      linuxer
      2014-10-30 18:34
      @forion:forion同學(xué),絕對(duì)是金牌讀者啊
      forion
      2014-10-30 21:12
      @linuxer:不敢當(dāng),不敢當(dāng)。
      這么好的文章不好好讀簡(jiǎn)直就是暴殄天物?。?
      樓主要堅(jiān)持啊。
      linuxer
      2014-10-31 10:13
      @forion:如果我現(xiàn)在的公司不倒閉的話,我應(yīng)該會(huì)堅(jiān)持下去的(現(xiàn)在我所在的公司沒有那么忙,呵呵~~)。不過(guò)如果公司倒了,我要去找新的工作,那就不好說(shuō)了。其實(shí),在中國(guó),技術(shù)大牛很多,只不過(guò)他們都沒有什么時(shí)間在互聯(lián)網(wǎng)上分享而已。
      forion
      2014-10-31 17:15
      @linuxer:是啊,所以找到一個(gè)好老師不容易啊。
      forion
      2014-10-24 17:28
      又一部扛鼎之作,待老衲細(xì)細(xì)品味!

        本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評(píng)論

        發(fā)表

        請(qǐng)遵守用戶 評(píng)論公約

        類似文章 更多