自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Linux內(nèi)核的固定映射:提升性能的秘密武器

系統(tǒng) Linux
Fixmap 作為 Linux Kernel 內(nèi)存管理體系中的關(guān)鍵 “先鋒”,在系統(tǒng)啟動早期發(fā)揮著不可替代的作用。它以固定虛擬地址、靈活物理映射的獨特方式,為內(nèi)核突破初始化困境提供了可能,保障諸如早期控制臺信息輸出、設(shè)備樹解析、早期 I/O 內(nèi)存映射等關(guān)鍵任務(wù)順利完成,是內(nèi)核平穩(wěn)起航的 “幕后英雄”。

在當(dāng)今數(shù)字化時代,高效穩(wěn)定的 Linux 內(nèi)核是眾多技術(shù)應(yīng)用的基石。你是否好奇,如何讓 Linux 內(nèi)核在復(fù)雜任務(wù)中實現(xiàn)卓越性能?今天,我們要揭開其提升性能的秘密武器 —— 固定映射。它就像一位默默發(fā)力的幕后英雄,通過獨特的機(jī)制,優(yōu)化內(nèi)核內(nèi)存訪問,讓系統(tǒng)運行如絲般順滑。下面,讓我們一同走進(jìn)固定映射的奇妙世界。

一、Fixmap固定映射簡介

1.1Fixmap概述

在 Linux Kernel 的內(nèi)存管理體系里,F(xiàn)ixmap(固定映射)可是個相當(dāng)關(guān)鍵的角色。當(dāng)系統(tǒng)啟動,內(nèi)核初始化前期,內(nèi)存管理系統(tǒng)還在 “籌備” 階段,大部分物理內(nèi)存尚未建立頁表,常規(guī)的內(nèi)存操作函數(shù)(像 ioremap、kmalloc 等)都無法施展拳腳。這時候,F(xiàn)ixmap 就登場啦!它就像是內(nèi)核提前備好的 “應(yīng)急通道”,為特定模塊提供了一種臨時卻可靠的物理內(nèi)存映射機(jī)制,保障內(nèi)核在初始化早期,也能順利訪問關(guān)鍵內(nèi)存區(qū)域,完成諸如 early console、FDT+映射、early ioremap、建立 paging init 等重要任務(wù),為系統(tǒng)的順利啟動和后續(xù)穩(wěn)定運行 “保駕護(hù)航”。

固定映射的線性地址(Fixed-mapped linear addresses)是一組特殊的線性地址,這些線性地址在編譯時就已經(jīng)確定,但是其映射的物理地址是在系統(tǒng)啟動時確定的。

內(nèi)核為 fixmap 保留了地址空間,在頁表創(chuàng)建時,就為它們創(chuàng)建了對應(yīng)的表項:

NEXT_PAGE(level2_fixmap_pgt)
     .fill   506,8,0
     .quad   level1_fixmap_pgt - __START_KERNEL_map + _PAGE_TABLE
     /* 8MB reserved for vsyscalls + a 2MB hole = 4 + 1 entries */
     .fill   5,8,0
 
 NEXT_PAGE(level1_fixmap_pgt)
     .fill   512,8,0

level2_fixmap_pgt 緊挨著 level2_kernel_pgt ,level2_kernel_pgt里保存了內(nèi)核的 code+data+bss 段。

NEXT_PAGE(level3_kernel_pgt)
     .fill   L3_START_KERNEL,8,0
     /* (2^48-(2*1024*1024*1024)-((2^39)*511))/(2^30) = 510 */
     .quad   level2_kernel_pgt - __START_KERNEL_map + _KERNPG_TABLE
     .quad   level2_fixmap_pgt - __START_KERNEL_map + _PAGE_TABLE
 
 NEXT_PAGE(level2_kernel_pgt)
     /*
      * 512 MB kernel mapping. We spend a full page on this pagetable
      * anyway.
      *
      * The kernel code+data+bss must not be bigger than that.
      *
      * (NOTE: at +512MB starts the module area, see MODULES_VADDR.
      *  If you want to increase this then increase MODULES_VADDR
      *  too.)
      */
     PMDS(0, __PAGE_KERNEL_LARGE_EXEC,
         KERNEL_IMAGE_SIZE/PMD_SIZE)

頁表創(chuàng)建時,fixmap 區(qū)域在頁表中的位置如下圖所示:

圖片圖片

1.2為何 Linux Kernel 需要 Fixmap?

⑴內(nèi)核啟動初期的困境

咱們把時間拉回到內(nèi)核啟動初期,這時候內(nèi)存管理系統(tǒng)還在 “籌備” 階段,大部分物理內(nèi)存尚未建立頁表,常規(guī)的內(nèi)存操作函數(shù)(像 ioremap、kmalloc 等)都無法施展拳腳。想象一下,內(nèi)核就像是一個剛搬進(jìn)毛坯房的住戶,雖然房子(物理內(nèi)存)有了,但家具(頁表等內(nèi)存管理機(jī)制)還沒置辦齊,想找個東西(訪問特定內(nèi)存區(qū)域)都困難重重。

這時候要是想進(jìn)行一些關(guān)鍵操作,比如初始化早期控制臺(early console)來輸出啟動信息,或者讀取設(shè)備樹(FDT)獲取硬件配置信息,根本沒辦法像正常運行階段那樣,通過靈活的虛擬地址去訪問物理內(nèi)存。沒有這些關(guān)鍵信息,內(nèi)核后續(xù)的初始化步驟就如同盲人摸象,根本無從下手,整個啟動流程就會陷入僵局。

⑵Fixmap 如何巧妙化解難題

