1

免责声明:我是一个 C 菜鸟,正在从事一个使用共享内存段进行 IPC 的项目。我的计划是定义一个结构,它有一个指针 ( void *) 指向映射 (via smget()) 的剩余内存,它位于结构之外,我将使用它作为标头在进程之间传递有关请求状态的信息(它还将具有互斥体/条件结构)。

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

如果我不清楚某些事情,请告诉我,我对此还是很陌生。

#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;
}
4

1 回答 1

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)as传递segment_size给这个函数。在这种情况下,更改data为 a可能是一个更好的主意。void*


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

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

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

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

于 2015-07-04T04:58:53.200 回答