Linux內存管理:Fixmaps(固定映射地址)和ioremap【轉】


轉自:https://rtoax.blog.csdn.net/article/details/114749083

目錄

Fixmaps和ioremap

映射

ioremap工作原理

早期ioremap的使用

Links

相關閱讀


Fix-Mapped地址是一組特殊的編譯時地址,其對應的物理地址不必是線性地址減__START_KERNEL_map;

ioremap 功能是將給定的物理地址映射為虛擬地址。

 

Fixmaps和ioremap

Fix-Mapped地址是一組特殊的編譯時地址,其對應的物理地址不必是線性地址減__START_KERNEL_map。每個固定映射的地址都映射一個頁面框架,內核將其用作永遠不會更改其地址的指針。這就是這些地址的重點。如評論所述:to have a constant address at compile time, but to set the physical address only in the boot process。您可能還記得,在最早的部分中,我們已經設置了level2_fixmap_pgt

  1.  
    NEXT_PAGE(level2_fixmap_pgt)
  2.  
    .fill 506,8,0
  3.  
    .quad level1_fixmap_pgt - __START_KERNEL_map + _PAGE_TABLE
  4.  
    .fill 5,8,0
  5.  
     
  6.  
    NEXT_PAGE(level1_fixmap_pgt)
  7.  
    .fill 512,8,0

如您所見level2_fixmap_pgt,緊隨其后的level2_kernel_pgt是內核代碼+數據+ bss。每個修訂映射的地址都由一個整數索引表示,該整數索引fixed_addressesarch / x86 / include / asm / fixmap.h中的枚舉中定義。例如,它包含以下各項的條目VSYSCALL_PAGE-如果啟用了對舊版vsyscall頁面的仿真,FIX_APIC_BASE則為本地apic等。在虛擬內存中,固定映射區域位於模塊區域:

  1.  
    +-----------+-----------------+---------------+------------------+
  2.  
    | | | | |
  3.  
    |kernel text| kernel | | vsyscalls |
  4.  
    | mapping | text | Modules | fix-mapped |
  5.  
    |from phys 0| data | | addresses |
  6.  
    | | | | |
  7.  
    +-----------+-----------------+---------------+------------------+
  8.  
    __START_KERNEL_map __START_KERNEL MODULES_VADDR 0xffffffffffffffff

基本的虛擬地址和fix-mapped區域的大小由以下兩個宏表示:

  1.  
    #define FIXADDR_SIZE (__end_of_permanent_fixed_addresses << PAGE_SHIFT)
  2.  
    #define FIXADDR_START (FIXADDR_TOP - FIXADDR_SIZE)

__end_of_permanent_fixed_addressesfixed_addresses枚舉的一個元素,正如我上面所寫:每個固定映射的地址都由一個整數索引表示,該索引在中定義fixed_addressesPAGE_SHIFT確定頁面的大小。例如,我們可以通過1 << PAGE_SHIFT表達式獲得一頁的大小。

在我們的例子中,我們需要獲取修訂映射區域的大小,而不僅僅是一個page,這就是為什么我們__end_of_permanent_fixed_addresses要獲取修訂映射區域的大小的原因。的__end_of_permanent_fixed_addresses是最后索引fixed_addresses枚舉或換句話說,__end_of_permanent_fixed_addresses包含在一個固定的映射區域頁面量。因此,如果將__end_of_permanent_fixed_addresses頁面大小值的乘積,我們將獲得固定映射區域的大小。以我的為例,它超過了536千字節。在您的情況下,它可能是一個不同的數字,因為大小取決於修訂映射地址的數量,而修訂映射地址的數量取決於內核的配置。

第二個FIXADDR_START宏只是從固定映射區域的最后一個地址中減去固定映射區域的大小,以獲取其基本虛擬地址。FIXADDR_TOP是從vsyscall空間的基地址開始的四舍五入地址:

#define FIXADDR_TOP (round_up(VSYSCALL_ADDR + PAGE_SIZE, 1<<PMD_SHIFT) - PAGE_SIZE)

