Linux用戶搶佔和內核搶佔詳解(概念, 實現和觸發時機)--Linux進程的管理與調度(二十)【轉】

轉自:http://www.javashuo.com/article/p-pkwolsgu-m.htmllinux

版權聲明:本文爲博主原創文章 && 轉載請著名出處 @ http://blog.csdn.net/gatieme https://blog.csdn.net/gatieme/article/details/51872618
日期 內核版本 架構 做者 GitHub CSDN
2016-07-01 Linux-4.6 X86 & arm gatieme LinuxDeviceDrivers Linux進程管理與調度
前面咱們瞭解了linux進程調度器的設計思路和注意框架算法

週期調度器scheduler_tick經過linux定時器週期性的被激活, 進行程序調度數據結構

進程主動放棄CPU或者發生阻塞時, 則會調用主調度器schedule進行程序調度架構

在分析的過程當中, 咱們提到了內核搶佔和用戶搶佔的概念, 可是並無詳細講, 所以咱們在這裏詳細分析一會兒併發

CPU搶佔分兩種狀況, 用戶搶佔, 內核搶佔負載均衡

其中內核搶佔是在Linux2.5.4版本發佈時加入, 同SMP(Symmetrical Multi-Processing, 對稱多處理器), 做爲內核的可選配置。框架

1 前景回顧
1.1 Linux的調度器組成
2個調度器異步

能夠用兩種方法來激活調度electron

一種是直接的, 好比進程打算睡眠或出於其餘緣由放棄CPU函數

另外一種是經過週期性的機制, 以固定的頻率運行, 不時的檢測是否有必要

所以當前linux的調度程序由兩個調度器組成:主調度器,週期性調度器(二者又統稱爲通用調度器(generic scheduler)或核心調度器(core scheduler))

而且每一個調度器包括兩個內容:調度框架(其實質就是兩個函數框架)及調度器類

6種調度策略

linux內核目前實現了6中調度策略(即調度算法), 用於對不一樣類型的進程進行調度, 或者支持某些特殊的功能

SCHED_NORMAL和SCHED_BATCH調度普通的非實時進程

SCHED_FIFO和SCHED_RR和SCHED_DEADLINE則採用不一樣的調度策略調度實時進程

SCHED_IDLE則在系統空閒時調用idle進程.

5個調度器類

而依據其調度策略的不一樣實現了5個調度器類, 一個調度器類能夠用一種種或者多種調度策略調度某一類進程, 也能夠用於特殊狀況或者調度特殊功能的進程.

其所屬進程的優先級順序爲

stop_sched_class -> dl_sched_class -> rt_sched_class -> fair_sched_class -> idle_sched_class
1
3個調度實體

調度器不限於調度進程, 還能夠調度更大的實體, 好比實現組調度.

這種通常性要求調度器不直接操做進程, 而是處理可調度實體, 所以須要一個通用的數據結構描述這個調度實體,即seched_entity結構, 其實際上就表明了一個調度對象,能夠爲一個進程,也能夠爲一個進程組.

linux中針對當前可調度的實時和非實時進程, 定義了類型爲seched_entity的3個調度實體

sched_dl_entity 採用EDF算法調度的實時調度實體

sched_rt_entity 採用Roound-Robin或者FIFO算法調度的實時調度實體

sched_entity 採用CFS算法調度的普通非實時進程的調度實體

1.2 主調度器與內核/用戶搶佔
1.2.1 調度過程當中關閉內核搶佔
咱們在上一篇linux內核主調度器schedule(文章連接, CSDN, Github)中在分析主調度器的時候, 咱們會發現內核在進行調度以前都會經過preempt_disable關閉內核搶佔, 而在完成調度工做後, 又會從新開啓內核搶佔

參見主調度器函數schedule

