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)