Linux中斷 - tasklet

一、前言

對于中斷處理而言,linux將其分成了兩個部分,一個叫做中斷handler(top half),屬于不那么緊急需要處理的事情被推遲執行,我們稱之deferable task,或者叫做bottom half,。具體如何推遲執行分成下面幾種情況:

1、推遲到top half執行完畢

2、推遲到某個指定的時間片(例如40ms)之后執行

3、推遲到某個內核線程被調度的時候執行

對于第一種情況,內核中的機制包括softirq機制和tasklet機制。第二種情況是屬于softirq機制的一種應用場景(timer類型的softirq),在本站的時間子系統的系列文檔中會描述。第三種情況主要包括threaded irq handler以及通用的workqueue機制,當然也包括自己創建該驅動專屬kernel thread(不推薦使用)。本文主要描述tasklet這種機制,第二章描述一些背景知識和和tasklet的思考,第三章結合代碼描述tasklet的原理。

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

?

二、為什么需要tasklet?

1、基本的思考

我們的驅動程序或者內核模塊真的需要tasklet嗎?每個人都有自己的看法。我們先拋開linux kernel中的機制,首先進行一番邏輯思考。

將中斷處理分成top half(cpu和外設之間的交互,獲取狀態,ack狀態,收發數據等)和bottom half(后段的數據處理)已經深入人心,對于任何的OS都一樣,將不那么緊急的事情推遲到bottom half中執行是OK的,具體如何推遲執行分成兩種類型:有具體時間要求的(對應linux kernel中的低精度timer和高精度timer)和沒有具體時間要求的。對于沒有具體時間要求的又可以分成兩種:

(1)越快越好型,這種實際上是有性能要求的,除了中斷top half可以搶占其執行,其他的進程上下文(無論該進程的優先級多么的高)是不會影響其執行的,一言以蔽之,在不影響中斷延遲的情況下,OS會盡快處理。

(2)隨遇而安型。這種屬于那種沒有性能需求的,其調度執行依賴系統的調度器。

本質上講,越快越好型的bottom half不應該太多,而且tasklet的callback函數不能執行時間過長,否則會產生進程調度延遲過大的現象,甚至是非常長而且不確定的延遲,對real time的系統會產生很壞的影響。

2、對linux中的bottom half機制的思考

在linux kernel中,“越快越好型”有兩種,softirq和tasklet,“隨遇而安型”也有兩種,workqueue和threaded irq handler。“越快越好型”能否只留下一個softirq呢?對于崇尚簡單就是美的程序員當然希望如此。為了回答這個問題,我們先看看tasklet對于softirq而言有哪些好處:

(1)tasklet可以動態分配,也可以靜態分配,數量不限。

(2)同一種tasklet在多個cpu上也不會并行執行,這使得程序員在撰寫tasklet function的時候比較方便,減少了對并發的考慮(當然損失了性能)。

對于第一種好處,其實也就是為亂用tasklet打開了方便之門,很多撰寫驅動的軟件工程師不會仔細考量其driver是否有性能需求就直接使用了tasklet機制。對于第二種好處,本身考慮并發就是軟件工程師的職責。因此,看起來tasklet并沒有引入特別的好處,而且和softirq一樣,都不能sleep,限制了handler撰寫的方便性,看起來其實并沒有存在的必要。在4.0 kernel的代碼中,grep一下tasklet的使用,實際上是一個很長的列表,只要對這些使用進行簡單的歸類就可以刪除對tasklet的使用。對于那些有性能需求的,可以考慮并入softirq,其他的可以考慮使用workqueue來取代。Steven Rostedt試圖進行這方面的嘗試(http://lwn.net/Articles/239484/),不過這個patch始終未能進入main line。

?

三、tasklet的基本原理

1、如何抽象一個tasklet

內核中用下面的數據結構來表示tasklet:

struct tasklet_struct
{
??? struct tasklet_struct *next;
??? unsigned long state;
??? atomic_t count;
??? void (*func)(unsigned long);
??? unsigned long data;
};

每個cpu都會維護一個鏈表,將本cpu需要處理的tasklet管理起來,next這個成員指向了該鏈表中的下一個tasklet。func和data成員描述了該tasklet的callback函數,func是調用函數,data是傳遞給func的參數。state成員表示該tasklet的狀態,TASKLET_STATE_SCHED表示該tasklet以及被調度到某個CPU上執行,TASKLET_STATE_RUN表示該tasklet正在某個cpu上執行。count成員是和enable或者disable該tasklet的狀態相關,如果count等于0那么該tasklet是處于enable的,如果大于0,表示該tasklet是disable的。在softirq文檔中,我們知道local_bh_disable/enable函數就是用來disable/enable bottom half的,這里就包括softirq和tasklet。但是,有的時候內核同步的場景不需disable所有的softirq和tasklet,而僅僅是disable該tasklet,這時候,tasklet_disable和tasklet_enable就派上用場了。

static inline void tasklet_disable(struct tasklet_struct *t)
{
??? tasklet_disable_nosync(t);-------給tasklet的count加一
??? tasklet_unlock_wait(t);-----如果該tasklet處于running狀態,那么需要等到該tasklet執行完畢
??? smp_mb();
}

static inline void tasklet_enable(struct tasklet_struct *t)
{
??? smp_mb__before_atomic();
??? atomic_dec(&t->count);-------給tasklet的count減一
}

tasklet_disable和tasklet_enable支持嵌套,但是需要成對使用。

2、系統如何管理tasklet?

系統中的每個cpu都會維護一個tasklet的鏈表,定義如下:

static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);
static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);