do {
preempt_disable(); /* 關閉內核搶佔 */
__schedule(false); /* 完成調度 */
sched_preempt_enable_no_resched(); /* 開啓內核搶佔 */

} while (need_resched()); /* 若是該進程被其餘進程設置了TIF_NEED_RESCHED標誌,則函數從新執行進行調度 */
1
2
3
4
5
6
這個很容易理解, 咱們在內核完成調度器過程當中, 這時候若是發生了內核搶佔, 咱們的調度會被中斷, 而調度卻尚未完成, 這樣會丟失咱們調度的信息.

1.2.2 調度完成檢查need_resched看是否須要從新調度
而一樣咱們能夠看到, 在調度完成後, 內核會去判斷need_resched條件, 若是這個時候爲真, 內核會從新進程一次調度.

這個的緣由, 咱們在前一篇博客中, 也已經說的很明白了,

內核在thread_info的flag中設置了一個標識來標誌進程是否須要從新調度, 即從新調度need_resched標識TIF_NEED_RESCHED, 內核在即將返回用戶空間時會檢查標識TIF_NEED_RESCHED標誌進程是否須要從新調度,若是設置了,就會發生調度, 這被稱爲用戶搶佔

2 非搶佔式和可搶佔式內核
爲了簡化問題,我使用嵌入式實時系統uC/OS做爲例子

首先要指出的是,uC/OS只有內核態,沒有用戶態,這和Linux不同

多任務系統中, 內核負責管理各個任務, 或者說爲每一個任務分配CPU時間, 而且負責任務之間的通信.

內核提供的基本服務是任務切換. 調度(Scheduler),英文還有一詞叫dispatcher, 也是調度的意思.

這是內核的主要職責之一, 就是要決定該輪到哪一個任務運行了. 多數實時內核是基於優先級調度法的, 每一個任務根據其重要程度的不一樣被賦予必定的優先級. 基於優先級的調度法指,CPU老是讓處在就緒態的優先級最高的任務先運行. 然而, 究竟什麼時候讓高優先級任務掌握CPU的使用權, 有兩種不一樣的狀況, 這要看用的是什麼類型的內核, 是不可剝奪型的仍是可剝奪型內核

2.1 非搶佔式內核
非搶佔式內核是由任務主動放棄CPU的使用權

非搶佔式調度法也稱做合做型多任務, 各個任務彼此合做共享一個CPU. 異步事件仍是由中斷服務來處理. 中斷服務可使一個高優先級的任務由掛起狀態變爲就緒狀態.

但中斷服務之後控制權仍是回到原來被中斷了的那個任務, 直到該任務主動放棄CPU的使用權時,那個高優先級的任務才能得到CPU的使用權。非搶佔式內核以下圖所示.

 

非搶佔式內核的優勢有

中斷響應快(與搶佔式內核比較);

容許使用不可重入函數;

幾乎不須要使用信號量保護共享數據, 運行的任務佔有CPU,沒必要擔憂被別的任務搶佔。這不是絕對的,在打印機的使用上,仍須要知足互斥條件。

非搶佔式內核的缺點有

任務響應時間慢。高優先級的任務已經進入就緒態,但還不能運行,要等到當前運行着的任務釋放CPU

非搶佔式內核的任務級響應時間是不肯定的,不知道何時最高優先級的任務才能拿到CPU的控制權,徹底取決於應用程序何時釋放CPU

2.2 搶佔式內核
使用搶佔式內核能夠保證系統響應時間. 最高優先級的任務一旦就緒, 總能獲得CPU的使用權。當一個運行着的任務使一個比它優先級高的任務進入了就緒態, 當前任務的CPU使用權就會被剝奪,或者說被掛起了,那個高優先級的任務馬上獲得了CPU的控制權。若是是中斷服務子程序使一個高優先級的任務進入就緒態,中斷完成時,中斷了的任務被掛起,優先級高的那個任務開始運行。

搶佔式內核以下圖所示

 

搶佔式內核的優勢有

