自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

深入理解Linux內(nèi)核之內(nèi)核搶占

系統(tǒng) Linux
我們或許經(jīng)常聽說過內(nèi)核搶占,可是我們是否真正理解它呢?內(nèi)核搶占和搶占式內(nèi)核究竟有什么關(guān)系呢?搶占計(jì)數(shù)器究竟干什么用?... 本文我們就來好好討論下,關(guān)于內(nèi)核搶占的一些技術(shù)細(xì)節(jié),力求讓大家理解內(nèi)核搶占。

[[400324]]

本文轉(zhuǎn)載自微信公眾號(hào)「Linux內(nèi)核遠(yuǎn)航者」,作者Linux內(nèi)核遠(yuǎn)航者。轉(zhuǎn)載本文請聯(lián)系Linux內(nèi)核遠(yuǎn)航者公眾號(hào)。

1.開場白

環(huán)境:

處理器架構(gòu):arm64

內(nèi)核源碼:linux-5.11

ubuntu版本:20.04.1

代碼閱讀工具:vim+ctags+cscope

我們或許經(jīng)常聽說過內(nèi)核搶占,可是我們是否真正理解它呢?內(nèi)核搶占和搶占式內(nèi)核究竟有什么關(guān)系呢?搶占計(jì)數(shù)器究竟干什么用?... 本文我們就來好好討論下,關(guān)于內(nèi)核搶占的一些技術(shù)細(xì)節(jié),力求讓大家理解內(nèi)核搶占。

注:本文主要關(guān)注CFS調(diào)度類。

2.內(nèi)核搶占和搶占式內(nèi)核

我們經(jīng)常使用uname -a命令能看到“PREEMPT”的字樣,沒錯(cuò),我們使用的是搶占式內(nèi)核。

  1. # uname -a 
  2. Linux (none) 5.11.0-g08a3831f3ae1 #1 SMP PREEMPT Fri Apr 30 17:41:53 CST 2021 aarch64 GNU/Linux 

那什么是搶占式內(nèi)核呢? 實(shí)際上,支持內(nèi)核搶占的內(nèi)核叫做搶占式內(nèi)核,不支持內(nèi)核搶占的內(nèi)核叫做不可搶占式內(nèi)核。那么問題又來了,什么是內(nèi)核搶占呢?我們都知道,拿周期性的tick來說:對(duì)于用戶任務(wù),當(dāng)每個(gè)時(shí)鐘中斷到來后都會(huì)檢查它的實(shí)際運(yùn)行時(shí)間是否超過理想運(yùn)行時(shí)間,或者運(yùn)行隊(duì)列中有沒有優(yōu)先級(jí)更高的進(jìn)程,一般如果滿足其中一個(gè)條件就會(huì)設(shè)置重新調(diào)度標(biāo)志,然后在中斷返回用戶態(tài)的前夕發(fā)生調(diào)度,這是所謂的用戶任務(wù)搶占。

但是如果處于一個(gè)內(nèi)核態(tài)的任務(wù)正在運(yùn)行,這個(gè)時(shí)候發(fā)生中斷喚醒了一個(gè)高優(yōu)先級(jí)的任務(wù),那么這個(gè)被喚醒的任務(wù)能否被調(diào)度執(zhí)行呢?這個(gè)時(shí)候就會(huì)分兩種情況分析,如果是搶占式內(nèi)核那么高優(yōu)先級(jí)任務(wù)就有可能搶占當(dāng)前任務(wù)而調(diào)度執(zhí)行(之所有是有可能是因?yàn)閮烧咛摂M運(yùn)行時(shí)間差值要大于搶占粒度才允許搶占),如果是不可搶占式內(nèi)核那么不允許搶占,除非當(dāng)前進(jìn)程執(zhí)行完或者主動(dòng)發(fā)生調(diào)度高優(yōu)先級(jí)進(jìn)程該有機(jī)會(huì)被調(diào)度。

也就是說,支持內(nèi)核搶占的內(nèi)核不僅允許在用戶態(tài)的任務(wù)可以被搶占,處在內(nèi)核態(tài)的任務(wù)也允許被搶占(請注意這里說的是內(nèi)核態(tài),因?yàn)橛脩艨臻g任務(wù)可以通過系統(tǒng)調(diào)用等進(jìn)入內(nèi)核態(tài)),這樣對(duì)于交互性或者低延遲的應(yīng)用場景很友好,如手持設(shè)備和桌面應(yīng)用,響應(yīng)會(huì)很快。而對(duì)于服務(wù)器來說,它就對(duì)吞吐量要求較高,希望獲得更多的cpu時(shí)間,而交互性或者低延遲都是次要的,所以被設(shè)計(jì)成不可搶占式內(nèi)核。