linux kernel中,和tasklet相關的softirq有兩項,HI_SOFTIRQ用于高優先級的tasklet,TASKLET_SOFTIRQ用于普通的tasklet。對于softirq而言,優先級就是出現在softirq pending register(__softirq_pending)中的先后順序,位于bit 0擁有最高的優先級,也就是說,如果有多個不同類型的softirq同時觸發,那么執行的先后順序依賴在softirq pending register的位置,kernel總是從右向左依次判斷是否置位,如果置位則執行。HI_SOFTIRQ占據了bit 0,其優先級甚至高過timer,需要慎用(實際上,我grep了內核代碼,似乎沒有發現對HI_SOFTIRQ的使用)。當然HI_SOFTIRQ和TASKLET_SOFTIRQ的機理是一樣的,因此本文只討論TASKLET_SOFTIRQ,大家可以舉一反三。

3、如何定義一個tasklet?

你可以用下面的宏定義來靜態定義tasklet:

#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }

#define DECLARE_TASKLET_DISABLED(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }

這兩個宏都可以靜態定義一個struct tasklet_struct的變量,只不過初始化后的tasklet一個是處于eable狀態,一個處于disable狀態的。當然,也可以動態分配tasklet,然后調用tasklet_init來初始化該tasklet。

4、如何調度一個tasklet

為了調度一個tasklet執行,我們可以使用tasklet_schedule這個接口:

static inline void tasklet_schedule(struct tasklet_struct *t)
{
??? if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
??????? __tasklet_schedule(t);
}

程序在多個上下文中可以多次調度同一個tasklet執行(也可能來自多個cpu core),不過實際上該tasklet只會一次掛入首次調度到的那個cpu的tasklet鏈表,也就是說,即便是多次調用tasklet_schedule,實際上tasklet只會掛入一個指定CPU的tasklet隊列中(而且只會掛入一次),也就是說只會調度一次執行。這是通過TASKLET_STATE_SCHED這個flag來完成的,我們可以用下面的圖片來描述:

tasklet

我們假設HW block A的驅動使用的tasklet機制并且在中斷handler(top half)中將靜態定義的tasklet(這個tasklet是各個cpu共享的,不是per cpu的)調度執行(也就是調用tasklet_schedule函數)。當HW block A檢測到硬件的動作(例如接收FIFO中數據達到半滿)就會觸發IRQ line上的電平或者邊緣信號,GIC檢測到該信號會將該中斷分發給某個CPU執行其top half handler,我們假設這次是cpu0,因此該driver的tasklet被掛入CPU0對應的tasklet鏈表(tasklet_vec)并將state的狀態設定為TASKLET_STATE_SCHED。HW block A的驅動中的tasklet雖已調度,但是沒有執行,如果這時候,硬件又一次觸發中斷并在cpu1上執行,雖然tasklet_schedule函數被再次調用,但是由于TASKLET_STATE_SCHED已經設定,因此不會將HW block A的驅動中的這個tasklet掛入cpu1的tasklet鏈表中。

下面我們再仔細研究一下底層的__tasklet_schedule函數:

void __tasklet_schedule(struct tasklet_struct *t)
{
??? unsigned long flags;

??? local_irq_save(flags);-------------------(1)
??? t->next = NULL;---------------------(2)
??? *__this_cpu_read(tasklet_vec.tail) = t;
??? __this_cpu_write(tasklet_vec.tail, &(t->next));
??? raise_softirq_irqoff(TASKLET_SOFTIRQ);----------(3)
??? local_irq_restore(flags);
}

(1)下面的鏈表操作是per-cpu的,因此這里禁止本地中斷就可以攔截所有的并發。

(2)這里的三行代碼就是將一個tasklet掛入鏈表的尾部

(3)raise TASKLET_SOFTIRQ類型的softirq。

5、在什么時機會執行tasklet?

上面描述了tasklet的調度,當然調度tasklet不等于執行tasklet,系統會在適合的時間點執行tasklet callback function。由于tasklet是基于softirq的,因此,我們首先總結一下softirq的執行場景:

(1)在中斷返回用戶空間(進程上下文)的時候,如果有pending的softirq,那么將執行該softirq的處理函數。這里限定了中斷返回用戶空間也就是意味著限制了下面兩個場景的softirq被觸發執行:

??? (a)中斷返回hard interrupt context,也就是中斷嵌套的場景

??? (b)中斷返回software interrupt context,也就是中斷搶占軟中斷上下文的場景

(2)上面的描述缺少了一種場景:中斷返回內核態的進程上下文的場景,這里我們需要詳細說明。進程上下文中調用local_bh_enable的時候,如果有pending的softirq,那么將執行該softirq的處理函數。由于內核同步的要求,進程上下文中有可能會調用local_bh_enable/disable來保護臨界區。在臨界區代碼執行過程中,中斷隨時會到來,搶占該進程(內核態)的執行(注意:這里只是disable了bottom half,沒有禁止中斷)。在這種情況下,中斷返回的時候是否會執行softirq handler呢?當然不會,我們disable了bottom half的執行,也就是意味著不能執行softirq handler,但是本質上bottom half應該比進程上下文有更高的優先級,一旦條件允許,要立刻搶占進程上下文的執行,因此,當立刻離開臨界區,調用local_bh_enable的時候,會檢查softirq pending,如果bottom half處于enable的狀態,pending的softirq handler會被執行。

(3)系統太繁忙了,不過的產生中斷,raise softirq,由于bottom half的優先級高,從而導致進程無法調度執行。這種情況下,softirq會推遲到softirqd這個內核線程中去執行。

對于TASKLET_SOFTIRQ類型的softirq,其handler是tasklet_action,我們來看看各個tasklet是如何執行的:

static void tasklet_action(struct softirq_action *a)
{
??? struct tasklet_struct *list;

??? local_irq_disable();--------------------------(1)
??? list = __this_cpu_read(tasklet_vec.head);
??? __this_cpu_write(tasklet_vec.head, NULL);
??? __this_cpu_write(tasklet_vec.tail, this_cpu_ptr(&tasklet_vec.head));
??? local_irq_enable();

??? while (list) {---------遍歷tasklet鏈表
??????? struct tasklet_struct *t = list;

??????? list = list->next;

??????? if (tasklet_trylock(t)) {-----------------------(2)
??????????? if (!atomic_read(&t->count)) {------------------(3)
??????????????? if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))
??????????????????? BUG();
??????????????? t->func(t->data);
??????????????? tasklet_unlock(t);
??????????????? continue;-----處理下一個tasklet
??????????? }
??????????? tasklet_unlock(t);----清除TASKLET_STATE_RUN標記
??????? }

??????? local_irq_disable();-----------------------(4)
??????? t->next = NULL;
??????? *__this_cpu_read(tasklet_vec.tail) = t;
??????? __this_cpu_write(tasklet_vec.tail, &(t->next));
??????? __raise_softirq_irqoff(TASKLET_SOFTIRQ); ------再次觸發softirq,等待下一個執行時機
??????? local_irq_enable();
??? }
}

(1)從本cpu的tasklet鏈表中取出全部的tasklet,保存在list這個臨時變量中,同時重新初始化本cpu的tasklet鏈表,使該鏈表為空。由于bottom half是開中斷執行的,因此在操作tasklet鏈表的時候需要使用關中斷保護

(2)tasklet_trylock主要是用來設定該tasklet的state為TASKLET_STATE_RUN,同時判斷該tasklet是否已經處于執行狀態,這個狀態很重要,它決定了后續的代碼邏輯。