使用搶佔式內核,最高優先級的任務何時能夠執行,能夠獲得CPU的使用權是可知的。使用搶佔式內核使得任務級響應時間得以最優化。
搶佔式內核的缺點有:

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

3 linux用戶搶佔
3.1 linux用戶搶佔
當內核即將返回用戶空間時, 內核會檢查need_resched是否設置, 若是設置, 則調用schedule(),此時,發生用戶搶佔.

3.2 need_resched標識
內核如何檢查一個進程是否須要被調度呢?

內核在即將返回用戶空間時檢查進程是否須要從新調度,若是設置了,就會發生調度, 這被稱爲用戶搶佔, 所以內核在thread_info的flag中設置了一個標識來標誌進程是否須要從新調度, 即從新調度need_resched標識TIF_NEED_RESCHED

並提供了一些設置可檢測的函數

函數 描述 定義
set_tsk_need_resched 設置指定進程中的need_resched標誌 include/linux/sched.h, L2920
clear_tsk_need_resched 清除指定進程中的need_resched標誌 include/linux/sched.h, L2926
test_tsk_need_resched 檢查指定進程need_resched標誌 include/linux/sched.h, L2931
而咱們內核中調度時經常使用的need_resched()函數檢查進程是否須要被從新調度其實就是經過test_tsk_need_resched實現的, 其定義以下所示

// http://lxr.free-electrons.com/source/include/linux/sched.h?v=4.6#L3093
static __always_inline bool need_resched(void)
{
return unlikely(tif_need_resched());
}

// http://lxr.free-electrons.com/source/include/linux/thread_info.h?v=4.6#L106
#define tif_need_resched() test_thread_flag(TIF_NEED_RESCHED)
1
2
3
4
5
6
7
8
3.3 用戶搶佔的發生時機(何時須要從新調度need_resched)
通常來講,用戶搶佔發生幾下狀況:

從系統調用返回用戶空間;

從中斷(異常)處理程序返回用戶空間

從這裏咱們能夠看到, 用戶搶佔是發生在用戶空間的搶佔現象.

更詳細的觸發條件以下所示, 其實不外乎就是前面所說的兩種狀況: 從系統調用或者中斷返回用戶空間

時鐘中斷處理例程檢查當前任務的時間片,當任務的時間片消耗完時,scheduler_tick()函數就會設置need_resched標誌;

信號量、等到隊列、completion等機制喚醒時都是基於waitqueue的,而waitqueue的喚醒函數爲default_wake_function,其調用try_to_wake_up將被喚醒的任務更改成就緒狀態並設置need_resched標誌。

設置用戶進程的nice值時,可能會使高優先級的任務進入就緒狀態;

改變任務的優先級時,可能會使高優先級的任務進入就緒狀態;

新建一個任務時,可能會使高優先級的任務進入就緒狀態;

對CPU(SMP)進行負載均衡時,當前任務可能須要放到另一個CPU上運行

4 linux內核搶佔
4.1 內核搶佔的概念
對比用戶搶佔, 顧名思義, 內核搶佔就是指一個在內核態運行的進程, 可能在執行內核函數期間被另外一個進程取代.

4.2 爲何linux須要內核搶佔
linux系統中, 進程在系統調用後返回用戶態以前, 或者是內核中某些特定的點上, 都會調用調度器. 這確保除了一些明確指定的狀況以外, 內核是沒法中斷的, 這不一樣於用戶進程.

若是內核處於相對耗時的操做中, 好比文件系統或者內存管理相關的任務, 這種行爲可能會帶來問題. 這種狀況下, 內核代替特定的進程執行至關長的時間, 而其餘進程沒法執行, 沒法調度, 這就形成了系統的延遲增長, 用戶體驗到」緩慢」的響應. 好比若是多媒體應用長時間沒法獲得CPU, 則可能發生視頻和音頻漏失現象.

在編譯內核時若是啓用了對內核搶佔的支持, 則能夠解決這些問題. 若是高優先級進程有事情須要完成, 那麼在啓用了內核搶佔的狀況下, 不只用戶空間應用程序能夠被中斷, 內核也能夠被中斷,

