1. 程式人生 > 其它 >Linux記憶體管理:Fixmaps(固定對映地址)和ioremap【轉】

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. NEXT_PAGE(level1_fixmap_pgt)
  6. .fill 512,8,0

如您所見level2_fixmap_pgt,緊隨其後的level2_kernel_pgt是核心程式碼+資料+ bss。每個修訂對映的地址都由一個整數索引表示,該整數索引fixed_addresses

arch / 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索引,Xenbootmap等開始的區域中...我們已經在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. for (i = 0; i < FIX_BTMAPS_SLOTS; i++)
  5. if (WARN_ON(prev_map[i]))
  6. break;
  7. for (i = 0; i < FIX_BTMAPS_SLOTS; i++)
  8. slot_virt[i] = __fix_to_virt(FIX_BTMAP_BEGIN - NR_FIX_BTMAPS*i);
  9. }

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那些事兒

【作者】張昺華 【出處】http://www.cnblogs.com/sky-heaven/ 【部落格園】 http://www.cnblogs.com/sky-heaven/ 【知乎】 http://www.zhihu.com/people/zhang-bing-hua 【我的作品---旋轉倒立擺】 http://v.youku.com/v_show/id_XODM5NDAzNjQw.html?spm=a2hzp.8253869.0.0&from=y1.7-2 【我的作品---自平衡自動循跡車】 http://v.youku.com/v_show/id_XODM5MzYyNTIw.html?spm=a2hzp.8253869.0.0&from=y1.7-2 【大餅教你學系列】https://edu.csdn.net/course/detail/10393 【新浪微博】 張昺華--sky 【twitter】 @sky2030_ 【微信公眾號】 張昺華 本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連線,否則保留追究法律責任的權利.