[英]mmap, msync and linux process termination
我想通过使用 mmap() 和 MAP_SHARED 标志集将固定大小的结构与众所周知的文件名相关联,使用 mmap 在 Linux 下运行的 C 程序中实现程序状态的某些部分的持久性。 出于性能原因,我宁愿根本不调用 msync(),并且没有其他程序会访问此文件。 当我的程序终止并重新启动时,它会再次映射同一个文件并对其进行一些处理以恢复它在终止前所处的状态。 我的问题是:如果我从不调用文件描述符上的 msync(),内核是否会保证对内存的所有更新都将写入磁盘并随后可以恢复,即使我的进程因 SIGKILL 终止? 此外,即使我的程序从不调用 msync(),内核是否会定期将页面写入磁盘,也会产生一般系统开销?
编辑:我已经解决了是否写入数据的问题,但我仍然不确定这是否会导致一些意外的系统加载尝试使用 open()/write()/fsync() 和如果进程被 KILL/SEGV/ABRT/等击中,则可能会丢失一些数据。 添加了一个 'linux-kernel' 标签,希望有知识的人可以加入。
我发现 Linus Torvalds 的评论回答了这个问题http://www.realworldtech.com/forum/?threadid=113923&curpostid=114068
映射的页面是文件系统缓存的一部分,这意味着即使对该页面进行更改的用户进程死亡,该页面仍由内核管理,因为对该文件的所有并发访问都将通过内核,其他进程将从该缓存中获得服务。 在一些旧的 Linux 内核中它是不同的,这就是为什么一些内核文档仍然告诉强制msync
。
编辑:谢谢 RobH 更正了链接。
编辑:
从Linux 4.15开始引入了一个新的标志MAP_SYNC,可以保证一致性。
具有此标志的共享文件映射提供了保证,虽然某些内存可写地映射到进程的地址空间中,但即使在系统崩溃或重新启动后,它仍将在同一文件中的相同偏移量中可见。
参考:
http://man7.org/linux/man-pages/man2/mmap.2.html在页面中搜索MAP_SYNC
我决定不那么懒惰,通过编写一些代码来回答数据是否明确写入磁盘的问题。 答案是会写。
这是一个在将一些数据写入 mmap 文件后突然杀死自己的程序:
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
typedef struct {
char data[100];
uint16_t count;
} state_data;
const char *test_data = "test";
int main(int argc, const char *argv[]) {
int fd = open("test.mm", O_RDWR|O_CREAT|O_TRUNC, (mode_t)0700);
if (fd < 0) {
perror("Unable to open file 'test.mm'");
exit(1);
}
size_t data_length = sizeof(state_data);
if (ftruncate(fd, data_length) < 0) {
perror("Unable to truncate file 'test.mm'");
exit(1);
}
state_data *data = (state_data *)mmap(NULL, data_length, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_POPULATE, fd, 0);
if (MAP_FAILED == data) {
perror("Unable to mmap file 'test.mm'");
close(fd);
exit(1);
}
memset(data, 0, data_length);
for (data->count = 0; data->count < 5; ++data->count) {
data->data[data->count] = test_data[data->count];
}
kill(getpid(), 9);
}
这是一个在前一个程序死后验证结果文件的程序:
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
typedef struct {
char data[100];
uint16_t count;
} state_data;
const char *test_data = "test";
int main(int argc, const char *argv[]) {
int fd = open("test.mm", O_RDONLY);
if (fd < 0) {
perror("Unable to open file 'test.mm'");
exit(1);
}
size_t data_length = sizeof(state_data);
state_data *data = (state_data *)mmap(NULL, data_length, PROT_READ, MAP_SHARED|MAP_POPULATE, fd, 0);
if (MAP_FAILED == data) {
perror("Unable to mmap file 'test.mm'");
close(fd);
exit(1);
}
assert(5 == data->count);
unsigned index;
for (index = 0; index < 4; ++index) {
assert(test_data[index] == data->data[index]);
}
printf("Validated\n");
}
我发现了一些增加我的困惑的东西:
munmap 不会影响被映射的对象,也就是说,调用 munmap 不会导致映射区域的内容写入磁盘文件。 当我们存储到内存映射区域时,内核的虚拟内存算法会自动更新 MAP_SHARED 区域的磁盘文件。
这摘自UNIX® 环境中的高级编程。
从 linux 联机帮助页:
MAP_SHARED 与映射此对象的所有其他进程共享此映射。 存储到区域等同于写入文件。 在调用 msync(2) 或 munmap(2) 之前,该文件实际上可能不会被更新。
两者似乎是矛盾的。 APUE错了吗?
我没有找到您问题的非常准确的答案,因此决定再添加一个:
要么 Linux 联机帮助页信息不正确,要么 Linux 非常不符合标准。 msync
不应该与更改是否提交到文件的逻辑状态,或者其他使用mmap
或read
访问文件的进程是否看到更改有关; 它纯粹是fsync
的类似物,除了在电源故障或其他硬件级故障的情况下确保数据完整性的目的外,应将其视为无操作。
根据手册页,
在调用 msync(2) 或 munmap() 之前,该文件实际上可能不会更新。
因此,您至少需要确保在退出之前调用munmap()
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.