這時候,F(xiàn)ixmap 就像是內(nèi)核提前備好的 “應(yīng)急通道” 閃亮登場啦!它在內(nèi)核編譯的時候,就預(yù)留了一段固定的虛擬地址段。就好比在毛坯房里提前規(guī)劃出幾個固定的儲物空間,不管房子(內(nèi)存布局)怎么裝修變動,這些儲物空間(固定虛擬地址)的位置不變。當(dāng)內(nèi)核啟動初期需要訪問關(guān)鍵內(nèi)存區(qū)域時,就能利用這段固定虛擬地址,快速建立起與物理內(nèi)存的臨時映射關(guān)系。

比如說,要初始化 early console,F(xiàn)ixmap 可以將預(yù)留的虛擬地址映射到串口相關(guān)的物理內(nèi)存區(qū)域,這樣內(nèi)核就能順利往控制臺輸出信息,讓我們看到啟動過程中的各種日志,了解內(nèi)核的 “啟動心聲”;讀取 FDT 時,同樣通過 Fixmap 建立映射,精準(zhǔn)找到存儲硬件配置的物理內(nèi)存,獲取設(shè)備信息,為后續(xù)硬件初始化做好準(zhǔn)備。有了 Fixmap 的 “搭橋牽線”,內(nèi)核在啟動早期那些艱難時刻,也能有條不紊地推進(jìn)各項關(guān)鍵任務(wù),逐步搭建起完整的運行環(huán)境,最終順利 “長大成人”,進(jìn)入穩(wěn)定運行狀態(tài)。

二、Fixmap的實現(xiàn)原理

2.1虛擬地址的精心規(guī)劃

Fixmap 所占據(jù)的虛擬地址范圍可是在編譯階段就被精心規(guī)劃好了。在 ARM 架構(gòu)下,通常是一段特定的高地址空間,比如 0xFFC00000 - 0xFFF00000 ,這段地址空間就像是內(nèi)核專門預(yù)留的 “黃金地段”,為啟動初期關(guān)鍵模塊的內(nèi)存映射需求隨時待命。

而在 x86 架構(gòu)中,又有所不同,它處于內(nèi)核模塊區(qū)域附近,與其他內(nèi)存區(qū)域劃分清晰,像在一些常見的內(nèi)核配置下,會在靠近內(nèi)核代碼段和數(shù)據(jù)段的特定位置 “安營扎寨”,確保內(nèi)核在啟動早期,能迅速精準(zhǔn)地找到這塊 “應(yīng)急寶地”,利用其完成關(guān)鍵物理內(nèi)存的映射。這種因架構(gòu)而異的地址規(guī)劃,是充分考慮了不同硬件平臺的內(nèi)存管理特性、地址總線布局以及內(nèi)核啟動流程中的實際需求,量身定制的方案,只為保障系統(tǒng)順利起航。

2.2Fixmap 空間分配

固定映射區(qū)可以看做由多個頁組成的數(shù)組,數(shù)組的索引定義在枚舉類型 fixed_addresses 中。每個索引表示一個固定映射的線性地址,這些地址是 4KB 對齊的,意味著每個地址都是頁基地址。正常情況下,每個索引對應(yīng)著一個 4KB 的頁;當(dāng)fixed_addresses 中兩個相鄰的索引不連續(xù)時,意味著低序索引對應(yīng)著多個頁。

枚舉類型 fixed_addresses 定義如下:

// file: arch/x86/include/asm/fixmap.h
 /*
  * Here we define all the compile-time 'special' virtual
  * addresses. The point is to have a constant address at
  * compile time, but to set the physical address only
  * in the boot process.
  * for x86_32: We allocate these special addresses
  * from the end of virtual memory (0xfffff000) backwards.
  * Also this lets us do fail-safe vmalloc(), we
  * can guarantee that these special addresses and
  * vmalloc()-ed addresses never overlap.
  *
  * These 'compile-time allocated' memory buffers are
  * fixed-size 4k pages (or larger if used with an increment
  * higher than 1). Use set_fixmap(idx,phys) to associate
  * physical memory with fixmap indices.
  *
  * TLB entries of such buffers will not be flushed across
  * task switches.
  */
 enum fixed_addresses {
 #ifdef CONFIG_X86_32
     FIX_HOLE,
     FIX_VDSO,
 #else
     VSYSCALL_LAST_PAGE,
     VSYSCALL_FIRST_PAGE = VSYSCALL_LAST_PAGE
                 + ((VSYSCALL_END-VSYSCALL_START) >> PAGE_SHIFT) - 1,
     VVAR_PAGE,
     VSYSCALL_HPET,
 #endif
 #ifdef CONFIG_PARAVIRT_CLOCK
     PVCLOCK_FIXMAP_BEGIN,
     PVCLOCK_FIXMAP_END = PVCLOCK_FIXMAP_BEGIN+PVCLOCK_VSYSCALL_NR_PAGES-1,
 #endif
     FIX_DBGP_BASE,
     FIX_EARLYCON_MEM_BASE,
 #ifdef CONFIG_PROVIDE_OHCI1394_DMA_INIT
     FIX_OHCI1394_BASE,
 #endif
 #ifdef CONFIG_X86_LOCAL_APIC
     FIX_APIC_BASE,  /* local (CPU) APIC) -- required for SMP or not */
 #endif
 #ifdef CONFIG_X86_IO_APIC
     FIX_IO_APIC_BASE_0,
     FIX_IO_APIC_BASE_END = FIX_IO_APIC_BASE_0 + MAX_IO_APICS - 1,
 #endif
 #ifdef CONFIG_X86_VISWS_APIC
     FIX_CO_CPU, /* Cobalt timer */
     FIX_CO_APIC,    /* Cobalt APIC Redirection Table */
     FIX_LI_PCIA,    /* Lithium PCI Bridge A */
     FIX_LI_PCIB,    /* Lithium PCI Bridge B */
 #endif
     FIX_RO_IDT, /* Virtual mapping for read-only IDT */
 #ifdef CONFIG_X86_32
     FIX_KMAP_BEGIN, /* reserved pte's for temporary kernel mappings */
     FIX_KMAP_END = FIX_KMAP_BEGIN+(KM_TYPE_NR*NR_CPUS)-1,
 #ifdef CONFIG_PCI_MMCONFIG
     FIX_PCIE_MCFG,
 #endif
 #endif
 #ifdef CONFIG_PARAVIRT
     FIX_PARAVIRT_BOOTMAP,
 #endif
     FIX_TEXT_POKE1, /* reserve 2 pages for text_poke() */
     FIX_TEXT_POKE0, /* first page is last, because allocation is backward */
 #ifdef  CONFIG_X86_INTEL_MID
     FIX_LNW_VRTC,
 #endif
     __end_of_permanent_fixed_addresses,
 
     /*
      * 256 temporary boot-time mappings, used by early_ioremap(),
      * before ioremap() is functional.
      *
      * If necessary we round it up to the next 256 pages boundary so
      * that we can have a single pgd entry and a single pte table:
      */
 #define NR_FIX_BTMAPS       64
 #define FIX_BTMAPS_SLOTS    4
 #define TOTAL_FIX_BTMAPS    (NR_FIX_BTMAPS * FIX_BTMAPS_SLOTS)
     FIX_BTMAP_END =
      (__end_of_permanent_fixed_addresses ^
       (__end_of_permanent_fixed_addresses + TOTAL_FIX_BTMAPS - 1)) &
      -PTRS_PER_PTE
      ? __end_of_permanent_fixed_addresses + TOTAL_FIX_BTMAPS -
        (__end_of_permanent_fixed_addresses & (TOTAL_FIX_BTMAPS - 1))
      : __end_of_permanent_fixed_addresses,
     FIX_BTMAP_BEGIN = FIX_BTMAP_END + TOTAL_FIX_BTMAPS - 1,
 #ifdef CONFIG_X86_32
     FIX_WP_TEST,
 #endif
 #ifdef CONFIG_INTEL_TXT
     FIX_TBOOT_BASE,
 #endif
     __end_of_fixed_addresses
 };