static inline int tasklet_trylock(struct tasklet_struct *t)
{
??? return !test_and_set_bit(TASKLET_STATE_RUN, &(t)->state);
}

你也許會奇怪:為何這里從tasklet的鏈表中摘下一個本cpu要處理的tasklet list,而這個list中的tasklet已經處于running狀態了,會有這種情況嗎?會的,我們再次回到上面的那個軟硬件結構圖。同樣的,HW block A的驅動使用的tasklet機制并且在中斷handler(top half)中將靜態定義的tasklet 調度執行。HW block A的硬件中斷首先送達cpu0處理,因此該driver的tasklet被掛入CPU0對應的tasklet鏈表并在適當的時間點上開始執行該tasklet。這時候,cpu0的硬件中斷又來了,該driver的tasklet callback function被搶占,雖然tasklet仍然處于running狀態。與此同時,HW block A硬件又一次觸發中斷并在cpu1上執行,這時候,該driver的tasklet處于running狀態,并且TASKLET_STATE_SCHED已經被清除,因此,調用tasklet_schedule函數將會使得該driver的tasklet掛入cpu1的tasklet鏈表中。由于cpu0在處理其他硬件中斷,因此,cpu1的tasklet后發先至,進入tasklet_action函數調用,這時候,當從cpu1的tasklet摘取所有需要處理的tasklet鏈表中,HW block A對應的tasklet實際上已經是在cpu0上處于執行狀態了。

我們在設計tasklet的時候就規定,同一種類型的tasklet只能在一個cpu上執行,因此tasklet_trylock就是起這個作用的。

(3)檢查該tasklet是否處于enable狀態,如果是,說明該tasklet可以真正進入執行狀態了。主要的動作就是清除TASKLET_STATE_SCHED狀態,執行tasklet callback function。

(4)如果該tasklet已經在別的cpu上執行了,那么我們將其掛入該cpu的tasklet鏈表的尾部,這樣,在下一個tasklet執行時機到來的時候,kernel會再次嘗試執行該tasklet,在這個時間點,也許其他cpu上的該tasklet已經執行完畢了。通過這樣代碼邏輯,保證了特定的tasklet只會在一個cpu上執行,不會在多個cpu上并發。

轉載于:https://www.cnblogs.com/alantu2018/p/8447483.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/452641.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/452641.shtml
英文地址,請注明出處:http://en.pswp.cn/news/452641.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

數字電視制播設備間的文件交換格式

在現今的數字電視演播室中,設備之間基本上采用信號流連接方式,如SDI、STDI、模擬YUV、VBS等信號流。在非線性編輯系統和播出系統與服務器之間的連接,還有基于MPEG-2傳輸流等的信號連接方式。基于信號流連接方式的主要特點是,傳送時…

oracle 位移運算符,Oracle“(+)”運算符

