一个共享内存段是否有可能多次附加到同一个父 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?
对于给定的 [现有] 文件和 8 位 proj_id
,[如您所见] 只能生成 256 个唯一密钥。我们需要一个 不同的 文件参数来获取下一个 256.
最好完全放弃 ftok
[我 从未 在使用 shmget
时使用它]。我已经完成 0xAA000000
作为基础 key_t
值。我已经用我需要的任何唯一 sub-key 值替换了零(有大约 2400 万种可能的组合)。
如果我们控制所有将访问共享内存区域的程序,则不需要有多个区域。
可能就够了,多希望有一个单共享内存区。在那种情况下,我们只做 one shmget
和 one 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程序将不得不(重新)通过shmget
和shmat
建立自己的映射。
但是,如果我们使用 shm_open
[而不是 shmget
],我们 可以 保持文件描述符打开 if 我们使用 fcntl
在调用 execvp
.
之前清除描述符上的 FD_CLOEXEC
标志
然而,这可能没什么用,因为 child 程序 (execvp
的目标)将 [可能] 不知道文件描述符编号parent 是用 shm_open
打开的,所以有点没有实际意义。
我已经创建了一个父进程的 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?
对于给定的 [现有] 文件和 8 位 proj_id
,[如您所见] 只能生成 256 个唯一密钥。我们需要一个 不同的 文件参数来获取下一个 256.
最好完全放弃 ftok
[我 从未 在使用 shmget
时使用它]。我已经完成 0xAA000000
作为基础 key_t
值。我已经用我需要的任何唯一 sub-key 值替换了零(有大约 2400 万种可能的组合)。
如果我们控制所有将访问共享内存区域的程序,则不需要有多个区域。
可能就够了,多希望有一个单共享内存区。在那种情况下,我们只做 one shmget
和 one 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程序将不得不(重新)通过shmget
和shmat
建立自己的映射。
但是,如果我们使用 shm_open
[而不是 shmget
],我们 可以 保持文件描述符打开 if 我们使用 fcntl
在调用 execvp
.
FD_CLOEXEC
标志
然而,这可能没什么用,因为 child 程序 (execvp
的目标)将 [可能] 不知道文件描述符编号parent 是用 shm_open
打开的,所以有点没有实际意义。