Unix2018/Управление памятью
Содержание
Виртуальная память
В современных операционных системах каждый процесс работает в своём собственном пространстве памяти. Вместо того чтобы сопоставлять адреса памяти непосредственно с аппаратными адресами физической памяти, операционная система служит в качестве уровня абстракции оборудования и создаёт пространство виртуальной памяти для каждого процесса. Отображение между виртуальным адресом и адресом физической памяти выполняется CPU с использованием таблицы трансляции адресов каждого процесса, которую поддерживает ядро (каждый раз, когда ядро меняет выполняемый процесс на конкретном ядре CPU, оно меняет таблицу трансляции этого ядра).
Виртуальная память служит для нескольких целей.
- Изоляция процессов. Процесс в пользовательском пространстве может только выполнять доступ к памяти по виртуальным адресам. Как следствие, он может получить доступ только к данным, которые ранее были отображены в его собственное виртуальное пространство и, следовательно, не может получить доступ к памяти других процессов (если явно не настроен общий доступ).
- Абстрагирование от аппаратного обеспечения. Ядро может изменять физический адрес, на который отображается виртуальный адрес. Оно также может не предоставлять физическую память для определенного виртуального адреса, пока это не станет фактически необходимым. Кроме того, ядро может вытеснить память на диск, когда она не использовалась в течение длительного времени и системе не хватает физической памяти. Это глобально даёт большую свободу ядру, его единственным ограничением является то, что когда программа считывает память, она должна считывать то, что ранее записала туда.
- Возможность дать адрес сущностям, которые не находятся в RAM. Это принцип, лежащий в основе mmap и отображения файлов в память. Вы можете дать виртуальный адрес файлу, чтобы к данным файла можно было обращаться так же, как если бы это был массив в памяти. Это очень полезная абстракция, которая помогает сохранить код довольно простым. Поскольку на 64-битных системах у вас есть огромное виртуальное пространство, если хотите, вы можете отмапить весь свой жёсткий диск в виртуальную память.
- Общий доступ. Ядро может избежать двойной загрузки данных в физическую память, заставляя виртуальные адреса в процессах, которые используют одни и те же ресурсы, указывать в одну и ту же физическую память (даже если фактический виртуальный адрес специфичен для каждого процесса). Следствием совместного использования является использование copy-on-write (COW) ядром: когда два процесса используют одни и те же данные, но один из них изменяет их, а другой не может видеть изменение, ядро будет создаст копию в момент, когда данные будут изменены. Не так давно операционные системы также получили возможность обнаруживать идентичную память в нескольких адресных пространствах и автоматически превращать их в одну физическую память, в Linux это называется KSM (Kernel SamePage Merging).
Самый известный пример COW — это fork(). После fork оба процесса продолжают точно в одной точке с теми же открытыми файлами и той же памятью. Благодаря COW, fork не будет дублировать память процесса: только данные, которые модифицируются либо родителем, либо ребёнком, дублируются в RAM. Поскольку в большинстве применений после fork сразу следует вызов exec, который делает недействительным все адресное пространство, механизм COW позволяет избежать полной бесполезной копии памяти родительского процесса.
Другим побочным эффектом является то, что fork создает снимок (частной) памяти процесса с минимальными затратами. Если вы хотите выполнить некоторую операцию в памяти процесса, не подвергая риску его данные, и не хотите добавлять дорогостоящий механизм блокировки, чреватый ошибками, просто используйте fork, выполняйте свою работу и сообщайте результат вашего вычисления обратно в родительский процесс (кодом возврата, файлом, общей памятью, каналом, ...).
Это будет работать очень хорошо, пока ваши вычисления будут достаточно быстрыми, так что большая часть памяти остается разделённой между родительским и дочерним процессами. Это также способствует тому, что ваш код остаётся простым, сложность скрыта в коде менеджера памяти в ядре, а не в вашем.
Страницы
Виртуальная память делится на страницы. Размер размера страницы задается процессором и обычно на x86-64 составляет 4 KiB. Это означает, что управление памятью в ядре выполняется с точностью до страницы. Когда вам понадобится новая память, ядро предоставит вам одну или несколько страниц. При освобождении памяти вы вернёте одну или несколько страниц... Каждый более гранулярный API (например malloc) реализуется в пространстве пользователя.
Для каждой выделенной страницы ядро хранит набор разрешений: страница может быть читаемой, записываемой и/или исполняемой (обратите внимание, что не все комбинации возможны). Эти разрешения устанавливаются либо при отображении памяти, либо при помощи системного вызова mprotect(). Страницы, которые еще не выделены, недоступны. Когда вы пытаетесь выполнить запрещённое действие над страницей (например, чтение данных из страницы без разрешения на чтение), вы вызываете (в Linux) ошибку сегментации (Segmentation Fault). В качестве примечания отметим, что, поскольку ошибка сегментации имеет детализацию до страницы, не всякий выход за границы буфера приводит к segfault.
Виртуальные адреса
Хотя виртуальные адреса на x86-64 имеют разрядность в 64 бита, текущие реализации не позволяют использовать всё виртуальное адресное пространство из 264 байт (16 экзабайт). В обозримом будущем большинству операционных систем и приложений не потребуется такое большое адресное пространство, поэтому внедрение таких широких виртуальных адресов просто увеличит сложность и расходы на трансляцию адреса без реальной выгоды.
Поэтому ещё в первых реализациях архитектуры AMD решила, что фактически при трансляции адресов будут использоваться только младшие 48 бит виртуального адреса. Кроме того, спецификация AMD требует, что старшие 16 бит любого виртуального адреса, биты с 48-го по 63-й, должны быть копиями бита 47 (по принципу sign extension). Адреса, соответствующие этому правилу, называются «канонической формой». Канонические адреса в общей сложности составляют 256 терабайт полезного виртуального адресного пространства. Это по-прежнему в 65536 раз больше, чем 4 ГБ виртуального адресного пространства 32-битных машин.
Отображение виртуальных адресов в физические
Исполняемый процессором пользовательский поток обращается к памяти с помощью адреса виртуальной памяти, который делится на номер страницы и смещение внутри страницы. Процессор преобразует номер виртуальной страницы в адрес соответствующей ей физической страницы при помощи буфера ассоциативной трансляции (TLB, translation lookaside buffer). Если ему не удалось это сделать, то требуется дозаполнение буфера путём обращения к таблице страниц (так называемый Page Walk).
Полная иерархия сопоставления страниц размером 4 КБ для всего 48-битного пространства займет немногим больше 512 ГБ ОЗУ (около 0.195% от виртуального пространства 256 ТБ).
Типы памяти
Не все память, выделенная в пространстве виртуальной памяти, одинакова. Мы можем классифицировать его по двум осям:
- первая ось — это то, является ли память частной (специфичной для этого процесса) или общей,
- вторая ось — это то, является ли память файловой или нет (в этом случае говорят, что она анонимная).
Это создает классификацию с 4 классами памяти:
PRIVATE | SHARED | |
ANONYMOUS | 1
stack malloc() mmap(ANON, PRIVATE) brk()/sbrk() |
2
mmap(ANON, SHARED) |
FILE-BACKED | 3
mmap(fd, PRIVATE) binary/shared libraries |
4
mmap(fd, SHARED) |
Private
Память, специфичная для процесса. Таковой является большая часть памяти, с которой вы работаете (стек, куча, статические переменные).
Поскольку изменения, сделанные в частной памяти, не видны другим процессам, они являются предметом COW. В качестве побочного эффекта это означает, что даже если память является частной, несколько процессов могут совместно использовать одну и ту же физическую память для хранения данных. Например, это касается двоичных файлов программ и разделяемых библиотек.
Общим заблуждением является то, что KDE занимает много оперативной памяти, потому что каждый отдельный процесс загружает Qt и KDElib, однако благодаря механизму COW все процессы будут использовать ту же физическую память для частей этих библиотек, предназначенных только для чтения.
В случае файловой частной памяти изменения, сделанные процессом, не записываются обратно в основной файл, однако изменения, внесенные в файл, могут быть или могут не быть доступны для процесса.
Общая память — это нечто, предназначенное для взаимодействия между процессами. Она может быть создана только путем явного запроса с помощью правильного вызова mmap() или отдельного вызова (shm*). Когда процесс записывает в общую память, модификация видна всем процессам, которые отображают себе в адресные пространства эту же память.
В случае, если память является file-backed, любой процесс, отображающий файл, будет видеть изменения в файле, поскольку эти изменения распространяются через сам файл.
Anonymous
Анонимная память — это чисто оперативная память (RAM). Тем не менее, ядро фактически не будет отображать эту память на физический адрес, прежде чем он в него будет фактически выполнена запись. Как следствие, анонимная память не добавляет никакой нагрузки на ядро до её фактического использования. Это позволяет процессу «резервировать» большую часть памяти в адресном пространстве виртуальной памяти без использования RAM. Как следствие, ядро позволяет зарезервировать больше памяти, чем доступно. Такое поведение часто упоминается как over-commit (или overcommitment).
File-backed
Когда память «поддерживается» файлами, данные загружаются с диска. В большинстве случаев они загружаются по требованию, однако вы можете дать подсказки ядру, чтобы оно могло предварительно подгрузить память перед чтением. Это помогает сохранять скорость вашей программы, когда вы знаете, что у вас есть определённый паттерн доступа (в основном последовательный доступ). Чтобы избежать использования слишком большого количества RAM, вы также можете сообщить ядру, что вы не хотели бы больше держать определённые страницы в RAM, при этом не нужно освобождать всю область памяти целиком. Советы ядру даются вызовами madvise().
Когда системе не хватает физической памяти, ядро попытается перенести некоторые данные из RAM на диск. Если память является file-backed и shared, это довольно просто. Поскольку файл является источником данных, они просто удаляются из RAM, а затем, в следующий раз, когда их нужно будет прочитать, они будут загружены из файла.
Ядро также может решить удалить анонимную/частную память из RAM. В этом случае эти данные записываются в определенном месте на диске. Говорят, что эти данные были вытеснены — «swapped out». Затем оно работает так же, как и для файловой памяти: когда к ней требуется доступ, она считывается с диска и перезагружается в RAM.
Благодаря использованию виртуального адресного пространства, замена страниц в обе стороны полностью прозрачна для процесса... Кроме того, что есть ощутимые задержки, вызванные дисковым вводом-выводом.
Резидентная память
Всё, что мы рассматривали выше, относилось к виртуальной памяти. Речь шла о резервировании адресов памяти, но зарезервированный адрес не всегда сразу отображается в физическую память ядром. В большинстве случаев ядро откладывает фактическое распределение физической памяти до момента первого доступа (или время первой записи в некоторых случаях)... и даже тогда это делается с детализацией до страницы (обычно 4 KiB). Более того, некоторые страницы могут быть вытеснены после выделения, это означает, что они записываются на диск, чтобы другие страницы могли быть помещены в RAM.
Как следствие, узнать фактический размер физической памяти, используемой процессом (так называемая резидентная память процесса) действительно непросто... и единственным компонентом системы, который на самом деле знает об этом, является ядро (это его работа). К счастью, ядро предоставляет некоторые интерфейсы, которые позволят вам получить некоторую статистику о системе или конкретном процессе.
Примеры
Private Anonymous
- Размеры в mmap() и в munmap() округляются вверх до кратного размеру страницы.
- Страницы всегда заполнены нулями. Из соображений безопасности ОС не может предоставить процессу «б/у» страницы с данными другого процесса, так как в них могут быть конфиденциальные сведения.
- Как мы ранее обсуждали на курсе по C, функция malloc() при запросе большого объёма памяти выделяет блок у ОС именно посредством одного вызова mmap().
#include <stdio.h> #include <stdlib.h> #include <sys/mman.h> int main() { size_t n = 10; void* addr = mmap(NULL, n * sizeof(int), PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); if (addr == MAP_FAILED) { perror("mmap()"); return 1; } int* a = addr; for (size_t i = 0; i < n; ++i) { printf("%d ", a[i]); } printf("\n"); if (munmap(addr, n * sizeof(int)) == -1) { perror("munmap()"); return 1; }; return 0; }
File-backed
Рассмотрим следующий пример. По умолчанию он делает mmap на файл в режиме MAP_PRIVATE, с ключом -s — в режиме MAP_SHARED.
#include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/mman.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> #include <fcntl.h> void UseMemory(int* a, size_t n) { printf("Initially:\n"); for (size_t i = 0; i < n; ++i) { printf("%d ", a[i]); } printf("\n"); for (size_t i = 0; i < n; ++i) { a[i] += i; } printf("Finally:\n"); for (size_t i = 0; i < n; ++i) { printf("%d ", a[i]); } printf("\n"); } int RunMain(int fd, bool shared) { size_t n = 10; if (ftruncate(fd, n * sizeof(int)) == -1) { perror("ftruncate"); return 1; } int flag = shared ? MAP_SHARED : MAP_PRIVATE; void* addr = mmap(NULL, n * sizeof(int), PROT_READ | PROT_WRITE, flag, fd, 0); if (addr == MAP_FAILED) { perror("mmap()"); return 1; } UseMemory(addr, n); if (munmap(addr, n * sizeof(int)) == -1) { perror("munmap()"); return 1; }; return 0; } int main(int argc, char** argv) { bool shared = false; if (argc > 1) { if (strcmp(argv[1], "-s") == 0) { shared = true; } } int fd = open("data.bin", O_RDWR | O_CREAT, 0666); if (fd == -1) { perror("open"); return 1; } int ret = RunMain(fd, shared); if (close(fd) == -1) { perror("close"); return 1; } return ret; }
Отметим следующие особенности.
- Файл должен быть достаточного размера, иначе можно получить ошибку Bus error (core dumped).
- В режиме private все изменения не сохраняются в файле и теряются при завершении программы. В режиме shared изменения автоматически записываются в файл.
Такая память используется для обмена данными между процессами (IPC).
Способ работает быстро, но требует от разработчика самостоятельного создания протокола общения и контроля за синхронизацией процессов (чтобы не было гонки: один пишет в память, другой читает из неё).
Есть два способа работы с такой памятью:
- shmget
- mmap
shmget
Интересующиеся могут посмотреть книжку Advanced Linux Programming: Chapter 5. Interprocess Communication
Этот способ иногда называют SysV-style и он считается в некотором роде устаревшим (deprecated).
Область общей памяти идентифицируется целым числом. Нельзя ограничить список процессов, которые могут иметь к нему доступ. Зато можно выставлять права типа 644, как на файлы.
#include <stdio.h> #include <sys/shm.h> #include <sys/stat.h> int main () { int segment_id; char* shared_memory; struct shmid_ds shmbuffer; int segment_size; const int shared_segment_size = 0x6400; /* Allocate a shared memory segment. */ segment_id = shmget (IPC_PRIVATE, shared_segment_size, IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR); /* Attach the shared memory segment. */ shared_memory = (char*) shmat (segment_id, 0, 0); printf ("shared memory attached at address %p\n", shared_memory); /* Determine the segment’s size. */ shmctl (segment_id, IPC_STAT, &shmbuffer); segment_size = shmbuffer.shm_segsz; printf ("segment size: %d\n", segment_size); /* Write a string to the shared memory segment. */ sprintf (shared_memory, "Hello, world."); /* Detach the shared memory segment. */ shmdt (shared_memory); /* Reattach the shared memory segment, at a different address. */ shared_memory = (char*) shmat (segment_id, (void*) 0x5000000, 0); printf ("shared memory reattached at address %p\n", shared_memory); /* Print out the string from shared memory. */ printf ("%s\n", shared_memory); /* Detach the shared memory segment. */ shmdt (shared_memory); /* Deallocate the shared memory segment. */ shmctl (segment_id, IPC_RMID, 0); return 0; }
Для дебага используется команда ipcs.
mmap
В Linux 2.4 появилась возможность комбинировать флаги MAP_SHARED | MAP_ANONYMOUS у функции mmap.
Эта память анонимная, доступ к ней могут получить только процессы-потомки, полученные посредством форков.
#include <stdbool.h> #include <stdio.h> #include <string.h> #include <sys/mman.h> #include <sys/wait.h> #include <unistd.h> #define BUFSIZE 4096 int Run(char* buf) { strcpy(buf, "[empty]"); printf("buf: %s\n", buf); pid_t child = fork(); if (child == -1) { perror("fork"); return 1; } if (child == 0) { strcpy(buf, "Hello, I am your child."); return 0; } else { if (wait(NULL) == -1) { perror("wait"); return 1; } } printf("buf: %s\n", buf); return 0; } int main(int argc, char** argv) { bool shared = false; for (int i = 1; i < argc; ++i) { if (strcmp(argv[i], "-s") == 0) { shared = true; } } int flag = (shared ? MAP_SHARED : MAP_PRIVATE) | MAP_ANONYMOUS; void* addr = mmap(NULL, BUFSIZE, PROT_READ | PROT_WRITE, flag, -1, 0); if (addr == MAP_FAILED) { perror("mmap"); return 1; } int ret = Run(addr); if (munmap(addr, BUFSIZE) != 0) { perror("munmap"); return 1; } return ret; }
Page faults
Page fault («отказ страницы») — разновидность аппаратного исключения, возникающего в момент обращения к странице памяти, которая не включена блоком управления памятью (MMU) в виртуальное адресное пространство процесса. Обращение к этой странице может быть логически допустимым, но требовать её добавления в таблицу страниц и, возможно, загрузки её содержимого из вторичного хранилища (жёсткого диска) в оперативную память. Обнаружение page fault выполняет аппаратное обеспечение (MMU), а его обработку выполняет программное обеспечение, являющееся частью ядра операционной системы. При обработке отказа страницы операционная система пытается поместить требуемую страницу в оперативную память и сделать доступной процессу, либо завершает программу в случае недопустимого обращения к памяти.
Несмотря на название, page faults являются нормальной частью функционирования любой операционной системы, использующей виртуальную память, включая Windows и UNIX-подобные системы.
Minor page fault
Означает, что требуемая страница либо уже находится в оперативной памяти, но не отмечена в блоке управления памятью как загруженная, либо она вновь выделена и ещё ни разу не использовалась. Обработчик исключения в операционной системе должен только добавить запись в таблицу страниц для требуемой страницы и указать, что она загружена в память. Загрузка страницы с диска не требуется.
Minor page fault происходит в таких случаях:
- страница присутствует в памяти, но включена в рабочий набор другого процесса (например, если несколько процессов взаимодействуют через разделяемую память),
- страница находится в промежуточном состоянии, потому что она либо была исключена из рабочих наборов всех процессов и ожидает записи на диск, либо была предзагружена ранее (ядро предсказало, что она понадобится),
- процесс обращается к вновь выделенной странице в первый раз.
Поскольку обработка минорных отказов страницы не сопровождается задержкой для обращения к диску, они обрабатываются быстрее мажорных.
Major page fault
Основной механизм, используемый операционной системой для выделения программам памяти по их запросу. Операционная система откладывает загрузку частей программы с диска до тех пор, пока программа не попытается получить доступ к ним и тем самым сгенерирует отказ страницы. Если страница не загружена в память на момент отказа, тогда отказ называется мажорным. Обработчик page fault в ОС должен найти свободное место в оперативной памяти: свободную либо занятую страницу. Занятая страница может принадлежать другому процессу. В этом случае операционная система должна выгрузить данные этой страницы на диск (если они не были выгружены ранее) и пометить эту страницу в таблице страниц процесса как отсутствующую в памяти. Как только свободное место становится доступным, операционная система может загрузить данные для новой страницы в память, добавить её физический адрес в таблицу страниц исходного процесса и пометить страницу, как находящуюся в памяти. Необходимость обращения к диску делает обработку таких отказов гораздо более медленной по сравнению с лёгкими.
Invalid page fault
Возникает при обращении к адресу, не принадлежащему виртуальному адресному пространству процесса, то есть страницы в памяти, соответствующей этому адресу, не может быть. Обработчик отказа страницы в операционной системе в этом случае, как правило, передает ошибку сегментации исходному процессу, показывая, что обращение было недействительным.
Анализ показаний (h)top
К сожалению, даже в официальной справке есть неточности с толкованием понятий. Рассмотрим, какие показатели использования памяти про каждый процесс выдаёт программа (h)top.
VIRT
Общий размер виртуального адресного пространства, зарезервированного процессом.
CODE
Размер исполняемого кода бинарника, который запущен.
RES
Размер рабочего набора (resident size). Количество физической памяти, которую ядро считает назначенным данному процессу. Это актуальное значение объёма физической памяти, которую использует данный процесс.
Вычисляется ядром как сумма двух показателей:
- anonymous resident pages (MM_ANONPAGES)
- file-backed resident pages (MM_FILEPAGES)
Некоторые страницы могут быть резидентными для более чем одного процесса, поэтому сумма RES может быть даже больше, чем объём физической памяти.
%MEM
Прямо пропорционально RES.
SHR
Это объём резидентной разделяемой памяти процесса. Казалось бы, тут должен учитываться второй столбец таблицы, приведённой выше. Однако, как мы ранее замечали, некоторая private-память тоже может быть разделяемой. Чтобы понять смысл этой колонки в выводе top, надо заглянуть немного глубже в ядро.
Число SHR заполняется из поля shared файла /proc/[pid]/statm, который сам по себе является счётчиком MM_FILEPAGES ядра, а это один из компонентов резидентной памяти. Это просто значит, что в SHR записывается объём резидентной file-backed-памяти (квадранты 3 and 4).
Тем не менее, вспомним квадрант 2 — shared anonymous. В предыдущем определении речь шла только про file-backed-память. Однако такой пример показывает, что anonymous-память тоже входит в SHR:
#include <sys/mman.h> #include <unistd.h> #include <stdint.h> int main() { /* mmap 50MiB of shared anonymous memory */ char *p = mmap(NULL, 50 << 20, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0); /* Touch every single page to make them resident */ for (int i = 0; i < (50 << 20) / 4096; i++) { p[i * 4096] = 1; } /* Let us see the process in top */ sleep(1000000); return 0; }
top показывает 50M в обоих столбцах RES и SHR.
Это связано с тонкостью работы ядра Linux. В Linux shared anonymous память фактически основана на файлах. Ядро создает файл в tmpfs (экземпляр /dev/zero). Файл немедленно удаляется, поэтому он не может быть доступен никаким другим процессам, если они не унаследовали отображение (путем форка). Это довольно умно, так как совместное использование осуществляется через файловый уровень так же, как это делается для shared file-backed памяти (квадрант 4).
В конце концов, так как private file-backed страницы, которые были изменены, не синхронизируются на диск, они больше не file-backed (они переносятся с MM_FILEPAGES на MM_ANONPAGES). Как следствие, они больше не учитываются в SHR.
Обратите внимание, что страница руководства top неверна, так как она указывает, что SHR может содержать нерезидентную память: объём доступной памяти для задачи, не вся из которой обычно является резидентной.
SHR просто отражает память, которая потенциально может быть разделена с другими процессами.
DATA
Значение колонки DATA довольно непрозрачно. Документация top говорит «Data + Stack», но это на самом деле не помогает, поскольку не даётся определение, что есть «Data». Таким образом, нужно снова копаться в ядре.
Это поле вычисляется ядром как разница между двумя переменными: total_vm, которая совпадает с VIRT, и shared_vm. shared_vm как-то похоже на SHR по названию, но вместо учета только резидентной части он содержит сумму всей адресуемой файловой памяти. Как следствие, shared_vm представляет собой сумму квадрантов 2, 3 и 4. Это означает, что разница между total_vm и shared_vm является точно содержанием квадранта 1.
В столбце DATA содержится количество зарезервированной частной анонимной памяти. По определению частная анонимная память — это память, которая специфична для программы и хранит её данные. Она может стать общей только путем форка (в стиле COW). Он включает (но не ограничивается) стеки и кучу. Этот столбец не содержит какой-либо информации о том, сколько памяти фактически используется программой, он просто сообщает нам, что программа зарезервировала некоторый объем памяти, однако эта память может оставаться нетронутой в течение длительного времени.
Типичным примером бессмысленного значения DATA является то, что происходит, когда запускается программа x86_64, скомпилированная с AddressSanitizer. ASan работает, резервируя 16 TiB памяти, но использует только 1 байт из тех терабайт на 8-байтное слово памяти, фактически выделенное процессом. Как следствие, вывод top выглядит следующим образом:
3 PID %MEM VIRT SWAP RES CODE DATA SHR COMMAND 16190 0.687 16.000t 0 56056 13784 16.000t 2912 zchk-asan
Обратите внимание, что страница man для top снова неверна, поскольку в ней указано, что DATA — это объем физической памяти, предназначенный для других целей, кроме хранения исполняемого кода, также известный как размер набора резидентных данных или DRS. Мы просто увидели, что у DATA нет никакой связи с резидентной памятью.
SWAP
Смысл значения менялся от версии к версии... Рассмотрим случай современной системы, где top не старше 3.3.0 и ядро Linux не старше 2.6.34.
В современной системе счётчик SWAP увеличивается, когда страница вытесняется из памяти, и уменьшается, когда страница возвращается обратно. Как следствие, если это число больше 0, то оно явно говорит о том, что система ощущает нехватку памяти и память данного процесса не влазит в RAM.
Итого
Ввод-вывод посредством отображения файла в память
Отображение файла в память (memory mapping) — это способ работы с файлами, при котором всему файлу или некоторой непрерывной его части ставится в соответствие определённый участок памяти (диапазон адресов оперативной памяти). При этом чтение данных из этих адресов фактически приводит к чтению данных из отображенного файла, а запись данных по этим адресам приводит к записи этих данных в файл.
Рассмотрим пример.
#include <fcntl.h> #include <stdbool.h> #include <stdio.h> #include <sys/mman.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> bool GetFilesize(const char* filename, size_t* size) { struct stat st; if (stat(filename, &st) == -1) { perror("stat"); return false; } *size = st.st_size; return true; } int main(int argc, char** argv) { if (argc != 2) { fprintf(stderr, "Usage: %s FILE\n", argv[0]); return 2; } size_t filesize; if (!GetFilesize(argv[1], &filesize)) { return 1; } //Open file int fd = open(argv[1], O_RDONLY, 0); if (fd == -1) { perror("open"); return 1; } //Execute mmap void* mmappedData = mmap(NULL, filesize, PROT_READ, MAP_PRIVATE | MAP_POPULATE, fd, 0); if (mmappedData == MAP_FAILED) { perror("mmap"); return 1; } //Write the mmapped data to stdout (= FD #1) write(1, mmappedData, filesize); //Cleanup int rc = munmap(mmappedData, filesize); if (rc != 0) { perror("munmap"); } close(fd); }
Блокировка страниц в памяти
#include <sys/mman.h> int mlock(const void *addr, size_t len); int mlock2(const void *addr, size_t len, int flags); int munlock(const void *addr, size_t len); int mlockall(int flags); int munlockall(void);
Приложения:
- системы реального времени, где недопустимы случайные задержки
- обработка приватных данных (ключи, пароли), чтобы их нельзя было отыскать в недрах свопа
Подсказка по поводу будущих обращений к памяти
#include <sys/mman.h> int madvise(void *addr, size_t length, int advice);