天天看點

UNIX環境進階程式設計——存儲映射I/O(mmap函數)

     共享記憶體可以說是最有用的程序間通信方式,也是最快的IPC形式,因為程序可以直接讀寫記憶體,而不需要任何資料的拷貝。對于像管道和消息隊列等通信方式,則需要在核心和使用者空間進行四次的資料拷貝,而共享記憶體則隻拷貝兩次資料: 一次從輸入檔案到共享記憶體區,另一次從共享記憶體區到輸出檔案。實際上,程序之間在共享記憶體時,并不總是讀寫少量資料後就解除映射,有新的通信時,再重建立立共享記憶體區域。而是保持共享區域,直到通信完畢為止,這樣,資料内容一直儲存在共享記憶體中,并沒有寫回檔案。共享記憶體中的内容往往是在解除映射時才寫回檔案的。是以,采用共享記憶體的通信方式效率是非常高的。

一. 傳統檔案通路

     UNIX通路檔案的傳統方法是用open打開它們, 如果有多個程序通路同一個檔案, 則每一個程序在自己的位址空間都包含有該

檔案的副本,這不必要地浪費了存儲空間. 下圖說明了兩個程序同時讀一個檔案的同一頁的情形. 系統要将該頁從磁盤讀到高

速緩沖區中, 每個程序再執行一個存儲器内的複制操作将資料從高速緩沖區讀到自己的位址空間.

UNIX環境進階程式設計——存儲映射I/O(mmap函數)

二. 共享存儲映射

     現在考慮另一種處理方法: 程序A和程序B都将該頁映射到自己的位址空間, 當程序A第一次通路該頁中的資料時, 它生成一

個缺頁中斷. 核心此時讀入這一頁到記憶體并更新頁表使之指向它.以後, 當程序B通路同一頁面而出現缺頁中斷時, 該頁已經在

記憶體, 核心隻需要将程序B的頁表登記項指向次頁即可. 如下圖所示: 

UNIX環境進階程式設計——存儲映射I/O(mmap函數)

三、mmap()及其相關系統調用

     mmap()系統調用使得程序之間通過映射同一個普通檔案實作共享記憶體。普通檔案被映射到程序位址空間後,程序可以向訪

問普通記憶體一樣對檔案進行通路,不必再調用read(),write()等操作。

     mmap函數把一個檔案或一個Posix共享記憶體區對象映射到調用程序的位址空間。使用該函數有三個目的:

(1)使用普通檔案以提供記憶體映射I/O;

(2)使用特殊檔案以提供匿名記憶體映射;

(3)使用shm_open以提供無親緣關系程序間的Posix共享記憶體區。

#include <sys/mman.h>
void *mmap(void *addr,size_t len,int prot,int flags,int fd,off_t off);      

     其中addr可以指定描述符fd應被映射到的程序内空間的起始位址。它通常被指定為一個空指針,這樣告訴核心自己去選擇起始位址。無論哪種情況下,該函數的傳回值都是描述符fd所映射到記憶體區的起始位址。

     注意:fd指定要被映射檔案的描述符,在映射該檔案到一個位址空間之前,先要打開該檔案。

     同時,fd可以指定為-1,此時須指定flags參數中的MAP_ANON,表明進行的是匿名映射(不涉及具體的檔案名,避免了檔案的建立及打開,很顯然隻能用于具有親緣關系的程序間通信)。

     len是映射到調用程序位址空間中的位元組數,它從被映射檔案開頭起第off個位元組處開始算。off通常設定為0.

UNIX環境進階程式設計——存儲映射I/O(mmap函數)

     記憶體映射區得保護由prot參數指定,它使用如下的常值。該參數的常見值是代表讀寫通路的PROT_READ | PROT_WRITE。

      對指定映射區的prot參數指定,不能超過檔案open模式通路權限。例如:若該檔案是隻讀打開的,那麼對映射存儲區就不能指定PROT_WRITE。

UNIX環境進階程式設計——存儲映射I/O(mmap函數)

     flags使用如下的常值指定。MAP_SHARED或MAP_PRIVATE這兩個标志必須指定一個,并可有選擇的或上MAP_FIXED。如果指定了MAP_PRIVATE,那麼調用程序被映射資料所作的修改隻對該程序可見,而不改變其底層支撐對象(或者是一個檔案獨享,或者是一個共享記憶體區對象)。如果指定了MAP_SHARED,那麼調用程序對被映射資料所作的修改對于共享該對象的所有程序都可見,而且确實改變了其底層支撐對象。

     mmap成功傳回後,fd參數可以關閉。該操作對于由mmap建立的映射關系沒有影響。

     為從某個程序的位址空間删除一個映射關系,我們調用munmap。

