深刻淺出WebAssembly(5) Memory

這系列主要是我對WASM研究的筆記,可能內容比較簡略。總共包括:git

  1. 深刻淺出WebAssembly(1) Compilation
  2. 深刻淺出WebAssembly(2) Basic Api
  3. 深刻淺出WebAssembly(3) Instructions
  4. 深刻淺出WebAssembly(4) Validation
  5. 深刻淺出WebAssembly(5) Memory
  6. 深刻淺出WebAssembly(6) Binary Format
  7. 深刻淺出WebAssembly(7) Future
  8. 深刻淺出WebAssembly(8) Wasm in Rust(TODO)

內存尋址

Convertions

  1. 實模式: 邏輯地址 = 物理地址
  2. 保護模式: 分段 + 分頁(option)
  3. 邏輯地址 :在進行C語言編程中,能讀取變量地址值(&操做),實際上這個值就是邏輯地址,也能夠是經過malloc或是new調用返回的地址。該地址是相對於當前進程數據段的地址,不和絕對物理地址相干。只有在Intel實模式下,邏輯地址才和物理地址相等(由於實模式沒有分段或分頁機制,CPU不進行自動地址轉換)。應用程序員僅需和邏輯地址打交道,而分段和分頁機制對通常程序員來講是徹底透明的,僅由系統編程人員涉及。應用程序員雖然本身能直接操做內存,那也只能在操做系統給你分配的內存段操做。一個邏輯地址,是由一個段標識符加上一個指定段內相對地址的偏移量,表示爲 [段標識符:段內偏移量]。
  4. 線性地址:是邏輯地址到物理地址變換之間的中間層。程序代碼會產生邏輯地址,或說是段中的偏移地址,加上相應段的基地址就生成了一個線性地址。若是啓用了分頁機制,那麼線性地址能再經變換以產生一個物理地址。若沒有啓用分頁機制,那麼線性地址直接就是物理地址。Intel 80386的線性地址空間容量爲4G(2的32次方即32根地址總線尋址)。
  5. 物理地址(Physical Address) 是指出目前CPU外部地址總線上的尋址物理內存的地址信號,是地址變換的最終結果地址。若是啓用了分頁機制,那麼線性地址會使用頁目錄和頁表中的項變換成物理地址。若是沒有啓用分頁機制,那麼線性地址就直接成爲物理地址了,好比在實模式下。

內存分段

現代的內存尋址機制,都引入了名爲「分段」的概念:不一樣級別的程序、程序的不一樣數據類型,存放在不一樣的「段」上面,而後再定義在「段」上的偏移量。也就是說,現代程序看到的地址都不是線性的,而是分段過的地址,形如:segmentSelector:offset。這些段和偏移量組成的空間,就是邏輯內存空間;這些二元組,就是邏輯地址。程序員

段標識符是由一個16位長的字段組成,又稱爲段選擇符(Selector),由處理器提供段寄存器來存放段標識符,段寄存器有6種:github

  1. cs 代碼段寄存器,指向包含程序指令的段;
  2. ss 棧寄存器,指向包含當前程序的段;
  3. ds 數據段寄存器,指向包含靜態數據或者全局數據段;
  4. 其餘三個寄存器es, fs, gs稱爲附加段寄存器,做通常用途,能夠指向任意的數據段

段寄存器存放的並非段基地址也不是段描述符。段的詳細信息須要經過選擇器從描述符表(Descriptor Table,段表)中獲得。 這樣作能夠加快查詢速度,也能夠進行權限控制,只有訪問級別夠的程序才能成功拿到段基地址和長度web

16位選擇器具體組成:編程

  1. 第0-1位: 訪問權限, 0最高(內核態),3最低(都能訪問),低權限的請求不能訪問高權限的內存
  2. 第2位:段表類型,0 - 全局段表(Global DT, GDT)對應寄存器gdtr; 1 - 局部段表(Local DT, LDT)對應寄存器 ldtr,DT相似於一個數組,每項佔8個字節
  3. 第3-15位:段表描述符的索引信息,經過它能夠得到段表描述符

段表描述符的結構比較複雜,不過最重要的是段的段基(BASE, 32bit)和段界(LIMIT, 20bit)數組

經過段描述符獲得BASE 以後,再與邏輯地址偏移量offset相加,就獲得了線性地址:緩存

內存分頁

當段描述符中的G = 1時, 分頁機制啓用。markdown

分頁就是人爲地在邏輯上將連續的內存空間,按照固定大小切分紅一段一段。對於線性內存來講,這樣切分出來的固定大小叫作頁(Page);對於物理內存來講,這樣切分出來的固定大小叫作頁幀(Page Frame)。 分頁機制將線性內存分爲若干頁,將物理內存分爲若干幀,並創建從頁到幀的映射關係。這個映射關係,是一個「多對一」的映射。 線性地址的轉換分兩步完成,每一步都基於一種都基於一種轉換表,第一種轉換表稱爲頁目錄錶轉換,第二種轉換稱爲頁錶轉換。使用這種二級模式的目的在於減小每一個進程頁表所需的RAM的數量。就像咱們看書有個書目錄同樣,方便快捷。具體轉換以下圖所示:架構

