v82.01 鴻蒙內核源碼分析 (協處理器篇) | CPU 的好幫手 | 百篇部落格分析 OpenHarmony 源碼

本篇關鍵詞:CP15MCRMRCASIDMMU

硬體架構相關篇為:

本篇很重要,對CP15協處理所有16個暫存器一一介紹,可能是全網介紹CP15最全面的一篇,鴻蒙內核的彙編部分(尤其開機啟動)中會使用,熟練掌握後看彙編程式碼將如虎添翼。

協處理器

協處理器 (co-processor) 顧名思義是協助主處理器完成工作,例如浮點、影像、音頻處理這一類外圍工作。角色相當於老闆的助理/秘書,咱皇上身邊的人,專幹些咱皇上又不好出面的臟活累活,您可別小看了這個角色,權利不大但能力大,是能通天的人,而且老闆越大,身邊這樣的人還不止一個。

arm 的協處理器設計中,最多可以支援 16 個協處理器,通常被命名為 cp0cp15,本篇主要說第16號協處理器 cp15

CP15

關於 cp15詳細介紹見於 << ARM體系架構參考手冊(ARMv7-A/R).pdf >>B3.17
cp15 一共有 1632位的暫存器,其編號為C0 ~ C15 ,用來控制cacheTCM和存儲器管理。cp15 暫存器都是複合功能暫存器,不同功能對應不同的記憶體實體,全由訪問指令的參數來決定,對於 armv7 架構而言,A 系列和 R 系列是統一設計的,A 系列帶有 MMU 相關的控制,而 R 系列帶有 MPU 相關控制,針對不同的功能需要做區分,同時又因為協處理器 cp15 只支援 16 個暫存器,而需要支援的功能較多,所以通過同一暫存器不同功能的方式來滿足需求。

mcr | mrc 指令

armv7 中對於協處理器的訪問,CP15的暫存器只能被MRCMCR(Move to Coprocessor from ARM Register )指令訪問。MCR表示將 arm 核心暫存器中的值的寫到 cp15 暫存器中,MRCcp15 暫存器中讀到 arm 核心暫存器中,大部分指令都需要在 PL1 以及更高的特權級下才能正常執行,這是因為 cp15 協處理器大多都涉及到系統和記憶體的設置,user 模式沒有操作許可權,user 模式僅能訪問 cp15 中有限的幾個暫存器比如:ISB、DSB、DMB、TPIDRURW、TPIDRURO 暫存器。

從 `cp**` 暫存器中讀到 `arm` 核心暫存器中
MRC<cond> <coproc>, <opc1>, <Rt>, <CRn>, <CRm>{, <opc2>}
  • cond : 指令後綴,表示條件執行,關於條件執行可以參考 arm狀態暫存器
  • coproc :協處理器的名稱,cp0~cp15 分別對應名稱 p0~p15
  • opc1 :對於 cp15 而言,這一個參數一般為0。
  • Rt :arm 的通用暫存器
  • CRn :與 arm 核心暫存器交換數據的核心暫存器名,c0~c15
  • CRm :需要額外操作的協處理器的暫存器名,c0~c15,針對多種功能的 cp15 暫存器,需要使用 CRm 和 opc2 來確定 CRn 對應哪個暫存器實體。
  • opc2 :可選,與 CRm搭配使用,同樣是決定多功能暫存器中指定實體。

啥玩意,太抽象沒看懂,後面直接上內核程式碼就懂了,先看16個暫存器的功能介紹表

c0 暫存器

c0 暫存器提供處理器和特徵識別 ,內核宏定義為,可參考圖理解

/*!
 * Identification registers (c0)	| c0 - 身份暫存器
 */
#define MIDR                CP15_REG(c0, 0, c0, 0)    /*! Main ID Register | 主ID暫存器 */
#define MPIDR               CP15_REG(c0, 0, c0, 5)    /*! Multiprocessor Affinity Register | 多處理器關聯暫存器給每個CPU制定一個邏輯地址*/
#define CCSIDR              CP15_REG(c0, 1, c0, 0)    /*! Cache Size ID Registers | 快取大小ID暫存器*/	
#define CLIDR               CP15_REG(c0, 1, c0, 1)    /*! Cache Level ID Register | 快取登記ID暫存器*/	
#define VPIDR               CP15_REG(c0, 4, c0, 0)    /*! Virtualization Processor ID Register | 虛擬化處理器ID暫存器*/	
#define VMPIDR              CP15_REG(c0, 4, c0, 5)    /*! Virtualization Multiprocessor ID Register | 虛擬化多處理器ID暫存器*/	

c1 暫存器

c1 為系統控制暫存器

/*!
 * System control registers (c1)	| c1 - 系統控制暫存器 各種控制位(可讀寫)
 */
