§ 11. Память
01 Второй важной и наиболее часто используемой группой системных вызовов являются функции работы с памятью. Ядро выделяет память постранично (фактически, ядро выделяет страницы, а не произвольные блоки памяти), размер страницы равен 4 Кб. Если за один раз выделяется очень много страниц, то ядро может объединить их в одну большую размером 2 Мб или 2 Гб в зависимости от настроек. Функция mmap
используется для выделения памяти, а также для отображения содержимого файлов на страницы памяти. Первый аргумент обозначает адрес из адресного пространства процесса, где должна отобразиться страница. Выбор адреса полезен при создании страниц, с которым одновременно работает сразу несколько процессов. Третий аргумент задает права доступа. Как правило используют чтение/запись, но не исполнение кода, чтобы избежать возможных уязвимостей. Следующий аргумент — это флаги. Их в Linux больше десяти. Наиболее часто используются либо анонимные страницы, то есть память доступная только текущему процессу, либо общедоступные процессы, то есть память доступная сразу нескольким процессам и использующаяся для межпроцессного взаимодействия. Также общедоступная память используется для отображения файлов, чтобы все процессы, отображающие его в память, видели изменения его содержимого. Предпоследний аргумент — это файловый дескриптор, используемые для отображения содержимого файла, а последний аргумент — это отступ в байтах внутри файла.
size_t size = 4096; void* ptr = mmap( // выделить страницы памяти nullptr, // адрес size, // размер в байтах PROT_READ|PROT_WRITE, // права доступа MAP_PRIVATE|MAP_ANONYMOUS, // опции -1, // файловый дескриптор 0 // отступ внутри файла ); munmap(ptr, size); // освободить страницы памяти
02 Функция munmap
освобождает отображенную страницу, записывая ее содержимое в связанный с ней файл, если таковой имеется.
03 Эти функции используются для выделения и освобождения памяти в соответствующих функциях из стандартной библиотеки C (malloc
и др.) и операторах new
и delete
языка C++.
04 Функция mremap
аналогичная функции realloc
языка C. Она используется для изменения размера выделенного участка памяти.
05 Наконец, функция madvise
используется для управления выделенным участком памяти. В таблице показаны четыре флага в качестве примера. Первые два флага являются указанием ядру, как будут использоваться страницы памяти: имеет ли смысл делать упреждающее чтение или нет? Вторые два флага позволяют пометить страницы как нужные или ненужные, что используется для для эффективного чтения файлов большого размера.
ptr = mremap( ptr, // текущий адрес size, // текущий размер size*2, // новый размер MREMAP_MAYMOVE // опции ); size *= 2; madvise(ptr, size, MADV_SEQUENTIAL); // управление страницами
Флаг | Описание |
---|---|
MADV_SEQUENTIAL | последовательный |
MADV_RANDOM | произвольный |
MADV_WILLNEED | скоро понадобится |
MADV_DONTNEED | больше не нужен |
madvise
.06 В различных источниках можно найти упоминание того, что раз ядро использует страницы памяти для чтения и записи файлов, то использование этих страниц напрямую позволит наиболее эффективно читать и писать файлы. Чтобы проверить это утверждения я написал небольшую программу, которая копировала файлы разных размеров с помощью стандартных средств C++, с помощью системного вызова read
и с помощью системного вызова mmap
. Как видно из графиков для больших файлов использование страниц напрямую действительно наиболее эффективно при чтении, а для маленьких файлов (размером не более трех страниц) использование обычных операций чтения и записи позволяет получить небольшое преимущество.
mmap
по сравнению со стандартными спообами чтения файлов. 07 Куда же попадают страницы, на которые отображаются файлы? Все очень просто: они так и остаются в отдельной области памяти ядра, называемой кэшем. Делается это из соображений эффективности, Linux настраивается путем изменения текстовых файлов. Любое повторное чтение файла происходит быстрее, чем первое чтение, что оптимизирует скорость работы системы. Как только свободной памяти начинает не хватать приложениям, вместо нее используются страницы из кэша.
$ free -m total used free shared buff/cache available Mem: 7973 1669 3860 64 2442 6140 Swap: 7999 55 7944
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
.
Видео
- 00:00 Введение.
- 03:40 Вызов
mmap
. - 15:25 Вызов
mremap
. - 19:25 Вызов
madvise
. - 21:35 Чтение и запись файлов с помощью
mmap
. - 37:14 Вопрос про
stat
.