ventajas sistemas operativos metodo memoria libre espacio encadenada desventajas definicion contigua asignacion administracion c++ boost shared-memory mmap boost-interprocess

c++ - sistemas - memoria contigua definicion



AsignaciĆ³n de bloques no contiguos de un archivo a direcciones de memoria contiguas (3)

Estoy interesado en la posibilidad de utilizar IO asignada en memoria, preferiblemente explotar las instalaciones en boost :: interprocess para soporte multiplataforma, para asignar bloques no contiguos de tamaño de página de sistema en un archivo a un espacio de direcciones contiguas en la memoria.

Un escenario concreto simplificado:

Tengo varias estructuras de ''datos antiguos'', cada una de una longitud fija (menor que el tamaño de la página del sistema). Estas estructuras se concatenan en una secuencia (muy larga) con el tipo y la ubicación de las estructuras determinadas por el Valores de aquellas estructuras que los proceden en la corriente. Mi objetivo es minimizar la latencia y maximizar el rendimiento en un entorno concurrente exigente.

Puedo leer estos datos de manera muy efectiva al mapearlos en memoria en bloques de al menos el doble del tamaño de la página del sistema ... y establecer un nuevo mapeo inmediatamente después de haber leído una estructura que se extiende más allá del penúltimo límite de la página del sistema. Esto permite que el código que interactúa con las estructuras de datos antiguos no tenga en cuenta que estas estructuras se asignan a la memoria ... y, por ejemplo, podría comparar dos estructuras diferentes utilizando memcmp () directamente sin tener que preocuparse por los límites de la página.

Donde las cosas se ponen interesantes es con respecto a la actualización de estos flujos de datos ... mientras se están leyendo (al mismo tiempo). La estrategia que me gustaría usar está inspirada en ''Copiar en escritura'' en una granularidad del tamaño de la página del sistema ... esencialmente escribiendo ''páginas superpuestas'' - permitiendo que un proceso lea los datos antiguos mientras que otro lee los datos actualizados.

Si bien administrar las páginas de superposición que se deben usar y cuándo, no es necesariamente trivial ... esa no es mi principal preocupación. Mi principal preocupación es que puedo tener una estructura que abarca las páginas 4 y 5, luego actualizar una estructura contenida en su totalidad en la página 5 ... escribiendo la nueva página en la ubicación 6 ... dejando la página 5 como ''basura recolectada'' cuando está determinado a no ser más accesible. Esto significa que, si mapeo la página 4 en la ubicación M, necesito mapear la página 6 en la ubicación de la memoria M + page_size ... para poder procesar de manera confiable las estructuras que cruzan los límites de las páginas usando las existentes (no mapeo de memoria) funciones)

Estoy tratando de establecer la mejor estrategia y me obstaculiza la documentación que creo que está incompleta. Esencialmente, necesito separar la asignación del espacio de direcciones de la asignación de memoria a ese espacio de direcciones. Con mmap (), soy consciente de que puedo usar MAP_FIXED; si deseo controlar explícitamente la ubicación del mapa ... pero no tengo claro cómo debo reservar el espacio de direcciones para hacerlo de manera segura. ¿Puedo asignar / dev / cero para dos páginas sin MAP_FIXED, y luego usar MAP_FIXED dos veces para asignar dos páginas a ese espacio asignado en direcciones VM explícitas? Si es así, ¿debería llamar a munmap () tres veces también? ¿Perderá recursos y / o tendrá alguna otra sobrecarga adversa? Para que el problema sea aún más complejo, me gustaría un comportamiento comparable en Windows ... ¿hay alguna forma de hacerlo? ¿Hay soluciones claras si tuviera que comprometer mis ambiciones multiplataforma?

-

Gracias por su respuesta, Mahmoud ... He leído, y creo que he entendido ese código ... Lo he compilado bajo Linux y se comporta como usted sugiere.

Mis principales preocupaciones son con la línea 62: usar MAP_FIXED. Hace algunas suposiciones sobre mmap, que no he podido confirmar cuando leí la documentación que puedo encontrar. Está mapeando la página de ''actualización'' en el mismo espacio de direcciones que mmap () devuelto inicialmente. Supongo que esto es ''correcto'', es decir, ¿no es algo que simplemente funciona en Linux? También debo asumir que funciona multiplataforma para las asignaciones de archivos, así como las asignaciones anónimas.

