Подтвердить что ты не робот

Выделение копии в памяти записи в процессе

У меня есть сегмент памяти, который был получен через mmap с MAP_ANONYMOUS.

Как я могу выделить второй сегмент памяти того же размера, который ссылается на первый, и сделать как скопированную запись в Linux (рабочий Linux 2.6.36 на данный момент)?

Я хочу иметь тот же эффект, что и fork, без создания нового процесса. Я хочу, чтобы новое отображение оставалось в том же процессе.

Весь процесс должен повторяться как на страницах происхождения, так и на копии (как будто родительский и дочерний будут продолжать fork).

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

Что я пробовал:

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

Затем используйте libsigsegv для перехвата попыток записи, вручную создайте копию страницы, а затем mprotect для чтения и записи.

Есть трюк, но очень грязный. Я по существу реализую свою собственную виртуальную машину.

Печально mmap ing /proc/self/mem не поддерживается в текущей Linux, в противном случае отображение MAP_PRIVATE могло бы сделать трюк.

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

В качестве примечания: Я нашел подходящую механику в Mach VM.

Следующий код компилируется в моей OS X 10.7.5 и имеет ожидаемое поведение: Darwin 11.4.2 Darwin Kernel Version 11.4.2: Thu Aug 23 16:25:48 PDT 2012; root:xnu-1699.32.7~1/RELEASE_X86_64 x86_64 i386

gcc version 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.11.00)

#include <sys/mman.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#ifdef __MACH__
#include <mach/mach.h>
#endif


int main() {

    mach_port_t this_task = mach_task_self();

    struct {
        size_t rss;
        size_t vms;
        void * a1;
        void * a2;
        char p1;
        char p2;
        } results[3];

    size_t length = sysconf(_SC_PAGE_SIZE);
    vm_address_t first_address;
    kern_return_t result = vm_allocate(this_task, &first_address, length, VM_FLAGS_ANYWHERE);

    if ( result != ERR_SUCCESS ) {
        fprintf(stderr, "Error allocating initial 0x%zu memory.\n", length);
           return -1;
    }

    char * first_address_p = first_address;
    char * mirror_address_p;
    *first_address_p = 'a';

    struct task_basic_info t_info;
    mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT;

    task_info(this_task, TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count);

    task_info(this_task, TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count);
    results[0].rss = t_info.resident_size;
    results[0].vms = t_info.virtual_size;
    results[0].a1 = first_address_p;
    results[0].p1 = *first_address_p;

    vm_address_t mirrorAddress;
    vm_prot_t cur_prot, max_prot;
    result = vm_remap(this_task,
                      &mirrorAddress,   // mirror target
                      length,    // size of mirror
                      0,                 // auto alignment
                      1,                 // remap anywhere
                      this_task,  // same task
                      first_address,     // mirror source
                      1,                 // Copy
                      &cur_prot,         // unused protection struct
                      &max_prot,         // unused protection struct
                      VM_INHERIT_COPY);

    if ( result != ERR_SUCCESS ) {
        perror("vm_remap");
        fprintf(stderr, "Error remapping pages.\n");
              return -1;
    }

    mirror_address_p = mirrorAddress;

    task_info(this_task, TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count);
    results[1].rss = t_info.resident_size;
    results[1].vms = t_info.virtual_size;
    results[1].a1 = first_address_p;
    results[1].p1 = *first_address_p;
    results[1].a2 = mirror_address_p;
    results[1].p2 = *mirror_address_p;

    *mirror_address_p = 'b';

    task_info(this_task, TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count);
    results[2].rss = t_info.resident_size;
    results[2].vms = t_info.virtual_size;
    results[2].a1 = first_address_p;
    results[2].p1 = *first_address_p;
    results[2].a2 = mirror_address_p;
    results[2].p2 = *mirror_address_p;

    printf("Allocated one page of memory and wrote to it.\n");
    printf("*%p = '%c'\nRSS: %zu\tVMS: %zu\n",results[0].a1, results[0].p1, results[0].rss, results[0].vms);
    printf("Cloned that page copy-on-write.\n");
    printf("*%p = '%c'\n*%p = '%c'\nRSS: %zu\tVMS: %zu\n",results[1].a1, results[1].p1,results[1].a2, results[1].p2, results[1].rss, results[1].vms);
    printf("Wrote to the new cloned page.\n");
    printf("*%p = '%c'\n*%p = '%c'\nRSS: %zu\tVMS: %zu\n",results[2].a1, results[2].p1,results[2].a2, results[2].p2, results[2].rss, results[2].vms);

    return 0;
}