#include <sys/mman.h>
int munmap(void *addr,szie_t len);      

     其中addr參數由mmap傳回的位址,len是映射區的大小。再次通路這些位址将導緻向調用程序産生一個SIGSEGV信号(當然這裡假設以後的mmap調用并不重用這部分位址空間)。

     如果被映射區是使用MAP_PRIVATE标志映射的,那麼調用程序對它所作的變動都會被丢棄掉,即不會同步到檔案中。

     注意:程序終止時,或調用munmap之後,存儲映射區就被自動解除映射。關閉檔案描述符fd并不解除,munmap不會影響被映射的對象,在解除了映射之後,對于MAP_PRIVATE存儲區的修改被丢棄。

     核心的虛拟記憶體算法保持記憶體映射檔案(一般在硬碟上)與記憶體映射區(在記憶體中)的同步,前提是它是一個MAP_SHARED記憶體區。這就是說,如果我們修改了處于記憶體映射到某個檔案的記憶體區中某個位置的内容,那麼核心将在稍後的某個時刻相應的更新檔案。然而有時候我們希望确信硬碟上的檔案内容與記憶體映射區中的内容一緻,于是調用msync來執行這種同步。

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

    其中addr和len參數通常指代記憶體中的整個記憶體映射區,不過也可以指定該記憶體區的一個子集。flags參數如下所示的各常值的組合。

UNIX環境進階程式設計——存儲映射I/O(mmap函數)

     MS_ASYNC和MS_SYNC這兩個常值中必須指定一個,但不能都指定。他們的差别是,一旦寫操作已由核心排入隊列,MS_ASYNC即傳回,而MS_SYNC則要等到寫操作完成後才傳回。如果指定了MS_INVALIDATE,那麼與其最終副本不一緻的檔案資料的所有記憶體中副本都失效。後續的引用将從檔案中取得資料。

     為何使用mmap

     到此為止就mmap的描述符間接說明了記憶體映射檔案:我們open它之後調用mmap把它映射到調用程序位址空間的某個檔案。使用記憶體映射檔案得到的奇妙特性是,所有的I/O都在核心的掩蓋下完成,我們隻需編寫存取記憶體映射區中各個值得代碼。我們決不調用read,write或lseek。這麼一來往往可以簡化我們的代碼。

     然而需要了解以防誤解的說明是,不是所有檔案都能進行記憶體映射。例如,試圖把一個通路終端或套接字的描述符映射到記憶體将導緻mmap傳回一個錯誤。這些類型的描述符必須使用read和write(或者他們的變體)來通路。

     mmap的另一個用途是在無親緣關系的程序間提供共享記憶體區。這種情形下,所映射檔案的實際内容成了被共享記憶體區的初始内容,而且這些程序對該共享記憶體區所作的任何變動都複制回所映射的檔案(以提供随檔案系統的持續性)。這裡假設指定了MAP_SHARED标志,它是程序間共享記憶體所需求的。

示例代碼:

1 通過共享映射的方式修改檔案

#include <sys/mman.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <error.h>  
  
#define BUF_SIZE 100  
  
int main(int argc, char **argv)  
{  
    int fd, nread, i;  
    struct stat sb;  
    char *mapped, buf[BUF_SIZE];  
  
    for (i = 0; i < BUF_SIZE; i++) {  
        buf[i] = '#';  
    }  
  
    /* 打開檔案 */  
    if ((fd = open(argv[1], O_RDWR)) < 0) {  
        perror("open");  
    }  
  
    /* 擷取檔案的屬性 */  
    if ((fstat(fd, &sb)) == -1) {  
        perror("fstat");  
    }  
  
    /* 将檔案映射至程序的位址空間 */  
    if ((mapped = (char *)mmap(NULL, sb.st_size, PROT_READ |   
                    PROT_WRITE, MAP_SHARED, fd, 0)) == (void *)-1) {  
        perror("mmap");  
    }  
  
    /* 映射完後, 關閉檔案也可以操縱記憶體 */  
    close(fd);  
  
    printf("%s", mapped);  
  
    /* 修改一個字元,同步到磁盤檔案 */  
    mapped[20] = '9';  
    if ((msync((void *)mapped, sb.st_size, MS_SYNC)) == -1) {  
        perror("msync");  
    }  
  
    /* 釋放存儲映射區 */  
    if ((munmap((void *)mapped, sb.st_size)) == -1) {  
        perror("munmap");  
    }  
  
    return 0;  
}        