下圖給出非搶占式內(nèi)核調(diào)度情況:

下圖給出搶占式內(nèi)核調(diào)度情況:

對(duì)比兩個(gè)圖可以發(fā)現(xiàn):采用搶占式內(nèi)核調(diào)度的情況下,在中斷中喚醒一個(gè)高優(yōu)先級(jí)任務(wù)能夠得到很好的響應(yīng)。

關(guān)于搶占式內(nèi)核還是不可搶占式內(nèi)核的選擇在源碼的kernel/Kconfig.preempt有所描述:

  1. config PREEMPT_NONE 
  2.         bool "No Forced Preemption (Server)" 
  3.         help 
  4.         ¦ This is the traditional Linux preemption model, geared towards 
  5.         ¦ throughput. It will still provide good latencies most of the 
  6.         ¦ time, but there are no guarantees and occasional longer delays 
  7.         ¦ are possible. 
  8.  
  9.         ¦ Select this option if you are building a kernel for a server or 
  10.         ¦ scientific/computation system, or if you want to maximize the 
  11.         ¦ raw processing power of the kernel, irrespective of scheduling 
  12.         ¦ latencies. 
  13.  
  14. config PREEMPT 
  15.         bool "Preemptible Kernel (Low-Latency Desktop)" 
  16.         depends on !ARCH_NO_PREEMPT 
  17.         select PREEMPTION 
  18.         select UNINLINE_SPIN_UNLOCK if !ARCH_INLINE_SPIN_UNLOCK 
  19.         select PREEMPT_DYNAMIC if HAVE_PREEMPT_DYNAMIC 
  20.         help 
  21.         ¦ This option reduces the latency of the kernel by making 
  22.         ¦ all kernel code (that is not executing in a critical section
  23.         ¦ preemptible.  This allows reaction to interactive events by 
  24.         ¦ permitting a low priority process to be preempted involuntarily 
  25.         ¦ even if it is in kernel mode executing a system call and would 
  26.         ¦ otherwise not be about to reach a natural preemption point. 
  27.         ¦ This allows applications to run more 'smoothly' even when the 
  28.         ¦ system is under loadat the cost of slightly lower throughput 
  29.         ¦ and a slight runtime overhead to kernel code. 
  30.  
  31.         ¦ Select this if you are building a kernel for a desktop or 
  32.         ¦ embedded system with latency requirements in the milliseconds 
  33.         ¦ range. 

上面列舉了兩個(gè)編譯選項(xiàng)一個(gè)是支持內(nèi)核搶占一個(gè)是不支持內(nèi)核搶占,其實(shí)還有PREEMPT_VOLUNTARY和PREEMPT_RT,前者會(huì)顯式增加一些搶占點(diǎn),后者用于支持實(shí)時(shí)性 。

3.重新調(diào)度標(biāo)志和搶占計(jì)數(shù)器

內(nèi)核有些路徑是不允許調(diào)度的,如原子上下文,那么這個(gè)時(shí)候如果喚醒一個(gè)高優(yōu)先級(jí)的任務(wù)或者tick的時(shí)候檢查可重新調(diào)度條件滿足,那么高優(yōu)先級(jí)的任務(wù)將不能馬上得到執(zhí)行,但是我又要標(biāo)識(shí)一下需要重新調(diào)度,那么就需要設(shè)置重新調(diào)度標(biāo)志,當(dāng)返回到可調(diào)度上下文的時(shí)候(如開搶占),這個(gè)時(shí)候就會(huì)檢查是否設(shè)置了這個(gè)標(biāo)志來決定是否調(diào)用調(diào)度器來選擇下一個(gè)任務(wù)來運(yùn)行。

標(biāo)識(shí)重新調(diào)度是設(shè)置:

  1. //當(dāng)前任務(wù)的task_struct的thread_info的flag 
  2. stsk->thread_info->flags設(shè)置TIF_NEED_RESCHED標(biāo)志 
  3. #define TIF_NEED_RESCHED        1       /* rescheduling necessary */ 

內(nèi)核的某些路徑上設(shè)置了這個(gè)標(biāo)志之后,將在最近的調(diào)度點(diǎn)發(fā)生調(diào)度(可能是最近開啟搶占的時(shí)候,也可能是最近中斷異常返回的時(shí)候)。

當(dāng)前任務(wù)被設(shè)置了重新調(diào)度標(biāo)志,只是表明不久的將來會(huì)發(fā)生調(diào)度,并不是馬上發(fā)生調(diào)度,對(duì)于用戶任務(wù)來說就是中斷異常返回用戶態(tài)的前夕發(fā)生調(diào)度,而對(duì)于處于內(nèi)核態(tài)的任務(wù)來說,想要在內(nèi)核態(tài)搶占當(dāng)前進(jìn)程,僅僅置位重新調(diào)度標(biāo)志還不行,還需要判斷當(dāng)前進(jìn)程的搶占計(jì)數(shù)器是否為0。

所有對(duì)于處于內(nèi)核態(tài)的任務(wù)來說,搶占計(jì)數(shù)器對(duì)于重新調(diào)度至關(guān)重要,只要搶占計(jì)數(shù)器不為0,無論被喚醒的任務(wù)在緊急都不能獲得調(diào)度器,我們來看看這個(gè)搶占計(jì)數(shù)器:

  1. tsk->thread_info->preempt.count  

我們來看下對(duì)于arm64架構(gòu),搶占計(jì)數(shù)器的定義:

  1. 24 struct thread_info { 
  2. 25         unsigned long           flags;          /* low level flags */ 
  3.  
  4. 29         union { 
  5. 30                 u64             preempt_count;  /* 0 => preemptible, <0 => bug */ 
  6. 31                 struct { 
  7. 32 #ifdef CONFIG_CPU_BIG_ENDIAN 
  8. 33                         u32     need_resched; 
  9. 34                         u32     count
  10. 35 #else 
  11. 36                         u32     count
  12. 37                         u32     need_resched; 
  13. 38 #endif 
  14. 39                 } preempt; 
  15. 40         }; 
  16. 45 }; 

可以發(fā)現(xiàn)它是一個(gè)共用體,內(nèi)核某些路徑使用preempt_count,有的是preempt,為何會(huì)使用這么奇怪的定義呢?因?yàn)橐粋€(gè)成員可以表示兩種狀態(tài):重新調(diào)度標(biāo)志和搶占計(jì)數(shù)器的數(shù)值