#define SCTLR               CP15_REG(c1, 0, c0, 0)    /*! System Control Register | 系統控制暫存器*/	
#define ACTLR               CP15_REG(c1, 0, c0, 1)    /*! Auxiliary Control Register | 輔助控制暫存器*/	
#define CPACR               CP15_REG(c1, 0, c0, 2)    /*! Coprocessor Access Control Register | 協處理器訪問控制暫存器*/	

/// 讀取CP15的系統控制暫存器到 R0暫存器
STATIC INLINE UINT32 OsArmReadSctlr(VOID)
{
    UINT32 val;
    __asm__ volatile("mrc p15, 0, %0, c1,c0,0" : "=r"(val));
    return val;
}
/// R0暫存器寫入CP15的系統控制暫存器
STATIC INLINE VOID OsArmWriteSctlr(UINT32 val)
{
    __asm__ volatile("mcr p15, 0, %0, c1,c0,0" ::"r"(val));
    __asm__ volatile("isb" ::: "memory");
}

解讀

  • 從圖中找到 c1-0-c0-0行,後邊的備註是 SCTLR, System Control Register 系統控制暫存器,其操作模式是支援 Read/Write
  • %0表示 r0 暫存器,注意這個暫存器是CPU的暫存器,: “=r”(val) 意思向編譯器聲明,會修改R0暫存器的值,改之前提前打好招呼,都是紳士文明人。其實編譯器的功能是非常強大的,不僅僅是大家普遍認為的只是編譯程式碼的工具而已。OsArmReadSctlr的含義就是讀取CP15的系統控制暫存器到R0暫存器。
  • volatile的意思還告訴編譯器,不要去優化這段程式碼,原封不動的生成目標指令。
  • “isb” ::: “memory” 還是告訴編譯器記憶體的內容要被更改了,需要無效所有Cache,並訪問實際的內容,而不是Cache!
  • CRn | CRm | opc2 是一套組合拳,c7-0-c10-4 c7-0-c10-5 都表示不同的功能含義

c2、c3 暫存器

/*!
 * Memory protection and control registers (c2 & c3) | c2 - 傳說中的TTB暫存器,主要是給MMU使用 c3 - 域訪問控制位
 */
#define TTBR0               CP15_REG(c2, 0, c0, 0)    /*! Translation Table Base Register 0 | 轉換表基地址暫存器0*/	
#define TTBR1               CP15_REG(c2, 0, c0, 1)    /*! Translation Table Base Register 1 | 轉換表基地址暫存器1*/	
#define TTBCR               CP15_REG(c2, 0, c0, 2)    /*! Translation Table Base Control Register | 轉換表基地址控制暫存器*/	
#define DACR                CP15_REG(c3, 0, c0, 0)    /*! Domain Access Control Register | 域訪問控制暫存器*/	


看段程式碼

STATIC INLINE UINT32 OsArmReadTtbr0(VOID)
{
    UINT32 val;
    __asm__ volatile("mrc p15, 0, %0, c2,c0,0" : "=r"(val));
    return val;
}
STATIC INLINE VOID OsArmWriteTtbr0(UINT32 val)
{
    __asm__ volatile("mcr p15, 0, %0, c2,c0,0" ::"r"(val));
    __asm__ volatile("isb" ::: "memory");
}
STATIC INLINE UINT32 OsArmReadTtbr1(VOID)
{
    UINT32 val;
    __asm__ volatile("mrc p15, 0, %0, c2,c0,1" : "=r"(val));
    return val;
}
STATIC INLINE VOID OsArmWriteTtbr1(UINT32 val)
{
    __asm__ volatile("mcr p15, 0, %0, c2,c0,1" ::"r"(val));
    __asm__ volatile("isb" ::: "memory");
}

c2暫存器負責存頁表的基地址,即一級映射描述符表的基地址。還記得嗎?每個進程的頁表都是獨立的!c2值一變,當前使用的頁表就發生了變化,頁表變化意味著虛擬地址和物理地址的映射關係發生了變化。那麼什麼情況下會修改裡面的值呢?很容易想到只有在進程切換時發生的mmu上下文切換,直接看程式碼吧!

