Linux內核態搶占機制分析

【摘要】本文首先介紹非搶占式內核(Non-Preemptive Kernel)和可搶占式內核(Preemptive Kernel)的區別。接着分析Linux下有兩種搶占:用戶態搶占(User Preemption)、內核態搶占(Kernel Preemption)。然後分析了在內核態下:如何判斷能否搶占內核(什麼是可搶占的條件);何時觸發重新調度(何時設置可搶占條件);搶占發生的時機(何時檢查可搶占的條件);什麼時候不能搶占內核。最後分析了2.6kernel中如何支持搶占內核。

【關鍵字】內核態搶占 用戶態搶占 中斷 實時性 自旋锁 linux kernel schedule preemption reentrant


非搶占式和可搶占式內核的區別

为了簡化問題,我使用嵌入式實時系統uC/OS作为例子。首先要指出的是,uC/OS只有內核態,沒有用戶態,這和Linux不一样。

多任務系統中,內核負責管理各個任務,或者說为每個任務分配CPU時間,並且負責任務之間的通訊。內核提供的基本服務是任務切換。調度(Scheduler),英文還有一詞叫dispatcher,也是調度的意思。這是內核的主要職責之一,就是要决定該輪到哪個任務運行了。多數實時內核是基於優先級調度法的。每個任務根據其重要程度的不同被賦予一定的優先級。基於優先級的調度法指,CPU總是讓處在就緒態的優先級最高的任務先運行。然而,究竟何時讓高優先級任務掌握CPU的使用權,有兩種不同的情況,這要看用的是什麼類型的內核,是不可剝奪型的還是可剝奪型內核。



非搶占式內核

非搶占式內核是由任務主動放棄CPU的使用權。非搶占式調度法也稱作合作型多任務,各個任務彼此合作共享一個CPU。異步事件還是由中斷服務來處理。中斷服務可以使一個高優先級的任務由掛起狀態變为就緒狀態。但中斷服務以後控制權還是回到原來被中斷了的那個任務,直到該任務主動放棄CPU的使用權時,那個高優先級的任務才能獲得CPU的使用權。非搶占式內核如下圖所示。


非搶占式內核的優點有:

  • 中斷響應快(與搶占式內核比較);
  • 允許使用不可重入函數;
  • 幾乎不需要使用信號量保護共享數據。運行的任務占有CPU,不必擔心被別的任務搶占。這不是絕對的,在打印機的使用上,仍需要滿足互斥條件。

非搶占式內核的缺點有:

  • 任務響應時間慢。高優先級的任務已經進入就緒態,但還不能運行,要等到當前運行着的任務釋放CPU。
  • 非搶占式內核的任務級響應時間是不確定的,不知道什麼時候最高優先級的任務才能拿到CPU的控制權,完全取决於應用程序什麼時候釋放CPU。


搶占式內核

使用搶占式內核可以保證系統響應時間。最高優先級的任務一旦就緒,總能得到CPU的使用權。當一個運行着的任務使一個比它優先級高的任務進入了就緒態,當前任務的CPU使用權就會被剝奪,或者說被掛起了,那個高優先級的任務立刻得到了CPU的控制權。如果是中斷服務子程序使一個高優先級的任務進入就緒態,中斷完成時,中斷了的任務被掛起,優先級高的那個任務開始運行。搶占式內核如下圖所示。


搶占式內核的優點有:

  • 使用搶占式內核,最高優先級的任務什麼時候可以執行,可以得到CPU的使用權是可知的。使用搶占式內核使得任務級響應時間得以最優化。

搶占式內核的缺點有:

  • 不能直接使用不可重入型函數。調用不可重入函數時,要滿足互斥條件,這點可以使用互斥型信號量來實現。如果調用不可重入型函數時,低優先級的任務CPU的使用權被高優先級任務剝奪,不可重入型函數中的數據有可能被破壞。


Linux下的用戶態搶占和內核態搶占

Linux除了內核態外還有用戶態。用戶程序的上下文屬於用戶態,系統調用和中斷處理例程上下文屬於內核態。在2.6 kernel以前,Linux kernel只支持用戶態搶占。



2.1 用戶態搶占(User Preemption)

在kernel返回用戶態(user-space)時,並且need_resched標志为1時,scheduler被調用,這就是用戶態搶占。當kernel返回用戶態時,系統可以安全的執行當前的任務,或者切換到另外一個任務。當中斷處理例程或者系統調用完成後,kernel返回用戶態時,need_resched標志的值會被檢查,假如它为1,調度器會選擇一個新的任務並執行。中斷和系統調用的返回路徑(return path)的實現在entry.S中(entry.S不僅包括kernel entry code,也包括kernel exit code)。



2.2 內核態搶占(Kernel Preemption)

在2.6 kernel以前,kernel code(中斷和系統調用屬於kernel code)會一直運行,直到code被完成或者被阻塞(系統調用可以被阻塞)。在 2.6 kernel裏,Linux kernel變成可搶占式。當從中斷處理例程返回到內核態(kernel-space)時,kernel會檢查是否可以搶占和是否需要重新調度。kernel可以在任何時間點上搶占一個任務(因为中斷可以發生在任何時間點上),只要在這個時間點上kernel的狀態是安全的、可重新調度的。



內核態搶占的設計

3.1 可搶占的條件

