Que fait l'appel de système brk ()?
selon le manuel des programmeurs Linux:
brk() et sbrk() changement de l'emplacement du programme de pause, c' définit la fin du segment de données du processus.
que signifie le segment de données ici? Est-ce seulement le segment de données ou les données, BSS, et heap combinés?
selon wiki:
parfois, les données, BSS, et les zones tas sont collectivement, le "segment de données".
Je ne vois aucune raison de changer la taille du segment de données seulement. S'il s'agit de données, de BSS et de heap collectivement, alors il est logique que heap obtienne 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 tas pousse vers le haut et la pile pousse vers le bas. Mais ce qu'ils n'expliquent pas, c'est que ce qui se passe quand tas occupe tout l'espace entre tas et de la pile?
9 réponses
je vois beaucoup de réponses partielles, mais pas de réponse complète. Voici cette photo que vous avez postée à nouveau:
le "break"--l'adresse manipulée par brk
et sbrk
--est la ligne pointillée en haut du tas . La documentation que vous avez lue décrit ceci comme la fin du "segment de données" parce que dans les traditionnelles (pré-bibliothèques partagées, pré - mmap
) Unix les données segment était continu avec le tas; avant le démarrage du programme, le noyau chargeait les blocs "text" et "data" dans la RAM à partir de l'adresse zéro (en fait un peu au-dessus de l'adresse zéro, de sorte que le pointeur nul ne pointait vraiment vers rien) et mettait l'adresse de rupture à la fin du segment de données. Le premier appel à malloc
utiliserait alors sbrk
pour déplacer la rupture et créer le tas entre le haut du segment de données et le nouveau, plus haut break adresse, comme indiqué dans le diagramme, et l'utilisation subséquente de malloc
l'utiliserait pour rendre le tas plus grand si nécessaire.
en attendant, la pile commence au sommet de la mémoire et s'allonge. La pile n'a pas besoin d'appels système explicites pour la rendre plus grande; soit elle démarre avec autant de RAM qu'elle peut l'avoir fait (c'était l'approche traditionnelle), soit il y a une région d'adresses réservées sous la pile, à laquelle le noyau attribue automatiquement de la RAM. quand il détecte une tentative d'écrire là-bas (c'est l'approche moderne). De toute façon, il peut y avoir ou non une zone de "garde" au bas de l'espace d'adresse qui peut être utilisée pour la pile. Si cette région existe (tous les systèmes modernes le font) elle est en permanence non cartographiée; si soit la pile ou le tas tente de se développer en elle, vous obtenez une faille de segmentation. Traditionnellement, cependant, le noyau n'a fait aucune tentative pour imposer une frontière, la pile pourrait croître dans le tas, ou le tas pourrait croître dans la pile, et de toute façon ils griffonnerait unes des autres, les données et le programme crash. Si vous aviez de la chance, il s'écraserait immédiatement.
Je ne sais pas d'où vient le numéro 512GB de ce diagramme. Cela implique un espace d'adresse virtuel de 64 bits, ce qui est incompatible avec la carte mémoire très simple que vous avez là. Un véritable espace d'adresse 64 bits ressemble plus à ceci:
ce n'est pas du tout à l'échelle, et il ne devrait pas être interprété comme exactement comment un OS donné fait des choses (après que je l'ai dessiné, J'ai découvert que Linux met en fait l'exécutable beaucoup plus près d'adresse zéro que je pensais qu'il faisait, et les bibliothèques partagées à des adresses étonnamment élevées). Les régions noires de ce diagramme ne sont pas mappées -- n'importe quel accès provoque un segfault immédiat -- et elles sont "1519160920 gigantesque par rapport aux zones grises. La lumière-gris les régions sont le programme et ses bibliothèques partagées (il peut y avoir des dizaines de bibliothèques partagées); chacune a un indépendant segment de texte et de données (et "BSS" segment, qui contient également des données globales, mais est initialisé à tous-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 fait pas cela. La pile est non plus rattachée au haut de l'espace d'adressage virtuel, et la distance entre le tas et la pile est tellement énorme que vous n'avez pas à vous soucier de le franchir.
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 douzaines d'attributions indépendantes de mémoire là-bas dans le noir quelque part, faites avec mmap
au lieu de brk
. (L'OS va essayer de garder ces loin de la zone brk
afin qu'ils n'entrent pas en collision.)
Minimal praticable exemple
Qu'est-brk( ) de l'appel système?
demande au noyau de vous laisser lire et écrire à un morceau contigu de mémoire appelé le tas.
si vous ne le demandez pas, il pourrait vous segfault.
sans brk
:
#define _GNU_SOURCE
#include <unistd.h>
int main(void) {
/* Get the first address beyond the end of the heap. */
void *b = sbrk(0);
int *p = (int *)b;
/* May segfault because it is outside of the heap. */
*p = 1;
return 0;
}
avec brk
:
#define _GNU_SOURCE
#include <assert.h>
#include <unistd.h>
int main(void) {
void *b = sbrk(0);
int *p = (int *)b;
/* Move it 2 ints forward */
brk(p + 2);
/* Use the ints. */
*p = 1;
*(p + 1) = 2;
assert(*p == 1);
assert(*(p + 1) == 2);
/* Deallocate back. */
brk(b);
return 0;
}
testé sur Ubuntu 14.04.
l'espace d'adressage Virtuel de visualisation
avant brk
:
+------+ <-- Heap Start == Heap End
après brk(p + 2)
:
+------+ <-- Heap Start + 2 * sizof(int) == Heap End
| |
| You can now write your ints
| in this memory area.
| |
+------+ <-- Heap Start
après brk(b)
:
+------+ <-- Heap Start == Heap End
pour mieux comprendre les espaces d'adresse, vous devez vous familiariser avec la pagination: Comment fonctionne la pagination x86?
plus d'informations
brk
utilisé pour être POSIX, mais il a été supprimé dans POSIX 2001, donc la nécessité de _GNU_SOURCE
pour accéder à l'emballage glibc.
la suppression est probablement due à l'introduction mmap
, qui est un super-ensemble qui permet d'attribuer plusieurs plages et plus d'options d'attribution.
à l'intérieur, le noyau décide si le processus peut avoir beaucoup de mémoire, et consacre pages de mémoire pour cet usage.
brk
et mmap
, telles sont les mécanismes sous-jacents de la libc utilise pour mettre en œuvre malloc
dans POSIX systèmes.
cela 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?
vous pouvez utiliser brk
et sbrk
vous-même pour éviter le" malloc overhead " dont tout le monde se plaint. Mais vous ne pouvez pas facilement utiliser cette méthode en conjonction avec malloc
donc il est seulement approprié quand vous n'avez pas à free
quoi que ce soit. Parce que tu ne peux pas. De plus, vous devez éviter tout appel de bibliothèque qui pourrait utiliser malloc
en interne. IE. strlen
est probablement sans danger, mais fopen
ne l'est probablement pas.
Appel sbrk
tout comme vous appelleriez malloc
. Elle renvoie un pointeur sur l'actuel et incrémente la rupture de ce montant.
void *myallocate(int n){
return sbrk(n);
}
alors que vous ne pouvez pas libérer des allocations individuelles (parce qu'il n'y a pas de malloc-overhead , rappelez-vous), vous can libre l'espace entier en appelant brk
avec la valeur retournée par le premier appel à sbrk
, donc rembobinage de la brk .
void *memorypool;
void initmemorypool(void){
memorypool = sbrk(0);
}
void resetmemorypool(void){
brk(memorypool);
}
vous pourriez même empiler ces régions, en rejetant la région la plus récente en rembobinant la pause au début de la région.
une dernière chose ...
sbrk
est également utile dans code golf parce que c'est 2 caractères plus court que malloc
.
il y a une carte mémoire privée anonyme spéciale désignée (traditionnellement situé juste au-delà des données/bss, mais Linux moderne va effectivement ajuster l'emplacement avec ASLR). En principe , ce n'est pas mieux que n'importe quelle autre cartographie que vous pourriez créer avec mmap
, mais Linux a quelques optimisations qui permettent d'étendre la fin de cette cartographie (en utilisant le brk
syscall) vers le haut avec un coût de verrouillage réduit par rapport à ce que mmap
ou mremap
entraînerait. Cela rend attrayant pour les implémentations malloc
d'utiliser lors de la mise en œuvre du tas principal.
je peux répondre à votre deuxième question. Malloc échouera et retournera un pointeur nul. C'est pourquoi vous vérifiez toujours un pointeur null lors de l'allocation dynamique de la mémoire.
le tas est placé en dernier dans le segment de données du programme. brk()
est utilisé pour changer (étendre) la taille du tas. Lorsque le tas ne peut plus croître, tout appel malloc
échouera.
le segment de données est la partie de la mémoire qui contient toutes vos données statiques, lue à partir de l'exécutable au lancement et généralement remplie de zéro.
malloc utilise l'appel système brk pour allouer la mémoire.
inclure
int main(void){
char *a = malloc(10);
return 0;
}
exécuter ce programme simple avec strace, il appellera système brk.
-
l'appel système qui traite l'allocation de mémoire est
sbrk(2)
. Il augmente ou diminue l'espace d'adressage du processus par un nombre spécifié d'octets. -
la fonction d'allocation de mémoire,
malloc(3)
, implémente un type particulier d'allocation. La fonctionmalloc()
, qui utilisera probablement l'appel systèmesbrk()
.
l'appel système sbrk(2)
dans le kernel attribue un morceau supplémentaire d'espace au nom du processus. La fonction de bibliothèque malloc()
gère cet espace à partir de niveau utilisateur .