fixed_addresses枚舉被用作指數由獲得虛擬地址fix_to_virt的功能。該功能的實現很容易:

  1.  
    static __always_inline unsigned long fix_to_virt(const unsigned int idx)
  2.  
    {
  3.  
    BUILD_BUG_ON(idx >= __end_of_fixed_addresses);
  4.  
    return __fix_to_virt(idx);
  5.  
    }

首先,它檢查為該fixed_addresses枚舉給出的索引是否大於或等於__end_of_fixed_addressesBUILD_BUG_ON宏,然后返回該__fix_to_virt宏的結果:

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

在這里,我們向左移動fix-mapped區域的給定索引,該索引PAGE_SHIFT決定了我在上面所寫的頁面大小,並從區域FIXADDR_TOP的最高地址那里減去它fix-mapped

  1.  
    +-----------------+
  2.  
    | PAGE 1 | FIXADDR_TOP (virt address)
  3.  
    | PAGE 2 |
  4.  
    | PAGE 3 |
  5.  
    | PAGE 4 (idx) | x - 4
  6.  
    | PAGE 5 |
  7.  
    +-----------------+

有一個反函數,用於獲取與給定虛擬地址相對應的修訂映射區域的索引:

  1.  
    static inline unsigned long virt_to_fix(const unsigned long vaddr)
  2.  
    {
  3.  
    BUG_ON(vaddr >= FIXADDR_TOP || vaddr < FIXADDR_START);
  4.  
    return __virt_to_fix(vaddr);
  5.  
    }

virt_to_fix采用虛擬地址,檢查該地址是間FIXADDR_STARTFIXADDR_TOP並調用__virt_to_fix其實現為宏:

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

正如我們可以看到的,__virt_to_fix宏清除第一12在給定的虛擬地址位,從最后地址的減去它fix-mapped區域(FIXADDR_TOP),並轉移該結果正確的PAGE_SHIFT12。讓我解釋一下它是如何工作的。

與前面的示例一樣(在__fix_to_virt宏中),我們從修復映射區域的頂部開始。我們還從上到下從頭到尾搜索與給定虛擬地址相對應的固定映射區域的索引。如您所見,首先我們將12使用x & PAGE_MASK表達式清除給定虛擬地址中的前幾位。這使我們能夠獲取頁面的基地址。如果給定的虛擬地址指向頁面的開頭/中間或結尾的某個位置,而不指向頁面的基地址,則需要執行此操作。在下一步中,從中減去此值FIXADDR_TOP,這將為我們提供修訂映射區域中相應頁面的虛擬地址。最后,我們將這個地址的值除以PAGE_SHIFT。這為我們提供了與給定虛擬地址相對應的固定映射區域的索引。看起來可能很難,但是如果您逐步進行此操作,則可以確保該__virt_to_fix宏非常簡單。

就這樣。現在,我們對fix-mapped地址有所了解,但是接下來就足夠了。

Fix-mapped地址在linux內核中的不同位置使用。IDT描述符存儲在此處,英特爾可信執行技術UUID存儲在fix-mappedFIX_TBOOT_BASE索引,Xen bootmap等開始的區域中...我們已經在linux內核初始化fix-mapped的第五部分中看到了一些地址。我們fix-mapped在早期ioremap初始化中使用area 。讓我們更仔細地看一下它,並嘗試了解ioremap它是什么,如何在內核中實現它以及它與fix-mapped地址的關系。

 

映射

Linux內核提供了許多不同的原語來管理內存。此時此刻,提出I/O memory。每個設備都通過對其寄存器的讀/寫操作來控制。例如,驅動程序可以通過寫入設備的寄存器來關閉/打開設備,或者通過讀取設備的寄存器來獲取設備的狀態。除寄存器外,許多設備還具有緩沖區,驅動程序可以在其中寫入或讀取內容。眾所周知,目前有兩種訪問設備寄存器和數據緩沖區的方法:

  • 通過I / O端口;
  • 將所有寄存器映射到存儲器地址空間;

