1. 程式人生 > >mlock家族:鎖定物理內存【轉】

mlock家族:鎖定物理內存【轉】

簡單 %0 ive alloc 即使 編程 string類 第一個 lang

轉自:http://blog.csdn.net/fjt19900921/article/details/8074541

鎖住內存是為了防止這段內存被操作系統swap掉。並且由於此操作風險高,僅超級用戶可以執行。

看家族成員:

#include <sys/mman.h>

int mlock(const void *addr, size_t len);

int munlock(const void *addr, size_t len);

int mlockall(int flags);

int munlockall(void);

系統調用 mlock 家族允許程序在物理內存上鎖住它的部分或全部地址空間。這將阻止Linux 將這個內存頁調度到交換空間(swap space),即使該程序已有一段時間沒有訪問這段空間。

一個嚴格時間相關的程序可能會希望鎖住物理內存,因為內存頁面調出調入的時間延遲可能太長或過於不可預知。安全性要求較高的應用程序可能希望防止敏感數據被換出到交換文件中,因為這樣在程序結束後,攻擊者可能從交換文件中恢復出這些數據。

鎖定一個內存區間只需簡單將指向區間開始的指針及區間長度作為參數調用 mlock。linux 分配內存到頁(page)且每次只能鎖定整頁內存,被指定的區間涉及到的每個內存頁都將被鎖定。getpagesize 函數返回系統的分頁大小,在 x86 Linux 系統上,這個值是 4KB。

簡單的測試程序:

#include<unistd.h>
#include<stdio.h>

int main()
{
int i = getpagesize();
printf("page size = %d.\n",i);
return 0;
}

舉個例子來說,分配 32Mb 的地址空間並把它鎖進內存中,您需要使用如下的代碼:

const int alloc_size = 32 * 1024 * 1024; char* memory = malloc (alloc_size); mlock (memory, alloc_size);

需註意的是,僅分配內存並調用 mlock 並不會為調用進程鎖定這些內存,因為對應的分頁可能是寫時復制(copy-on-write)5。因此,你應該在每個頁面中寫入一個假的值:

size_t i; size_t page_size = getpagesize (); for (i = 0; i < alloc_size; i += page_size) memory[i] = 0;

這樣針對每個內存分頁的寫入操作會強制 Linux 為當前進程分配一個獨立、私有的內存頁

要解除鎖定,可以用同樣的參數調用 munlock。

如果你希望程序的全部地址空間被鎖定在物理內存中,請用 mlockall。這個系統調用接受一個參數;如果指定 MCL_CURRENT,則僅僅當前已分配的內存會被鎖定,之後分配的內存則不會;MCL_FUTURE 則會鎖定之後分配的所有內存。使用 MCL_CURRENT|MCL_FUTURE 將已經及將來分配的所有內存鎖定在物理內存中。

鎖定大量的內存,尤其是通過 mlockall,對整個系統而言可能是危險的。不加選擇的內存加鎖會把您的系統折磨到死機,因為其余進程被迫爭奪更少的資源的使用權,並且會更快地被交換進出物理內存(這被稱之為 thrashing)。如果你鎖定了太多的內存,Linux 系統將整體缺乏必需的內存空間並開始殺死進程。

出於這個原因,只有具有超級用戶權限的進程才能利用 mlock 或 mlockall 鎖定內存。如果一個並無超級用戶權限的進程調用了這些系統調用將會失敗、得到返回值 -1 並得到 errno 錯誤號 EPERM。

munlock 系統調用會將當前進程鎖定的所有內存解鎖,包括經由 mlock 或 mlockall 鎖定的所有區間。

一個監視程序內存使用情況的方便方法是使用top命令。在top的輸出中,SIZE顯示了每個程序的虛地址空間的大小(您的整個程序代碼、數據、棧,其中一些應該已被交換出到交換區間)。RSS 列(Resident set size,持久集合大小)顯示了程序所占用的的物理內存大小。所有當前運行程序的 RSS 數值總和不會超過您的計算機物理內存大小,並且所有地址空間的大小限制值為2GB(對於32字節版本的Linux來說)

如果您使用了mlock系統調用,請引入<sys/mman.h>頭文件。

5 Copy-on-write 寫時復制意味著僅當進程在內存區間的任意位置寫入內容時,Linux 系統才會為進程創建該區內存的私有副本

給一段示例程序:

#include<stdio.h>
#include<stdlib.h>
#include<sys/mman.h>

