我有一个GNU / Linux应用程序,它使用了许多共享内存对象。它可能会在同一系统上运行多次。为了保持整洁,我首先在/dev/shm
中为每个共享内存对象创建一个目录。
问题是在较新的GNU / Linux发行版上,我似乎不再能够在/dev/shm
的子目录中创建它们。
以下是一个最小的C程序,说明了我在说什么:
/*****************************************************************************
* shm_minimal.c
*
* Test shm_open()
*
* Expect to create shared memory file in:
* /dev/shm/
* └── my_dir
* └── shm_name
*
* NOTE: Only visible on filesystem during execution. I try to be nice, and
* clean up after myself.
*
* Compile with:
* $ gcc -lrt shm_minimal.c -o shm_minimal
*
******************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
int main(int argc, const char* argv[]) {
int shm_fd = -1;
char* shm_dir = "/dev/shm/my_dir";
char* shm_file = "/my_dir/shm_name"; /* does NOT work */
//char* shm_file = "/my_dir_shm_name"; /* works */
// Create directory in /dev/shm
mkdir(shm_dir, 0777);
// make shared memory segment
shm_fd = shm_open(shm_file, O_RDWR | O_CREAT, 0600);
if (-1 == shm_fd) {
switch (errno) {
case EINVAL:
/* Confirmed on:
* kernel v3.14, GNU libc v2.19 (ArchLinux)
* kernel v3.13, GNU libc v2.19 (Ubuntu 14.04 Beta 2)
*/
perror("FAIL - EINVAL");
return 1;
default:
printf("Some other problem not being tested\n");
return 2;
}
} else {
/* Confirmed on:
* kernel v3.8, GNU libc v2.17 (Mint 15)
* kernel v3.2, GNU libc v2.15 (Xubuntu 12.04 LTS)
* kernel v3.1, GNU libc v2.13 (Debian 6.0)
* kernel v2.6.32, GNU libc v2.12 (RHEL 6.4)
*/
printf("Success !!!\n");
}
// clean up
close(shm_fd);
shm_unlink(shm_file);
rmdir(shm_dir);
return 0;
}
/* vi: set ts=2 sw=2 ai expandtab:
*/
当我在一个相当新的发行版上运行此程序时,对shm_open()
的调用会返回-1
,而errno
会设置为EINVAL
。但是,当我运行较旧的东西时,它会按预期在/dev/shm/my_dir
中创建共享内存对象。
对于更大的应用程序,解决方案很简单。我可以使用公共前缀而不是目录。
如果你能帮助我了解这种明显的行为改变,那将会非常有帮助。我怀疑其他人可能会尝试做类似的事情。
答案 0 :(得分:8)
事实证明,问题源于GNU libc如何验证共享内存名称。具体来说,共享内存对象现在必须位于shmfs
挂载点的根目录。
由于错误git commit b20de2c3d9导致glibc BZ #16274中的内容发生了变化。
具体来说,改变是行:
if (name[0] == '\0' || namelen > NAME_MAX || strchr (name, '/') != NULL)
现在不允许'/'
来自文件名中的任何位置(不计入前导'/'
)
答案 1 :(得分:4)
如果你有第三方工具被这个shm_open更改打破了,一位出色的同事找到了一个解决方法:预加载一个覆盖shm_open调用的库并交换下划线的斜杠。对于shm_unlink也是如此,因此应用程序可以在需要时正确释放共享内存。
deslash_shm.cc:
#include <dlfcn.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <algorithm>
#include <string>
// function used in place of the standard shm_open() function
extern "C" int shm_open(const char *name, int oflag, mode_t mode)
{
// keep a function pointer to the real shm_open() function
static int (*real_open)(const char *, int, mode_t) = NULL;
// the first time in, ask the dynamic linker to find the real shm_open() function
if (!real_open) real_open = (int (*)(const char *, int, mode_t)) dlsym(RTLD_NEXT,"shm_open");
// take the name we were given and replace all slashes with underscores instead
std::string n = name;
std::replace(n.begin(), n.end(), '/', '_');
// call the real open function with the patched path name
return real_open(n.c_str(), oflag, mode);
}
// function used in place of the standard shm_unlink() function
extern "C" int shm_unlink(const char *name)
{
// keep a function pointer to the real shm_unlink() function
static int (*real_unlink)(const char *) = NULL;
// the first time in, ask the dynamic linker to find the real shm_unlink() function
if (!real_unlink) real_unlink = (int (*)(const char *)) dlsym(RTLD_NEXT, "shm_unlink");
// take the name we were given and replace all slashes with underscores instead
std::string n = name;
std::replace(n.begin(), n.end(), '/', '_');
// call the real unlink function with the patched path name
return real_unlink(n.c_str());
}
编译此文件:
c++ -fPIC -shared -o deslash_shm.so deslash_shm.cc -ldl
在开始尝试在shm_open中使用非标准斜杠字符的进程之前预加载它:
bash中的:
export LD_PRELOAD=/path/to/deslash_shm.so
在tcsh中:
setenv LD_PRELOAD /path/to/deslash_shm.so