Selon le manuel des programmeurs Linux:
brk () et sbrk () modifient l'emplacement de l'interruption du programme, qui définit la fin du segment de données du processus.
Que signifie le segment de données ici? S'agit-il uniquement du segment de données ou des données, du BSS et du tas combinés?
Selon wiki:
Parfois, les données, le BSS et les zones de tas sont collectivement appelés le «segment de données».
Je ne vois aucune raison de modifier la taille du segment de données uniquement. S'il s'agit de données, de BSS et de tas collectivement, cela a du sens car le tas obtiendra plus d'espace.
Ce qui m'amène à ma deuxième question. Dans tous les articles que j'ai lus jusqu'à présent, l'auteur dit que le tas se développe vers le haut et la pile se développe vers le bas. Mais ce qu'ils n'expliquent pas, c'est ce qui se passe lorsque le tas occupe tout l'espace entre le tas et la pile?
brk()
appel système est plus utile en langage assembleur qu'en C. En C, ilmalloc()
doit être utilisé à la place debrk()
pour des fins d'allocation de données - mais cela n'invalide en aucun cas la question proposée.brk()
etsbrk()
? Les piles sont gérées par l'allocateur de page, à un niveau beaucoup plus bas.Réponses:
Dans le diagramme que vous avez publié, la "rupture" - l'adresse manipulée par
brk
etsbrk
- est la ligne en pointillé en haut du tas.La documentation que vous avez lue décrit cela comme la fin du "segment de données" parce que dans les bibliothèques traditionnelles (pré-partagées, pré-
mmap
) Unix, le segment de données était continu avec le tas; avant le démarrage du programme, le noyau chargerait les blocs «texte» et «données» dans la RAM en commençant à l'adresse zéro (en fait un peu au-dessus de l'adresse zéro, de sorte que le pointeur NULL ne pointait vraiment pas vers quoi que ce soit) et définissait l'adresse de rupture sur la fin du segment de données. Le premier appel àmalloc
utiliserait ensuitesbrk
pour déplacer la rupture et créer le tas entre le haut du segment de données et la nouvelle adresse de rupture plus élevée, comme indiqué dans le diagramme, et l'utilisation ultérieure demalloc
l'utiliserait pour agrandir le tas le cas échéant.Pendant ce temps, la pile commence au sommet de la mémoire et se développe. La pile n'a pas besoin d'appels système explicites pour l'agrandir; soit il commence avec autant de RAM allouée que possible (c'était l'approche traditionnelle) ou il y a une région d'adresses réservées sous la pile, à laquelle le noyau alloue automatiquement de la RAM lorsqu'il remarque une tentative d'écriture là-bas (c'est l'approche moderne). Dans tous les cas, il peut y avoir ou non une région de «garde» au bas de l'espace d'adressage qui peut être utilisée pour la pile. Si cette région existe (tous les systèmes modernes le font), elle est définitivement non mappée; si soitla pile ou le tas essaie de s'y développer, vous obtenez une erreur de segmentation. Traditionnellement, cependant, le noyau ne faisait aucune tentative pour imposer une limite; la pile pourrait se développer dans le tas, ou le tas pourrait se développer dans la pile, et de toute façon ils griffonneraient sur les données de l'autre et le programme planterait. Si vous étiez très chanceux, il planterait immédiatement.
Je ne sais pas d'où vient le nombre 512 Go dans ce diagramme. Cela implique un espace d'adressage virtuel de 64 bits, ce qui est incompatible avec la carte mémoire très simple que vous avez là-bas. Un véritable espace d'adressage 64 bits ressemble plus à ceci:
Ce n'est pas à l'échelle à distance, et cela ne devrait pas être interprété comme exactement comment un système d'exploitation donné fait les choses (après l'avoir dessiné, j'ai découvert que Linux rapproche en fait l'exécutable de l'adresse zéro que je ne le pensais, et les bibliothèques partagées à des adresses étonnamment élevées). Les régions noires de ce diagramme ne sont pas mappées - tout accès provoque un segfault immédiat - et elles sont gigantesques par rapport aux zones grises. Les régions gris clair sont le programme et ses bibliothèques partagées (il peut y avoir des dizaines de bibliothèques partagées); chacun a un indépendantsegment de texte et de données (et segment "bss", qui contient également des données globales mais est initialisé à tous les bits-zéro plutôt que de prendre de l'espace dans l'exécutable ou la bibliothèque sur le disque). Le tas n'est plus nécessairement continu avec le segment de données de l'exécutable - je l'ai dessiné de cette façon, mais il semble que Linux, au moins, ne le fasse pas. La pile n'est plus ancrée au sommet de l'espace d'adressage virtuel, et la distance entre le tas et la pile est si énorme que vous n'avez pas à vous soucier de la traverser.
La rupture est toujours la limite supérieure du tas. Cependant, ce que je n'ai pas montré, c'est qu'il pourrait y avoir des dizaines d'allocations indépendantes de mémoire là-bas dans le noir quelque part, faites avec
mmap
au lieu debrk
. (Le système d'exploitation essaiera de les garder loin de labrk
zone afin qu'ils n'entrent pas en collision.)la source
malloc
repose toujours surbrk
ou s'il utilisemmap
pour pouvoir "rendre" des blocs de mémoire séparés?malloc
s actuels utilisent labrk
zone pour les petites allocations et les individusmmap
pour les grandes allocations (par exemple> 128K). Voir, par exemple, la discussion de MMAP_THRESHOLD dans lamalloc(3)
page de manuel Linux .mmap
; c'est extrêmement dépendant du système d'exploitation.Exemple exécutable minimal
Demande au noyau de vous permettre de lire et d'écrire dans un bloc de mémoire contigu appelé le tas.
Si vous ne le demandez pas, cela pourrait vous déranger.
Sans
brk
:Avec
brk
:GitHub en amont .
Ce qui précède pourrait ne pas frapper une nouvelle page et pas segfault même sans le
brk
, voici donc une version plus agressive qui alloue 16 Mo et est très susceptible de segfault sans lebrk
:Testé sur Ubuntu 18.04.
Visualisation de l'espace d'adressage virtuel
Avant
brk
:Après
brk(p + 2)
:Après
brk(b)
:Pour mieux comprendre les espaces d'adressage, vous devez vous familiariser avec la pagination: comment fonctionne la pagination x86? .
Pourquoi avons-nous besoin des deux
brk
etsbrk
?brk
pourrait bien sûr être implémenté avec dessbrk
calculs de + offset, les deux existent juste pour des raisons de commodité.Dans le backend, le noyau Linux v5.0 a un seul appel système
brk
qui est utilisé pour implémenter les deux: https://github.com/torvalds/linux/blob/v5.0/arch/x86/entry/syscalls/syscall_64. tbl # L23Est-ce
brk
POSIX?brk
Auparavant, c'était POSIX, mais il a été supprimé dans POSIX 2001, d'où la nécessité_GNU_SOURCE
d'accéder au wrapper glibc.La suppression est probablement due à l'introduction
mmap
, qui est un sur-ensemble qui permet d'allouer plusieurs plages et plus d'options d'allocation.Je pense qu'il n'y a pas de cas valable où vous devriez utiliser à la
brk
placemalloc
ou demmap
nos jours.brk
contremalloc
brk
est une ancienne possibilité de mise en œuvremalloc
.mmap
est le nouveau mécanisme strictement plus puissant que tous les systèmes POSIX utilisent actuellement pour implémentermalloc
. Voici un exemple d'allocation de mémoire exécutable minimalemmap
.Puis-je mélanger
brk
et malloc?Si votre
malloc
est implémenté avecbrk
, je ne sais pas comment cela peut ne pas faire exploser les choses, carbrk
ne gère qu'une seule plage de mémoire.Je n'ai cependant rien trouvé à ce sujet sur la documentation de la glibc, par exemple:
Les choses fonctionneront probablement là-bas, je suppose, car elles
mmap
sont probablement utiliséesmalloc
.Voir également:
Plus d'informations
En interne, le noyau décide si le processus peut avoir autant de mémoire et réserve des pages de mémoire pour cet usage.
Ceci explique comment la pile se compare au tas: Quelle est la fonction des instructions push / pop utilisées sur les registres dans l'assemblage x86?
la source
p
s'agit d'un pointeur sur le typeint
, cela n'aurait-il pas dû l'êtrebrk(p + 2);
?*(p + i) = 1;
brk(p + 2)
au lieu de simplement l'augmentersbrk(2)
? Brk est-il vraiment nécessaire?brk
syscall).brk
est un peu plus pratique pour restaurer la pile précédemment allouée.Vous pouvez utiliser
brk
etsbrk
vous - même pour éviter les «frais généraux de malloc» dont tout le monde se plaint toujours. Mais vous ne pouvez pas facilement utiliser cette méthode en conjonction avec,malloc
donc elle n'est appropriée que lorsque vous n'avez rien à fairefree
. Parce que tu ne peux pas. De plus, vous devez éviter tout appel de bibliothèque qui pourrait être utilisé enmalloc
interne. C'est à dire.strlen
est probablement sans danger, maisfopen
ne l'est probablement pas.Appelez
sbrk
comme vous appelleriezmalloc
. Il renvoie un pointeur vers la rupture actuelle et incrémente la rupture de ce montant.Bien que vous ne puissiez pas libérer d'allocations individuelles (car il n'y a pas de surcharge de malloc , rappelez-vous), vous pouvez libérer tout l'espace en appelant
brk
avec la valeur renvoyée par le premier appel àsbrk
, rembobinant ainsi le brk .Vous pouvez même empiler ces régions, en supprimant la région la plus récente en rembobinant la pause au début de la région.
Encore une chose ...
sbrk
est également utile dans le code golf car il est plus court de 2 caractères quemalloc
.la source
malloc
/ peutfree
très certainement (et le fait) redonner de la mémoire au système d'exploitation. Ils peuvent ne pas toujours le faire quand vous le souhaitez, mais c'est une question d'heuristique imparfaitement adaptée à votre cas d'utilisation. Plus important encore, il n'est pas sûr d'appeler avec un argument différent de zéro dans n'importe quel programme qui pourrait appeler - et presque toutes les fonctions de la bibliothèque C sont autorisées à appeler en interne. Les seuls qui ne le seront certainement pas sont les fonctions de sécurité du signal asynchrone .sbrk
malloc
malloc
malloc
.sbrk
pour cela est seulement utile pour le code-golf, car manuellement à l' aidemmap(MAP_ANONYMOUS)
est meilleure dans tous les sens , sauf la taille du code source.Il existe un mappage de mémoire privée anonyme désigné spécial (situé traditionnellement juste au-delà des données / bss, mais Linux moderne ajustera en fait l'emplacement avec ASLR). En principe , il est pas mieux que toute autre application , vous pouvez créer avec
mmap
, mais Linux a quelques optimisations qui permettent d'élargir la fin de cette cartographie ( en utilisant lebrk
syscall) vers le haut avec un coût de blocage réduit par rapport à cemmap
oumremap
encourrait. Cela le rend attrayant pour lesmalloc
implémentations à utiliser lors de l'implémentation du tas principal.la source
Je peux répondre à votre deuxième question. Malloc échouera et renverra un pointeur nul. C'est pourquoi vous recherchez toujours un pointeur nul lors de l'allocation dynamique de mémoire.
la source
malloc()
utiliserabrk()
et / ousbrk()
sous le capot - et vous pouvez aussi, si vous souhaitez implémenter votre propre version personnalisée demalloc()
.Le tas est placé en dernier dans le segment de données du programme.
brk()
est utilisé pour modifier (développer) la taille du tas. Lorsque le tas ne peut plus croître, toutmalloc
appel échoue.la source
Le segment de données est la partie de la mémoire qui contient toutes vos données statiques, lues à partir de l'exécutable au lancement et généralement remplies de zéro.
la source
.bss
initialisées ( ) sont initialisées à tous les bits-zéro par le système d'exploitation avant le démarrage du programme; ceci est en fait garanti par la norme C. Certains systèmes embarqués pourraient ne pas déranger, je suppose (je n'en ai jamais vu un, mais je ne travaille pas tout ce qui est intégré)mmap
, mais je suppose que le.bss
serait toujours mis à zéro. L'espace BSS est probablement le moyen le plus compact d'exprimer le fait qu'un programme veut des tableaux de zéros..bss
et ne met pas à zéro.bss
serait donc non conforme. Mais rien n'oblige une implémentation C à utiliser.bss
ou même à avoir une telle chose.main
; ce code pourrait mettre à zéro la.bss
zone plutôt que de laisser le noyau le faire, et cela serait toujours conforme.malloc utilise l'appel système brk pour allouer de la mémoire.
comprendre
lancez ce programme simple avec strace, il appellera brk system.
la source