【原創】ARMv8 MMU及Linux頁表映射

来源:https://www.cnblogs.com/LoyenWang/archive/2019/08/25/11406693.html
-Advertisement-
Play Games

背景 By 魯迅 By 高爾基 說明: 1. Kernel版本:4.14 2. ARM64處理器,Contex A53,雙核 3. 使用工具:Source Insight 3.5, Visio 1. 介紹 要想理解好Linux的頁表映射,MMU的機制是需要去熟悉的,因此將這兩個模塊放到一起介紹。 關 ...


背景

  • Read the fucking source code! --By 魯迅
  • A picture is worth a thousand words. --By 高爾基

說明:

  1. Kernel版本:4.14
  2. ARM64處理器,Contex-A53,雙核
  3. 使用工具:Source Insight 3.5, Visio

1. 介紹

要想理解好Linux的頁表映射,MMU的機制是需要去熟悉的,因此將這兩個模塊放到一起介紹。
關於ARMv8 MMU的相關內容,主要參考文檔:《ARM Cortex-A Series Programmer’s Guide for ARMv8-A》

2. ARMv8 MMU

2.1 MMU/TLB/Cache概述

  1. MMU:完成的工作就是虛擬地址到物理地址的轉換,可以讓系統中的多個程式跑在自己獨立的虛擬地址空間中,相互不會影響。程式可以對底層的物理記憶體一無所知,物理地址可以是不連續的,但是不妨礙映射連續的虛擬地址空間。
  2. TLBMMU工作的過程就是查詢頁表的過程,頁表放置在記憶體中時查詢開銷太大,因此專門有一小片訪問更快的區域用於存放地址轉換條目,用於提高查找效率。當頁表內容有變化的時候,需要清除TLB,以防止地址映射出錯。
  3. Cache:處理器和存儲器之間的緩存機制,用於提高訪問速率,在ARMv8上會存在多級Cache,其中L1 Cache分為指令Cache數據Cache,在CPU Core的內部,支持虛擬地址定址;L2 Cache容量更大,同時存儲指令和數據,為多個CPU Core共用,這多個CPU Core也就組成了一個Cluster

下圖淺黃色部分描述的就是一個地址轉換的過程。

由於上圖沒有體現出L1和L2 CacheMMU的關係,所以再來一張圖吧:

那具體是怎麼訪問的呢?再來一張圖:

2.2 虛擬地址到物理地址的轉換

虛擬地址到物理地址的映射通過查表的機制來實現,ARMv8中,Kernel Space的頁表基地址存放在TTBR1_EL1寄存器中,User Space頁表基地址存放在TTBR0_EL0寄存器中,其中內核地址空間的高位為全1,(0xFFFF0000_00000000 ~ 0xFFFFFFFF_FFFFFFFF),用戶地址空間的高位為全0,(0x00000000_00000000 ~ 0x0000FFFF_FFFFFFFF)

ARMv8中:

  • 虛擬地址支持
    64位虛擬地址中,並不是所有位都用上,除了高16位用於區分內核空間和用戶空間外,有效位的配置可以是:36, 39, 42, 47。這可決定Linux內核中地址空間的大小。比如我使用的內核中有效位配置為CONFIG_ARM64_VA_BITS=39,用戶空間地址範圍:0x00000000_00000000 ~ 0x0000007f_ffffffff,大小為512G,內核空間地址範圍:0xffffff80_00000000 ~ 0xffffffff_ffffffff,大小為512G。

  • 頁面大小支持
    支持3種頁面大小:4KB, 16KB, 64KB

  • 頁表支持
    支持至少兩級頁表,至多四級頁表,Level 0 ~ Level 3

結合有效虛擬地址位, 頁面大小,頁表的級數,可以組合成不同的頁表映射方式。
我使用的內核配置為:39位有效位,4KB大小頁面,3級頁表,所以我會以這個組合來介紹。
在ARMv8的手冊中剛好找到了下圖,描述了整個translation的過程,簡直完美:

  1. 虛擬地址[63:39]用於區分內核空間與用戶空間,從而選擇不同的TTBRn寄存器來獲取Level 1頁表基地址
  2. 虛擬地址[38:30]放置Level 1頁表中的索引,從而找到對應的描述符地址並獲取描述符內容,根據描述符中的內容獲取Level 2頁表基地址;
  3. 虛擬地址[29:21]Level 2頁表中的索引,從而找到對應的描述符地址並獲取描述符內容,根據描述符中的內容獲取Level 3頁表基地址;
  4. 虛擬地址[20:12]Level 3頁表中的索引,從而找到對應的描述符地址並獲取描述符內容,根據描述符中的內容獲取物理地址的高36位,以4K地址對齊;
  5. 虛擬地址[11:0]放置的是物理地址的偏移,結合獲取的物理地址高位,最終得到物理地址。