固定映射區(qū)分為 2 個部分:永久映射區(qū)和臨時映射區(qū)。永久映射是指建立的映射關(guān)系不會改變,每段區(qū)域只供特定模塊使用。臨時映射區(qū)主要是內(nèi)核啟動時供 early_ioremap 函數(shù)使用,此時內(nèi)存管理子系統(tǒng)還沒有就緒, ioremap 函數(shù)還無法使用。

⑴永久映射區(qū)

永久映射區(qū)起始地址和大小使用以下兩個宏表示:

// file: arch/x86/include/asm/fixmap.h
 #define FIXADDR_SIZE    (__end_of_permanent_fixed_addresses << PAGE_SHIFT)
 #define FIXADDR_START        (FIXADDR_TOP - FIXADDR_SIZE)

宏 FIXADDR_SIZE 表示永久映射區(qū)的大小。__end_of_permanent_fixed_addresses 是永久映射區(qū)的邊界索引,PAGE_SHIFT (擴(kuò)展為 12)決定了頁的大小。由于每個索引對應(yīng)著單頁大小,__end_of_permanent_fixed_addresses << PAGE_SHIFT 就計算出了永久映射區(qū)的大小。索引 __end_of_permanent_fixed_addresses的值與內(nèi)核配置相關(guān),在我的系統(tǒng)中,__end_of_permanent_fixed_addresses的值為 2206,也就是說永久映射區(qū)為 2206 個頁大小,即 8824 KB。

宏 FIXADDR_START 是永久映射區(qū)的起始地址,其計算方法是用FIXADDR_TOP減去該區(qū)域的大小。宏 FIXADDR_TOP 定義如下:

// file: arch/x86/include/asm/fixmap.h
 #define FIXADDR_TOP (VSYSCALL_END-PAGE_SIZE)

宏VSYSCALL_END其定義如下:

// file: arch/x86/include/uapi/asm/vsyscall.h
 #define VSYSCALL_END (-2UL << 20)

宏VSYSCALL_END 擴(kuò)展為 0xffffffffffe00000,宏 FIXADDR_TOP 擴(kuò)展為 0xffffffffffdff000。對比一下 Linux 內(nèi)核內(nèi)存布局:

Virtual memory map with 4 level page tables:
 
 0000000000000000 - 00007fffffffffff (=47 bits) user space, different per mm
 hole caused by [48:63] sign extension
 ffff800000000000 - ffff80ffffffffff (=40 bits) guard hole
 ffff880000000000 - ffffc7ffffffffff (=64 TB) direct mapping of all phys. memory
 ffffc80000000000 - ffffc8ffffffffff (=40 bits) hole
 ffffc90000000000 - ffffe8ffffffffff (=45 bits) vmalloc/ioremap space
 ffffe90000000000 - ffffe9ffffffffff (=40 bits) hole
 ffffea0000000000 - ffffeaffffffffff (=40 bits) virtual memory map (1TB)
 ... unused hole ...
 ffffffff80000000 - ffffffffa0000000 (=512 MB)  kernel text mapping, from phys 0
 ffffffffa0000000 - ffffffffff5fffff (=1525 MB) module mapping space
 ffffffffff600000 - ffffffffffdfffff (=8 MB) vsyscalls
 ffffffffffe00000 - ffffffffffffffff (=2 MB) unused hole

