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

Инженер и разработчик программного обеспечения, фанат математики и технологий. Ему всегда нравились компьютеры, математика и физика. Он разработал проекты игровых движков, а также машинное обучение, искусственные нейронные сети и библиотеки линейной алгебры. Кроме того, продолжает работать над машинным обучением и линейными матрицами.
Подпишитесь на нашу рассылку
Подпишитесь на нашу рассылку технических советов, обзоров, бесплатных электронных книг и эксклюзивных предложений!
Нажмите здесь, чтобы подписаться