Как заставить копирование при записи работать с общей памятью в Linux

Я попытался написать небольшое приложение, чтобы познакомиться с концепцией копирования при записи в пользовательском пространстве. Я прочитал ответ MSalters и решил, что он будет работать, только если я начну с mmap'ed файла, чтобы хранить мои данные. Поскольку мне не нужна постоянство на основе файлов, я попытался сделать то же самое с общей памятью. Сначала я mmap запустил и инициализировал shm fd, затем сопоставил вторую копию с MAP_PRIVATE и снова прочитал из нее. Однако простое чтение из него заставляет ядро ​​копировать все, что занимает значительно больше времени и потребляет вдвое больше памяти. Почему не работает КОРОВА?

Вот программа, которую я придумал, чтобы проиллюстрировать поведение:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <assert.h>

static const size_t ARRAYSIZE = 1UL<<30;

void init(int* A)
{
    for (size_t i = 0; i < ARRAYSIZE; ++i)
        A[i] = i;
}

size_t agg(const int* A)
{
    size_t sum = 0;
    for (size_t i = 0; i < ARRAYSIZE; ++i)
        sum += A[i];
    return sum;
}

int main()
{
    assert(sizeof(int) == 4);
    shm_unlink("/cowtest");
    printf("ARRAYSIZE: %lu\n", ARRAYSIZE);
    int fd = shm_open("/cowtest", O_RDWR | O_CREAT | O_TRUNC, 0);
    if (fd == -1)
    {
        perror("Error allocating fd\n");
        return 1;
    }
    if (ftruncate(fd, sizeof(int) * ARRAYSIZE) == -1)
    {
        perror("Error ftruncate\n");
        return 1;
    }
    /* Open shm */
    int* A= (int*)mmap(NULL, sizeof(int) * ARRAYSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (A == (int*)-1)
    {
        perror("Error mapping A to memory\n");
        return 1;
    }
    init(A);

    /* Create cow copy */
    int* Acopy = (int*)mmap(NULL, sizeof(int) * ARRAYSIZE, PROT_READ, MAP_PRIVATE, fd, 0);
    if (Acopy == (int*)-1)
    {
        printf("Error mapping copy from file\n");
        return 1;
    }

    /* Aggregate over A */
    size_t sumA = agg(A);
    size_t expected = (ARRAYSIZE * (ARRAYSIZE - 1)) >> 1;
    assert(expected == sumA);

    /* Aggregate over Acopy */
    size_t sumCopy = agg(Acopy);
    assert(expected == sumCopy);


    shm_unlink("/cowtest");
    printf("Enter to exit\n");
    getchar();
    return 0;
}

Скомпилировал с помощью g++ -O3 -mtune=native -march=native -o shm-min shm-min.cpp -lrt.

Создаваемый им массив содержит 4 ГБ целочисленных значений. Однако прямо перед завершением программы выделяется 8 ГБ общей памяти, и в /proc/<pid>/smaps вы можете видеть, что она фактически сделала полную копию во время операции только для чтения. Понятия не имею, почему это так. Это ошибка ядра? Или я что-то упускаю?

Большое спасибо за любые идеи. Ларс

Изменить Вот соответствующее содержимое /proc/<pid>/smaps в Ubuntu 14.04 (3.13.0-24):

7f3b9b4ae000-7f3c9b4ae000 r--p 00000000 00:14 168154                     /run/shm/cowtest (deleted)
Size:            4194304 kB
Rss:             4194304 kB
Pss:             2097152 kB
Shared_Clean:          0 kB
Shared_Dirty:    4194304 kB
Private_Clean:         0 kB
Private_Dirty:         0 kB
Referenced:      4194304 kB
Anonymous:             0 kB
AnonHugePages:         0 kB
Swap:                  0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Locked:                0 kB
VmFlags: rd mr mw me sd
7f3c9b4ae000-7f3d9b4ae000 rw-s 00000000 00:14 168154                     /run/shm/cowtest (deleted)
Size:            4194304 kB
Rss:             4194304 kB
Pss:             2097152 kB
Shared_Clean:          0 kB
Shared_Dirty:    4194304 kB
Private_Clean:         0 kB
Private_Dirty:         0 kB
Referenced:      4194304 kB
Anonymous:             0 kB
AnonHugePages:         0 kB
Swap:                  0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Locked:                0 kB
VmFlags: rd wr sh mr mw me ms sd

person lekv    schedule 25.06.2014    source источник
comment
Извините, я должен был сказать / proc / ‹pid› / smaps. Я пробовал его как на SLES11 SP2, так и на Ubuntu 14.04, очевидно, оба 64-битные. Оба показали мне поведение копирования.   -  person lekv    schedule 25.06.2014
comment
Я не знаю, почему ядро ​​это делает, но вы можете попробовать создать временный файл, открыв его, а затем немедленно отсоединив его, а затем посмотреть, произойдет ли то же самое.   -  person Spudd86    schedule 16.07.2014


Ответы (1)


Копирования не было. В файле smaps есть подсказка:

Size:            4194304 kB
Rss:             4194304 kB
Pss:             2097152 kB

Видите, как Pss составляет половину реального размера отображаемой области? Это потому, что он делится на два использования (Pss = пропорциональный общий размер). То есть у вас есть один и тот же файл, дважды сопоставленный с разными диапазонами виртуальной памяти, но лежащие в основе физические страницы одинаковы для обоих сопоставлений.

Чтобы выяснить физические адреса соответствующих страниц, вы можете использовать инструмент здесь. Сохраните его как page-types.c, запустите make page-types, а затем ./page-types -p <pid> -l -N. Вы увидите, что разные виртуальные адреса (в первом столбце) соответствуют одним и тем же физическим страницам (во втором столбце).

Если вы добавите бит разрешения PROT_WRITE для второго сопоставления и вызовете init(Acopy), вы увидите, что Pss перескакивает на 4 ГБ, а физические адреса соответствующих страниц больше не совпадают.

TL; DR COW работает.

person Community    schedule 15.07.2014
comment
Выглядит правильно (и интересно) ... в пути за наградой. Можете ли вы уточнить выяснить физические адреса соответствующих страниц? (Возможно, ответив stackoverflow.com/questions/24768199/) - person quantdev; 16.07.2014