Redis 服务器及其 fork() - 何时调用 vm_enough_memory?

Redis server and its fork() - when the vm_enough_memory is called?

我正在尝试了解 fork() 如何在 Linux 上的 Redis 服务器 运行 中工作,以及 Redis 如何生成 fork: 无法分配内存 响应。

根据我的调查,我看到了下一个:

1 redis-server 在其 rdbSaveBackground():

中调用 fork()

if ((childpid = fork()) == 0) {

2 这从 glibc 的 sysdeps/nptl/fork.c(似乎在 /usr/lib/libc.so.6 中)调用了一个 fork()

$ ldd /usr/bin/redis-server
    linux-vdso.so.1 (0x00007ffde8d93000)
    libjemalloc.so.2 => /usr/lib/libjemalloc.so.2 (0x00007fa5da60b000)
    libm.so.6 => /usr/lib/libm.so.6 (0x00007fa5da4c5000)
    libdl.so.2 => /usr/lib/libdl.so.2 (0x00007fa5da4c0000)
    libpthread.so.0 => /usr/lib/libpthread.so.0 (0x00007fa5da49f000)
    libc.so.6 => /usr/lib/libc.so.6 (0x00007fa5da2dc000)

但我不确定 ldd 是否是检查它的正确方法。 ltrace 仅显示 fork() 本身 - 但不显示调用它的来源。

3 sysdeps/nptl/fork.c执行arch_fork

4 然后 sysdeps/unix/sysv/linux/arch-fork.h 恰好调用 clone() Linux 系统调用:

ret = INLINE_SYSCALL_CALL (clone, flags, 0, NULL, 0, ctid);

可以在 strace 的输出中看到:

accept(5, {sa_family=AF_INET, sin_port=htons(60816), sin_addr=inet_addr("127.0.0.1")}, [128->16]) = 6
...
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=2097, ...}) = 0
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD,
child_tidptr=0x7ff26beda190) = 1790

如果是 - 什么时候执行 vm_enough_memory

一定要起Redis著名的“无法在后台保存:fork:无法分配内存”。

我能够在 Linux 的 fork() 系统调用中找到 vm_enough_memory

security_vm_enough_memory_mm:

        if (security_vm_enough_memory_mm(oldmm, len)) /* sic */
            goto fail_nomem;

include/linux/security.h中描述:

static inline int security_vm_enough_memory_mm(struct mm_struct *mm, long pages)
{
    return __vm_enough_memory(mm, pages, cap_vm_enough_memory(mm, pages));
}

所以,问题是(是的,我知道这很糟糕 post 同一主题中的两个问题,但它们是并列的):

  1. 查看进程调用库函数的正确方法是什么(除了我使用的 ltraceldd

  2. glibc的fork()和Linuxclone()呢? Redis如何产生fork: Cannot allocate memory if it is really uses glibc 的 fork()?

也许我看错方向了,Redis 会调用 Linux fork() 系统调用?如果是这样 - 这将解释一切(但不是 strace 的输出与 clone()...)。

Redis server and its fork() - when the vm_enough_memory is called?

Redis 不直接调用 vm_enough_memory - 它只是调用 glibc 中的 fork 包装器,后者又调用内核的 fork 系统调用(即 clone 所有系统调用现代 Linux 内核)。

What is the correct way to see library functions calls by a process (besides the ltrace and ldd I used)

ltrace 可以获取库调用。 ldd 列出共享库依赖项,但不会告诉您给定函数所在的库。您可能会发现 nm and objdump 实用程序在这方面很有用。

If so - when then vm_enough_memory is executed?

vm_enough_memory 是内核函数 - strace 或其他工具不会列出它们。 strace 通常足以让应用程序找出用户代码 and/or C 库正在调用哪些系统调用(除非您正在调试内核本身)。

例如,strlen 函数在 C 库中定义(它在文本部分,所以你知道它是在那里定义的):

 $ objdump -T  /lib/x86_64-linux-gnu/libc.so.6 | grep strlen
000000000007fd10 g   iD  .text  000000000000003d  GLIBC_2.2.5 strlen

(阅读手册页了解各种选项)。

What about glibc's fork() and Linux clone()? How would Redis produce the fork: Cannot allocate memory if it is really uses glibc's fork()?

再一次,你让事情复杂化了。 Redis 只需调用 fork(),如果它 returns -1(分叉失败),则它会根据 errno 值简单地打印该错误消息。

那个特定的消息 comes from this line of code:

   serverLog(LL_WARNING,"Can't save in background: fork: %s",
            strerror(errno));

当fork失败时,childpid为-1,设置errno表示错误。当 errno 设置为 ENOMEM 时,您会收到 Cannot allocate memory 错误消息。

下面是一个简单的示例:

#include <stdio.h>
#include <string.h>
#include <errno.h>

int main(int argc, char** argv) {
    errno = ENOMEM;
    printf("errno (ENOMEM): %s\n", strerror(errno));
}

它输出:

$ gcc -Wall -std=c11 test.c
$ ./a.out
errno (ENOMEM): Cannot allocate memory