尋址過程(386):

  1. 讀取段寄存器中的選擇器;
  2. 驗證訪問權級(保護模式)——經過;
  3. 根據段表類型和段表位置索引,讀取段表中的描述符;
  4. 檢查訪問權級位(保護模式)——經過;
  5. 檢查 offset,看偏移量是否超過段界限;
  6. 檢查 P 位,確保目標位置在物理內存中可用;
  7. 將段基址與 offset 拼接成線性地址;
  8. 檢查是否命中高速緩存——未命中;
  9. 根據線性地址最高 10 位,讀取一級頁表;
  10. 一級頁表檢查訪問權級——經過;
  11. 獲得 20 位 + 12 位補 0 的二級頁表位置;
  12. 根據二級頁表位置訪問二級頁表;
  13. 根據線性地址中間 10 位,讀取二級頁表;
  14. 二級頁表檢查訪問權級——經過;
  15. 獲得 20 位幀基址;
  16. 與線性地址的低位 12 位拼接成 32 位的物理地址;
  17. 訪存。

虛擬內存(VAS)的好處:

  1. 屏蔽底層:虛擬地址程序編寫更加方便
  2. 權限控制:解決內存非法訪問的問題
  3. 高效:虛擬內存尋址空間能夠比物理內存大。能夠靈活分配(高速緩存,LRU) 內核分段課程.PDF

內存分段與分頁功能重合,所以不少新架構或OS傾向於使用Flat Segmentation,如x86-64和Linuxide

在Linux中細分了四種段:

全部的用戶進程都是使用同一個用戶代碼段描述符和用戶數據段描述符,它們是__USER_CS__USER_DS,也就是每一個進程處於用戶態時,它們的CS寄存器和DS寄存器中的值是相同的。當任何進程或者中斷異常進入內核後,都是使用相同的內核代碼段描述符和內核數據段描述符,它們是__KERNEL_CS__KERNEL_DS。這裏要明確記得,內核數據段實際上就是內核態堆棧段。 邏輯地址是由段選擇符(16位) + 段內偏移量offset(32位)得來。以前也說到,只有處於用戶態,CS和DS寄存器中的值都是__USER_CS__USER_DS。只要處於內核態,CS和DS寄存器中的值都是__KERNEL_CS__KERNEL_DS。在咱們編程過程當中,實際上提供的地址都是一個偏移量,系統會自動將這個偏移量與CS中的段選擇符進行結合。也就是咱們使用的邏輯地址實際上只使用了offset這一段,段選擇符都爲空。以前也說了這四個段描述符的BASE都爲0x00000000,也得出當邏輯地址經過這樣的分段機制轉爲線性地址後,實際上並無變化,也就是邏輯地址=線性地址(其實這兩個地址都是offset的值)。

WASM的虛擬內存管理

除了內存分段管理以外,應用程序也有段的概念,主要是描述的程序對數據的組織。通常Linux程序擁有下面幾個段:

wasm的棧能夠簡化爲:

\_\_data\_end 往低位增加而堆從\_\_heap\_base往高位增加,由於棧先放置因此須要在編譯的時候給一個最大值。

stack\_size = \_\_heap\_base - \_\_data\_end

棧空間能夠經過下面方式設置:

clang \
--target=wasm32 \
-O3 \
-flto \
-nostdlib \
-Wl,--no-entry \
-Wl,--export-all \
-Wl,--lto-O3 \
-Wl,-z,stack-size=$[8 * 1024 * 1024] \ # Set maximum stack size to 8MiB
-o add.wasm \
add.c
複製代碼
// llvm 源碼:
// <https://github.com/llvm-mirror/lld/blob/master/wasm/Driver.cpp#L355>
Config->InitialMemory = args::getInteger(Args, OPT_initial_memory, 0);
Config->GlobalBase = args::getInteger(Args, OPT_global_base, 1024);
Config->MaxMemory = args::getInteger(Args, OPT_max_memory, 0);
Config->ZStackSize =
      args::getZOptionValue(Args, OPT_z, "stack-size", WasmPageSize);
//...
複製代碼

refer: dassur.ma/things/c-to…

內存對齊

If the effective address of a memory access is a multiple of the alignment attribute value of the memory access, the memory access is considered aligned, otherwise it is considered misaligned. Aligned and misaligned accesses have the same behavior

若是一個內存的訪問有效地址(Effective address)是儲存器訪問的對齊屬性的倍數,那麼此次儲存器訪問就被稱爲是對齊的,不然是不對齊。對齊與不對齊的訪問具備相同的行爲,可是對齊會提升CPU的處理速度。

wasm32 的對齊屬性是32,wasm64的native 對齊屬性就是64

Effective Address

也便是當前訪問的真實地址(相對於offset來講)

effective\_adress = address\_operand + offset\_immediate

https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/6/13/172ab8b78623cc76~tplv-t2oaga2asx-image.image

i32.const 3       ;; address_operand = 3
i64.const 1234    ;; value
i64.store16 1 3   ;; alignment=1, offset=3, effective_address = 3 + 3 = 6
複製代碼

https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/6/13/172ab8b79c937788~tplv-t2oaga2asx-image.image

上述是對齊的,可是若是是:

i32.const 3       ;; address_operand = 3
i64.const 1234    ;; value
i64.store16 2 3   ;; alignment=2
複製代碼

那麼將會對不齊:

https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/6/13/172ab8b7961d13d4~tplv-t2oaga2asx-image.image
相關文章
相關標籤/搜索