/// mmu 上下文切換
VOID LOS_ArchMmuContextSwitch(LosArchMmu *archMmu)
{
    UINT32 ttbr;
    UINT32 ttbcr = OsArmReadTtbcr();//讀取TTB暫存器的狀態值
    if (archMmu) {
        ttbr = MMU_TTBRx_FLAGS | (archMmu->physTtb);//進程TTB物理地址值
        /* enable TTBR0 */
        ttbcr &= ~MMU_DESCRIPTOR_TTBCR_PD0;//使能TTBR0
    } else {
        ttbr = 0;
        /* disable TTBR0 */
        ttbcr |= MMU_DESCRIPTOR_TTBCR_PD0;
    }
#ifdef LOSCFG_KERNEL_VM
    /* from armv7a arm B3.10.4, we should do synchronization changes of ASID and TTBR. */
    OsArmWriteContextidr(LOS_GetKVmSpace()->archMmu.asid);//這裡先把asid切到內核空間的ID
    ISB; //指令必須同步 ,清楚流水線中未執行指令
#endif
    OsArmWriteTtbr0(ttbr);//通過r0暫存器將進程頁面基址寫入TTB
    ISB; //指令必須同步
    OsArmWriteTtbcr(ttbcr);//寫入TTB狀態位
    ISB; //指令必須同步
#ifdef LOSCFG_KERNEL_VM
    if (archMmu) {
        OsArmWriteContextidr(archMmu->asid);//通過R0暫存器寫入進程標識符至C13暫存器
        ISB;
    }
#endif
}

至於具體內核哪些地方會觸發到 mmu的切換,可前往翻看 (進程切換篇)

c4 暫存器

c4 沒有用於任何 ARMv7 實現,這麼不待見4,難道原因跟中國人一樣覺得數字不吉利 ,但老師教的老外是不喜歡 13 啊 , 但c13確很重要

c5 c6 暫存器

c5和c6暫存器提供記憶體系統故障報告。此外,c6還提供了MPU區域暫存器。這一類暫存器在軟體排錯時可以提供非常大的幫助,比如通過 DFSR(數據狀態暫存器)、IFSR(指令狀態暫存器) 的 status bits 可以查到系統 abort 類型,內核中的缺頁異常就是通過該暫存器傳遞異常地址,從而分配頁面的。

/*!
 * Memory system fault registers (c5 & c6)	| c5 - 記憶體失效狀態 c6 - 記憶體失效地址
 */
#define DFSR                CP15_REG(c5, 0, c0, 0)    /*! Data Fault Status Register | 數據故障狀態暫存器 */			
#define IFSR                CP15_REG(c5, 0, c0, 1)    /*! Instruction Fault Status Register | 指令故障狀態暫存器*/	
#define DFAR                CP15_REG(c6, 0, c0, 0)    /*! Data Fault Address Register | 數據故障地址暫存器*/			
#define IFAR                CP15_REG(c6, 0, c0, 2)    /*! Instruction Fault Address Register | 指令錯誤地址暫存器*/	

c7 暫存器

c7暫存器提供高速快取維護操作和記憶體屏障操作。

c8 暫存器

c8 暫存器提供 TLB 維護功能

TLB是硬體上的一個cache,因為頁表一般都很大,並且存放在記憶體中,所以處理器引入MMU後,讀取指令、數據需要訪問兩次記憶體:首先通過查詢頁表得到物理地址,然後訪問該物理地址讀取指令、數據。為了減少因為MMU導致的處理器性能下降,引入了TLB,可翻譯為「地址轉換後援緩衝器」,也可簡稱為「快表」。簡單地說,TLB就是頁表的Cache,其中存儲了當前最可能被訪問到的頁表項,其內容是部分頁表項的一個副本。只有在TLB無法完成地址翻譯任務時,才會到記憶體中查詢頁表,這樣就減少了頁表查詢導致的處理器性能下降。詳細看

照著圖說吧,步驟是這樣的。

  • 圖中的page table的基地址就是上面TTB暫存器值,整個page table非常大,有多大接下來會講,所以只能存在記憶體里,TTB中只是存一個開始位置而已。
  • 虛擬地址是程式的地址邏輯地址,也就是餵給CPU的地址,必須經過MMU的轉換後變成物理記憶體才能取到真正的指令和數據。
  • TLBpage table的迷你版,MMU先從TLB里找物理頁,找不到了再從page table中找,從page table中找到後會放入TLB中,注意這一步非常非常的關鍵。因為page table是屬於進程的會有很多個,而TLB只有一個,不放入就會出現多個進程的page table都映射到了同一個物理頁框而不自知。一個物理頁同時只能被一個page table所映射。但除了TLB的唯一性外,要做到不錯亂還需要了一個東西,就是進程在映射層面的唯一標識符 – asid,具體可前往翻看 (進程切換篇) 有詳細說明。

c9 暫存器

c9 暫存器主要為 cache、分之預測 和 tcm 保留功能,這些保留功能由處理的實現決定

c10 暫存器

c10 暫存器主要提供記憶體重映射和 TLB 控制功能

c11 暫存器

c11 暫存器主要提供 TCM 和 DMA 的保留功能,這些保留功能由處理的實現決定

c12 暫存器

c12 安全擴展暫存器

c13 暫存器

c13 暫存器提供進程、上下文以及執行緒ID處理功能