可以看到,宏 VSYSCALL_END 表示的是 vsyscalls 區(qū)域的結(jié)束地址。永久映射區(qū)的最高地址空間,分配給了 vsyscalls 區(qū)域:

VSYSCALL_LAST_PAGE,
     VSYSCALL_FIRST_PAGE = VSYSCALL_LAST_PAGE
                 + ((VSYSCALL_END-VSYSCALL_START) >> PAGE_SHIFT) - 1,

在 x86-64 模式下,VSYSCALL_LAST_PAGE是 fixed_addresses 的第一個元素,其值為 0;VSYSCALL_FIRST_PAGE經(jīng)過計算后,其值為 2047。也就是說,vsyscalls 區(qū)域擁有 2048 個頁,即 2048 \times 4K = 8M 內(nèi)存空間。

另外,在永久映射區(qū),還為 Local APIC 、 I/O APIC 以及中斷描述符表(IDT)分配了空間:

#ifdef CONFIG_X86_LOCAL_APIC
     FIX_APIC_BASE,  /* local (CPU) APIC) -- required for SMP or not */
 #endif
 #ifdef CONFIG_X86_IO_APIC
     FIX_IO_APIC_BASE_0,
     FIX_IO_APIC_BASE_END = FIX_IO_APIC_BASE_0 + MAX_IO_APICS - 1,
 #endif
 
 ...
 
 FIX_RO_IDT, /* Virtual mapping for read-only IDT */
 
 ...

宏 MAX_IO_APICS 擴(kuò)展為 128,其定義如下:

// file: arch/x86/include/asm/apicdef.h
# define MAX_IO_APICS 128

其中,元素 FIX_APIC_BASE 對應(yīng)的 4KB 空間分配給 Local APIC;元素FIX_IO_APIC_BASE_0 到 FIX_IO_APIC_BASE_END 對應(yīng)的 512KB 空間分配給 I/O APIC;元素 FIX_RO_IDT 對應(yīng)的 4KB 空間分配給中斷描述符表(IDT)。

⑵臨時映射區(qū)

在永久映射區(qū)的下面,是臨時映射區(qū)。臨時映射區(qū)主要用于內(nèi)核啟動時供 early_ioremap() 函數(shù)使用,此時內(nèi)存管理子系統(tǒng)還未就緒,ioremap() 函數(shù)還無法使用。

/*
      * 256 temporary boot-time mappings, used by early_ioremap(),
      * before ioremap() is functional.
      *
      * If necessary we round it up to the next 256 pages boundary so
      * that we can have a single pgd entry and a single pte table:
      */
 #define NR_FIX_BTMAPS       64
 #define FIX_BTMAPS_SLOTS    4
 #define TOTAL_FIX_BTMAPS    (NR_FIX_BTMAPS * FIX_BTMAPS_SLOTS)
     FIX_BTMAP_END =
      (__end_of_permanent_fixed_addresses ^
       (__end_of_permanent_fixed_addresses + TOTAL_FIX_BTMAPS - 1)) &
      -PTRS_PER_PTE
      ? __end_of_permanent_fixed_addresses + TOTAL_FIX_BTMAPS -
        (__end_of_permanent_fixed_addresses & (TOTAL_FIX_BTMAPS - 1))
      : __end_of_permanent_fixed_addresses,
     FIX_BTMAP_BEGIN = FIX_BTMAP_END + TOTAL_FIX_BTMAPS - 1,
 #ifdef CONFIG_X86_32
     FIX_WP_TEST,
 #endif
 #ifdef CONFIG_INTEL_TXT
     FIX_TBOOT_BASE,
 #endif
     __end_of_fixed_addresses

臨時映射區(qū)的索引位于 FIX_BTMAP_END 與 __end_of_fixed_addresses 之間,這部分空間僅在內(nèi)核啟動時使用。其中,從FIX_BTMAP_END 到 FIX_BTMAP_BEGIN 共分配了 256 個頁的空間,供 early_ioremap() 使用。

因為臨時映射區(qū)的存在,內(nèi)核又單獨定義了 2 個宏,表示啟動時映射區(qū)的大小和起始地址:

// file: arch/x86/include/asm/fixmap.h
 #define FIXADDR_BOOT_SIZE   (__end_of_fixed_addresses << PAGE_SHIFT)
 #define FIXADDR_BOOT_START  (FIXADDR_TOP - FIXADDR_BOOT_SIZE)

其計算過程類似于永久映射區(qū),不再贅述。

⑶固定映射區(qū)內(nèi)存布局

固定映射區(qū)內(nèi)存布局如下圖所示:

圖片圖片

可以看到,除了 vsyscalls 區(qū)域之外,固定映射區(qū)的其它部分延伸到了模塊映射區(qū)。

2.3頁表的精細(xì)構(gòu)建流程

Fixmap 初始化時,頁表的構(gòu)建可是個精細(xì)活兒。以 ARM64 架構(gòu)為例,來看看代碼層面的操作:

void __init early_fixmap_init(void)
{
    pgd_t *pgd;
    pud_t *pud;
    pmd_t *pmd;
    unsigned long addr = FIXADDR_START;

    // 首先獲取對應(yīng)虛擬地址在全局頁目錄(PGD)中的項
    pgd = pgd_offset_k(addr);
    if (pgd_none(*pgd))
        __pgd_populate(pgd, __pa_symbol(bm_pud), PUD_TYPE_TABLE);

    // 接著獲取下一級頁目錄(PUD)項
    pud = fixmap_pud(addr);
    if (pud_none(*pud))
        __pud_populate(pud, __pa_symbol(bm_pmd), PMD_TYPE_TABLE);

    // 再獲取頁中間目錄(PMD)項
    pmd = fixmap_pmd(addr);
    __pmd_populate(pmd, __pa_symbol(bm_pte), PMD_TYPE_TABLE);

    BUILD_BUG_ON((__fix_to_virt(FIX_BTMAP_BEGIN) >> PMD_SHIFT)!= (__fix_to_virt(FIX_BTMAP_END) >> PMD_SHIFT));
    if ((pmd!= fixmap_pmd(fix_to_virt(FIX_BTMAP_BEGIN))) || pmd!= fixmap_pmd(fix_to_virt(FIX_BTMAP_END)))
    {
        WARN_ON(1);
    }
}