Мне нужен тот же эффект в Linux.

4b9b3361

Ответ 1

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

Поддержка прямого ядра (или его отсутствие): путем изменения/добавления модуля это должно быть возможно. Однако нет простого способа настроить новую область COW из существующей. Код, используемый fork (copy_page_rank), копирует vm_area_struct из одного пространства процессов/виртуального адреса в другое (новое), но предполагает, что адрес нового сопоставления совпадает с адресом старого. Если вы хотите реализовать функцию "переназначения", функция должна быть изменена/дублирована для копирования vm_area_struct с преобразованием адреса.

BTRFS. Я думал об использовании COW на btrfs для этого. Я написал простую программу, сопоставляющую два файла reflink-ed и попытался их сопоставить. Однако просмотр информации о странице с помощью /proc/self/pagemap показывает, что два экземпляра файла не используют одни и те же страницы кеша. (По крайней мере, если мой тест не соответствует действительности). Таким образом, вы не выиграете, сделав это. Физические страницы одних и тех же данных не будут использоваться для разных экземпляров.

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

void* map_file(const char* file) {
  struct stat file_stat;
  int fd = open(file, O_RDWR);
  assert(fd>=0);
  int temp = fstat(fd, &file_stat);
  assert(temp==0);
  void* res = mmap(NULL, file_stat.st_size, PROT_READ, MAP_SHARED, fd, 0);
  assert(res!=MAP_FAILED);
  close(fd);
  return res;
}

static int pagemap_fd = -1;

uint64_t pagemap_info(void* p) {
  if(pagemap_fd<0) {
    pagemap_fd = open("/proc/self/pagemap", O_RDONLY);
    if(pagemap_fd<0) {
      perror("open pagemap");
      exit(1);
    }
  }
  size_t page = ((uintptr_t) p) / getpagesize();
  int temp = lseek(pagemap_fd, page*sizeof(uint64_t), SEEK_SET);
  if(temp==(off_t) -1) {
    perror("lseek");
    exit(1);
  }
  uint64_t value;
  temp = read(pagemap_fd, (char*)&value, sizeof(uint64_t));
  if(temp<0) {
    perror("lseek");
    exit(1);
  }
  if(temp!=sizeof(uint64_t)) {
    exit(1);
  }
  return value;
}

int main(int argc, char** argv) {

  char* a = (char*) map_file(argv[1]);
  char* b = (char*) map_file(argv[2]);

  int fd = open("/proc/self/pagemap", O_RDONLY);
  assert(fd>=0);

  int x = a[0];  
  uint64_t info1 = pagemap_info(a);

  int y = b[0];
  uint64_t info2 = pagemap_info(b);

  fprintf(stderr, "%" PRIx64 " %" PRIx64 "\n", info1, info2);

  assert(info1==info2);

  return 0;
}

