Linux記憶體子系統——Locking Pages(記憶體鎖定)

該部分內容可以參考libc man page 3.5 LockingPages

概述

你可以讓系統將特定的虛擬記憶體頁與實際頁幀相”關聯”,並保持這樣的狀態(稱為鎖定)。該部分記憶體不會被swap機制交換出來,也不會產生pagefault(因為已經分配了實際的物理記憶體)。

為什麼需要鎖定記憶體

一個背景知識pagefault

用戶在分配出一部分虛擬記憶體時,其背後可能並沒有真正的物理記憶體與之對應,只有在用戶真正需要訪問記憶體時,系統才會為這段虛擬記憶體分配實際的物理記憶體,這個過程叫做pagefault(缺頁異常)。這個過程對用戶來說時不感知的,所以用戶可以總是假定他要使用的虛擬記憶體背後有實際的物理記憶體

1. 速度

當用戶只是執行簡單的記憶體訪問時,pagefault流程對用戶來說雖然是不感知的,耗時可以忽略不記,但是對於一些時間敏感型進程,尤其是實時進程,可能無法容忍執行速度的下降。

這種情況下,程式設計師可以先把所需要使用到的記憶體全部鎖定,為它們提前分配好實際的物理記憶體,這樣在訪問時,就可以省去pagefault流程,提升程式執行速度。

2. 安全

如果你把一些秘密存放在虛擬記憶體中(比如用戶輸入的密碼),當虛擬記憶體被swap到磁碟後,就可能導致泄露。且可能在虛擬記憶體和物理記憶體被清除後很長一段時間依然存在。

副作用

當你每多lock一個頁幀,那麼可供其他虛擬記憶體使用的頁幀就少了一個,意味者系統里可能發生更多的缺頁異常,更多的swap,而導致系統執行速度變慢。

一個極端的情況是,當你鎖定了所有的記憶體,系統將因為沒有實際可用的記憶體而無法運行

一些細節

1. 堆疊

記憶體鎖不會堆疊,你即使鎖定一段記憶體兩次,也只需要解鎖一次

2. 生命周期

記憶體鎖定會一直持續到擁有記憶體的進程顯示的解鎖它。但是進程終止和exec會導致虛擬記憶體不再存在,這可能意味著它不再被鎖定

3. 繼承

記憶體所不會被子進程繼承,(但請注意,在現代Unix系統中,在fork之後,父級和子級的虛擬地址空間由相同的實頁幀支援,因此子級享有父級的鎖)

4. 許可權

由於它能夠影響其他進程,因此只有超級用戶可以鎖定,但所有進程都可以解鎖自己的記憶體

5. 寫入時複製的行為

這裡有一個非常有趣的行為,但我還沒有研究透,允許我先挖個坑

libc介面

mlock

將從addr開始長度len的記憶體鎖定

int mlock (const void *addr, size t len)

munlock

將從addr開始長度len的記憶體解鎖

int munlock (const void *addr, size t len)

mlockall

全部鎖定

int mlockall (int flags)

標誌位說明:

MCL_CURRENT代表只鎖定當前已經分配的記憶體

MCL_FUTURE將來分配的記憶體也會被立刻鎖定,注意單獨設置這個標誌位不會鎖定當前已經被分配的記憶體

注意 MCL_FUTURE不會影響未來的進程地址空間,例如exec後,該標誌位將被擦除

munlockall

沒啥好說的了,一次解鎖所有記憶體(自己進程的)

int munlockall (void)