linux內核搶佔是在Linux2.5.4版本發佈時加入的, 儘管使內核可搶佔須要的改動特別少, 可是該機制不像搶佔用戶空間進程那樣容易實現. 若是內核沒法一次性完成某些操做(例如, 對數據結構的操做), 那麼可能出現靜態條件而使得系統不一致.

內核搶佔和用戶層進程被其餘進程搶佔是兩個不一樣的概念, 內核搶佔主要是從實時系統中引入的, 在非實時系統中的確也能提升系統的響應速度, 但也不是在全部狀況下都是最優的,由於搶佔也須要調度和同步開銷,在某些狀況下甚至要關閉內核搶佔, 好比前面咱們將主調度器的時候, linux內核在完成調度的過程當中是關閉了內核搶佔的.

內核不能再任意點被中斷, 幸運的是, 大多數不能中斷的點已經被SMP實現標識出來了. 而且在實現內核搶佔時能夠重用這些信息. 若是內核能夠被搶佔, 那麼單處理器系統也會像是一個SMP系統

4.3 內核搶佔的發生時機
要知足什麼條件,kernel才能夠搶佔一個任務的內核態呢?

沒持有鎖。鎖是用於保護臨界區的,不能被搶佔。

Kernel code可重入(reentrant)。由於kernel是SMP-safe的,因此知足可重入性。

內核搶佔發生的時機,通常發生在:

當從中斷處理程序正在執行,且返回內核空間以前。當一箇中斷處理例程退出,在返回到內核態時(kernel-space)。這是隱式的調用schedule()函數,當前任務沒有主動放棄CPU使用權,而是被剝奪了CPU使用權。

當內核代碼再一次具備可搶佔性的時候,如解鎖(spin_unlock_bh)及使能軟中斷(local_bh_enable)等, 此時當kernel code從不可搶佔狀態變爲可搶佔狀態時(preemptible again)。也就是preempt_count從正整數變爲0時。這也是隱式的調用schedule()函數

若是內核中的任務顯式的調用schedule(), 任務主動放棄CPU使用權

若是內核中的任務阻塞(這一樣也會致使調用schedule()), 致使須要調用schedule()函數。任務主動放棄CPU使用權

內核搶佔,並非在任何一個地方均可以發生,如下狀況不能發生

內核正進行中斷處理。在Linux內核中進程不能搶佔中斷(中斷只能被其餘中斷停止、搶佔,進程不能停止、搶佔中斷),在中斷例程中不容許進行進程調度。進程調度函數schedule()會對此做出判斷,若是是在中斷中調用,會打印出錯信息。

內核正在進行中斷上下文的Bottom Half(中斷下半部,即軟中斷)處理。硬件中斷返回前會執行軟中斷,此時仍然處於中斷上下文中。若是此時正在執行其它軟中斷,則再也不執行該軟中斷。

內核的代碼段正持有spinlock自旋鎖、writelock/readlock讀寫鎖等鎖,處幹這些鎖的保護狀態中。內核中的這些鎖是爲了在SMP系統中短期內保證不一樣CPU上運行的進程併發執行的正確性。當持有這些鎖時,內核不該該被搶佔。

內核正在執行調度程序Scheduler。搶佔的緣由就是爲了進行新的調度,沒有理由將調度程序搶佔掉再運行調度程序。

內核正在對每一個CPU「私有」的數據結構操做(Per-CPU date structures)。在SMP中,對於per-CPU數據結構未用spinlocks保護,由於這些數據結構隱含地被保護了(不一樣的CPU有不同的per-CPU數據,其餘CPU上運行的進程不會用到另外一個CPU的per-CPU數據)。可是若是容許搶佔,但一個進程被搶佔後從新調度,有可能調度到其餘的CPU上去,這時定義的Per-CPU變量就會有問題,這時應禁搶佔。

