我这样做对吗?将结构中的指针映射到结构外部以供 IPC 共享内存使用

Am I doing this right? Mapping pointer in struct to position outside of struct for IPC shared memory usage

免责声明:我是一名 C 菜鸟,正在从事一个使用共享内存段进行 IPC 的项目。我的计划是定义一个结构,它有一个指针 (void *) 指向映射的剩余内存(通过 smget()),它位于我将用作 [=20= 的结构之外] 在进程之间传递有关请求状态的信息(它还将具有 mutex/cond 结构)。

我只是想看看我这样做是否正确...我的问题在我的主要功能的评论中。

如果我不清楚某些事情,请告诉我,我还是个新手。

#include <stdio.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <string.h>

#define clean_errno() (errno == 0 ? "None" : strerror(errno))
#define log_error(M, ...) fprintf(stderr, "[ERROR] (%s:%d: errno: %s) " M "\n", __FILE__, __LINE__, clean_errno(), ##__VA_ARGS__)
#define log_warn(M, ...) fprintf(stderr, "[WARN] (%s:%d: errno: %s) " M "\n", __FILE__, __LINE__, clean_errno(), ##__VA_ARGS__)
#define log_info(M, ...) fprintf(stderr, "[INFO] (%s:%d) " M "\n", __FILE__, __LINE__, ##__VA_ARGS__)

#define PATHNAME "/tmp"

typedef struct shm_data
{
    /* segment id assigned by shmget() */
    int segment_id;

    /* max size of char *data */
    unsigned int buffer_size;

    /* nbytes currently in char *data to be read */
    unsigned int nbytes_buffer;

    /* nbytes remaining to be sent to be read */
    unsigned int nbytes_remaining;

    /* nbytes total that need to be read */
    unsigned int nbytes_total;

    /* pointer to the memory poistion just outside of the struct */
    char *buffer;
} shm_data;

shm_data *create_shm(unsigned int segment_number, unsigned int segment_size)
{
    int segment_id;
    shm_data *shm;

    // just doing segment_size + 1 for this example so when I print data it has a '[=10=]' for output
    segment_id = shmget(ftok(PATHNAME, segment_number), segment_size + 1, IPC_CREAT | S_IRUSR | S_IWUSR);
    void *shm_addr = shmat(segment_id, (void *) 0, 0);
    shm = (shm_data *) shm_addr;

    shm->segment_id = segment_id;
    shm->buffer_size = segment_size - sizeof(shm_data);
    shm->nbytes_buffer = 0;
    shm->nbytes_remaining = 0;
    shm->nbytes_total = 0;

    // 1 - am I initializing my pointer correctly? I want it to point to the first byte that comes after my struct
    shm->buffer = shm_addr + sizeof(shm_data) + 1;
    memset(&shm->buffer[0], '[=10=]', shm->buffer_size);

    return shm;
}

int main(int argc, char *argv[])
{
    char *data = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
    unsigned int segment_size = 16;

    shm_data *shm = create_shm(1, sizeof(shm_data) + segment_size);
    shm->nbytes_total = strlen(data);
    shm->nbytes_remaining = shm->nbytes_total;

    int count = 0;
    while (shm->nbytes_remaining > 0)
    {
        // 2 - is this an appropriate way to "clear" the memory outside of the struct?
        memset(&shm->buffer[0], '[=10=]', shm->buffer_size + 1);

        int nbytes = shm->nbytes_remaining;
        if (nbytes > shm->buffer_size)
        {
            // max we can buffer is this buffer_size
            nbytes = shm->buffer_size;
        }

        // 3 - is this an appropriate way to copy a segment of data with an offset into the memory outside of the struct?
        int offset = count * shm->buffer_size;
        memcpy(shm->buffer, &data[0] + offset, nbytes);
        log_info("[%d] %s", nbytes, shm->buffer);

        shm->nbytes_remaining = shm->nbytes_remaining - nbytes;

        count++;
    }

    return 0;
}

是的,那是行不通的。不能保证一个进程将共享内存映射到的地址与另一个进程看到的地址相同。

要记住的规则是:

  • 不要使用指针。如果必须,请使用偏移量。 (尽管在这种情况下,每个进程都应该有自己的私有结构,并带有指向共享内存块各个部分的指针。)
  • 您不能使用共享内存块中的指针来访问另一个进程的非共享(正常)内存。

此外,我怀疑是否可以保证两个进程中的段 ID 相同。最后,在共享内存块本身中存储有关如何访问共享内存块的信息是没有意义的;这是循环推理。

控制块设置的示例是:

shm_data *create_shm(unsigned int segment_number, unsigned int segment_size)
{
    shm_data *shm;
    shm = (shm_data*)malloc(sizeof(shm_data));
    if(shm == NULL) return shm;

    // The following should have more error-trapping code.
    shm->segment_id = shmget(ftok(PATHNAME, segment_number), 
        segment_size, IPC_CREAT | S_IRUSR | S_IWUSR);
    shm->buffer_size = segment_size;
    shm->data = shmat(shm->segment_id, (void *) 0, 0);
    memset(shm->data, 0, shm->buffer_size);

    return shm;
}

此外,不要在此代码中隐藏额外的 "termination" 字节。如果调用者需要在其中放置一个字符串,则需要确保它要求 space 作为终止符字节。此代码不应假定共享内存块包含字符串。通常将 shm->data 转换为指向另一种 struct 的指针,后者仅描述共享内存的内部结构(因此会将字符串的存储声明为 char 数组)。然后你可以将 sizeof(struct) 作为 segment_size 传递给这个函数。在这种情况下,将 data 更改为 void* 可能是更好的主意。


好的,我看到你已经编辑了问题,将共享内存变成了一个字符缓冲区。我仍然认为最好将共享内存管理代码与缓冲区代码分开。

我还建议您根据读取偏移量和写入偏移量在共享内存中定义缓冲区变量。这些偏移量需要采用通过 原子操作访问的数据类型 (unsigned int?)。

对于每个进程访问内存的顺序没有 OS 级别的控制;一个进程在某事中间被阻塞是完全正常的,然后另一个进程会看到 "half-baked" 状态。

因此,要么使用原子类型,并设置您的代码,以便单个原子变量的更改完全将状态从一个状态更改为另一个状态,或者您将需要使用另一种 IPC 机制,如互斥锁或信号量来控制访问共享内存。