Read the fucking source code!
--By 魯迅A picture is worth a thousand words.
--By 高爾基說明:架構
進程切換:內核將CPU上正在運行的進程掛起,選擇下一個進程來運行。
ARM架構中,CPU上一次只能運行一個任務,內核須要爲任務分配運行時間來進行調度,以便同時能處理多個任務請求。
以下圖所示:函數
當進行任務切換的時候,思考下兩個問題:工具
這兩個問題,也是本文探討的主題了。this
TIF_NEED_RESCHED
標誌來對進程進行標記的,設置該位則代表須要進行調度切換,而實際的切換將在搶佔執行點來完成。不看代碼來說結論,那都是耍流氓。先看一下兩個關鍵結構體:struct task_struct
和struct thread_info
。咱們在前邊的文章中也講過struct task_struct
用於描述任務,該結構體的首個字段放置的正是struct thread_info
,struct thread_info
結構體中flag
字段就可用於設置TIF_NEED_RESCHED
,此外該結構體中的preempt_count
也與搶佔相關。線程
struct task_struct { #ifdef CONFIG_THREAD_INFO_IN_TASK /* * For reasons of header soup (see current_thread_info()), this * must be the first element of task_struct. */ struct thread_info thread_info; #endif ... } /* * low level task data that entry.S needs immediate access to. */ struct thread_info { unsigned long flags; /* low level flags */ mm_segment_t addr_limit; /* address limit */ #ifdef CONFIG_ARM64_SW_TTBR0_PAN u64 ttbr0; /* saved TTBR0_EL1 */ #endif int preempt_count; /* 0 => preemptable, <0 => bug */ }; #include <asm/current.h> #define current_thread_info() ((struct thread_info *)current) //經過該宏能夠直接獲取thread_info的信息 #endif
看看具體哪些函數過程當中,設置了TIF_NEED_RESCHED
標誌吧:3d
set_tsk_need_resched
函數來將thread_info
中flag
字段設置成TIF_NEED_RESCHED
;TIF_NEED_RESCHED
標誌,代表須要發生搶佔調度;用戶搶佔:搶佔執行發生在進程處於用戶態。
搶佔的執行,最明顯的標誌就是調用了schedule()
函數,來完成任務的切換。
具體來講,在用戶態執行搶佔在如下幾種狀況:code
以下圖:blog
ENTRY(vectors)
向量表處開始執行;TIF_NEED_RESCHED
則須要進行調度切換,沒有設置該標誌,則檢查是否有收到信號,有信號未處理的話,還須要進行信號的處理操做;Linux內核有三種內核搶佔模型,先上圖:接口
struct thread_info
的flag
字段,設置TIF_NEED_RESCHED
代表須要請求從新調度。內核搶佔:搶佔執行發生在進程處於內核態。隊列
整體而言,內核搶佔執行點能夠歸屬於兩大類:
preemp_enable
或schedule
等接口的地方進行搶佔調度;struct thread_info
中的preempt_count
字段來控制搶佔。preempt_count
的低8位用於控制搶佔,當大於0時表示不可搶佔,等於0表示可搶佔。preempt_enable()
會將preempt_count
值減1,並判斷是否須要進行調度,在條件知足時進行切換;preempt_disable()
會將preempt_count
值加1;此外,preemt_count
字段還用於判斷進程處於各種上下文以及開關控制等,如圖:
struct task_struct
結構體中,以便在切換時能完成恢復工做;進程上下文切換的入口就是__schedule()
,分析也圍繞這函數展開。
__schedule()
__schedule()
函數調用分析以下:
主要的邏輯:
task
,也就是切換前的prev
;prev
的狀態進行處理,好比pending
信號的處理等,若是該任務是一個worker線程
還須要將其睡眠,並喚醒同CPU上的另外一個worker線程
;task
,也就是next
;context_switch
完成進程的切換;context_switch()
context_switch()
的調用分析以下:
核心的邏輯有兩部分:
進程的地址空間切換
:切換的時候要判斷切入的進程是否爲內核線程,1)全部的用戶進程都共用一個內核地址空間,而擁有不一樣的用戶地址空間;2)內核線程自己沒有用戶地址空間。在進程在切換的過程當中就須要對這些因素來考慮,涉及到頁表的切換,以及cache/tlb
的刷新等操做。寄存器的切換
:包括CPU的通用寄存器切換、浮點寄存器切換,以及ARM處理器相關的其餘一些寄存器的切換;進程的切換,帶來的開銷不只是頁表切換和硬件上下文的切換,還包含了Cache/TLB
刷新後帶來的miss
的開銷。在實際的開發中,也須要去評估新增進程帶來的調度開銷。