2011年6月19日 星期日

ARM Linux kernel 記憶體配置

摘譯這篇由 Russell King 在 2005 年 11 月 17 日針對 2.6.15 Linux kernel 撰寫的 ARM Linux kernel 記憶體配置

裡面指的是 linux kernel 使用 arm cpu 的虛擬記憶體的位址配置與排列方式

arm cpu 的虛擬記憶體定址空間: 4GB (32 位元的定址空間, 0000,0000 ~ FFFF,FFFF)

沒有遵循這個記憶體配置的 kernel 可能無法開機或發生隨機當機


===========開始=========針對 user space 的加入內容==========================

針對每個 user space 程式而言,其實都跟硬體裝置的記憶體映射空間, kernel 使用的記憶體空間,三者共用整個 4GB 的虛擬記憶體。

user space 程式這個區塊內部的記憶體配置如下:
   +----------------------+---------------------+-----------+------------+------------+
    | Code segment(r+x) | Data segment(r+w) | BSS(r+w)  | Heap(r+w) | Stack(r+w) |
   +-------------------------------------------------------------------------------------+

Code segment 是 instruction memory,儲存程式碼
Data segment 是 data memory,使用 brk(), sbrk() system call 改變或查詢 Program break 大小
BSS segment 是未初始化的 global 變數存放位置
Heap segment 儲存程式執行期產生的動態變數、記憶體區塊由 malloc() 取用,free()  釋放
Stack segment 使用 SP stack pointer 暫存器輔助 IP instrcution pointer 指定,由 function call, 與其 local 變數使用 

註:參考 Memory Management for System Programmers (pdf)

在 Linux 系統底下我們可以藉由 /proc/some_pid/maps 看到執行檔與 library memory map,另外 smaps 則詳列各區段 memory map 的使用狀態。其中可以看到從  0x0000,1000~0xBF00,0000 的載入函式庫與 heap, stack, 其中 stack 與 TASK_SIZE 相鄰

舉某個 android process 為例:
其中

    0xBEBE,9000- 0xBEBF,E00 是 stack 使用
    0xB000,0000 ~ 0xB001,0000 是 linker 載入位置, Thread 0 Stack
    0x8000,0000 ~ 0xAFE46000  是 Non-prelinked libraries
    0x4154,C000 ~ 0x8000,0000 是 mmap'd 記譯體,Java jar library, apk 檔案
    0x4000,0000 ~ 0x4154,C000 是 mmaped open file
    0x0000,A000 ~ 0x003C,4000 用在 heap
    0x0000,1000~0x0000, A000 佔用 .text (code segment) / .data 

Android 2.3 以前的 prelink library build/core/prelink-linux-arm.map 裡面除了重述 Russel King 的 ARM Linux kernel memory 的記憶體配置,Android 的記憶體配置中,包含兩塊系統預設的 library memory mapping:

    其中 Android prelink 定義了以下兩大塊記憶體對應:

    # 0xA0000000 - 0xBFFFFFFF Prelinked System Libraries
    # 0x90000000 - 0x9FFFFFFF Prelinked App Libraries
    可以在 prelink-linux-arm.map 中找到或新增你要加入的 library 記憶體對應。

    # 0xA0000000 - 0xBFFFFFFF Prelinked System Libraries
    0xAEF00000  - 0xAFF00000 # core system libraries
    0xAE700000 - 0xAEE00000 # bluetooth libraries
    0xAE300000 - 0xAE600000 # extended system libraries
    0xACA00000 - 0xAE200000 # core dalvik runtime support
    0xA9C00000 - 0xAC900000 # graphics
    0xA8D00000 - 0xA9B00000 # audio
    0xA7000000 - 0xA8B00000 # assorted system libraries
    0xA4800000 - 0xA6F00000 # pv libraries
    0xA4000000 - 0xA4700000 # opencore hardware support
    0xA3800000 - 0xA3900000 # pv libraries
    0xA2F00000 - 0xA3700000 # stagefright libraries
    0xA2A00000 - 0xA2E00000 # libraries for specific hardware

    # 0x90000000 - 0x9FFFFFFF Prelinked App Libraries
    0x9CA00000 - 0x9F000000 # libraries for specific apps or temporary libraries

