久久精品人人爽,华人av在线,亚洲性视频网站,欧美专区一二三

Linux內核態搶占怎么實現

160次閱讀
沒有評論

共計 6478 個字符,預計需要花費 17 分鐘才能閱讀完成。

本篇內容介紹了“Linux 內核態搶占怎么實現”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓丸趣 TV 小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

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

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

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

非搶占式內核

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

非搶占式內核的優點有:

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

允許使用不可重入函數;

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

非搶占式內核的缺點有:

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

非搶占式內核的任務級響應時間是不確定的,不知道什么時候 *** 優先級的任務才能拿到 CPU 的控制權,完全取決于應用程序什么時候釋放 CPU。

搶占式內核

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

搶占式內核的優點有:

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

搶占式內核的缺點有:

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

2. 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. 內核態搶占的設計

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 變量就會有問題,這時應禁搶占。

4. Linux 內核態搶占的實現

4.1 數據結構

[cpp] view plain copy

struct thread_info { struct task_struct *task; /* main task structure */ struct exec_domain *exec_domain; /* execution domain */ /** *  如果有 TIF_NEED_RESCHED 標志,則必須調用調度程序。 */ unsigned long flags; /* low level flags */ /** *  線程標志: * TS_USEDFPU: 表示進程在當前執行過程中,是否使用過 FPU、MMX 和 XMM 寄存器。 */ unsigned long status; /* thread-synchronous flags */ /** *  可運行進程所在運行隊列的 CPU 邏輯號。 */ __u32 cpu; /* current CPU */ __s32 preempt_count; /* 0 =  preemptable,  0 =  BUG */ mm_segment_t addr_limit; /* thread address space: 0-0xBFFFFFFF for user-thead 0-0xFFFFFFFF for kernel-thread */ struct restart_block restart_block; unsigned long previous_esp; /* ESP of the previous stack in case of nested (IRQ) stacks */ __u8 supervisor_stack[0]; };

4.2 代碼流程

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

[cpp] view plain copy

#ifdef CONFIG_DEBUG_PREEMPT extern void fastcall add_preempt_count(int val); extern void fastcall sub_preempt_count(int val); #else # define add_preempt_count(val) do { preempt_count() += (val); } while (0) # define sub_preempt_count(val) do { preempt_count() -= (val); } while (0) #endif #define inc_preempt_count() add_preempt_count(1) #define dec_preempt_count() sub_preempt_count(1) /** *  在 thread_info 描述符中選擇 preempt_count 字段  */ #define preempt_count() (current_thread_info()- preempt_count) #ifdef CONFIG_PREEMPT asmlinkage void preempt_schedule(void); /** *  使搶占計數加 1  */ #define preempt_disable() \ do { \ inc_preempt_count(); \ barrier(); \ } while (0) /** *  使搶占計數減 1  */ #define preempt_enable_no_resched() \ do { \ barrier(); \ dec_preempt_count(); \ } while (0) #define preempt_check_resched() \ do { \ if (unlikely(test_thread_flag(TIF_NEED_RESCHED))) \ preempt_schedule(); \ } while (0) /** *  使搶占計數減 1,并在 thread_info 描述符的 TIF_NEED_RESCHED 標志被置為 1 的情況下,調用 preempt_schedule() */ #define preempt_enable() \ do { \ preempt_enable_no_resched(); \ preempt_check_resched(); \ } while (0) #else #define preempt_disable() do { } while (0) #define preempt_enable_no_resched() do { } while (0) #define preempt_enable() do { } while (0) #define preempt_check_resched() do { } while (0) #endif

設置 need_resched 標志的函數

[cpp] view plain copy

static inline void set_tsk_need_resched(struct task_struct *tsk) { set_tsk_thread_flag(tsk,TIF_NEED_RESCHED); } static inline void clear_tsk_need_resched(struct task_struct *tsk) { clear_tsk_thread_flag(tsk,TIF_NEED_RESCHED); }

“Linux 內核態搶占怎么實現”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注丸趣 TV 網站,丸趣 TV 小編將為大家輸出更多高質量的實用文章!

正文完
 
丸趣
版權聲明:本站原創文章,由 丸趣 2023-08-16發表,共計6478字。
轉載說明:除特殊說明外本站除技術相關以外文章皆由網絡搜集發布,轉載請注明出處。
評論(沒有評論)
主站蜘蛛池模板: 湖南省| 凤城市| 嘉峪关市| 略阳县| 原平市| 永定县| 绥滨县| 东台市| 嘉义市| 连平县| 云林县| 原阳县| 兴和县| 红安县| 溧水县| 长寿区| 罗平县| 巩义市| 宜宾县| 辽中县| 霍山县| 邵阳市| 绿春县| 石泉县| 隆德县| 德令哈市| 石林| 合水县| 囊谦县| 德阳市| 卢湾区| 张北县| 鄯善县| 泰安市| 定南县| 贵德县| 安远县| 荥经县| 历史| 青阳县| 西乡县|