在第一種情況下,設備的每個控制寄存器都具有多個輸入和輸出端口。設備驅動程序可以從一個端口讀取和寫入兩個inout指示,我們已經看到了。如果您想了解當前注冊的端口區域,可以通過訪問來了解它們/proc/ioports

  1.  
    $ cat /proc/ioports
  2.  
    0000-0cf7 : PCI Bus 0000:00
  3.  
    0000-001f : dma1
  4.  
    0020-0021 : pic1
  5.  
    0040-0043 : timer0
  6.  
    0050-0053 : timer1
  7.  
    0060-0060 : keyboard
  8.  
    0064-0064 : keyboard
  9.  
    0070-0077 : rtc0
  10.  
    0080-008f : dma page reg
  11.  
    00a0-00a1 : pic2
  12.  
    00c0-00df : dma2
  13.  
    00f0-00ff : fpu
  14.  
    00f0-00f0 : PNP0C04:00
  15.  
    03c0-03df : vesafb
  16.  
    03f8-03ff : serial
  17.  
    04d0-04d1 : pnp 00:06
  18.  
    0800-087f : pnp 00:01
  19.  
    0a00-0a0f : pnp 00:04
  20.  
    0a20-0a2f : pnp 00:04
  21.  
    0a30-0a3f : pnp 00:04
  22.  
    0cf8-0cff : PCI conf1
  23.  
    0d00-ffff : PCI Bus 0000:00
  24.  
    ...
  25.  
    ...
  26.  
    ...

/proc/ioports提供有關哪個驅動程序使用I/O端口區域的哪個地址的信息。所有這些存儲器區域的,例如0000-0cf7,用所要求保護的request_region功能從包括/ LINUX / ioport.h。實際上request_region是一個宏,定義為:

#define request_region(start,n,name) __request_region(&ioport_resource, (start), (n), (name), 0)

如我們所見,它帶有三個參數:

  • start -地區開始;
  • n -區域的長度;
  • name -請求者名稱。

request_region分配I/O端口區域。通常在check_region之前調用該函數request_region以檢查給定的地址范圍是否可用以及release_region釋放存儲區域的函數。request_region返回指向該resource結構的指針。該resource結構表示系統資源的樹狀子集的抽象。我們已經resource在內核初始化過程的第五部分中看到了該結構,它看起來如下:

  1.  
    struct resource { //該resource結構表示系統資源的樹狀子集的抽象
  2.  
    resource_size_t start;
  3.  
    resource_size_t end;
  4.  
    const char *name;
  5.  
    unsigned long flags;
  6.  
    struct resource *parent, *sibling, *child;
  7.  
    };

並包含資源,名稱等的開始和結束地址每一個resource結構包含指向的parentsiblingchild資源。因為它有一個父母和一個孩子,這意味着資源的每個子集都具有根resource結構。例如,對於I/O端口,它是以下ioport_resource結構:

  1.  
    struct resource ioport_resource = {
  2.  
    .name = "PCI IO",
  3.  
    .start = 0,
  4.  
    .end = IO_SPACE_LIMIT,
  5.  
    .flags = IORESOURCE_IO,
  6.  
    };
  7.  
    EXPORT_SYMBOL(ioport_resource);

或者iomem,它是iomem_resource結構:

  1.  
    struct resource iomem_resource = {
  2.  
    .name = "PCI mem",
  3.  
    .start = 0,
  4.  
    .end = -1,
  5.  
    .flags = IORESOURCE_MEM,
  6.  
    };

正如我之前提到的,request_regions它用於注冊I / O端口區域,並且該宏在內核中的許多地方都使用。例如,讓我們看一下drivers/char/rtc.c。此源代碼文件在linux內核中提供了Real Time Clock接口。作為每個內核模塊,rtc模塊包含module_init定義:

module_init(rtc_init);

這里rtc_initrtc初始化函數。此功能在同一rtc.c源代碼文件中定義。在rtc_init函數中,我們可以看到對函數的幾個調用,例如rtc_request_region包裝request_region

r = rtc_request_region(RTC_IO_EXTENT);

rtc_request_region被調用

r = request_region(RTC_PORT(0), size, "rtc");

RTC_IO_EXTENT是內存區域的大小,它是0x8"rtc"是該區域的名稱,並且RTC_PORT是:

#define RTC_PORT(x) (0x70 + (x))

因此,request_region(RTC_PORT(0), size, "rtc")我們使用來注冊一個存儲區域,該存儲區域的起始位置為0x70,大小為0x8。讓我們看一下/proc/ioports

  1.  
    ~$ sudo cat /proc/ioports | grep rtc
  2.  
    0070-0077 : rtc0

