在 shm_open() 之后使用 fallocate() 导致内存在 shm_unlink() 之后没有被释放
Using fallocate() after shm_open() results in memory not being freed after shm_unlink()
我有一个使用共享内存和内存映射文件的应用程序。目标操作系统是 Ubuntu 14.04(64 位)。此发行版上的 Linux 内核版本为 4.4.0。 gcc 的版本是 4.8.4.
直到最近,我一直在使用以下函数调用(按所示顺序)来分配和取消分配共享内存。
shm_open
ftruncate
mmap
/* use shared memory */
munmap
shm_unlink
这种方法存在的问题是它不会检测是否有足够的内存可用于共享内存。当访问共享内存时,应用程序将在稍后崩溃并发出 SIGBUS
信号。
我发现人们遇到了同样的问题 here,他们使用 fallocate()
而不是 ftruncate()
解决了这个问题。如果没有足够的内存可用于请求的大小,fallocate()
将 return 出错。
我在我的应用程序中实现了相同的功能,fallocate()
可以在内存不足时正确检测到这种情况但是,我现在 运行 遇到了另一个问题。
问题是fallocate()
保留的内存在调用shm_unlink()
后没有释放。使用 ftruncate()
.
时这不是问题
考虑以下展示此行为的最小示例 (fallocate.c
)。
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/mman.h>
#include <fcntl.h>
static const char* name = "/test";
static const size_t size = (size_t)4*1024*1024*1024;
int main ()
{
int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, S_IRWXU | S_IRWXG | S_IRWXO);
if (fd == -1) {
printf("shm_open failed (%s)\n", strerror(errno));
return 1;
}
if (fallocate(fd, 0, 0, size)) {
printf("fallocate failed (%s)\n", strerror(errno));
return 1;
}
if (shm_unlink(name)) {
printf("shm_unlink failed (%s)\n", strerror(errno));
return 1;
}
printf("break here to check if memory still used\n");
return 0;
}
我使用了下面的CMakeLists.txt
来编译
add_executable(fallocate fallocate.c)
target_compile_definitions(fallocate PRIVATE _GNU_SOURCE)
target_link_libraries(fallocate PRIVATE rt)
运行 此示例在 gdb
中并在最后一个 printf
语句处中断。您将看到以下行为。
test
文件不再存在于 /dev/shm
中
- 查看
top
输出时,内存仍属于"used"类;它只会在进程终止后移至 "free" 类别
这是预期的行为还是我错误地使用了 API?
编辑:按要求 shm_unlink()
之后的进程地址 space (使用 shm_unlink()
之后的 gets()
来保持进程)
cat /proc/<PID>/status
的输出
Name: fallocate
State: S (sleeping)
Tgid: 12445
Ngid: 0
Pid: 12445
PPid: 26349
TracerPid: 0
Uid: 1001 1001 1001 1001
Gid: 1001 1001 1001 1001
FDSize: 256
Groups: 4 27 108 124 999 1001 1002
NStgid: 12445
NSpid: 12445
NSpgid: 12445
NSsid: 26349
VmPeak: 8628 kB
VmSize: 8460 kB
VmLck: 0 kB
VmPin: 0 kB
VmHWM: 840 kB
VmRSS: 840 kB
VmData: 80 kB
VmStk: 132 kB
VmExe: 4 kB
VmLib: 2052 kB
VmPTE: 36 kB
VmPMD: 12 kB
VmSwap: 0 kB
HugetlbPages: 0 kB
Threads: 1
SigQ: 0/61795
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000000000000
SigCgt: 0000000180000000
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 0000003fffffffff
CapAmb: 0000000000000000
Seccomp: 0
Speculation_Store_Bypass: thread vulnerable
Cpus_allowed: ff
Cpus_allowed_list: 0-7
Mems_allowed: 00000000,00000001
Mems_allowed_list: 0
voluntary_ctxt_switches: 1
nonvoluntary_ctxt_switches: 2
pmap <PID>
的输出
0000000000400000 4K r-x-- fallocate
0000000000600000 4K r---- fallocate
0000000000601000 4K rw--- fallocate
00007f1e92093000 100K r-x-- libpthread-2.19.so
00007f1e920ac000 2044K ----- libpthread-2.19.so
00007f1e922ab000 4K r---- libpthread-2.19.so
00007f1e922ac000 4K rw--- libpthread-2.19.so
00007f1e922ad000 16K rw--- [ anon ]
00007f1e922b1000 1784K r-x-- libc-2.19.so
00007f1e9246f000 2048K ----- libc-2.19.so
00007f1e9266f000 16K r---- libc-2.19.so
00007f1e92673000 8K rw--- libc-2.19.so
00007f1e92675000 20K rw--- [ anon ]
00007f1e9267a000 28K r-x-- librt-2.19.so
00007f1e92681000 2044K ----- librt-2.19.so
00007f1e92880000 4K r---- librt-2.19.so
00007f1e92881000 4K rw--- librt-2.19.so
00007f1e92882000 140K r-x-- ld-2.19.so
00007f1e92a75000 16K rw--- [ anon ]
00007f1e92aa3000 4K rw--- [ anon ]
00007f1e92aa4000 4K r---- ld-2.19.so
00007f1e92aa5000 4K rw--- ld-2.19.so
00007f1e92aa6000 4K rw--- [ anon ]
00007ffe6f72b000 132K rw--- [ stack ]
00007ffe6f7ee000 12K r---- [ anon ]
00007ffe6f7f1000 8K r-x-- [ anon ]
ffffffffff600000 4K r-x-- [ anon ]
total 8464K
shm_unlink
仅删除与内存对象关联的名称。如果有其他东西引用它,它不会删除该对象。您有一个引用内存对象的打开文件描述符。关闭它后,引用计数应该变为零,内存应该被释放。
您没有关闭打开的文件描述符,并且共享内存 "file" 可能在基于 tmpfs
内存的文件系统中(假设 Linux)。
此代码创建一个文件:
int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, S_IRWXU | S_IRWXG | S_IRWXO);
此代码使其变大 (4 GB):
if (fallocate(fd, 0, 0, size)) {
这段代码刚刚从文件系统中取出link:
if (shm_unlink(name)) {
到那时,打开的文件描述符意味着支持文件仍然存在,即使它已从目录中删除了它的名称。 (这就是 "unlink" 的字面意思。)这样的文件实际上不会从文件系统中删除,直到文件的最后一个 link 关闭 - 最后一个 link 是您进程的打开文件描述符。
添加
close( fd );
并检查 close()
调用前后的系统内存使用情况。
我有一个使用共享内存和内存映射文件的应用程序。目标操作系统是 Ubuntu 14.04(64 位)。此发行版上的 Linux 内核版本为 4.4.0。 gcc 的版本是 4.8.4.
直到最近,我一直在使用以下函数调用(按所示顺序)来分配和取消分配共享内存。
shm_open
ftruncate
mmap
/* use shared memory */
munmap
shm_unlink
这种方法存在的问题是它不会检测是否有足够的内存可用于共享内存。当访问共享内存时,应用程序将在稍后崩溃并发出 SIGBUS
信号。
我发现人们遇到了同样的问题 here,他们使用 fallocate()
而不是 ftruncate()
解决了这个问题。如果没有足够的内存可用于请求的大小,fallocate()
将 return 出错。
我在我的应用程序中实现了相同的功能,fallocate()
可以在内存不足时正确检测到这种情况但是,我现在 运行 遇到了另一个问题。
问题是fallocate()
保留的内存在调用shm_unlink()
后没有释放。使用 ftruncate()
.
考虑以下展示此行为的最小示例 (fallocate.c
)。
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/mman.h>
#include <fcntl.h>
static const char* name = "/test";
static const size_t size = (size_t)4*1024*1024*1024;
int main ()
{
int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, S_IRWXU | S_IRWXG | S_IRWXO);
if (fd == -1) {
printf("shm_open failed (%s)\n", strerror(errno));
return 1;
}
if (fallocate(fd, 0, 0, size)) {
printf("fallocate failed (%s)\n", strerror(errno));
return 1;
}
if (shm_unlink(name)) {
printf("shm_unlink failed (%s)\n", strerror(errno));
return 1;
}
printf("break here to check if memory still used\n");
return 0;
}
我使用了下面的CMakeLists.txt
来编译
add_executable(fallocate fallocate.c)
target_compile_definitions(fallocate PRIVATE _GNU_SOURCE)
target_link_libraries(fallocate PRIVATE rt)
运行 此示例在 gdb
中并在最后一个 printf
语句处中断。您将看到以下行为。
test
文件不再存在于/dev/shm
中
- 查看
top
输出时,内存仍属于"used"类;它只会在进程终止后移至 "free" 类别
这是预期的行为还是我错误地使用了 API?
编辑:按要求 shm_unlink()
之后的进程地址 space (使用 shm_unlink()
之后的 gets()
来保持进程)
cat /proc/<PID>/status
Name: fallocate
State: S (sleeping)
Tgid: 12445
Ngid: 0
Pid: 12445
PPid: 26349
TracerPid: 0
Uid: 1001 1001 1001 1001
Gid: 1001 1001 1001 1001
FDSize: 256
Groups: 4 27 108 124 999 1001 1002
NStgid: 12445
NSpid: 12445
NSpgid: 12445
NSsid: 26349
VmPeak: 8628 kB
VmSize: 8460 kB
VmLck: 0 kB
VmPin: 0 kB
VmHWM: 840 kB
VmRSS: 840 kB
VmData: 80 kB
VmStk: 132 kB
VmExe: 4 kB
VmLib: 2052 kB
VmPTE: 36 kB
VmPMD: 12 kB
VmSwap: 0 kB
HugetlbPages: 0 kB
Threads: 1
SigQ: 0/61795
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000000000000
SigCgt: 0000000180000000
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 0000003fffffffff
CapAmb: 0000000000000000
Seccomp: 0
Speculation_Store_Bypass: thread vulnerable
Cpus_allowed: ff
Cpus_allowed_list: 0-7
Mems_allowed: 00000000,00000001
Mems_allowed_list: 0
voluntary_ctxt_switches: 1
nonvoluntary_ctxt_switches: 2
pmap <PID>
0000000000400000 4K r-x-- fallocate
0000000000600000 4K r---- fallocate
0000000000601000 4K rw--- fallocate
00007f1e92093000 100K r-x-- libpthread-2.19.so
00007f1e920ac000 2044K ----- libpthread-2.19.so
00007f1e922ab000 4K r---- libpthread-2.19.so
00007f1e922ac000 4K rw--- libpthread-2.19.so
00007f1e922ad000 16K rw--- [ anon ]
00007f1e922b1000 1784K r-x-- libc-2.19.so
00007f1e9246f000 2048K ----- libc-2.19.so
00007f1e9266f000 16K r---- libc-2.19.so
00007f1e92673000 8K rw--- libc-2.19.so
00007f1e92675000 20K rw--- [ anon ]
00007f1e9267a000 28K r-x-- librt-2.19.so
00007f1e92681000 2044K ----- librt-2.19.so
00007f1e92880000 4K r---- librt-2.19.so
00007f1e92881000 4K rw--- librt-2.19.so
00007f1e92882000 140K r-x-- ld-2.19.so
00007f1e92a75000 16K rw--- [ anon ]
00007f1e92aa3000 4K rw--- [ anon ]
00007f1e92aa4000 4K r---- ld-2.19.so
00007f1e92aa5000 4K rw--- ld-2.19.so
00007f1e92aa6000 4K rw--- [ anon ]
00007ffe6f72b000 132K rw--- [ stack ]
00007ffe6f7ee000 12K r---- [ anon ]
00007ffe6f7f1000 8K r-x-- [ anon ]
ffffffffff600000 4K r-x-- [ anon ]
total 8464K
shm_unlink
仅删除与内存对象关联的名称。如果有其他东西引用它,它不会删除该对象。您有一个引用内存对象的打开文件描述符。关闭它后,引用计数应该变为零,内存应该被释放。
您没有关闭打开的文件描述符,并且共享内存 "file" 可能在基于 tmpfs
内存的文件系统中(假设 Linux)。
此代码创建一个文件:
int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, S_IRWXU | S_IRWXG | S_IRWXO);
此代码使其变大 (4 GB):
if (fallocate(fd, 0, 0, size)) {
这段代码刚刚从文件系统中取出link:
if (shm_unlink(name)) {
到那时,打开的文件描述符意味着支持文件仍然存在,即使它已从目录中删除了它的名称。 (这就是 "unlink" 的字面意思。)这样的文件实际上不会从文件系统中删除,直到文件的最后一个 link 关闭 - 最后一个 link 是您进程的打开文件描述符。
添加
close( fd );
并检查 close()
调用前后的系统内存使用情况。