從代碼里可以清晰看到,先是以 FIXADDR_START 為起點,在全局頁目錄(PGD)里找到對應(yīng)的項,如果該項為空,就用 __pgd_populate 函數(shù)建立與下一級頁目錄(PUD)的關(guān)聯(lián),將 bm_pud 對應(yīng)的物理地址填充進(jìn)去,并標(biāo)記好頁表類型為 PUD_TYPE_TABLE;接著在 PUD 中如法炮制,通過 fixmap_pud 找到對應(yīng)項,為空時用 __pud_populate 關(guān)聯(lián)到頁中間目錄(PMD),填充 bm_pmd 物理地址;

最后在 PMD 里用 __pmd_populate 關(guān)聯(lián)到頁表項(PTE),填充 bm_pte 物理地址,如此層層遞進(jìn),就像搭積木一樣,構(gòu)建起從虛擬地址到物理地址的精準(zhǔn)映射通道,讓內(nèi)核在早期能順利訪問特定物理內(nèi)存,為系統(tǒng)啟動的各項關(guān)鍵任務(wù)提供有力支撐。不同架構(gòu)在細(xì)節(jié)上雖有差異,但都是圍繞著如何快速、精準(zhǔn)地搭建起這一臨時卻關(guān)鍵的內(nèi)存映射架構(gòu)展開,確保內(nèi)核初始化一路綠燈。

三、Fixmap相關(guān)函數(shù)詳解

3.1 fix_to_virt

fix_to_virt 函數(shù)的功能是獲取索引值對應(yīng)的固定映射地址。這個函數(shù)的實現(xiàn)很簡單:

static __always_inline unsigned long fix_to_virt(const unsigned int idx)
 {
         BUILD_BUG_ON(idx >= __end_of_fixed_addresses);
         return __fix_to_virt(idx);
 }

首先檢查入?yún)⑹欠穹弦?。fixed_addresses 中元素的最大值為 __end_of_fixed_addresses,該值僅作為邊界值使用,沒有其它意義,所以入?yún)⒉荒艽笥诨虻扔谠撨吔缰?。?BUILD_BUG_ON 會在編譯時檢查給定條件是否為真,如果條件為真,則在打印錯誤信息后將進(jìn)程掛起。

檢查通過后,使用 __fix_to_virt 宏將索引值轉(zhuǎn)換成虛擬地址,該宏定義如下:

#define __fix_to_virt(x)        (FIXADDR_TOP - ((x) << PAGE_SHIFT))

每個索引對應(yīng)一個頁,把索引值左移 PAGE_SHIFT 后,就得到索引對應(yīng)的頁基地址到 FIXADDR_TOP 的偏移量;然后用 FIXADDR_TOP 減去該偏移量,得到頁基地址。計算過程請參考下圖:

圖片圖片

3.2 virt_to_fix

virt_to_fix 函數(shù)實現(xiàn)的功能與 fix_to_virt 函數(shù)相反, 是將虛擬地址轉(zhuǎn)換成固定映射區(qū)的索引值,其定義如下:

static inline unsigned long virt_to_fix(const unsigned long vaddr)
 {
         BUG_ON(vaddr >= FIXADDR_TOP || vaddr < FIXADDR_START);
         return __virt_to_fix(vaddr);
 }

函數(shù)執(zhí)行時,首先檢查待轉(zhuǎn)換虛擬地址是否低于 FIXADDR_START 或者大于 FIXADDR_TOP 。如果條件為真,BUG_ON 會使程序陷入死循環(huán)。

檢查通過后,調(diào)用宏 __virt_to_fix 將虛擬地址轉(zhuǎn)換成索引值,該宏定義如下:

#define __virt_to_fix(x)        ((FIXADDR_TOP - ((x)&PAGE_MASK)) >> PAGE_SHIFT)

宏 PAGE_MASK定義如下:

/* PAGE_SHIFT determines the page size */
 #define PAGE_SHIFT  12
 #define PAGE_SIZE   (_AC(1,UL) << PAGE_SHIFT)
 #define PAGE_MASK   (~(PAGE_SIZE-1))

PAGE_MASK的低 12 位為 0,其余位為 1,使用它可以清空地址的低 12 位,得到頁基地址。__virt_to_fix 宏工作原理如下:

  • 使用 (x)&PAGE_MASK 清空給定地址的低 12 位,得到頁基地址
  • 然后用FIXADDR_TOP減去上一步得到的頁基地址,得到兩者的地址差。由于兩者都對齊到頁基地址,相減之后的差值,低 12 位仍然為 0。
  • 將上一步得到的地址差,右移 PAGE_SHIFT (擴(kuò)展為 12 )位后,得到了兩者之間頁號差。由于每個索引映射一個頁,所以頁號差就是索引差;而FIXADDR_TOP對應(yīng)的索引值為 0,所以索引差就等于虛擬地址的索引值。

3.3 set_fixmap

宏 set_fixmap 的作用是將物理地址映射到索引對應(yīng)的虛擬地址。該宏接收 2 個參數(shù),分別是索引值以及待映射的物理地址。

