U-Boot內存管理


如《Linux內核內存管理架構》一文中提到,linux內核中的內存管理支持內存地址映射、內存分配、內存回收、內存碎片管理、頁面緩存等眾多功能。但U-Boot做為啟動引導程序,其核心功能就是引導內核鏡像,所以其內存管理功能並不用像Linux內核中的內存管理一樣功能齊全。U-Boot中沒有內存分配、回收、緩存等功能,內存管理其實只做一件事:虛實地址映射,而且是固定映射。

為了提高效率,現代處理器的內存管理都由MMU(Memory Management Unit)硬件單元實現(示意圖如下),其核心模塊主要有TLB和page table。

不同的CPU體系的MMU架構差異很大,有的MMU可以跳過,有的不可以;有的MMU分為L1MMU、L2MMU;有的TLB分為頁式TLB、段式TLB;有的page table分為page entry、block entry;有的TLB reload和page table 查找由MMU硬件實現(ARM、x86, PowerPC),有的則是由軟件實現(MIPS, Alpha)等。U-Boot的虛實地址映射,一般能跳過MMU就跳過,能不使用頁表就不用頁表,總之,怎么簡單怎么來。

可見,U-Boot中的內存管理的實現與CPU架構和MMU強相關,本文挑選了PowerPC e500,MIPS,ARMv8三款處理器並對其MMU架構進行分析,並討論它們在U-Boot中的內存虛實地址映射實現。

 

U-Boot PowerPC內存管理

下圖是PowerPC e500的MMU結構框圖。

MMU地址映射過程中涉及到3種地址形式:

  1. 32位有效地址EA:軟件可以直接訪問的地址;
  2. 41位虛擬地址VA:經過段映射的過渡地址,由32位EA和AS以及8位PID位組成;
  3. 36位物理地址RA:也稱為實地址,由36位地址總線訪問的地址空間。

PID0-2 用來存放當前進程有效地址的進程ID號,主要作用是在進程上下文切換時,提高TLB刷新的精准性。

LAW(Local Access Window) 用於描述PowerPC處理器物理地址空間的划分,其中LAWBAR用於指定基址,LAWAR用於指定此空間用作PCI、Local Bus還是DDR等設備的空間。

e500支持2種形式的TLB:TLB0和TLB1。

TLB0支持固定4K頁大小映射,512個entry最大可以映射512*4K=2M的物理地址空間,需要動態更新,會產生TLB miss異常。TLB0靈活,可以滿足復雜系統應用的要求。
TLB1 是一種段式映射,有效地址和物理地址之間是一一對應的關系。TLB1支持可變頁大小映射,16個entry 支持4K~4G頁大小的映射,最大可映射16*4G =64G的物理地址空間,不需要動態更新,不會產生TLB miss異常。TLB1不夠靈活,無法滿足復雜系統應用的要求。

PowerPC e500核心的MMU是無法跳過的,所以只能通過MMU來映射地址空間。雖然MMU中的TLB1段式映射不夠靈活,但是簡單,可以滿足U-Boot中的內存固定映射需求。PowerPC對內存虛實地址映射的處理是先設置DDR內存的物理地址(LAW),再把虛擬地址到物理地址的映射關系寫到TLB1中。

相關代碼實現:

phys_size_t fixed_sdram(void)
{
// ... 初始化DDR配置參數ddr_cfg_regs
    ddr_size = (phys_size_t) CONFIG_SYS_SDRAM_SIZE * 1024 * 1024;
    fsl_ddr_set_memctl_regs(&ddr_cfg_regs, 0);

    if (set_ddr_laws(CONFIG_SYS_DDR_SDRAM_BASE, ddr_size,
                    LAW_TRGT_IF_DDR_1) < 0) {
        printf("ERROR setting Local Access Windows for DDR\n");
        return 0;
    }

    return ddr_size;
}

unsigned int
setup_ddr_tlbs_phys(phys_addr_t p_addr, unsigned int memsize_in_meg)
{
 /// ... 計算size

    for (i = 0; size && i < 8; i++) {
/// ... 計算ram_tlb_address, p_addr,  ram_tlb_index, tlb_size

        set_tlb(1, ram_tlb_address, p_addr,
            MAS3_SX|MAS3_SW|MAS3_SR, wimge,
            0, ram_tlb_index, tlb_size, 1);
    }

    return memsize_in_meg;
}

