Op computers moet een proces, om uitvoerbaar te zijn, in het geheugen worden geplaatst. Hiervoor moet een veld worden toegewezen aan een proces in het geheugen. Geheugentoewijzing is een belangrijk punt om op te letten, vooral in kernel- en systeemarchitecturen.

Laten we de toewijzing van Linux-geheugen in detail bekijken en begrijpen wat er achter de schermen gebeurt.

Hoe wordt geheugentoewijzing gedaan?

De meeste software-engineers kennen de details van dit proces niet. Maar als u een kandidaat voor systeemprogrammeur bent, moet u er meer over weten. Wanneer we naar het toewijzingsproces kijken, is het noodzakelijk om in een klein detail in te gaan op Linux en de glibc bibliotheek.

Wanneer applicaties geheugen nodig hebben, moeten ze dit opvragen bij het besturingssysteem. Dit verzoek van de kernel vereist natuurlijk een systeemaanroep. U kunt in de gebruikersmodus niet zelf geheugen toewijzen.

De mallok() familie van functies is verantwoordelijk voor geheugentoewijzing in de C-taal. De vraag die hier moet worden gesteld, is of malloc(), als een glibc-functie, een directe systeemaanroep doet.

Er is geen systeemaanroep genaamd malloc in de Linux-kernel. Er zijn echter twee systeemaanroepen voor geheugenvereisten voor toepassingen, namelijk: brk en mmap.

Aangezien u via glibc-functies geheugen in uw toepassing gaat opvragen, vraagt ​​u zich misschien af ​​welke van deze systeemaanroepen glibc op dit moment gebruikt. Het antwoord is beide.

De eerste systeemoproep: brk

Elk proces heeft een aangrenzend gegevensveld. Met de brk-systeemaanroep wordt de programma-onderbrekingswaarde, die de limiet van het gegevensveld bepaalt, verhoogd en wordt het toewijzingsproces uitgevoerd.

Hoewel geheugentoewijzing met deze methode erg snel is, is het niet altijd mogelijk om ongebruikte ruimte terug te geven aan het systeem.

Houd er bijvoorbeeld rekening mee dat u vijf velden, elk 16 KB groot, toewijst met de systeemaanroep brk via de functie malloc(). Wanneer u klaar bent met nummer twee van deze velden, is het niet mogelijk om de betreffende resource terug te geven (deallocation) zodat het systeem deze kan gebruiken. Want als je de adreswaarde verkleint om de plaats weer te geven waar je veld nummer twee begint, met een call to brk, heb je deallocatie gedaan voor velden nummer drie, vier en vijf.

Om geheugenverlies in dit scenario te voorkomen, bewaakt de malloc-implementatie in glibc de toegewezen plaatsen in het procesgegevensveld en specificeert vervolgens om het terug te sturen naar het systeem met de functie free(), zodat het systeem de vrije ruimte kan gebruiken voor meer geheugen toewijzingen.

Met andere woorden, nadat vijf gebieden van 16 KB zijn toegewezen, als het tweede gebied wordt geretourneerd met de functie free() en nog een gebied van 16 KB wordt na een tijdje opnieuw gevraagd, in plaats van het gegevensgebied te vergroten via de brk-systeemaanroep, wordt het vorige adres geretourneerd.

Als het nieuw aangevraagde gebied echter groter is dan 16 KB, dan wordt het gegevensgebied vergroot door een nieuw gebied toe te wijzen aan de brk-systeemaanroep, aangezien gebied twee niet kan worden gebruikt. Hoewel gebied nummer twee niet in gebruik is, kan de applicatie het vanwege het verschil in grootte niet gebruiken. Vanwege dit soort scenario's is er een situatie die interne fragmentatie wordt genoemd, en in feite kun je zelden alle delen van het geheugen ten volle gebruiken.

Probeer voor een beter begrip de volgende voorbeeldtoepassing te compileren en uit te voeren:

#erbij betrekken <stdio.h>
#erbij betrekken <stdlib.h>
#erbij betrekken <unistd.h>
inthoofd(int argc, char* argv[])
{
char *ptr[7];
int n;
printf("Pid van %s: %d", argv[0], getpid());
printf("Initiële programma-onderbreking: %p", sbrk (0));
voor (n=0; n<5; n++) ptr[n] = malloc (16 * 1024);
printf("Na 5 x 16kB malloc: %p", sbrk (0));
vrij(ptr[1]);
printf("Na vrij van tweede 16kB: %p", sbrk (0));
ptr[5] = malloc (16 * 1024);
printf("Na toewijzing 6e van 16kB: %p", sbrk (0));
vrij(ptr[5]);
printf("Na het vrijmaken van het laatste blok: %p", sbrk (0));
ptr[6] = malloc (18 * 1024);
printf("Na het toewijzen van een nieuwe 18kB: %p", sbrk (0));
getchar();
opbrengst0;
}

Wanneer u de toepassing uitvoert, krijgt u een resultaat dat lijkt op de volgende uitvoer:

Pid van ./a.uit: 31990
Eerste programma pauze: 0x55ebcadf4000
Na 5 x 16kB malloc: 0x55ebcadf4000
Na vrij van tweede 16kB: 0x55ebcadf4000
Na het toewijzen van de 6e van 16 kB: 0x55ebcadf4000
Na het vrijmaken van het laatste blok: 0x55ebcadf4000
Na het toewijzen van a nieuwe18kB: 0x55ebcadf4000

