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

語言: CN / TW / HK

子曰:“居上不寬,為禮不敬,臨喪不哀,吾何以觀之哉?”《論語》:八佾篇

硬體架構相關篇為:

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

協處理器

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

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

CP15

關於 cp15詳細介紹見於《ARM體系結構參考手冊》的 B3.17cp15 一共有 16個暫存器32位的暫存器,其編號為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無法完成地址翻譯任務時,才會到記憶體中查詢頁表,這樣就減少了頁表查詢導致的處理器效能下降。詳細看

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

  • 1. 圖中的page table的基地址就是上面TTB暫存器值,整個page table非常大,有多大接下來會講,所以只能存在記憶體裡,TTB中只是存一個開始位置而已。
    1. 虛擬地址是程式的地址邏輯地址,也就是餵給CPU的地址,必須經過MMU的轉換後變成實體記憶體才能取到真正的指令和資料。
  • 3. 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 中回覆 百文 可方便閱讀。

按功能模組:

百萬注原始碼 | 處處扣細節

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

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

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

「其他文章」