mmap內存映射
內存共享定義
內存映射,簡而言之就是將用戶空間的一段內存區域映射到內核空間,映射成功后,用戶對這段內存區域的修改可以直接反映到內核空間,同樣,內核空間對這段區域的修改也直接反映用戶空間。那么對于內核空間<---->用戶空間兩者之間需要大量數據傳輸等操作的話效率是非常高的。
以下是一個把普遍文件映射到用戶空間的內存區域的示意圖
mmap內存映射方法
mmap是一種內存映射文件的方法,即將一個文件或者其它對象映射到進程的地址空間,實現文件磁盤地址和進程虛擬地址空間中一段虛擬地址的一一對映關系。
實現這樣的映射關系后,進程就可以采用指針的方式讀寫操作這一段虛擬內存,而系統會自動回寫到對應的文件磁盤上,即完成了對文件的操作而不必再調用read,write等系統調用函數。
相反,內核空間對這段區域的修改也直接反映用戶空間,從而可以實現不同進程間的文件共享。如下圖所示:
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
參數說明:
參數start:指向欲映射的內存起始地址,通常設為 NULL,代表讓系統自動選定地址,映射成功后返回該地址。
參數length:代表將文件中多大的部分映射到內存。
參數prot:映射區域的保護方式。可以為以下幾種方式的組合:
PROT_READ(可讀)
PROT_WRITE(可寫)
PROT_EXEC(可執行)
PROT_NONE(不可訪問)
參數flags:由以下幾個常值指定:
MAP_SHARED(共享的)
MAP_PRIVATE(私有的)
MAP_FIXED(表示必須使用 start 參數作為開始地址,如果失敗不進行修正)
其中,MAP_SHARED , MAP_PRIVATE必選其一,而 MAP_FIXED 則不推薦使用。MAP_ANONYMOUS(匿名映射,用于血緣關系進程間通信)
參數fd:表示要映射的文件句柄。如果匿名映射寫-1。
參數offset:表示映射文件的偏移量,一般設置為 0 表示從文件頭部開始映射。offset必須是分頁大小的整數倍(一般是4096的整數倍)。
寫共享文件
#include"stdio.h"
#include"unistd.h"
#include"string.h"
#include"fcntl.h"
#include"sys/mman.h"
#include"sys/stat.h"
#include"stdlib.h"
#include"sys/types.h"
int main(int agrc,char* agrv[])
{int len;int fd;void* map;fd = open("1.txt",O_RDWR);if(fd<0){perror("open");return -1;}len = lseek(fd, 0, SEEK_END); printf("%d\n",len);map = mmap(NULL,len,PROT_WRITE,MAP_SHARED,fd,0);if(map == MAP_FAILED){perror("mmap");return -1;}for(int i=0;i<5;i++){memcpy(map++, "b", 1);}return 0;
}
讀共享文件
#include"stdio.h"
#include"unistd.h"
#include"string.h"
#include"fcntl.h"
#include"sys/mman.h"
#include"sys/stat.h"
#include"stdlib.h"
#include"sys/types.h"
int main(int agrc,char* agrv[])
{int len;int fd;void* map;fd = open("1.txt",O_RDWR);if(fd<0){perror("open");return -1;}len = lseek(fd, 0, SEEK_END); map = mmap(NULL,len,PROT_READ,MAP_SHARED,fd,0);if(map == MAP_FAILED){perror("mmap");return -1;}while (1) {printf("%s\n",(char*)map);}return 0;
}
mmap內存映射注意事項
(1) 創建映射區的過程中,隱含著一次對映射文件的讀操作,將文件內容讀取到映射區。
(2) 當MAP_SHARED時,要求:映射區的權限應 <= 文件打開的權限(出于對映射區的保護),如果不滿足報非法參數(Invalid argument)錯誤。
當MAP_PRIVATE時候,mmap中的權限是對內存的限制,只需要文件有讀權限即可,操作只在內存有效,不會寫到物理磁盤,且不能在進程間共享。
(3) 映射區的釋放與文件關閉無關,只要映射建立成功,文件可以立即關閉。
(4) 用于映射的文件大小必須>0,當映射文件大小為0時,指定非0大小創建映射區,訪問映射地址會報總線錯誤,指定0大小創建映射區,報非法參數錯誤(Invalid argument)
(5) 文件偏移量必須為0或者4096的整數倍(不是會報非法參數Invalid argument錯誤).
(6)映射大小可以大于文件大小,但只能訪問文件page的內存地址,否則報總線錯誤 ,超出映射的內存大小報段錯誤.
system V共享內存
使用system V共享內存的步驟
1、創建/打開共享內存。
2、映射共享內存,即把指定的共享內存映射到進程的地址空間用于訪問。
3、讀寫共享內存。
4、撤銷共享內存映射。
5、刪除共享內存對象。
相關API
//共享內存創建
int shmget(key_t key, int size, int shmflg);
//共享內存映射
void *shmat(int shmid, const void *shmaddr, int shmflg);
//共享內存撤銷,撤銷后內存地址不可再訪問
int shmdt(void *shmaddr);
//共享內存控制
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmctl(shmid, IPC_RMID, NULL);刪除共享內存
寫數據
#include<stdio.h>
#include"stdlib.h"
#include"unistd.h"
#include"sys/types.h"
#include"string.h"
#include"sys/stat.h"
#include"sys/shm.h"
#include"sys/ipc.h"
int main(int agrc,char* agrv[])
{key_t key;int shimd;void* shmaddr;key = ftok("2.txt", 100);printf("key = %d\n",key);if(key == -1){perror("key");return -1;}shimd = shmget(key, 512, IPC_CREAT|0666);printf("shimd = %d\n",shimd);if(shimd == -1){perror("get");return -1;}shmaddr = shmat(shimd, NULL, 0);strcpy(shmaddr, "hello,world!");sleep(1);return 0;
}
讀數據
#include<stdio.h>
#include"stdlib.h"
#include"unistd.h"
#include"sys/types.h"
#include"string.h"
#include"sys/stat.h"
#include"sys/shm.h"
#include"sys/ipc.h"
int main(int agrc,char* agrv[])
{key_t key;int shimd;void* shmaddr;key = ftok("2.txt", 100);if(key == -1){perror("key");return -1;}shmaddr = shmat(32811, NULL, 0);printf("%s\n",(char*)shmaddr);shmdt(shmaddr);sleep(1);return 0;
}