講到這裡還沒有完,是時候看一下Table Descriptor了,也就是頁表中存放的內容,有以下四種類型:

類型有低兩位來決定,其中Level 0中的Table Descriptor只能輸出Level 1頁表的地址,Level 3中的Table Descriptor只能輸出block addresses
看到圖中的attributes了嗎,這些可以用於memory的許可權控制,memory ordering,cache policy的操作等。

在ARMv8中,與頁表相關的寄存器有:TCR_EL1, TTBRx_EL1.

3. Linux頁表映射

3.1 Linux頁表基本操作

看過《深入理解Linux內核》的同學應該很熟悉下邊這張圖片,Linux的分頁模式(圖中以X86為例,頁表基地址由CR3寄存器指定):

在Linux內核中支持4級頁表的模型,同時適用於32位和64位系統。

那麼ARMv8與Linux內核是怎麼結合的呢?以我實際使用的設置(39位有效位,4KB大小頁面,3級頁表)為例,如下圖所示:

基本上內核中關於頁表的操作都會圍繞著上圖進行操作,似乎脫離了代碼有點不太合適,那麼就來一波fucking source code解析吧,主要講講各類page table相關的API。

代碼路徑:
arch/arm64/include/asm/pgtable-types.h:定義pgd_t, pud_t, pmd_t, pte_t等類型;
arch/arm64/include/asm/pgtable-prot.h:針對頁表中entry中的許可權內容設置;
arch/arm64/include/asm/pgtable-hwdef.h:主要包括虛擬地址中PGD/PMD/PUD等的劃分,這個與虛擬地址的有效位及分頁大小有關,此外還包括硬體頁表的定義, TCR寄存器中的設置等;
arch/arm64/include/asm/pgtable.h:頁表設置相關;

在這些代碼中可以看到,

  • CONFIG_PGTABLE_LEVELS=4時:pgd-->pud-->pmd-->pte;
  • CONFIG_PGTABLE_LEVELS=3時,沒有PUD頁表:pgd(pud)-->pmd-->pte;
  • CONFIG_PGTABLE_LEVELS=2時,沒有PUDPMD頁表:pgd(pud, pmd)-->pte

常用的巨集定義

頁表處理

/*描述各級頁表中的頁表項*/
typedef struct { pteval_t pte; } pte_t;
typedef struct { pmdval_t pmd; } pmd_t;
typedef struct { pudval_t pud; } pud_t;
typedef struct { pgdval_t pgd; } pgd_t;

/*  將頁表項類型轉換成無符號類型 */
#define pte_val(x)  ((x).pte)
#define pmd_val(x)  ((x).pmd)
#define pud_val(x)  ((x).pud)
#define pgd_val(x)  ((x).pgd)

/*  將無符號類型轉換成頁表項類型 */
#define __pte(x)    ((pte_t) { (x) } )
#define __pmd(x)    ((pmd_t) { (x) } )
#define __pud(x)    ((pud_t) { (x) } )
#define __pgd(x)    ((pgd_t) { (x) } )

/* 獲取頁表項的索引值 */
#define pgd_index(addr)     (((addr) >> PGDIR_SHIFT) & (PTRS_PER_PGD - 1))
#define pud_index(addr)     (((addr) >> PUD_SHIFT) & (PTRS_PER_PUD - 1))
#define pmd_index(addr)     (((addr) >> PMD_SHIFT) & (PTRS_PER_PMD - 1))
#define pte_index(addr)     (((addr) >> PAGE_SHIFT) & (PTRS_PER_PTE - 1))

