存儲映射
存儲映射I/O(Memory-mapped I/O)使一個磁盤文件與存儲空間中的一個緩沖區相映射. 於是當從緩沖區中取數據, 就相當於讀文件中的相應字節. 與此類似, 將數據存入緩沖區, 則相應的字節就自動寫入文件. 這樣, 就可在不適用read和write函數的情況下, 使用地址(指針)完成I/O操作
使用這種方法, 首先應通知內核, 將一個指定文件映射到存儲區域中. 這個映射工作可以通過mmap函數來實現
作用: 將磁盤文件的數據映射到內存, 用戶通過修改內存就能修改磁盤文件
mmap創建內存映射
匿名映射
通過使用我們發現, 使用映射區來完成文件讀寫操作十分方便, 父子進程間通信也較容易. 但缺陷是, 每次創建映射區一定要依賴一個文件才能實現. 通常為了建立映射區要open一個temp文件, 創建好了再unlink, close掉, 比較麻煩. 可以直接使用匿名映射來代替. 其實Linux系統給我們提供了創建匿名映射區的方法,無需依賴一個文件即可創建映射區。同樣需要借助標志位參數flags來指定
使用MAP_ANONYMOUS (或MAP_ANON), 如: int *p = mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
需注意的是, MAP_ANONYMOUS和MAP_ANON這兩個宏是Linux操作系統特有的宏. 在類Unix系統中如無該宏定義, 可使用如下兩步來完成匿名映射區的建立
fd = open("/dev/zero", O_RDWR);
p = mmap(NULL, size, PROT_READ|PROT_WRITE, MMAP_SHARED, fd, 0);
mmap無血緣關系進程間通信
實質上mmap是內核借助文件幫我們創建了一個映射區, 多個進程之間利用該映射區(需借助文件)完成數據傳遞. 由於內核空間多進程共享, 因此無血緣關系的進程間也可以使用mmap來完成通信. 只要設置相應的標志位參數flags即可. 若想實現共享, 當然應該使用MAP_SHARED了
基礎API
mmap
void *mmap(
void *addr, // 建立映射區首地址, 由linux內核指定, 傳NULL
size_t length, // 映射區的大小, 4k的整數倍, 不能為0, 一般文件有多大length就為多大
int prot, // 映射區的權限, PROT_READ(映射區 必須 要有讀權限), PROT_WRITE
int flags, // 標志位參數, MAP_SHARED(數據同步到磁盤), MAP_PRIVATE(數據不會同步到磁盤), 有血緣關系通信需是MAP_SHARED
int fd, // 文件描述符, 要映射文件對應的fd(open得來的)
off_t offset // 映射文件的偏移量, 映射時文件指針的偏移量, 必須是4k的整數倍, 一般為0
);
返回值: 成功, 映射區首地址; 失敗, MAP_FAILED
思考問題
如果對mmap的返回值(ptr)做++操作(ptr++), munmap是否能夠成功?
不能對ptr做操作, 可以復制一份, 對復制的指針進行操作
如果open是O_RDONLY, mmap時prot參數指定PROT_READ|PROT_WRITE會怎樣
mmap調用失敗, open文件指定權限應該大於等於mmap第三個參數prot指定的權限
如果文件偏移量為1000會怎樣
必須是4096的整數倍
如果不檢測mmap的返回值會怎么樣
沒什么影響
mmap什么情況下會調用失敗
第二個參數length=0; 第三個參數沒有指定PROT_READ; 第五個參數fd對應的open權限必須大於port權限; offset必須是4096的整數倍
可以open的時候O_CREAT一個新文件來創建映射區嗎
可以, 需要做文件拓展(lseek, truncate)
mmap后關閉文件描述符, 對mmap映射有無影響
沒有影響
對ptr越界操作會怎樣
段錯誤
munmap
int munmap(void *addr, size_t length);
參數:
addr: mmap的返回值
length: mmap的第二個參數
進程間通信, 不阻塞, 數據直接才內存中處理,
有血緣關系,
父子進程共享內存映射區, 可以創建匿名映射區, 可以不需要磁盤文件進行通信
沒有血緣關系
不能使用匿名映射的方式, 只能借助磁盤文件創建映射區
A(a.c) B(b.c)
a.c: int fd1 = open("XXX"); void *ptr = mmap(,,,fd1, 0);
對映射區(ptr)進行讀寫操作
b.c: int fd2 = open("hello"); void ptr* = mmap(,,,fd2, 0);
對映射區(ptr)進行寫操作
示例程序
利用內存映射區讀文件
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
int main(int argc, const char* argv[]) {
// 打開一個文件
int fd = open("english.txt", O_RDWR);
int len = lseek(fd, 0, SEEK_END);
// 創建內存映射區
void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) {
perror("error");
exit(1);
}
printf("%s", (char*)ptr);
// 釋放內存映射區
munmap(ptr, len);
close(fd);
return 0;
}
MAP_PRIVATE與MAP_SHARED測試
父子進程共享:
- 打開的文件
- mmap建立的映射區(但必須要使用MAP_SHARED)
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/wait.h>
int main(int argc, const char* argv[]) {
// 打開一個文件
int fd = open("english.txt", O_RDWR);
int len = lseek(fd, 0, SEEK_END);
// 通信測試
//void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); // MAP_PRIVATE, 父子進程不可通信
void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); // MAP_SHARED, 父子進程可通信
if (ptr == MAP_FAILED) {
perror("error");
exit(1);
}
close(fd);
//printf("%s", (char*)ptr);
pid_t pid = fork();
if (pid == -1) {
perror("fork error");
exit(1);
}
if (pid > 0) {
// 父進程寫數據
strcpy((char*)ptr, "haha, hehe");
// 回收
wait(NULL);
}
else if (pid == 0) {
// 讀數據
printf("%s\n", (char*)ptr);
}
// 釋放內存映射區
//ptr++;
int ret = munmap(ptr, len);
if (ret == -1) {
perror("mmap error");
exit(1);
}
return 0;
}
有血緣關系匿名映射區通信
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/wait.h>
int main(int argc, const char* argv[]) {
// 創建匿名內存映射區
int len = 4096;
void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0); // MAP_SHARED可通信
//void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); // MAP_PRIVATE, 不可通信
if (ptr == MAP_FAILED) {
perror("error");
exit(1);
}
//printf("%s", (char*)ptr);
pid_t pid = fork();
if (pid == -1) {
perror("fork error");
exit(1);
}
if (pid > 0) {
// 父進程寫數據
strcpy((char*)ptr, "haha");
// 回收
wait(NULL);
}
else if (pid == 0) {
// 讀數據
printf("%s\n", (char*)ptr);
}
// 釋放內存映射區
//ptr++;
int ret = munmap(ptr, len);
if (ret == -1) {
perror("mmap error");
exit(1);
}
return 0;
}
無血緣關系利用內存映射區通信
讀端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>
int main(int argc, const char *argv[]) {
int fd = open("temp", O_RDWR | O_CREAT, 0664);
ftruncate(fd, 4096);
int len = lseek(fd, 0, SEEK_END);
void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) {
perror("mmap");
exit(1);
}
while (1) {
sleep(1);
printf("%s\n", (char*)ptr+1024);
}
// 釋放
int ret = munmap(ptr, len);
if (ret == -1) {
perror("munmap");
exit(1);
}
return 0;
}
寫端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>
int main(int argc, const char *argv[]) {
int fd = open("temp", O_RDWR | O_CREAT, 0664);
void *ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) {
perror("mmap");
exit(1);
}
while (1) {
char *p = (char *)ptr;
p += 1024;
strcpy(p, "haha, I'm fine ~\n");
sleep(2);
}
int ret = munmap(ptr, 4096);
if (ret == -1) {
perror("munmap");
exit(1);
}
return 0;
}