6

shm_open手册页:

一个新的共享内存对象最初的长度为零。可以使用 ftruncate(2) 设置对象的大小。[...] shm_open() 函数本身不会创建指定大小的共享对象,因为这样做会复制一个现有函数,该函数设置文件描述符引用的对象的大小。

这不会将应用程序暴露给竞争条件吗?考虑以下伪代码:

int fd = shm_open("/foo", CREATE);
if ( fd is valid ) {
  // created shm object, so set its size
  ftruncate(fd, 128);
} else {
  fd = shm_open("/foo", GET_EXISTING);
}
void* mem = mmap(fd, 128);

由于shm_openandftruncate调用(一起)不是原子的,因此您可能有一个竞争条件,即一个进程调用shm_openCREATEcase),但在调用之前ftruncate,另一个进程调用shm_openGET_EXISTINGcase)并尝试mmap0 大小的对象,甚至可能写入它。

我可以想出两种方法来避免这种竞争条件:

  1. 使用 IPC 互斥体/信号量使整个事物同步,或者...

  2. 如果它是安全的(根据 POSIX),请ftruncate同时调用CREATEGET_EXISTING情况。

避免这种竞争条件的首选方法是什么?

4

2 回答 2

6

您的方法(从两者调用ftruncate)应该有效,但无论如何您都需要一种方法来同步共享内存段内容的使用。由于内存最初是空的(零填充),因此不包含有效的同步对象,除非您打算使用原子来滚动自己的同步对象,否则无论如何您都需要辅助形式的同步来控制对共享内存的访问。

我通常认为,与其让多个进程竞相创建或打开具有固定名称的共享内存段,不如让所有者进程负责创建具有随机名称的段,O_EXCL以避免随机或恶意冲突,然后在成功打开它、调整大小并在其中创建同步对象后将名称传递给需要访问它的其他进程。

于 2013-05-12T01:00:19.550 回答
1

作为@R。提到,这里的另一个问题是,在创建文件之后,在内容(例如互斥锁)被初始化并准备好使用之前,仍然有一个窗口。

与上述略有不同的解决方案是:

尝试打开()。如果 open() 成功,只需 map() 并使用必要的保证(见下文)内容已经初始化并且可以使用。如果 open() 失败,创建并初始化一个临时文件,然后尝试将临时文件硬链接() 为所需文件并 unlink() 临时名称。

如果 link() 成功,我们现在已经为我们自己和其他进程提供了初始化文件。如果 link() 因 EEXIST 失败,则另一个进程首先到达那里(与 rename() 不同,如果目标名称存在,则 link() 失败)。无论哪种方式,我们重复的 open() 现在应该会成功并初始化一个可以使用的文件。

使用这种策略,对于临时文件的初始化显然存在竞争条件,但是,如果初始化过程是幂等的,资源不会过于昂贵,并且每个进程都选择一个唯一的临时文件,这没有任何后果。如果多重初始化可能是一个问题,一个解决方案是将初始化分成一个两阶段的过程,第一阶段只是文件中的一个互斥锁,用于防止在第二阶段对文件的其余部分进行多次初始化。

于 2015-05-25T00:34:26.510 回答