1. 程式人生 > >Linux中斷上下文

Linux中斷上下文

中斷線程 spa swapper include func script ask 需要 裏的

一、前言

每一個Linux驅動工程師都知道這樣一個準則:在中斷上下文中不能睡眠。但是為什麽interrupt context中不能調用導致睡眠的kernel API呢?如果驅動這麽做會導致什麽樣的後果呢?這就是本文探討的主題。為了理解這個主題,我們設計了一些非常簡單的驅動程序和用戶空間的程序,實際做實驗觀察實驗效果,最後給出了結果和分析。

BTW,本文的實驗在X86 64bit + 標準4.4內核上完成。

二、測試程序

1、cst驅動模塊

我們首先準備一個能夠在中斷上下文中睡眠的驅動程序,在這裏我稱之Context schedule test module(後文簡稱cst模塊)。這個驅動程序類似潛伏在內核中的“搗蛋鬼”,每隔1秒隨機命中一個進程,然後引發調度。首先準備一個Makefile,代碼如下:

KERNELSRC ?= /home/xxxx/work/linux-4.4.6

default:
$(MAKE) -C $(KERNELSRC) M=$$PWD

clean:
$(MAKE) -C $(KERNELSRC) M=$$PWD clean

按理說代碼中的xxxx應該是我的名字,如果你願意在你的環境中測試,可以修改成你的名字,當然,最重要的是需要某個版本的內核代碼。在內核升級文檔中,我已經編譯了/home/xxxx/work/linux-4.4.6目錄下的內核,並把我的計算機升級到4.4.6的內核上,如果你願意可以按照那份文檔進行升級,否則可能會有一些版本問題需要處理。除了Makefile之外,還需要一個Kbuild文件:

obj-m := cst.o

當然,最重要的是cst模塊的源代碼:

#include
#include
#include
#include

#define DRIVER_DESC "context schedule test driver"

static struct timer_list cst_timer;

static void cst_timer_handler (unsigned long data)
{
struct task_struct *p = current;

pr_info("=====in timer handler=====\n");
pr_info("cst shoot %16s [%x] task:\n", p->comm, preempt_count());
mod_timer(&cst_timer, jiffies + HZ);
schedule();
}

static int __init cst_init(void)
{
init_timer(&cst_timer);
cst_timer.function = cst_timer_handler;
cst_timer.expires = jiffies + HZ;
add_timer(&cst_timer);

pr_info(DRIVER_DESC " : init on cpu:%d\n", smp_processor_id());
return 0;
}
module_init(cst_init);

static void __exit cst_exit(void)
{
del_timer_sync(&cst_timer);
pr_info(DRIVER_DESC " : exit\n");
}
module_exit(cst_exit);

MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_AUTHOR("linuxer <[email protected]>");
MODULE_LICENSE("GPL");

代碼非常的簡單,無需多說,直接make就可以編譯得到cst.ko的內核模塊了。

2、用戶空間測試程序

為了更方便的測試,我們需要準備一個“受害者”,代碼如下:

#include
#include

int main(int argc, char **argv)
{
int i = 0;

while (1) {
sqrt (rand ());

if ((i % 0xffffff) == 0)
printf ("=\n");

if ((i % 0xfffffff) == 0)
printf ("haha......still alive\n");

i++;
}

return 0;
}

這段代碼也很簡單:不斷的產生一個隨機數,並運算其平方根,在使得的時候,向用戶輸出一些字符,表明自己的狀態。當程序執行起來的時候,大部分時間在用戶態(運算),偶爾進入內核態(printf)。這個進程並不知道在內核態有一個cst的模塊,每隔一秒就發射一只休眠之箭,可能命中用戶態,也可能命中內核態,看運氣吧,但是無論怎樣,該進程被射中之後都會進入睡眠。

三、執行測試並觀察結果

1、先把用戶空間的測試程序跑起來

要想測試導彈(呵呵~~我們的cst模塊就是一個搗蛋) 的性能,必須要有靶機或者靶艦。當然也可以不用“靶機”程序,只不過搗蛋鬼cst總是命中swapper進程,有點無趣,因此這裏需要把我們用戶空間的那個測試程序跑起來,讓CPU先活躍起來。

需要註意的是,在多核cpu上,我們需要多跑幾個“靶機”進程才能讓系統不會always進入idle狀態。例如我的T450是4核cpu,因此我需要運行4個靶機程序才能讓系統中的4個cpu core都燥起來。可以通過下面的命令確認:

