精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Linux內核的固定映射:提升性能的秘密武器

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

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

一、Fixmap固定映射簡介

1.1Fixmap概述

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

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

內核為 fixmap 保留了地址空間,在頁表創建時,就為它們創建了對應的表項:

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里保存了內核的 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)

頁表創建時,fixmap 區域在頁表中的位置如下圖所示:

圖片圖片

1.2為何 Linux Kernel 需要 Fixmap?

⑴內核啟動初期的困境

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

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

⑵Fixmap 如何巧妙化解難題

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

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

二、Fixmap的實現原理

2.1虛擬地址的精心規劃

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

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

2.2Fixmap 空間分配

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

枚舉類型 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
 };

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

⑴永久映射區

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

// 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 表示永久映射區的大小。__end_of_permanent_fixed_addresses 是永久映射區的邊界索引,PAGE_SHIFT (擴展為 12)決定了頁的大小。由于每個索引對應著單頁大小,__end_of_permanent_fixed_addresses << PAGE_SHIFT 就計算出了永久映射區的大小。索引 __end_of_permanent_fixed_addresses的值與內核配置相關,在我的系統中,__end_of_permanent_fixed_addresses的值為 2206,也就是說永久映射區為 2206 個頁大小,即 8824 KB。

宏 FIXADDR_START 是永久映射區的起始地址,其計算方法是用FIXADDR_TOP減去該區域的大小。宏 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 擴展為 0xffffffffffe00000,宏 FIXADDR_TOP 擴展為 0xffffffffffdff000。對比一下 Linux 內核內存布局:

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 區域的結束地址。永久映射區的最高地址空間,分配給了 vsyscalls 區域:

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經過計算后,其值為 2047。也就是說,vsyscalls 區域擁有 2048 個頁,即 2048 \times 4K = 8M 內存空間。

另外,在永久映射區,還為 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 擴展為 128,其定義如下:

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

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

⑵臨時映射區

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