/*!
 * Process, context and thread ID registers (c13) | c13 - 進程標識符
 */
#define FCSEIDR             CP15_REG(c13, 0, c0, 0)    /*! FCSE Process ID Register | FCSE(Fast Context Switch Extension,快速上下文切換)進程ID暫存器 位於CPU和MMU之間*/
#define CONTEXTIDR          CP15_REG(c13, 0, c0, 1)    /*! Context ID Register | 上下文ID暫存器*/	
#define TPIDRURW            CP15_REG(c13, 0, c0, 2)    /*! User Read/Write Thread ID Register | 用戶讀/寫執行緒ID暫存器*/	
#define TPIDRURO            CP15_REG(c13, 0, c0, 3)    /*! User Read-Only Thread ID Register | 用戶只讀寫執行緒ID暫存器*/	
#define TPIDRPRW            CP15_REG(c13, 0, c0, 4)    /*! PL1 only Thread ID Register | 僅PL1執行緒ID暫存器*/

c14 暫存器

c14 暫存器提供通用定時器擴展的保留功能

c15 暫存器

ARMv7 保留 c15 用於實現定義的目的,並且不對 c15 編碼的使用施加任何限制。
意思就是可以將他當通用暫存器來使用 語法: c15 0-7 c0-c15 0-7

百文說內核 | 抓住主脈絡

  • 百文相當於摸出內核的肌肉和器官系統,讓人開始豐滿有立體感,因是直接從注釋源碼起步,在加註釋過程中,每每有心得處就整理,慢慢形成了以下文章。內容立足源碼,常以生活場景打比方儘可能多的將內核知識點置入某種場景,具有畫面感,容易理解記憶。說別人能聽得懂的話很重要! 百篇部落格絕不是百度教條式的在說一堆詰屈聱牙的概念,那沒什麼意思。更希望讓內核變得栩栩如生,倍感親切。
  • 與程式碼需不斷debug一樣,文章內容會存在不少錯漏之處,請多包涵,但會反覆修正,持續更新,v**.xx 代表文章序號和修改的次數,精雕細琢,言簡意賅,力求打造精品內容。
  • 百文在 < 鴻蒙研究站 | 開源中國 | 部落格園 | 51cto | csdn | 知乎 | 掘金 > 站點發布,鴻蒙研究站 | weharmonyos 中回復 百文 可方便閱讀。

按功能模組:

基礎知識 進程管理 任務管理 記憶體管理
雙向鏈表
內核概念
源碼結構
地址空間
計時單位
宏的使用
鉤子框架
點陣圖管理
POSIX
main函數
調度故事
進程式控制制塊
進程空間
線性區
紅黑樹
進程管理
Fork進程
進程回收
Shell編輯
Shell解析
任務控制塊
並發並行
就緒隊列
調度機制
任務管理
用棧方式
軟體定時器
控制台
遠程登錄
協議棧
記憶體規則
物理記憶體
虛擬記憶體
虛實映射
頁表管理
靜態分配
TLFS演算法
記憶體池管理
原子操作
圓整對齊
通訊機制 文件系統 硬體架構 內核彙編
通訊總覽
自旋鎖
互斥鎖
快鎖使用
快鎖實現
讀寫鎖
訊號量
事件機制
訊號生產
訊號消費
消息隊列
消息封裝
消息映射
共享記憶體
文件概念
文件故事
索引節點
VFS
文件句柄
根文件系統
掛載機制
管道文件
文件映射
寫時拷貝
晶片模式
ARM架構
指令集
協處理器
工作模式
暫存器
多核管理
中斷概念
中斷管理
編碼方式
彙編基礎
彙編傳參
可變參數
開機啟動
進程切換
任務切換
中斷切換
異常接管
缺頁中斷
編譯運行 調測工具
編譯過程
編譯構建
GN語法
忍者無敵
ELF格式
ELF解析
靜態鏈接
重定位
動態鏈接
進程映像
應用啟動
系統調用
VDSO
模組監控
日誌跟蹤
系統安全
測試用例

百萬注源碼 | 處處扣細節

  • 百萬漢字註解內核目的是要看清楚其毛細血管,細胞結構,等於在拿放大鏡看內核。內核並不神秘,帶著問題去源碼中找答案是很容易上癮的,你會發現很多文章對一些問題的解讀是錯誤的,或者說不深刻難以自圓其說,你會慢慢形成自己新的解讀,而新的解讀又會碰到新的問題,如此層層遞進,滾滾向前,拿著放大鏡根本不願意放手。

  • < gitee | github | coding | gitcode > 四大碼倉推送 | 同步官方源碼,鴻蒙研究站 | weharmonyos 中回復 百萬 可方便閱讀。

據說喜歡點贊分享的,後來都成了大神。😃