系統現象
我們執行ps會發現一個叫ksoftirqd的線程
ps -ef | grep ksoftirqd
root 6 2 0 8月19 ? 00:01:15 [ksoftirqd/0]
root 14 2 0 8月19 ? 00:01:13 [ksoftirqd/1]
root 20 2 0 8月19 ? 00:00:51 [ksoftirqd/2]
root 26 2 0 8月19 ? 00:00:56 [ksoftirqd/3]
root 31 2 0 8月19 ? 00:00:58 [ksoftirqd/4]
root 36 2 0 8月19 ? 00:00:56 [ksoftirqd/5]
root 41 2 0 8月19 ? 00:01:01 [ksoftirqd/6]
root 46 2 0 8月19 ? 00:01:00 [ksoftirqd/7]
root 51 2 0 8月19 ? 00:00:46 [ksoftirqd/8]
root 56 2 0 8月19 ? 00:00:31 [ksoftirqd/9]
root 61 2 0 8月19 ? 00:00:57 [ksoftirqd/10]
root 66 2 0 8月19 ? 00:01:20 [ksoftirqd/11]
root 71 2 0 8月19 ? 00:01:29 [ksoftirqd/12]
root 76 2 0 8月19 ? 00:00:58 [ksoftirqd/13]
root 81 2 0 8月19 ? 00:00:49 [ksoftirqd/14]
root 86 2 0 8月19 ? 00:00:48 [ksoftirqd/15]
軟中斷:
從上面的結果可以看出,每個處理器都有一個這樣的線程。所有的線程的名字都叫做ksoftirq/n,區別在於n,他對應的是處理器的編號。
這個線程就是專門用於處理軟中斷的程序。
軟中斷:
UN1X系統提供軟中斷機制作爲進程通信的一種手段。軟中斷是通過發送規定的信號到指定進程,對方進程定時地查詢有無外來信號,若有則按約定進行處理,處理完畢,返回斷點繼續執行原來的指令。可見,軟中斷是對硬中斷的一種模擬。軟中斷存在較大的時延,不象硬中斷能獲得及時響應。例如,對方進程若處在阻塞隊列,那麼只有等到該進程執行時才能查詢軟中斷信號。顯然,從軟中斷信號發出到對方響應,時間可能拖得很長。此外,軟中斷處理程序運行在用戶態,硬中斷處理程序則運行在覈心態。
優先級:
中斷>軟中斷>用戶進行
對於軟中斷,內核會在幾個特殊的時機執行(注意執行和調度的區別,調度軟中斷只是對軟中斷打上待執行的標記,並沒有真正執行),而在中斷處理程序返回時處理是最常見的。
代碼分析
在<Softirq.c(kernel)>中
static int ksoftirqd(void * __bind_cpu)
{
set_user_nice(current, 19);
current->flags |= PF_NOFREEZE;
set_current_state(TASK_INTERRUPTIBLE); //處於等待隊伍中,等待資源有效時喚醒(比方等待鍵盤輸入、socket連接、信號等等)
while (!kthread_should_stop()) {
preempt_disable();
if (!local_softirq_pending()) {
preempt_enable_no_resched();
schedule();
preempt_disable();
}
set_current_state(TASK_RUNNING); //正在運行或處於就緒狀態
while (local_softirq_pending()) { //當有軟中斷未解決
// cpu正常可用
if (cpu_is_offline((long)__bind_cpu))
goto wait_to_die;
do_softirq(); //執行軟中斷 這裏一般執行的就是 註冊的回調函數,比如epoll
preempt_enable_no_resched(); //搶佔cpu計數-1 但不立即搶佔式調度
cond_resched(); //在內核態主動讓出cpu可以調用cond_resched,讓出cpu,此時就是等待調度
preempt_disable(); //cpu搶佔計數+1 就是當cpu重新調度到此處時,提高計數
/**
當從內核態返回到用戶態的時候,要檢查是否進行調度,而調度要看兩個條件:
1. preempt_count是否爲0
2. rescheduled是否置位
因此上方讓出cpu之前先將preempt_count-1(否則cond_resched可能失敗),調度到ksoftirq又加回來
*/
}
preempt_enable();
set_current_state(TASK_INTERRUPTIBLE); //執行完一次 標記線程進入等待隊列
}
set_current_state(TASK_RUNNING); //這裏是爲了執行之後的處理 保證本線程正常終止
return 0;
wait_to_die:
preempt_enable();
/* Wait for kthread_stop */
set_current_state(TASK_INTERRUPTIBLE);
//kthread_should_stop()返回should_stop標誌。它用於創建的線程檢查結束標誌,並決定是否退出
while (!kthread_should_stop()) { //只要不接受到結束信號,就繼續進行調度
schedule();
set_current_state(TASK_INTERRUPTIBLE);
}
set_current_state(TASK_RUNNING); //這裏是爲了執行之後的處理 保證本線程正常終止
return 0;
}
小知識
cpu搶佔
在中斷或臨界區代碼中,線程需要關閉內核搶佔,因此,互斥機制(如:自旋鎖(spinlock)、RCU等)、中斷代碼、鏈表數據遍歷等需要關閉內核搶佔,臨界代碼運行完時,需要開啓內核搶佔。關閉/開啓內核搶佔需要使用內核搶佔API函數preempt_disable和 preempt_enable。
關於搶佔cpu的幾個函數認知
內核搶佔API函數說明如下(在include/linux/preempt.h中):
preempt_enable() //內核搶佔計數preempt_count減1
preempt_disable() //內核搶佔計數preempt_count加1
preempt_enable_no_resched() //內核搶佔計數preempt_count減1,但不立即搶佔式調度
preempt_check_resched () //如果必要進行調度
preempt_count() //返回搶佔計數
preempt_schedule() //核搶佔時的調度程序的入口點
線程狀態
看完這段代碼,要了解一下linux線程的狀態:
- TASK_RUNNING:正在運行或處於就緒狀態:就緒狀態是指進程申請到了CPU以外的其它全部資源。正所謂:萬事俱備,僅僅欠東風.提醒:一般的操作系統教科書將正在CPU上運行的進程定義爲RUNNING狀態、而將可運行可是尚未被調度運行的進程定義爲READY狀態。這兩種狀態在Linux下統一爲 TASK_RUNNING狀態.
- TASK_INTERRUPTIBLE:處於等待隊伍中,等待資源有效時喚醒(比方等待鍵盤輸入、socket連接、信號等等),但能夠被中斷喚醒.普通情況下,進程列表中的絕大多數進程都處於TASK_INTERRUPTIBLE狀態.畢竟皇帝僅僅有一個(單個CPU時),後宮佳麗幾千;假設不是絕大多數進程都在睡眠,CPU又怎麼響應得過來.
- TASK_UNINTERRUPTIBLE:處於等待隊伍中,等待資源有效時喚醒(比方等待鍵盤輸入、socket連接、信號等等),但不能夠被中斷喚醒.
- TASK_ZOMBIE:僵死狀態。進程資源用戶空間被釋放,但內核中的進程PCB並沒有釋放。等待父進程回收.
- TASK_STOPPED:進程被外部程序暫停(如收到SIGSTOP信號,進程會進入到TASK_STOPPED狀態),當再次同意時繼續運行(進程收到SIGCONT信號,進入TASK_RUNNING狀態)。
ksoftirqd創建過程
inux/init/main.c(do_pre_smp_initcalls)-> linux/kernel/softirq.c(spawn_ksoftirqd)-> linux/kernel/softirq.c(cpu_callback)