要滿足什麼條件,kernel才可以搶占一個任務的內核態呢?

  • 沒持有锁。锁是用於保護臨界區的,不能被搶占。
  • Kernel code可重入(reentrant)。因为kernel是SMP-safe的,所以滿足可重入性。

如何判斷當前上下文(中斷處理例程、系統調用、內核線程等)是沒持有锁的?Linux在每個每個任務的thread_info結構中增加了preempt_count變量作为preemption的計數器。這個變量初始为0,當加锁時計數器增一,當解锁時計數器減一。



3.2 內核態需要搶占的觸發條件

內核提供了一個need_resched標志(這個標志在任務結構thread_info中)來表明是否需要重新執行調度。



3.3 何時觸發重新調度

set_tsk_need_resched():設置指定進程中的need_resched標志

clear_tsk need_resched():清除指定進程中的need_resched標志

need_resched():檢查need_ resched標志的值;如果被設置就返回真,否則返回假

什麼時候需要重新調度:

  • 時钟中斷處理例程檢查當前任務的時間片,當任務的時間片消耗完時,scheduler_tick()函數就會設置need_resched標志;
  • 信號量、等到隊列、completion等機制喚醒時都是基於waitqueue的,而waitqueue的喚醒函數为default_wake_function,其調用try_to_wake_up將被喚醒的任務更改为就緒狀態並設置need_resched標志。
  • 設置用戶進程的nice值時,可能會使高優先級的任務進入就緒狀態;
  • 改變任務的優先級時,可能會使高優先級的任務進入就緒狀態;
  • 新建一個任務時,可能會使高優先級的任務進入就緒狀態;
  • 對CPU(SMP)進行負載均衡時,當前任務可能需要放到另外一個CPU上運行;


3.4 搶占發生的時機(何時檢查可搶占條件)

  • 當一個中斷處理例程退出,在返回到內核態時(kernel-space)。這是隱式的調用schedule()函數,當前任務沒有主動放棄CPU使用權,而是被剝奪了CPU使用權。
  • 當kernel code從不可搶占狀態變为可搶占狀態時(preemptible again)。也就是preempt_count從正整數變为0時。這也是隱式的調用schedule()函數。
  • 一個任務在內核態中顯式的調用schedule()函數。任務主動放棄CPU使用權。
  • 一個任務在內核態中被阻塞,導致需要調用schedule()函數。任務主動放棄CPU使用權。


3.5 禁用/使能可搶占條件的操作

對preempt_count操作的函數有add_preempt_count()、sub_preempt_count()、inc_preempt_count()、dec_preempt_count()。

使能可搶占條件的操作是preempt_enable(),它調用dec_preempt_count()函數,然後再調用preempt_check_resched()函數去檢查是否需要重新調度。

禁用可搶占條件的操作是preempt_disable(),它調用inc_preempt_count()函數。

在內核中有很多函數調用了preempt_enable()和preempt_disable()。比如spin_lock()函數調用了preempt_disable()函數,spin_unlock()函數調用了preempt_enable()函數。



3.6 什麼時候不允許搶占

preempt_count()函數用於獲取preempt_count的值,preemptible()用於判斷內核是否可搶占。

有幾種情況Linux內核不應該被搶占,除此之外,Linux內核在任意一點都可被搶占。這幾種情況是:

  • 內核正進行中斷處理。在Linux內核中進程不能搶占中斷(中斷只能被其他中斷中止、搶占,進程不能中止、搶占中斷),在中斷例程中不允許進行進程調度。進程調度函數schedule()會對此作出判斷,如果是在中斷中調用,會打印出錯信息。
  • 內核正在進行中斷上下文的Bottom Half(中斷的下半部)處理。硬件中斷返回前會執行軟中斷,此時仍然處於中斷上下文中。
  • 內核的代碼段正持有spinlock自旋锁、writelock/readlock讀寫锁等锁,處幹這些锁的保護狀態中。內核中的這些锁是为了在SMP系統中短時間內保證不同CPU上運行的進程並發執行的正確性。當持有這些锁時,內核不應該被搶占,否則由於搶占將導致其他CPU長期不能獲得锁而死等。
  • 內核正在執行調度程序Scheduler。搶占的原因就是为了進行新的調度,沒有理由將調度程序搶占掉再運行調度程序。
  • 內核正在對每個CPU“私有”的數據結構操作(Per-CPU date structures)。在SMP中,對於per-CPU數據結構未用spinlocks保護,因为這些數據結構隱含地被保護了(不同的CPU有不一样的per-CPU數據,其他CPU上運行的進程不會用到另一個CPU的per-CPU數據)。但是如果允許搶占,但一個進程被搶占後重新調度,有可能調度到其他的CPU上去,這時定義的Per-CPU變量就會有問題,這時應禁搶占。


Linux內核態搶占的實現

4.1 數據結構

在thread_info.h中


4.2 代碼流程

禁用/使能可搶占條件的函數

 
 
 
檢查可搶占條件 

自旋锁的加锁與解锁

設置need_resched標志的函數



時钟中斷時調用的task_tick()函數,當時間片消耗完之後,設置need_resched標志



設置任務的need_resched標志,並觸發任務所在CPU的調度器。




参考資料


http://rritw.com/a/JAVAbiancheng/ANT/20111113/142156.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值