mprotect + mmap анонимные страницы. Это не работает в вашем случае, но решение заключается в использовании файла MAP_SHARED для моей основной области памяти. В моментальном снимке файл отображается в другом месте, и оба экземпляра защищены. При записи, анонимной странице, отображаемой в моментальном снимке, данные копируются на этой новой странице, а исходная страница не защищена. Однако это решение не работает в вашем случае, так как вы не сможете повторить процесс в снимке (потому что это не обычная область MAP_SHARED, а MAP_SHARED с некоторыми страницами MAP_ANONYMOUS. Кроме того, он не масштабируется с количеством копий: если у меня много копий COW, мне придется повторить один и тот же процесс для каждой копии, и эта страница не будет дублироваться для копий. И я не могу отобразить анонимную страницу в исходной области, так как невозможно будет отобразить анонимные страницы в копиях. Это решение не работает в любом случае.

mprotect + remap_file_pages: похоже, единственный способ сделать это, не касаясь ядра Linux. Недостатком этого является то, что в общем случае вам, вероятно, придется сделать scyscall_file_page для каждой страницы при копировании: возможно, это не так эффективно, чтобы сделать много системных вызовов. При дедупликации разделяемой страницы вам необходимо, по крайней мере,: remap_file_page создать новую/бесплатную страницу для новой записи на страницу, m-un-protect для новой страницы. Необходимо ссылаться на каждую страницу.

Я не думаю, что подходы, основанные на mprotect(), будут очень хорошо масштабироваться (если вы справитесь с такой большой памятью). В Linux mprotect() не работает с гранулярностью страницы памяти, а с детализацией vm_area_struct (записи, которые вы находите в /prod//maps ). Выполнение mprotect() при гранулярности страницы памяти приведет к постоянному разделению ядра и объединению vm_area_struct:

  • у вас будет очень mm_struct;

  • поиск vm_area_struct (который используется для журнала операций, связанных с виртуальной памятью) находится на O(log #vm_area_struct), но он может по-прежнему оказывать отрицательное влияние на производительность;

  • потребление памяти для этих структур.

Для этой причины, syscall для remap_file_pages() был создан [http://lwn.net/Articles/24468/], чтобы сделать отображение нелинейной памяти файл. Для этого с помощью mmap требуется журнал vm_area_struct. Я не думаю, что они были предназначены для сопоставления гранулярности страницы: remap_file_pages() не очень оптимизирован для этого прецедента, так как для него потребуется syscall на страницу.

Я думаю, что единственным жизнеспособным решением является позволить ядру сделать это. Это можно сделать в пользовательском пространстве с помощью remap_file_pages, но, вероятно, это будет довольно неэффективно, поскольку моментальный снимок будет генерировать потребность в количестве syscalls, пропорциональном числу страниц. Вариант remap_file_pages может сделать трюк.

Этот подход, однако, дублирует логику страницы ядра. Я склонен думать, что мы должны позволить ядру сделать это. В целом, реализация в ядре, по-видимому, является лучшим решением. Для тех, кто знает эту часть ядра, это должно быть довольно легко сделать.

КСМ(Объединение Samepage ядра): есть что-то, что может сделать ядро. Он может попытаться дедуплицировать страницы. Вам все равно придется копировать данные, но ядро ​​должно быть способно их объединить. Вам нужно снять новую анонимную область для своей копии, скопировать ее вручную с помощью memcpy и madvide(start, end, MADV_MERGEABLE) областей. Вам нужно включить KSM (в корневом каталоге):

echo 1 > /sys/kernel/mm/ksm/run
echo 10000 > /sys/kernel/mm/ksm/pages_to_scan

Он работает, он не очень хорошо работает с моей рабочей нагрузкой, но, вероятно, потому, что в конце страницы не так много общего. Недостатком является то, что вам все равно придется делать копию (у вас не может быть эффективной COW), а затем ядро ​​будет не объединять страницу. Он будет генерировать ошибки страницы и кеша при копировании, поток демонов KSM будет потреблять много CPU (у меня есть процессор, работающий на A00% для всей симуляции) и, вероятно, потребляет кэш журнала. Таким образом, вы не выиграете время при копировании, но можете получить некоторую память. Если ваша основная мотивация заключается в том, чтобы использовать меньшую память в долгосрочной перспективе, и вам все равно, чтобы избежать копирования, это решение может сработать для вас.

Ответ 2

Хм... вы можете создать файл в /dev/shm с помощью MAP_SHARED, записать в него, а затем снова открыть его дважды с помощью MAP_PRIVATE.