La muestra definitivamente me hace avanzar ... documentar que lo que en última instancia necesito es probablemente alcanzable con mmap () en Linux, al menos. Lo que realmente me gustaría es un puntero a la documentación que muestre que la línea MAP_FIXED funcionará como muestra el ejemplo ... e, idealmente, una transformación de mmap () específica de Linux / Unix a una plataforma independiente (Boost :: interprocess ) enfoque.


pero no tengo claro cómo debo reservar el espacio de direcciones para hacer esto de forma segura

Eso variará según el sistema operativo, pero un poco de investigación en msdn para mmap (comencé con "xp mmap" en la búsqueda de msdn) muestra que Microsoft tiene su nombre VerboseAandHelpfullyCapitalizedNames para (las muchas) funciones que implementan piezas de mmap. Tanto los asignadores de archivos como los anónimos pueden manejar las solicitudes de direcciones fijas de la misma manera que cualquier sistema POSIX-2001, es decir, si algo más en su espacio de direcciones está hablando con el núcleo, puede resolverlo. De ninguna manera voy a tocar "de forma segura", no existe tal cosa como "de forma segura" con el código que desea trasladar a plataformas no especificadas. Tendrá que crear su propio grupo de memoria anónima previamente asignada que puede desasignar y parcelar más tarde bajo su propio control.


Probé el código de Windows de @Mahmoud, bueno, en realidad, probé el siguiente código similar, y no funciona (el código de Linux funciona). Si descomenta VirtualFree, funcionará. Como se mencionó en mi comentario anterior, en Windows puede reservar el espacio de direcciones con VirtualAlloc, pero no puede usar MapViewOfFileEx con una dirección ya asignada, por lo que primero necesita VirtualFree. Luego hay una condición de carrera en la que otro hilo puede tomar la dirección de la memoria antes de hacerlo, por lo que tiene que hacer todo en un bucle, por ejemplo, intentar hasta 1000 veces y luego darse por vencido.