ps –eo comm,psr | grep cst

BTW,靶機程序是cst_test。通過上面的命令,可以看到系統中運行了四個cst_test進程,分別在4個cpu上。

2、插入內核模塊

靶機已經就緒,是時候發射搗蛋了,命令如下:

sudo insmod ./cst.ko

一旦插入了cst內核模塊,搗蛋鬼就開始運作了,每隔1秒鐘發射一次,總有一個倒黴蛋被命中,被調度。當然,在我們的測試中,一般總是cst_test這個進程被命中。

3、觀察結果

一切準備就緒,是時候搬個小板凳坐下來看好戲了。當然,我們需要一個觀察的工具,輸入如下命令:

sudo tail –f /var/log/messages

在上面的cst模塊中,輸出並沒有直接到控制臺,因此我們需要通過內核日誌來看看cst的運行情況。

四、結果和分析

1、結果

很奇怪,一切都是正常的,系統沒有死,cst模塊也運行正常,cst_test進程也始終保持alive狀態,不斷的運行在無聊的平方根、打印著無聊的字符串。唯一異常的是日誌,每隔1秒鐘就會dump stack一次。

2、分析

當cst模塊命中cst_test進程,無論是userspace還是kernel space,都會在內核棧上保存中斷發生那一點的上下文,唯一不同是如果發生在userspace,那麽發生中斷那一刻,內核棧是空的,而如果在kernel space,內核棧上已經有了cst_test通過系統調用進入內核的現場以及該系統調用各個函數的stack frame,當中斷發生的時候,在當前內核棧的棧頂上繼續壓入了中斷現場,之後就是中斷處理的各個函數的stack frame,最後是cst_timer_handler的stack frame,由於調用了schedule函數,cst_test進程的現場被繼續壓入內核棧,調度器決定下一個調度的進程。

cst_test進程雖然被調度了,但是仍然在runqueue中,調度器一定會在適當的時機調度cst_test進程重新進入執行態,這時候恢復其執行就OK了,cpu執行cst_timer_handler函數schedule之後的代碼,繼續未完的中斷上下文的執行,然後從內核棧恢復中斷現場,一切又按照原路返回了。

當然,這裏的測試看起來一切OK,但這並不是說可以自由的在中斷上下文中調用導致睡眠的內核API,因為我們這裏給出了一個簡單的例子,實際上也有可能導致系統死鎖。例如在內核態持有鎖的時候被中斷,然後發生調度。有興趣的同學可以自己修改上面的代碼實驗這種情況。

3、why

最後還是回到這個具體的技術問題:為什麽interrupt context中不能調用導致睡眠的kernel API?

我的看法是這樣的:調度器是每一個OS的必備組件,在編碼階段之前,我們往往要制定下我們的設計概念。對於Linux 調度器,它的目標就是調度一個線程,一個線程就是調度實體(暫不考慮group sched)。中斷上下文是不是調度實體呢?當然不是,它沒有專屬的task struct,內核無從調度。這是調度器設計者的決定,這樣的決定讓調度器設計起來簡潔而美麗。

基於上面的設計概念,中斷上下文(hard irq和softirq context)並不參與調度(暫不考慮中斷線程化),它們是異步事件的處理機制,目標就是盡快完成處理,返回現場。因此,所有中斷上下文的優先級都是高於進程上下文的,也就是說,對於用戶進程(無論內核態還是用戶態)或者內核線程,除非disable了CPU的本地中斷,否則一旦中斷發生,它們是沒有任何能力阻擋中斷上下文搶占當前進程上下文的執行的。

因此,Linux kernel的設計者制定了規則:

1、中斷上下文不是調度實體

2、中斷上下文的優先級高於進程上下文

而在中斷上下文中調度毫無疑問會打破規則,因此不能在硬中斷、軟中斷環境中調用阻塞函數。不過,在linux調度器的具體實現的時候,檢測到了在中斷上下文中調度schedule函數也並沒有強制linux進入panic,可能是linux的開發者認為一個好的內核調度器無論如何也盡自己最大的努力讓系統運行下去吧。但是,在廠商自己提供的內核中,往往修改調度器行為,在中斷上下文中檢測到調度就直接panic了,對於內核開發者而言,這樣更好,可以盡早的發現問題。

Linux中斷上下文