Linux 在/dev/shm子目录中创建共享内存时,shm_open()与EINVAL一起失败

Linux 在/dev/shm子目录中创建共享内存时,shm_open()与EINVAL一起失败,linux,shared-memory,glibc,Linux,Shared Memory,Glibc,我有一个GNU/Linux应用程序,它使用了许多共享内存对象。它可能在同一个系统上运行多次。为了保持整洁,我首先在/dev/shm中为每个共享内存对象集创建一个目录 问题是,在较新的GNU/Linux发行版上,我似乎不再能够在/dev/shm的子目录中创建这些 下面是一个简单的C程序,它演示了我所说的内容: /***************************************************************************** * shm_minimal.c

我有一个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_minimal.c
*
*测试shm_open()
*
*希望在以下位置创建共享内存文件:
*/dev/shm/
*  └── 我的主任
*      └── shm_名称
*
*注意:仅在执行期间在文件系统上可见。我试着和蔼可亲,而且
*我自己收拾一下。
*
*编译时使用:
*$gcc-轻轨最小值。c-o最小值
*
******************************************************************************/
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
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”/*不起作用*/
//char*shm\u file=“/my\u dir\u shm\u name”/*有效*/
//在/dev/shm中创建目录
mkdir(shm_dir,0777);
//创建共享内存段
shm_fd=shm_open(shm_文件,O_RDWR | O_CREAT,0600);
如果(-1==shm\u fd){
开关(错误号){
案例EINVAL:
/*确认日期:
*内核v3.14,GNU libc v2.19(ArchLinux)
*内核v3.13,GNU libc v2.19(Ubuntu 14.04 Beta 2)
*/
perror(“FAIL-EINVAL”);
返回1;
违约:
printf(“其他一些未测试的问题”);
返回2;
}
}否则{
/*确认日期:
*内核v3.8,GNU libc v2.17(Mint 15)
*内核v3.2,GNU libc v2.15(Xubuntu 12.04 LTS)
*内核v3.1,GNU libc v2.13(Debian 6.0)
*内核v2.6.32,GNU libc v2.12(RHEL 6.4)
*/
printf(“成功!!!\n”);
}
//清理
关闭(shm_fd);
shm_取消链接(shm_文件);
rmdir(shm_dir);
返回0;
}
/*vi:设置ts=2 sw=2 ai扩展选项卡:
*/
当我在一个相当新的发行版上运行这个程序时,对
shm_open()
的调用返回
-1
,并且
errno
被设置为
EINVAL
。但是,当我在稍旧的东西上运行时,它会像预期的那样在
/dev/shm/my_dir
中创建共享内存对象

对于较大的应用程序,解决方案很简单。我可以使用公共前缀而不是目录


如果你能帮助我了解这种行为上的明显变化,那将非常有帮助。我怀疑其他人可能也在尝试做类似的事情。

因此,问题源于GNU libc如何验证共享内存名。具体来说,共享内存对象现在必须位于
shmfs
装载点的根

这是在glibc中由于错误而更改的

具体而言,变化是:

if (name[0] == '\0' || namelen > NAME_MAX || strchr (name, '/') != NULL)

现在文件名中的任何位置都不允许使用
'/'
(不包括前导的
'/'

如果您有第三方工具被此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());
}
并在启动尝试在shm_open中使用非标准斜杠字符的进程之前预加载:

在bash中:

export LD_PRELOAD=/path/to/deslash_shm.so
在tcsh中:

setenv LD_PRELOAD /path/to/deslash_shm.so

POSIX表示,
shm_open
的参数中除第一个字符外的斜杠字符是“实现定义的”()。实际上,我会尝试(a)在
strace
下运行,看看实际发生了什么;(b)使用不带斜杠的
my_dir/my_name
,看看会发生什么。(尽管您处于实现定义的领域,但您可能只想创建自己的tmpfs并直接使用它。)@Nemo谢谢,这证实了我刚才在阅读glibc源代码和bug追踪器时发现的并不是POSIX违规行为。我猜新的“实现定义”是没有子目录的。它发生的方式很有趣。就在我即将放弃的时候,我找到了答案。@Nemo使用
strace
是一个很好的建议。谢天谢地,这个大型应用程序是我正在编写的,所以我只需附加调试器就可以做得更好。关于领先的
“/”
,现在我已经阅读了glibc代码,我可以说它不会有任何区别。他们要做的第一件事是删除所有的前导字符。实现在文件
sysdeps/unix/sysv/linux/shm_open.c
中。谢谢。调查得不错(+1)。让glibc以最不有用的方式解释“实现定义”。。。这意味着如果你真的想要子目录结构,就要创建你自己的tmpfs。谢谢。这是保存在工具箱中的一小段方便的代码。
setenv LD_PRELOAD /path/to/deslash_shm.so