// file: arch/x86/include/asm/fixmap.h
 #define set_fixmap(idx, phys)               \
     __set_fixmap(idx, phys, PAGE_KERNEL)

其內(nèi)部調(diào)用了 __set_fixmap 函數(shù)來實現(xiàn)具體功能,該函數(shù)接收 3 個參數(shù),分別是:索引值、待映射的物理地址以及頁屬性。宏 PAGE_KERNEL 表示頁屬性,其本質(zhì)是多個標(biāo)志位組合成的位圖,其定義如下:

// file: arch/x86/include/asm/fixmap.h
 #define PAGE_KERNEL         __pgprot(__PAGE_KERNEL)
 #define __PAGE_KERNEL       (__PAGE_KERNEL_EXEC | _PAGE_NX)
 #define __PAGE_KERNEL_EXEC                      \
     (_PAGE_PRESENT | _PAGE_RW | _PAGE_DIRTY | _PAGE_ACCESSED | _PAGE_GLOBAL)

宏 __pgprot 作用,是將表示位圖的基本類型 unsigned long,包裝成結(jié)構(gòu)體 pgprot_t。

3.4 clear_fixmap

宏 clear_fixmap 的功能與 set_fixmap 相反,會清除索引與物理地址的映射關(guān)系。

// file: arch/x86/include/asm/fixmap.h
 #define clear_fixmap(idx)           \
     __set_fixmap(idx, 0, __pgprot(0))

clear_fixmap 內(nèi)部也是調(diào)用 __set_fixmap 函數(shù)通過將頁屬性設(shè)置為 0 來實現(xiàn)清除映射的。當(dāng)表項的存在 (Present) 位為 0 時,該表項是無效的。

3.5 set_fixmap_nocache

宏 set_fixmap_nocache 實現(xiàn)的功能與set_fixmap類似,也是建立索引與物理地址的映射關(guān)系。不過與 set_fixmap不同的是,通過set_fixmap_nocache映射的頁面,是不會被緩存的。

// file: arch/x86/include/asm/fixmap.h
 /*
  * Some hardware wants to get fixmapped without caching.
  */
 #define set_fixmap_nocache(idx, phys)           \
     __set_fixmap(idx, phys, PAGE_KERNEL_NOCACHE)

宏 PAGE_KERNEL_NOCACHE 是頁標(biāo)志位組合,其定義如下:

// file: arch/x86/include/asm/pgtable_types.h
 #define PAGE_KERNEL_NOCACHE     __pgprot(__PAGE_KERNEL_NOCACHE)
 #define __PAGE_KERNEL_NOCACHE       (__PAGE_KERNEL | _PAGE_PCD | _PAGE_PWT)

可以看到,該宏除了包含 __PAGE_KERNEL中的各種標(biāo)志以外,還包括 _PAGE_PCD (位 4)和 _PAGE_PWT (位 3)標(biāo)志。

// file: arch/x86/include/asm/pgtable_types.h
 #define _PAGE_PWT   (_AT(pteval_t, 1) << _PAGE_BIT_PWT)
 #define _PAGE_PCD   (_AT(pteval_t, 1) << _PAGE_BIT_PCD)
 
 #define _PAGE_BIT_PWT       3   /* page write through */
 #define _PAGE_BIT_PCD       4   /* page cache disabled */

PWT 標(biāo)志、PCD 標(biāo)志、PAT 標(biāo)志與內(nèi)存類型范圍寄存器( Memory-Type Range Registers,MTRR)一起,共同決定了頁面的緩存類型。當(dāng)把 PWT 標(biāo)志位 和 PCD 標(biāo)志位都設(shè)置為 1 時,不管 PAT 標(biāo)志與 MTRR 是什么狀態(tài),此時的緩存類型均為不可緩存( Uncacheable ,UC)狀態(tài)。

3.6 __set_fixmap

__set_fixmap 的實現(xiàn)涉及到較多內(nèi)核分頁相關(guān)知識 -- 原理、數(shù)據(jù)結(jié)構(gòu)、APIs 等,__set_fixmap 實現(xiàn)的功能是將物理地址映射到索引對應(yīng)的虛擬地址空間。下面我們來看下 __set_fixmap 函數(shù)的實現(xiàn)細(xì)節(jié)。該函數(shù)接收 3 個參數(shù),分別是:索引值 ,需要映射的物理地址以及頁屬性。

// file: arch/x86/include/asm/fixmap.h
 static inline void __set_fixmap(enum fixed_addresses idx,
                 phys_addr_t phys, pgprot_t flags)
 {
     native_set_fixmap(idx, phys, flags);
 }

__set_fixmap 函數(shù)內(nèi)部調(diào)用了native_set_fixmap,并將參數(shù)透傳給該函數(shù)。

四、Fixmap的典型應(yīng)用場景實例

4.1早期控制臺(Early Console)的信息輸出保障

在系統(tǒng)啟動最初階段,控制臺驅(qū)動可能還沒完全初始化,但內(nèi)核需要及時輸出啟動信息,這些信息對于調(diào)試、了解系統(tǒng)啟動狀態(tài)至關(guān)重要,就好比建筑開工前,工頭得先找個地方記錄施工進(jìn)度和問題。這時候,F(xiàn)ixmap 就派上用場啦!它會將一段預(yù)留的虛擬地址,映射到串口相關(guān)的物理內(nèi)存區(qū)域。

串口作為早期控制臺輸出信息的重要硬件,內(nèi)核通過 Fixmap 建立的映射,就能順利地往控制臺輸出各種日志,像內(nèi)核初始化到哪一步了、內(nèi)存檢測結(jié)果如何、硬件初始化有沒有報錯等等。這些日志就像內(nèi)核啟動過程中的 “日記本”,讓開發(fā)人員能實時追蹤內(nèi)核的 “啟動心聲”,一旦出現(xiàn)問題,能迅速定位根源,保障啟動流程順利推進(jìn)。

