精品欧美一区二区三区在线观看 _久久久久国色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技術棧公眾號

欧美国产欧美亚州国产日韩mv天天看完整| 色屁屁草草影院ccyy.com| www.黄色片| 99亚洲一区二区| 国产亚洲欧美日韩一区二区| 最新av免费在线观看| 91av久久| 中文字幕制服丝袜成人av| 国产精品视频入口| 亚洲网站免费观看| 国产偷自视频区视频一区二区| 尤物yw午夜国产精品视频| 永久看看免费大片| 在线一区视频观看| 亚洲成人午夜电影| 亚洲综合激情五月| 美女做暖暖视频免费在线观看全部网址91 | 国产小视频免费在线网址| 日韩在线视屏| 日韩成人激情视频| 午夜视频在线观| 蜜桃精品在线| 欧美日韩中文字幕综合视频| 日本丰满少妇黄大片在线观看| 天天干天天色天天| 国产成人免费高清| 亚洲iv一区二区三区| 天堂av免费在线观看| 一区二区国产精品| 九九热在线精品视频| 美国一级黄色录像| 少妇精品久久久| 亚洲精品v天堂中文字幕| 手机看片国产精品| 国产一区二区三区免费在线| 欧美视频一二三区| 7777精品伊人久久久大香线蕉 | 欧美黑人狂野猛交老妇| 欧美成人久久久免费播放| 日本成人中文| 亚洲福利精品在线| 白嫩情侣偷拍呻吟刺激| 综合视频一区| 亚洲第一中文字幕| 亚洲av永久无码精品| h视频久久久| 欧美精品一区二区三区久久久| japan高清日本乱xxxxx| 亚洲综合资源| 欧美精品v国产精品v日韩精品| 国产一级做a爰片久久| 青青草免费在线视频观看| 国产女主播在线写真| 久久久久久久久久久电影| 欧美一级爱爱| 国产黄在线观看免费观看不卡| 国产夜色精品一区二区av| 久久青青草综合| 国自产拍在线网站网址视频| 久久久蜜臀国产一区二区| 日本一区二区久久精品| 国产精品影院在线| 国产精品护士白丝一区av| 中文字幕剧情在线观看一区| 国产日产一区二区| 亚洲综合丁香婷婷六月香| 隔壁人妻偷人bd中字| sm捆绑调教国产免费网站在线观看 | 筱崎爱全乳无删减在线观看| 欧美视频免费在线观看| 午夜激情福利在线| 999色成人| 欧美成人午夜电影| 亚洲国产精品无码久久久久高潮| 小说区图片区色综合区| 日韩在线视频网站| 天天干中文字幕| 国产精品日韩精品欧美精品| 亚洲免费三区一区二区| 亚洲图片欧美午夜| 网站永久看片免费| 亚洲无吗在线| 国产精品草莓在线免费观看| 国产特级黄色片| 99国产精品久久| 亚洲三区在线| a√中文在线观看| 精品污污网站免费看| 成人免费播放视频| 国产乱码精品一区二区亚洲| 北条麻妃一区二区三区中文字幕 | 五月天网站亚洲| 国产精品萝li| 久久久精品国产一区二区三区| 大胆av不用播放器在线播放 | ww久久综合久中文字幕| 欧美一区二区美女| 亚洲 小说 欧美 激情 另类| 91精品久久久久久久蜜月| 97视频色精品| 国产男男gay体育生白袜| 99久久精品国产精品久久 | 在线国产精品一区| 国产精品一久久香蕉国产线看观看| 国产毛片久久久久| 久久久精品中文字幕麻豆发布| 一区二区三区免费看| 理论不卡电影大全神| 91麻豆精品国产| 亚洲国产日韩一区无码精品久久久| 欧美激情日韩| 国产精品三级美女白浆呻吟 | 国产一区二区三区免费在线 | 一本在线高清不卡dvd| gai在线观看免费高清| 亚洲三级网页| 欧美精品久久一区二区 | 国产麻豆精品在线| 欧美亚洲免费高清在线观看| 欧洲一区二区三区| 91麻豆精品国产自产在线观看一区| 99久久国产精| 最新日韩欧美| 99理论电影网| 国产日产一区二区| 欧美人与z0zoxxxx视频| 天天躁日日躁aaaa视频| 性色av一区二区怡红| 国产精品一区二区三区在线观| av在线free| 欧美精品日韩精品| youjizz亚洲女人| 成人看av片| 久久电影网站中文字幕| 日本一区二区在线视频观看| 成人三级高清视频在线看| 精品国产污网站| 青娱乐国产在线| 国产一区二区三区免费播放| 亚洲一区三区电影在线观看| 欧美色网在线| 在线观看国产精品日韩av| 亚洲日本视频在线观看| 91玉足脚交白嫩脚丫在线播放| 免费看欧美黑人毛片| 亚洲电影一区| 久久久久久香蕉网| 天堂中文在线观看视频| 天天爽夜夜爽夜夜爽精品视频| 图片区偷拍区小说区| 欧美日韩理论| 国产成人免费电影| 波多野结衣久久| 亚洲精品乱码久久久久久按摩观| 亚洲老女人av| 亚洲天堂av片| 久久精品国产亚洲a| 欧美在线视频一区二区三区| 亚洲爱爱视频| 日韩中文字幕精品| 99在线观看免费| 亚洲二区在线观看| yy1111111| 久久一区视频| 亚洲国产欧美日韩| 欧美一区在线观看视频| 国模精品系列视频| 免费在线超碰| 欧美日韩高清一区二区三区| 国产老头老太做爰视频| 国产电影一区二区三区| www.亚洲成人网| 亚州av日韩av| 91精品国产自产在线老师啪| 久久www人成免费看片中文| 亚洲国产天堂久久综合| 91porny九色| 欧美精品高清| 午夜精品久久久久久久99樱桃| 在线观看国产网站| 日本aⅴ亚洲精品中文乱码| 樱空桃在线播放| 偷拍亚洲色图| 91久久久国产精品| a国产在线视频| 在线午夜精品自拍| 丰满肉嫩西川结衣av| 色综合久久久久久久久久久| 在线免费观看亚洲视频| 99久久久精品| 国产农村妇女精品久久| 国产日产高清欧美一区二区三区| 亚洲午夜精品久久久中文影院av | 久久99精品久久久久久| 久久久久久久久久网| 色综合咪咪久久网| 久久久精品国产一区二区三区| 9999精品| 极品尤物久久久av免费看| 91久久精品www人人做人人爽| 在线看片国产福利你懂的| 久久久www成人免费精品张筱雨 | 成人在线综合网站| 91av俱乐部| 精品动漫3d一区二区三区免费| 日韩一区免费观看| 女同另类激情重口| 91在线观看免费观看| av激情成人网| 4438全国亚洲精品在线观看视频| a免费在线观看| 日韩在线激情视频| 巨骚激情综合| 亚洲国内高清视频| 午夜精品一二三区| 欧美精品丝袜中出| 国产成人av免费| 欧美性猛交xxxx| 久久精品视频国产| 亚洲精品国产视频| 男女男精品视频网站| 亚洲网站在线免费观看| 2021中文字幕一区亚洲| 在线播放第一页| 国产精品一区在线观看你懂的| 亚洲欧洲日本精品| 日韩电影免费一区| 88av.com| 久久亚洲图片| 日韩av播放器| 免费亚洲网站| 欧美v在线观看| 国产农村妇女精品一二区| 国产成人无码a区在线观看视频| 一本一道久久a久久精品蜜桃 | 国产精品v亚洲精品v日韩精品| 一区二区高清视频| 四季av一区二区凹凸精品| 天堂社区 天堂综合网 天堂资源最新版| 日日狠狠久久偷偷综合色| 国产一区二区三区奇米久涩| 加勒比久久高清| 久久久久久久有限公司| 麻豆一区二区| 久久国产精品 国产精品| 日韩欧美美女在线观看| 欧美激情第六页| 精品久久视频| 一区二区三区三区在线| 中文永久免费观看| 中文字幕在线免费不卡| 国产探花在线视频| 亚洲美女屁股眼交3| 美国黄色小视频| 亚欧色一区w666天堂| 800av免费在线观看| 色悠悠久久综合| 亚洲 小说区 图片区| 欧美日韩午夜精品| 99热这里只有精品在线观看| 日韩精品资源二区在线| 人妻视频一区二区三区| 亚洲视频欧洲视频| 天堂中文а√在线| 九九热最新视频//这里只有精品| 黄网在线免费看| 青青久久aⅴ北条麻妃| 成人国产精品| 不卡一区二区三区四区五区| 亚洲自拍电影| 在线观看免费91| 亚洲激情欧美| 污污视频网站免费观看| 极品美女销魂一区二区三区| 肉色超薄丝袜脚交| 97aⅴ精品视频一二三区| 55夜色66夜色国产精品视频| 中文字幕在线网站| 欧美一区二区三区在线观看| 婷婷色在线视频| 日韩一区二区欧美| av在线最新| 国产日韩欧美影视| 久久精品66| 国产精品99久久久久久大便| 99亚洲一区二区| www.51色.com| 久久综合久久综合久久| 老熟妇高潮一区二区三区| 欧美日韩国产在线看| 91精品国产乱码久久| 亚洲国产天堂久久综合网| 日本最黄一级片免费在线| 68精品久久久久久欧美| 伊人久久一区| 日韩精品一线二线三线| 欧美婷婷在线| 中文字幕资源在线观看| 91亚洲大成网污www| 欧美激情国产精品免费| 欧美日韩一区二区在线视频| 亚洲人妻一区二区三区| 久久成人综合视频| 97精品国产综合久久久动漫日韩| 日韩三区在线观看| www.日本一区| 粉嫩aⅴ一区二区三区四区| 免费福利视频网站| 精品国产乱码久久久久酒店| 国产露脸无套对白在线播放| 亚洲少妇中文在线| gogo高清午夜人体在线| 亚洲一区美女视频在线观看免费| 精品一区二区三| 日本三级免费网站| 国产91对白在线观看九色| 国产高清视频免费在线观看| 欧洲视频一区二区| 欧美日韩免费做爰大片| 久久久免费高清电视剧观看| 欧美黄色a视频| 日韩欧美一区二区视频在线播放| 亚洲精选91| 老司机免费视频| 一区二区视频免费在线观看| 国产一区二区视频免费观看| 在线播放国产精品| 欧美日韩尤物久久| 日韩精品一区二区三区色偷偷| 久久婷婷激情| 玖草视频在线观看| 图片区小说区区亚洲影院| 亚洲av无码片一区二区三区| 久久伊人精品一区二区三区| 暗呦丨小u女国产精品| 销魂美女一区二区三区视频在线| 91亚洲一区二区| 亚洲免费观看高清完整版在线| 国产精品久久免费| 久久艹在线视频| 国产亚洲观看| 国产精品免费看久久久无码| 国产酒店精品激情| 久久免费在线观看视频| 精品久久久三级丝袜| 超碰97免费在线| 久久涩涩网站| 石原莉奈在线亚洲二区| 中字幕一区二区三区乱码| 欧美午夜在线观看| 精品视频在线一区二区| 成人淫片在线看| 欧美激情1区2区| 精品一区二区三区四区五区六区| 精品动漫一区二区三区| 五月婷婷伊人网| 国产精品r级在线| 99久久久久国产精品| 日韩精品在线播放视频| 亚洲一区中文日韩| 四虎国产精品永远| 国产精品一区二区久久久久| 亚洲破处大片| 无码人妻精品一区二区三| 精品国产91乱高清在线观看| 精品福利视频导航大全| 亚洲丝袜自拍清纯另类| aaa黄色大片| 精品国产乱码久久久久久虫虫漫画| 深夜福利视频在线免费观看| 国产精品电影观看| 亚洲一区二区| 亚洲一区二区三区四区五区六区| 欧洲生活片亚洲生活在线观看| 免费a级人成a大片在线观看| 国产精品久久久久久久天堂第1集| 亚洲一区黄色| 国产jizz18女人高潮| 亚洲成人999| 日本美女久久| 免费在线看黄色片| 国产视频一区二区三区在线观看| 国产手机视频在线| 欧美一区二区三区免费观看| 先锋资源久久| 99久久久久久久久久| 欧美精品99久久久**| 久久亚洲导航| 亚洲自拍的二区三区| 成人av在线一区二区三区| 中文字幕一区二区三区四区视频| 欧美xxxx18性欧美| 国产欧美日韩精品一区二区三区 | 久久韩剧网电视剧| 人体久久天天| 精产国品一二三区| 国产精品18久久久久久久久久久久 | 无码精品人妻一区二区| 国产精品亚洲综合天堂夜夜| 亚洲韩日在线|