1. 文件通信
如果你已經完成了進程間通信的學習,相信你對進程間通信有了一個初步的解了。由於進程之間是相互獨立的,不能相互訪問,進程之間想要通信必須藉助內核空間,因爲內核空間對於進程之間是共享的。
像這樣的進程間通信的方式有很多種,例如管道、信號、共享內存、消息隊列、套接字、命名管道等,在linux早期是使用文件來完成進程間通信的。
對於文件通信,本質上也是通過內核空間來完成的。首先系統內核會創建一個內核緩衝區,通過把文件映射到內核緩衝區裏,然後進程將數據寫入到緩衝區,另一進程從緩衝區將數據讀走,完成進程間通信。
接下來要講的內存映射和文件通信有很多相似之處。
2. mmap函數創建內存映射
內存映射是讓一個磁盤文件與進程空間中的一塊虛擬內存區域映射,完成映射後,進程就可以通過在內存映射區中讀寫操作來訪問文件中的數據,換句話說,你可以理解爲對這塊內存的讀寫數據等同於讀寫文件。
mmap函數就是用於完成內存映射的
<sys/mman.h>
void *mmap(void *adrr, size_t length, int prot, int flags, int fd, off_t offset);
返回值說明:成功返回創建的映射區void *類型的首地址;失敗返回MAP_FAILED宏(這個宏其實就是個void *類型的-1,即(void *)-1 )。
參數說明:
addr : 建立內存映射區的首地址,如果指定addr爲NULL,那麼將由Linux內核自動指定一個合適的首地址。
length: 創建映射區的大小,以字節爲單位(建議length最好是系統內存頁的整數倍)。
prot: 內存映射區的保護權限,可以多個選項組合使用,例如PORT_READ | PORT_WRITE表示可讀寫。
值 | 描述 |
PROT_EXEC | 內存映射區域可以被執行 |
PROT_READ | 內存映射區域可以被讀取 |
PROT_WRITE | 內存映射區域可以被寫入 |
PROT_NONE | 內存映射區域不可訪問 |
flags:用於控制內存映射區操作的選項。
如果flags = MAP_PRIVATE,創建一個私有的映射區,在映射區所做的修改操作不會反映到物理磁盤的文件上,對使用同一映射區的其他進程不可見。
如果flags = MAP_SHARED,創建一個共享的映射區,在映射區所做的修改操作會直接反映到物理磁盤的文件上,那麼這一修改操作對其他進程都可見。
fd:用來創建內存映射區的文件描述符
offset:從文件的哪個位置開始映射,如果offset爲0表示從文件頭開始映射,注意offset必須是內存頁4k的整數倍,數據類型爲off_t,在32位系統下爲long int類型,64爲linux系統爲long long int類型。
3. mmap函數映射過程
圖1-內存映射文件過程(圖片來自Linux/UNIX系統編程手冊)
length是映射到進程地址空間的字節數,offset表示文件的映射位置從文件頭到第offset個字節開始,通常offset設置爲0表示從文件頭開始映射,當映射成功,mmap將會返回映射區域的首地址。
4. munmap函數
mmap函數是用於解除內存映射,即從進程的虛擬地址空間刪除映射。
#include <sys/mman.h>
int munmap(void *addr, size_t length);
返回值說明:成功返回0,失敗返回-1
參數addr:由mmap返回的內存映射區首地址
參數length:映射區大小
5. 內存映射示例程序
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <errno.h>
int main(void){
char *addr;
int len = 0;
int ret;
int fd = open("test.txt", O_RDWR|O_CREAT, 0644);
if (fd < 0){
perror("open error");
}
//拓展文件大小1024
lseek(fd , 1024, SEEK_SET);
write(fd , "0" , 1);
//指向文件開頭
lseek(fd , 0 , SEEK_SET);
//創建共享映射區,可讀寫
addr =(char *)mmap(NULL , 1024 , PROT_WRITE|PROT_READ , MAP_SHARED , fd , 0);
//創建私有映射區,可讀寫
//addr = mmap(NULL, 1024, PROT_WRITE | PROT_READ, MAP_PRIVATE, fd, 0);
if (addr == MAP_FAILED){
perror("mmap err: ");
}
//關閉文件與文件描述符間的關聯
close(fd);
//實際操作是通過mmap返回的文件指針去操作文件讀寫,不需要用到文件描述符
strcpy(addr, "AAAAABBBBB");
printf("%s\n", addr);
//解除映射
ret = munmap(addr , 1024);
if(ret < 0){
perror("munmap error: ");
}
return 0;
}
在test文件寫入hello world:
[root@localhost memory]# cat test.txt
hello world
[root@localhost memory]#
指定映射區爲MAP_PRIVATE,程序執行結果:
指定MAP_PRIVATE創建私有映射區,對映射區所做的修改操作不會反映到物理磁盤上的文件。
指定映射區爲MAP_SHARED:
指定MAP_SHARED創建共享映射區,對映射區所做的修改操作會反映到物理磁盤上的文件。
6. munmap函數使用細節
1. 如果參數addr和length指定的區域不存在映射關係,那麼調用munmap函數將不會發生任何事情並返回0(表示成功)。
2. 當一個進程終止或調用了exec系列函數後,進程中所有的映射關係將自動解除。
3. 爲確保映射區的數據寫入物理磁盤上的文件中,在調用munmap解除映射前需要調用msync函數。
4. munmap函數可以解除映射區中的部分區域的映射,一旦這樣做的話可能會使原來的映射區變小或分成兩個映射區,強烈建議不要這麼做。