4.2設(shè)備樹(Device Tree)的高效解析支撐

設(shè)備樹(Device Tree)是內(nèi)核了解硬件配置信息的關(guān)鍵數(shù)據(jù)源,它詳細(xì)記錄了系統(tǒng)中有哪些硬件設(shè)備、設(shè)備的參數(shù)、連接關(guān)系等信息,就像是內(nèi)核的 “硬件地圖”。在內(nèi)核啟動初期讀取設(shè)備樹時,常規(guī)的內(nèi)存映射機(jī)制還沒就位,F(xiàn)ixmap 再次登場。它把設(shè)備樹所在的物理地址,精準(zhǔn)映射到內(nèi)核可訪問的虛擬地址空間。

這樣一來,內(nèi)核就能輕松 “讀懂” 設(shè)備樹,知曉系統(tǒng)中有哪些 CPU 核心、內(nèi)存布局怎樣、有哪些外接設(shè)備如 USB 控制器、網(wǎng)卡等,以及它們對應(yīng)的中斷號、寄存器地址等關(guān)鍵參數(shù)。基于這些信息,內(nèi)核才能有條不紊地進(jìn)行后續(xù)硬件初始化工作,為各個硬件設(shè)備加載合適的驅(qū)動,讓它們協(xié)同工作,保障系統(tǒng)穩(wěn)定運行。

4.3早期 I/O 內(nèi)存映射(Early Ioremap)的得力助手

在系統(tǒng)啟動的早期,有些硬件設(shè)備的 I/O 內(nèi)存區(qū)域需要提前訪問,以便進(jìn)行初始化設(shè)置,像顯卡要初始化顯示模式、硬盤控制器要設(shè)置初始工作參數(shù)等,但這時候常規(guī)的 ioremap 函數(shù)還不能用,因為內(nèi)存管理系統(tǒng)的相關(guān)頁表還不完善。Fixmap 就充當(dāng)了 “臨時橋梁”,它為特定的 I/O 內(nèi)存區(qū)域建立臨時映射,讓內(nèi)核可以直接通過固定的虛擬地址訪問到這些關(guān)鍵的 I/O 內(nèi)存。

例如,對于一些早期啟動就需要配置的硬件寄存器,內(nèi)核借助 Fixmap 臨時映射其所在的 I/O 內(nèi)存,寫入初始化命令,使硬件進(jìn)入準(zhǔn)備狀態(tài),確保后續(xù)系統(tǒng)啟動過程中,硬件能及時響應(yīng)內(nèi)核指令,跟上啟動節(jié)奏,為整個系統(tǒng)的順利起航提供有力保障。

五、Fixmap與其他內(nèi)存映射方式的異同對比

5.1與直接映射(Direct Mapping)的區(qū)別剖析

直接映射通常是將內(nèi)核的虛擬地址空間與物理內(nèi)存按固定偏移量進(jìn)行一一對應(yīng),比如在常見的 32 位系統(tǒng)中,內(nèi)核空間起始的一段虛擬地址直接對應(yīng)物理內(nèi)存的低地址部分,這就像給每個物理內(nèi)存頁在虛擬地址空間里安排了一個固定的 “座位”,只要知道虛擬地址,通過簡單計算就能快速定位物理地址,訪問速度極快,常用于內(nèi)核代碼段、數(shù)據(jù)段等頻繁讀寫的區(qū)域。

而 Fixmap 則不同,它更像是內(nèi)核預(yù)留的 “機(jī)動部隊”,虛擬地址雖然在編譯時固定,但映射的物理內(nèi)存頁不固定,在內(nèi)核啟動早期,哪里需要緊急訪問,就臨時將 Fixmap 的虛擬地址映射過去,像前面提到的早期控制臺、設(shè)備樹讀取等場景。并且,F(xiàn)ixmap 的地址范圍相對較小,是專門為那些啟動關(guān)鍵階段的臨時需求開辟的 “特區(qū)”,不像直接映射覆蓋大片連續(xù)的內(nèi)核虛擬地址空間。直接映射全程 “在崗”,保障內(nèi)核穩(wěn)定運行期的常規(guī)內(nèi)存訪問;Fixmap 則是在內(nèi)核初始化前期 “沖鋒陷陣”,解決燃眉之急,二者分工明確,保障內(nèi)核不同階段的內(nèi)存需求。

5.2與動態(tài)映射(如 Vmalloc)的優(yōu)勢比較

Vmalloc 是內(nèi)核用于分配連續(xù)虛擬地址空間的 “利器”,它的優(yōu)勢在于能靈活地按需分配大塊連續(xù)虛擬內(nèi)存,這些虛擬地址對應(yīng)的物理內(nèi)存可以不連續(xù),適用于一些對虛擬地址連續(xù)性有要求,但物理內(nèi)存布局復(fù)雜的場景,比如加載大型內(nèi)核模塊時,模塊可能分散在各處物理內(nèi)存,Vmalloc 能為其構(gòu)建連續(xù)的虛擬訪問視圖。不過,Vmalloc 的建立過程相對復(fù)雜,需要遍歷內(nèi)核的頁表結(jié)構(gòu),尋找合適的物理頁并建立映射,耗時較長。

