Почему мы можем выделить массив размером 1 ПБ (10 ^ 15) и получить доступ к последнему элементу, но не можем его освободить?

Как известно: http://linux.die.net/man/3/malloc

По умолчанию Linux следует оптимистической стратегии распределения памяти. Это означает, что когда malloc () возвращает значение, отличное от NULL, нет гарантии, что память действительно доступна. В случае, если окажется, что системе не хватает памяти, один или несколько процессов будут остановлены убийцей OOM.

И мы можем успешно выделить 1 петабайт VMA (область виртуальной памяти), используя malloc(petabyte);: http://ideone.com/1yskmB < / а>

#include <stdio.h>
#include <stdlib.h>

int main(void) {

    long long int petabyte = 1024LL * 1024LL * 1024LL * 1024LL * 1024LL;    // 2^50
    printf("petabyte %lld \n", petabyte);

    volatile char *ptr = (volatile char *)malloc(petabyte);
    printf("malloc() - success, ptr = %p \n", ptr);

    ptr[petabyte - 1LL] = 10;
    printf("ptr[petabyte - 1] = 10; - success \n");

    printf("ptr[petabyte - 1] = %d \n", (int)(ptr[petabyte - 1LL]));

    free((void*)ptr);   // why the error is here?
    //printf("free() - success \n");

    return 0;
}

Результат:

Error   time: 0 memory: 2292 signal:6
petabyte 1125899906842624 
malloc() - success, ptr = 0x823e008 
ptr[petabyte - 1] = 10; - success 
ptr[petabyte - 1] = 10 

И мы можем успешно получить доступ (сохранить / загрузить) к последнему члену петабайта, но почему мы получаем ошибку на free((void*)ptr);?

Примечание. https://en.wikipedia.org/wiki/Petabyte

  • 1000 ^ 5 ПБ петабайт
  • 1024 ^ 5 ПиБ пебибайт - Я использую

На самом деле, если мы хотим выделить больше, чем RAM + swap и обойти ограничение overcommit_memory, тогда мы можем выделить память, используя _ 6_ в Windows или _ 7_ в Linux, например:


person Alex    schedule 25.07.2016    source источник
comment
По какой причине вы квалифицируетесь как volatile? Скорее всего, это чушь и не поддается никаким оптимизациям.   -  person too honest for this site    schedule 25.07.2016
comment
@Olaf volatile, чтобы избежать оптимизации, при которой нет доступа к памяти, и будет работать в регистрах. Чтобы показать, что мы действительно работаем с памятью.   -  person Alex    schedule 25.07.2016
comment
Тем не менее, просто придирка, (int) приведение не требуется в ... %d \n", (int)(ptr[petabyte - 1LL]));, это неявно.   -  person Sourav Ghosh    schedule 25.07.2016
comment
Нитпик. Ваш petabyte комментарий неверен. Это не 10 ^ 15. Это 2 ^ 50. (Если правильно посчитал биты).   -  person Zan Lynx    schedule 25.07.2016
comment
Распределение памяти ничего не знает о ваших структурах данных. Когда вы пишете в последний байт массива, фактически выделяется только последняя страница памяти.   -  person stark    schedule 25.07.2016
comment
@Alex: Это совершенно бесполезно, так как будет к чему обратиться, если malloc не вернет нулевой указатель.   -  person too honest for this site    schedule 25.07.2016
comment
Включите предупреждения, особенно -Wconversion.   -  person too honest for this site    schedule 25.07.2016
comment
Если вы раскомментируете free, вы заметите, что он вылетает. Записывая в ptr[-1], вы уничтожили хранилища данных malloc (в этой реализации) перед выделенным блоком памяти (размером 0 байт), и, таким образом, вызов free((void*)ptr) завершился аварийно.   -  person Daniel Fischer    schedule 27.07.2016
comment
не приводите malloc к C   -  person phuclv    schedule 26.04.2017


Ответы (2)


Я считаю, что ваша проблема в том, что malloc() не принимает long long int в качестве аргумента. Требуется size_t.

После изменения кода для определения petabyte как size_t ваша программа больше не возвращает указатель из malloc. Вместо этого он терпит неудачу.

Я думаю, что ваш параметр доступа к массиву от петабайт-1 до 10 записывает далеко-далеко за пределы возвращаемого массива malloc. Это крах.

При вызове функций всегда используйте правильные типы данных.

Используйте этот код, чтобы узнать, что происходит:

long long int petabyte = 1024LL * 1024LL * 1024LL * 1024LL * 1024LL;
size_t ptest = petabyte;
printf("petabyte %lld %lu\n", petabyte, ptest);

Если я компилирую в 64-битном режиме, он не может выделить 1 петабайт. Если я компилирую в 32-битном режиме, он успешно блокирует 0 байтов, а затем пытается писать за пределами своего массива и segfaults.