unsigned int setup_ddr_tlbs(unsigned int memsize_in_meg)
{
    return
        setup_ddr_tlbs_phys(CONFIG_SYS_DDR_SDRAM_BASE, memsize_in_meg);
}

 

U-Boot MIPS內存管理

MIPS的虛擬地址空間分為多個段,即Kseg0-3和Kuseg。其中kseg0/kseg1可以跳過MMU,支持直接映射,各512MB。kseg2/3和Kuseg必須經過MMU。

因為U-Boot階段對內存的需求量很小,512MB內存空間已足夠滿足需要,所以MIPS對內存虛實地址映射的處理是先設置好DDR內存的物理地址BAR,把其虛擬地址設置到kseg0/1即可。沒有必要經過MMU和TLB。換句話說,如果CPU需要訪問高於512M的DDR內存物理地址空間,必須通過MMU地址轉換。

 

相關代碼實現:

#define mem_map(x) (void *)(CAC_BASE + (x))

 

U-Boot ARMv8內存管理

 ARMv8的MMU結構如下圖,其支持:

  • L1指令TLB,全相連,48個entry,支持4KB, 64KB和1MB頁面大小;
  • L1數據TLB,全相連,32個entry,支持4KB, 64KB和1MB頁面大小;
  • L2 TLB,4路組相連,1024個entry,支持4KB, 64KB和1MB頁面大小;
  • page table 查找由MMU中的Translation Control Unit (TCU) 硬件實現;

ARMv8的page table 結構如下。其分為4級,每級頁表有512個條目,支持如下3種表條目描述符。

  • Table descriptor,指向下一級table;
  • Page descriptor,指向一個4KB(或64KB、1MB)大小page size的頁面;
  • Block descriptor,指向一個block size的內存區域;在1級頁表中block size是1GB,2級頁表中block size是2MB。

 

 

 

與上面提到的PowerPC、MIPS不同,ARMv8的MMU即無法跳過,TLB也不支持段式映射。所以只能通過page table完成虛實地址映射。這就帶來一個問題:在DDR內存准備好之前,page table又放在哪里?

ARMv8核心的處理器片內包含一塊2MB大小的OCRAM(On Chip RAM),在DDR RAM准備好之前,MMU table就放在CONFIG_SYS_FSL_OCRAM_BASE處,最大支持EARLY_PGTABLE_SIZE(0x5000)個條目。2MB大小的頁表在Linux內核中是遠遠不夠的,但在U--Boot中已足夠,因為:

  1. page table支持塊映射(1GB,2MB),對於連續的大塊地址空間映射,block entry能極大簡化page table的大小;
  2. U-Boot中的虛實地址映射都是固定映射;
  3. 只有和啟動相關的設備和地址空間才需要映射,其數量有限。

等DDR內存初始化好后,再將table放到DRAM中。

相關代碼實現:

void setup_pgtables(void)
{
    int i;

    if (!gd->arch.tlb_fillptr || !gd->arch.tlb_addr)
        panic("Page table pointer not setup.");

    create_table();

    /* Now add all MMU table entries one after another to the table */
    for (i = 0; mem_map[i].size || mem_map[i].attrs; i++)
        add_map(&mem_map[i]);
}

static void add_map(struct mm_region *map)
{
    u64 *pte;
    u64 virt = map->virt;
    u64 phys = map->phys;
    u64 size = map->size;
    u64 attrs = map->attrs | PTE_TYPE_BLOCK | PTE_BLOCK_AF;
    u64 blocksize;
    int level;
    u64 *new_table;

    while (size) {
        pte = find_pte(virt, 0);
        if (pte && (pte_type(pte) == PTE_TYPE_FAULT)) {
            debug("Creating table for virt 0x%llx\n", virt);
            new_table = create_table();
            set_pte_table(pte, new_table);
        }

        for (level = 1; level < 4; level++) {
            pte = find_pte(virt, level);
            if (!pte)
                panic("pte not found\n");

            blocksize = 1ULL << level2shift(level);
            if (size >= blocksize && !(virt & (blocksize - 1))) {
                /* Page fits, create block PTE */
                *pte = phys | attrs;
                virt += blocksize;
                phys += blocksize;
                size -= blocksize;
                break;
            } else if (pte_type(pte) == PTE_TYPE_FAULT) {
                /* Page doesn't fit, create subpages */
                new_table = create_table();
                set_pte_table(pte, new_table);
            } else if (pte_type(pte) == PTE_TYPE_BLOCK) {
                split_block(pte, level);
            }
        }
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM