Unix/Linux環境C程式設計入門教程(27) 記憶體那些事兒

尹成發表於2014-07-20
  1. calloc() free() getpagesize() malloc() mmap() munmap()函式介紹

calloc(配置記憶體空間)

相關函式

malloc,free,realloc,brk

表標頭檔案

#include <stdlib.h>

定義函式

void *calloc(size_t nmemb,size_t size);

函式說明

calloc()用來配置nmemb個相鄰的記憶體單位,每一單位的大小為size,並返回指向第一個元素的指標。這和使用下列的方式效果相同:malloc(nmemb*size);不過,在利用calloc()配置記憶體時會將記憶體內容初始化為0。

返回值

若配置成功則返回一指標,失敗則返回NULL。

範例

/* 動態配置10個struct test 空間*/
#include<stdlib.h>
struct test
{
int a[10];
char b[20];
}
main()
{
struct test *ptr=calloc(sizeof(struct test),10);
}

   




free(釋放原先配置的記憶體)

相關函式

malloc,calloc,realloc,brk

表標頭檔案

#include<stdlib.h>

定義函式

void free(void *ptr);

函式說明

引數ptr為指向先前由malloc()、calloc()或realloc()所返回的記憶體指標。呼叫free()後ptr所指的記憶體空間便會被收回。假若引數ptr所指的記憶體空間已被收回或是未知的記憶體地址,則呼叫free()可能會有無法預期的情況發生。若引數ptr為NULL,則free()不會有任何作用。

   




getpagesize(取得記憶體分頁大小)

相關函式

sbrk

表標頭檔案

#include<unistd.h>

定義函式

size_t getpagesize(void);

函式說明

返回一分頁的大小,單位為位元組(byte)。此為系統的分頁大小,不一定會和硬體分頁大小相同。

返回值

記憶體分頁大小。附加說明在Intel x86 上其返回值應為4096bytes。

範例

#include <unistd.h>
main()
{
printf("page size = %d\n",getpagesize( ) );
}

   




malloc(配置記憶體空間)

相關函式

calloc,free,realloc,brk

表標頭檔案

#include<stdlib.h>

定義函式

void * malloc(size_t size);

函式說明

malloc()用來配置記憶體空間,其大小由指定的size決定。

返回值

若配置成功則返回一指標,失敗則返回NULL。

範例

void p = malloc(1024); /*配置1k的記憶體*/

   




相關函式

munmap,open

表標頭檔案

#include <unistd.h>
#include <sys/mman.h>

定義函式

void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offsize);

函式說明

mmap()用來將某個檔案內容對映到記憶體中,對該記憶體區域的存取即是直接對該檔案內容的讀寫。引數start指向欲對應的記憶體起始地址,通常設為NULL,代表讓系統自動選定地址,對應成功後該地址會返回。引數length代表將檔案中多大的部分對應到記憶體。

引數

prot代表對映區域的保護方式有下列組合
PROT_EXEC 對映區域可被執行
PROT_READ 對映區域可被讀取
PROT_WRITE 對映區域可被寫入
PROT_NONE 對映區域不能存取

引數

flags會影響對映區域的各種特性
MAP_FIXED 如果引數start所指的地址無法成功建立對映時,則放棄對映,不對地址做修正。通常不鼓勵用此旗標。
MAP_SHARED對對映區域的寫入資料會複製迴檔案內,而且允許其他對映該檔案的程式共享。
MAP_PRIVATE 對對映區域的寫入操作會產生一個對映檔案的複製,即私人的"寫入時複製"(copy on write)對此區域作的任何修改都不會寫回原來的檔案內容。
MAP_ANONYMOUS建立匿名對映。此時會忽略引數fd,不涉及檔案,而且對映區域無法和其他程式共享。
MAP_DENYWRITE只允許對對映區域的寫入操作,其他對檔案直接寫入的操作將會被拒絕。
MAP_LOCKED 將對映區域鎖定住,這表示該區域不會被置換(swap)。
在呼叫mmap()時必須要指定MAP_SHARED 或MAP_PRIVATE。引數fd為open()返回的檔案描述詞,代表欲對映到記憶體的檔案。引數offset為檔案對映的偏移量,通常設定為0,代表從檔案最前方開始對應,offset必須是分頁大小的整數倍。

返回值

若對映成功則返回對映區的記憶體起始地址,否則返回MAP_FAILED(-1),錯誤原因存於errno 中。

錯誤程式碼

EBADF 引數fd 不是有效的檔案描述詞
EACCES 存取許可權有誤。如果是MAP_PRIVATE 情況下檔案必須可讀,使用MAP_SHARED則要有PROT_WRITE以及該檔案要能寫入。
EINVAL 引數start、length 或offset有一個不合法。
EAGAIN 檔案被鎖住,或是有太多記憶體被鎖住。
ENOMEM 記憶體不足。





   




munmap(解除記憶體對映)

相關函式

mmap

表標頭檔案

#include<unistd.h>
#include<sys/mman.h>

定義函式

int munmap(void *start,size_t length);

函式說明

munmap()用來取消引數start所指的對映記憶體起始地址,引數length則是欲取消的記憶體大小。當程式結束或利用exec相關函式來執行其他程式時,對映記憶體會自動解除,但關閉對應的檔案描述詞時不會解除對映。

返回值

如果解除對映成功則返回0,否則返回-1,錯誤原因存於errno中錯誤程式碼EINVAL

引數

start或length 不合法。

範例

參考mmap()

  1. Linux檔案與檔案描述符的概念
  • 檔案的概念

    大多數資源,Linux都是以檔案的方式來訪問。

Linux中主要的檔案型別分為4種:普通檔案、目錄檔案、連結檔案和裝置檔案

Linux中的檔案屬性:

-rwx rwx rwx

首先,Linux中檔案的擁有者可以把檔案的訪問屬性設成3種不同的訪問許可權:可讀(r)、可寫(w)和可執行(x)。

檔案又有3種不同的使用者級別:檔案擁有者(u)、所屬的使用者組(g)和系統裡的其他使用者(o)

第一個字元顯示檔案的型別:

"-"表示普通檔案

"d"表示目錄檔案

"l"表示連結檔案

"c"表示字元裝置

"b"表示塊裝置

"p"表示命名管道比如FIFO檔案

"f"表示堆疊檔案比如LILO檔案

第一個字元之後的3個三位字元組:

第一個三位字元組表示檔案擁有者(u)對該檔案的許可權

第二個三位字元組表示檔案使用者組(g)對該檔案的許可權

第三個三位字元組表示系統其他使用者(o)對該檔案的許可權

若該使用者組對此沒有許可權,一般顯示"-"字元

如圖所示:

  • 檔案描述符。