所以,我們明白了!好的,就是I / O端口了。與驅動程序通信的第二種方法是使用I/O內存。如前所述,這是通過將控制寄存器和設備內存映射到內存地址空間來實現的。I/O內存是設備通過總線向CPU提供的一組連續地址。內核沒有直接使用任何內存映射的I / O地址。有一個特殊ioremap功能允許我們將總線上的物理地址轉換為內核虛擬地址。換句話說,ioremap映射I / O物理內存區域以使其可從內核訪問。該ioremap函數有兩個參數:

  • 內存區域的開始;
  • 內存區域的大小;

I / O內存映射API提供了用於檢查,請求和釋放作為I / O內存的內存區域的功能。為此,有三個功能:

  • request_mem_region
  • release_mem_region
  • check_mem_region
  1.  
    ~$ sudo cat /proc/iomem
  2.  
    ...
  3.  
    ...
  4.  
    ...
  5.  
    be826000-be82cfff : ACPI Non-volatile Storage
  6.  
    be82d000-bf744fff : System RAM
  7.  
    bf745000-bfff4fff : reserved
  8.  
    bfff5000-dc041fff : System RAM
  9.  
    dc042000-dc0d2fff : reserved
  10.  
    dc0d3000-dc138fff : System RAM
  11.  
    dc139000-dc27dfff : ACPI Non-volatile Storage
  12.  
    dc27e000-deffefff : reserved
  13.  
    defff000-deffffff : System RAM
  14.  
    df000000-dfffffff : RAM buffer
  15.  
    e0000000-feafffff : PCI Bus 0000:00
  16.  
    e0000000-efffffff : PCI Bus 0000:01
  17.  
    e0000000-efffffff : 0000:01:00.0
  18.  
    f7c00000-f7cfffff : PCI Bus 0000:06
  19.  
    f7c00000-f7c0ffff : 0000:06:00.0
  20.  
    f7c10000-f7c101ff : 0000:06:00.0
  21.  
    f7c10000-f7c101ff : ahci
  22.  
    f7d00000-f7dfffff : PCI Bus 0000:03
  23.  
    f7d00000-f7d3ffff : 0000:03:00.0
  24.  
    f7d00000-f7d3ffff : alx
  25.  
    ...
  26.  
    ...
  27.  
    ...

這些地址的一部分來自該e820_reserve_resources函數的調用。我們可以在arch/x86/kernel/setup.c找到對此函數的調用,並且函數本身在arch/x86/kernel/e820.c.中定義e820_reserve_resources遍歷e820映射並將內存區域插入到根iomem資源結構中。e820插入iomem資源中的所有內存區域具有以下類型:

  1.  
    static inline const char *e820_type_to_string(int e820_type)
  2.  
    {
  3.  
    switch (e820_type) {
  4.  
    case E820_RESERVED_KERN:
  5.  
    case E820_RAM: return "System RAM";
  6.  
    case E820_ACPI: return "ACPI Tables";
  7.  
    case E820_NVS: return "ACPI Non-volatile Storage";
  8.  
    case E820_UNUSABLE: return "Unusable memory";
  9.  
    default: return "reserved";
  10.  
    }
  11.  
    }

我們可以在/proc/iomem(如上所示)中看到它們。

 

ioremap工作原理

ioremap功能允許我們將總線上的物理地址轉換為內核虛擬地址

現在,讓我們嘗試了解其ioremap工作原理。我們已經了解了一點ioremap,我們在第五部分中了解了Linux內核初始化。如果您已閱讀此部分,則可以記住arch / x86 / mm / ioremap.c中的early_ioremap_init函數調用。的初始化是分為兩個部分:有,我們可以前的正常使用初期可用且正常這是后可用初始化和調用。我們暫時一無所知,所以讓我們考慮對的早期初始化。首先檢查在頁面中間目錄邊界上對齊的內容:ioremapioremapioremapvmallocpaging_initvmallocioremapearly_ioremap_initfixmap

BUILD_BUG_ON((fix_to_virt(0) + PAGE_SIZE) & ((1 << PMD_SHIFT) - 1));

