linux編程之記憶體映射

来源:http://www.cnblogs.com/yuuyuu/archive/2016/01/17/5137345.html
-Advertisement-
Play Games

一.概述 記憶體映射是在調用進程的虛擬地址空間創建一個新的記憶體映射。記憶體映射分為2種:1.文件映射:將一個普通文件的全部或者一部分映射到進程的虛擬記憶體中。映射後,進程就可以直接在對應的記憶體區域操作文件內容!2.匿名映射:匿名映射沒有對應的文件或者對應的文...


一.概述                                                  

記憶體映射是在調用進程的虛擬地址空間創建一個新的記憶體映射。

記憶體映射分為2種:

1.文件映射:將一個普通文件的全部或者一部分映射到進程的虛擬記憶體中。映射後,進程就可以直接在對應的記憶體區域操作文件內容!

2.匿名映射:匿名映射沒有對應的文件或者對應的文件時虛擬文件(如:/dev/zero),映射後會把記憶體分頁全部初始化為0。

當多個進程映射了同一個記憶體區域時,它們會共用物理記憶體的相同分頁。通過fork()創建的子進程也會繼承父進程的映射副本!!!

如果多個進程都會同一個記憶體區域操作時,會根據映射的特性,會有不同的行為。映射特征可分為私有映射和共用映射:

1.私有映射:映射的內容對其他進程不可見。對於文件映射來說,某一個進程在映射記憶體中改變文件的內容不會反映到被映射的底層文件中。內核會使用copy-on-write(寫時複製)技術來解決這個問題:只要有一個進程修改了分頁中的內容,內核會為該進程重新創建一個新的分頁,並將需要修改的內容複製到新分頁中。

2.共用映射:某一個進程對共用的記憶體區域操作都對其他進程可見!!!對於文件映射,操作的內容會反映到底層文件中。

註意:進程執行exec()調用後,先前的記憶體映射會丟失,而fork()創建的子進程會繼承父進程的,映射的特征(私有和共用)也會被繼承。

異常信號:

1.當映射記憶體的屬性設置只讀時,如果進行寫操作會產生SIGSEGV信號。

2.當映射記憶體的位元組數大於被映射文件的大小,且大於該文件當前的記憶體分頁大小時。如果訪問的區域超過了該文件分頁大小,會產生SIGBUS信號

有點繞口,舉個簡單的例子:假設內核維護的記憶體分頁是4k(一般都是4k,4096位元組),一個普通文件a.txt的大小是10位元組。如果創建一個映射記憶體為4097位元組,並映射該文件。此時,因為a.txt的大小用一個分頁就可以完全映射,10位元組遠小於一個分頁的4096位元組,所以內核只會給它一個分頁。記憶體地址是從0開始,0-9區間的內容對應a.txt文件的數據,我們也是可以訪問10-4095的區間。但如果訪問4096區間時,已經超過一個分頁的大小了,此時會產生SIGBUS信號!!!

等會我們用個簡單的例子演示下這2個異常。

二.函數介面                                            

1.創建映射

1 #include <sys/mman.h>
2 
3 void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

addr:映射後要存放的虛擬記憶體地址。如果是NULL,內核會自動幫你選擇。

length:映射記憶體的位元組數。

prot:許可權保護:PROT_NONE(無法訪問),PROT_READ(可讀),PROT_WRITE(可寫),PROT_EXEC(可執行)。

flags:映射特征:MAP_PRIVATE(私有),MAP_SHARED(共用),MAP_ANONYMOUS。還有一些其他的可查詢man手冊。

fd:要映射的文件描述符。

offset:文件的偏移量,如果為0,且length為文件長度,代表映射整個文件。

2.解除映射

1 #include <sys/mman.h>
2 
3 int munmap(void *addr, size_t length);

addr:要解除記憶體的起始地址。如果addr不在剛剛映射區域的開始位置,解除一部分後記憶體區域可能會分成兩半!!!

length:要解除的位元組數。

 3.同步映射區