而 Fixmap 在映射建立上堪稱 “閃電俠”,由于虛擬地址固定且預(yù)先規(guī)劃好頁表層級,在內(nèi)核啟動早期,幾乎是瞬間就能完成特定物理內(nèi)存的映射,讓內(nèi)核迅速開展關(guān)鍵任務(wù),不耽誤 “啟動工期”。而且,F(xiàn)ixmap 映射的地址穩(wěn)定性強(qiáng),只要內(nèi)核不重啟,相關(guān)虛擬地址對應(yīng)的用途不變,這對于一些依賴固定地址的硬件設(shè)備初始化至關(guān)重要;Vmalloc 分配的虛擬地址在復(fù)雜的內(nèi)存管理操作下,有重新映射的可能,地址穩(wěn)定性相對較弱。所以,在對啟動速度、地址穩(wěn)定性要求極高的內(nèi)核初始化場景,F(xiàn)ixmap 完勝;在常規(guī)運行階段,面對復(fù)雜多樣的大塊內(nèi)存分配需求,Vmalloc 則大顯身手。

六、全文總結(jié)

Fixmap 作為 Linux Kernel 內(nèi)存管理體系中的關(guān)鍵 “先鋒”,在系統(tǒng)啟動早期發(fā)揮著不可替代的作用。它以固定虛擬地址、靈活物理映射的獨特方式,為內(nèi)核突破初始化困境提供了可能,保障諸如早期控制臺信息輸出、設(shè)備樹解析、早期 I/O 內(nèi)存映射等關(guān)鍵任務(wù)順利完成,是內(nèi)核平穩(wěn)起航的 “幕后英雄”。與直接映射、動態(tài)映射(Vmalloc)等方式相比,F(xiàn)ixmap 憑借其啟動初期快速響應(yīng)、地址穩(wěn)定的優(yōu)勢,在內(nèi)核啟動流程中牢牢占據(jù)一席之地。隨著硬件技術(shù)不斷演進(jìn)、內(nèi)核功能日益復(fù)雜,F(xiàn)ixmap 或許也將面臨新挑戰(zhàn)與優(yōu)化契機(jī),但其為內(nèi)核關(guān)鍵階段內(nèi)存管理需求 “兜底” 的核心價值,將持續(xù)助力 Linux 系統(tǒng)穩(wěn)定高效運行,為開源世界蓬勃發(fā)展筑牢根基。

從功能特性來看,F(xiàn)ixmap 通過在編譯時預(yù)留特定的虛擬地址范圍,為物理內(nèi)存建立起固定的映射關(guān)系。這一特性在 Linux Kernel 的多個關(guān)鍵環(huán)節(jié)發(fā)揮著不可或缺的作用。在系統(tǒng)啟動初期,當(dāng)常規(guī)內(nèi)存管理機(jī)制尚未完備時,F(xiàn)ixmap 為內(nèi)核提供了穩(wěn)定的虛擬地址到物理地址的映射,確保了內(nèi)核能夠順利啟動并完成關(guān)鍵的初始化操作,諸如早期控制臺信息輸出、設(shè)備樹解析以及早期 I/O 內(nèi)存映射等,為后續(xù)系統(tǒng)的正常運行筑牢根基。

從系統(tǒng)性能與穩(wěn)定性角度分析,F(xiàn)ixmap 使得內(nèi)核在訪問特定內(nèi)存區(qū)域時,能夠避開復(fù)雜的動態(tài)映射流程,從而顯著提升訪問效率,尤其在對時間和穩(wěn)定性要求極高的場景下,這種優(yōu)勢更為凸顯。同時,其固定的映射方式減少了因動態(tài)映射可能引發(fā)的錯誤與不確定性,有力地增強(qiáng)了系統(tǒng)的穩(wěn)定性。

盡管在現(xiàn)代復(fù)雜的內(nèi)存管理生態(tài)中,存在多種內(nèi)存映射方式,但 Fixmap 憑借其獨特的機(jī)制,與其他映射方式相互配合,共同構(gòu)建起一個高效、穩(wěn)定的 Linux 內(nèi)核內(nèi)存管理體系。展望未來,隨著硬件技術(shù)的不斷革新和操作系統(tǒng)功能的持續(xù)拓展,F(xiàn)ixmap 有望在更多新的應(yīng)用場景中展現(xiàn)其價值,為 Linux Kernel 的發(fā)展注入源源不斷的動力,我們也期待在后續(xù)的研究與實踐中,能進(jìn)一步挖掘其潛力,見證它為操作系統(tǒng)領(lǐng)域帶來更多的驚喜與突破。

責(zé)任編輯:武曉燕 來源: 深度Linux
相關(guān)推薦

2025-01-03 16:32:13

SpringBoot虛擬線程Java

2013-10-16 09:28:14

亞馬遜AWSSDN

2013-10-16 09:33:36

亞馬遜AWSSDN

2024-01-31 08:04:43

PygmentsPython

2014-01-07 10:46:39

2011-08-11 17:05:26

2025-01-15 13:25:47

MySQL命令數(shù)據(jù)庫

2022-02-11 10:47:17

CIOIT團(tuán)隊企業(yè)

2019-11-27 10:40:34

數(shù)據(jù)工具CIO

2023-05-08 14:54:00

AI任務(wù)HuggingGPT

2009-07-28 10:36:58

云計算Google秘密武器

2019-11-27 10:38:37

數(shù)據(jù)分析數(shù)據(jù)準(zhǔn)備工具

2024-12-18 16:00:00

C++性能優(yōu)化consteval

2024-07-11 08:34:48

2010-09-02 16:09:43

Linux

2011-06-02 10:24:11

iTravel蘋果

2023-02-24 10:26:34

語音AI人工智能

2023-11-20 07:39:07

2023-09-25 15:29:44

Go并發(fā)Goroutines

2023-02-13 08:00:00

深度學(xué)習(xí)數(shù)據(jù)算法
點贊
收藏

51CTO技術(shù)棧公眾號