關於BUILD_BUG_ON您的更多信息,可以在第一部分中閱讀有關Linux Kernel初始化的信息。因此,BUILD_BUG_ON如果給定的表達式為true,則macro會引發編譯錯誤。在檢查之后的下一步中,我們可以early_ioremap_setupmm / early_ioremap.c中看到該函數的調用。此函數提供的通用初始化ioremapearly_ioremap_setup函數slot_virt使用早期Fixmap的虛擬地址填充數組。所有早期的修訂圖都__end_of_permanent_fixed_addresses在內存中。它們從FIX_BITMAP_BEGIN(頂部)開始,以FIX_BITMAP_END(向下)結束。實際上512,早期有一些臨時的啟動時映射ioremap

  1.  
    #define NR_FIX_BTMAPS 64
  2.  
    #define FIX_BTMAPS_SLOTS 8
  3.  
    #define TOTAL_FIX_BTMAPS (NR_FIX_BTMAPS * FIX_BTMAPS_SLOTS)

early_ioremap_setup

  1.  
    void __init early_ioremap_setup(void)
  2.  
    {
  3.  
    int i;
  4.  
     
  5.  
    for (i = 0; i < FIX_BTMAPS_SLOTS; i++)
  6.  
    if (WARN_ON(prev_map[i]))
  7.  
    break;
  8.  
     
  9.  
    for (i = 0; i < FIX_BTMAPS_SLOTS; i++)
  10.  
    slot_virt[i] = __fix_to_virt(FIX_BTMAP_BEGIN - NR_FIX_BTMAPS*i);
  11.  
    }

slot_virt和其它陣列在相同的源代碼文件中定義:

  1.  
    static void __iomem *prev_map[FIX_BTMAPS_SLOTS] __initdata;
  2.  
    static unsigned long prev_size[FIX_BTMAPS_SLOTS] __initdata;
  3.  
    static unsigned long slot_virt[FIX_BTMAPS_SLOTS] __initdata;

slot_virt包含區域的虛擬地址fix-mappedprev_map數組包含早期ioremap區域的地址。請注意,我在上面寫過:Actually there are 512 temporary boot-time mappings, used by early ioremap並且您可以看到所有數組都使用__initdata屬性定義,這意味着該內存將在內核初始化過程后釋放。后early_ioremap_setup完成其工作,我們正在頁中間目錄,早期的ioremap與開始early_ioremap_pmd剛剛得到頁全局目錄的基地址和計算給定的地址頁面中間目錄功能:

  1.  
    static inline pmd_t * __init early_ioremap_pmd(unsigned long addr)
  2.  
    {
  3.  
    pgd_t *base = __va(read_cr3_pa());
  4.  
    pgd_t *pgd = &base[pgd_index(addr)];
  5.  
    pud_t *pud = pud_offset(pgd, addr);
  6.  
    pmd_t *pmd = pmd_offset(pud, addr);
  7.  
    return pmd;
  8.  
    }

之后,我們bm_pte用零填充(早期的ioremap頁面表條目)並調用pmd_populate_kernel函數:

  1.  
    pmd = early_ioremap_pmd(fix_to_virt(FIX_BTMAP_BEGIN));
  2.  
    memset(bm_pte, 0, sizeof(bm_pte));
  3.  
    pmd_populate_kernel(&init_mm, pmd, bm_pte);

pmd_populate_kernel 帶有三個參數:

  • init_mm-init進程的內存描述符(您可以在上一部分中了解它);
  • pmdioremap-Fixmaps 開頭的頁面中間目錄;
  • bm_pte -早期ioremap頁面表條目數組,其定義為:
static pte_t bm_pte[PAGE_SIZE/sizeof(pte_t)] __page_aligned_bss;

pmd_populate_kernel函數在arch / x86 / include / asm / pgalloc.h中定義,pmd使用給定的頁面表項(bm_pte)填充作為參數提供的頁面中間目錄():

  1.  
    static inline void pmd_populate_kernel(struct mm_struct *mm,
  2.  
    pmd_t *pmd, pte_t *pte)
  3.  
    {
  4.  
    paravirt_alloc_pte(mm, __pa(pte) >> PAGE_SHIFT);
  5.  
    set_pmd(pmd, __pmd(__pa(pte) | _PAGE_TABLE));
  6.  
    }