在Oracle中,()表示JOIN中的“可選”表。 所以在你的查詢中,select a.id, b.id, a.col_2, b.col_2, ... from a,b where a.idb.id()這是一個左外加B表與一個表。 就像現代的左連接查詢一樣。 (它將返回a表的所有數據,而不會丟失在另一邊的數據…

JAVA-數據類型-復習

JAVA-數據類型-復習 Java中,一共有8種數據類型,4種整型,2種浮點型,1種用于表示Unicode編碼的字符單元的字符類型char,1種布爾類型。 整型 類型存儲需求(字節)一個字節包含8個位取值范圍byte1-12…

什么是實體-聯系圖(ER圖)

實體-聯系圖(ER圖)數據模型中包含3種相互關聯的信息:數據對象、數據對象的屬性及數據對象彼此間相互連接的關系。 1.數據對象 數據對象是對軟件必須理解的復合信息的抽象。所謂符合信息是指具有一系列不同性質或屬性的事物,僅有單…

記錄的習慣

記錄的習慣 書籍是人類進步的階梯,承載了人類文明進步的歷程。大多數人都寫過日記,但不知道有多少人重視過日記。常常我們會用相機記錄一些生活中的場景,然后收藏起來,等到若干年后再拿出來看,總能感覺到很溫馨很美好。…

php 去掉實體,用PHP刪除除5個預定義HTML實體之外的所有實體的最佳方法-用于XHTML5輸出...

我目前正在嘗試提供XHTML5.目前,我在正在處理的頁面上提供XHTML 1.1 Strict.那就是我為有能力的瀏覽器所做的.對于那些不接受XML編碼數據的人,我會嚴格遵循HTML4.1.在嘗試使用HTML5進行試驗時,以HTML5格式交付時,所有功能或多或少都可以按預期工作.但是,作為XHTML5交付時,我遇到…

Flask愛家租房--發布新房源(保存房屋基本信息)

0.頁面展示效果 1.后端代碼 api.route("/houses/info", methods["POST"]) login_required def save_house_info():"""保存房屋的基本信息前端發送過來的json數據{"title":"","price":"","ar…

今后最有前途的媒體格式 MXF

MXF格式已經被推出幾年了,從當初一個陌生的不為人們重視的格式逐漸獲得了業內人士的認知和認可,現如今正被廣泛應用于廣播電視與后期制作領域,且有不斷擴大之勢,松下公司推出的基于PII卡的無磁帶式標清攝像機,它所采用…

【c#】RabbitMQ學習文檔(一)Hello World

一、簡介 RabbitMQ是一個消息的代理器,用于接收和發送消息,你可以這樣想,他就是一個郵局,當您把需要寄送的郵件投遞到郵筒之時,你可以確定的是郵遞員先生肯定會把郵件發送到需要接收郵件的人的手里,不…

什么是狀態轉換圖

通過描繪系統的狀態及引起系統狀態轉換的事件,來表示系統的行為。此外狀態轉換圖還指明了作為特定事件的結果系統將做哪些動作(例如,處理數據)。因此狀態轉換圖提供了行為建模機制。

Python學習筆記三

參考教程:廖雪峰官網https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000 一、函數的定義 Python中定義一個函數需要使用def語句,依次確定函數名、參數及函數體內容: #一個求絕對值的函數 def my_abs(x):i…

oracle中如何分頁,Oracle中操作分頁

mysql中分頁的寫法:select t.* from tbl_user t order by t.id limit $offset , $perpage$currentPage 1;//當前頁碼其中后面$sql:with partdata as (select rownum rowno,t.* from tablename t where column1090order by column) select * from partda…

Flask愛家租房--發布新房源(保存房屋圖片)

0.頁面展示效果 1)首先房東填寫房屋信息; 2)當房東填寫發布的房源信息之后,隱藏(hide)剛才填寫信息的界面,同時顯示(show)上傳房屋圖片的界面。 1.后端代碼 api.route("/houses/image&q…

數字的處理 :小數點四舍五入

js取float型小數點后兩位數的方法 轉載 發布時間:2014年01月18日 17:03:32 投稿:shangke 我要評論 js中取小數點后兩位方法最常用的就是四舍五入函數了,前面我介紹過js中四舍五入一此常用函數,這里正好用上,下面…

如何成為一名優秀的C程序員

問題的提出 每過一段時間我總會收到一些程序員發來的電子郵件,他們會問我是用什么編程語言來編寫自己的游戲的,以及我是如何學習這種編程語言的。因此,我認為在這篇博文里列出一些有關C語言的最佳讀物應該能幫到不少人。如果你知道其它的優秀…

CFS調度器

一、前言 隨著內核版本的演進,其源代碼的膨脹速度也在遞增,這讓Linux的學習曲線變得越來越陡峭了。這對初識內核的同學而言當然不是什么好事情,滿腔熱情很容易被當頭澆滅。我有一個循序漸進的方法,那就是先不要看最新的內核&#…

oracle索引分類與區分,深入理解Oracle表(6):堆組織表(HOT)和索引組織表(IOT)的區別...

摘要:堆表:又稱堆組織表,常用的表類型,以堆的方式管理,當增加數據時,將使用段中第一個適合數據大小的空閑空間。當刪除數據時,留下的空間允許以后的DML操作重用。 堆組織表(heap table)應用中99…

Flask愛家租房--發布新房源(總結)

重點總結 學習過程中,發現house_id貫穿兩個接口內容,現對后端邏輯部分做以下總結: 1)房東首先在前端填寫房屋的基本信息,此時通過newhouse.js文件$("#form-house-info").submit(function (e) {…}進行處理&…

關系的三類完整性約束

規則2.1實體完整性規則(Entity Integrity)若屬性A是基本關系R的主屬性,則屬性A不能取空值空值就是“不知道”或“不存在”或“無意義”的值例: 選修(學號,課程號,成績) “學號、課…