mmap迭代分块映射大文件时段错误问题求助
问题场景
order向量加载时占用8MB内存,尝试通过分块mmap(CHUNK_SIZE=10240)迭代映射小块数据以降低内存开销,但程序在执行VECTOR(*order)[act_rank++] = actvect;访问索引11264时触发段错误。
疑问
- 首次调用
memory_map_order_iterative()后,chunk大小为10240,为何向量能访问到索引11263? - 第二次迭代调用
memory_map_order_iterative()后,order向量本该访问10240至20479的索引,为何触发段错误?
期望逻辑
chunk1:0至10239,chunk2:10240至20479,以此类推。
迭代映射代码
#include <igraph.h> #include <sys/mman.h> #include <unistd.h> #include <string.h> #include <fcntl.h> #include <sys/stat.h> #define CHUNK_SIZE 10240 graph_error_t unmap_order(igraph_integer_t **mapped_order, igraph_integer_t chunk_size){ if (munmap(*mapped_order, chunk_size * sizeof(igraph_integer_t)) == -1) { perror("munmap"); return IGRAPH_EFILE; } *mapped_order = NULL; return IGRAPH_SUCCESS; } graph_error_t memory_map_order_iterative(igraph_vector_int_t *order, const char *order_filename, igraph_integer_t **mapped_order, igraph_integer_t no_of_elements, igraph_integer_t *remaining_elements, igraph_integer_t *offset) { int fd; fd = open(order_filename, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); if (fd == -1) { perror("open"); return IGRAPH_EFILE; } if (ftruncate(fd, no_of_elements * sizeof(igraph_integer_t)) == -1) { perror("ftruncate"); close(fd); return IGRAPH_EFILE; } igraph_integer_t chunk_size = (*remaining_elements > CHUNK_SIZE) ? CHUNK_SIZE : *remaining_elements; *mapped_order = mmap(NULL, chunk_size * sizeof(igraph_integer_t), PROT_READ | PROT_WRITE, MAP_SHARED, fd, (*offset) * sizeof(igraph_integer_t)); if (*mapped_order == MAP_FAILED) { perror("mmap"); close(fd); return IGRAPH_EFILE; } *offset += chunk_size; *remaining_elements -= chunk_size; close(fd); igraph_vector_int_view(order, *mapped_order, chunk_size); return IGRAPH_SUCCESS; } graph_error_t igraph_bfs(const igraph_t *graph, igraph_vector_int_t *order, void *extra) { const igraph_integer_t no_of_nodes = igraph_vcount(graph); igraph_dqueue_int_t Q; igraph_integer_t actroot = 0; igraph_integer_t act_rank = 0; IGRAPH_DQUEUE_INT_INIT_FINALLY(&Q, 100); igraph_integer_t *mapped_order = NULL; igraph_integer_t remaining_elements = 999998; igraph_integer_t offset = 0; igraph_error_t is_map_success; is_map_success = memory_map_order_iterative(order, "/tmp/orders_map.bin", &mapped_order, no_of_nodes, &remaining_elements, &offset); if (is_map_success != IGRAPH_SUCCESS) { return is_map_success; } while (1) { IGRAPH_CHECK(igraph_dqueue_int_push(&Q, actroot)); IGRAPH_CHECK(igraph_dqueue_int_push(&Q, 0)); while (!igraph_dqueue_int_empty(&Q)) { igraph_integer_t actvect = igraph_dqueue_int_pop(&Q); if (order) { if(act_rank>0 && act_rank%CHUNK_SIZE==0){ igraph_error_t unmap_error = unmap_order(&mapped_order, CHUNK_SIZE); if (unmap_error != IGRAPH_SUCCESS) { return unmap_error; } igraph_error_t is_map_success; is_map_success = memory_map_order_iterative(order, "/tmp/orders_map.bin", &mapped_order, no_of_nodes, &remaining_elements, &offset); if (is_map_success != IGRAPH_SUCCESS) { return is_map_success; } } VECTOR(*order)[act_rank++] = actvect; } } } return IGRAPH_SUCCESS; } int main(void) { igraph_t graph; FILE *file = fopen("random_graph.edgelist", "r"); if (!file) { return 1; } if (igraph_read_graph_edgelist(&graph, file, 0, IGRAPH_UNDIRECTED) == IGRAPH_SUCCESS) { } else { return 1; } igraph_vector_int_t order; igraph_vector_int_init(&order, 0); igraph_bfs(&graph, &order,/*extra=*/ NULL); igraph_destroy(&graph); return 0; }
全量映射代码
void unmap(igraph_integer_t *mapped_order, size_t size) { if (munmap(mapped_order, size) == -1) { perror("munmap"); } } graph_error_t memory_map_order(const char *order_filename, igraph_integer_t **mapped_order, igraph_integer_t no_of_elements, igraph_vector_int_t *order) { int fd; fd = open(order_filename, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); if (fd == -1) { perror("open"); return IGRAPH_EFILE; } if (ftruncate(fd, no_of_elements * sizeof(igraph_integer_t)) == -1) { perror("ftruncate"); close(fd); return IGRAPH_EFILE; } *mapped_order = mmap(NULL, no_of_elements * sizeof(igraph_integer_t), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (*mapped_order == MAP_FAILED) { perror("mmap"); close(fd); return IGRAPH_EFILE; } close(fd); igraph_vector_int_view(order, *mapped_order, no_of_elements); return IGRAPH_SUCCESS; }
问题原因分析
首次调用后能访问超出chunk的索引:
igraph的igraph_vector_int_view仅创建内存视图,不做任何边界校验。当你用VECTOR(*order)[act_rank]访问时,即使act_rank超过chunk_size(10240),igraph不会阻止你访问映射区域外的内存。此时只是刚好内存地址暂时有效(未触发段错误),但属于未定义行为,随时可能崩溃。第二次调用后触发段错误:
第二次映射后,igraph_vector_int_view将order视图绑定到了新的chunk(对应文件偏移10240的位置),此时视图的内存起始地址是新映射区域,索引0对应文件的10240位置。但你仍用全局的act_rank(此时为10240)作为索引访问,相当于访问新映射区域的10240位置,而新chunk只有10240个元素(索引范围0-10239),直接越界到映射区域外,触发段错误。
解决方案
1. 修正索引访问逻辑
每次切换chunk后,使用相对当前chunk的索引访问视图,而非全局act_rank:
if (order) { // 计算当前chunk的全局起始索引 igraph_integer_t chunk_start = offset - ((*remaining_elements > CHUNK_SIZE) ? CHUNK_SIZE : *remaining_elements); // 计算当前元素在chunk内的相对索引 igraph_integer_t local_rank = act_rank - chunk_start; if(act_rank > 0 && act_rank % CHUNK_SIZE == 0){ // 卸载旧chunk igraph_error_t unmap_error = unmap_order(&mapped_order, CHUNK_SIZE); if (unmap_error != IGRAPH_SUCCESS) { return unmap_error; } // 映射新chunk is_map_success = memory_map_order_iterative(order, "/tmp/orders_map.bin", &mapped_order, no_of_nodes, &remaining_elements, &offset); if (is_map_success != IGRAPH_SUCCESS) { return is_map_success; } // 更新新chunk的起始索引和相对索引 chunk_start = offset - CHUNK_SIZE; local_rank = 0; } // 用相对索引访问视图 VECTOR(*order)[local_rank] = actvect; act_rank++; }
2. 优化ftruncate调用
每次调用memory_map_order_iterative都执行ftruncate冗余且可能带来问题,仅在第一次创建文件时执行:
修改函数参数,增加is_first标记:
graph_error_t memory_map_order_iterative(igraph_vector_int_t *order, const char *order_filename, igraph_integer_t **mapped_order, igraph_integer_t no_of_elements, igraph_integer_t *remaining_elements, igraph_integer_t *offset, int is_first) { int fd; fd = open(order_filename, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); if (fd == -1) { perror("open"); return IGRAPH_EFILE; } // 仅首次调用时截断文件到目标大小 if (is_first) { if (ftruncate(fd, no_of_elements * sizeof(igraph_integer_t)) == -1) { perror("ftruncate"); close(fd); return IGRAPH_EFILE; } } igraph_integer_t chunk_size = (*remaining_elements > CHUNK_SIZE) ? CHUNK_SIZE : *remaining_elements; *mapped_order = mmap(NULL, chunk_size * sizeof(igraph_integer_t), PROT_READ | PROT_WRITE, MAP_SHARED, fd, (*offset) * sizeof(igraph_integer_t)); if (*mapped_order == MAP_FAILED) { perror("mmap"); close(fd); return IGRAPH_EFILE; } *offset += chunk_size; *remaining_elements -= chunk_size; close(fd); igraph_vector_int_view(order, *mapped_order, chunk_size); return IGRAPH_SUCCESS; }
调用时传入标记:
// 首次调用 is_map_success = memory_map_order_iterative(order, "/tmp/orders_map.bin", &mapped_order, no_of_nodes, &remaining_elements, &offset, 1); // 后续切换chunk时调用 is_map_success = memory_map_order_iterative(order, "/tmp/orders_map.bin", &mapped_order, no_of_nodes, &remaining_elements, &offset, 0);
3. 修正remaining_elements初始化
硬编码的999998与实际节点数不匹配,改为用no_of_nodes:
igraph_integer_t remaining_elements = no_of_nodes;
内容的提问来源于stack exchange,提问作者SSM Tariq

