shm_open:Mac 和 Linux 之间的区别
shm_open: Differences between Mac and Linux
我在共享内存中有一个队列。它确实适用于 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 会用零填充共享内存,因此我依赖 isInitiualized 在新的共享内存页面上为零,并在初始化结构后立即在其中写入 1。
正如我所说,它适用于 Linux,所以我不认为这是一个简单的实现错误。 Mac 上的 shm_open 与我可能没有意识到的 Linux 之间是否存在任何细微差别?我看到的错误看起来像 reader 试图从一个空队列中读取,所以,也许 pthread mutex/condition 在 Mac?
中的共享内存上不起作用
您必须对互斥变量和条件变量都设置 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
函数不存在或 return 出现错误,那么您的平台可能不支持它。
为了安全起见,如果支持,您可能需要设置 PTHREAD_MUTEX_ROBUST
。如果进程在持有锁时退出,这将防止互斥锁死锁(尽管不能保证队列一致性)。
编辑:作为额外的警告,拥有一个布尔值 "is initialized" 标志本身是一个不充分的计划。要真正保证只有一个进程可以初始化结构,您需要的不仅仅是这些。至少你需要做:
// 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
的解决方案
在共享内存中实现您自己的队列的一种替代方法是使用 OS 提供的使用 mkfifo
命名的 FIFO。 FIFO 和命名管道之间的一个主要区别是您可以同时拥有多个 reader 和写入器。
A "catch" 是 reader 在最后一个编写器退出时看到文件末尾,所以如果您希望 readers 无限期地进行,您可以需要打开一个虚拟写句柄。
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);
}
}
问题是 PTHREAD_PROCESS_SHARED 在 mac 上不受支持。
http://alesteska.blogspot.de/2012/08/pthreadprocessshared-not-supported-on.html
我在共享内存中有一个队列。它确实适用于 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 会用零填充共享内存,因此我依赖 isInitiualized 在新的共享内存页面上为零,并在初始化结构后立即在其中写入 1。
正如我所说,它适用于 Linux,所以我不认为这是一个简单的实现错误。 Mac 上的 shm_open 与我可能没有意识到的 Linux 之间是否存在任何细微差别?我看到的错误看起来像 reader 试图从一个空队列中读取,所以,也许 pthread mutex/condition 在 Mac?
中的共享内存上不起作用您必须对互斥变量和条件变量都设置 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
函数不存在或 return 出现错误,那么您的平台可能不支持它。
为了安全起见,如果支持,您可能需要设置 PTHREAD_MUTEX_ROBUST
。如果进程在持有锁时退出,这将防止互斥锁死锁(尽管不能保证队列一致性)。
编辑:作为额外的警告,拥有一个布尔值 "is initialized" 标志本身是一个不充分的计划。要真正保证只有一个进程可以初始化结构,您需要的不仅仅是这些。至少你需要做:
// 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
的解决方案
在共享内存中实现您自己的队列的一种替代方法是使用 OS 提供的使用 mkfifo
命名的 FIFO。 FIFO 和命名管道之间的一个主要区别是您可以同时拥有多个 reader 和写入器。
A "catch" 是 reader 在最后一个编写器退出时看到文件末尾,所以如果您希望 readers 无限期地进行,您可以需要打开一个虚拟写句柄。
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);
}
}
问题是 PTHREAD_PROCESS_SHARED 在 mac 上不受支持。
http://alesteska.blogspot.de/2012/08/pthreadprocessshared-not-supported-on.html