24 votos

La asignación de copia en escritura de la memoria dentro de un proceso

Tengo un segmento de memoria que se obtuvo a través de mmap con MAP_ANONYMOUS.

¿Cómo puedo asignar un segundo segmento de memoria de la misma dimensión que hace referencia a la primera y hacer la copia de escritura en Linux (de Trabajo Linux 2.6.36 en el momento)?

Quiero tener exactamente el mismo efecto como fork, sólo que sin la creación de un nuevo proceso. Quiero la nueva asignación para permanecer en el mismo proceso.

Todo el proceso tiene que ser repetible en el origen y copia de las páginas (como si los padres y el niño seguirá fork).

La razón por la que no desea asignar una copia directa de todo el segmento es porque son varios gigabytes grande y no quiero utilizar la memoria de lo que podría ser la copia por escritura compartida.

Lo que he intentado:

mmap el segmento compartido, anónimo. En la duplicación mprotect en sólo lectura y crear una segunda asignación con remap_file_pages también de sólo lectura.

A continuación, utilice libsigsegv a interceptar escribir intentos de realizar manualmente una copia de la página y, a continuación, mprotect tanto para lectura y escritura.

Hace el truco, pero es muy sucio. Yo soy esencialmente la aplicación de mi propia máquina virtual.

Lamentablemente mmaping /proc/self/mem no está soportado en Linux actual, de lo contrario un MAP_PRIVATE de asignación podría hacer el truco.

Copy-on-write mecánica es la parte de la VM Linux, tiene que haber una manera de hacer uso de ellos sin la creación de un nuevo proceso.

Como una nota: He encontrado el adecuado mecánica en el Mach VM.

El siguiente código se compila en mi OS X 10.7.5 y tiene el comportamiento esperado: 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;
}

Quiero el mismo efecto en Linux.

3voto

ysdx Puntos 2444

Traté de conseguir la misma cosa (de hecho, sus vistosas más sencillo, ya que sólo se necesita para tomar instantáneas en vivo de una región, no necesito hacer copias de las copias). No he encontrado una buena solución para esto.

Directo del kernel de apoyo (o la falta de ella): Mediante la modificación/adición de un módulo debe ser posible lograr esto. Sin embargo no hay una manera sencilla de configurar un nuevo VACA de la región a partir de uno existente. El código utilizado por la horquilla (copy_page_rank) copiar un vm_area_struct de un proceso virtual de direcciones del espacio a otro (uno nuevo), pero se supone que la dirección de la nueva asignación es la misma que la dirección de la antigua. Si uno desea implementar una "reasignación" característica, la función debe ser modificado/duplicar para copiar un vm_area_struct con la dirección de la traducción.

BTRFS: yo thaught de uso de VACA en btrfs para esto. Escribí un simple programa de asignación de dos reflink-ed archivos y trató de mapa. Sin embargo, buscando en la página de información de /proc/self/pagemap muestra las dos instancias del archivo no comparten la misma memoria caché de páginas. (Al menos, a menos que mi prueba es incorrecto). Así que usted no va a ganar mucho por soing este. Las páginas físicas de los mismos datos no serán compartidos entre las distintas instancias.

#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 anónimo páginas: no funciona en tu caso, pero una solución es utilizar un MAP_SHARED archivo de mi memoria principal de la región. En una foto, el archivo se asigna a otro lado y ambos casos son mprotected. En una escritura, un anónimo en la página asignada en la foto, los datos se copian en la nueva página y la página original está desprotegido. Sin embargo esta solución no funciona en su caso, como usted no será capaz de repetir el proceso en la foto (porque no es un simple MAP_SHARED área pero un MAP_SHARED con algunos MAP_ANONYMOUS páginas. Moreower no escala con el número de copias : si tengo muchos VACA copias, voy a tener que repetir el mismo proceso para cada copia y esta página no pueden ser duplicados por las copias. Y yo no puedo asignar el anónimo de página en el original de la zona, ya que no será posible asignar el anónimo de las páginas en las copias. Esta solución no funciona, de todos modos.

mprotect+remap_file_pages: Esto parece la única manera de hacer esto sin tocar el kernel de Linux. La desventaja es que, en general, usted probablemente tendrá que hacer un remap_file_page syscall para cada página cuando se hace una copia : no puede ser que eficiente para hacer un montón de llamadas al sistema. Cuando desduplicación una página compartida, necesita al menos a : remap_file_page un nuevo/página libre para el nuevo escrito a la página, m-onu-proteger a la nueva página. Es necesaria para el recuento de referencia de cada página.

No creo que el mprotect() enfoques basados iba a escala muy bien (si usted maneja una gran cantidad de memoria, como este). En Linux, mprotect() no funciona en la página de memoria granularidad pero en el vm_area_struct granularidad (las entradas se encuentran en /prod//maps). Haciendo un mprotect() en la página de memoria granularidad hará que el kernel constantemente dividir y combinar vm_area_struct:

  • usted va a terminar con un muy mm_struct ;

  • buscando una vm_area_struct (que se utiliza para un registro de la memoria virtual de operaciones relacionadas) está en O(log #vm_area_struct), pero aún puede tener un negativo impacto en el rendimiento;

  • el consumo de memoria de esas estructuras.

Por esta razón, la remap_file_pages() syscall fue creado [http://lwn.net/Articles/24468/] en fin ¿no-lineal de la asignación de memoria de un archivo. Hacer esto con mmap, requiere un registro de vm_area_struct. Yo no, no caso creo que este fue diseñado para la página de la granularidad de la cartografía: el remap_file_pages() no está muy optimizado para este caso de uso se necesitaría una syscall por página.

Creo que la única solución viable es dejar el kernel de hacerlo. Es posible hacerlo en el espacio de usuario con remap_file_pages, pero probablemente será bastante ineficiente como una instantánea en la necesidad de generar una serie de llamadas al sistema proporcional en el número de páginas. Una variante de remap_file_pages podría hacer el truco.

Este enfoque, sin embargo duplicar la lógica de la página del kernel. Tiendo a pensar que debemos dejar el kernel de hacer esto. En definitiva, de una aplicación en el núcleo parece ser la mejor solución. Para alguien que conoce esta parte del kernel, que debería ser bastante fácil de hacer.

KSM (Kernel Samepage Merging): Hay una cosa que el kernel puede hacer. Se puede tratar de reduplicar las páginas. Usted todavía tiene que copiar los datos, pero el núcleo debe ser capaz de combinación de ellos. Usted necesita mmap un anónimo nueva área de copia, copia de forma manual con memcpy y madvide(inicio, final, MADV_MERGEABLE) las áreas. Necesita activar KSM (en la raíz):

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

Funciona, no funciona tan bien con mi carga de trabajo, pero probablemente porque las páginas no se comparten mucho en el final. La desventaja de ig que usted todavía tiene que hacer la copia (no se puede tener una eficiente VACA) y, a continuación, el kernel va a separar de la página. Va a generar la página y errores de caché cuando se hace la copia, la KSM dameon hilo se consume una gran cantidad de CPU (tengo un CPU ejecuta en A00% para el conjunto de la simulación) y probablemente consumen un registro de una memoria caché. Así que usted no va a ganar tiempo al hacer la copia, pero usted podría ganar algo de memoria. Id su principal motivación, es el uso de menos memoria en el largo plazo y no tanto cuidado en evitar las copias, esta solución podría funcionar para usted.

1voto

thejh Puntos 20901

Hmm... puede crear un archivo en /dev/shm con MAP_SHARED, escribir en él, a continuación, vuelva a abrirlo dos veces con MAP_PRIVATE.

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X