注釋掉44-46與沒有注釋,運作結果都為:

huangcheng@ubuntu:~$ cat data.txt
aaaaaaaaaaaa
bbbbbbbbbbbb
cccccccccccc
dddddddddddd
eeeeeeeeeeee
ffffffffffff
huangcheng@ubuntu:~$ ./a.out data.txt
aaaaaaaaaaaa
bbbbbbbbbbbb
cccccccccccc
dddddddddddd
eeeeeeeeeeee
ffffffffffff
huangcheng@ubuntu:~$ cat data.txt
aaaaaaaaaaaa
bbbbbbb9bbbb
cccccccccccc
dddddddddddd
eeeeeeeeeeee
ffffffffffff      

2 私有映射無法修改檔案

#include <sys/mman.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <error.h>  
  
#define BUF_SIZE 100  
  
int main(int argc, char **argv)  
{  
    int fd, nread, i;  
    struct stat sb;  
    char *mapped, buf[BUF_SIZE];  
  
    for (i = 0; i < BUF_SIZE; i++) {  
        buf[i] = '#';  
    }  
  
    /* 打開檔案 */  
    if ((fd = open(argv[1], O_RDWR)) < 0) {  
        perror("open");  
    }  
  
    /* 擷取檔案的屬性 */  
    if ((fstat(fd, &sb)) == -1) {  
        perror("fstat");  
    }  
  
    /* 将檔案映射至程序的位址空間,這裡是私有映射 */  
    if ((mapped = (char *)mmap(NULL, sb.st_size, PROT_READ |   
                    PROT_WRITE, MAP_PRIVATE, fd, 0)) == (void *)-1) {  
        perror("mmap");  
    }  
  
    /* 映射完後, 關閉檔案也可以操縱記憶體 */  
    close(fd);  
  
    printf("%s", mapped);  
  
    /* 修改一個字元,同步到磁盤檔案 */  
    mapped[20] = '9';  
    if ((msync((void *)mapped, sb.st_size, MS_SYNC)) == -1) {  
        perror("msync");  
    }  
  
    /* 釋放存儲映射區 */  
    if ((munmap((void *)mapped, sb.st_size)) == -1) {  
        perror("munmap");  
    }  
  
    return 0;  
}        

運作結果:

huangcheng@ubuntu:~$ cat data.txt
aaaaaaaaaaaa
bbbbbbbbbbbb
cccccccccccc
dddddddddddd
eeeeeeeeeeee
ffffffffffff
huangcheng@ubuntu:~$ ./a.out data.txt
aaaaaaaaaaaa
bbbbbbbbbbbb
cccccccccccc
dddddddddddd
eeeeeeeeeeee
ffffffffffff
huangcheng@ubuntu:~$ cat data.txt
aaaaaaaaaaaa
bbbbbbbbbbbb
cccccccccccc
dddddddddddd
eeeeeeeeeeee
ffffffffffff      

3.兩個程序中通信

     兩個程式映射同一個檔案到自己的位址空間, 程序A先運作, 每隔兩秒讀取映射區域, 看是否發生變化. 程序B後運作, 它修改映射區域, 然後推出, 此時程序A能夠觀察到存儲映射區的變化。

     程序A的代碼:

#include <sys/mman.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <error.h>  
  
#define BUF_SIZE 100  
  
int main(int argc, char **argv)  
{  
    int fd, nread, i;  
    struct stat sb;  
    char *mapped, buf[BUF_SIZE];  
  
    for (i = 0; i < BUF_SIZE; i++) {  
        buf[i] = '#';  
    }  
  
    /* 打開檔案 */  
    if ((fd = open(argv[1], O_RDWR)) < 0) {  
        perror("open");  
    }  
  
    /* 擷取檔案的屬性 */  
    if ((fstat(fd, &sb)) == -1) {  
        perror("fstat");  
    }  
  
    /* 将檔案映射至程序的位址空間 */  
    if ((mapped = (char *)mmap(NULL, sb.st_size, PROT_READ |   
                    PROT_WRITE, MAP_SHARED, fd, 0)) == (void *)-1) {  
        perror("mmap");  
    }  
  
    /* 檔案已在記憶體, 關閉檔案也可以操縱記憶體 */  
    close(fd);  
      
    /* 每隔兩秒檢視存儲映射區是否被修改 */  
    while (1) {  
        printf("%s\n", mapped);  
        sleep(2);  
    }  
  
    return 0;  
}        