const int alloc_size = 32 * 1024 * 1024;//分配32M內存
int main()
{
char *memory = malloc(alloc_size);
if(mlock(memory,alloc_size) == -1) {
perror("mlock");
return (-1);
}
size_t i;
size_t page_size = getpagesize();
for(i=0;i<alloc_size;i+=page_size) {
printf("i=%zd\n",i);
memory[i] = 0;
}

if(munlock(memory,alloc_size) == -1) {
perror("munlock");
return (-1);
}

return 0;
}

記住用root權限執行。

Copy On Write(寫時復制)是在編程中比較常見的一個技術,面試中也會偶爾出現(好像Java中就經常有字符串寫時復制的筆試題),今天在看《More Effective C++》的引用計數時就講到了Copy On Write——寫時復制。下面簡單介紹下Copy On Write(寫時復制),我們假設STL中的string支持寫時復制(只是假設,具體未經考證,這裏以Mircosoft Visual Studio 6.0為例,如果有興趣,可以自己翻閱源碼)

Copy On Write(寫時復制)的原理是什麽?
有一定經驗的程序員應該都知道Copy On Write(寫時復制)使用了“引用計數”,會有一個變量用於保存引用的數量。當第一個類構造時,string的構造函數會根據傳入的參數從堆上分配內存,當有其它類需要這塊內存時,這個計數為自動累加,當有類析構時,這個計數會減一,直到最後一個類析構時,此時的引用計數為1或是0,此時,程序才會真正的Free這塊從堆上分配的內存。
引用計數就是string類中寫時才拷貝的原理!

什麽情況下觸發Copy On Write(寫時復制)
很顯然,當然是在共享同一塊內存的類發生內容改變時,才會發生Copy On Write(寫時復制)。比如string類的[]、=、+=、+等,還有一些string類中諸如insert、replace、append等成員函數等,包括類的析構時。

示例代碼:

[cpp] view plaincopyprint?
  1. // 作者:代碼瘋子
  2. // 博客:http://www.programlife.net/
  3. // 引用計數 & 寫時復制
  4. #include <iostream>
  5. #include <string>
  6. using namespace std;
  7. int main(int argc, char **argv)
  8. {
  9. string sa = "Copy on write";
  10. string sb = sa;
  11. string sc = sb;
  12. printf("sa char buffer address: 0x%08X\n", sa.c_str());
  13. printf("sb char buffer address: 0x%08X\n", sb.c_str());
  14. printf("sc char buffer address: 0x%08X\n", sc.c_str());
  15. sc = "Now writing...";
  16. printf("After writing sc:\n");
  17. printf("sa char buffer address: 0x%08X\n", sa.c_str());
  18. printf("sb char buffer address: 0x%08X\n", sb.c_str());
  19. printf("sc char buffer address: 0x%08X\n", sc.c_str());
  20. return 0;
  21. }
[cpp] view plain copy print?
  1. // 作者:代碼瘋子
  2. // 博客:http://www.programlife.net/
  3. // 引用計數 & 寫時復制
  4. #include <iostream>
  5. #include <string>
  6. using namespace std;
  7. int main(int argc, char **argv)
  8. {
  9. string sa = "Copy on write";
  10. string sb = sa;
  11. string sc = sb;
  12. printf("sa char buffer address: 0x%08X\n", sa.c_str());
  13. printf("sb char buffer address: 0x%08X\n", sb.c_str());
  14. printf("sc char buffer address: 0x%08X\n", sc.c_str());
  15. sc = "Now writing...";
  16. printf("After writing sc:\n");
  17. printf("sa char buffer address: 0x%08X\n", sa.c_str());
  18. printf("sb char buffer address: 0x%08X\n", sb.c_str());
  19. printf("sc char buffer address: 0x%08X\n", sc.c_str());
  20. return 0;
  21. }

輸出結果如下(VC 6.0):

技術分享

可以看到,VC6裏面的string是支持寫時復制的,但是我的Visual Studio 2008就不支持這個特性(Debug和Release都是):

技術分享


拓展閱讀:(摘自《Windows Via C/C++》5th Edition,不想看英文可以看中文的PDF,中文版第442頁)
Static Data Is Not Shared by Multiple Instances of an Executable or a DLL

When you create a new process for an application that is already running, the system simply opens another memory-mapped view of the file-mapping object that identifies the executable file’s image and creates a new process object and a new thread object (for the primary thread). The system also assigns new process and thread IDs to these objects. By using memory-mapped files, multiple running instances of the same application can share the same code and data in RAM.

Note one small problem here. Processes use a flat address space. When you compile and link your program, all the code and data are thrown together as one large entity. The data is separated from the code but only to the extent that it follows the code in the .exe file. (See the following note for more detail.) The following illustration shows a simplified view of how the code and data for an application are loaded into virtual memory and then mapped into an application’s address space.

