iT邦幫忙

2021 iThome 鐵人賽

DAY 20
0

今天直奔新主題!XDD
昨天提要 trace 的程式碼,trace 的不多,今天就還是先來看個文件,實體記憶體管理相關的程式碼,也許之後在另闢一篇或是就放在附錄好了~XD
今天讀的文件是 High Memory Handling,一個增加 32 bit 機器能夠存取實體記憶體大小的技術。

文件

.. _highmem:

================
High Memory 處理
================

By: Peter Zijlstra <a.p.zijlstra@chello.nl>

.. contents:: :local:

什麼是 High Memory?
===================

High memory(highmem)是當實體記憶體的大小接近
或超過虛擬記憶體的大小時使用的,
此時核心已經不可能時時刻刻保存著所有的實體記憶體映射。
這意味著核心需要開始使用臨時的映射,
來存取想要存取的實體記憶體片段。

永久性映射未映射到的(實體)記憶體即是'highmem'。
有各種和架構相關的限制來決定那個highmem 的邊界在哪裡。

例如,在 i386 架構中,我們選擇將核心映射到每個程序的虛擬位址空間,
這樣我們就不必在進出核心時花費使 TLB 失效的代價。
這意味著可用的虛擬記憶體空間(4GiB on i386) 
必須在使用者空間和核心空間之間進行劃分。

使用這種方法的架構通常是 3:1 的拆分,
3GiB 用於使用者空間,高位 1GiB 則是核心空間::

        +--------+ 0xffffffff
        | Kernel |
        +--------+ 0xc0000000
        |        |
        | User   |
        |        |
        +--------+ 0x00000000

這意味著核心可以在任何一個時刻映射最多 1GiB 的實體記憶體,
但因為我們需要虛擬地址空間 - 包括
暫時性映射存取實體記憶體的其餘部分 - 
實際直接映射的虛擬記憶體位址通常會更少(通常約為 896MiB)。

其他具有 mm 上下文標記 TLBs (mm context tagged TLBs) 的架構,
可以具有單獨的核心和使用者映射。
但是,某些硬體(如某些 ARM)使用 mm 上下文標籤時,
僅有有限的虛擬空間。

暫時性的虛擬映射
===============

核心包含幾種創建暫時性映射的方法:

* vmap().  這可用於製作多個持續時間較長的實體分頁映射
  到一個連續的虛擬空間。它需要全域的同步機制來取消映射。

* kmap().  這實現了單一分頁的短期映射。它需要全域同步,
  但有所攤銷。使用巢狀結構時,它容易出現死鎖,
  因此不建議用於新程式碼。

* kmap_atomic().  這實現了單一分頁非常短期的映射。
  由於映射僅限於使用它的 CPU;
  它能正常運作,但需要讓使用它的任務保持在該 CPU 上直到它完成,
  以免其他任務替換掉它的映射。

  kmap_atomic() 也可以被中斷上下文使用,
  因為它不會 sleep 並且呼叫者也不能 sleep 直到 kunmap_atomic() 被呼叫。
  
  可以假設 k[un]map_atomic() 不會失敗。

使用 kmap_atomic
================

何時、在哪使用 kmap_atomic() 很直覺。
使用時機是當程式碼時使用想要存取的
可能是從 high memory 分配到的分頁內容
(參見 __GFP_HIGHMEM),例如分頁快取中的分頁。 
API 有兩個函數,它們可以以類似於以下的方式使用::

    /* Find the page of interest. */
    struct page *page = find_get_page(mapping, offset);

    /* Gain access to the contents of that page. */
    void *vaddr = kmap_atomic(page);

    /* Do something to the contents of that page. */
    memset(vaddr, 0, PAGE_SIZE);

    /* Unmap that page. */
    kunmap_atomic(vaddr);

請注意 kunmap_atomic() 使用的是 
kmap_atomic() 的結果,而非它的參數。

如果需要映射兩個分頁,因為想從其中一個分頁
複製到另一個分頁,需要嚴格的巢狀呼叫 kmap_atomic,例如::

    vaddr1 = kmap_atomic(page1);
    vaddr2 = kmap_atomic(page2);

    memcpy(vaddr1, vaddr2, PAGE_SIZE);

    kunmap_atomic(vaddr2);
    kunmap_atomic(vaddr1);


暫時性映射的代價
===============

創建暫時性映射的成本可能非常高。
架構相關程式碼必須操作核心的分頁表、dTLB 和/或 MMU 的暫存器。

如果未設置 CONFIG_HIGHMEM,
則核心將嘗試使用簡單的算數來創建映射,
這些映射能將 struct page 位址轉換為指向分頁內容的指標,
而不是像 highmem 映射一樣,做一大堆映射的操作。
在這種情況下,取消映射操作可能是 nop。

如果沒有設置 CONFIG_MMU,
那麼就沒有暫時性映射,也沒有 highmem。
在這種情況下,也將使用簡單的運算方法。

i386 實體位址擴充 (PAE, Physical Address Extension)
==================================================

在某些情況下,i386 arch 將可以支援高達到 64GiB 的 RAM 
在 32 位元的機器上。而這會有一些後果:

* Linux 需要為系統中的每個分頁提供 page-frame 的結構,
  而 pageframse 需要存在在永久性的映射中,這意味著:

* 最多會有 896M/sizeof(struct page) page-frames;
  而 struct page 是 32 位元組,最終將是 112G 數量級的頁數;
  然而,核心需要儲存的不僅僅是那個記憶體中的 page-frames...

* PAE 使分頁表更大 - 這會減慢系統速度
  因為有更多的資料需要被存取,才能在 TLB 填充等中遍歷。
  有一個優點是 PAE 有更多的 PTE 位元,可以提供進階特性像 NX 和 PAT。

一般建議是在 32 位元機器上不要使用超過 8GiB - 
儘管更多可能適合您和您的需求,但您很大概率只能靠自己 - 
不要期待核心開發人員會在意如果無法運作。

我的理解

  • 實體記憶體大小超出虛擬記憶體可以定址的範圍,那麼多出來的實體記憶體需要透過 high memory 才能存取
  • kernel 需要打開 CONFIG_MMU 和 CONFIG_HIGHMEM 才會有 highmem 支援
  • 大多數架構的程序都是如文中所提的,在 32 位元的設置下,會把 kernel 映射到虛擬位址高位的 1G 而剩餘的 3G 是使用者可使用的,highmem 則是透過使用部分 (約 128 MB) 的 kernel 虛擬位址,拿來作為暫時性的映射空間。
  • 建立這些暫時性映射有 vmapkmapkmap_atomic 這些 API,分別對應的是映射存在的時間長短,vmap 最長; kmap_atomic 最短
    • 其中有 kmap_atomic 的示例說明
  • 暫時性映射的 cost 很高是因為,在 context swtich 下,kernel page table 的更動、tlb flush 等等的操作會很頻繁。
  • mm context tagged TLBs:TLB entry 中有包含 mm (各個 process) 的資訊 (( 猜測
  • 112G 還不太確定這個數字怎麼來的XD

延伸閱讀


上一篇
# Day 19 Physical Memory Model (Summary)
下一篇
# Day 21 Heterogeneous Memory Management (HMM) (一)
系列文
閱讀 Linux Kernel 文件30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言