背景
Read the fucking source code!--By 魯迅A picture is worth a thousand words.--By 高爾基
說明:
- Kernel版本:4.14
- ARM64處理器,Contex-A53,雙核
- 使用工具:Source Insight 3.5, Visio
1. 概述
行程切換:內核將CPU上正在運行的行程掛起,選擇下一個行程來運行,
ARM架構中,CPU上一次只能運行一個任務,內核需要為任務分配運行時間來進行調度,以便同時能處理多個任務請求,
如下圖所示:

當進行任務切換的時候,思考下兩個問題:
- 怎樣通過搶占來實作行程的切換?
- 當行程切換的時候,到底切換的什么,是怎么實作的?
這兩個問題,也是本文探討的主題了,
2. 搶占
2.1 用戶搶占
2.1.1 搶占觸發點
- 可以觸發搶占的情況很多,比如行程的時間片耗盡、行程等待在某些資源上被喚醒時、行程優先級改變等,Linux內核是通過設定
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標志吧:

- 內核提供了
set_tsk_need_resched函式來將thread_info中flag欄位設定成TIF_NEED_RESCHED; - 設定了
TIF_NEED_RESCHED標志,表明需要發生搶占調度;
2.1.2 搶占執行點
用戶搶占:搶占執行發生在行程處于用戶態,
搶占的執行,最明顯的標志就是呼叫了schedule()函式,來完成任務的切換,
具體來說,在用戶態執行搶占在以下幾種情況:
- 例外處理后回傳到用戶態;
- 中斷處理后回傳到用戶態;
- 系統呼叫后回傳到用戶態;
如下圖:

- ARMv8有4個Exception Level,其中用戶程式運行在EL0,OS運行在EL1,Hypervisor運行在EL2,Secure monitor運行在EL3;
- 用戶程式在執行程序中,遇到例外或中斷后,將會跳到
ENTRY(vectors)向量表處開始執行; - 回傳用戶空間時進行標志位判斷,設定了
TIF_NEED_RESCHED則需要進行調度切換,沒有設定該標志,則檢查是否有收到信號,有信號未處理的話,還需要進行信號的處理操作;
2.2 內核搶占
Linux內核有三種內核搶占模型,先上圖:

- CONFIG_PREEMPT_NONE:不支持搶占,中斷退出后,需要等到低優先級任務主動讓出CPU才發生搶占切換;
- CONFIG_PREEMPT_VOLUNTARY:自愿搶占,代碼中增加搶占點,在中斷退出后遇到搶占點時進行搶占切換;
- CONFIG_PREEMPT:搶占,當中斷退出后,如果遇到了更高優先級的任務,立即進行任務搶占;
2.2.1 搶占觸發點
- 在內核中搶占觸發點,也是設定
struct thread_info的flag欄位,設定TIF_NEED_RESCHED表明需要請求重新調度, - 搶占觸發點的幾種情況,在用戶搶占中已經分析過,不管是用戶搶占還是內核搶占,觸發點都是一致的;
2.2.2 搶占執行點
內核搶占:搶占執行發生在行程處于內核態,

總體而言,內核搶占執行點可以歸屬于兩大類:
- 中斷執行完畢后進行搶占調度;
- 主動呼叫
preemp_enable或schedule等介面的地方進行搶占調度;
2.3 preempt_count
- Linux內核中使用
struct thread_info中的preempt_count欄位來控制搶占, preempt_count的低8位用于控制搶占,當大于0時表示不可搶占,等于0表示可搶占,preempt_enable()會將preempt_count值減1,并判斷是否需要進行調度,在條件滿足時進行切換;preempt_disable()會將preempt_count值加1;
此外,preemt_count欄位還用于判斷行程處于各類背景關系以及開關控制等,如圖:

3. 背景關系切換
- 行程背景關系:包含CPU的所有暫存器值、行程的運行狀態、堆疊中的內容等,相當于行程某一時刻的快照,包含了所有的軟硬體資訊;
- 行程切換時,完成的就是背景關系的切換,行程背景關系的資訊會保存在每個
struct task_struct結構體中,以便在切換時能完成恢復作業;
行程背景關系切換的入口就是__schedule(),分析也圍繞這函式展開,
3.1 __schedule()
__schedule()函式呼叫分析如下:

主要的邏輯:
- 根據CPU獲取運行佇列,進而得到運行佇列當前的
task,也就是切換前的prev; - 根據
prev的狀態進行處理,比如pending信號的處理等,如果該任務是一個worker執行緒還需要將其睡眠,并喚醒同CPU上的另一個worker執行緒; - 根據調度類來選擇需要切換過去的下一個
task,也就是next; context_switch完成行程的切換;
3.2 context_switch()
context_switch()的呼叫分析如下:

核心的邏輯有兩部分:
行程的地址空間切換:切換的時候要判斷切入的行程是否為內核執行緒,1)所有的用戶行程都共用一個內核地址空間,而擁有不同的用戶地址空間;2)內核執行緒本身沒有用戶地址空間,在行程在切換的程序中就需要對這些因素來考慮,涉及到頁表的切換,以及cache/tlb的重繪等操作,暫存器的切換:包括CPU的通用暫存器切換、浮點暫存器切換,以及ARM處理器相關的其他一些暫存器的切換;
行程的切換,帶來的開銷不僅是頁表切換和硬體背景關系的切換,還包含了Cache/TLB重繪后帶來的miss的開銷,在實際的開發中,也需要去評估新增行程帶來的調度開銷,

轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/137084.html
標籤:Linux
上一篇:Centos8下搭建私人開源網盤NextCloud步驟及使用(基于LAMP)
下一篇:MySQL主從配置