技術分享
As an example, let’s say that a second instance of an application is run. The system simply maps the pages of virtual memory containing the file’s code and data into the second application’s address space, as shown next.

技術分享
If one instance of the application alters some global variables residing in a data page, the memory contents for all instances of the application change. This type of change could cause disastrous effects and must not be allowed.

The system prohibits this by using the copy-on-write feature of the memory management system. Any time an application attempts to write to its memory-mapped file, the system catches the attempt, allocates a new block of memory for the page containing the memory the application is trying to write to, copies the contents of the page, and allows the application to write to this newly allocated memory block. As a result, no other instances of the same application are affected. The following illustration shows what happens when the first instance of an application attempts to change a global variable in data page 2:

技術分享
The system allocated a new page of virtual memory (labeled as “New page” in the image above) and copied the contents of data page 2 into it. The first instance’s address space is changed so that the new data page is mapped into the address space at the same location as the original address page. Now the system can let the process alter the global variable without fear of altering the data for another instance of the same application.

A similar sequence of events occurs when an application is being debugged. Let’s say that you’re running multiple instances of an application and want to debug only one instance. You access your debugger and set a breakpoint in a line of source code. The debugger modifies your code by changing one of your assembly language instructions to an instruction that causes the debugger to activate itself. So you have the same problem again. When the debugger modifies the code, it causes all instances of the application to activate the debugger when the changed assembly instruction is executed. To fix this situation, the system again uses copy-on-write memory. When the system senses that the debugger is attempting to change the code, it allocates a new block of memory, copies the page containing the instruction into the new page, and allows the debugger to modify the code in the page copy.


Copyed From 程序人生
Home Page:http://www.programlife.NET
Source URL:http://www.programlife.net/copy-on-write.html

寫時拷貝:

在復制一個對象的時候並不是真正的把原先的對象復制到內存的另外一個位置上,而是在新對象的內存映射表中設置一個指針,指向源對象的位置,並把那塊內存的Copy-On-Write位設置為1.

這樣,在對新的對象執行讀操作的時候,內存數據不發生任何變動,直接執行讀操作;而在對新的對象執行寫操作時,將真正的對象復制到新的內存地址中,並修改新對象的內存映射表指向這個新的位置,並在新的內存位置上執行寫操作。

這個技術需要跟虛擬內存和分頁同時使用,好處就是在執行復制操作時因為不是真正的內存復制,而只是建立了一個指針,因而大大提高效率。但這不是一直成立的,如果在復制新對象之後,大部分對象都還需要繼續進行寫操作會產生大量的分頁錯誤,得不償失。所以COW高效的情況只是在復制新對象之後,在一小部分的內存分頁上進行寫操作。

Linux的fork使用copy-on-write實現。寫時拷貝是一種可以推遲甚至免除拷貝數據的技術。內核此時並不復制整個地址空間,而是讓父進程和子進程共享同一拷貝。 只有在需要寫入的時候,數據才會被復制,從而使各個進程擁有各自的拷貝。也就是說,資源的復制只有在需要寫入的時候才進行,在此之前,只是以可讀方式共享。這種技術使地址空間上的頁的拷貝被推遲到實際發生寫入的時候。在頁根根本不會被寫入的情況下--舉例來說,fork之後立即調用exec--它們無需復制了。

vfork和fork的功能相同,除了不拷貝父進程的頁表項。子進程作為父進程的一個單獨的線程在它的地址空間裏運行,父進程被阻塞,直到子進程退出或執行exec。子進程不能向地址空間寫入。

fork:子進程拷貝父進程的數據段,堆棧段。vfork:子進程與父進程共享數據段。

fork:父子進程的執行次序不確定。vfork:保證子進程先運行,在調用exec或exit之前與父進程數據是共享的,在它調用exec或exit之後父進程才可能被調度運行。如果在調用這兩個函數之前子進程依賴於父進程的進一步動作,則會導致死鎖。

fork系統調用是創建一個新進程的首選方式,fork的返回值要麽是0,要麽是非0,父進程與子進程的根本區別在於fork函數的返回值.

vfork系統調用除了能保證用戶空間內存(數據段)不會被復制之外,它與fork幾乎是完全相同的.

vfork存在的問題是它要求子進程立即調用exec,而不用修改任何內存,這在真正實現的時候要困難的多,尤其是考慮到exec調用有可能失敗.

vfork的出現是為了解決當初fork浪費用戶空間內存的問題,因為在fork後,很有可能去執行exec,vfork的思想就是取消這種復制.

現在的所有unix/linux變量都使用一種寫拷貝的技術(copy on write),它使得一個普通的fork調用非常類似於vfork.因此vfork變得沒有必要.

mlock家族:鎖定物理內存【轉】