person Zan Lynx    schedule 25.07.2016
comment
Не согласен с ... ваша программа больше не возвращает указатель из malloc. Вместо этого он терпит неудачу. Если 1024LL * 1024LL * 1024LL * 1024LL * 1024LL превысит SIZE_MAX, ожидаемое значение size_t petabyte будет 0. Разумеется, malloc(0) не завершился ошибкой. malloc(0) возвращает указатель или NULL, и оба являются допустимыми значениями для указателя и не указывают на OOM. Ошибка заключается в более позднем коде, который пытается отменить ссылку на ptr. - person chux - Reinstate Monica; 25.07.2016
comment
@chux: На моей 64-битной машине он отказывается выделить петабайт, независимо от того, установлено ли значение overcommit_memory на 0 или 1. Что касается ожидаемого значения, равного 0, почему вы так думаете? 2 ^ 50 оборачивается вокруг 2 ^ 32, и я не понимаю, как это когда-либо становится равным нулю. Помните, что перенос целого числа со знаком является неопределенным поведением в C. - person Zan Lynx; 25.07.2016
comment
Это не подписанный целочисленный перенос. size_t - беззнаковый тип. Назначение 2 ^ 50 для size_t хорошо определено независимо от его разрядности. Если size_t является 50-битным или более узким типом, size_t petabyte = 1024LL ... будет 0. - person chux - Reinstate Monica; 25.07.2016
comment
@chux Когда знаковое целое число размера 64 принудительно превращается в 32-битное значение без знака, оно завершается. Может быть. Это не определено. Ключевые слова для поиска: c integer narrowing conversion - person Zan Lynx; 25.07.2016
comment
@chux: Хорошо, теперь я понимаю, о чем ты говоришь. Да, на обычном оборудовании x86 он выглядит как ноль. - person Zan Lynx; 25.07.2016
comment
6.3.1.3 Целые числа со знаком и без знака 2, если новый тип является беззнаковым, значение преобразуется путем многократного добавления или вычитания на единицу большего, чем максимальное значение, которое может быть представлено в новом типе, до тех пор, пока значение не окажется в диапазоне нового типа. Это не подписанное переполнение. - person chux - Reinstate Monica; 25.07.2016

(Это не ответ, а важное замечание для всех, кто работает с большими наборами данных в Linux)

Это не то, как вы используете очень большие - порядка терабайт и выше - наборы данных в Linux.

Когда вы используете malloc() или mmap() (библиотека GNU C будет использовать mmap() внутри для больших распределений в любом случае) для выделения частной памяти, ядро ​​ограничивает размер размером (теоретически) доступной оперативной памяти и SWAP, умноженного на коэффициент переопределения.

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

Чтобы обойти это, мы создаем файл, который будет использоваться как «своп» для данных, и сопоставляем его с помощью флага MAP_NORESERVE. Это говорит ядру, что мы не хотим использовать стандартную подкачку для этого сопоставления. (Это также означает, что если по какой-либо причине ядро ​​не может получить новую страницу поддержки, приложение получит сигнал SIGSEGV и умрет.)

Большинство файловых систем в Linux поддерживают разреженные файлы. Это означает, что у вас может быть файл размером в терабайт, который занимает всего несколько килобайт фактического дискового пространства, если большая часть его содержимого не записана (и, следовательно, является нулями). (Создать разреженные файлы легко; вы просто пропускаете длинные серии нулей. Пробить дырку сложнее, поскольку запись нулей требует обычного дискового пространства, вместо этого необходимо использовать другие методы.)

Вот пример программы mapfile.c, которую можно использовать для исследования:

#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
    const char    *filename;
    size_t         page, size;
    int            fd, result;
    unsigned char *data;
    char           dummy;

    if (argc != 3 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
        fprintf(stderr, "       %s MAPFILE BYTES\n", argv[0]);
        fprintf(stderr, "\n");
        return EXIT_FAILURE;
    }

    page = sysconf(_SC_PAGESIZE);
    if (page < 1) {
        fprintf(stderr, "Unknown page size.\n");
        return EXIT_FAILURE;
    }

    filename = argv[1];
    if (!filename || !*filename) {
        fprintf(stderr, "No map file name specified.\n");
        return EXIT_FAILURE;
    }

    if (sscanf(argv[2], " %zu %c", &size, &dummy) != 1 || size < 3) {
        fprintf(stderr, "%s: Invalid size in bytes.\n", argv[2]);
        return EXIT_FAILURE;
    }

    if (size % page) {
        /* Round up to next multiple of page */
        size += page - (size % page);
        fprintf(stderr, "Adjusted to %zu pages (%zu bytes)\n", size / page, size);
    }

    do {
        fd = open(filename, O_RDWR | O_CREAT | O_EXCL, 0600);
    } while (fd == -1 && errno == EINTR);
    if (fd == -1) {
        fprintf(stderr, "Cannot create %s: %s.\n", filename, strerror(errno));
        return EXIT_FAILURE;
    }

    do {
        result = ftruncate(fd, (off_t)size);
    } while (result == -1 && errno == EINTR);
    if (result == -1) {
        fprintf(stderr, "Cannot resize %s: %s.\n", filename, strerror(errno));
        unlink(filename);
        close(fd);
        return EXIT_FAILURE;
    }

    data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_NORESERVE, fd, 0);
    if ((void *)data == MAP_FAILED) {
        fprintf(stderr, "Mapping failed: %s.\n", strerror(errno));
        unlink(filename);
        close(fd);
        return EXIT_FAILURE;
    }

    fprintf(stderr, "Created file '%s' to back a %zu-byte mapping at %p successfully.\n", filename, size, (void *)data);

    fflush(stdout);
    fflush(stderr);

    data[0] = 1U;
    data[1] = 255U;

    data[size-2] = 254U;
    data[size-1] = 127U;

    fprintf(stderr, "Mapping accessed successfully.\n");

    munmap(data, size);
    unlink(filename);
    close(fd);

    fprintf(stderr, "All done.\n");
    return EXIT_SUCCESS;
}

