今天無意中看了眼 2.6 內核的軟中斷實現,發現和以前我看到的大不相同(以前也是走馬觀花,不大仔細),能說改動非常大。連 softirq 的調用點都不相同了,以前是三個調用點,今天搜索了一下原始碼,發目前多出了ksoftirqd 這個東西后,softirq 在系統中的調用點僅是在 ISR 返回時和使用了 local_bh_enable() 函數後被調用了。網卡部分的顯示調用,我覺得應該不算是系統中的調用點。ksoftirqd 返回去調用 do_softirq() 函數應該也只能算是其中的一個分支,因爲其本身從源頭上來講也還是在 ISR 返回時 irq_exit() 調用的。這樣一來就和前些日子寫的那份筆記(視窗系統/Linux/Solaris 軟中斷機制)裏介紹的 Linux 內核部分的軟中斷有出處了,看來以後討論 Linux kernel 代碼一定要以內核版本爲前題來說,要不非亂了不可。看來得買本 Linux 方面的書了,每次上來直接看相關代碼也不是回事,時間也不允許。
linux kernel source 2.6.19.1
/kernel/softirq.c
1. //
2. // do_IRQ 函數執行完硬件 ISR 後退出時調用此函數。
3. //
4. void irq_exit(void)
5. {
6. account_system_vtime(current);
7. trace_hardirq_exit();
8. sub_preempt_count(IRQ_EXIT_OFFSET);
9. //
10. // 判斷當前是否有硬件中斷嵌套,並且是否有軟中斷在
11. // pending 狀態,注意:這裏只有兩個條件同時滿足
12. // 時,纔有可能調用 do_softirq() 進入軟中斷。也就是
13. // 說確認當前所有硬件中斷處理完成,且有硬件中斷安裝了
14. // 軟中斷處理時理時纔會進入。
15. //
16. if (!in_interrupt() && local_softirq_pending())
17. //
18. // 其實這裏就是調用 do_softirq() 執行
19. //
20. invoke_softirq();
21. preempt_enable_no_resched();
22. }
23. #ifndef __ARCH_HAS_DO_SOFTIRQ
24. asmlinkage void do_softirq(void)
25. {
26. __u32 pending;
27. unsigned long flags;
28. //
29. // 這個函數判斷,如果當前有硬件中斷嵌套,或
30. // 有軟中斷正在執行時候,則馬上返回。在這個
31. // 入口判斷主要是爲了和 ksoftirqd 互斥。
32. //
33. if (in_interrupt())
34. return;
35. //
36. // 關中斷執行以下代碼
37. //
38. local_irq_save(flags);
39. //
40. // 判斷是否有 pending 的軟中斷需要處理。
41. //
42. pending = local_softirq_pending();
43. //
44. // 如果有則調用 __do_softirq() 進行實際處理
45. //
46. if (pending)
47. __do_softirq();
48. //
49. // 開中斷繼續執行
50. //
51. local_irq_restore(flags);
52. }
53. //
54. // 最大軟中斷調用次數爲 10 次。
55. //
56. #define MAX_SOFTIRQ_RESTART 10
57. asmlinkage void __do_softirq(void)
58. {
59. //
60. // 軟件中斷處理結構,此結構中包括了 ISR 中
61. // 註冊的回調函數。
62. //
63. struct softirq_action *h;
64. __u32 pending;
65. int max_restart = MAX_SOFTIRQ_RESTART;
66. int cpu;
67. //
68. // 得到當前所有 pending 的軟中斷。
69. //
70. pending = local_softirq_pending();
71. account_system_vtime(current);
72. //
73. // 執行到這裏要屏蔽其他軟中斷,這裏也就證實了
74. // 每個 CPU 上同時運行的軟中斷只能有一個。
75. //
76. __local_bh_disable((unsigned long)__builtin_return_address(0));
77. trace_softirq_enter();
78. //
79. // 針對 SMP 得到當前正在處理的 CPU
80. //
81. cpu = smp_processor_id();
82. //
83. // 循環標誌
84. //
85. restart:
86. //
87. // 每次循環在允許硬件 ISR 強佔前,首先重置軟中斷
88. // 的標誌位。
89. //
90. /* Reset the pending bitmask before enabling irqs */
91. set_softirq_pending(0);
92. //
93. // 到這裏纔開中斷運行,注意:以前運行狀態一直是關中斷
94. // 運行,這時當前處理軟中斷纔可能被硬件中斷搶佔。也就
95. // 是說在進入軟中斷時不是一開始就會被硬件中斷搶佔。只有
96. // 在這裏以後的代碼纔可能被硬件中斷搶佔。
97. //
98. local_irq_enable();
99. //
100. // 這裏要注意,以下代碼運行時能被硬件中斷搶佔,但
101. // 這個硬件 ISR 執行完成後,他的所註冊的軟中斷無法馬上運行,
102. // 別忘了,目前雖是開硬件中斷執行,但前面的 __local_bh_disable()
103. // 函數屏蔽了軟中斷。所以這種環境下只能被硬件中斷搶佔,但這
104. // 個硬中斷註冊的軟中斷回調函數無法運行。要問爲什麼,那是因爲
105. // __local_bh_disable() 函數設置了一個標誌當作互斥量,而這個
106. // 標誌正是上面的 irq_exit() 和 do_softirq() 函數中的
107. // in_interrupt() 函數判斷的條件之一,也就是說 in_interrupt()
108. // 函數不僅檢測硬中斷而且還判斷了軟中斷。所以在這個環境下觸發
109. // 硬中斷時註冊的軟中斷,根本無法重新進入到這個函數中來,只能
110. // 是做一個標誌,等待下面的重複循環(最大 MAX_SOFTIRQ_RESTART)
111. // 纔可能處理到這個時候觸發的硬件中斷所註冊的軟中斷。
112. //
113. //
114. // 得到軟中斷向量表。
115. //
116. h = softirq_vec;
117. //
118. // 循環處理所有 softirq 軟中斷註冊函數。
119. //
120. do {
121. //
122. // 如果對應的軟中斷設置 pending 標誌則表明
123. // 需要進一步處理他所註冊的函數。
124. //
125. if (pending & 1) {
126. //
127. // 在這裏執行了這個軟中斷所註冊的回調函數。
128. //
129. h->action(h);
130. rcu_bh_qsctr_inc(cpu);
131. }
132. //
133. // 繼續找,直到把軟中斷向量表中所有 pending 的軟
134. // 中斷處理完成。
135. //
136. h++;
137. //
138. // 從代碼裏能看出按位操作,表明一次循環只
139. // 處理 32 個軟中斷的回調函數。
140. //
141. pending >>= 1;
142. } while (pending);
143. //
144. // 關中斷執行以下代碼。注意:這裏又關中斷了,下面的
145. // 代碼執行過程中硬件中斷無法搶佔。
146. //
147. local_irq_disable();
148. //
149. // 前面提到過,在剛纔開硬件中斷執行環境時只能被硬件中斷
150. // 搶佔,在這個時候是無法處理軟中斷的,因爲剛纔開中
151. // 斷執行過程中可能多次被硬件中斷搶佔,每搶佔一次就有可
152. // 能註冊一個軟中斷,所以要再重新取一次所有的軟中斷。
153. // 以便下面的代碼進行處理後跳回到 restart 處重複執行。
154. //
155. pending = local_softirq_pending();
156. //
157. // 如果在上面的開中斷執行環境中觸發了硬件中斷,且每個都
158. // 註冊了一個軟中斷的話,這個軟中斷會設置 pending 位,
159. // 但在當前一直屏蔽軟中斷的環境下無法得到執行,前面提
160. // 到過,因爲 irq_exit() 和 do_softirq() 根本無法進入到
161. // 這個處理過程中來。這個在上面周詳的記錄過了。那麼在
162. // 這裏又有了一個執行的機會。注意:雖然當前環境一直是
163. // 處於屏蔽軟中斷執行的環境中,但在這裏又給出了一個執行
164. // 剛纔在開中斷環境過程中觸發硬件中斷時所註冊的軟中斷的
165. // 機會,其實只要理解了軟中斷機制就會知道,無非是在一些特
166. // 定環境下調用 ISR 註冊到軟中斷向量表裏的函數而已。
167. //
168. //
169. // 如果剛纔觸發的硬件中斷註冊了軟中斷,並且重複執行次數
170. // 沒有到 10 次的話,那麼則跳轉到 restart 標誌處重複以上
171. // 所介紹的所有步驟:設置軟中斷標誌位,重新開中斷執行...
172. // 注意:這裏是要兩個條件都滿足的情況下才可能重複以上步驟。
173. //
174. if (pending && --max_restart)
175. goto restart;
176. //
177. // 如果以上步驟重複了 10 次後更有 pending 的軟中斷的話,
178. // 那麼系統在一定時間內可能達到了一個峯值,爲了平衡這點。
179. // 系統專門建立了一個 ksoftirqd 線程來處理,這樣避免在一
180. // 定時間內負荷太大。這個 ksoftirqd 線程本身是個大循環,
181. // 在某些條件下爲了不負載過重,他是能被其他進程搶佔的,
182. // 但注意,他是顯示的調用了 preempt_xxx() 和 schedule()
183. // 纔會被搶佔和轉換的。這麼做的原因是因爲在他一旦調用
184. // local_softirq_pending() 函數檢測到有 pending 的軟中斷
185. // 需要處理的時候,則會顯示的調用 do_softirq() 來處理軟中
186. // 斷。也就是說,下面代碼喚醒的 ksoftirqd 線程有可能會回
187. // 到這個函數當中來,尤其是在系統需要響應非常多軟中斷的情況
188. // 下,他的調用入口是 do_softirq(),這也就是爲什麼在 do_softirq()
189. // 的入口處也會用 in_interrupt() 函數來判斷是否有軟中斷
190. // 正在處理的原因了,目的還是爲了防止重入。ksoftirqd 實現
191. // 看下面對 ksoftirqd() 函數的分析。
192. //
193. if (pending)
194. //
195. // 此函數實際是調用 wake_up_process() 來喚醒 ksoftirqd
196. //
197. wakeup_softirqd();
198. trace_softirq_exit();
199. account_system_vtime(current);
200. //
201. // 到最後纔開軟中斷執行環境,允許軟中斷執行。注意:這裏
202. // 使用的不是 local_bh_enable(),不會再次觸發 do_softirq()
203. // 的調用。
204. //
205. _local_bh_enable();
206. }
207. static int ksoftirqd(void * __bind_cpu)
208. {
209. //
210. // 顯示調用此函數設置當前進程的靜態優先級。當然,
211. // 這個優先級會隨調度器策略而變化。
212. //
213. set_user_nice(current, 19);
214. //
215. // 設置當前進程不允許被掛啓
216. //
217. current->flags |= PF_NOFREEZE;
218. //
219. // 設置當前進程狀態爲可中斷的狀態,這種睡眠狀
220. // 態可響應信號處理等。
221. //
222. set_current_state(TASK_INTERRUPTIBLE);
223. //
224. // 下面是個大循環,循環判斷當前進程是否會停止,
225. // 不會則繼續判斷當前是否有 pending 的軟中斷需
226. // 要處理。
227. //
228. while (!kthread_should_stop()) {
229. //
230. // 如果能進行處理,那麼在此處理期間內禁止
231. // 當前進程被搶佔。
232. //
233. preempt_disable();
234. //
235. // 首先判斷系統當前沒有需要處理的 pending 狀態的
236. // 軟中斷
237. //
238. if (!local_softirq_pending()) {
239. //
240. // 沒有的話在主動放棄 CPU 前先要允許搶佔,因爲
241. // 一直是在不允許搶佔狀態下執行的代碼。
242. //
243. preempt_enable_no_resched();
244. //
245. // 顯示調用此函數主動放棄 CPU 將當前進程放入睡眠隊列,
246. // 並轉換新的進程執行(調度器相關不記錄在此)
247. //
248. schedule();
249. //
250. // 注意:如果當前顯示調用 schedule() 函數主動轉換的進
251. // 程再次被調度執行的話,那麼將從調用這個函數的下一條
252. // 語句開始執行。也就是說,在這裏當前進程再次被執行的
253. // 話,將會執行下面的 preempt_disable() 函數。
254. //
255. //
256. // 當進程再度被調度時,在以下處理期間內禁止當前進程
257. // 被搶佔。
258. //
259. preempt_disable();
260. }
261. //
262. // 設置當前進程爲運行狀態。注意:已設置了當前進程不可搶佔
263. // 在進入循環後,以上兩個分支不論走哪個都會執行到這裏。一是
264. // 進入循環時就有 pending 的軟中斷需要執行時。二是進入循環時
265. // 沒有 pending 的軟中斷,當前進程再次被調度獲得 CPU 時繼續
266. // 執行時。
267. //
268. __set_current_state(TASK_RUNNING);
269. //
270. // 循環判斷是否有 pending 的軟中斷,如果有則調用 do_softirq()
271. // 來做具體處理。注意:這裏又是個 do_softirq() 的入口點,
272. // 那麼在 __do_softirq() 當中循環處理 10 次軟中斷的回調函數
273. // 後,如果更有 pending 的話,會又調用到這裏。那麼在這裏則
274. // 又會有可能去調用 __do_softirq() 來處理軟中斷回調函數。在前
275. // 面介紹 __do_softirq() 時已提到過,處理 10 次還處理不完的
276. // 話說明系統正處於繁忙狀態。根據以上分析,我們能試想如果在
277. // 系統非常繁忙時,這個進程將會和 do_softirq() 相互交替執行,
278. // 這時此進程佔用 CPU 應該會非常高,雖然下面的 cond_resched()
279. // 函數做了一些處理,他在處理完一輪軟中斷後當前處理進程可能會
280. // 因被調度而減少 CPU 負荷,不過在非常繁忙時這個進程仍然有可
281. // 能大量佔用 CPU。
282. //
283. while (local_softirq_pending()) {
284. /* Preempt disable stops cpu going offline.
285. If already offline, we’ll be on wrong CPU:
286. don’t process */
287. if (cpu_is_offline((long)__bind_cpu))
288. //
289. // 如果當前被關聯的 CPU 無法繼續處理則跳轉
290. // 到 wait_to_die 標記出,等待結束並退出。
291. //
292. goto wait_to_die;
293. //
294. // 執行 do_softirq() 來處理具體的軟中斷回調函數。注
295. // 意:如果此時有一個正在處理的軟中斷的話,則會馬上
296. // 返回,還記得前面介紹的 in_interrupt() 函數麼。
297. //
298. do_softirq();
299. //
300. // 允許當前進程被搶佔。
301. //
302. preempt_enable_no_resched();
303.
304. //
305. // 這個函數有可能間接的調用 schedule() 來轉換當前
306. // 進程,而且上面已允許當前進程可被搶佔。也就是
307. // 說在處理完一輪軟中斷回調函數時,有可能會轉換到
308. // 其他進程。我認爲這樣做的目的一是爲了在某些負載
309. // 超標的情況下不至於讓這個進程長時間大量的佔用 CPU,
310. // 二是讓在有非常多軟中斷需要處理時不至於讓其他進程
311. // 得不到響應。
312. //
313. cond_resched();
314. //
315. // 禁止當前進程被搶佔。
316. //
317. preempt_disable();
318. //
319. // 處理完所有軟中斷了嗎?沒有的話繼續循環以上步驟
320. //
321. }
322. //
323. // 待一切都處理完成後,允許當前進程被搶佔,並設置
324. // 當前進程狀態爲可中斷狀態,繼續循環以上所有過程。
325. //
326. preempt_enable();
327. set_current_state(TASK_INTERRUPTIBLE);
328. }
329.
330. //
331. // 如果將會停止則設置當前進程爲運行狀態後直接返回。
332. // 調度器會根據優先級來使當前進程運行。
333. //
334. __set_current_state(TASK_RUNNING);
335. return 0;
336. //
337. // 一直等待到當前進程被停止
338. //
339. wait_to_die:
340. //
341. // 允許當前進程被搶佔。
342. //
343. preempt_enable();
344. /* Wait for kthread_stop */
345. //
346. // 設置當前進程狀態爲可中斷的狀態,這種睡眠狀
347. // 態可響應信號處理等。
348. //
349. set_current_state(TASK_INTERRUPTIBLE);
350. //
351. // 判斷當前進程是否會被停止,如果不是的話
352. // 則設置進程狀態爲可中斷狀態並放棄當前 CPU
353. // 主動轉換。也就是說這裏將一直等待當前進程
354. // 將被停止時候才結束。
355. //
356. while (!kthread_should_stop()) {
357. schedule();
358. set_current_state(TASK_INTERRUPTIBLE);
359. }
360. //
361. // 如果將會停止則設置當前進程爲運行狀態後直接返回。
362. // 調度器會根據優先級來使當前進程運行。
363. //
364. __set_current_state(TASK_RUNNING);
365. return 0;
366. }