# 0xC0000000 - 0xFFFFFFFF Kernel
# 0xB0100000 - 0xBFFFFFFF Thread 0 Stack
# 0xB0000000 - 0xB00FFFFF Linker
# 0xA0000000 - 0xBFFFFFFF Prelinked System Libraries
# 0x90000000 - 0x9FFFFFFF Prelinked App Libraries
# 0x80000000 - 0x8FFFFFFF Non-prelinked Libraries
# 0x40000000 - 0x7FFFFFFF mmap'd stuff
# 0x10000000 - 0x3FFFFFFF Thread Stacks
# 0x00000000 - 0x0FFFFFFF .text / .data / heap

====================針對 user space 的加入內容==========結束================

因為 cpu 架構的發展變化可能影響 kernel 記憶體位置的配置,user space 程式應該只使用到 0000,1000 ~ TASK_SIZE -1 這段記憶體。請使用 open(), mmap() 系統呼叫使用這段記憶體。

    FFFF,8000 ~ FFFF,FFFF           copy_user_page/clear_user_page 使用
    SA11xx 跟 Xscale cpu 建立 minicache 的記憶體位址

    FFFF,1000 ~ FFFF,7FFF           保留
    各平台禁止使用這塊記憶體配置

    FFFF,0000 ~ FFFF,0FFF           cpu vector page
    如果 cpu 支援改變中斷向量位址 (control register V bit), 會把中斷向量映射到這
    4K bytes 區塊

    FFC0,0000 ~ fffe,ffff           DMA 記憶體映射區塊,由 dma_alloc_xxx 的
    函式呼叫都使用這塊記憶體動態配置,總共 0x003F,0000 佔 4MB

    FF00,0000 ~ FFBFF,FFFF          保留給 DMA 記憶體映射擴充使用 佔 12MB

    VMALLOC_END ~ feff,ffff         平台自由運用的記憶體區塊都建議用這段
    VMALLOC_END 必須對齊 2MB 的記憶體區塊
    在 arch/arm/mach-pxa/include/mach/memory.h 中定義

    #define ARM_DMA_ZONE_SIZE SZ_64M 除了以上 16MB 保留給 DMA 另外用了 48 MB
    使用 FBC0,0000~fffe,ffff
    在 arch/arm/mach-pxa/include/mach/vmalloc.h 定義
    #define VMALLOC_END       (0xE8000000UL)

    VMALLOC_START ~ VMALLOC_END - 1 vmalloc() / ioremap() space
    由 vmalloc() 及 ioremap() 取得的記憶體會動態配置在這區塊,VMALLOC_START 相依
    於 high_memory(不能有重疊)
    在 arch/arm/include/asm/pgtable.h 中註解有說明留 8MB 給誤動作的記憶體錯誤緩衝空間,
    定義如下:
    #ifndef VMALLOC_START
    #define VMALLOC_OFFSET      (8*1024*1024)
    #define VMALLOC_START       (((unsigned long)high_memory + VMALLOC_OFFSET) &
                                                      ~(VMALLOC_OFFSET-1))
    #endif