當(dāng)需要重新調(diào)度的時(shí)候會(huì)置位flags的TIF_NEED_RESCHED標(biāo)志,與此同時(shí)會(huì)將preempt.need_resched清零。當(dāng)檢查thread_info 的preempt_count==0成立時(shí),說明搶占計(jì)數(shù)器的數(shù)值為0且flags的TIF_NEED_RESCHED標(biāo)志被置位,這個(gè)時(shí)候可以進(jìn)程重新調(diào)度(如中斷返回內(nèi)核態(tài)前夕的檢查)。

下面看下如何設(shè)置重新調(diào)度標(biāo)志:

  1. resched_curr   //kernel/sched/core.c 
  2.  613         if (cpu == smp_processor_id()) {    
  3.  614                 set_tsk_need_resched(curr); 
  4.  615                 set_preempt_need_resched(); 
  5.  616                 return;                     
  6.  617         }     
  7.  
  8.  
  9. 29 static inline void set_preempt_need_resched(void)   //arch/arm64/include/asm/preempt.h 
  10. 30 { 
  11. 31         current_thread_info()->preempt.need_resched = 0; 
  12. 32 } 
  13.                                

當(dāng)內(nèi)核的某個(gè)路徑設(shè)置重新調(diào)度標(biāo)志(如時(shí)鐘中斷tick時(shí)),會(huì)調(diào)用到resched_curr 來設(shè)置重新調(diào)度標(biāo)志:可以看到除了設(shè)置任務(wù)的flags的TIF_NEED_RESCHED標(biāo)志外,還設(shè)置了preempt.need_resched為0。

如何清除重新調(diào)度標(biāo)志:

  1. kernel/sched/core.c 
  2. __schedule    //主動(dòng)調(diào)度或搶占式調(diào)度 都會(huì)調(diào)用到這 
  3.  5046         clear_tsk_need_resched(prev);  
  4.  5047         clear_preempt_need_resched();  
  5.  
  6. //arch/arm64/include/asm/preempt.h 
  7. 34 static inline void clear_preempt_need_resched(void)       
  8. 35 {                 
  9. 36         current_thread_info()->preempt.need_resched = 1; 
  10. 37 } 

可以看到在主調(diào)度器中,除了調(diào)用clear_tsk_need_resched來清除任務(wù)的flags的TIF_NEED_RESCHED標(biāo)志外,會(huì)調(diào)用clear_preempt_need_resched來設(shè)置preempt.need_resched為1, 來清除重新調(diào)度。

下面為搶占計(jì)數(shù)器的各個(gè)域的表示:

0-7 表示搶占計(jì)數(shù) ,8-15表示軟中斷計(jì)數(shù), 16-19表示硬中斷計(jì)數(shù),20-23表示不可屏蔽中斷計(jì)數(shù)。當(dāng)進(jìn)入不同的上下文時(shí)會(huì)設(shè)置響應(yīng)的位域,表示在某個(gè)上下文中,當(dāng)某個(gè)位域被設(shè)置,搶占計(jì)數(shù)器不為0,任務(wù)在內(nèi)核態(tài)就不容許被搶占。

所以,搶占計(jì)數(shù)器有兩個(gè)作用:一個(gè)是標(biāo)識(shí)內(nèi)核路徑在某個(gè)原子上下文,一個(gè)是用來判斷是否允許任務(wù)在內(nèi)核態(tài)被搶占。

  1. include/linux/preempt.h 
  2.  
  3. 85 /*                                                                                     
  4. 86  * Macros to retrieve the current execution context:                                   
  5. 87  *                                                                                     
  6. 88  * in_nmi()             - We're in NMI context                                         
  7. 89  * in_hardirq()         - We're in hard IRQ context                                    
  8. 90  * in_serving_softirq() - We're in softirq context                                     
  9. 91  * in_task()            - We're in task context                                        
  10. 92  */                                                                                    
  11. 93 #define in_nmi()                (nmi_count())     //判斷是否在 不可屏蔽中斷上下文                                   
  12. 94 #define in_hardirq()            (hardirq_count())     //判斷是否在硬中斷上下文                                     
  13. 95 #define in_serving_softirq()    (softirq_count() & SOFTIRQ_OFFSET)        //判斷是否在軟中斷上下文                 
  14. 96 #define in_task()               (!(in_nmi() | in_hardirq() | in_serving_softirq()))  //判斷是否在進(jìn)程上下文  
  15. 97                                                                                        
  16.  
  17.  
  18.  98 /*                                                                               
  19.  99  * The following macros are deprecated and should not be used in new code:       
  20. 100  * in_irq()       - Obsolete version of in_hardirq()                             
  21. 101  * in_softirq()   - We have BH disabled, or are processing softirqs              
  22. 102  * in_interrupt() - We're in NMI,IRQ,SoftIRQ context or have BH disabled         
  23. 103  */                                                                              
  24. 104 #define in_irq()                (hardirq_count())    //判斷是否在硬中斷上下文                            
  25. 105 #define in_softirq()            (softirq_count())    //判斷是否在軟中斷上下文(關(guān)閉軟中斷或者在執(zhí)行軟中斷)                               
  26. 106 #define in_interrupt()          (irq_count())      //判斷是否在中斷上下文(包括硬中斷 軟中斷和不可屏蔽中斷)                                 
  27.  
  28.  
  29. //判斷是否在原子上下文(搶占計(jì)數(shù)器不為0) 
  30. 144 #define in_atomic()     (preempt_count() != 0) 

4.內(nèi)核搶占的調(diào)度時(shí)機(jī)

這里調(diào)度時(shí)機(jī)我將它細(xì)分為兩種情況,一種是不進(jìn)行調(diào)度的cheek點(diǎn),一種是真正的搶占點(diǎn)(即是調(diào)用主調(diào)度器進(jìn)行調(diào)度):

cheek點(diǎn)->

tick的時(shí)候 : 滿足條件(任務(wù)使用完理想運(yùn)行時(shí)間,運(yùn)行時(shí)間大于最小搶占粒度且運(yùn)行隊(duì)列有優(yōu)先級(jí)更高的任務(wù)) 時(shí),設(shè)置TIF_NEED_RESCHED標(biāo)志,最近的搶占點(diǎn)發(fā)生調(diào)度 。

喚醒搶占 : 滿足條件(喚醒的任務(wù)與當(dāng)前任務(wù)的虛擬運(yùn)行時(shí)間差值大于最小喚醒搶占粒度 ,喚醒的任務(wù)虛擬運(yùn)行時(shí)間更小) 時(shí), 設(shè)置TIF_NEED_RESCHED標(biāo)志,最近的搶占點(diǎn)發(fā)生調(diào)度。

搶占點(diǎn)->

中斷返回內(nèi)核態(tài) : 滿足條件(重新調(diào)度標(biāo)志置位且搶占計(jì)數(shù)器為0) 時(shí), 搶占式調(diào)度 。

打開搶占的時(shí)候 : (如開搶占,開中斷下半部,釋放自旋鎖) 滿足條件(重新調(diào)度標(biāo)志置位且搶占計(jì)數(shù)器為0)時(shí), 搶占式調(diào)度。

開啟軟中斷的時(shí)候 : 滿足條件(重新調(diào)度標(biāo)志置位且搶占計(jì)數(shù)器為0)時(shí), 搶占式調(diào)度。

