如何将未命名的 Posix 信号量写入共享内存?

How to write unnamed Posix Semaphore to Shared Memory?

我想将信号量写入共享内存。我的第一个想法是将 mmap 返回的指针传递给 sem_init():

#include <stdio.h>
#include <semaphore.h>
#include <string.h>
#include <errno.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(void)
{
    sem_t *sem_ptr;
    int shm_fd = shm_open("Shm", O_CREAT | O_RDWR, DEFFILEMODE);
    fprintf(stderr, "%s\n", strerror(errno));
    sem_ptr = mmap(NULL, sizeof(sem_t), PROT_WRITE, MAP_SHARED, shm_fd, 0);
    fprintf(stderr, "%p\n", strerror(errno));
    sem_init(sem_ptr, 1, 1);
    fprintf(stderr, "%s\n", strerror(errno));

    sem_destroy(sem_ptr);
    return 0;
}

但它会导致这个错误(当调用 sem_init() 时):Process finished with exit code 135 (interrupted by signal 7: SIGEMT)

然后我尝试用sem_t变量初始化信号量并将其写入共享内存:

int main(void)
{
    sem_t *sem_ptr;
    sem_t s;
    int shm_fd = shm_open("Shm", O_CREAT | O_RDWR, DEFFILEMODE);
    fprintf(stderr, "%s\n", strerror(errno));
    sem_ptr = mmap(NULL, sizeof(sem_t), PROT_WRITE, MAP_SHARED, shm_fd, 0);
    fprintf(stderr, "%p\n", strerror(errno));
    sem_init(&s, 1, 1);
    fprintf(stderr, "%s\n", strerror(errno));

    *sem_ptr = s;

    sem_destroy(&s);
    return 0;
}

现在行 *sem_ptr = s; 导致与第一个程序相同的错误

有人能帮帮我吗?

您创建信号量的第一个策略是正确的。您不一定将 sem_t 对象复制到不同的内存地址并让它仍然有效。

我不确定你为什么会得到 SIGEMT,我认为现代 Unix 从未生成过它。但是,当我 运行 在我的计算机上使用您的任何一个程序时,它们会崩溃并显示 SIGBUS,这让我发现了一个我知道如何修复的错误。当你mmap一个文件(共享内存对象被认为是一个文件),而你在mmap调用中要求的大小比文件大,然后你访问超出的内存区域文件的末尾(到目前为止 CPU 可以捕获它),你会得到一个 SIGBUS。让我引用 shm_open 联机帮助页的关键部分:

O_CREAT: Create the shared memory object if it does not exist. [...]

A new shared memory object initially has zero length—the size of the object can be set using ftruncate(2).

您需要做的是在 shm_fd 上调用 ftruncate 以使共享内存对象足够大以容纳信号量。

您应该同时修复一些不太重要的错误:

所有使用内存映射的系统调用可能如果您给它们提供的偏移量或大小不是系统页面大小的倍数,则它们可能会发生故障。 (他们应该为你四舍五入,但从历史上看,这方面有很多错误。)你通过调用 sysconf(_SC_PAGESIZE) 获得系统页面大小,然后使用如下所示的小辅助函数进行四舍五入.

大多数 C 库函数都允许将 errno 设置为非零值,即使它们成功了。您应该在打印 strerror(errno) 之前检查每个函数是否真的失败了。 (在下面的代码中,为了简洁起见,我使用了 perror。)

共享内存对象的名称必须以斜杠开头,后面最多可以跟 NAME_MAX 个非斜杠的字符。

sem_init 可以读取和写入 sem_ptr 指向的内存,随后使用 sem_waitsem_post 肯定会,所以你应该在 mmap 调用中使用 PROT_READ|PROT_WRITE

综上所述,这是您在我的计算机上运行的第一个程序的修订版。由于 SIGEMT 的原因,我无法保证它对你有用。

#include <fcntl.h>
#include <semaphore.h>
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>

#ifndef DEFFILEMODE
# define DEFFILEMODE 0666
#endif

static long round_up(long n, long mult)
{
    return ((n + mult - 1) / mult) * mult;
}

int main(void)
{
    long pagesize;
    long semsize;
    sem_t *sem_ptr;
    int shm_fd;

    pagesize = sysconf(_SC_PAGESIZE);
    if (pagesize == -1) {
        perror("sysconf(_SC_PAGESIZE)");
        return 1;
    }

    shm_fd = shm_open("/Shm", O_CREAT|O_RDWR, DEFFILEMODE);
    if (shm_fd == -1) {
        perror("shm_open");
        return 1;
    }

    semsize = round_up(sizeof(sem_t), pagesize);
    if (ftruncate(shm_fd, semsize) == -1) {
        perror("ftruncate");
        return 1;
    }

    sem_ptr = mmap(0, semsize, PROT_READ|PROT_WRITE, MAP_SHARED, shm_fd, 0);
    if (sem_ptr == MAP_FAILED) {
        perror("mmap");
        return 1;
    }
    if (sem_init(sem_ptr, 1, 1)) {
        perror("sem_init");
        return 1;
    }

    sem_destroy(sem_ptr);
    shm_unlink("/Shm");
    return 0;
}

您应该注意的另一个复杂情况是,在已经初始化的信号量上调用 sem_init 会导致未定义的行为。这意味着您必须围绕创建共享内存段和其中的信号量使用某种其他类型的锁定。在我的脑海中,我不知道如何以防弹的方式做到这一点。