其中 high_memory 在 arch/arm/mm/init.c 的 void __init bootmem_init(void) 計算:
    high_memory = __va(((phys_addr_t)max_low << PAGE_SHIFT) - 1) + 1;

    PAGE_OFFSET ~ high_memory - 1   Kernel direct-mapped RAM region
    這段記憶體對應到平台上的實體記憶體,通常跟系統記憶體是 1:1 的對應
    (direct addressing) 在 arch/arm/include/asm/memory.h 看到 PAGE_OFFSET 定義為
    UL(CONFIG_PAGE_OFFSET)
    也因此我們可以在 arch/arm/include/asm/memory.h 看到以下 virtual, physical 記憶體互轉的
    巨集定義:
    #define __virt_to_phys(x)   ((x) - PAGE_OFFSET + PHYS_OFFSET)
    #define __phys_to_virt(x)   ((x) - PHYS_OFFSET + PAGE_OFFSET)

    其中的 PHYS_OFFSET 實體記憶體在 cpu 定址的記憶體初始(start of bank 0 dram)位址
    ,是定義在同檔案的以下巨集:
    #define PHYS_OFFSET UL(CONFIG_DRAM_BASE) 與
    #define PHYS_OFFSET PLAT_PHYS_OFFSET
    CONFIG_DRAM_BASE 是實體記憶體起始的 offset 值,在 pxa 平台可以
    在 arch/arm/mach-pxa/include/mach/memory.h 中看到定義為:
    #define PLAT_PHYS_OFFSET    UL(0xa0000000)
    我們可以對照 pxa270 spec memory map 章節的 dram map 起始位址
    0xa000,0000 作為實體記憶體的起始位址得到驗證。
    再配合 arch/arm/Makefile 定義的 Linux kernel image text area 值
    TEXT_OFFSET := $(textofs-y)
    而 textofs-y   := 0x00008000 是預設值。
    arch/arm/boot/Makefile 告訴我們 zImage 等 target 就是
     21 #   ZRELADDR == virt_to_phys(PAGE_OFFSET + TEXT_OFFSET)
    因此可以算出:ZRELADDR = PHYS_OFFSET + TEXT_OFFSET = 0xA000,8000 (physical)
    而虛擬位置是 0xC000,8000(virtual),這個計算結果可以跟 Makefile include 的
    include $(srctree)/$(MACHINE)/Makefile.boot 檔案,在 pxa 平台就是
    arch/arm/mach-pxa/Makefile.boot 檔案裡面定義的 zreladdr-y
    zreladdr-y   := 0xa0008000
    在實務上我們也可以修改 zreladdr-y 定義的實體記憶體位址,來改成我們想要把 linux kernel
    起始點放在 physical memory 的哪一個位置上。這也就是為何我們從 boot loader 載入 linux
    kernel 都要指定到某個固定實體記憶體位址的原因,因為在 make kernel 時就決定了這個位址。

    PAGE_OFFSET 也就是 linux kernel image 的虛擬位址起始點, 0xC000,0000 3GB 的 user address
    space

    TASK_SIZE ~ PAGE_OFFSET - 1     Kernel module space
    使用 insmod 動態載入的 kernel modules 會在這段記憶體使用動態配置
    arch/arm/include/asm/memory.h 定義
    #define PAGE_OFFSET     UL(CONFIG_PAGE_OFFSET)

    0000,1000 ~ TASK_SIZE - 1       User space mapping
    每個 thread 使用 mmap 對應到這段位置
    arch/arm/include/asm/memory.h 定義
    #define TASK_SIZE         (UL(CONFIG_PAGE_OFFSET) - UL(0x01000000))
    如果 PAGE_OFFSET = 0xC000,0000,則 TASK_SIZE = 0xBF00,0000 (3GB - 16MB)

    0000,0000 ~ 0000,0FFF           cpu vector page, null pointer trap
    不支援中斷向量重新映射的 cpu 會把中斷向量 page 放在這 4K bytes

關於以上作業系統的 Virtual address map 我們可以使用 arch/arm/mach-pxa/generic.c 與 arch/arm/mach-pxa/include/mach/hardware.h 的建立 memory mapped device 跟 chip select memory mapped device 的 Virtual -> Physical mapping

arm 因為使用到 co processor 15,controller register(1th), v bit (13th), 因此用到 vector page relocate 功能,原本會到 0x0000, 0000 找 vector table 的動作改為到 0xffff, 0000 找 vector table,而我們在 mmu 啟動後使用的 0xffff,0000 Virtual address 就是在 arch/arm/mm/mmu.c 的 devicemaps_init() 中將 Linux kernel 開機時在 arch/arm/kernel/entry-armv.S 建立的 vector table 存在 SDRAM 後,建立一組 0xffff, 0000 high vector page 對映到實體記憶體的 vector table,因此 Linux kernel 開機啟動 mmu 進入 virtual address mode 後才能在硬體發出 exception 中斷時,找到 vector table 處理中斷。