§ 11. Память
01 Второй важной и наиболее часто используемой группой системных вызовов являются функции работы с памятью. Ядро выделяет память постранично (фактически, ядро выделяет страницы, а не произвольные блоки памяти), размер страницы равен 4 Кб. Если за один раз выделяется очень много страниц, то ядро может объединить их в одну большую размером 2 Мб или 2 Гб в зависимости от настроек. Функция mmap
используется для выделения памяти, а также для отображения содержимого файлов на страницы памяти. Первый аргумент обозначает адрес из адресного пространства процесса, где должна отобразиться страница. Выбор адреса полезен при создании страниц, с которым одновременно работает сразу несколько процессов. Третий аргумент задает права доступа. Как правило используют чтение/запись, но не исполнение кода, чтобы избежать возможных уязвимостей. Следующий аргумент — это флаги. Их в Linux больше десяти. Наиболее часто используются либо анонимные страницы, то есть память доступная только текущему процессу, либо общедоступные процессы, то есть память доступная сразу нескольким процессам и использующаяся для межпроцессного взаимодействия. Также общедоступная память используется для отображения файлов, чтобы все процессы, отображающие его в память, видели изменения его содержимого. Предпоследний аргумент — это файловый дескриптор, используемые для отображения содержимого файла, а последний аргумент — это отступ в байтах внутри файла.
02 Функция munmap
освобождает отображенную страницу, записывая ее содержимое в связанный с ней файл, если таковой имеется.
03 Эти функции используются для выделения и освобождения памяти в соответствующих функциях из стандартной библиотеки C (malloc
и др.) и операторах new
и delete
языка C++.
04 Функция mremap
аналогичная функции realloc
языка C. Она используется для изменения размера выделенного участка памяти.
05 Наконец, функция madvise
используется для управления выделенным участком памяти. В таблице показаны четыре флага в качестве примера. Первые два флага являются указанием ядру, как будут использоваться страницы памяти: имеет ли смысл делать упреждающее чтение или нет? Вторые два флага позволяют пометить страницы как нужные или ненужные, что используется для для эффективного чтения файлов большого размера.
Флаг | Описание |
---|---|
MADV_SEQUENTIAL | последовательный |
MADV_RANDOM | произвольный |
MADV_WILLNEED | скоро понадобится |
MADV_DONTNEED | больше не нужен |
madvise
.06 В различных источниках можно найти упоминание того, что раз ядро использует страницы памяти для чтения и записи файлов, то использование этих страниц напрямую позволит наиболее эффективно читать и писать файлы. Чтобы проверить это утверждения я написал небольшую программу, которая копировала файлы разных размеров с помощью стандартных средств C++, с помощью системного вызова read
и с помощью системного вызова mmap
. Как видно из графиков для больших файлов использование страниц напрямую действительно наиболее эффективно при чтении, а для маленьких файлов (размером не более трех страниц) использование обычных операций чтения и записи позволяет получить небольшое преимущество.
07 Куда же попадают страницы, на которые отображаются файлы? Все очень просто: они так и остаются в отдельной области памяти ядра, называемой кэшем. Делается это из соображений эффективности, Linux настраивается путем изменения текстовых файлов. Любое повторное чтение файла происходит быстрее, чем первое чтение, что оптимизирует скорость работы системы. Как только свободной памяти начинает не хватать приложениям, вместо нее используются страницы из кэша.
08 Подведем итог.
- Объем выделенной памяти кратен размеру страницы (4Кб).
- При чтении/записи ядро отображает содержимое файлов на страницы памяти, даже если не использовать
mmap
. - Все считанные файлы попадают в кэш.
- Память можно сделать
видимой
другим процессам.
Задания
Буферы1 балл
09 Напишите класс ByteBuffer
, который представляет собой массив байт, размер которого можно изменять динамически. Для этого в конструкторе выделите память с помощью системного вызова mmap
, в деструкторе освободите память с помощью munmap
. Не забудьте про проверку возвращаемого значения каждого системного вызова!
Перемещение1 балл
10 Добавьте в класс ByteBuffer
конструкторы и операторы перемещения. Проверьте, что память корректно освобождается с помощью санитайзеров.
Изменение размера1 балл
11 Добавьте в класс ByteBuffer
метод для изменения размера выделенной области памяти resize
, который использует вызов mremap
.
File + ByteBuffer2 балла
12 Объедините класс File
из заданий к предыдущем занятию с классом ByteBuffer
. Для этого сделайте File
полем класса ByteBuffer
и добавьте конструктор ByteBuffer
, который позволяет создавать буфер из файла: вам понадобится вызов stat
, чтобы узнать размер файла. Также модифицируйте деструктор, так чтобы он записывал буфер в файл с помощью вызова msync
.