glibc 中的 mmap 实现 - 带符号 mmap 的动态库

mmap implementation in glibc - dynamic library with symbol mmap

我想看看 Linux 内核函数 mmap() 是如何实现的,所以我从 GNU page 下载了 GNU C 库 (glibc) 源代码。 (我下载了 glibc-2.27 因为 ldd --version 告诉我我正在使用 GLIBC 2.27
现在要找到 mmap() 的定义,我做了 grep -r "mmap(void" * 没有返回任何内容,所以我尝试 grep -r "mmap (void" * 返回以下内容:

conform/data/sys/mman.h-data:function {void*} mmap (void*, size_t, int, int, int, off_t)
include/sys/mman.h:extern void *__mmap (void *__addr, size_t __len, int __prot,
malloc/memusage.c:mmap (void *start, size_t len, int prot, int flags, int fd, off_t offset)
manual/llio.texi:@deftypefun {void *} mmap (void *@var{address}, size_t @var{length}, int @var{protect}, int @var{flags}, int @var{filedes}, off_t @var{offset})
misc/sys/mman.h:extern void *mmap (void *__addr, size_t __len, int __prot,
misc/mmap.c:__mmap (void *addr, size_t len, int prot, int flags, int fd, off_t offset)
support/xunistd.h:void *xmmap (void *addr, size_t length, int prot, int flags, int fd);
support/xmmap.c:xmmap (void *addr, size_t length, int prot, int flags, int fd)
sysdeps/unix/sysv/linux/mmap.c:__mmap (void *addr, size_t len, int prot, int flags, int fd, off_t offset)
sysdeps/mach/hurd/dl-sysdep.c:__mmap (void *addr, size_t len, int prot, int flags, int fd, off_t offset)
sysdeps/mach/hurd/mmap.c:__mmap (void *addr, size_t len, int prot, int flags, int fd, off_t offset)

在关于 mmap() 而不是关于 __mmap() 的所有结果中,我发现 mmap() 的定义在 malloc/memusage.c 中,它定义了 mmap()如下:

/* `mmap' replacement.  We do not have to keep track of the size since
   `munmap' will get it as a parameter.  */
void *
mmap (void *start, size_t len, int prot, int flags, int fd, off_t offset)
{
  void *result = NULL;

  /* Determine real implementation if not already happened.  */
  if (__glibc_unlikely (initialized <= 0))
    {
      if (initialized == -1)
        return NULL;

      me ();
    }

  /* Always get a block.  We don't need extra memory.  */
  result = (*mmapp)(start, len, prot, flags, fd, offset);

  ...

  /* Return the pointer to the user buffer.  */
  return result;
}

我认为 result = (*mmapp)(start, len, prot, flags, fd, offset); 才是最重要的,在这个文件中还有另外两个部分处理这个 mmapp 函数指针,它们是:

  1. 声明:static void *(*mmapp) (void *, size_t, int, int, int, off_t);
  2. 在一些名为 me() 的函数中初始化:mmapp = (void *(*)(void *, size_t, int, int, int, off_t))dlsym (RTLD_NEXT, "mmap");
    根据 the manualdlsym() 函数采用 dlopen() 返回的动态库的“句柄”和以 null 结尾的符号名称,返回该符号加载到内存中的地址。

因此,整个过程可以总结如下:

  1. mmap()调用指针指向的函数mmapp
  2. mmapp 设置为指向加载到内存中的动态库中的符号 "mmap"

但是我找不到任何关于符号"mmap"的动态库的信息。
我在代码分析过程中做错了吗?我在代码分析方面没有太多经验,更不用说研究系统调用函数或内核代码了,所以任何正确方向的建议或推动将不胜感激。
提前致谢!

Linux 内核有多个不同的 mmap 系统调用,它们在不同的版本和架构中并不相同。 libc mmap 函数将其抽象化并向其用户呈现 POSIX 接口。然而,Glibc 代码很复杂,因为它还针对 non-Linux 平台,并且具有 off_t/off64_t 转换的所有部分的代码路径(更多信息请参见 Glibc manual § Feature Test Macros有关的信息)。您可以更轻松地查看替代的最小 libc,例如 musl,它仅针对现代 Linux,既不针对历史也不针对完全 POSIX 兼容性。

在 musl 的情况下:原型和常量在 include/sys/mman.h. Implementation is in src/mman/mmap.c 中,它在移交给 syscall(SYS_mmap2) 之前进行一些参数处理(在大多数情况下)。

在 Glibc 的情况下:原型在 misc/sys/mman.h. Constants are scattered as not all platforms are the same; see bits/mman.h, sysdeps/unix/sysv/linux/bits/mman-shared.h, sysdeps/unix/sysv/linux/bits/mman-map-flags-generic.h, sysdeps/unix/sysv/linux/x86/bits/mman.h for example. The actual definition is in sysdeps/unix/sysv/linux/mmap.c 中,并且像 musl 的一样,它在移交给 syscall(SYS_mmap2) 之前进行一些参数处理(在大多数情况下)。

无论如何,libc 中的 mmap 只能在内核支持下工作。因此@NateEldredge 评论说它相当无趣。

顺便说一下,您在 malloc/memusage.c is not the definition of mmap that you are using. That gets built into a separate libmemusage library which intercepts mmap calls (among others) and adds some tracking around them. Your distribution may or may not ship this; if it does, you would have to opt-in to using it by running your program with LD_PRELOAD=libmemusage.so (which is effectively what the memusage 包装器脚本中看到的就是这样)。这就是它使用 dlsym 的原因:它正在定义替换 mmap 符号,但需要查找原始 mmap 才能包装对它的调用。

在 Linux 内核中,SYS_mmap2 在大多数架构上映射到 sys_mmap_pgoff,在 mm/mmap.c 中定义为

SYSCALL_DEFINE6(mmap_pgoff, unsigned long, addr, unsigned long, len,
        unsigned long, prot, unsigned long, flags,
        unsigned long, fd, unsigned long, pgoff)

如果给定一个文件描述符,它会导致调用

struct file {
    const struct file_operations {
        int (*mmap) (struct file *, struct vm_area_struct *);
    } *f_op;
}

回调(来自 include/linux/fs.h),当由提供该文件的文件系统定义时。它们有各种不同的实现,但通常它们会向进程的页表添加一个新的 vma,这要么使其他内核机制将特定页面映射到进程中,要么使该区域内的页面错误回调到文件系统以提供要映射的页面那个时候。