程序B的代碼:

#include <sys/mman.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <error.h>  
  
#define BUF_SIZE 100  
  
int main(int argc, char **argv)  
{  
    int fd, nread, i;  
    struct stat sb;  
    char *mapped, buf[BUF_SIZE];  
  
    for (i = 0; i < BUF_SIZE; i++) {  
        buf[i] = '#';  
    }  
  
    /* 打開檔案 */  
    if ((fd = open(argv[1], O_RDWR)) < 0) {  
        perror("open");  
    }  
  
    /* 擷取檔案的屬性 */  
    if ((fstat(fd, &sb)) == -1) {  
        perror("fstat");  
    }  
  
    /* 将檔案映射至程序的位址空間 */ 
    if ((mapped = (char *)mmap(NULL, sb.st_size, PROT_READ |   
                    PROT_WRITE, MAP_SHARED, fd, 0)) == (void *)-1) {  
        perror("mmap");  
    }  
  
    /* 映射完後, 關閉檔案也可以操縱記憶體 */  
    close(fd);  
  
    /* 修改一個字元 */  
    mapped[20] = '9';  
   
    return 0;  
}        
huangcheng@ubuntu:~$ ./a data.txt
aaaaaaaaaaaa
bbbbbbbbbbbb
cccccccccccc
dddddddddddd
eeeeeeeeeeee
ffffffffffff

aaaaaaaaaaaa
bbbbbbb9bbbb
cccccccccccc
dddddddddddd
eeeeeeeeeeee
ffffffffffff

aaaaaaaaaaaa
bbbbbbb9bbbb
cccccccccccc
dddddddddddd
eeeeeeeeeeee
ffffffffffff
............      

如果程序B中的映射設定為私有映射,運作結果:

huangcheng@ubuntu:~$ ./a data.txt
aaaaaaaaaaaa
bbbbbbbbbbbb
cccccccccccc
dddddddddddd
eeeeeeeeeeee
ffffffffffff

aaaaaaaaaaaa
bbbbbbbbbbbb
cccccccccccc
dddddddddddd
eeeeeeeeeeee
ffffffffffff

aaaaaaaaaaaa
bbbbbbbbbbbb
cccccccccccc
dddddddddddd
eeeeeeeeeeee
ffffffffffff
............      

4. 匿名映射實作父子程序通信

#include <sys/mman.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
  
#define BUF_SIZE 100  
  
int main(int argc, char** argv)  
{  
    char    *p_map;  
  
    /* 匿名映射,建立一塊記憶體供父子程序通信 */  
    p_map = (char *)mmap(NULL, BUF_SIZE, PROT_READ | PROT_WRITE,  
            MAP_SHARED | MAP_ANONYMOUS, -1, 0);  
  
    if(fork() == 0) {  
        sleep(1);  
        printf("child got a message: %s\n", p_map);  
        sprintf(p_map, "%s", "hi, dad, this is son");  
        munmap(p_map, BUF_SIZE); //實際上,程序終止時,會自動解除映射。  
        exit(0);  
    }  
  
    sprintf(p_map, "%s", "hi, this is father");  
    sleep(2);  
    printf("parent got a message: %s\n", p_map);  
  
    return 0;  
}        

四、對mmap位址的通路

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

        mmap将一個檔案或者其它對象映射進記憶體。檔案被映射到多個頁上,如果檔案的大小不是所有頁的大小之和,最後一個頁不被使用的空間将會清零。

        mmap()必須以PAGE_SIZE()為機關進行映射,而記憶體也隻能以頁為機關進行映射,若要映射非PAGE_SIZE整數倍的位址範圍,要先進行記憶體對齊,強行以PAGE_SIZE的倍數大小進行映射。

        mmap操作提供了一種機制,讓使用者程式直接通路裝置記憶體,這種機制,相比較在使用者空間和核心空間互相拷貝資料,效率更高。在要求高性能的應用中比較常用。mmap映射記憶體必須是頁面大小的整數倍,面向流的裝置不能進行mmap,mmap的實作和硬體有關。

       記憶體映射一個普通檔案時,記憶體中映射區的大小(mmap的第二個參數)通常等于該檔案的大小,然而檔案的大小和記憶體的映射區大小可以不同。

       我們要展示的第一種情形的前提是:檔案大小等于記憶體映射區大小,但這個大小不是頁面大小的倍數。

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/mman.h>
#define max(A,B) (((A)>(B))?(A):(B))

