莫道男儿心如铁
君不见满川红叶
尽是离人眼中血
前面写了一篇文章关于luajit2.05中的内存限制,有讲到mmap分配内存中的一些问题,今天就自己动手过一下mmap文件映射内存以及mmap如何共享内存。
文件映射内存
mmap简单例子
往/dev/zero
里写是不用考虑文件的大小的,所以很方便,如果是父子进程之间的共享内存的话,使用/dev/zero
映射是非常好的选择。
#include <cstdio>
#include <sys/mman.h>
#include <cstring>
#include <iostream>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
int fd = open("/dev/zero", O_RDWR, 0);
void *addr = mmap(nullptr, 4, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, fd, 0);
printf("%d\n", *(int *)addr);
munmap(addr, 4);
close(fd);
return 0;
}
但如果是普通文件,用mmap
进行映射的话,就是要考虑文件的大小的,实际操作的内存长度不能大于文件的长度,可以使用stat
函数获取文件的信息,包括文件大小等。
首先把文件准备如下:
这里是6个字符的长度,我们用mmap把文件修改成同等长度的“2333”,通过映射到内存的方式进行修改。
然后可以看到,文件大小为6:
所以首先获取到文件的长度,然后映射到内存中,然后在该范围内操作内存。
下面代码给出了例子:
#include <cstdio>
#include <sys/mman.h>
#include <cstring>
#include <iostream>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
int get_file_size(const char *file) {
struct stat statbuf;
if (stat(file, &statbuf) == 0)
return statbuf.st_size;
return -1;
}
int main() {
std::string str = "2333333333333333333333333333";
const char filename[] = "2333.txt";
int fd = open(filename, O_RDWR, 0);
int len = get_file_size(filename);
void *addr = mmap(nullptr, len, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, fd, 0);
strncpy((char *)addr, str.c_str(), len);
munmap(addr, 4);
close(fd);
return 0;
}
执行后,看到文件发生了变化:
内容由原来的“000000”变成了“233333”,且等长。
其实,mmap分配的内存是存在页对齐,比如我的机器上是4K对齐,那无论申请的内存大小是4k还是1字节,实际上分配出去的都是4K。
修改文件大小
很多时候,我们希望能够直接修改文件的大小,以便于能够当内存访问文件时,不会“越界”。。
lseek能够帮忙做到这件事,其实lseek只是移动文件的指针,我们这里可以当做修改文件大小来用。
还是之前的例子,文件大小初始只有6字节,然后用lseek扩展到要操作的内存等长。当然,lseek是不会真的修改文件大小的,只是移动了文件指针,到了真正写入的时候才会改变大小,所以配合write来用。。
示例代码如下:
#include <cstdio>
#include <sys/mman.h>
#include <cstring>
#include <iostream>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
int get_file_size(const char *file) {
struct stat statbuf;
if (stat(file, &statbuf) == 0)
return statbuf.st_size;
return -1;
}
int main() {
std::string str = "2333333333333333333333333333";
const char filename[] = "2333.txt";
int fd = open(filename, O_RDWR, 0);
int len = get_file_size(filename);
void *addr = mmap(nullptr, str.length(), PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, fd, 0);
lseek(fd, str.length(), SEEK_SET);
write(fd, "", 1);
strcpy((char *)addr, str.c_str());
munmap(addr, str.length());
close(fd);
return 0;
}
然后文件就被修改了:
共享内存
我感觉共享内存和之前的文件映射是有一定的联系的。
共享内存的原理大概可以如下图:
简单来说就是不同的虚拟空间,映射到了同一个物理空间,中间经过各自的用户级页表。
父子进程
比较简单的就是在父子进程间使用共享内存,这样的话其实两个进程其实操作的虚拟地址值都是一样的。
下面代码简单演示了:
#include <cstdio>
#include <sys/mman.h>
#include <cstring>
#include <iostream>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
int fd = open("/dev/zero", O_RDWR, 0);
void *addr = mmap(nullptr, 4, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, fd, 0);
if (fork() == 0) {
*(int *)addr = 2333;
printf("val in process %d is %d.\n", getpid(), *(int *)addr);
munmap(addr, 4);
close(fd);
exit(0);
}
wait(nullptr);
printf("val in process %d is %d.\n", getpid(), *(int *)addr);
munmap(addr, 4);
close(fd);
return 0;
}
然后输出:
很明显共享内存通信成功了。
非亲缘关系进程
简单来说就是不同的进程打开同一个文件,然后分别用mmap进行映射,这样操作的就是同一块内存了。
进程1,先执行,等addr存储的值被改成2333:
#include <cstdio>
#include <sys/mman.h>
#include <cstring>
#include <iostream>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
int main() {
int fd = open("2333.txt", O_RDWR, 0);
void *addr = mmap(nullptr, 4, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, fd, 0);
while ((*(int *)addr) != 2333) {
printf("Wait for other process(1 second)!\n");
fflush(stdout);
sleep(1);
}
printf("from other process: %d\n", *(int *)addr);
munmap(addr, 4);
close(fd);
return 0;
}
进程2,将addr存储的值改成2333:
#include <cstdio>
#include <sys/mman.h>
#include <cstring>
#include <iostream>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
int main() {
int fd = open("2333.txt", O_RDWR, 0);
void *addr = mmap(nullptr, 4, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, fd, 0);
(*(int *)addr) = 2333;
munmap(addr, 4);
close(fd);
return 0;
}
输出如下:
可以看到进程1存储在addr被进程2修改了。。。