You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在POSIX共享内存中创建哈希表结构体二维数组,实现4个fork子进程共享访问?

问题分析与解决方案

我来帮你梳理一下问题所在,以及正确的实现方式——你的核心问题出在共享内存的指针跨进程无效、私有内存分配无法共享,还有僵尸进程未回收这几点上,下面一步步拆解解决:

核心问题拆解

  1. 指针跨进程失效:你用malloc分配的内存是进程私有虚拟地址空间的,子进程fork后会复制这份内存,但mmap的共享内存里如果直接存储指针,不同进程的虚拟地址空间可能不一样,导致其他进程访问时指针指向无效地址,直接崩溃。
  2. 私有内存无法共享:set函数里用malloc创建的node、key、data都是当前进程私有内存,其他进程根本看不到这些内存区域,自然无法访问数据。
  3. 僵尸进程产生:父进程没有处理子进程的退出状态,子进程退出后会变成僵尸进程残留。

正确实现步骤

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 06:07:39