package main import ( "fmt" "os" "syscall" ) func main() { const size = 1024 * 1024 file, err := os.Create("foo.dat") if err != nil { panic(err) } if err := file.Truncate(size); err != nil { panic(err) } const MEM_COMMIT = 0x1000 addr, err := virtualAlloc(0, size, MEM_COMMIT, protReadWrite) if err != nil { panic(err) } fd, err := syscall.CreateFileMapping( syscall.Handle(file.Fd()), nil, uint32(protReadWrite), 0, uint32(size), nil, ) //if err := virtualFree(addr); err != nil { // panic(err) //} base, err := mapViewOfFileEx(fd, syscall.FILE_MAP_READ|syscall.FILE_MAP_WRITE, 0, 0, size, addr) if base == 0 { panic("mapViewOfFileEx returned 0") } if err != nil { panic(err) } fmt.Println("success!") } type memProtect uint32 const ( protReadOnly memProtect = 0x02 protReadWrite memProtect = 0x04 protExecute memProtect = 0x20 protAll memProtect = 0x40 ) var ( modkernel32 = syscall.MustLoadDLL("kernel32.dll") procMapViewOfFileEx = modkernel32.MustFindProc("MapViewOfFileEx") procVirtualAlloc = modkernel32.MustFindProc("VirtualAlloc") procVirtualFree = modkernel32.MustFindProc("VirtualFree") procVirtualProtect = modkernel32.MustFindProc("VirtualProtect") ) func mapViewOfFileEx(handle syscall.Handle, prot memProtect, offsetHigh uint32, offsetLow uint32, length uintptr, target uintptr) (addr uintptr, err error) { r0, _, e1 := syscall.Syscall6(procMapViewOfFileEx.Addr(), 6, uintptr(handle), uintptr(prot), uintptr(offsetHigh), uintptr(offsetLow), length, target) addr = uintptr(r0) if addr == 0 { if e1 != 0 { err = error(e1) } else { err = syscall.EINVAL } } return addr, nil } func virtualAlloc(addr, size uintptr, allocType uint32, prot memProtect) (mem uintptr, err error) { r0, _, e1 := syscall.Syscall6(procVirtualAlloc.Addr(), 4, addr, size, uintptr(allocType), uintptr(prot), 0, 0) mem = uintptr(r0) if e1 != 0 { return 0, error(e1) } return mem, nil } func virtualFree(addr uintptr) error { const MEM_RELEASE = 0x8000 _, _, e1 := syscall.Syscall(procVirtualFree.Addr(), 3, addr, 0, MEM_RELEASE) if e1 != 0 { return error(e1) } return nil }


Tu pregunta es un poco confusa. Por lo que entendí, este código hará lo que necesites:

#define PAGESIZE 4096 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/mman.h> #include <errno.h> #include <sys/types.h> #include <fcntl.h> #include <unistd.h> #include <assert.h> struct StoredObject { int IntVal; char StrVal[25]; }; int main(int argc, char **argv) { int fd = open("mmapfile", O_RDWR | O_CREAT | O_TRUNC, (mode_t) 0600); //Set the file to the size of our data (2 pages) lseek(fd, PAGESIZE*2 - 1, SEEK_SET); write(fd, "", 1); //The final byte unsigned char *mapPtr = (unsigned char *) mmap(0, PAGESIZE * 2, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); struct StoredObject controlObject; controlObject.IntVal = 12; strcpy(controlObject.StrVal, "Mary had a little lamb./n"); struct StoredObject *mary1; mary1 = (struct StoredObject *)(mapPtr + PAGESIZE - 4); //Will fall on the boundary between first and second page memcpy(mary1, &controlObject, sizeof(StoredObject)); printf("%d, %s", mary1->IntVal, mary1->StrVal); //Should print "12, Mary had a little lamb./n" struct StoredObject *john1; john1 = mary1 + 1; //Comes immediately after mary1 in memory; will start and end in the second page memcpy(john1, &controlObject, sizeof(StoredObject)); john1->IntVal = 42; strcpy(john1->StrVal, "John had a little lamb./n"); printf("%d, %s", john1->IntVal, john1->StrVal); //Should print "12, Mary had a little lamb./n" //Make sure the data''s on the disk, as this is the initial, "read-only" data msync(mapPtr, PAGESIZE * 2, MS_SYNC); //This is the inital data set, now in memory, loaded across two pages //At this point, someone could be reading from there. We don''t know or care. //We want to modify john1, but don''t want to write over the existing data //Easy as pie. //This is the shadow map. COW-like optimization will take place: //we''ll map the entire address space from the shared source, then overlap with a new map to modify //This is mapped anywhere, letting the system decide what address we''ll be using for the new data pointer unsigned char *mapPtr2 = (unsigned char *) mmap(0, PAGESIZE * 2, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); //Map the second page on top of the first mapping; this is the one that we''re modifying. It is *not* backed by disk unsigned char *temp = (unsigned char *) mmap(mapPtr2 + PAGESIZE, PAGESIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | MAP_ANON, 0, 0); if (temp == MAP_FAILED) { printf("Fixed map failed. %s", strerror(errno)); } assert(temp == mapPtr2 + PAGESIZE); //Make a copy of the old data that will later be changed memcpy(mapPtr2 + PAGESIZE, mapPtr + PAGESIZE, PAGESIZE); //The two address spaces should still be identical until this point assert(memcmp(mapPtr, mapPtr2, PAGESIZE * 2) == 0); //We can now make our changes to the second page as needed struct StoredObject *mary2 = (struct StoredObject *)(((unsigned char *)mary1 - mapPtr) + mapPtr2); struct StoredObject *john2 = (struct StoredObject *)(((unsigned char *)john1 - mapPtr) + mapPtr2); john2->IntVal = 52; strcpy(john2->StrVal, "Mike had a little lamb./n"); //Test that everything worked OK assert(memcmp(mary1, mary2, sizeof(struct StoredObject)) == 0); printf("%d, %s", john2->IntVal, john2->StrVal); //Should print "52, Mike had a little lamb./n" //Now assume our garbage collection routine has detected that no one is using the original copy of the data munmap(mapPtr, PAGESIZE * 2); mapPtr = mapPtr2; //Now we''re done with all our work and want to completely clean up munmap(mapPtr2, PAGESIZE * 2); close(fd); return 0; }

Mi respuesta modificada debe abordar sus preocupaciones de seguridad. Solo use MAP_FIXED en la segunda llamada de mmap (como la que tengo arriba). Lo bueno de MAP_FIXED es que le permite sobrescribir una sección de dirección mmap existente. Descargará el rango que está superponiendo y lo reemplazará con su nuevo contenido asignado:

MAP_FIXED [...] If the memory region specified by addr and len overlaps pages of any existing mapping(s), then the overlapped part of the existing mapping(s) will be discarded. [...]

De esta manera, dejas que el sistema operativo se ocupe de encontrar un bloque de memoria contiguo de cientos de megas para ti (nunca llames a MAP_FIXED en una dirección que no estés seguro de que no esté disponible). Luego llama a MAP_FIXED en una subsección de ese enorme espacio asignado ahora con los datos que modificará. Tada

En Windows, algo como esto debería funcionar (estoy en una Mac en este momento, por lo que no he probado):

int main(int argc, char **argv) { HANDLE hFile = CreateFile(L"mmapfile", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); //Set the file to the size of our data (2 pages) SetFilePointer(hFile, PAGESIZE*2 - 1, 0, FILE_BEGIN); DWORD bytesWritten = -1; WriteFile(hFile, "", 1, &bytesWritten, NULL); HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, PAGESIZE * 2, NULL); unsigned char *mapPtr = (unsigned char *) MapViewOfFile(hMap, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, PAGESIZE * 2); struct StoredObject controlObject; controlObject.IntVal = 12; strcpy(controlObject.StrVal, "Mary had a little lamb./n"); struct StoredObject *mary1; mary1 = (struct StoredObject *)(mapPtr + PAGESIZE - 4); //Will fall on the boundary between first and second page memcpy(mary1, &controlObject, sizeof(StoredObject)); printf("%d, %s", mary1->IntVal, mary1->StrVal); //Should print "12, Mary had a little lamb./n" struct StoredObject *john1; john1 = mary1 + 1; //Comes immediately after mary1 in memory; will start and end in the second page memcpy(john1, &controlObject, sizeof(StoredObject)); john1->IntVal = 42; strcpy(john1->StrVal, "John had a little lamb./n"); printf("%d, %s", john1->IntVal, john1->StrVal); //Should print "12, Mary had a little lamb./n" //Make sure the data''s on the disk, as this is the initial, "read-only" data //msync(mapPtr, PAGESIZE * 2, MS_SYNC); //This is the inital data set, now in memory, loaded across two pages //At this point, someone could be reading from there. We don''t know or care. //We want to modify john1, but don''t want to write over the existing data //Easy as pie. //This is the shadow map. COW-like optimization will take place: //we''ll map the entire address space from the shared source, then overlap with a new map to modify //This is mapped anywhere, letting the system decide what address we''ll be using for the new data pointer unsigned char *reservedMem = (unsigned char *) VirtualAlloc(NULL, PAGESIZE * 2, MEM_RESERVE, PAGE_READWRITE); HANDLE hMap2 = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, PAGESIZE, NULL); unsigned char *mapPtr2 = (unsigned char *) MapViewOfFileEx(hMap2, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, PAGESIZE, reservedMem); //Map the second page on top of the first mapping; this is the one that we''re modifying. It is *not* backed by disk unsigned char *temp = (unsigned char *) MapViewOfFileEx(hMap2, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, PAGESIZE, reservedMem + PAGESIZE); if (temp == NULL) { printf("Fixed map failed. 0x%x/n", GetLastError()); return -1; } assert(temp == mapPtr2 + PAGESIZE); //Make a copy of the old data that will later be changed memcpy(mapPtr2 + PAGESIZE, mapPtr + PAGESIZE, PAGESIZE); //The two address spaces should still be identical until this point assert(memcmp(mapPtr, mapPtr2, PAGESIZE * 2) == 0); //We can now make our changes to the second page as needed struct StoredObject *mary2 = (struct StoredObject *)(((unsigned char *)mary1 - mapPtr) + mapPtr2); struct StoredObject *john2 = (struct StoredObject *)(((unsigned char *)john1 - mapPtr) + mapPtr2); john2->IntVal = 52; strcpy(john2->StrVal, "Mike had a little lamb./n"); //Test that everything worked OK assert(memcmp(mary1, mary2, sizeof(struct StoredObject)) == 0); printf("%d, %s", john2->IntVal, john2->StrVal); //Should print "52, Mike had a little lamb./n" //Now assume our garbage collection routine has detected that no one is using the original copy of the data //munmap(mapPtr, PAGESIZE * 2); mapPtr = mapPtr2; //Now we''re done with all our work and want to completely clean up //munmap(mapPtr2, PAGESIZE * 2); //close(fd); return 0; }