一个共享内存段是否有可能多次附加到同一个父 pid?

Is it possibility that one shared memory segment will attach multiple time to same parent pid?

我已经创建了一个父进程的 200 个子进程,它们通过共享内存 IPC 机制进行通信:

父 <-> SHM <-> 子

但观察结果很奇怪。

我发现有 4 个进程附加到同一个 SHM id,其中 2 个是父 pid,2 个是子 pid。(意外行为)。 某处有 2 个进程附加到一个 SHM id(预期行为)。

请在下面找到输出-

-bash-4.2# grep 123076652 /proc/*/maps
/proc/27750/maps:7f1323576000-7f1323577000 rw-s 00000000 00:04 123076652  /SYSV2c006eff (deleted)    
/proc/27750/maps:7f1323676000-7f1323677000 rw-s 00000000 00:04 123076652  /SYSV2c006eff (deleted)
/proc/27827/maps:7f87ac3c0000-7f87ac3c1000 rw-s 00000000 00:04 123076652  /SYSV2c006eff (deleted)
/proc/28090/maps:7f9d33b8b000-7f9d33b8c000 rw-s 00000000 00:04 123076652  /SYSV2c006eff (deleted)

我们可以很容易地看到 PID-27750(Parent) 两次附加到一个网段。

怎么可能? 是 Centos Bug 吗?

回答你的问题:当然可以。您的问题中就有证据。

这是怎么发生的?如果您多次对同一个文件调用 mmap(),它会多次映射它。

为避免这种情况发生,答案是:不要那样做。

我纯粹是在猜测,但我敢打赌,您的 fork() 调用之一失败了,您从未进行任何错误检查,并且代码继续在父进程中执行子代码。这就解释了在一个 PID 上有两张地图。

I found the problem i was using same id to generate the ftok() key for two child processes.

一个key_t是32位,可以有任何我们想要的值。

ftok 只是一个“便捷”函数,用于生成唯一的 key_t 值,用于调用 shmget(见下文)。

如果我们为这个 key_t 值使用 IPC_PRIVATE,我们会得到一个私有描述符,其中任何 child of a single parent 进程可以使用。它在 ipcs 中显示为 0 的 key_t,具有唯一的 shmid [类似于文件描述符]。

所以,如果有一个 parent 而我们 只是 fork,我们可以使用它,因为 child ren 将从 parent 继承它。这是首选方法。因此,在这种情况下,不需要 ftok

使用私钥,当所有附加进程终止时,内核会自动删除共享区域。

如果我们使用 non-zero key_t 值,我们将创建一个 永久 区域。它将保留(数据仍然存在)。

要删除它,最终过程(即 parent 过程)必须对从 shmget 来电。

如果 parent 进程在 执行此操作之前死亡 ,则该区域仍然存在。这些区域仍会出现在 ipcs


下面是一些示例代码,说明了 IPC_PRIVATE 的用法。它 可以 调整为使用 non-zero key_t 值,但对于您的应用程序,这可能不被保证:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/wait.h>

#define NCHILD      200
#define SIZE        4096

// child process control
typedef struct {
    int tsk_cldidx;                     // child index
    int tsk_shmid;                      // shmid for process
    pid_t tsk_pid;                      // pid of process
    void *tsk_shmadr;                   // address of attached area
} tsk_t;

tsk_t cldlist[NCHILD];

void
dofork(int cldidx)
{
    tsk_t *tskself = &cldlist[cldidx];

    do {
        tskself->tsk_pid = fork();

        // parent
        if (tskself->tsk_pid != 0)
            break;

        // detach from all areas that are not ours
        for (cldidx = 0;  cldidx < NCHILD;  ++cldidx) {
            tsk_t *tsk = &cldlist[cldidx];
            if (tsk->tsk_cldidx != tskself->tsk_cldidx)
                shmdt(tsk->tsk_shmadr);
        }

        // do something useful ...

        exit(0);
    } while (0);
}

int
main(void)
{
    int cldidx;
    tsk_t *tsk;

    // create private shared memory ids for each child
    for (cldidx = 0;  cldidx < NCHILD;  ++cldidx) {
        tsk = &cldlist[cldidx];
        tsk->tsk_cldidx = cldidx;
        tsk->tsk_shmid = shmget(IPC_PRIVATE,SIZE,0);
        tsk->tsk_shmadr = shmat(tsk->tsk_shmid,NULL,0);
    }

    // start up all children
    for (cldidx = 0;  cldidx < NCHILD;  ++cldidx)
        dofork(cldidx);

    // do something useful with children ...

    // wait for all children
    while (wait(NULL) >= 0);

    // remove all segments
    // NOTE: with IPC_PRIVATE, this may not be necessary -- it may happen
    // automatically when we exit
    for (cldidx = 0;  cldidx < NCHILD;  ++cldidx) {
        tsk = &cldlist[cldidx];
        shmctl(tsk->tsk_shmid,IPC_RMID,NULL);
    }

    return 0;
}

如果我们有单独的程序 没有 parent/child 关系,我们需要 non-zero key_t。可能很难生成与另一个不 collide/conflict 的唯一 key_t,可能来自完全不相关的一组程序(例如另一个用户)

Can you please explain how much maximum unique keys can be generated using ftok().

AFAIK only last low 8 bits are significant. Can we use can we use 2 byte integer like "300" to generate the key . What is the chance for keys duplicate here?

对于给定的 [现有] 文件和 8proj_id,[如您所见] 只能生成 256 个唯一密钥。我们需要一个 不同的 文件参数来获取下一个 256.

最好完全放弃 ftok [我 从未 在使用 shmget 时使用它]。我已经完成 0xAA000000 作为基础 key_t 值。我已经用我需要的任何唯一 sub-key 值替换了零(有大约 2400 万种可能的组合)。


如果我们控制所有将访问共享内存区域的程序,则不需要有多个区域。

可能就够了,希望有一个共享内存区。在那种情况下,我们只做 one shmgetone shmat。然后,ftok(myfile,0) 可以生成一个漂亮的密钥。

如果与 child 通信所需的大小是(例如)一个页面 (PER_CHILD = 4096),并且我们有 NCHILD children 来创建,我们可以只创建一个 TOTAL_SIZE = PER_CHILD * NCHILD 大小的区域。那么,对于给定的child X,它的区域指针是shmaddr + (X * PER_CHILD)


更新:

Can I use IPC_CREAT flag and do exec() call to child?

我认为你的意思是将 non-zero 键与 shmget 结合使用。

执行调用将关闭任何 映射

它还会关闭 shmget [或 shm_open] 返回的文件描述符。

因此,使用 non-zero 密钥是确保它在 execvp

中工作的唯一 [实用] 方法

Will it cause any problem. AFAIK if we use exec() then a process will have a different address space. Will it cause any problem?

child程序将不得不(重新)通过shmgetshmat建立自己的映射。

但是,如果我们使用 shm_open [而不是 shmget],我们 可以 保持文件描述符打开 if 我们使用 fcntl 在调用 execvp.

之前清除描述符上的 FD_CLOEXEC 标志

然而,这可能没什么用,因为 child 程序 execvp 的目标)将 [可能] 不知道文件描述符编号parent 是用 shm_open 打开的,所以有点没有实际意义。