Как да накарам копирането при запис да работи върху споделена памет на linux

Опитах се да напиша малко приложение, за да се запозная с концепцията за копиране при запис в потребителското пространство. Прочетох отговора от MSalters и прецених, че ще работи само ако започна с mmap файл за съхранявам данните си. Тъй като не се нуждая от постоянство, базирано на файлове, опитах да направя същото със споделената памет. Първо направих mmap и инициализирах shm fd, след това картографирах второ копие с MAP_PRIVATE и прочетох от него отново. Самото четене от него обаче кара ядрото да копира цялото нещо, което отнема значително повече време и изяжда два пъти повече памет. Защо не прави COW?

Ето програмата, която измислих, за да илюстрирам поведението:

#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 GB цели числа. Непосредствено преди прекратяване на програмата обаче разпределя 8 GB споделена памет и в /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 GB и физическите адреси на съответните страници вече не са същите.

TL;DR COW работи.

person Community    schedule 15.07.2014
comment
Изглежда правилно (и интересно)... на път за наградата. Можете ли да разработите разберете физическите адреси на съответните страници? (Евентуално като отговорите на stackoverflow.com/questions/24768199/ ) - person quantdev; 16.07.2014