日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux中断(interrupt)子系统之五:软件中断(softIRQ)

發布時間:2023/12/19 linux 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux中断(interrupt)子系统之五:软件中断(softIRQ) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

轉自:http://blog.csdn.net/droidphone/article/details/7518428

軟件中斷(softIRQ)是內核提供的一種延遲執行機制,它完全由軟件觸發,雖然說是延遲機制,實際上,在大多數情況下,它與普通進程相比,能得到更快的響應時間。軟中斷也是其他一些內核機制的基礎,比如tasklet,高分辨率timer等。

/*****************************************************************************************************/
聲明:本博內容均由http://blog.csdn.net/droidphone原創,轉載請注明出處,謝謝!
/*****************************************************************************************************/

1. ?軟件中斷的數據結構

1.1 ?struct softirq_action

內核用softirq_action結構管理軟件中斷的注冊和激活等操作,它的定義如下: [cpp]?view plaincopy
  • struct?softirq_action??
  • {??
  • ????void????(*action)(struct?softirq_action?*);??
  • };??
  • 非常簡單,只有一個用于回調的函數指針。軟件中斷的資源是有限的,內核目前只實現了10種類型的軟件中斷,它們是: [cpp]?view plaincopy
  • 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??
  • };??
  • 內核的開發者們不建議我們擅自增加軟件中斷的數量,如果需要新的軟件中斷,盡可能把它們實現為基于軟件中斷的tasklet形式。與上面的枚舉值相對應,內核定義了一個softirq_action的結構數組,每種軟中斷對應數組中的一項: [cpp]?view plaincopy
  • static?struct?softirq_action?softirq_vec[NR_SOFTIRQS]?__cacheline_aligned_in_smp;??
  • 1.2 ?irq_cpustat_t

    多個軟中斷可以同時在多個cpu運行,就算是同一種軟中斷,也有可能同時在多個cpu上運行。內核為每個cpu都管理著一個待決軟中斷變量(pending),它就是irq_cpustat_t: [cpp]?view plaincopy
  • typedef?struct?{??
  • ????unsigned?int?__softirq_pending;??
  • }?____cacheline_aligned?irq_cpustat_t;??
  • [cpp]?view plaincopy
  • irq_cpustat_t?irq_stat[NR_CPUS]?____cacheline_aligned;??
  • __softirq_pending字段中的每一個bit,對應著某一個軟中斷,某個bit被置位,說明有相應的軟中斷等待處理。

    1.3 ?軟中斷的守護進程ksoftirqd

    在cpu的熱插拔階段,內核為每個cpu創建了一個用于執行軟件中斷的守護進程ksoftirqd,同時定義了一個per_cpu變量用于保存每個守護進程的task_struct結構指針: [cpp]?view plaincopy
  • DEFINE_PER_CPU(struct?task_struct?*,?ksoftirqd);??
  • 大多數情況下,軟中斷都會在irq_exit階段被執行,在irq_exit階段沒有處理完的軟中斷才有可能會在守護進程中執行。

    2. ?觸發軟中斷

    要觸發一個軟中斷,只要調用api:raise_softirq即可,它的實現很簡單,先是關閉本地cpu中斷,然后調用:raise_softirq_irqoff [cpp]?view plaincopy
  • void?raise_softirq(unsigned?int?nr)??
  • {??
  • ????unsigned?long?flags;??
  • ??
  • ????local_irq_save(flags);??
  • ????raise_softirq_irqoff(nr);??
  • ????local_irq_restore(flags);??
  • }??
  • 再看看raise_softirq_irqoff: [cpp]?view plaincopy
  • inline?void?raise_softirq_irqoff(unsigned?int?nr)??
  • {??
  • ????__raise_softirq_irqoff(nr);??
  • ??
  • ????????......??
  • ????if?(!in_interrupt())??
  • ????????wakeup_softirqd();??
  • }??
  • 先是通過__raise_softirq_irqoff設置cpu的軟中斷pending標志位(irq_stat[NR_CPUS] ),然后通過in_interrupt判斷現在是否在中斷上下文中,或者軟中斷是否被禁止,如果都不成立,則喚醒軟中斷的守護進程,在守護進程中執行軟中斷的回調函數。否則什么也不做,軟中斷將會在中斷的退出階段被執行。

    3. ?軟中斷的執行

    基于上面所說,軟中斷的執行既可以守護進程中執行,也可以在中斷的退出階段執行。實際上,軟中斷更多的是在中斷的退出階段執行(irq_exit),以便達到更快的響應,加入守護進程機制,只是擔心一旦有大量的軟中斷等待執行,會使得內核過長地留在中斷上下文中。

    3.1 ?在irq_exit中執行

    看看irq_exit的部分: [cpp]?view plaincopy
  • void?irq_exit(void)??
  • {??
  • ????????......??
  • ????sub_preempt_count(IRQ_EXIT_OFFSET);??
  • ????if?(!in_interrupt()?&&?local_softirq_pending())??
  • ????????invoke_softirq();??
  • ????????......??
  • }??
  • 如果中斷發生嵌套,in_interrupt()保證了只有在最外層的中斷的irq_exit階段,invoke_interrupt才會被調用,當然,local_softirq_pending也會實現判斷當前cpu有無待決的軟中斷。代碼最終會進入__do_softirq中,內核會保證調用__do_softirq時,本地cpu的中斷處于關閉狀態,進入__do_softirq: [cpp]?view plaincopy
  • asmlinkage?void?__do_softirq(void)??
  • {??
  • ????????......??
  • ????pending?=?local_softirq_pending();??
  • ??
  • ????__local_bh_disable((unsigned?long)__builtin_return_address(0),??
  • ????????????????SOFTIRQ_OFFSET);??
  • restart:??
  • ????/*?Reset?the?pending?bitmask?before?enabling?irqs?*/??
  • ????set_softirq_pending(0);??
  • ??
  • ????local_irq_enable();??
  • ??
  • ????h?=?softirq_vec;??
  • ??
  • ????do?{??
  • ????????if?(pending?&?1)?{??
  • ????????????????????......??
  • ????????????trace_softirq_entry(vec_nr);??
  • ????????????h->action(h);??
  • ????????????trace_softirq_exit(vec_nr);??
  • ????????????????????????......??
  • ????????}??
  • ????????h++;??
  • ????????pending?>>=?1;??
  • ????}?while?(pending);??
  • ??
  • ????local_irq_disable();??
  • ??
  • ????pending?=?local_softirq_pending();??
  • ????if?(pending?&&?--max_restart)??
  • ????????goto?restart;??
  • ??
  • ????if?(pending)??
  • ????????wakeup_softirqd();??
  • ??
  • ????lockdep_softirq_exit();??
  • ??
  • ????__local_bh_enable(SOFTIRQ_OFFSET);??
  • }??
    • 首先取出pending的狀態;
    • 禁止軟中斷,主要是為了防止和軟中斷守護進程發生競爭;
    • 清除所有的軟中斷待決標志;
    • 打開本地cpu中斷;
    • 循環執行待決軟中斷的回調函數;
    • 如果循環完畢,發現新的軟中斷被觸發,則重新啟動循環,直到以下條件滿足,才退出:
      • 沒有新的軟中斷等待執行;
      • 循環已經達到最大的循環次數MAX_SOFTIRQ_RESTART,目前的設定值時10次;
    • 如果經過MAX_SOFTIRQ_RESTART次循環后還未處理完,則激活守護進程,處理剩下的軟中斷;
    • 推出前恢復軟中斷;

    3.2 ?在ksoftirqd進程中執行

    從前面幾節的討論我們可以看出,軟中斷也可能由ksoftirqd守護進程執行,這要發生在以下兩種情況下:
    • 在irq_exit中執行軟中斷,但是在經過MAX_SOFTIRQ_RESTART次循環后,軟中斷還未處理完,這種情況雖然極少發生,但畢竟有可能;
    • 內核的其它代碼主動調用raise_softirq,而這時正好不是在中斷上下文中,守護進程將被喚醒;
    守護進程最終也會調用__do_softirq執行軟中斷的回調,具體的代碼位于run_ksoftirqd函數中,內核會關閉搶占的情況下執行__do_softirq,具體的過程這里不做討論。

    4. ?tasklet

    因為內核已經定義好了10種軟中斷類型,并且不建議我們自行添加額外的軟中斷,所以對軟中斷的實現方式,我們主要是做一個簡單的了解,對于驅動程序的開發者來說,無需實現自己的軟中斷。但是,對于某些情況下,我們不希望一些操作直接在中斷的handler中執行,但是又希望在稍后的時間里得到快速地處理,這就需要使用tasklet機制。 tasklet是建立在軟中斷上的一種延遲執行機制,它的實現基于TASKLET_SOFTIRQ和HI_SOFTIRQ這兩個軟中斷類型。

    4.1 ?tasklet_struct ? ? ? ?

    在軟中斷的初始化函數softirq_init的最后,內核注冊了TASKLET_SOFTIRQ和HI_SOFTIRQ這兩個軟中斷: [cpp]?view plaincopy
  • void?__init?softirq_init(void)??
  • {??
  • ????????......??
  • ????open_softirq(TASKLET_SOFTIRQ,?tasklet_action);??
  • ????open_softirq(HI_SOFTIRQ,?tasklet_hi_action);??
  • }??
  • ? ? ? ? 內核用一個tasklet_struct來表示一個tasklet,它的定義如下: [cpp]?view plaincopy
  • struct?tasklet_struct??
  • {??
  • ????struct?tasklet_struct?*next;??
  • ????unsigned?long?state;??
  • ????atomic_t?count;??
  • ????void?(*func)(unsigned?long);??
  • ????unsigned?long?data;??
  • };??
  • next用于把同一個cpu的tasklet鏈接成一個鏈表,state用于表示該tasklet的當前狀態,目前只是用了最低的兩個bit,分別用于表示已經準備被調度執行和已經在另一個cpu上執行: [cpp]?view plaincopy
  • enum??
  • {??
  • ????TASKLET_STATE_SCHED,????/*?Tasklet?is?scheduled?for?execution?*/??
  • ????TASKLET_STATE_RUN???/*?Tasklet?is?running?(SMP?only)?*/??
  • };??
  • 原子變量count用于tasklet對tasklet_disable和tasklet_enable的計數,count為0時表示允許tasklet執行,否則不允許執行,每次tasklet_disable時,該值加1,tasklet_enable時該值減1。func是tasklet被執行時的回調函數指針,data則用作回調函數func的參數。

    4.2 ?初始化一個tasklet

    有兩種辦法初始化一個tasklet,第一種是靜態初始化,使用以下兩個宏,這兩個宏定義一個tasklet_struct結構,并用相應的參數對結構中的字段進行初始化:
    • DECLARE_TASKLET(name, func, data);定義名字為name的tasklet,默認為enable狀態,也就是count字段等于0。
    • DECLARE_TASKLET_DISABLED(name, func, data);定義名字為name的tasklet,默認為enable狀態,也就是count字段等于1。
    第二個是動態初始化方法:先定義一個tasklet_struct,然后用tasklet_init函數進行初始化,該方法默認tasklet處于enable狀態: [cpp]?view plaincopy
  • struct?tasklet_struct?tasklet_xxx;??
  • ......??
  • tasklet_init(&tasklet_xxx,?func,?data);??
  • 4.3 ?tasklet的使用方法

    使能和禁止tasklet,使用以下函數:
    • tasklet_disable() ?通過給count字段加1來禁止一個tasklet,如果tasklet正在運行中,則等待運行完畢才返回(通過TASKLET_STATE_RUN標志)。
    • tasklet_disable_nosync() ?tasklet_disable的異步版本,它不會等待tasklet運行完畢。
    • tasklet_enable() ?使能tasklet,只是簡單地給count字段減1。
    調度tasklet的執行,使用以下函數:
    • tasklet_schedule(struct tasklet_struct *t) ?如果TASKLET_STATE_SCHED標志為0,則置位TASKLET_STATE_SCHED,然后把tasklet掛到該cpu等待執行的tasklet鏈表上,接著發出TASKLET_SOFTIRQ軟件中斷請求。
    • tasklet_hi_schedule(struct tasklet_struct *t) ?效果同上,區別是它發出的是HI_SOFTIRQ軟件中斷請求。
    銷毀tasklet,使用以下函數:
    • tasklet_kill(struct tasklet_struct *t) ?如果tasklet處于TASKLET_STATE_SCHED狀態,或者tasklet正在執行,則會等待tasklet執行完畢,然后清除TASKLET_STATE_SCHED狀態。

    4.4 ?tasklet的內部執行機制

    內核為每個cpu用定義了一個tasklet_head結構,用于管理每個cpu上的tasklet的調度和執行: [cpp]?view plaincopy
  • struct?tasklet_head??
  • {??
  • ????struct?tasklet_struct?*head;??
  • ????struct?tasklet_struct?**tail;??
  • };??
  • ??
  • static?DEFINE_PER_CPU(struct?tasklet_head,?tasklet_vec);??
  • static?DEFINE_PER_CPU(struct?tasklet_head,?tasklet_hi_vec);??
  • 回到4.1節,我們知道,tasklet是利用TASKLET_SOFTIRQ和HI_SOFTIRQ這兩個軟中斷來實現的,兩個軟中斷只是有優先級的差別,所以我們只討論TASKLET_SOFTIRQ的實現,TASKLET_SOFTIRQ的中斷回調函數是tasklet_action,我們看看它的代碼: [cpp]?view plaincopy
  • static?void?tasklet_action(struct?softirq_action?*a)??
  • {??
  • ????struct?tasklet_struct?*list;??
  • ??
  • ????local_irq_disable();??
  • ????list?=?__this_cpu_read(tasklet_vec.head);??
  • ????__this_cpu_write(tasklet_vec.head,?NULL);??
  • ????__this_cpu_write(tasklet_vec.tail,?&__get_cpu_var(tasklet_vec).head);??
  • ????local_irq_enable();??
  • ??
  • ????while?(list)?{??
  • ????????struct?tasklet_struct?*t?=?list;??
  • ??
  • ????????list?=?list->next;??
  • ??
  • ????????if?(tasklet_trylock(t))?{??
  • ????????????if?(!atomic_read(&t->count))?{??
  • ????????????????if?(!test_and_clear_bit(TASKLET_STATE_SCHED,?&t->state))??
  • ????????????????????BUG();??
  • ????????????????t->func(t->data);??
  • ????????????????tasklet_unlock(t);??
  • ????????????????continue;??
  • ????????????}??
  • ????????????tasklet_unlock(t);??
  • ????????}??
  • ??
  • ????????local_irq_disable();??
  • ????????t->next?=?NULL;??
  • ????????*__this_cpu_read(tasklet_vec.tail)?=?t;??
  • ????????__this_cpu_write(tasklet_vec.tail,?&(t->next));??
  • ????????__raise_softirq_irqoff(TASKLET_SOFTIRQ);??
  • ????????local_irq_enable();??
  • ????}??
  • }??
  • 解析如下:
    • 關閉本地中斷的前提下,移出當前cpu的待處理tasklet鏈表到一個臨時鏈表后,清除當前cpu的tasklet鏈表,之所以這樣處理,是為了處理當前tasklet鏈表的時候,允許新的tasklet被調度進待處理鏈表中。
    • 遍歷臨時鏈表,用tasklet_trylock判斷當前tasklet是否已經在其他cpu上運行,而且tasklet沒有被禁止:
      • 如果沒有運行,也沒有禁止,則清除TASKLET_STATE_SCHED狀態位,執行tasklet的回調函數。
      • 如果已經在運行,或者被禁止,則把該tasklet重新添加會當前cpu的待處理tasklet鏈表上,然后觸發TASKLET_SOFTIRQ軟中斷,等待下一次軟中斷時再次執行。
    分析到這了我有個疑問,看了上面的代碼,如果一個tasklet被tasklet_schedule后,在沒有被執行前被tasklet_disable了,豈不是會無窮無盡地引發TASKLET_SOFTIRQ軟中斷? 通過以上的分析,我們需要注意的是,tasklet有以下幾個特征:
    • 同一個tasklet只能同時在一個cpu上執行,但不同的tasklet可以同時在不同的cpu上執行;
    • 一旦tasklet_schedule被調用,內核會保證tasklet一定會在某個cpu上執行一次;
    • 如果tasklet_schedule被調用時,tasklet不是出于正在執行狀態,則它只會執行一次;
    • 如果tasklet_schedule被調用時,tasklet已經正在執行,則它會在稍后被調度再次被執行;
    • 兩個tasklet之間如果有資源沖突,應該要用自旋鎖進行同步保護;
    【作者】張昺華 【出處】http://www.cnblogs.com/sky-heaven/ 【博客園】 http://www.cnblogs.com/sky-heaven/ 【新浪博客】 http://blog.sina.com.cn/u/2049150530 【知乎】 http://www.zhihu.com/people/zhang-bing-hua 【我的作品---旋轉倒立擺】 http://v.youku.com/v_show/id_XODM5NDAzNjQw.html?spm=a2hzp.8253869.0.0&from=y1.7-2 【我的作品---自平衡自動循跡車】 http://v.youku.com/v_show/id_XODM5MzYyNTIw.html?spm=a2hzp.8253869.0.0&from=y1.7-2 【新浪微博】 張昺華--sky 【twitter】 @sky2030_ 【facebook】 張昺華 zhangbinghua 本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利.

    總結

    以上是生活随笔為你收集整理的Linux中断(interrupt)子系统之五:软件中断(softIRQ)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。