Самые первые версии Unix работали на компьютерах без аппаратной поддержки виртуальной памяти, однако очень скоро такая поддержка появилась в большинстве процессорных архитектур и стала обязательным условием для возможности запуска Unix/Linux на соответствующей платформе. В линейке Intel аппаратная поддержка виртуальной памяти появилась в процессорах i286. С выходом этих процессоров на рынок связано появление лицензионной версии Unix для ПК - Xenix от Microsoft.
Несмотря на то, что современные процессоры адресуют память с точностью до байта, основными единицами управления памятью в Unix являются страницы.
Страница - это минимальная единица отображения непрерывного диапазона адресов виртуальной памяти на непрерывный диапазон адресов физической памяти. Соответственно, выделение физической память для данных процессов и ядра, а так же буферов дискового кеша и буферов для получения данных с устройств по DMA всегда выполняется страницами.
Страничная организация памяти поддерживается специализированным аппаратным устройством - модулем управления памятью (MMU, Memory Management Unit), которое выполняет преобразование виртуальных адресов в физические. MMU может быть интегрировано в процессор или входить в чипсет.
Размеры страниц отличаются для различных аппаратных платформ.Можно считать, что для 32-разрядных аппаратных платформ размер страницы равен 4 Кбайта, а для 64-разрядных платформ - 8 Кбайт. Современные процессоры от Intel позволяют выбирать размер страницы, и, соответственно, ядро Linux позволяет управлять этим выбором.
Отображение виртуальной памяти в физическую осуществляется через таблиц страниц, в которых каждой виртуальной странице ставится в соответствие физический адрес. Таблицы страниц хранятся в ОЗУ, а физический адрес текущей таблицы страниц заносится в специальный регистр процессора.
Для каждого процесса в момент его создания инициализируется своя таблица страниц. Таким образом одни и те же адреса в виртуальной памяти различных процессов ссылаются на различные адреса физической памяти. Благодаря этому, физические страницы памяти одного процесса становятся недоступными для других процессов. Таблица страниц ядра отображает часть адресов на физическую память, в которой расположено само ядро, а вторую часть - на страницы памяти какого-либо процесса. Такая таблица позволят ядру получить доступ к физической памяти процессов для копирования входных/выходных данных системных вызовов. Например, при вызове read(fd,buf,size)
код в ядре имеет доступ к буферу buf для заполнения его данными, прочитанными из файла.
Таблица страниц процесса не отображается в его виртуальную память, а команда процессора, загружающая адрес таблицы страниц в соответствующий регистр, является привилегированной и не может быть выполнена в коде пользовательского процесса. Это не даёт процессам возможности хоть как-то повлиять на таблицы страниц. В момент переключения на выполнение кода ядра (по аппаратному прерыванию или при системном вызове) процессор переключается в привилегированный режим, после чего код в ядре может указать в регистре свою таблицу страниц, которая включает отображение всей физической памяти, после чего ядро может поменять таблицу страниц любого процесса.
Интересное замечание: существуют процессорные архитектуры, в которых физическая память может быть больше виртуальной. Так серверные материнские платы на процессорах x32 позволяют устанавливать больше чем 4 Гбайта оперативной памяти, в этом случае ядру приходится модифицировать часть собственной таблицы страниц, чтобы получить доступ к нужным участкам физической памяти.
Часть ссылок в таблице страниц может иметь специальное значение, указывающее на то, что страница не отображается на физическую память. Данный механизм используется для реализации файлов подкачки (swap). При попытке обращения к такой "несуществующей" странице, возникает аппаратное прерывание, которое обрабатывается в ядре. Код ядра может выделить процессу свободную страницу физической памяти, загрузить в неё данные из файла подкачки, поменять таблицу страниц так, чтобы требуемая виртуальная память отображалась на свежеподготовленную физическую ти вернуть управления процессу. Выполнение процесса начнётся с той же инструкции доступа к памяти, на которой возникло прерывание, но теперь эта инструкция выполнится нормально, поскольку изменилась ТС.
В некоторых случаях возможно отображение одной физической страницы памяти в виртуальную память нескольких процессов. Такое совместное использование физической памяти называется разделяемой памятью (shared memory). Данный механизм используется для обмена данными между процессами и требует аппаратной или программной поддержки в виде семафоров, для синхронизации параллельного доступа к памяти.
Часть физической памяти используется для хранения файлового кеша. В кеше хранится блоки данных файла размерами кратными странице памяти. При операциях считывания/записи в ядре происходит копирование нужного числа страниц между памятью процесса и кешем, а операции с файловой системой откладываются по времени. Если физическую память требуется освободить, то она сохраняется в файл, если кто-то обращается к странице, отсутствующей в кеше, она считывается из файла. Как правило, страницы кеша доступны только ядру, однако существует несколько случаев, когда они отображаются непосредственно в виртуальную память процессов. Механизм отображения страниц кеша в память процесса называется отображением файлов в память (memory mapped file). Данный механизм может использоваться как альтернатива разделяемой памяти (анонимные файлы в памяти), а так же как механизм совместного использования кода программ, путём отображения секции кода из исполняемого файла в виртуальное адресное пространство процессов, которые этот код исполняют.
При создании нового процесса вызовом fork() (в Linux фактически вызывается clone()) таблица страниц копируется в дочерний процесс и некоторое время родительский и дочерний процесс совместно используют общую физическую память. В дальнейшем, при изменении какой-либо страницы в одном из двух процессов для этого процесса создаётся копия страницы в физической памяти и соответственно изменяется таблица страниц. Данный механизм позволяет экономить время при создании нового процесса. Этот механизм называется "копирование при записи" (Copy on write, COW).
Созданиие новых потоков в Linux вызовом clone() аналогично созданию нового процесса, но дочерний процесс наследует от родительского процесса таблицу страниц. Таким образом, потоки в Linux можно рассматривать как дочерние процессы, использующие общую с родительским процессом виртуальную память. Главным отличием между процессами является размещение стека в виртуальной памяти.
Вызов execve("prog_file"), загружающий в память процесса новую программу, полностью изменяет таблицу страниц, освобождая занятую физическую память и выделяя новую, которая инициализируется в соответвии с данными, хранящимися в исполняемом файле.
Вся виртуальная память процесса в Linux поделена на несколько областей (сегментов), различного назначения. Выделяются следующие области:
Каждая область памяти описывается структурой, которая хранит начало и конец области в виртуальном пространстве процесса, позицию в файле, если область является отображением файла в память, права доступа и флаги, описывающие свойства области.
Флаги, описывающие права доступа к страницам, входящих в область:
Просмотреть список областей процесса можно в файле /proc/
Пример вывода программой cat собственных областей памяти. Буква p в флагах доступа означает private. Если бы область использовалась совместно, то стояла бы буква s -shared.
$ cat /proc/self/maps
00400000-0040b000 r-xp 00000000 fd:01 134395423 /usr/bin/cat
0060b000-0060c000 r--p 0000b000 fd:01 134395423 /usr/bin/cat
0060c000-0060d000 rw-p 0000c000 fd:01 134395423 /usr/bin/cat
017f1000-01812000 rw-p 00000000 00:00 0 [heap]
7f5f529df000-7f5f58f08000 r--p 00000000 fd:01 67442653 /usr/lib/locale/locale-archive
7f5f58f08000-7f5f590cb000 r-xp 00000000 fd:01 201328510 /usr/lib64/libc-2.17.so
7f5f590cb000-7f5f592ca000 ---p 001c3000 fd:01 201328510 /usr/lib64/libc-2.17.so
7f5f592ca000-7f5f592ce000 r--p 001c2000 fd:01 201328510 /usr/lib64/libc-2.17.so
7f5f592ce000-7f5f592d0000 rw-p 001c6000 fd:01 201328510 /usr/lib64/libc-2.17.so
7f5f592d0000-7f5f592d5000 rw-p 00000000 00:00 0
7f5f592d5000-7f5f592f7000 r-xp 00000000 fd:01 201328504 /usr/lib64/ld-2.17.so
7f5f594e8000-7f5f594eb000 rw-p 00000000 00:00 0
7f5f594f5000-7f5f594f6000 rw-p 00000000 00:00 0
7f5f594f6000-7f5f594f7000 r--p 00021000 fd:01 201328504 /usr/lib64/ld-2.17.so
7f5f594f7000-7f5f594f8000 rw-p 00022000 fd:01 201328504 /usr/lib64/ld-2.17.so
7f5f594f8000-7f5f594f9000 rw-p 00000000 00:00 0
7ffc13623000-7ffc13644000 rw-p 00000000 00:00 0 [stack]
7ffc13785000-7ffc13787000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
Прикрепленный файл | Размер |
---|---|
memory-maps.png | 33.08 КБ |