如何在POSIX共享内存中创建哈希表结构体二维数组,实现4个fork子进程共享访问?
问题分析与解决方案
我来帮你梳理一下问题所在,以及正确的实现方式——你的核心问题出在共享内存的指针跨进程无效、私有内存分配无法共享,还有僵尸进程未回收这几点上,下面一步步拆解解决:
核心问题拆解
- 指针跨进程失效:你用
malloc分配的内存是进程私有虚拟地址空间的,子进程fork后会复制这份内存,但mmap的共享内存里如果直接存储指针,不同进程的虚拟地址空间可能不一样,导致其他进程访问时指针指向无效地址,直接崩溃。 - 私有内存无法共享:
set函数里用malloc创建的node、key、data都是当前进程私有内存,其他进程根本看不到这些内存区域,自然无法访问数据。 - 僵尸进程产生:父进程没有处理子进程的退出状态,子进程退出后会变成僵尸进程残留。
正确实现步骤
1. 修改哈希表结构体:用偏移量代替指针
把结构体里的指针换成相对于共享内存起始地址的偏移量(off_t类型),这样所有进程都能通过共享内存基地址计算出正确的实际地址:
#include <sys/mman.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <sys/wait.h> #include <string.h> #include <stdlib.h> #include <iostream> #include <pthread.h> // 用偏移量替代指针,解决跨进程虚拟地址不一致问题 struct node { off_t key_offset; // 相对于共享内存起始地址的偏移 off_t data_offset; off_t next_offset; }; #define TABLESIZE 100 #define SHARED_MEM_SIZE (1024 * 1024) // 预定义足够大的共享内存空间 unsigned int hash(const char *str, int tablesize) { return std::hash<std::string>{}(str) % tablesize; }
2. 创建/映射共享内存
使用shm_open创建POSIX共享内存对象,再通过mmap映射到进程地址空间:
// 创建或打开共享内存并映射到当前进程 void* create_shared_memory(const char* name, size_t size) { int fd = shm_open(name, O_CREAT | O_RDWR, 0666); if (fd == -1) { perror("shm_open failed"); exit(EXIT_FAILURE); } // 设置共享内存大小 if (ftruncate(fd, size) == -1) { perror("ftruncate failed"); close(fd); exit(EXIT_FAILURE); } // 映射共享内存到进程地址空间 void* ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (ptr == MAP_FAILED) { perror("mmap failed"); close(fd); exit(EXIT_FAILURE); } close(fd); // 映射完成后可关闭文件描述符 return ptr; }
3. 实现共享内存内的内存分配器
不能用进程私有malloc,需要在共享内存内自己管理空闲空间,这里实现一个简单的线性分配器:
// 共享内存内存管理器 struct mem_manager { off_t next_free; // 下一个空闲位置的偏移 pthread_mutex_t mutex; // 进程间互斥锁,避免竞态 }; // 转换偏移量为实际指针 void* offset_to_ptr(void* shared_base, off_t offset) { return (char*)shared_base + offset; } // 在共享内存中分配size字节空间,返回偏移量 off_t shared_malloc(void* shared_base, size_t size, struct mem_manager* mm) { pthread_mutex_lock(&mm->mutex); if (mm->next_free + size > SHARED_MEM_SIZE) { pthread_mutex_unlock(&mm->mutex); return -1; // 共享内存不足 } off_t offset = mm->next_free; mm->next_free += size; // 内存对齐(可选,提升访问效率) mm->next_free = (mm->next_free + sizeof(void*) - 1) & ~(sizeof(void*) - 1); pthread_mutex_unlock(&mm->mutex); return offset; }
4. 修改lookup和set函数适配共享内存
所有操作都基于共享内存基地址和偏移量进行:
int lookup(void* shared_base, struct node* table, const char* key) { unsigned index = hash(key, TABLESIZE); off_t next_offset = table[index].next_offset; while (next_offset != 0) { // 0表示空节点 struct node* node_ptr = (struct node*)offset_to_ptr(shared_base, next_offset); char* node_key = (char*)offset_to_ptr(shared_base, node_ptr->key_offset); if (strcmp(node_key, key) == 0) { return 1; } next_offset = node_ptr->next_offset; } return 0; } int set(void* shared_base, struct node* table, struct mem_manager* mm, const char* key, const char* data) { if (lookup(shared_base, table, key)) { return 0; // 键已存在,不插入 } unsigned index = hash(key, TABLESIZE); size_t key_len = strlen(key) + 1; size_t data_len = strlen(data) + 1; // 分配节点、key、data的共享内存空间 off_t node_offset = shared_malloc(shared_base, sizeof(struct node), mm); off_t key_offset = shared_malloc(shared_base, key_len, mm); off_t data_offset = shared_malloc(shared_base, data_len, mm); if (node_offset == -1 || key_offset == -1 || data_offset == -1) { return 0; } // 写入key和data到共享内存 char* dest_key = (char*)offset_to_ptr(shared_base, key_offset); strcpy(dest_key, key); char* dest_data = (char*)offset_to_ptr(shared_base, data_offset); strcpy(dest_data, data); // 初始化新节点并插入哈希表(头插法) struct node* new_node = (struct node*)offset_to_ptr(shared_base, node_offset); new_node->key_offset = key_offset; new_node->data_offset = data_offset; pthread_mutex_lock(&mm->mutex); new_node->next_offset = table[index].next_offset; table[index].next_offset = node_offset; pthread_mutex_unlock(&mm->mutex); return 1; }
5. 主进程创建子进程并管理共享内存
初始化共享内存结构,回收子进程避免僵尸,最后清理共享内存:
int main() { const char* shm_name = "/my_shared_hash_table"; // 创建共享内存 void* shared_base = create_shared_memory(shm_name, SHARED_MEM_SIZE); // 初始化共享内存内的管理器和哈希表 struct mem_manager* mm = (struct mem_manager*)shared_base; mm->next_free = sizeof(struct mem_manager) + TABLESIZE * sizeof(struct node); // 初始化进程间互斥锁 pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); pthread_mutex_init(&mm->mutex, &attr); struct node* hash_table = (struct node*)((char*)shared_base + sizeof(struct mem_manager)); memset(hash_table, 0, TABLESIZE * sizeof(struct node)); // 初始化所有链表头为空 // 创建4个子进程 for (int i = 0; i < 4; ++i) { pid_t pid = fork(); if (pid == -1) { perror("fork failed"); exit(EXIT_FAILURE); } else if (pid == 0) { // 子进程测试逻辑 char key[32], data[32]; snprintf(key, sizeof(key), "key_%d_%d", getpid(), i); snprintf(data, sizeof(data), "data_%d_%d", getpid(), i); if (set(shared_base, hash_table, mm, key, data)) { printf("Child %d: Inserted %s -> %s\n", getpid(), key, data); } else { printf("Child %d: Failed to insert %s\n", getpid(), key); } if (lookup(shared_base, hash_table, key)) { printf("Child %d: Found %s\n", getpid(), key); } else { printf("Child %d: Could not find %s\n", getpid(), key); } munmap(shared_base, SHARED_MEM_SIZE); exit(EXIT_SUCCESS); } } // 父进程回收所有子进程,避免僵尸 int status; pid_t pid; while ((pid = wait(&status)) > 0) { printf("Parent: Child %d exited with status %d\n", pid, WEXITSTATUS(status)); } // 清理共享内存 pthread_mutex_destroy(&mm->mutex); munmap(shared_base, SHARED_MEM_SIZE); shm_unlink(shm_name); return 0; }
额外注意事项
- 编译命令:需要链接pthread库和rt库,比如:
g++ -o shared_hash shared_hash.cpp -lpthread -lrt - 竞态条件:加入了
pthread_mutex_t并设置为进程共享,避免多个进程同时修改哈希表导致数据丢失。 - 共享内存清理:如果程序异常退出,需要手动执行
shm_unlink /my_shared_hash_table清理残留的共享内存对象。
内容的提问来源于stack exchange,提问作者Big Muncha
相关产品推荐
相关产品推荐

