关于将共享内存与进程一起使用,我有几个问题。我看了几篇以前的帖子,但无法准确地收集答案。在此先感谢您的帮助。
我正在使用shm_open + mmap,如下所示。此代码按照预期的方式与父级和子级交替使用以递增g_shared-> count(同步不可移植;它仅适用于某些内存模型,但对于我的情况现在已经足够了)。但是,当我将MAP_SHARED更改为MAP_ANONYMOUS |时MAP_SHARED,内存不共享,程序挂起,因为'flag'没有被翻转。删除标志确认每个进程从0到10计数发生了什么(暗示每个进程都有自己的结构副本,因此有'count'字段)。这是预期的行为吗?我不希望内存由文件支持;我真的想模仿如果这些是线程而不是进程可能会发生什么(由于其他原因,它们需要是进程)。
我真的需要shm_open吗?由于进程属于同一层次结构,我可以单独使用mmap吗?我知道如果没有'exec',这将是相当简单的,但是当'fork'后面有'exec'时,如何让它工作?
我在x86_64(Intel i7-2600)上使用内核版本3.2.0-23。对于这个实现,mmap是否提供与共享内存相同的行为(正确性和性能)与pthread共享相同的全局对象?例如,MMU是否使用“可缓存”MTRR / TLB属性映射该段?
cleanup_shared()代码是否正确?它泄漏了任何记忆吗?我怎么检查?例如,是否有相当于System V的'ipcs?'
感谢, / Doobs
shmem.h:
#ifndef __SHMEM_H__
#define __SHMEM_H__
//includes
#define LEN 1000
#define ITERS 10
#define SHM_FNAME "/myshm"
typedef struct shmem_obj {
int count;
char buff[LEN];
volatile int flag;
} shmem_t;
extern shmem_t* g_shared;
extern char proc_name[100];
extern int fd;
void cleanup_shared() {
munmap(g_shared, sizeof(shmem_t));
close(fd);
shm_unlink(SHM_FNAME);
}
static inline
void init_shared() {
int oflag;
if (!strcmp(proc_name, "parent")) {
oflag = O_CREAT | O_RDWR;
} else {
oflag = O_RDWR;
}
fd = shm_open(SHM_FNAME, oflag, (S_IREAD | S_IWRITE));
if (fd == -1) {
perror("shm_open");
exit(EXIT_FAILURE);
}
if (ftruncate(fd, sizeof(shmem_t)) == -1) {
perror("ftruncate");
shm_unlink(SHM_FNAME);
exit(EXIT_FAILURE);
}
g_shared = mmap(NULL, sizeof(shmem_t),
(PROT_WRITE | PROT_READ),
MAP_SHARED, fd, 0);
if (g_shared == MAP_FAILED) {
perror("mmap");
cleanup_shared();
exit(EXIT_FAILURE);
}
}
static inline
void proc_write(const char* s) {
fprintf(stderr, "[%s] %s\n", proc_name, s);
}
#endif // __SHMEM_H__
shmem1.c(父进程):
#include "shmem.h"
int fd;
shmem_t* g_shared;
char proc_name[100];
void work() {
int i;
for (i = 0; i < ITERS; ++i) {
while (g_shared->flag);
++g_shared->count;
sprintf(g_shared->buff, "%s: %d", proc_name, g_shared->count);
proc_write(g_shared->buff);
g_shared->flag = !g_shared->flag;
}
}
int main(int argc, char* argv[], char* envp[]) {
int status, child;
strcpy(proc_name, "parent");
init_shared(argv);
fprintf(stderr, "Map address is: %p\n", g_shared);
if (child = fork()) {
work();
waitpid(child, &status, 0);
cleanup_shared();
fprintf(stderr, "Parent finished!\n");
} else { /* child executes shmem2 */
execvpe("./shmem2", argv + 2, envp);
}
}
shmem2.c(子进程):
#include "shmem.h"
int fd;
shmem_t* g_shared;
char proc_name[100];
void work() {
int i;
for (i = 0; i < ITERS; ++i) {
while (!g_shared->flag);
++g_shared->count;
sprintf(g_shared->buff, "%s: %d", proc_name, g_shared->count);
proc_write(g_shared->buff);
g_shared->flag = !g_shared->flag;
}
}
int main(int argc, char* argv[], char* envp[]) {
int status;
strcpy(proc_name, "child");
init_shared(argv);
fprintf(stderr, "Map address is: %p\n", g_shared);
work();
cleanup_shared();
return 0;
}
答案 0 :(得分:3)
传递MAP_ANONYMOUS会导致内核忽略您的文件描述符参数,而是为您提供私有映射。那不是你想要的。
是的,您可以在父进程fork中创建匿名共享映射,并让子进程继承映射,与父进程和任何其他子进程共享内存。尽管如此,obviusly并不存在于exec()中。
我不明白这个问题; pthreads不分配内存。可缓存性取决于您映射的文件描述符。如果它是磁盘文件或匿名映射,那么它是可缓存的内存。如果它是一个视频帧缓冲设备,它可能不是。
这是调用munmap()的正确方法,但我没有验证超出该范围的逻辑。所有进程都需要取消映射,只有一个应该调用unlink。
答案 1 :(得分:1)
2b)作为一种中间立场,可以打电话:
int const shm_fd = shm_open(fn,...);
shm_unlink(fn);
在父进程中,然后通过argp或envp将fd传递给fork()/ execve()创建的子进程。由于这种类型的打开文件描述符将在fork()/ execve()中存活,因此您可以在父进程和任何已处理的进程中对fd进行mmap。这是一个更完整的代码示例,从我在Ubuntu 12.04 / linux内核3.13 / glibc 2.15下成功运行的代码中复制和简化/清理:
int create_shm_fd( void ) {
int oflags = O_RDWR | O_CREAT | O_TRUNC;
string const fn = "/some_shm_fn_maybe_with_pid";
int fd;
neg_one_fail( fd = shm_open( fn.c_str(), oflags, S_IRUSR | S_IWUSR ), "shm_open" );
if( fd == -1 ) { rt_err( strprintf( "shm_open() failed with errno=%s", str(errno).c_str() ) ); }
// for now, we'll just pass the open fd to our child process, so
// we don't need the file/name/link anymore, and by unlinking it
// here we can try to minimize the chance / amount of OS-level shm
// leakage.
neg_one_fail( shm_unlink( fn.c_str() ), "shm_unlink" );
// by default, the fd returned from shm_open() has FD_CLOEXEC
// set. it seems okay to remove it so that it will stay open
// across execve.
int fd_flags = 0;
neg_one_fail( fd_flags = fcntl( fd, F_GETFD ), "fcntl" );
fd_flags &= ~FD_CLOEXEC;
neg_one_fail( fcntl( fd, F_SETFD, fd_flags ), "fcntl" );
// resize the shm segment for later mapping via mmap()
neg_one_fail( ftruncate( fd, 1024*1024*4 ), "ftruncate" );
return fd;
}
如果删除FD_CLOEXEC并且/或者假设在执行此操作之后fd确实能够在exec中存活,那么对我来说并不是100%清楚。 exec的手册页不清楚;它说:“POSIX共享内存区域是未映射的”,但对我而言,前面的一般注释是多余的,不保留映射,并且不会说shm_open()'d fd将被关闭。当然,事实是,正如我所提到的,代码确实在至少一个案例中起作用。
我可能使用这种方法的原因是它似乎减少了泄漏共享内存段/文件名的机会,并且它清楚地表明我不需要内存段的持久性。