C语言中mmap写入FUSE inode结构体返回MAP_FAILED问题求助
我正在开发FUSE文件系统驱动以创建挂载点,为实现数据持久化,计划将inode保存至二进制文件。以下是我的inode结构体定义:
struct s_fuseInode { int id; /* Identificador del inodo */ char name[MAX_NAME]; /* Nombre del archivo */ char path[MAX_PATH]; /* Ruta del archivo */ int size; /* Tamaño del archivo */ int block_count; /* Número de bloques ocupados por el archivo */ int block[MAX_BLOCKS]; /* Bloques ocupados por el archivo */ struct s_fuseInode parent; /* Inodo padre */ int parent_id; /* Identificador del inodo padre */ struct s_fuseInode *children; /* Inodos hijos */ int children_count; /* Número de inodos hijos */ int is_dir; /* Indica si el inodo es un directorio */ char data; /* Datos del archivo */ time_t creation_time; /* Fecha de creación del archivo */ time_t modification_time; /* Fecha de modificación del archivo */ time_t access_time; /* Fecha de acceso al archivo */ int links; /* Número de enlaces al archivo */ mode_t mode; /* Permisos del archivo */ uid_t uid; /* Identificador de usuario */ gid_t gid; /* Identificador de grupo */ };
用于保存inode的函数如下:
static int save_inode(struct s_fuseInode *inode) { int fd = open("virtual_disk.bin", O_RDWR, S_IRUSR | S_IWUSR); // Abrimos el archivo de disco virtual if (fd == -1) // Si no se ha podido abrir { fprintf(stderr, "save_inode: Error al abrir el archivo de disco virtual\n"); return (1); } off_t offset = lseek(fd, 0, SEEK_END); // Nos situamos al final del archivo fprintf(stderr, "offset %ld\n", offset); if (offset == -1) // Si no se ha podido situar { fprintf(stderr, "save_inode: Error al situarse al final del archivo\n"); return (1); } ftruncate(fd, offset + sizeof(struct s_fuseInode)); // Truncamos el archivo fprintf(stderr, "size %ld\n", offset + sizeof(struct s_fuseInode)); struct s_fuseInode *map = mmap(0, sizeof(struct s_fuseInode), PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset); // Mapeamos el archivo if (map == MAP_FAILED) // Si no se ha podido mapear { close(fd); fprintf(stderr, "save_inode: Error al mapear el archivo\n"); return (1); } memcpy(map, inode, sizeof(struct s_fuseInode)); // Copiamos el inodo en el archivo if (munmap(map, sizeof(struct s_fuseInode)) == -1) // Si no se ha podido desmapear { close(fd); fprintf(stderr, "save_inode: Error al desmapear el archivo\n"); return (1); } close(fd); // Cerramos el archivo return (0); }
调用该函数时,mmap返回MAP_FAILED错误。已知该inode结构体大小为256字节,系统PAGE_SIZE为4096字节,我怀疑是页对齐问题,但作为首次使用mmap的开发者,不知如何解决。尝试添加char padding[3840]将结构体大小补至4096字节后可正常工作,但这样每个inode会占用4KB空间,希望能优化存储空间。
核心问题原因
mmap要求映射的文件偏移量必须是系统页大小的整数倍,你的inode大小是256字节,多次写入后offset会是256、512、768...这些值都不是4096的倍数,所以mmap调用失败。另外注意:你的结构体里包含struct s_fuseInode parent成员,这会导致结构体无限递归(自身嵌套),实际大小远不止256字节,必须改成指针或者仅用parent_id关联父节点。
优化解决方法
不需要给结构体加大量padding,换用以下两种更高效的方式:
方式1:直接用write代替mmap
既然只是写入一个256字节的结构体,完全没必要用mmap,直接调用write函数写入更简单高效,还能避免页对齐问题:
static int save_inode(struct s_fuseInode *inode) { int fd = open("virtual_disk.bin", O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR); if (fd == -1) { fprintf(stderr, "save_inode: Error al abrir el archivo de disco virtual\n"); return 1; } ssize_t written = write(fd, inode, sizeof(struct s_fuseInode)); if (written != sizeof(struct s_fuseInode)) { fprintf(stderr, "save_inode: Error al escribir el inodo\n"); close(fd); return 1; } close(fd); return 0; }
注:用O_APPEND模式打开文件,会自动把写入位置定位到文件末尾,省去手动lseek的步骤。
方式2:如果坚持用mmap,映射完整页并定位写入
如果必须使用mmap,需要映射完整的一页,然后计算inode在页内的偏移位置进行写入:
#include <unistd.h> // 用于获取PAGE_SIZE static int save_inode(struct s_fuseInode *inode) { int fd = open("virtual_disk.bin", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); if (fd == -1) { fprintf(stderr, "save_inode: Error al abrir el archivo de disco virtual\n"); return 1; } off_t file_size = lseek(fd, 0, SEEK_END); if (file_size == -1) { fprintf(stderr, "save_inode: Error al obtener tamaño del archivo\n"); close(fd); return 1; } // 计算下一个inode的起始偏移 off_t inode_offset = file_size; // 计算需要映射的页起始偏移(对齐到页大小) off_t map_offset = inode_offset & ~(PAGE_SIZE - 1); // 计算映射的长度:如果inode跨页,需要映射两页,否则一页 size_t map_len = ((inode_offset + sizeof(struct s_fuseInode) - map_offset) + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); // 确保文件大小足够容纳映射的区域 if (ftruncate(fd, map_offset + map_len) == -1) { fprintf(stderr, "save_inode: Error al truncar el archivo\n"); close(fd); return 1; } struct s_fuseInode *map = mmap(NULL, map_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, map_offset); if (map == MAP_FAILED) { fprintf(stderr, "save_inode: Error al mapear el archivo\n"); close(fd); return 1; } // 计算inode在映射区域内的相对偏移 size_t inode_rel_offset = inode_offset - map_offset; memcpy((char*)map + inode_rel_offset, inode, sizeof(struct s_fuseInode)); if (munmap(map, map_len) == -1) { fprintf(stderr, "save_inode: Error al desmapear el archivo\n"); close(fd); return 1; } close(fd); return 0; }
这种方式既满足mmap的页对齐要求,又不会浪费空间,每个inode只占用实际的256字节。
内容的提问来源于stack exchange,提问作者d3str3k

