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 同一主题中的两个问题,但它们是并列的):
查看进程调用库函数的正确方法是什么(除了我使用的 ltrace
和 ldd
)
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
我正在尝试了解 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 同一主题中的两个问题,但它们是并列的):
查看进程调用库函数的正确方法是什么(除了我使用的
ltrace
和ldd
)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