int main(int argc,char ** argv)
{
  int fd,i;
  char *ptr;
  size_t filesize,mmapsize,pagesize;

  if(argc != 4)
    printf("usage:tes1 <pathname> <filesname> <mmapsize>\n");
  filesize = atoi(argv[2]);
  mmapsize = atoi(argv[3]);

 /*open file:create or truncate;set file size*/
  fd = open(argv[1],O_RDWR | O_CREAT | O_TRUNC,0777);
  lseek(fd,filesize-1,SEEK_SET);
  write(fd," ",1);
  ptr = mmap(NULL,mmapsize,PROT_READ |PROT_WRITE,MAP_SHARED,fd,0);
  close(fd);

  pagesize = sysconf(_SC_PAGESIZE);
  printf("PAGESIZE = %ld\n",(long)pagesize);

  for(i = 0;i < max(filesize,mmapsize); i += pagesize)
  {
    printf("ptr[%d] = %d\n",i,ptr[i]);
    ptr[i] = 1;
    printf("ptr[%d] = %d\n",i + pagesize - 1,ptr[i + pagesize - 1]);
    ptr[i + pagesize - 1] = 1;
  }
  printf("ptr[%d] = %d\n",i,ptr[i]);
  exit(0);
}      

指令行參數

16-19   指令行參數有三個,分别指定即将建立并映射到記憶體的檔案的路徑名,該檔案将被設定成的大小以及記憶體映射區得大小。

建立,打開并截斷檔案;設定檔案大小

22-24   待打開的檔案若不存在則建立之,若已存在則把它的大小截短成0.接着把該檔案的大小設定成由指令行參數指定的大小,辦法是把檔案讀寫指針移動到這個大小減去1的位元組位置,然後寫1個位元組。

記憶體映射檔案

25-26   使用作為最後一個指令行參數指定的大小對該檔案進行記憶體映射。其描述符随後被關閉。

輸出頁面大小

28-29   使用sysconf擷取系統實作的頁面大小并将其輸出。

讀出和存入記憶體映射區

31-38   讀出記憶體映射區中每個頁面的首位元組和尾位元組,并輸出他們的值。我們預期這些值全為0.同時把每個頁面的這兩個位元組設定為1,。我們預期某個引用會最終引發一個信号,它将終止程式。當for循環結束時,我們輸出下一頁的首位元組,并預期這會失敗。

huangcheng@ubuntu:~$ ls -l test
ls: 無法通路test: 沒有那個檔案或目錄
huangcheng@ubuntu:~$ ./a.out test 5000 5000
PAGESIZE = 4096
ptr[0] = 0
ptr[4095] = 0
ptr[4096] = 0
ptr[8191] = 0
段錯誤
huangcheng@ubuntu:~$ ls -l test
-rwxr-xr-x 1 huangcheng huangcheng 5000 2013-07-09 15:48 test
huangcheng@ubuntu:~$ od -b -A d test
0000000 001 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000
0000016 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000
*
0004080 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 001
0004096 001 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000
0004112 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000
*
0004992 000 000 000 000 000 000 000 040
0005000      

     頁面大小為4096位元組,我們能夠讀完整的第2頁(下标為4096-8191),但是通路第3頁時(下标為8192)引發SIGSEGV信号,shell将它輸出成"Segmentation Fault(分段障)"。盡管我們把ptr[8191]設定成1,它也不寫到test檔案中,因而該檔案的大小仍然是5000.核心允許我們讀寫最後一頁中映射區以遠部分(核心的記憶體保護是以頁面為機關的)。但是我們寫向這部分擴充區的任何内容都不會寫到test檔案中。設定成1的其他3個位元組(下标分别為0,4905和4906)複制回test檔案,這一點可使用od指令來驗證。(-b選項指定以八進制數輸出各個位元組,-A d選項指定以十進制數輸出位址。)