/*  獲取頁表中entry的偏移值 */
#define pgd_offset(mm, addr)    (pgd_offset_raw((mm)->pgd, (addr)))
#define pgd_offset_k(addr)  pgd_offset(&init_mm, addr)
#define pud_offset_phys(dir, addr)  (pgd_page_paddr(*(dir)) + pud_index(addr) * sizeof(pud_t))
#define pud_offset(dir, addr)       ((pud_t *)__va(pud_offset_phys((dir), (addr))))
#define pmd_offset_phys(dir, addr)  (pud_page_paddr(*(dir)) + pmd_index(addr) * sizeof(pmd_t))
#define pmd_offset(dir, addr)       ((pmd_t *)__va(pmd_offset_phys((dir), (addr))))
#define pte_offset_phys(dir,addr)   (pmd_page_paddr(READ_ONCE(*(dir))) + pte_index(addr) * sizeof(pte_t))
#define pte_offset_kernel(dir,addr) ((pte_t *)__va(pte_offset_phys((dir), (addr))))

3.2 head.S中的頁表映射

3.2.1 idmap_pg_dir和swapper_pg_dir臨時頁表

是時候來個實例分析了,看看頁表的創建過程,代碼路徑:arch/arm64/kernel/head.S
內核啟動過程中,在真正的物理記憶體尚未添加進系統,以及頁表還未初始化之前,為了保證系統能正常運行,需要建立兩個臨時全局頁表:idmap_pg_dirswapper_pg_dir
其中兩個全局頁表的定義在arch/arm64/kernel/vmlinux.lds.S中,放置在BSS段之後:

    . = ALIGN(PAGE_SIZE);
    idmap_pg_dir = .;
    . += IDMAP_DIR_SIZE;
    swapper_pg_dir = .;
    . += SWAPPER_DIR_SIZE;
/*  定義了連續的幾個頁,分別存放PGD,PMD,PTE等,連續在一起,這個也是head.S中填充的 */
#define SWAPPER_DIR_SIZE    (SWAPPER_PGTABLE_LEVELS * PAGE_SIZE)
#define IDMAP_DIR_SIZE      (IDMAP_PGTABLE_LEVELS * PAGE_SIZE)
  • idmap_pg_dir
    從名字可以看出,identify map,也就是物理地址和虛擬地址是相等的。為什麼需要這麼一個映射呢?我們都知道在MMU打開之前,CPU訪問的都是物理地址,那麼當MMU打開後訪問的就是虛擬地址了,這段頁表的映射就是從CPU到打開MMU之前的這段代碼物理地址的映射,防止開啟MMU後,無法獲取頁表。可以從System.map文件中查看這些代碼:

  • swapper_pg_dir
    Linux內核編譯後,kernel image是需要進行映射的,包括text,data等各種段。

3.2.2 頁表創建

head.S中,創建頁表相關的有三個巨集:

  1. create_pgd_entry
/*
 * Macro to populate the PGD (and possibily PUD) for the corresponding
 * block entry in the next level (tbl) for the given virtual address.
 *
 * Preserves:   tbl, next, virt
 * Corrupts:    tmp1, tmp2
 */
    .macro  create_pgd_entry, tbl, virt, tmp1, tmp2
    create_table_entry \tbl, \virt, PGDIR_SHIFT, PTRS_PER_PGD, \tmp1, \tmp2
#if SWAPPER_PGTABLE_LEVELS > 3
    create_table_entry \tbl, \virt, PUD_SHIFT, PTRS_PER_PUD, \tmp1, \tmp2
#endif
#if SWAPPER_PGTABLE_LEVELS > 2
    create_table_entry \tbl, \virt, SWAPPER_TABLE_SHIFT, PTRS_PER_PTE, \tmp1, \tmp2
#endif
    .endm

上述函數主要是調用create_table_entry,由於SWAPPER_PGTABLES配置為3,因此相當於創建了pgd和pmd兩級頁表,此處需要註意一點,create_table_entry函數執行後,tbl參數會自動加上PAGE_SIZE,也就是說pgd和pmd兩級頁表是物理連續的。

  1. create_block_map
/*
 * Macro to populate block entries in the page table for the start..end
 * virtual range (inclusive).
 *
 * Preserves:   tbl, flags
 * Corrupts:    phys, start, end, pstate
 */
    .macro  create_block_map, tbl, flags, phys, start, end
    lsr \phys, \phys, #SWAPPER_BLOCK_SHIFT
    lsr \start, \start, #SWAPPER_BLOCK_SHIFT
    and \start, \start, #PTRS_PER_PTE - 1   // table index
    orr \phys, \flags, \phys, lsl #SWAPPER_BLOCK_SHIFT  // table entry
    lsr \end, \end, #SWAPPER_BLOCK_SHIFT
    and \end, \end, #PTRS_PER_PTE - 1       // table end index