De uitvoer voor brk met strace is als volgt:

brk(NUL) = 0x5608595b6000
brk (0x5608595d7000) = 0x5608595d7000

Zoals je kan zien, 0x21000 is toegevoegd aan het eindadres van het gegevensveld. U kunt dit begrijpen aan de hand van de waarde 0x5608595d7000. dus ongeveer 0x21000, of er is 132 KB geheugen toegewezen.

Er zijn hier twee belangrijke punten om te overwegen. De eerste is de toewijzing van meer dan het in de voorbeeldcode gespecificeerde bedrag. Een andere is welke regel code de brk-aanroep veroorzaakte die de toewijzing opleverde.

Randomisatie van adresruimte-indeling: ASLR

Wanneer u de bovenstaande voorbeeldtoepassing één voor één uitvoert, ziet u elke keer andere adreswaarden. Door de adresruimte op deze manier willekeurig te laten veranderen, wordt het werk van beveiligingsaanvallen en verhoogt de softwarebeveiliging.

In 32-bits architecturen worden echter over het algemeen acht bits gebruikt om de adresruimte willekeurig te maken. Het verhogen van het aantal bits is niet geschikt omdat het adresseerbare gebied over de resterende bits erg laag zal zijn. Ook maakt het gebruik van alleen 8-bit combinaties het de aanvaller niet moeilijk genoeg.

Aan de andere kant, in 64-bits architecturen, omdat er te veel bits zijn die kunnen worden toegewezen voor ASLR-bewerkingen, wordt een veel grotere willekeurigheid geboden en neemt de mate van beveiliging toe.

Linux-kernel werkt ook Android-apparaten en de ASLR-functie is volledig geactiveerd op Android 4.0.3 en hoger. Alleen al om deze reden zou het niet verkeerd zijn om te zeggen dat een 64-bits smartphone een aanzienlijk beveiligingsvoordeel biedt ten opzichte van 32-bits versies.

Door de ASLR-functie tijdelijk uit te schakelen met de volgende opdracht, lijkt het alsof de vorige testtoepassing dezelfde adreswaarden retourneert elke keer dat deze wordt uitgevoerd:

echo0 | sudo tee /proc/sys/kernel/randomize_va_space

Om het in zijn vorige staat te herstellen, volstaat het om 2 in plaats van 0 in hetzelfde bestand te schrijven.

De tweede systeemoproep: mmap

mmap is de tweede systeemaanroep die wordt gebruikt voor geheugentoewijzing op Linux. Met de mmap-aanroep wordt de vrije ruimte in elk gebied van het geheugen toegewezen aan de adresruimte van het aanroepende proces.

In een geheugentoewijzing die op deze manier is uitgevoerd, is er geen mechanisme om deze bewerking te voorkomen wanneer u de tweede partitie van 16 KB wilt retourneren met de functie free() in het vorige brk-voorbeeld. Het betreffende geheugensegment wordt uit de adresruimte van het proces verwijderd. Het wordt gemarkeerd als niet langer gebruikt en teruggestuurd naar het systeem.

Omdat geheugentoewijzingen met mmap erg traag zijn in vergelijking met die met brk, is brk-toewijzing nodig.

Met mmap wordt elk vrij geheugengebied toegewezen aan de adresruimte van het proces, zodat de inhoud van de toegewezen ruimte opnieuw wordt ingesteld voordat dit proces is voltooid. Als de reset niet op deze manier werd uitgevoerd, zouden de gegevens die behoren tot het proces dat eerder het relevante geheugengebied gebruikte, ook toegankelijk zijn voor het volgende niet-gerelateerde proces. Dit zou het onmogelijk maken om over beveiliging in systemen te praten.

Belang van geheugentoewijzing in Linux

Geheugentoewijzing is erg belangrijk, vooral bij optimalisatie- en beveiligingsproblemen. Zoals te zien is in de bovenstaande voorbeelden, kan het niet volledig begrijpen van dit probleem betekenen dat de beveiliging van uw systeem wordt vernietigd.

Zelfs concepten die vergelijkbaar zijn met push en pop en die in veel programmeertalen voorkomen, zijn gebaseerd op geheugentoewijzingsbewerkingen. Het goed kunnen gebruiken en beheersen van het systeemgeheugen is van vitaal belang, zowel bij het programmeren van embedded systemen als bij het ontwikkelen van een veilige en geoptimaliseerde systeemarchitectuur.

Als je ook je tenen wilt verdiepen in de ontwikkeling van de Linux-kernel, overweeg dan om eerst de programmeertaal C onder de knie te krijgen.

Een korte introductie tot de programmeertaal C

Lees volgende

DelenTweetenDelenE-mail

Gerelateerde onderwerpen

  • Linux
  • Computer geheugen
  • Linux-kernel

Over de auteur

Fatih Küçükkarakurt (7 artikelen gepubliceerd)

Een ingenieur en softwareontwikkelaar die een fan is van wiskunde en technologie. Hij heeft altijd van computers, wiskunde en natuurkunde gehouden. Hij heeft zowel game-engine-projecten als machine learning, kunstmatige neurale netwerken en lineaire algebra-bibliotheken ontwikkeld. Bovendien blijft men werken aan machine learning en lineaire matrices.

Meer van Fatih Küçükkarakurt

Abonneer op onze nieuwsbrief

Word lid van onze nieuwsbrief voor technische tips, recensies, gratis e-boeken en exclusieve deals!

Klik hier om je te abonneren