Скомпилируйте его, например,

gcc -Wall -O2 mapfile.c -o mapfile

и запустите его без аргументов, чтобы увидеть использование.

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

На моей машине с SMP-ядром 4.2.0-42-generic # 49 ~ 14.04.1-Ubuntu на x86-64 в файловой системе ext4 я не могу отобразить полный петабайт. Максимальный размер составляет около 17 592 186 040 320 байт (2 44 -4096) - 16 ТиБ - 4 КиБ -, что составляет 4 294 967 296 страниц размером 4096 байт (2 32 страницы по 2 12 байтов каждый). Похоже, что ограничение наложено файловой системой ext4, поскольку сбой происходит в вызове ftruncate() (до того, как будет выполнено сопоставление).

(На tmpfs я могу получить около 140 187 732 541 440 байтов или 127,5 ТиБ, но это просто уловка, потому что tmpfs поддерживается ОЗУ и свопингом, а не фактическим устройством хранения. Так что это не вариант для реальной работы с большими данными. напомнить xfs будет работать для действительно больших файлов, но я слишком ленив, чтобы форматировать раздел или даже искать спецификации; я не думаю, что кто-то на самом деле прочитает этот пост, хотя информация здесь была для меня очень полезна за последнее десятилетие или около того.)

Вот как этот пример выглядит на моей машине (с использованием оболочки Bash):

$ ./mapfile datafile $[(1<<44)-4096]
Created file 'datafile' to back a 17592186040320-byte mapping at 0x6f3d3e717000 successfully.
Mapping accessed successfully.
All done.

.

person Nominal Animal    schedule 25.07.2016
comment
Спасибо! Можем ли мы просто сопоставить /dev/zero (или использовать MAP_ANONYMOUS) вместо datafile, чтобы выделить незадействованный массив и обойти ограничения файловой системы: 2 ^ 32 страницы и поддерживающие разреженные файлы? Как сказано там: stackoverflow.com/questions/2782628/ - person Alex; 25.07.2016
comment
I don't think anybody will actually read this post Я прочитал весь пост и кое-что узнал. - person Aloha; 25.07.2016
comment
@Alex: Нет, не совсем. В конце концов, идея состоит в том, чтобы вывести page out страницы ядра, когда к ним в последнее время не обращались, потому что у нас недостаточно оперативной памяти для всего набора данных. Вместо этого вы можете просто использовать mmap(NULL, bytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_NORESERVE, -1, 0) для получения частных анонимных страниц, вплоть до того количества, которое может поддерживать ядро ​​(если не ограничено для текущего процесса). Если у вас закончится ОЗУ, вы получите SIGSEGV, потому что некуда удалить неиспользуемые страницы, чтобы освободить место для новых. - person Nominal Animal; 25.07.2016
comment
Да, вы правы, поэтому мы сможем выделить 16 ТиБ памяти, которая намного больше, чем размер ОЗУ, и когда она заполнится, то она поменяется (в файл данных) вместо падения всего приложения. Также мы можем выделить еще больше памяти - 127 ТиБ, если используются флаги MAP_NORESERVE | MAP_PRIVATE | MAP_ANONYMOUS и fd=-1, но когда ОЗУ заполнится, приложение выйдет из строя с SIGSEGV: coliru.stacked-crooked.com/a/c69ce8ad7fbe4560 - person Alex; 25.07.2016
comment
@Alex: Совершенно верно. Точные ограничения действительно различаются в зависимости от архитектуры Linux, файловых систем и версий ядра и даже могут быть установлены для каждого процесса (ulimit, часто устанавливается в модуле PAM во время входа в систему. Вы можете проверить их в Bash, используя ulimit -a - ulimit, встроенную функцию в Bash, а не отдельный двоичный файл.) - person Nominal Animal; 26.07.2016