在哪里set_pmd

#define set_pmd(pmdp, pmd) native_set_pmd(pmdp, pmd)

並且native_set_pmd是:

  1.  
    static inline void native_set_pmd(pmd_t *pmdp, pmd_t pmd)
  2.  
    {
  3.  
    *pmdp = pmd;
  4.  
    }

就這樣。早就ioremap可以使用了。early_ioremap_init函數中有幾項檢查,但是它們並不是那么重要,無論如何它們的初始化ioremap已完成。

 

早期ioremap的使用

一旦ioremap成功完成早期設置,我們就可以使用它。它提供兩個功能:

  • early_ioremap
  • early_iounmap

用於將I / O物理地址映射/取消映射到虛擬地址。兩種功能均取決於CONFIG_MMU配置選項。內存管理單元內存管理的特殊模塊。該塊的主要目的是將物理地址轉換為虛擬地址。存儲器管理單元pgdcr3控制寄存器中了解高級頁表地址()。如果CONFIG_MMUoptions設置為n,則early_ioremap僅返回給定的物理地址,early_iounmap而不執行任何操作。如果CONFIG_MMUoption設置為y,則early_ioremap調用__early_ioremap將使用三個參數:

  • phys_addr-I/O存儲器區域的基本物理地址,以映射到虛擬地址上;
  • size -I/O內存區域的大小;
  • prot -頁表入口位。

首先__early_ioremap,我們將遍歷所有早期的ioremap fixmap插槽,並搜索prev_map數組中的第一個空閑插槽。當我們找到它時,我們會記住它在slot變量中的編號並設置大小:

  1.  
    slot = -1;
  2.  
    for (i = 0; i < FIX_BTMAPS_SLOTS; i++) {
  3.  
    if (!prev_map[i]) {
  4.  
    slot = i;
  5.  
    break;
  6.  
    }
  7.  
    }
  8.  
    ...
  9.  
    ...
  10.  
    ...
  11.  
    prev_size[slot] = size;
  12.  
    last_addr = phys_addr + size - 1;

在下一個spte中,我們可以看到以下代碼:

  1.  
    offset = phys_addr & ~PAGE_MASK;
  2.  
    phys_addr &= PAGE_MASK;
  3.  
    size = PAGE_ALIGN(last_addr + 1) - phys_addr;

在這里,我們PAGE_MASK用於清除phys_addr除前12位之外的所有位。PAGE_MASK宏定義為:

#define PAGE_MASK (~(PAGE_SIZE-1))

我們知道頁面的大小是4096字節或1000000000000二進制。PAGE_SIZE - 1將是111111111111,但使用~,我們將獲得000000000000,但是使用時,~PAGE_MASK我們將111111111111再次獲得。在第二行中,我們執行相同的操作,但是清除了前12位,並在第三行中獲得了頁面對齊的區域大小。我們獲得了對齊的區域,現在我們需要獲取新ioremap區域所占據的頁面數,並fixed_addresses在接下來的步驟中計算出修正映射索引:

  1.  
    nrpages = size >> PAGE_SHIFT;
  2.  
    idx = FIX_BTMAP_BEGIN - NR_FIX_BTMAPS*slot;

現在,我們可以fix-mapped使用給定的物理地址填充區域。在循環的每次迭代中,我們__early_set_fixmaparch/x86/mm/ioremap.c調用該函數,將給定的物理地址增加頁面大小(即4096字節),並更新addresses索引和頁面數:

  1.  
    while (nrpages > 0) {
  2.  
    __early_set_fixmap(idx, phys_addr, prot);
  3.  
    phys_addr += PAGE_SIZE;
  4.  
    --idx;
  5.  
    --nrpages;
  6.  
    }

__early_set_fixmap函數使用以下命令獲取bm_pte給定物理地址的頁表條目(存儲在中,請參見上文):

pte = early_ioremap_pte(addr);