5 內核搶佔的實現
5.1 內核如何跟蹤它可否被搶佔?
前面咱們提到了, 系統中每一個進程都有一個特定於體系結構的struct thread_info結構, 用戶層程序被調度的時候會檢查struct thread_info中的need_resched標識TLF_NEED_RESCHED標識來檢查本身是否須要被從新調度.

天然內核搶佔·也能夠應用一樣的方法被實現, linux內核在thread_info結構中添加了一個自旋鎖標識preempt_count, 稱爲搶佔計數器(preemption counter).

struct thread_info
{
/* ...... */
int preempt_count; /* 0 => preemptable, <0 => BUG */
/* ...... */
}
1
2
3
4
5
6
preempt_count值 描述
0
禁止內核搶佔, 其值標記了使用preempt_count的臨界區的數目
0 開啓內核搶佔
<0 鎖爲負值, 內核出現錯誤
內核天然也提供了一些函數或者宏, 用來開啓, 關閉以及檢測搶佔計數器preempt_coun的值, 這些通用的函數定義在include/asm-generic/preempt.h, 而某些架構也定義了本身的接口, 好比x86架構/arch/x86/include/asm/preempt.h

函數 描述 定義
preempt_count 獲取當前current進程搶佔計數器的值 include/asm-generic/preempt.h, line 8
preempt_count_ptr 返回指向當前current進程的搶佔計數器的指針 include/asm-generic/preempt.h, line 13
preempt_count_set 重設當前current進程的搶佔計數器 include/asm-generic/preempt.h, line 18
init_task_preempt_count 初始化task的搶佔計數器爲FORK_PREEMPT_COUNT include/asm-generic/preempt.h, line 26
init_idle_preempt_count 初始化task的搶佔計數器爲PREEMPT_ENABLED include/asm-generic/preempt.h, line 30
preempt_count_add 將增長current的搶佔計數器增長val include/linux/preempt.h, line 132
preempt_count_sub 將增長current的搶佔計數器減小val include/linux/preempt.h, line 133
preempt_count_dec_and_test 將current的搶佔計數器減小1, 而後看是否能夠進程內核搶佔, 即檢查搶佔計數器是否爲0(容許搶佔), 同時檢查tif_need_resched標識是否爲真 include/linux/preempt.h, line 134, 61
preempt_count_inc current的搶佔計數器增長1 include/linux/preempt.h, line 140
preempt_count_dec current的搶佔計數器減小1 include/linux/preempt.h, line 141
還有其餘函數可用於開啓和關閉內核搶佔

函數 描述 定義
preempt_disable 經過preempt_count_inc來停用內核搶佔, 而且經過路障barrier同步來避免編譯器的優化 include/linux/preempt.h, line 145
preempt_enable preempt_count_dec_and_test啓用內核搶佔, 而後經過__preempt_schedule檢測是夠有必要進行調度 include/linux/preempt.h, line 162
preempt_enable_no_resched 開啓搶佔, 可是不進行重調度 include/linuxc/preempt.h, line 151
preempt_check_resched 調用__preempt_schedule檢測是夠有必要進行調度 include/linux/preempt.h, line 176
should_resched 檢查current的搶佔計數器是否爲參數preempt_offset的值, 同時檢查 tif_need_resched是否爲真 include/linux/preempt.h, line 74
preemptible 檢查是否能夠內核搶佔, 檢查搶佔計數器是否爲0, 以及是否停用了中斷 /include/linux/preempt.h, line159
5.2 內核如何知道是否須要搶佔?
首先必須設置了TLF_NEED_RESCHED標識來通知內核有進程在等待獲得CPU時間, 而後會在判斷搶佔計數器preempt_count是否爲0, 這個工做每每經過preempt_check_resched或者其相關來實現