9999:   str \phys, [\tbl, \start, lsl #3]       // store the entry
    add \start, \start, #1          // next entry
    add \phys, \phys, #SWAPPER_BLOCK_SIZE       // next block
    cmp \start, \end
    b.ls    9999b
    .endm

上述函數主要是往block中填充pte entry,真正創建虛擬地址到物理地址的映射,映射區域:start ~ end

  1. create_table_entry
/*
 * Macro to create a table entry to the next page.
 *
 *  tbl:    page table address
 *  virt:   virtual address
 *  shift:  #imm page table shift
 *  ptrs:   #imm pointers per table page
 *
 * Preserves:   virt
 * Corrupts:    tmp1, tmp2
 * Returns: tbl -> next level table page address
 */
    .macro  create_table_entry, tbl, virt, shift, ptrs, tmp1, tmp2
    lsr \tmp1, \virt, #\shift
    and \tmp1, \tmp1, #\ptrs - 1    // table index
    add \tmp2, \tbl, #PAGE_SIZE
    orr \tmp2, \tmp2, #PMD_TYPE_TABLE   // address of next table and entry type
    str \tmp2, [\tbl, \tmp1, lsl #3]
    add \tbl, \tbl, #PAGE_SIZE      // next level table page
    .endm

上述函數創建頁表項,並且返回下一個Level的頁表地址。

上述三個孤立的函數並不直觀,所以,圖來了:

總體來說,頁表的創建過程相對來說還是比較易懂的,掌握好幾級頁表及各級頁表index所占的位域,此外熟悉各個Level頁表中entry的格式,理解起來就會順暢很多了。

一摳細節深似海,點到為止,防止一葉障目不見泰山,收工!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 公司的項目架構演進,我們也趁機嘗試遷移到netcore,系列隨筆講記錄我們的踩坑和填坑記錄。 HttpClient不行? 這是我們第一次嘗試netcore 簡要介紹環境 netcore2.2+aspnetcore2.2+windows 2008R2+SqlServer2008R2 問題場景 支付寶支 ...
  • 文章起源來自一篇博客: "使用 .NET CORE 創建 項目模板,模板項目,Template DeepThought 博客園" 之前使用Abp的時候就很認同Abp創建模板項目的方式。想不到.Net Core出了更贊的方式創建模板。之前寫過一個系列文章,有不少對Abp框架的改動(見文章: "基於.N ...
  • 前提 入行已經7,8年了,一直想做一套漂亮點的自定義控制項,於是就有了本系列文章。 GitHub:https://github.com/kwwwvagaa/NetWinformControl 碼雲:https://gitee.com/kwwwvagaa/net_winform_custom_contr ...
  • .NET Core 中三種模式依賴註入的生命周期簡要說明 ...
  • 電子發票是電商時代的產物,PDF發票是最常見的電子發票之一。在這篇文章中,我將給大家分享一個免費的動態生成PDF電子發票的C#方案,併在文章末尾附上Java解決方案。 典型的發票包含客戶和供應商的名稱和地址、發票編號、購買物品的描述、付款金額等信息。為了動態地生成發票,我使用MS Word創建了一個 ...
  • 在開發個人博客的時候,用到了騰訊移動分析(MTA),相比其他數據統計平臺來說我喜歡她的簡潔高效,易上手,同時文檔也比較全面,提供了數據介面供用戶調用。 在看了MTA演示 "Demo" 和 "官方文檔" 後,我就決定使用 .NET Core將其HTML5統計API進行封裝,以供博客直接調用,省去各種鑒 ...
  • 一.概述 本章Web架構分層指南,參考了“Microsoft應用程式體繫結構指南”(該書是在2009年出版的,當時出版是為了幫助開發人員和架構師更快速,更低風險地使用Microsoft平臺和.NET Framework設計和構建有效,高質量的應用程式)。雖然已過去十年了,技術架構已更新(如流行的DD ...
  • 若是在 Linux 中搭建了 FTP 伺服器,為了安全性,就要考慮磁碟配額,以防伺服器磁碟空間被惡意占滿。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...