日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

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

 老匹夫 2015-04-08

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)行討論:

rrr

當(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)容):

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

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ù)格式可以參考下圖:

preempt-count

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,其中斷部分的代碼如下:

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說明進(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:

#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ù)語。這里的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,具體定義如下:

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)先級的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)分配的中斷描述符概念是類似的。具體定義如下:

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是對齊到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,沒有硬件寄存器,只有“軟件寄存器”,定義如下:

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è)專題來描述。__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ù),具體如下:

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

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,具體如下:

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

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

雖然大部分的使用場景都是在中斷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的代碼如下:

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ù)用來喚醒本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比較簡單:

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();
}

看起來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ù)雜,如下:

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ù)來保護(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,有可能是下面這樣的代碼造成的:

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ì)說:這也沒有什么,就是有些浪費(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)程上下文的代碼場景:

……

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)場的時(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的場景。我們來看中斷退出的代碼,具體如下:

void irq_exit(void)
{
……
    if (!in_interrupt() && local_softirq_pending())
        invoke_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的代碼:

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)選擇了對所有的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)代碼):

asmlinkage void __do_softirq(void)
{

……

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

    __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);---標(biāo)識下面的代碼是正在處理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)識softirq處理完畢

}

(注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

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

評論:

ayeu0425
2015-01-19 17:49
寫得好,一下子就全懂了
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)程。不知道這樣表述是否可以。
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)先級一樣,所以有時(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)程打斷?
linuxer
2014-11-11 11:56
@azureming:你的理解基本上是正確的,linux kernel提供了各種工具,如何正確的使用這些工具,適合自己的系統(tǒng)也是一個(gè)很有技術(shù)含量的工作,也就是傳說中的系統(tǒng)設(shè)計(jì)過程。
linuxer
2014-11-06 18:46
修改了強(qiáng)制線程化的內(nèi)容,原來的理解有偏差
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吧?
linuxer
2014-10-30 18:32
@forion:你說的是對的,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é),絕對是金牌讀者啊
forion
2014-10-30 21:12
@linuxer:不敢當(dāng),不敢當(dāng)。
這么好的文章不好好讀簡直就是暴殄天物??!
樓主要堅(jiān)持啊。
linuxer
2014-10-31 10:13
@forion:如果我現(xiàn)在的公司不倒閉的話,我應(yīng)該會(huì)堅(jiān)持下去的(現(xiàn)在我所在的公司沒有那么忙,呵呵~~)。不過如果公司倒了,我要去找新的工作,那就不好說了。其實(shí),在中國,技術(shù)大牛很多,只不過他們都沒有什么時(shí)間在互聯(lián)網(wǎng)上分享而已。
forion
2014-10-31 17:15
@linuxer:是啊,所以找到一個(gè)好老師不容易啊。
forion
2014-10-24 17:28
又一部扛鼎之作,待老衲細(xì)細(xì)品味!

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

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多