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

时间:2015-07-04 04:43:15

标签: c pointers struct ipc sysv-ipc

免责声明:我是一名致力于使用共享内存段进行IPC的项目的C noob。我的计划是定义一个结构,它有一个指针(void *)到映射的剩余内存(通过smget()),它存在于我将用作传递信息的头的结构之外关于进程之间请求的状态(它还将具有互斥/ 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 '\0' 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], '\0', 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], '\0', 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;
}

1 个答案:

答案 0 :(得分:2)

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

要记住的规则是:

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

此外,我怀疑是否可以保证两个流程中的段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;
}

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

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

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

对每个进程访问内存的顺序没有操作系统级别的控制;一个进程在某个东西中被阻塞是完全正常的,然后另一个进程会看到一个“半生不熟”的状态。

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