中斷返回內(nèi)核態(tài)是常規(guī)的搶占點(diǎn),一般情況下即使沒有其他中斷產(chǎn)生,周期性的tick中斷也會(huì)發(fā)生, 滿足條件(重新調(diào)度標(biāo)志置位且搶占計(jì)數(shù)器為0)時(shí),當(dāng)前任務(wù)就會(huì)被搶占。而在一些會(huì)發(fā)生多任務(wù)竟態(tài)的臨界區(qū)中,我們需要關(guān)閉內(nèi)核搶占,有的直接調(diào)用preempt_disable, 有的是間接調(diào)用preempt_disable(如申請自旋鎖的臨界區(qū)), 有的則是關(guān)閉軟中斷等,這些都會(huì)導(dǎo)致?lián)屨加?jì)數(shù)器不為0,但是在這些臨界區(qū)中如果中斷喚醒了高優(yōu)先級(jí)的任務(wù),中斷返回內(nèi)核態(tài)的前夕是不能進(jìn)行調(diào)度的,所以在這些臨界區(qū)結(jié)束的時(shí)候會(huì)檢查調(diào)度條件是否滿足,如果滿足進(jìn)行搶占式調(diào)度,從而使得被喚醒的任務(wù)被及時(shí)的響應(yīng)。一般,一些cheek點(diǎn)設(shè)置了當(dāng)前任務(wù)的重新調(diào)度標(biāo)志之后,如果搶占計(jì)數(shù)器為0,會(huì)在最近的搶占點(diǎn)發(fā)生調(diào)度(就是上面所說的三種情況)。還有需要注意的是:關(guān)搶占的臨界區(qū)中,只是禁止了當(dāng)前任務(wù)所在cpu的內(nèi)核搶占,其他cpu依然可以進(jìn)行內(nèi)核搶占,如果這段臨界區(qū)有可能被其他cpu訪問到,可以直接使用自旋鎖來保護(hù)。

4.1 cheek點(diǎn)

1) 時(shí)鐘中斷tick時(shí):

  1. kernel/sched/core.c 
  2.  
  3. scheduler_tick 
  4. ->curr->sched_class->task_tick(rq, curr, 0) 
  5.  ->task_tick_fair 
  6.   ->entity_tick 
  7.    ->check_preempt_tick 
  8.     ->4374         if (delta_exec > ideal_runtime) {  //1.當(dāng)前任務(wù)的實(shí)際運(yùn)行時(shí)間大于理想運(yùn)行時(shí)間 
  9.     4375                 resched_curr(rq_of(cfs_rq));   //設(shè)置重新調(diào)度標(biāo)志 
  10.     4389         if (delta_exec < sysctl_sched_min_granularity) //當(dāng)前任務(wù)的實(shí)際運(yùn)行時(shí)間 小于 最小調(diào)度粒度嗎? 
  11.     4390                 return
  12.  
  13.     4398         if (delta > ideal_runtime)  //2.紅黑樹最左邊的任務(wù)的虛擬運(yùn)行時(shí)間和當(dāng)前任務(wù)的虛擬運(yùn)行時(shí)間的差值小于 理想運(yùn)行時(shí)間 
  14.     4399                 resched_curr(rq_of(cfs_rq)); //設(shè)置重新調(diào)度標(biāo)志 

每個(gè)時(shí)鐘tick到來時(shí),會(huì)調(diào)用scheduler_tick來檢查是否需要重新調(diào)度,以下兩個(gè)條件有一個(gè)發(fā)生都會(huì)設(shè)置重新調(diào)度標(biāo)志:

1.當(dāng)前任務(wù)的實(shí)際運(yùn)行時(shí)間大于理想運(yùn)行時(shí)間(保證任務(wù)在一個(gè)調(diào)度周期內(nèi)運(yùn)行時(shí)間不會(huì)超過理想運(yùn)行時(shí)間,防止“流氓”任務(wù)一直霸占cpu,通過周期性的時(shí)鐘中斷奪回處理器的使用權(quán))。

2.當(dāng)前任務(wù)的實(shí)際運(yùn)行時(shí)間大于最小調(diào)度粒度,且紅黑樹最左邊的任務(wù)的虛擬運(yùn)行時(shí)間和當(dāng)前任務(wù)的虛擬運(yùn)行時(shí)間的差值小于理想運(yùn)行時(shí)間(紅黑樹中的高優(yōu)先級(jí)的任務(wù)可以搶占當(dāng)前任務(wù))。

2)喚醒搶占:

在fork和正常的喚醒路徑上:

