J'essaie de comprendre ce que font les fonctions memalign()
et posix_memalign()
. La lecture de la documentation disponible n'a pas aidé.
Quelqu'un peut-il m'aider à comprendre comment cela fonctionne et à quoi sert-il? Ou peut-être donner un exemple d'utilisation?
J'essaie de comprendre le fonctionnement de la mémoire Linux. Je dois écrire mon propre pool de mémoire simple (segment de mémoire à faible fragmentation).
Alors que malloc
vous donne un bloc de mémoire qui peut avoir un alignement quelconque (la seule condition requise est qu’il soit aligné pour le type de primitif le plus large pris en charge par la mise en œuvre), posix_memalign
vous donne un bloc de mémoire dont l’alignement demandé est garanti.
Donc, le résultat, par exemple, de posix_memalign(&p, 32, 128)
sera un bloc de mémoire de 128 octets dont l’adresse de départ est un multiple de 32.
Cela est utile pour diverses opérations de bas niveau (telles que l'utilisation d'instructions SSE ou DMA) nécessitant une mémoire obéissant à un alignement particulier.
malloc
renvoie toujours la mémoire définie sur l'alignement maximal requis par l'un des types primitifs. Cela permet à malloc
'd memory de stocker tout type de fichier dont vous pourriez avoir besoin. D'après ce que je comprends de la description de posix_memalign
, elle renvoie un emplacement de mémoire dont l'adresse sera un multiple de ce que vous spécifiez comme alignement.
Je ne sais pas à quel point cela serait utile lors de l'écriture d'un pool de mémoire personnalisé, mais je me suis efforcé de donner un exemple de la façon dont cela pourrait être implémenté. La différence est avec mon exemple, tout ce qui est attribué avec malloc_aligned
doit être libéré avec free_aligned
; Cependant, avec posix_memalign
, vous pouvez utiliser free
.
#include <stdlib.h>
#include <stdio.h>
void *malloc_aligned(size_t alignment, size_t bytes)
{
// we need to allocate enough storage for the requested bytes, some
// book-keeping (to store the location returned by malloc) and some extra
// padding to allow us to find an aligned byte. im not entirely sure if
// 2 * alignment is enough here, its just a guess.
const size_t total_size = bytes + (2 * alignment) + sizeof(size_t);
// use malloc to allocate the memory.
char *data = malloc(sizeof(char) * total_size);
if (data)
{
// store the original start of the malloc'd data.
const void * const data_start = data;
// dedicate enough space to the book-keeping.
data += sizeof(size_t);
// find a memory location with correct alignment. the alignment minus
// the remainder of this mod operation is how many bytes forward we need
// to move to find an aligned byte.
const size_t offset = alignment - (((size_t)data) % alignment);
// set data to the aligned memory.
data += offset;
// write the book-keeping.
size_t *book_keeping = (size_t*)(data - sizeof(size_t));
*book_keeping = (size_t)data_start;
}
return data;
}
void free_aligned(void *raw_data)
{
if (raw_data)
{
char *data = raw_data;
// we have to assume this memory was allocated with malloc_aligned.
// this means the sizeof(size_t) bytes before data are the book-keeping
// which points to the location we need to pass to free.
data -= sizeof(size_t);
// set data to the location stored in book-keeping.
data = (char*)(*((size_t*)data));
// free the memory.
free(data);
}
}
int main()
{
char *ptr = malloc_aligned(7, 100);
printf("is 5 byte aligned = %s\n", (((size_t)ptr) % 5) ? "no" : "yes");
printf("is 7 byte aligned = %s\n", (((size_t)ptr) % 7) ? "no" : "yes");
free_aligned(ptr);
return 0;
}
En plus de la réponse d’Oli, je voudrais vous signaler un problème encore plus important.
Sur les architectures x86 récentes, une ligne de cache, soit la plus petite quantité de données pouvant être extraite de mémoire en cache, est de 64 octets. Supposons que votre taille de structure est de 56 octets, vous en avez un grand nombre. Lorsque vous recherchez un élément, le processeur doit émettre 2 demandes de mémoire (il peut en émettre 2 même s'il se trouve au milieu de la ligne de cache). C'est mauvais pour les performances, car vous devez attendre de la mémoire et utiliser plus de cache, ce qui donne finalement un taux de cache supérieur. Dans ce cas, il ne suffit pas d'utiliser simplement posix_memalign, mais vous devez compléter ou compacter votre structure pour qu'elle soit sur des limites de 64 octets.
Avoir une structure de 40 octets n'est que malchance :)
Comment ça marche dépend de la mise en œuvre. Le but de la fonction est de vous donner un bloc de mémoire alignée sur n octets (l'adresse de début du bloc est un multiple de n).
Comme memalign est obsolète (réf: page de manuel), seule la différence entre malloc () et posix_memalign () sera décrite ici. malloc () est aligné sur 8 octets (par exemple, pour RHEL 32 bits), mais pour posix_memalign (), l'alignement peut être défini par l'utilisateur. Pour en connaître l’utilisation, un bon exemple est la définition d’un attribut de mémoire à l’aide de mprotect (). Pour utiliser mprotect (), le pointeur de la mémoire doit être aligné sur PAGE. Et donc, si vous appelez posix_memalign () avec pagesize comme alignement, le pointeur renvoyé peut facilement être soumis à mprotect () pour définir les attributs lecture-écriture-exécutable. (par exemple, après avoir copié les données dans le pointeur de la mémoire, vous pouvez le définir sur un attribut en lecture seule pour le protéger des modifications). Le pointeur renvoyé par "malloc ()" ne peut pas être utilisé ici.
Lorsque vous utilisez posix_memalign in GNU C , veillez à ce que le second paramètre ne soit pas seulement une puissance de deux, mais également un multiple de sizeof (void *). Notez que cette exigence est différente de celle de la fonction memalign, qui ne nécessite qu'une puissance de deux.
int
__posix_memalign (void **memptr, size_t alignment, size_t size)
{
void *mem;
/* Test whether the SIZE argument is valid. It must be a power of
two multiple of sizeof (void *). */
if (alignment % sizeof (void *) != 0
|| !powerof2 (alignment / sizeof (void *))
|| alignment == 0)
return EINVAL;
void *address = RETURN_ADDRESS (0);
mem = _mid_memalign (alignment, size, address);
if (mem != NULL)
{
*memptr = mem;
return 0;
}
return ENOMEM;
}
weak_alias (__posix_memalign, posix_memalign)
Lorsque nous examinons la première condition if dans l'implémentation posix_memalign, elle vérifie si l'alignement est multiple de sizeof (void *).
Facilite l'utilisation de THP dans le sous-système de mémoire virtuelle Linux: https://youtu.be/fgC6RUlkQE4?t=4930