/*
      * 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

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

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

// 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)

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

⑶固定映射區內存布局

固定映射區內存布局如下圖所示:

圖片圖片

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

2.3頁表的精細構建流程

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

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

    // 首先獲取對應虛擬地址在全局頁目錄(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)里找到對應的項,如果該項為空,就用 __pgd_populate 函數建立與下一級頁目錄(PUD)的關聯,將 bm_pud 對應的物理地址填充進去,并標記好頁表類型為 PUD_TYPE_TABLE;接著在 PUD 中如法炮制,通過 fixmap_pud 找到對應項,為空時用 __pud_populate 關聯到頁中間目錄(PMD),填充 bm_pmd 物理地址;

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

三、Fixmap相關函數詳解

3.1 fix_to_virt

fix_to_virt 函數的功能是獲取索引值對應的固定映射地址。這個函數的實現很簡單:

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);
 }

首先檢查入參是否符合要求。fixed_addresses 中元素的最大值為 __end_of_fixed_addresses,該值僅作為邊界值使用,沒有其它意義,所以入參不能大于或等于該邊界值。宏 BUILD_BUG_ON 會在編譯時檢查給定條件是否為真,如果條件為真,則在打印錯誤信息后將進程掛起。

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

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

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

圖片圖片

3.2 virt_to_fix

virt_to_fix 函數實現的功能與 fix_to_virt 函數相反, 是將虛擬地址轉換成固定映射區的索引值,其定義如下:

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

函數執行時,首先檢查待轉換虛擬地址是否低于 FIXADDR_START 或者大于 FIXADDR_TOP 。如果條件為真,BUG_ON 會使程序陷入死循環。

檢查通過后,調用宏 __virt_to_fix 將虛擬地址轉換成索引值,該宏定義如下:

#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 (擴展為 12 )位后,得到了兩者之間頁號差。由于每個索引映射一個頁,所以頁號差就是索引差;而FIXADDR_TOP對應的索引值為 0,所以索引差就等于虛擬地址的索引值。

3.3 set_fixmap

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

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

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

// 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,包裝成結構體 pgprot_t。

3.4 clear_fixmap

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

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

clear_fixmap 內部也是調用 __set_fixmap 函數通過將頁屬性設置為 0 來實現清除映射的。當表項的存在 (Present) 位為 0 時,該表項是無效的。

3.5 set_fixmap_nocache

宏 set_fixmap_nocache 實現的功能與set_fixmap類似,也是建立索引與物理地址的映射關系。不過與 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 是頁標志位組合,其定義如下:

// 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中的各種標志以外,還包括 _PAGE_PCD (位 4)和 _PAGE_PWT (位 3)標志。

// 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 標志、PCD 標志、PAT 標志與內存類型范圍寄存器( Memory-Type Range Registers,MTRR)一起,共同決定了頁面的緩存類型。當把 PWT 標志位 和 PCD 標志位都設置為 1 時,不管 PAT 標志與 MTRR 是什么狀態,此時的緩存類型均為不可緩存( Uncacheable ,UC)狀態。

3.6 __set_fixmap

__set_fixmap 的實現涉及到較多內核分頁相關知識 -- 原理、數據結構、APIs 等,__set_fixmap 實現的功能是將物理地址映射到索引對應的虛擬地址空間。下面我們來看下 __set_fixmap 函數的實現細節。該函數接收 3 個參數,分別是:索引值 ,需要映射的物理地址以及頁屬性。

// 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 函數內部調用了native_set_fixmap,并將參數透傳給該函數。

四、Fixmap的典型應用場景實例

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

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

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

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

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

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

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

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

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

五、Fixmap與其他內存映射方式的異同對比

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

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

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

5.2與動態映射(如 Vmalloc)的優勢比較

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

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

六、全文總結

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

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

從系統性能與穩定性角度分析,Fixmap 使得內核在訪問特定內存區域時,能夠避開復雜的動態映射流程,從而顯著提升訪問效率,尤其在對時間和穩定性要求極高的場景下,這種優勢更為凸顯。同時,其固定的映射方式減少了因動態映射可能引發的錯誤與不確定性,有力地增強了系統的穩定性。

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

責任編輯:武曉燕 來源: 深度Linux
相關推薦

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

2022-02-11 10:47:17

CIOIT團隊企業

2025-08-22 09:51:55

macOSjadxJava

2025-01-15 13:25:47

MySQL命令數據庫

2019-11-27 10:38:37

數據分析數據準備工具

2025-05-14 00:01:10

RxJS異步編程響應式

2009-07-28 10:36:58

云計算Google秘密武器

2023-05-08 14:54:00

AI任務HuggingGPT

2019-11-27 10:40:34

數據工具CIO

2024-07-11 08:34:48

2010-09-02 16:09:43

Linux

2024-12-18 16:00:00

C++性能優化consteval

2025-05-27 10:00:00

Python數據類代碼

2019-02-27 09:44:01

CIO秘密武器顧問

2023-02-24 10:26:34

語音AI人工智能
點贊
收藏

51CTO技術棧公眾號

视频一区中文| 日韩av中文| 亚洲国产免费看| 亚洲精品成人久久电影| 国产一区视频免费观看| 日韩精品毛片| av一区二区三区四区| 日本成熟性欧美| 亚洲天堂一级片| 国产精品视屏| 欧美视频在线一区| 日韩成人手机在线| wwwxxx在线观看| 国产精品2024| 国产精品久久久久久中文字 | 久久久亚洲国产美女国产盗摄| 国产精品视频免费在线| 久久久久久久福利| 欧美三级情趣内衣| 亚洲国产美女久久久久| www.午夜av| 天天综合网站| 午夜欧美大尺度福利影院在线看| 一区三区二区视频| 国产精品99久久久久久人| 少妇人妻丰满做爰xxx| 久久99国产精品视频| 精品毛片乱码1区2区3区| www.色就是色| 亚洲少妇视频| 亚洲成人精品在线观看| 日本三级福利片| 黄色片在线播放| 99亚偷拍自图区亚洲| 91国产在线免费观看| 最新中文字幕免费| 久久精品二区三区| 久久久在线视频| 国产精品 欧美激情| 欧美限制电影| 亚洲人成免费电影| 欧美夫妇交换xxx| 欧美黄色一级| 91精品一区二区三区久久久久久| 不卡av免费在线| 不卡福利视频| 岛国av一区二区三区| 国产www免费| 日本在线观看大片免费视频| 日韩一区中文字幕| 亚欧精品在线| 国产精品ⅴa有声小说| www成人在线观看| 精品免费视频123区| 亚洲精品成av人片天堂无码| 国产成人啪免费观看软件| 成人综合国产精品| 国产精品国产一区二区三区四区 | 久久久久久com| 欧美老熟妇一区二区三区| 日韩免费视频| 日韩在线小视频| 亚洲熟女毛茸茸| 91精品国产乱码久久久久久 | 成人做爰视频网站| 韩国三级av在线免费观看| 国产91精品入| 亚洲国产精品va在看黑人| 精品无码av一区二区三区| 亚洲午夜免费| 日韩成人中文字幕| 91中文字幕永久在线| 精品免费av| 日韩有码在线视频| 少妇久久久久久被弄高潮| 欧美日韩国产精品一区二区亚洲| 欧美激情一级精品国产| 国产成人免费观看视频| 久久国产主播| 国产日韩在线看片| 精品国产一级片| 9色porny自拍视频一区二区| 久久精品美女| 夜级特黄日本大片_在线| 国产精品国产三级国产aⅴ无密码 国产精品国产三级国产aⅴ原创 | 成人av在线影院| 久久综合九色综合久99| aaa在线观看| 一区二区三区精品视频在线| 777av视频| 日韩av电影资源网| 日韩一区二区麻豆国产| 欧美无人区码suv| 日本一区二区高清不卡| 久久97久久97精品免视看| 亚洲欧美在线视频免费| 欧美aa在线视频| 99精品国产高清在线观看| 国产精品国产高清国产| 国产精品伦一区| 波多野结衣av一区二区全免费观看| 大胆人体一区二区| 91精品国产福利在线观看| 亚洲天堂美女视频| 97精品在线| 98精品国产自产在线观看| 这里只有精品999| 2019年精品视频自拍| 男人的天堂久久精品| 99理论电影网| www.久久热.com| 亚洲成精国产精品女| 天天色综合社区| 国产精品任我爽爆在线播放| 色一情一乱一区二区| www.国产高清| 国产成人精品一区二| 日韩电影免费观看高清完整| 欧美色图天堂| 欧美日韩精品是欧美日韩精品| 李丽珍裸体午夜理伦片| 国产国产精品| 日韩美女在线观看一区| 高h放荡受浪受bl| 1024精品合集| 日韩av片网站| 免费成人av| 97精品国产91久久久久久| 国产视频在线免费观看| 欧美经典三级视频一区二区三区| 国产极品尤物在线| 日韩精品一区国产| 色777狠狠综合秋免鲁丝| 欧美 日韩 精品| youjizz久久| 黄色大片中文字幕| 亚洲亚洲一区二区三区| 欧美丰满少妇xxxxx做受| 国产又粗又黄又爽| 国产精品区一区二区三区| 中文字幕乱码人妻综合二区三区| 国产精品自在| 久久久久久国产精品三级玉女聊斋 | 中日韩免视频上线全都免费| 97av在线播放| 天堂网av在线播放| 亚洲在线一区二区三区| 国产伦理在线观看| 欧美精品91| 波多野结衣成人在线| 影音先锋在线视频| 日韩欧美第一区| 国产盗摄x88av| 国产成人在线视频网站| 国产激情在线看| 少妇高潮一区二区三区99小说| 久久久久久亚洲精品美女| 亚洲午夜av久久乱码| 久久久久久在线观看| 久久久久9999亚洲精品| 日本新janpanese乱熟| 精品一区电影| 成人在线国产精品| 18+激情视频在线| 精品免费视频一区二区| 日本在线视频免费观看| 99九九99九九九视频精品| 日韩av综合在线观看| 中文字幕亚洲影视| 国产精品免费一区豆花| 69久久久久| 欧美一区二区久久| 国产中文字幕免费| 91婷婷韩国欧美一区二区| 成人观看免费完整观看| 成人同人动漫免费观看| 91中文在线观看| 亚洲制服国产| 日韩成人中文字幕在线观看| 欧美日韩一级黄色片| 国产精品久线在线观看| 免费人成视频在线播放| 制服诱惑一区二区| 亚洲二区三区四区| 日韩精品免费视频一区二区三区| 欧美亚州一区二区三区| 国产高清视频在线播放| 欧美一级搡bbbb搡bbbb| 欧美videossex极品| 国产精品久久久久久久久免费丝袜| 日本一二三区在线| 午夜亚洲影视| 日韩视频在线免费播放| 国产精品国产| 国产精品一区av| 免费看电影在线| 国产亚洲a∨片在线观看| 精品国自产在线观看| 色综合天天综合在线视频| 国产午夜精品久久久久久久久| 国产成人在线视频免费播放| 日韩免费高清在线| 欧美日韩一卡| 杨幂一区欧美专区| 超碰地址久久| 国产欧美韩国高清| 黄色软件视频在线观看| 日韩在线观看免费全| 国产精品久久久影院| gogo在线观看| 国产香蕉一区二区三区在线视频 | 久久久久久久久99| 国产日韩精品久久久| 国产精品一区二区人妻喷水| 精品中文av资源站在线观看| av动漫在线看| 午夜视频精品| 一区不卡视频| 久草在线成人| 久久国产精品精品国产色婷婷| 96sao精品免费视频观看| 欧洲日本亚洲国产区| 黄色美女视频在线观看| 久久精品久久久久| 3p视频在线观看| 亚洲欧美日韩天堂| 天堂国产一区二区三区| 欧美大片在线观看一区二区| 国产一区二区三区成人| 欧美婷婷六月丁香综合色| 国偷自拍第113页| 亚洲一二三四区| 日本福利片在线观看| 国产精品久久精品日日| 手机看片日韩av| 久久久久久黄色| 噜噜噜在线视频| 99久久久久久| 好吊一区二区三区视频| 不卡视频一二三| 国产精品熟妇一区二区三区四区| 国产乱人伦偷精品视频不卡| 亚洲黄色小视频在线观看| 免费永久网站黄欧美| 欧美一区二区三区爽大粗免费| 国内成人在线| 日韩美女爱爱视频| 影音先锋在线一区| 免费成人午夜视频| 亚洲一区中文| 精品久久久久久久免费人妻| 亚洲欧美网站| 一级特黄性色生活片| 奇米色一区二区| 91亚洲精品久久久蜜桃借种| 激情av综合网| 国产黑丝在线视频| 国产iv一区二区三区| 污污免费在线观看| 不卡在线观看av| 狠狠人妻久久久久久综合蜜桃| 91丝袜美腿高跟国产极品老师| 日本丰满少妇裸体自慰 | 欧美日韩免费一区二区三区| 成人a v视频| 欧美人xxxx| 精品国产伦一区二区三区| 日韩欧美国产电影| 性感美女一级片| 这里只有精品在线观看| 激情影院在线观看| 久久久久久久久久久久av| 欧美在线极品| 国产精品一二三视频| 欧美经典一区| 欧美成人在线免费观看| 久久精品国产68国产精品亚洲| 熟女熟妇伦久久影院毛片一区二区| 午夜日韩av| 亚洲中文字幕久久精品无码喷水| 久久成人av少妇免费| 9191在线视频| 久久久久久夜精品精品免费| 可以免费看av的网址| 午夜精品福利视频网站| 亚洲va在线观看| 91麻豆精品国产91久久久更新时间| 性一交一乱一色一视频麻豆| 亚洲欧美日韩区| a在线免费观看| 国产91|九色| 国产一区二区视频在线看| 精品日本一区二区三区在线观看| 四季av一区二区凹凸精品| 男人天堂a在线| 久久99精品视频| 亚洲国产精品无码久久久久高潮 | 国产视频综合在线| 久做在线视频免费观看| 91黄色8090| 青草伊人久久| 欧美一区二区三区四区在线观看地址| 婷婷色综合网| 蜜臀久久99精品久久久酒店新书 | 在线中文字幕一区| 亚洲第一页视频| 色偷偷噜噜噜亚洲男人的天堂| 999精品网| 91在线色戒在线| 欧美综合在线视频观看| 日本手机在线视频| 国产真实乱对白精彩久久| 特级西西www444人体聚色 | 精品国产午夜福利| 欧美一级在线视频| 五月天婷婷在线视频| 5252色成人免费视频| 97人人澡人人爽91综合色| 久久艳妇乳肉豪妇荡乳av| 欧美激情第二页| 欧美一级特黄aaa| 中文文精品字幕一区二区| 久久一区二区三区视频| 麻豆精品少妇| 欧美日韩在线不卡一区| 激情视频一区二区三区| 国产亚洲视频一区| 国产女主播视频一区二区| av大全在线观看| 日韩电影免费观看在线观看| 女同一区二区免费aⅴ| 成人国产精品久久久| 欧美在线免费看视频| 日韩精品一区二区三区不卡| 91香蕉视频mp4| 亚洲黄色一区二区| 欧美成人一区二区| 18+激情视频在线| 51精品国产人成在线观看| 久久福利影院| 免费看涩涩视频| 欧美激情中文字幕一区二区| 四虎成人在线观看| 亚洲欧洲在线视频| 樱桃视频成人在线观看| 久久久久久九九九九| 免费日韩视频| 亚洲成人黄色av| 91国偷自产一区二区开放时间 | 日韩精品你懂的| 欧美激情一区二区三区蜜桃视频 | 97精品在线播放| 欧美丰满一区二区免费视频| 哥也色在线视频| 97中文在线| 亚洲国内欧美| 欧亚乱熟女一区二区在线| 欧美日韩午夜激情| 国产福利在线观看| 国产欧美日韩免费| 影视亚洲一区二区三区| 69久久精品无码一区二区| 一个色在线综合| 污污的视频网站在线观看| 91av在线网站| 日韩国产专区| 中国老熟女重囗味hdxx| 亚洲成av人影院在线观看网| 头脑特工队2在线播放| 国产精品免费一区豆花| 在线观看国产精品入口| 中文字幕一区二区三区乱码不卡| 都市激情亚洲色图| 日本免费中文字幕在线| 成人黄色片视频网站| 久久不射2019中文字幕| 538精品视频| 日韩色视频在线观看| 9999在线视频| 亚洲国产婷婷香蕉久久久久久99| 国产一区二区看久久| 日本一级片免费看| 在线视频欧美性高潮| 亚洲性视频在线| 欧美日韩免费高清一区色橹橹 | 中日韩av在线| 欧美精品生活片| 要久久爱电视剧全集完整观看| 欧美三级午夜理伦三级富婆| 一区二区三区高清在线| 婷婷国产在线| 亚洲qvod图片区电影| 久久99伊人| 欧美日韩在线视频免费播放| 亚洲欧洲日产国产网站| 香蕉免费一区二区三区在线观看 | 久久高清免费视频| 一区二区三区四区在线观看视频 | 亚洲欧美电影在线观看| 厕沟全景美女厕沟精品|