fork路徑:

  1. kernel/fork.c 
  2.  
  3. kernel_clone 
  4. ->wake_up_new_task(p) 
  5.  ->check_preempt_curr(rq, p, WF_FORK) 
  6.   ->rq->curr->sched_class->check_preempt_curr(rq, p, flags) 
  7.    ->check_preempt_wakeup    //kernel/sched/fair.c 
  8.     -> 6994         if (wakeup_preempt_entity(se, pse) == 1) {   //喚醒的任務(wù)的虛擬運(yùn)行時(shí)間和當(dāng)前任務(wù)的虛擬運(yùn)行時(shí)間差值小于最新喚醒搶占粒度轉(zhuǎn)換的虛擬運(yùn)行時(shí)間        
  9.      6995                 /*                                                   
  10.      6996                 ¦* Bias pick_next to pick the sched entity that is   
  11.      6997                 ¦* triggering this preemption.                       
  12.      6998                 ¦*/                                                  
  13.      6999                 if (!next_buddy_marked)                              
  14.      7000                         set_next_buddy(pse);                         
  15.      7001                 goto preempt;                                        
  16.      7002         }                                                            
  17.      7003                                                                      
  18.      7004         return;                                                      
  19.      7005                                                                      
  20.      7006 preempt:                                                             
  21.      7007         resched_curr(rq);      //設(shè)置重新調(diào)度標(biāo)志                                        

正常喚醒路徑:

  1. kernel/sched/core.c 
  2. wake_up_process 
  3. ->try_to_wake_up 
  4.  ->ttwu_queue 
  5.   ->ttwu_do_activate 
  6.    ->ttwu_do_wakeup 
  7.     ->check_preempt_curr(rq, p, wake_flags) 

無論是創(chuàng)建新任務(wù)或者是喚醒任務(wù)的時(shí)候,都有可能新喚醒的任務(wù)搶占當(dāng)前任務(wù),判斷條件如下:喚醒的任務(wù)的虛擬運(yùn)行時(shí)間和當(dāng)前任務(wù)的虛擬運(yùn)行時(shí)間差值小于最小喚醒搶占粒度轉(zhuǎn)換的虛擬運(yùn)行時(shí)間(喚醒的任務(wù)的虛擬運(yùn)行時(shí)間更小)。

4.2 搶占點(diǎn)

上面介紹的都是cheek點(diǎn),只是設(shè)置重新調(diào)度標(biāo)志,并沒有讓搶占的任務(wù)運(yùn)行,真正的搶占點(diǎn)是調(diào)用主調(diào)度器的時(shí)候。

1)中斷返回內(nèi)核態(tài)

當(dāng)開啟內(nèi)核搶占的時(shí)候,在中斷返回內(nèi)核態(tài)的前夕,會(huì)檢查當(dāng)前任務(wù)是否設(shè)置了重新調(diào)度標(biāo)志且搶占計(jì)數(shù)器為0,如果都滿足,進(jìn)行搶占式調(diào)度。

  1. arch/arm64/kernel/entry.S 
  2.  
  3. el1_irq 
  4. ->  671 #ifdef CONFIG_PREEMPTION                                                                    
  5.   672         ldr     x24, [tsk, #TSK_TI_PREEMPT]     // get preempt count                        
  6.   673 alternative_if ARM64_HAS_IRQ_PRIO_MASKING                                                   
  7.   674         /*                                                                                  
  8.   675         ¦* DA_F were cleared at start of handling. If anything is set in DAIF,              
  9.   676         ¦* we come back from an NMI, so skip preemption                                     
  10.   677         ¦*/                                                                                 
  11.   678         mrs     x0, daif                                                                    
  12.   679         orr     x24, x24, x0                                                                
  13.   680 alternative_else_nop_endif                                                                  
  14.   681         cbnz    x24, 1f                         // preempt count != 0 || NMI return path    
  15.   682         bl      arm64_preempt_schedule_irq      // irq en/disable is done inside            
  16.   683 1:                                                                                          
  17.   684 #endif                  

當(dāng)發(fā)生中斷時(shí),會(huì)執(zhí)行el1_irq來處理中斷,

672行 來讀取當(dāng)前任務(wù)的thread_info.preempt_count 681行 判斷thread_info.preempt_count是否為0,如果為0 則調(diào)用682 行的arm64_preempt_schedule_irq 進(jìn)行搶占式調(diào)度(上一節(jié)已經(jīng)分析過)。

下面看下?lián)屨际秸{(diào)度:

  1. arm64_preempt_schedule_irq 
  2. ->preempt_schedule_irq 
  3.  ->__schedule(true)  //調(diào)用主調(diào)度器進(jìn)行搶占式調(diào)度 

2)打開搶占的時(shí)候

開啟搶占:

  1. preempt_enable 
  2. ->if (unlikely(preempt_count_dec_and_test())) \   //搶占計(jì)數(shù)器減一  為0 
  3.         __preempt_schedule(); \                
  4.    ->preempt_schedule  //kernel/sched/core.c 
  5.     -> __schedule(true)  //調(diào)用主調(diào)度器進(jìn)行搶占式調(diào)度 

釋放自旋鎖:

  1. spin_unlock 
  2. ->raw_spin_unlock 
  3.  ->__raw_spin_unlock 
  4.   ->preempt_enable  //如上 

