【问题标题】:Using fallocate() after shm_open() results in memory not being freed after shm_unlink()在 shm_open() 之后使用 fallocate() 会导致在 shm_unlink() 之后内存没有被释放
【发布时间】:2019-06-05 08:25:39
【问题描述】:

我有一个应用程序将共享内存与内存映射文件一起使用。目标操作系统是 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() 将返回错误。

我在我的应用程序中实现了相同的功能,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 输出时,内存仍处于“已使用”类别;一旦进程终止,它只会移动到“免费”类别

这是预期的行为还是我错误地使用了 API?


编辑:根据请求shm_unlink()之后的进程地址空间(使用shm_unlink()之后的gets()来保存进程)

cat /proc/&lt;PID&gt;/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 &lt;PID&gt;的输出

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

【问题讨论】:

  • 您发布的示例是否完整?您实际上并没有使用mmap() 为进程分配任何内存,也没有使用munmap() 删除它。请参阅pubs.opengroup.org/onlinepubs/009604599/functions/shm_open.html 处的示例
  • 我省略了调用mmap()munmap() 以使示例尽可能简单。我看到的行为在没有调用这些函数的情况下发生。在实际应用中,我确实在使用它们。
  • 调用shm_unlink()前后的进程地址空间映射是什么样子的?
  • 我在原始问题中添加了一些附加信息。

标签: c linux shared-memory fallocate


【解决方案1】:

您没有关闭打开的文件描述符,共享内存“文件”可能位于基于 tmpfs-memory 的文件系统中(假设是 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)) {

这段代码只是将它从文件系统中取消链接:

if (shm_unlink(name)) {

此时,打开的文件描述符意味着支持文件仍然存在,即使它已从与其名称所在的目录中删除。 (这就是“取消链接”的字面意思。)在关闭文件的最后一个链接之前,不会真正从文件系统中删除此类文件 - 最后一个链接是您的进程的打开文件描述符。

添加

close( fd );

并检查close() 调用前后的系统内存使用情况。

【讨论】:

  • 我通过实验验证了这一点,谢谢!在调用shm_unlink() 之后调用close() 后内存被释放。这似乎是不直观的,原因有两个。首先,shm_open() 创建文件描述符,所以我希望shm_unlink()(对应于shm_open() 的清理函数)撤消shm_open() 所做的一切。其次,shm_open() 的文档中没有提到这种行为。在shm_unlink() 之后调用close() 总是安全的吗?
  • 这是一个实际使用close()shm_unlink() 的教程(按顺序):www3.physnet.uni-hamburg.de/physnet/Tru64-Unix/HTML/APS33DTE/…
  • 我认为 shm_open() 的清理函数将被称为 shm_close(),它仅以 close() 的形式存在。如果 shm_unlink() 不被视为 shm_open() 的对应物,它可能不再那么不直观了。
  • 由于 shm_unlink() 不对文件描述符进行操作,因此在 shm_open() 创建后的任何时间调用它都是安全的 - 无论是在 shm_unlink() 之前或之后,甚至是 close()。跨度>
【解决方案2】:

shm_unlink 仅删除与内存对象关联的名称。如果有其他东西引用它,它不会删除该对象。您有一个引用内存对象的打开文件描述符。关闭它后,引用计数应该为零,并且应该释放内存。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-24
    • 1970-01-01
    • 1970-01-01
    • 2013-05-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多