5.2.1 從新啓用內核搶佔時使用preempt_schedule檢查搶佔
在內核停用搶佔後從新啓用時, 檢測是否有進程打算搶佔當前執行的內核代碼, 是一個比較好的時機, 若是是這樣, 應該儘快完成, 則無需等待下一次對調度器的例行調用.

搶佔機制中主要的函數是preempt_schedule, 設置了TIF_NEED_RESCHED標誌並不能保證能夠搶佔內核, 內核可能處於臨界區, 不能被幹擾

// http://lxr.free-electrons.com/source/kernel/sched/core.c?v=4.6#L3307

/*
* this is the entry point to schedule() from in-kernel preemption
* off of preempt_enable. Kernel preemptions off return from interrupt
* occur there and call schedule directly.
*/
asmlinkage __visible void __sched notrace preempt_schedule(void)
{
/*
* If there is a non-zero preempt_count or interrupts are disabled,
* we do not want to preempt the current task. Just return..
*/
/* !preemptible() => preempt_count() != 0 || irqs_disabled()
* 若是搶佔計數器大於0, 那麼搶佔被停用, 該函數當即返回
* 若是
*/
if (likely(!preemptible()))
return;

preempt_schedule_common();
}
NOKPROBE_SYMBOL(preempt_schedule);
EXPORT_SYMBOL(preempt_schedule);

// http://lxr.free-electrons.com/source/include/linux/preempt.h?v=4.6#L159
#define preemptible() (preempt_count() == 0 && !irqs_disabled())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
!preemptible => preempt_count() != 0 || irqs_disabled()代表

若是搶佔計數器大於0, 那麼搶佔仍然是被停用的, 所以內核不能被打斷, 該函數當即結束.

若是在某些重要的點上內核停用了硬件中斷, 以保證一次性完成相關的處理, 那麼搶佔也是不可能的.irqs_disabled會檢測是否停用了中斷. 若是已經停用, 則內核不能被搶佔

接着若是能夠被搶佔, 則執行以下步驟