3) 開啟軟中斷

  1. local_bh_enable 
  2. ->__local_bh_enable_ip 
  3.  ->preempt_check_resched 
  4.   ->if (should_resched(0)) \      
  5.          __preempt_schedule(); 
  6.          ->preempt_schedule 
  7.     -> __schedule(true)  //調(diào)用主調(diào)度器進(jìn)行搶占式調(diào)度 

其實(shí),無論是主動(dòng)進(jìn)行調(diào)度還是搶占式調(diào)度都會(huì)調(diào)用__schedule,而__schedule是屬于關(guān)搶占上下文,在調(diào)度期間不允許被搶占。

5.不可搶占內(nèi)核的低延遲處理

下面我們來看下在沒有開啟內(nèi)核搶占的內(nèi)核中如何處理低延遲:

我們會(huì)看到在一些比較耗時(shí)的處理中如文件系統(tǒng)和內(nèi)存回收的一些路徑會(huì)調(diào)用cond_resched,它是干什么用呢:

下面是使用這個(gè)宏的例子:在內(nèi)存回收路徑中,會(huì)從不活躍的lru鏈表尾部取出一些頁面回收隔離到page_list中,最終會(huì)調(diào)用到shrink_page_list:

  1. mm/vmscan.c 
  2. shrink_page_list 
  3. -> 
  4.  1084         while (!list_empty(page_list)) { 
  5.   
  6.  ... 
  7.   
  8.  1091                 cond_resched(); 
  9.   
  10.  ... //回收處理 

可以看到對(duì)于page_list中的每一個(gè)被隔離的候選回收頁,在處理之前都會(huì)調(diào)用到cond_resched來主動(dòng)判斷是否需要重新調(diào)度。

下面我們來看下cond_resched這個(gè)宏實(shí)現(xiàn):

  1. include/linux/sched.h 
  2.  
  3. 1868 /* 
  4. 1869  * cond_resched() and cond_resched_lock(): latency reduction via 
  5. 1870  * explicit rescheduling in places that are safe. The return 
  6. 1871  * value indicates whether a reschedule was done in fact. 
  7. 1872  * cond_resched_lock() will drop the spinlock before scheduling, 
  8. 1873  */ 
  9. 1874 #ifndef CONFIG_PREEMPTION 
  10. 1875 extern int _cond_resched(void); 
  11. 1876 #else 
  12. 1877 static inline int _cond_resched(void) { return 0; } 
  13. 1878 #endif 
  14. 1879  
  15. 1880 #define cond_resched() ({                       \ 
  16. 1881         ___might_sleep(__FILE__, __LINE__, 0);  \ 
  17. 1882          _cond_resched();                    \ 
  18. 1883 }) 

我們可以很清楚的看到,搶占式內(nèi)核中(CONFIG_PREEMPTION=y)cond_resched宏的_cond_resched為空,并沒有主動(dòng)判斷重新調(diào)度的功能,只有非搶占式內(nèi)核才會(huì)調(diào)用_cond_resched來執(zhí)行主動(dòng)檢查可搶占性。

下面我們來看下_cond_resched:

  1. 6671 #ifndef CONFIG_PREEMPTION 
  2. 6672 int __sched _cond_resched(void) 
  3. 6673 { 
  4. 6674         if (should_resched(0)) {   //判斷搶占計(jì)數(shù)器是否為0 
  5. 6675                 preempt_schedule_common();  //進(jìn)行搶占式調(diào)度 
  6. 6676                 return 1; 
  7. 6677         } 
  8. 6678         rcu_all_qs(); 
  9. 6679         return 0; 
  10. 6680 } 
  11. 6681 EXPORT_SYMBOL(_cond_resched); 
  12. 6682 #endif 

會(huì)主動(dòng)檢查搶占計(jì)數(shù)器是否為0(實(shí)際上搶占計(jì)數(shù)器是否為0且當(dāng)前任務(wù)被設(shè)置了重新調(diào)度標(biāo)志),則進(jìn)行搶占式調(diào)度。

實(shí)際上,對(duì)于非搶占式內(nèi)核來說,在內(nèi)核的很多地方,特別是文件系統(tǒng)操作和內(nèi)存管理相關(guān)的一些耗時(shí)路徑中,都已經(jīng)被內(nèi)核開發(fā)者識(shí)別出來,并使用cond_resched來減小延遲(感興趣的小伙伴可以通過grep和wc -l命令來查看一下)。

6.自愿內(nèi)核搶占

