Read the fucking source code!
--By 魯迅A picture is worth a thousand words.
--By 高爾基說明:
進程切換:內(nèi)核將CPU上正在運行的進程掛起,選擇下一個進程來運行。
ARM架構(gòu)中,CPU上一次只能運行一個任務(wù),內(nèi)核需要為任務(wù)分配運行時間來進行調(diào)度,以便同時能處理多個任務(wù)請求。
如下圖所示:
當進行任務(wù)切換的時候,思考下兩個問題:
這兩個問題,也是本文探討的主題了。
TIF_NEED_RESCHED
標志來對進程進行標記的,設(shè)置該位則表明需要進行調(diào)度切換,而實際的切換將在搶占執(zhí)行點來完成。不看代碼來講結(jié)論,那都是耍流氓。先看一下兩個關(guān)鍵結(jié)構(gòu)體:struct task_struct
和struct thread_info
。我們在前邊的文章中也講過struct task_struct
用于描述任務(wù),該結(jié)構(gòu)體的首個字段放置的正是struct thread_info
,struct thread_info
結(jié)構(gòu)體中flag
字段就可用于設(shè)置TIF_NEED_RESCHED
,此外該結(jié)構(gòu)體中的preempt_count
也與搶占相關(guān)。
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_infothread_info;#endif ...}/* * low level task data that entry.S needs immediate access to. */struct thread_info {unsigned longflags;/* low level flags */mm_segment_taddr_limit;/* address limit */#ifdef CONFIG_ARM64_SW_TTBR0_PANu64ttbr0;/* saved TTBR0_EL1 */#endifintpreempt_count;/* 0 => preemptable, <0 => bug */};#include <asm/current.h>#define current_thread_info() ((struct thread_info *)current) //通過該宏可以直接獲取thread_info的信息#endif
看看具體哪些函數(shù)過程中,設(shè)置了TIF_NEED_RESCHED
標志吧:
set_tsk_need_resched
函數(shù)來將thread_info
中flag
字段設(shè)置成TIF_NEED_RESCHED
;TIF_NEED_RESCHED
標志,表明需要發(fā)生搶占調(diào)度;用戶搶占:搶占執(zhí)行發(fā)生在進程處于用戶態(tài)。
搶占的執(zhí)行,最明顯的標志就是調(diào)用了schedule()
函數(shù),來完成任務(wù)的切換。
具體來說,在用戶態(tài)執(zhí)行搶占在以下幾種情況:
如下圖:
ENTRY(vectors)
向量表處開始執(zhí)行;TIF_NEED_RESCHED
則需要進行調(diào)度切換,沒有設(shè)置該標志,則檢查是否有收到信號,有信號未處理的話,還需要進行信號的處理操作;Linux內(nèi)核有三種內(nèi)核搶占模型,先上圖:
struct thread_info
的flag
字段,設(shè)置TIF_NEED_RESCHED
表明需要請求重新調(diào)度。內(nèi)核搶占:搶占執(zhí)行發(fā)生在進程處于內(nèi)核態(tài)。
總體而言,內(nèi)核搶占執(zhí)行點可以歸屬于兩大類:
preemp_enable
或schedule
等接口的地方進行搶占調(diào)度;struct thread_info
中的preempt_count
字段來控制搶占。preempt_count
的低8位用于控制搶占,當大于0時表示不可搶占,等于0表示可搶占。preempt_enable()
會將preempt_count
值減1,并判斷是否需要進行調(diào)度,在條件滿足時進行切換;preempt_disable()
會將preempt_count
值加1;此外,preemt_count
字段還用于判斷進程處于各類上下文以及開關(guān)控制等,如圖:
struct task_struct
結(jié)構(gòu)體中,以便在切換時能完成恢復(fù)工作;進程上下文切換的入口就是__schedule()
,分析也圍繞這函數(shù)展開。
__schedule()
__schedule()
函數(shù)調(diào)用分析如下:
主要的邏輯:
task
,也就是切換前的prev
;prev
的狀態(tài)進行處理,比如pending
信號的處理等,如果該任務(wù)是一個worker線程
還需要將其睡眠,并喚醒同CPU上的另一個worker線程
;task
,也就是next
;context_switch
完成進程的切換;context_switch()
context_switch()
的調(diào)用分析如下:
核心的邏輯有兩部分:
進程的地址空間切換
:切換的時候要判斷切入的進程是否為內(nèi)核線程,1)所有的用戶進程都共用一個內(nèi)核地址空間,而擁有不同的用戶地址空間;2)內(nèi)核線程本身沒有用戶地址空間。在進程在切換的過程中就需要對這些因素來考慮,涉及到頁表的切換,以及cache/tlb
的刷新等操作。寄存器的切換
:包括CPU的通用寄存器切換、浮點寄存器切換,以及ARM處理器相關(guān)的其他一些寄存器的切換;進程的切換,帶來的開銷不僅是頁表切換和硬件上下文的切換,還包含了Cache/TLB
刷新后帶來的miss
的開銷。在實際的開發(fā)中,也需要去評估新增進程帶來的調(diào)度開銷。
聯(lián)系客服