UNIX環境進階程式設計——存儲映射I/O(mmap函數)

       我們仍然能通路記憶體映射區以遠部分,不過隻能在邊界所在的那個記憶體頁面内(下标為5000-8191)。通路ptr[8192]将引發SIGSEGV信号,這是我們預期的。

       現在我們把記憶體映射區大小(15000位元組)指定成大于檔案大小(5000位元組)。

huangcheng@ubuntu:~$ ./a.out test 5000 15000
PAGESIZE = 4096
ptr[0] = 0
ptr[4095] = 0
ptr[4096] = 0
ptr[8191] = 0
總線錯誤
huangcheng@ubuntu:~$ ls -l test
-rwxr-xr-x 1 huangcheng huangcheng 5000 2013-07-09 16:00 test      

     其結果與先前那個檔案大小等于記憶體映射區大小(都是5000位元組)的例子類似。本例子引發SIGBUS信号(其shell輸出為"Bus Error(總線出錯)"),前一個例子則引發SIGSEGV信号。兩者的差别是,SIGBUS意味着我們是在記憶體映射區通路的,但是已超出了底層支撐對象的大小。上一個例子中的SIGSEGV則意味着我們已在記憶體映射區以遠通路。可以看出,核心知道被映射的底層支撐對象(本例子中為檔案test)的大小,即使我們通路不了該對象以遠的部分(最後一頁上該對象以遠的那些位元組除外,他們的下标為5000-8191)。

UNIX環境進階程式設計——存儲映射I/O(mmap函數)

注意:

huangcheng@ubuntu:~$ ./a.out test 5000 1000
PAGESIZE = 4096
ptr[0] = 0
ptr[4095] = 0
段錯誤
huangcheng@ubuntu:~$ od -b -A d test
0000000 001 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000
0000016 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000
*
0004080 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 001   //修改為1了
0004096 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000
*
0004992 000 000 000 000 000 000 000 040
0005000      

     當檔案長度為5000位元組,映射1000位元組時,但是1000位元組不是PAGE_SIZE(4096)的整數倍,這樣會強制映射為PAGE_SIZE的整數倍,現在映射的是4096位元組。是以對映射區裡面的第4096位元組進行修改為1,會同步到檔案中。

     下面的程式展示了處理一個持續增長的檔案的一種常用技巧:指定一個大于該檔案大小的記憶體映射區大小,跟蹤該檔案的目前大小(以確定不通路目前檔案尾以遠的部分),然後就讓該檔案的大小随着往其中每次寫入資料而增長。

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <sys/types.h>
#define FILE "test.data"
#define SIZE 32768
int main(int argc,char **argv)
{
  int fd,i;
  char *ptr;

  /*open:create or truncate;then mmap file */
  fd = open(FILE,O_RDWR | O_CREAT | O_TRUNC,0777);
  ptr = mmap(NULL,SIZE,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);

  for(i = 4096;i <= SIZE;i += 4096)
  {
   printf("setting file size to %d\n",i);
   ftruncate(fd,i);
   printf("ptr[%d] = %d\n",i-1,ptr[i-1]);
  }
  exit(0);
}      

打開檔案

15-17  打開一個檔案,若不存在則建立之,若已存在則把它截短成大小為0.以32768位元組的大小對該檔案進行記憶體映射,盡管它目前的大小為0.

增長檔案大小

19-24  通過調用ftruncate函數把檔案的大小每次增長4096位元組,然後取出現在是該檔案最後一個位元組的那個位元組。

huangcheng@ubuntu:~$ ls -l test.data
ls: 無法通路test.data: 沒有那個檔案或目錄
huangcheng@ubuntu:~$ ./a.out
setting file size to 4096
ptr[4095] = 0
setting file size to 8192
ptr[8191] = 0
setting file size to 12288
ptr[12287] = 0
setting file size to 16384
ptr[16383] = 0
setting file size to 20480
ptr[20479] = 0
setting file size to 24576
ptr[24575] = 0
setting file size to 28672
ptr[28671] = 0
setting file size to 32768
ptr[32767] = 0
huangcheng@ubuntu:~$ ls -l test.data
-rwxr-xr-x 1 huangcheng huangcheng 32768 2013-07-09 16:47 test.data      

     本例子表明,核心跟蹤着被記憶體映射的底層支撐對象(本例子中為檔案test.data)的大小,而且我們總是能通路在目前檔案大小以内又在記憶體映射區以内的那些位元組。