【问题标题】:shm_open: Differences between Mac and Linuxshm_open:Mac 和 Linux 的区别
【发布时间】:2016-02-09 21:06:55
【问题描述】:

我在共享内存中有一个队列。它可以在 Linux(内核 4.3.4)上运行,但不能在 Mac OS X 上运行。Mac OS X 处理共享内存的方式与 linux 的处理方式之间是否存在差异,这可以解释这一点吗?

我通过以下方式获得共享内存:

int sh_fd = shm_open(shmName, O_RDWR | O_CREAT, 
        S_IROTH | S_IWOTH // others hav read/write permission
        | S_IRUSR | S_IWUSR // I have read/write permission
        );

// bring the shared memory to the desired size
ftruncate(sh_fd, getpagesize());

队列也很简单。这是基本结构:

typedef struct {
// this is to check whether the queue is initialized.
// on linux, this will be 0 initially
bool isInitialized;
// mutex to protect concurrent access
pthread_mutex_t access;
// condition for the reader, readers should wait here
pthread_cond_t reader;
// condition for the writer, writers should wait here
pthread_cond_t writer;
// whether the queue can still be used.
bool isOpen;
// maximum capacity of the queue.
int32_t capacity;
// current position of the reader and number of items.
int32_t readPos, items;

// entries in the queue. The array actually is longer, which means it uses the space behind the struct.
entry entries[1];
} shared_queue;

基本上每个想要访问的人都会获得互斥体,readPos 表示应该读取下一个值的位置(之后增加 readPos),(readPos+items) % capacity 是新项目的位置。唯一有点花哨的技巧是 isInitialized 字节。如果之前长度为 0,ftruncate 会用零填充共享内存,因此我依赖 isInitialized 在新的共享内存页面上为零,并在初始化结构后立即在此处写入 1。

正如我所说,它适用于 Linux,所以我不认为这是一个简单的实现错误。 Mac 上的 shm_open 与我可能不知道的 Linux 上的 shm_open 之间有什么细微的区别吗?我看到的错误看起来像是读者试图从一个空队列中读取,所以,也许 pthread 互斥/条件不适用于 Mac 中的共享内存?

【问题讨论】:

  • 我不明白isInitialisedftruncate 之间的联系。你能详细说明一下吗?
  • @terencehill - 我很确定他对那个标志所做的任何“花哨的技巧”都不足以(在一般情况下)阻止两个进程尝试初始化同一个队列并保证没有进程曾经尝试使用未初始化的队列。请参阅下面我编辑的答案...

标签: c linux macos shared-memory


【解决方案1】:

【讨论】:

  • 看起来你是对的,这意味着 Alex 的解决方案不能移植到 OSX。
【解决方案2】:

您必须在互斥量和条件变量上都设置PTHREAD_PROCESS_SHARED

所以对于互斥锁:

pthread_mutexattr_t mutex_attr;
pthread_mutex_t     the_mutex;

pthread_mutexattr_init(&mutex_attr);
pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED);
pthread_mutexattr(&the_mutex, &mutex_attr);

条件变量的步骤基本相同,但将 mutexattr 替换为 condattr

如果pthread_*attr_setpshared 函数不存在或返回错误,那么您的平台可能不支持它。

为了安全起见,如果支持,您可能需要设置PTHREAD_MUTEX_ROBUST。如果进程在持有锁的情况下退出,这将防止互斥锁死锁(尽管不能保证队列一致性)。

编辑:作为一个额外的警告,拥有一个布尔“已初始化”标志本身是一个不充分的计划。您需要更多才能真正保证只有一个进程可以初始化结构。至少你需要做:

// O_EXCL means this fails if not the first one here
fd = shm_open(name, otherFlags | O_CREAT | O_EXCL );  
if( fd != -1 )
{
   // initialize here

   // Notify everybody the mutex has been initialized.
}
else
{
    fd = shm_open(name, otherFlags ); // NO O_CREAT

    // magically somehow wait until queue is initialized.
}

您确定真的需要创建自己的队列吗? POSIX 消息队列(参见mq_open 手册页)可以完成这项工作吗?如果不是,那么使用众多消息中间件解决方案中的哪一种呢?

2016 年 2 月 10 日更新:可能基于 mkfifo 的解决方案

在共享内存中实现自己的队列的另一种方法是使用操作系统提供的名为 FIFO 的 mkfifo。 FIFO 和命名管道之间的主要区别在于,您可以同时拥有多个读取器和写入器。

对此的一个“问题”是,当最后一个写入器退出时,读取器会看到文件结尾,因此如果您希望读取器无限期地移动,您可能需要打开一个虚拟写入句柄。

FIFO 在命令行上非常容易使用,如下所示:

reader.sh

mkfifo my_queue
cat my_queue

write.sh

echo "hello world" > my_queue

或者在 C 语言中稍加努力:

reader.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>

int main(int argc, char**argv)
{
  FILE * fifo;
  FILE * wfifo;
  int res;
  char buf[1024];
  char * linePtr;

  /* Try to create the queue.  This may belong on reader or writer side
   * depending on your setup. */
  if( 0 !=  mkfifo("work_queue", S_IRUSR | S_IWUSR ) )
  {
    if( errno != EEXIST )
    {
      perror("mkfifo:");
      return -1;
    }
  }

  /* Get a read handle to the queue */
  fifo = fopen("work_queue", "r"); 

  /* Get a write handle to the queue */
  wfifo = fopen("work_queue", "w"); 
  if( !fifo )
  {
    perror("fopen: " );
    return -1;
  }

  while(1)
  {
    /* pull a single message from the queue at a time */
    linePtr = fgets(buf, sizeof(buf), fifo);
    if( linePtr )
    {
      fprintf(stdout, "new command=%s\n", linePtr);
    }
    else
    {
      break;
    }
  } 

  return 0;
}

writer.c

#include <stdio.h>
#include <unistd.h>
int main(int argc, char**argv)
{
  FILE * pipe = fopen("work_queue", "w");
  unsigned int job = 0;
  int my_pid = getpid(); 
  while(1)
  {
    /* Write one 'entry' to the queue */
    fprintf(pipe, "job %u from %d\n", ++job, my_pid);
  }
}

【讨论】:

  • 是的,你是对的,bool 标志是不够的。在开始第二个过程之前,我通常只等待几秒钟。感谢您的建议。
  • 对于 mq_open:如果我搜索“mac mq_open”,则该线程是第七次点击。你确定这个函数存在于 Mac OS X 上吗(在 linux 上,这可能是个好主意)。
  • mq_open 是一个 POSIX 的东西,而不是特定于 Linux 的。但奇怪的是,我猜你是对的,OSX 不支持它。 OSX确实支持 System V 消息队列(参见msgctlmsgsnd 等),这也很奇怪。
  • @Alex - 你可以试试mkfifo,它为你提供了一个多读者、多作者的安全队列。唯一需要注意的“技巧”是,当最后一个作者关闭时,所有读者都会看到一个EOF
  • @Alex - 查看更新的答案以获取使用 mkfifo 的快速示例。
猜你喜欢
  • 2020-12-01
  • 2020-07-22
  • 2011-07-12
  • 2014-09-11
  • 2015-01-20
  • 2012-07-30
  • 2010-12-14
  • 2015-10-27
  • 1970-01-01
相关资源
最近更新 更多