內(nèi)核搶占模型有一種叫做自愿內(nèi)核搶占模型(CONFIG_PREEMPT_VOLUNTARY=y),可以使得內(nèi)核開發(fā)者在進(jìn)行耗時(shí)操作的時(shí)候,主動(dòng)檢查是否需要發(fā)生搶占式調(diào)度,這個(gè)和上一節(jié)差不多。

  1. config PREEMPT_VOLUNTARY 
  2.         bool "Voluntary Kernel Preemption (Desktop)" 
  3.         depends on !ARCH_NO_PREEMPT 
  4.         help 
  5.         ¦ This option reduces the latency of the kernel by adding more 
  6.         ¦ "explicit preemption points" to the kernel code. These new 
  7.         ¦ preemption points have been selected to reduce the maximum 
  8.         ¦ latency of rescheduling, providing faster application reactions, 
  9.         ¦ at the cost of slightly lower throughput. 
  10.  
  11.         ¦ This allows reaction to interactive events by allowing a 
  12.         ¦ low priority process to voluntarily preempt itself even if it 
  13.         ¦ is in kernel mode executing a system call. This allows 
  14.         ¦ applications to run more 'smoothly' even when the system is 
  15.         ¦ under load
  16.  
  17.         ¦ Select this if you are building a kernel for a desktop system. 

使用might_resched:

  1. 83 #ifdef CONFIG_PREEMPT_VOLUNTARY 
  2. 84 extern int _cond_resched(void); 
  3. 85 # define might_resched() _cond_resched() 
  4. 86 #else 
  5. 87 # define might_resched() do { } while (0) 
  6. 88 #endif 

發(fā)現(xiàn)只有CONFIG_PREEMPT_VOLUNTARY=y時(shí),might_resched才有效,否則為空。

可以驚奇的發(fā)現(xiàn),當(dāng)搜索might_resched在內(nèi)核中使用的使用的時(shí)候,并沒有看見有任何地方在使用,猜想是因?yàn)榇蠖鄶?shù)耗時(shí)的內(nèi)核路徑,都已經(jīng)使用cond_resched來進(jìn)行檢查是否具備調(diào)度時(shí)機(jī)。

7.總結(jié)

 

本文講解了內(nèi)核搶占的方方面面,非搶占式內(nèi)核主要用于服務(wù)器等對(duì)吞吐量要求較高的場景,而搶占式內(nèi)核主要用于嵌入式設(shè)備和桌面等對(duì)響應(yīng)要求較高的場景。內(nèi)核搶占的調(diào)度時(shí)機(jī)主要從check點(diǎn)和搶占點(diǎn)兩個(gè)角度去分析:check點(diǎn)是在合適的時(shí)機(jī)(如時(shí)鐘中斷tick時(shí)或者任務(wù)喚醒的時(shí)候)判斷是否需要重新調(diào)度任務(wù),如果需要設(shè)置重新調(diào)度標(biāo)志(need_resched),并沒有馬上進(jìn)行調(diào)度,然后在最近的搶占點(diǎn)發(fā)生調(diào)度;而搶占點(diǎn)是真正調(diào)用主調(diào)度器發(fā)生調(diào)度的時(shí)機(jī),一般會(huì)在中斷返回內(nèi)核態(tài)或者重新開啟內(nèi)核搶占等情況下發(fā)生。最后,我們又分析了非搶占式內(nèi)核如何進(jìn)行低延遲處理已經(jīng)自愿搶占式內(nèi)核如何實(shí)現(xiàn)自愿式搶占。

 

責(zé)任編輯:武曉燕 來源: Linux內(nèi)核遠(yuǎn)航者
相關(guān)推薦

2021-07-26 07:47:36

數(shù)據(jù)庫

2021-12-09 08:09:31

Linux內(nèi)核臟頁

2021-07-02 06:54:44

Linux內(nèi)核主調(diào)度器

2021-07-05 06:51:45

Linux內(nèi)核調(diào)度器

2021-07-20 08:02:41

Linux進(jìn)程睡眠

2020-09-28 08:44:17

Linux內(nèi)核

2025-04-28 02:00:00

2025-04-22 08:21:10

2017-08-16 16:20:01

Linux內(nèi)核態(tài)搶占用戶態(tài)搶占

2024-09-05 08:13:05

2015-12-28 11:41:57

JVM內(nèi)存區(qū)域內(nèi)存溢出

2023-02-10 08:11:43

Linux系統(tǒng)調(diào)用

2022-11-09 08:12:07

2021-08-31 10:32:11

LinuxPage Cache命令

2020-11-20 07:55:55

Linux內(nèi)核映射

2019-07-08 20:00:35

Linux內(nèi)核模塊

2013-06-20 10:25:56

2010-06-01 15:25:27

JavaCLASSPATH

2016-12-08 15:36:59

HashMap數(shù)據(jù)結(jié)構(gòu)hash函數(shù)

2020-07-21 08:26:08

SpringSecurity過濾器
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)