В компютрите, за да бъде процесът изпълним, той трябва да бъде поставен в паметта. За това трябва да се присвои поле на процес в паметта. Разпределението на паметта е важен проблем, който трябва да знаете, особено в архитектурите на ядрото и системата.
Нека да разгледаме подробно разпределението на паметта на Linux и да разберем какво се случва зад кулисите.
Как се извършва разпределението на паметта?
Повечето софтуерни инженери не знаят подробностите за този процес. Но ако сте кандидат за системен програмист, трябва да знаете повече за това. Когато разглеждате процеса на разпределение, е необходимо да навлезете в малко подробности относно Linux и glibc библиотека.
Когато приложенията се нуждаят от памет, те трябва да я поискат от операционната система. Тази заявка от ядрото естествено ще изисква системно извикване. Не можете сами да разпределите памет в потребителски режим.
В malloc() семейството от функции е отговорно за разпределението на паметта в езика C. Въпросът, който трябва да зададете тук, е дали malloc(), като glibc функция, прави директно системно извикване.
В ядрото на Linux няма системно извикване, наречено malloc. Има обаче две системни извиквания за изискванията за памет на приложенията, които са brk и mmap.
Тъй като ще изисквате памет във вашето приложение чрез glibc функции, може да се чудите кои от тези системни повиквания glibc използва в този момент. Отговорът е и двете.
Първото системно повикване: brk
Всеки процес има непрекъснато поле за данни. При системното извикване brk стойността на прекъсване на програмата, която определя границата на полето с данни, се увеличава и се извършва процесът на разпределение.
Въпреки че разпределението на паметта с този метод е много бързо, не винаги е възможно да се върне неизползваното пространство в системата.
Например, имайте предвид, че разпределяте пет полета, всяко с размер 16KB, със системното извикване brk чрез функцията malloc(). Когато приключите с номер две от тези полета, не е възможно да върнете съответния ресурс (освобождаване), така че системата да може да го използва. Защото ако намалите стойността на адреса, за да покажете мястото, откъдето започва вашето поле номер две, с извикване на brk, ще сте направили освобождаване на полета с номер три, четири и пет.
За да предотврати загуба на памет в този сценарий, реализацията на malloc в glibc следи местата, разпределени в полето за данни на процеса и след това посочва да го върне в системата с функцията free(), така че системата да може да използва свободното пространство за допълнителна памет разпределения.
С други думи, след като се разпределят пет области от 16KB, ако втората област се върне с функцията free() и друга област от 16KB се изисква отново след известно време, вместо да се увеличава областта с данни чрез системното извикване brk, се връща предишният адрес.
Ако обаче новопоисканата област е по-голяма от 16KB, тогава областта с данни ще бъде увеличена чрез разпределяне на нова област със системното извикване brk, тъй като зона две не може да се използва. Въпреки че зона номер две не се използва, приложението не може да я използва поради разликата в размера. Поради подобни сценарии има ситуация, наречена вътрешна фрагментация, и всъщност рядко можете да използвате всички части на паметта докрай.
За по-добро разбиране опитайте да компилирате и стартирате следното примерно приложение:
#включи <stdio.h>
#включи <stdlib.h>
#включи <unistd.h>
международенглавен(международен argc, char* argv[])
{
char *ptr[7];
международен н;
printf("Pid на %s: %d", argv[0], getpid());
printf("Първоначално прекъсване на програмата: %p", sbrk (0));
за (n=0; н<5; n++) ptr[n] = malloc (16 * 1024);
printf("След 5 x 16kB malloc: %p", sbrk (0));
Безплатно(ptr[1]);
printf("След освобождаване от втори 16kB: %p", sbrk (0));
ptr[5] = malloc (16 * 1024);
printf("След разпределяне на 6-то от 16kB: %p", sbrk (0));
Безплатно(ptr[5]);
printf("След освобождаване на последния блок: %p", sbrk (0));
ptr[6] = malloc (18 * 1024);
printf("След разпределяне на нови 18kB: %p", sbrk (0));
getchar();
връщане0;
}
Когато стартирате приложението, ще получите резултат, подобен на следния изход:
Pid на ./a.out: 31990
Първоначална програма прекъсване: 0x55ebcadf4000
След 5 x 16 kB malloc: 0x55ebcadf4000
След освобождаване от втори 16kB: 0x55ebcadf4000
След разпределяне на 6-то от 16 kB: 0x55ebcadf4000
След освобождаване на последния блок: 0x55ebcadf4000
След разпределяне на a нов18kB: 0x55ebcadf4000
Резултатът за brk със strace ще бъде както следва:
brk(НУЛА) = 0x5608595b6000
brk (0x5608595d7000) = 0x5608595d7000
Както виждаш, 0x21000 е добавен към крайния адрес на полето с данни. Можете да разберете това от стойността 0x5608595d7000. Така че приблизително 0x21000, или 132 KB памет е разпределена.
Тук трябва да се вземат предвид два важни момента. Първият е разпределението на повече от сумата, посочена в примерния код. Друг е кой ред код е причинил повикването brk, което е осигурило разпределението.
Рандомизиране на оформлението на адресното пространство: ASLR
Когато стартирате горното примерно приложение едно след друго, всеки път ще виждате различни стойности на адреса. Промяната на адресното пространство по този начин на случаен принцип значително усложнява работата на атаки за сигурност и повишава сигурността на софтуера.
Въпреки това, в 32-битови архитектури, осем бита обикновено се използват за рандомизиране на адресното пространство. Увеличаването на броя на битовете няма да е подходящо, тъй като адресируемата област над останалите битове ще бъде много ниска. Освен това използването само на 8-битови комбинации не прави нещата достатъчно трудни за нападателя.
В 64-битовите архитектури, от друга страна, тъй като има твърде много битове, които могат да бъдат разпределени за ASLR работа, се осигурява много по-голяма произволност и степента на сигурност се увеличава.
Ядрото на Linux също има правомощия Устройства, базирани на Android и функцията ASLR е напълно активирана на Android 4.0.3 и по-нови версии. Дори само поради тази причина, не би било погрешно да се каже, че 64-битов смартфон осигурява значително предимство в сигурността пред 32-битовите версии.
Като деактивирате временно функцията ASLR със следната команда, ще изглежда, че предишното тестово приложение връща същите стойности на адреса всеки път, когато се стартира:
ехо0 | sudo tee /proc/sys/kernel/randomize_va_space
За да го възстановите до предишното му състояние, ще бъде достатъчно да напишете 2 вместо 0 в същия файл.
Второто системно повикване: mmap
mmap е второто системно извикване, използвано за разпределение на паметта в Linux. С извикването mmap, свободното пространство в която и да е област от паметта се съпоставя с адресното пространство на извикващия процес.
При разпределение на паметта, извършено по този начин, когато искате да върнете втория 16KB дял с функцията free() в предишния пример brk, няма механизъм, който да предотврати тази операция. Съответният сегмент на паметта се премахва от адресното пространство на процеса. Той е маркиран като вече неизползван и се връща в системата.
Тъй като заделянията на паметта с mmap са много бавни в сравнение с тези с brk, е необходимо разпределение на brk.
С mmap всяка свободна област от паметта се съпоставя с адресното пространство на процеса, така че съдържанието на разпределеното пространство се нулира, преди този процес да завърши. Ако нулирането не е направено по този начин, данните, принадлежащи на процеса, който преди е използвал съответната област на паметта, също могат да бъдат достъпни от следващия несвързан процес. Това би направило невъзможно да се говори за сигурност в системите.
Значението на разпределението на паметта в Linux
Разпределението на паметта е много важно, особено при въпросите за оптимизация и сигурност. Както се вижда в примерите по-горе, неразбирането на този проблем може да означава унищожаване на сигурността на вашата система.
Дори концепции, подобни на push и pop, които съществуват в много езици за програмиране, се основават на операции за разпределяне на паметта. Способността да се използва и овладява добре системната памет е жизненоважна както при програмирането на вградената система, така и при разработването на сигурна и оптимизирана системна архитектура.
Ако и вие искате да потопите пръстите си в разработката на ядрото на Linux, помислете първо за овладяването на езика за програмиране C.
Кратко въведение в езика за програмиране C
Прочетете Следващото
Свързани теми
- Linux
- Компютърна памет
- Linux ядро
За автора
Инженер и разработчик на софтуер, който е фен на математиката и технологиите. Винаги е харесвал компютрите, математиката и физиката. Той е разработил проекти на двигатели за игри, както и машинно обучение, изкуствени невронни мрежи и библиотеки с линейна алгебра. Освен това продължава да работи върху машинно обучение и линейни матрици.
Абонирайте се за нашия бюлетин
Присъединете се към нашия бюлетин за технически съвети, ревюта, безплатни електронни книги и ексклузивни оферти!
Щракнете тук, за да се абонирате