static void __sched notrace preempt_schedule_common(void)
{
do {
/*
preempt_disable_notrace定義在
http://lxr.free-electrons.com/source/include/linux/preempt.h?v=4.6#L198 等待於__preempt_count_inc();
*/
preempt_disable_notrace();
/* 完成一次調度 */
__schedule(true);

/*
preempt_enable_no_resched_notrace
http://lxr.free-electrons.com/source/include/linux/preempt.h?v=4.6#L204
等價於__preempt_count_dec
*/
preempt_enable_no_resched_notrace();

/*
* Check again in case we missed a preemption opportunity
* between schedule and now.
* 再次檢查, 以避免在__scheudle和當前點之間錯過了搶佔的時機
*/
} while (need_resched());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
咱們能夠看到, 內核在增長了搶佔計數器的計數後, 用__schedule進行了一次調度, 參數傳入preempt = true, 代表調度不是以普通的方式引起的, 而是因爲內核搶佔. 在內核重調度以後, 代碼流程回到當前進程, 那麼就井搶佔計數器減小1.

5.2.2 中斷以後返回內核態時經過preempt_schedule_irq觸發
上面preempt_schedule只是觸發內核搶佔的一種方法, 另外一種激活搶佔的方式是在處理了一個硬件中斷請求以後. 若是處理器在處理中斷請求後返回內核態(返回用戶態則沒有影響), 特定體系結構的彙編例程會檢查搶佔計數器是否爲0, 便是否容許搶佔, 以及是否設置了重調度標識, 相似於preempt_schedule的處理. 若是兩個條件都知足則經過preempt_schedule_irq調用調度器, 此時代表搶佔請求發自中斷上下文

該函數與preempt_schedule的本質區別在於: preempt_schedule_irq調用時停用了中斷, 防止終端形成的遞歸調用, 其定義在kernel/sched/core.c, line3360

/*
* this is the entry point to schedule() from kernel preemption
* off of irq context.
* Note, that this is called and return with irqs disabled. This will
* protect us against recursive calling from irq.
*/
asmlinkage __visible void __sched preempt_schedule_irq(void)
{
enum ctx_state prev_state;

/* Catch callers which need to be fixed */
BUG_ON(preempt_count() || !irqs_disabled());

prev_state = exception_enter();

do {
preempt_disable();
local_irq_enable();
__schedule(true);
local_irq_disable();
sched_preempt_enable_no_resched();
} while (need_resched());

exception_exit(prev_state);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
5.2.3 PREEMPT_ACTIVE標識位和PREEMPT_DISABLE_OFFSET
以前的內核版本中, 搶佔計數器中於一個標識位PREEMPT_ACTIVE, 這個位設置後即標識了能夠進行內核搶佔, 使得preempt_count有一個很大的值, 這樣就不受普通的搶佔計數器加1操做的影響了

PREEMPT_ACTIVE的引入, 參見PREEMPT_ACTIVE: add default defines

// http://lxr.free-electrons.com/source/include/linux/preempt.h?v=4.3#L58
#define PREEMPT_ACTIVE_BITS 1
#define PREEMPT_ACTIVE_SHIFT (NMI_SHIFT + NMI_BITS)
#define PREEMPT_ACTIVE (__IRQ_MASK(PREEMPT_ACTIVE_BITS) << PREEMPT_ACTIVE_SHIFT)
1
2
3
4
而後也爲其提供了一些置位的函數,其實就是將preempt_count加上/減去一個很大的數, 參見preempt: Disable preemption from preempt_schedule*() callers

可是在linux-4.4版本以後移除了這個標誌, 取而代之的是在linux-4.2時引入的PREEMPT_DISABLE_OFFSET

參見

Rename PREEMPT_CHECK_OFFSET to PREEMPT_DISABLE_OFFSET
preempt: Rename PREEMPT_CHECK_OFFSET to PREEMPT_DISABLE_OFFSET

preempt: Remove PREEMPT_ACTIVE unmasking off in_atomic()

sched: Kill PREEMPT_ACTIVE

sched: Stop setting PREEMPT_ACTIVE

參考

內核隨記(二)——內核搶佔與中斷返回

PREEMPT_ACTIVE

6 總結
通常來講,CPU在任什麼時候刻都處於如下三種狀況之一:

運行於用戶空間,執行用戶進程

運行於內核空間,處於進程上下文

運行於內核空間,處於中斷上下文

6.1 用戶搶佔
通常來講, 當進程從系統調用或者從中斷(異常)處理程序返回用戶空間時會觸發主調度器進行用戶搶佔

從系統調用返回用戶空間

從中斷(異常)處理程序返回用戶空間

爲了對一個進程須要被調度進行標記, 內核在thread_info的flag中設置了一個標識來標誌進程是否須要從新調度, 即從新調度need_resched標識TIF_NEED_RESCHED, 內核在即將返回用戶空間時會檢查標識TIF_NEED_RESCHED標誌進程是否須要從新調度,若是設置了,就會發生調度, 這被稱爲用戶搶佔

6.2 內核搶佔
若是內核處於相對耗時的操做中, 好比文件系統或者內存管理相關的任務, 這種行爲可能會帶來問題. 這種狀況下, 內核代替特定的進程執行至關長的時間, 而其餘進程沒法執行, 沒法調度, 這就形成了系統的延遲增長, 用戶體驗到」緩慢」的響應. 所以linux內核引入了內核搶佔.

linux內核經過在thread_info結構中添加了一個自旋鎖標識preempt_count, 稱爲搶佔計數器(preemption counter)來做爲內核搶佔的標記,

內核搶佔的觸發大體也是兩類, 內核搶佔關閉後從新開啓時, 中斷返回內核態時

內核從新開啓內核搶佔時使用preempt_schedule檢查內核搶佔

中斷以後返回內核態時經過preempt_schedule_irq觸發內核搶佔

相關文章
相關標籤/搜索