讀薄《Linux 內核設計與實現》(4) - 中斷與同步
這篇文章是《讀薄「linux 內核設計與實現」》系列文章的第 IV 篇,本文主要講了以下問題:中斷和中斷處理程序的概念與實現原理、linux 中的下半部以及內核同步方法。

0x00 中斷和中斷處理程序
I 中斷
中斷是一種特殊的電信號,由硬件發向處理器,處理器接收到中斷時,會馬上箱操作系統反映,由操作系統進行處理。中斷隨時可以產生,因此,內核隨時可能因為新到來的中斷而被打斷。
不同的設備對應的中斷不同,每個中斷通過一個唯一的數字標識,這些中斷值通常被稱為中斷請求(IRQ)線。
II 中斷處理程序
中斷處理程序又成為中斷處理例程(ISR),是內核在響應中斷時執行的一個函數
一個中斷處理程序對應一個中斷,一個設備可能發出多種中斷
對于外部設備,中斷處理程序是設備驅動程序的一部分
在 Linux 中,中斷處理程序和 C 函數區別不大,但有自己的規范,主要是運行時需要在中斷上下文中
0x01 中斷處理機制
I 注冊中斷處理程序
驅動程序可以通過request_irq()函數注冊一個中斷處理程序(linux/interrupt.h)
int request_irq(unsigned int irq, irqhandler_t handler, unsigined long falgs, const char *name, void *dev)
1
2
3
4
5
irq:表示要分配的中斷號
handler:一個指針,指向處理這個中斷的實際中斷處理函數
typedef irqhandler_t(*irq_handler_t)(int, void*);
1
II 釋放中斷處理程序
卸載驅動程序時,需要注銷響應中斷處理程序,并釋放中斷線。
void free_irq(unsigned int irq, void *dev);
1
如果指定的中斷線不是共享的,那么該函數刪除處理程序的同時將禁用這條中斷線;中斷線是共享的,則僅僅刪除 dev 對應的處理程序,而這條中斷線本身只有在刪除了最后一個處理程序時才會被禁用
III 中斷的禁止與激活
local_irq_disable(); local_irq_enable();
1
2
IV 上半部與下半部
又想中斷處理程序運行的快,又想中斷處理程序完成的工作多,這兩個目的顯然有所抵觸,所以把中斷處理分為兩個部分:
中斷處理程序是上半部,接收到一個中斷,它就立刻開始執行,但只做有嚴格時限的工作,例如一些只有在中斷被禁止的狀態下才能完成的工作
能夠被允許稍后完成的工作會推遲2到下半部去,此后,在合適的時機,下半部會被開中斷執行
Q1:為什么要分上半部和下半部? {% endcq %}
中斷程序以異步方式執行,可能打斷重要操作的執行,越快越好
中斷處理程序會屏蔽其他同級中斷,所以執行越快越好
中斷處理程序往往需要對硬件操作,通常有很高的時限要求
中斷處理程序不在進程的上下文中運行,所以不能阻塞
Q2:上半部和下半部如何分開? {% endcq %}
如果一個任務對時間非常敏感,將其放到上半部;
如果一個任務和硬件相關,將其放到上半部;
如果一個任務要保證不被其它中斷打斷,將其放到上半部;
其他的所有任務考慮放到下半部
0x02 下半部
下半部的任務就是執行與終端處理密切相關但中斷處理程序本身不執行的工作。我們期望中斷處理程序將盡量多的工作放到下半部執行,以快速從中斷返回。
I 下半部實現機制
a.軟中斷
此處的軟中斷和系統調用使用的 int 80H 不同,是操作系統支持的一種,在編譯期間靜態分配
定義于 linux/interrupt.h 中:
struct softirq_action{ void (*action)(struct sfotirq_action*); //待執行的函數 void *data; //傳遞的參數 }
1
2
3
4
最多可能32個軟中斷,定義于 kernel/softirq.c
static struct softirq_action softirq_vec[NR_SOFTIRQS];
1
void softirq_handler(struct softirq_action*); //傳遞整個結構體
1
一個注冊的軟中斷必須在被標記后才會執行下列地方,待處理的軟中斷會被檢查和執行:
在 ksoftirqd 內核線程中
在那些顯式檢查和和執行待處理的軟中斷的代碼中(如網絡子系統)
不管執行的時機,軟中斷都要在do_softirq 中執行
分配索引: 通過 linux/interrupt.h 中的一個枚舉類型中聲明一個新的軟中斷
注冊處理程序:在運行時通過調用open_softirq()注冊軟中斷處理程序,有兩個參數,軟中斷和處理函數
觸發軟中斷:raise_softirq()函數可以將一個軟中斷設置為掛起狀態,讓它在下次調用do_softirq()函數時投入運行
b.tasklet
基于軟中斷的實現,但它的接口更簡單,鎖保護要求更低
tasklet 結構體(linux/interrupt.h)
struct tasklet_struct{ struct tasklet_struct *next; //鏈表 unsigned long state; //tasklet 狀態 atomic_t count; //引用計數器 void (*funx)(unsigned long); //taklet 處理函數 unsigned long data; //給處理器函數傳遞的參數 }
1
2
3
4
5
6
7
被觸發的軟中斷存放在2個數據結構:tasklet_vec,task_hi_vec,這兩個數據結構都是由task_struct構成的鏈表,由tasklet_schedule()和task_hi_schedule()進行調度,調度步驟如下:
(1)檢查 tasklet 狀態,如果為TASK_STATE_SCHED則返回
(2)調用_tasklet_schedule()
(3)保存中斷狀態,禁止本地中斷
(4)把需要調度的 tasklet 加到 tasklet_vec或tasklet_hi_vec鏈表頭
(5)喚起TASKLET_SOFTIRQ或HI_SOFTIRQ軟中斷,下一次調用do_softirq()時會執行該 tasklet
(6)恢復中斷
軟中斷處理程序:tasklet_action()或task_hi_action()[tasklet 處理的核心]:
(1)禁止中斷
(2)將當前處理器上的該鏈表頭設為 NULL
(3)允許中斷
(4)循環遍歷鏈表上所有待處理的 tasklet
(5)如果是多個處理器,檢查TASKLET_STATE_RUN判斷這個 tasklet 是否在其他處理器上運行,如果是,跳到笑一個 tasklet
(6)如果否,設置TASKLET_STATE_RUN
(7)檢查 count 是否為0,確保 tasklet 沒有被禁止;如果被禁止,跳到下一個 tasklet
(8)執行 tasklet 處理程序
(9)執行完畢,清除TASKLET_STATE_RUN
(10)重復執行下一個 tasklet
靜態:linux/interrupt.h 中的2個宏:
DECLARE_TASKLET(name,func,data); DECLARE_TASKLET_DIASBLED(name,func,data);
1
2
動態:通過一個指針賦給一個動態創建的 tasklet_struct:
tasklet_init(t, takslet_handler, dev);
1
void tasklet_handler(unsigned long data)
1
注意:不能再 tasklet 中使用信號量或者其他阻塞式函數
tasklet_schedule(&my_tasklet); tasklet_enable(&my_tasklet); tasklet_disable(&my_tasklet);
1
2
3
ksoftirqd 是內核線程,每個處理器都有一個,用于在空閑處理器處理軟中斷
for(;;){ if(!softirq_pending(cpu)) schedule(); set_current_state(TASK_RUNNING); while(softirq_pending(cpu)){ do_softirq(); if(need_resched()) schedule(); } set_current_sdtate(TASK_INTERRUPTIBLE); }
1
2
3
4
5
6
7
8
9
10
11
只要有待處理的軟中斷,該線程就會處理
c.工作隊列
工作隊列機制將下半部功能交給內核縣城去執行,有線程上下文,可以睡眠
提供吧需要推后執行的任務排到隊列里的接口
線程將自己休眠,并加到等待隊列(TASK_INTERRUPTIBLE)
如果工作鏈表為空,線程調用schedule(),休眠
如果不為空,將自己設為TASK_RUNNING
調用run_workqueue()執行被推后的工作
該函數循環遍歷鏈表上每個待處理的工作:
當鏈表非空,選取下一個節點對象
獲取要執行的函數和參數
從鏈表上解下該節點,將 pending 位清零
調用函數
重復執行
創建推后的工作:
靜態:
DECLARE_WORK(name, void(*func)(void*), void *data);
1
動態:
INIT_WORK(struct work_struct *work, void(*func)(void*), void *data);
1
工作隊列處理函數
void work_handler(void *data)
1
對工作的調度
schedule_work(&work); schedule_delayed_work(&work, delay);
1
2
刷新操作
void flush_scheduled_work(void);
1
3種下半部機制的比較
Q:我們要選擇哪種機制? {% endcq %}
如果有休眠的要求,選擇工作隊列;否則,最好使用 tasklet;要是必須專注性能的提高,選擇軟中斷
II 在下半部之間加鎖
如果進程上下文和一個下半部共享數據,在訪問這些數據之前,你需要禁止下半部的處理并得到鎖的使用權
如果中斷上下文和一個下半部共享數據,在訪問數據之前,需要禁止中斷并得到鎖的使用權
0x03 內核同步
I 臨界區
臨界區就是訪問和操作共享資源的代碼段,必須保證原子地執行才能保證安全
II 加鎖
保證在臨界區執行的縣城只有一個
III 造成并發的原因
中斷
軟中斷和 tasklet
內核搶占
睡眠及用戶空間的同步
對稱多處理
IV 死鎖產生條件
要有一個或多個執行線程和一個或多個資源
每一個線程都在等待其中一個資源
所有的資源都被占用
所有縣城都在互相等待,但他們永遠不會釋放已經占有的資源
V 內核同步方法
原子操作
原子整數操作(asm/atomic.h)
atomic_dec_and_test(atmoic_t, *v)
1
原子位操作(asm/bitops.h)
set_bit(0, &word)
1
自旋鎖
自旋鎖只能被一個可執行進程持有
若爭用一個被占用的鎖則進程忙等(旋轉)
自旋鎖不能長期被占用,否則效率低
Linux 任務調度
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。