在下一步中,early_ioremap_pte我們使用pgprot_val宏檢查給定的頁面標志,並根據給定的標志進行調用set_ptepte_clear

  1.  
    if (pgprot_val(flags))
  2.  
    set_pte(pte, pfn_pte(phys >> PAGE_SHIFT, flags));
  3.  
    else
  4.  
    pte_clear(&init_mm, addr, pte);

如您在上方所見,我們將FIXMAP_PAGE_IO標記傳遞給__early_ioremapFIXMPA_PAGE_IO擴展為:

(__PAGE_KERNEL_EXEC | _PAGE_NX)

標記,因此我們調用set_pte函數來設置頁表條目,該條目的工作方式與set_pmdPTE相同(參見上面的內容)。PTEs在循環中進行所有設置后,我們現在可以看一下該__flush_tlb_one函數的調用:

__flush_tlb_one(addr);

此函數在arch/x86/include/asm/tlbflush.h定義,並根據的值進行調用__flush_tlb_single或:__flush_tlbcpu_has_invlpg

  1.  
    static inline void __flush_tlb_one(unsigned long addr)
  2.  
    {
  3.  
    if (cpu_has_invlpg)
  4.  
    __flush_tlb_single(addr);
  5.  
    else
  6.  
    __flush_tlb();
  7.  
    }

__flush_tlb_one函數使TLB中的給定地址無效。如您所見,我們更新了分頁結構,但TLB沒有通知更改,因此我們需要手動進行此操作。有兩種方法可以做到這一點。首先是更新cr3控制寄存器,然后__flush_tlb函數執行此操作:

native_write_cr3(__native_read_cr3());

第二種方法是使用invlpg指令來使TLB條目無效。讓我們看一下__flush_tlb_one實現。如您所見,首先進行功能檢查cpu_has_invlpg,其定義為:

  1.  
    #if defined(CONFIG_X86_INVLPG) || defined(CONFIG_X86_64)
  2.  
    # define cpu_has_invlpg 1
  3.  
    #else
  4.  
    # define cpu_has_invlpg (boot_cpu_data.x86 > 3)
  5.  
    #endif

如果CPU支持該invlpg指令,我們將調用__flush_tlb_single擴展為以下內容的宏__native_flush_tlb_single

  1.  
    static inline void __native_flush_tlb_single(unsigned long addr)
  2.  
    {
  3.  
    asm volatile("invlpg (%0)" ::"r" (addr) : "memory");
  4.  
    }

或調用__flush_tlb,它只會更新cr3我們所看到的寄存器。完成此步驟后,__early_set_fixmap功能執行完畢,我們可以返回到__early_ioremap實現。在為給定地址設置了fixmap區域后,我們需要prev_map使用slot索引將I / O Re-mapped區域的基本虛擬地址保存在以下位置:

prev_map[slot] = (void __iomem *)(offset + slot_virt[slot]);

並退回。

第二個函數early_iounmap取消映射I/O內存區域。此函數采用兩個參數:基地址和I/O區域大小,通常看起來與十分相似early_ioremap。它還會通過Fixmap插槽,並查找具有給定地址的插槽。之后,它獲取fixmap插槽的索引並進行調用__late_clear_fixmap__early_set_fixmap根據其after_paging_init值進行選擇。它的調用方式__early_set_fixmap有一個不同early_ioremap:作為物理地址early_iounmap傳遞zero。最后,它將I / O存儲區的地址設置為NULL

prev_map[slot] = NULL;

這就是fixmaps和有關的ioremap。當然,這部分內容並不涵蓋的所有功能ioremap,僅涵蓋早期的ioremap,但也包含普通的ioremap。但是在更詳細地研究之前,我們需要知道更多的事情。

 

 

相關閱讀

linux內存管理:kmap、vmap、ioremap

ARM SMMU原理與IOMMU技術(“VT-d” DMA、I/O虛擬化、內存虛擬化)

Linux內存管理:分頁機制

Linux內存管理:內存描述之內存節點node

Linux內存管理:內存描述之內存區域zone

Linux內存管理:內存描述之內存頁面page

Linux內存管理:內存描述之高端內存

內存管理:Linux Memory Management:MMU、段、分頁、PAE、Cache、TLB

Linux內存管理:MMU那些事兒


免責聲明!

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



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