Использование fallocate () после shm_open () приводит к тому, что память не освобождается после shm_unlink () - PullRequest
0 голосов
/ 10 января 2019

У меня есть приложение, которое использует разделяемую память с файлами сопоставленной памяти. Целевой операционной системой является Ubuntu 14.04 (64-разрядная версия). Ядро Linux в этом дистрибутиве имеет версию 4.4.0. gcc - версия 4.8.4.

До недавнего времени я использовал следующие вызовы функций (в указанном порядке) для выделения и освобождения общей памяти.

shm_open
ftruncate
mmap
/* use shared memory */
munmap
shm_unlink

У этого подхода есть проблема, заключающаяся в том, что он не обнаруживает, достаточно ли памяти для общей памяти. Приложение будет аварийно завершать работу позднее с сигналом SIGBUS при доступе к общей памяти.

Я обнаружил, что у людей была такая же проблема здесь , и они решили ее, используя 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() (используя gets() после shm_unlink() для удержания процесса)

Выход 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

Ответы [ 2 ]

0 голосов
/ 10 января 2019

Вы не закрываете дескриптор открытого файла, и, скорее всего, «файл» в общей памяти находится в файловой системе на основе tmpfs памяти (предполагается, что Linux).

Этот код создает файл:

int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, S_IRWXU | S_IRWXG | S_IRWXO);

Этот код делает его большим (4 ГБ):

if (fallocate(fd, 0, 0, size)) {

Этот код просто отвязывает его от файловой системы:

if (shm_unlink(name)) {

В этот момент дескриптор открытого файла означает, что файл резервной копии все еще существует, даже если он был удален из каталога с его именем. (Это буквально означает «unlink».) Такой файл не будет фактически удален из файловой системы, пока не будет закрыта последняя ссылка на файл - и эта последняя ссылка является дескриптором открытого файла вашего процесса.

Добавить

close( fd );

и проверьте использование системной памяти до и после вызова close().

0 голосов
/ 10 января 2019

shm_unlink удаляет только имя, связанное с объектом памяти. Это не удалит объект, если есть другие вещи, ссылающиеся на него. У вас есть открытый файловый дескриптор, ссылающийся на объект памяти. После того, как вы закроете его, refcount должен достигнуть нуля, и память должна быть освобождена.

...