1 #include <sys/mman.h>
2 
3 int msync(void *addr, size_t length, int flags);

addr:要同步的記憶體起始地址。

length:要同步的位元組長度。

flags:MS_SYNC(執行同步文件寫入),此操作內核會把內容直接寫到磁碟。MS_ASYNC(執行非同步文件寫入),此操作內核會先把內容寫到內核的緩衝區,某個合適的時候再寫到磁碟。

三.文件映射實例                                           

 1 /**
 2  * @file mmap_file.c
 3  */
 4 
 5 #include <stdio.h>
 6 #include <stdlib.h>
 7 #include <string.h>
 8 #include <fcntl.h>
 9 #include <signal.h>
10 #include <unistd.h>
11 #include <sys/mman.h>
12 
13 #define MMAP_FILE_NAME "a.txt"
14 #define MMAP_FILE_SIZE 10
15 
16 void err_exit(const char *err_msg)
17 {
18     printf("error:%s\n", err_msg);
19     exit(1);
20 }
21 
22 /* 信號處理器 */
23 void signal_handler(int signum)
24 {
25     if (signum == SIGSEGV)
26         printf("\nSIGSEGV handler!!!\n");
27     else if (signum == SIGBUS)
28         printf("\nSIGBUS handler!!!\n");
29     exit(1);
30 }
31 
32 int main(int argc, const char *argv[])
33 {
34     if (argc < 2)
35     {
36         printf("usage:%s text\n", argv[0]);
37         exit(1);
38     }
39 
40     char *addr;
41     int file_fd, text_len;
42     long int sys_pagesize;
43 
44     /* 設置信號處理器 */
45     if (signal(SIGSEGV, signal_handler) == SIG_ERR)
46         err_exit("signal()");
47     if (signal(SIGBUS, signal_handler) == SIG_ERR)
48         err_exit("signal()");
49 
50     if ((file_fd = open(MMAP_FILE_NAME, O_RDWR)) == -1)
51         err_exit("open()");
52 
53     /* 系統分頁大小 */
54     sys_pagesize = sysconf(_SC_PAGESIZE);
55     printf("sys_pagesize:%ld\n", sys_pagesize);
56 
57     /* 記憶體只讀 */
58     //addr = (char *)mmap(NULL, MMAP_FILE_SIZE, PROT_READ, MAP_SHARED, file_fd, 0);
59     
60     /* 映射大於文件長度,且大於該文件分頁大小 */
61     //addr = (char *)mmap(NULL, sys_pagesize + 1, PROT_READ | PROT_WRITE, MAP_SHARED, file_fd, 0);
62 
63     /* 正常分配 */
64     addr = (char *)mmap(NULL, MMAP_FILE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, file_fd, 0);
65     if (addr == MAP_FAILED)
66         err_exit("mmap()");
67 
68     /* 原始數據 */
69     printf("old text:%s\n", addr);
70 
71     /* 越界訪問 */
72     //addr += sys_pagesize + 1;
73     //printf("out of range:%s\n", addr);
74 
75     /* 拷貝新數據 */
76     text_len = strlen(argv[1]);
77     memcpy(addr, argv[1], text_len);
78 
79     /* 同步映射區數據 */
80     //if (msync(addr, text_len, MS_SYNC) == -1)
81     //    err_exit("msync()");
82 
83     /* 列印新數據 */
84     printf("new text:%s\n", addr);
85 
86     /* 解除映射區域 */
87     if (munmap(addr, MMAP_FILE_SIZE) == -1)
88         err_exit("munmap()");
89 
90     return 0;
91 }

1.首先創建一個10位元組的文件:

1 $:dd if=/dev/zero of=a.txt bs=1 count=10

2.把程式編譯運行後,依次執行2寫入:

可以看到本機的分頁大小是4096位元組。第一次寫入9個位元組,原來用dd命令創建的文件為空,old text為空。第二次寫入4個位元組,只覆蓋了最前面的1234。

3.驗證可訪問現有分頁的記憶體。寫入超過10位元組的數據:

上面我們寫入了17個位元組,雖然64行的mmap()映射了MMAP_FILE_SIZE=10位元組。但從輸入new text可以看出,我們依然可以訪問10位元組後面的記憶體,因為該數據都在一個分頁(4096)裡面。cat查看a.txt後,只有前10個位元組寫入了a.txt。

4.驗證SIGSEGV信號。把64行註釋調,58行打開,設置映射屬性為只讀,編譯後訪問:

設置只讀屬性後,第77行有寫操作。我們自定義的信號處理器就捕捉到了該信號。如果沒有自定義信號處理器,終端就會輸出Segmentation fault

5.驗證SIGBUS信號。用61行的方法來映射記憶體。映射了一個分頁大小再加1位元組的記憶體,並放開72,73行的代碼,讓指針指向一個分頁後的區域。編譯後運行:

SIGBUS信號被自定義處理器捕捉到了。如果沒有自定義信號處理器,終端就會輸出Bus error

四.匿名映射                                            

匿名映射有2種方式:

1.指定mmap()的flags參數為MAP_ANONYMOUS,在linux上當指定這個值後會忽略fd參數的值。不過在有的UNIX上還需要把fd指定為-1。

2.把/dev/zero當做文件描述符打開,從/dev/zero讀取數據時它會給你提供無窮無盡的0,向它寫數據,它會丟棄。丟棄這點跟/dev/null一樣,只是/dev/null不跟你提供數據。

3.匿名映射的使用跟上面的文件映射差不多。這裡不再給例子。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 這個在SQL2005之後最見的一種分頁方式,也是Linq預設生成的執行分頁的方法(skip,take),當然在性能上小數量沒有問題,在數據達到百萬時會很慢,這是我們要清楚的,有時我們在LINQ環境下也需要分頁寫SQL,這時如何去分佈就成為了一個很不好處理的問題,所以大叔還是把準備的分頁代碼貢獻一下D...
  • 當一切正常時,沒有必要特別留意什麼是事務日誌,它是如何工作的。你只要確保每個資料庫都有正確的備份。當出現問題時,事務日誌的理解對於採取修正操作是重要的,尤其在需要緊急恢複數據庫到指定點時。這系列文章會告訴你每個DBA應該知道的具體細節。對於在我們關註下的所有資料庫,在日誌維護方面,我們的首要目標是最...
  • 今天打開網站時,突然報這個錯誤,平時都好好的Cannot open database "JMECC" requested by the login. The login failed.Login failed for user 'NT AUTHORITY\IUSR'.我的連接字元串是這樣的,用的是 ...
  • 一、簡介 Bazaar 是一種強大的新一代源代碼控制系統,它能夠適用於所有主流操作系統,能夠適應任何開發團隊的工作模式。 二、安裝 1)yum方式 yum install -y bzr 三、教程 http://doc.bazaar.canonical.com/bzr.2.1/en/tutorials...
  • C# Lambda 實例
  • 作為APUE 14.3節的參考 linux是有強制鎖的,但是預設不開啟.想讓linux支持強制性鎖,不但在mount的時候需要加上-o mand,而且對要加鎖的文件也需要設置相關許可權。 1、建議鎖又稱協同鎖。對於這種類型的鎖,內核只是提供加減鎖以及檢測是否加鎖的操作,但是不提供鎖的控制與協調工作。也...
  • Linux 配置無網路狀態利用yum安裝軟體 在有網路的情況下安裝軟體只需一條yum install xxx命令,例如安裝gcc只需一條指令:yum install gcc 那麼在沒有網路的情況下該如何安裝gcc呢?雖然沒有網路,但是我想你應該有安裝光碟或者ISO鏡像,至少也有gcc所需要的安裝包....
  • 作者信息作者: 彭東林郵箱:[email protected]:405728433平臺簡介開發板:tiny4412ADK + S700 + 4GB Flash要移植的內核版本:Linux-4.4.0 (支持device tree)u-boot版本:友善之臂自帶的 U-Boot 2010...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...