檔案描述符是個很小的正整數,它是一個索引值,指向核心為每個程式所維護的該程式開啟檔案的記錄表。

  1. 記憶體對映機制與系統呼叫

    實際上,記憶體對映機制並不是完全為了共享記憶體的目的而設計的,它本身提供了不同於一般普通檔案的訪問方式,程式可以像訪問記憶體一樣對普通檔案程式操作.而POSIX或System V共享記憶體IPC則純粹是用於共享記憶體的目的.當然記憶體對映實現共享記憶體,也是記憶體對映的應用之一

    記憶體對映機制的用途: A、以訪問記憶體的方式讀寫檔案; B、實現共享記憶體;

     

    mmap()系統呼叫:
       mmap()系統呼叫使得程式之間通過對映同一個普通檔案而實現共享記憶體的目的.普通檔案被對映到程式的地址空間之後,程式就可以像訪問普通記憶體一樣對檔案進行訪問,不必再呼叫read()、write()等系統呼叫操作.

       mmap()系統呼叫介紹:
       void* mmap(void* addr, size_t len, int prot, int flags, int fd, off_t offset);

       該函式在程式的地址空間與檔案物件或共享記憶體物件之間建立一種對映關係;

       addr  :該引數指定檔案應該被對映到程式地址空間的起始地址,一般被指定為一個空指標,此時,程式把選擇起始地址的任務留給核心來完成了.這個地址是程式地址空間中需要對映到檔案中的記憶體區域的首地址;也就是說,在程式地址空間中用於檔案對映的記憶體區域的首地址;

       len   :檔案被對映到呼叫程式的地址空間中的位元組數,它從被對映檔案開頭offset個位元組處開始算起,取len個位元組,把檔案中的這len個位元組的檔案空間對映到程式的地址空間中;

       port  :指定檔案被對映到記憶體中之後的訪問許可權.可取的值有:PORT_READ(可讀)、PORT_WRITE(可寫)、PORT_EXEC(可執行)、PORT_NONE(不可訪問);
       flags :對映標記;取值如下:MAP_SHARED、MAP_PRIVATE、MAP_FIXED,其中,MAP_SHARED和MAP_PRIVATE必選其一,而MAP_FIXED則不推薦使用;

       fd    :即將被對映到程式地址空間中的檔案的描述符.一般由系統呼叫open()返回;同時,fd可以指定為-1,此時,必須指定flags引數中的MAP_ANON,表明程式的是匿名對映(不涉及具體的檔名,避免了檔案的建立及開啟,很顯然,只能用於具有親屬關係的程式之間的通訊).

       offset:從檔案開頭計算offset個位元組處開始對映;也就是,檔案中需要被對映的檔案內容的起始地址,這個起始地址的計算是以檔案開頭為參照的;這個引數一般取值為0,表示從檔案開頭處開始對映;
       返回值:檔案最終對映到程式地址空間中的起始地址;程式可直接以該地址為有效的起始地址進行操作;也就是檔案中開始對映的起始位元組點到程式中對應對映記憶體區的起始地址點處的一個對映;換句話就是說,在程式地址空間中用於檔案對映的記憶體區域的首地址;

    系統呼叫mmap()用於共享記憶體的兩種方式:
       A、使用普通檔案提供的記憶體對映/共享記憶體:適用於任何程式之間;此時,需要使用系統呼叫open()事先開啟或建立一個檔案,然後再呼叫mmap():
          fd = open(filename, flag, mode);
          ......
          ptr = mmap(NULL, len, PORT_READ|PORT_WRITE, MAP_SHARED, fd, 0);
         五、解除記憶體對映關係:
       當程式間通訊結束時,需要解除檔案頁面空間到程式地址空間之間的對映關係;也就說,程式通訊結束時,需要把掛載到程式地址空間上的檔案解除安裝下來;這個任務由系統呼叫munmap();
       int munmap(void* addr, size_t len);
       該系統呼叫用於在程式地址空間中結束對映關係;
       addr:是呼叫mmap()返回的程式地址空間中用於檔案對映的記憶體區域的首地址;
       len :程式地址空間中對映區域的大小,單位:位元組;
       當對映關係解除之後,對原來對映地址的訪問將導致段錯誤發生;
       返回值: -1:失敗; 0:成功;
    記憶體對映的同步:
       一般來說,程式在對映空間中對共享內容的修改並不會直接寫回到磁碟檔案中,可以通過呼叫msync()來實現磁碟上檔案內容與共享記憶體區中的內容與一致

  2. 小試牛刀

    整體流程就是:

    1. 首先開啟一個檔案,讀取出檔案資訊
    2. 根據檔案長度分配相應長度的堆記憶體
    3. Mmap函式做記憶體對映
    4. 從記憶體對映中開始拷貝內容
    5. 輸出函式返回的地址開始的內容
    6. 輸出完畢之後關閉記憶體對映檔案
    7. 輸出拷貝的檔案
    8. 釋放記憶體
    9. 關閉檔案描述符

    原始碼:

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/mman.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdlib.h>
     
    #include <string.h>
    int main()
    {
    int fd;//檔案描述符
    char *start = NULL,*flag = NULL;//指標接收檔案影射的開始地址
    struct stat st;
    printf("page size = %d\n",getpagesize( ) );
    fd=open("read.log",O_RDONLY); /*開啟/etc/passwd*/
    if(fd < -1) //開啟失敗
    return -1;
    printf("開啟成功\n");
    fstat(fd,&st); /*取得檔案大小*/
    start =(char *) malloc(st.st_size+1);
    if(start == NULL)
    return -1;
    printf("記憶體分配成功\n");
    flag=mmap(start,st.st_size,PROT_READ,MAP_PRIVATE,fd,0); /*私有不共享*/
    if(flag == MAP_FAILED && start != flag) /*判斷是否對映成功*/
    return -1;
    printf("對映成功\n");
     
    printf("%s",flag);
    memcpy(start,flag,st.st_size);
    printf("輸出完成\n");
    munmap(start,st.st_size); /*解除對映*/
    printf("解除對映完畢\n");
             return 0;
    }


  3. 各個平臺的執行情況

    首先建立testmem.c

    再建立read.log

    在RHEL7上的執行情況

    在RHEL6上

    在MAC上

     

    在Solaris上

    先進入桌面

    將兩個檔案都拷貝過來

相關文章