Файлы, отображаемые на память
В операционную систему Microsoft Windows NT встроен эффективный механизм виртуальной памяти, описанный нами в предыдущем томе “Библиотеки системного программиста”. При использовании этого механизма приложениям доступно больше виртуальной оперативной памяти, чем объем физической оперативной памяти, установленной в компьютере.
Как вы знаете, виртуальная память реализована с использованием обычной оперативной памяти и дисковой памяти. Когда приложение обращается к странице виртуальной памяти, отсутствующей в физической памяти, операционная система автоматически читает ее из файла виртуальной памяти в физическую память и предоставляет приложению. Если же приложение изменяет содержимое страницы памяти в физической оперативной памяти, операционная система сохраняет такую страницу в файле виртуальной памяти.
Почему мы вспомнили о виртуальной памяти в главе, посвященной файлам?
Потому что механизм работы с файлами, отображаемыми на память, напоминает механизм работы вирутальной памяти.
Напомним, что в операционной системе Microsoft Windows NT каждому процессу выделяется 2 Гбайта адресного пространства. Любой фрагмент этого пространства может быть отображен на фрагмент файла соответствующего размера.
На рис. 1.1 показано отображение фрагмента адресного пространства приложения, размером в 1 Гбайт, на фрагмент файла, имеющего размер 5 Гбайт.
Рис. 1.1. Отображение фрагмента адресного пространства приложения на файл
С помощью соответсвующей функции программного интерфейса, которую мы скоро рассмотрим, приложение Microsoft Windows NT может выбрать любой фрагмент большого файла для отображения в адресное пространство. Поэтому, несмотря на ограничение адресного пространства величиной 2 Гбайт, вы можете отображать (по частям) в это пространство файлы любой длины, возможной в Microsoft Windows NT. В простейшем случае при работе с относительно небольшими файлами вы можете выбрать в адресном пространстве фрагмент подходящего размера и отобразить его на начало файла.
Как выполняется отображение фрагмента адресного пространства на фрагмент файла?
Если установлено такое отображение, то операционная система обеспечивает тождественность содержимого отображаемого фрагмента памяти и фрагмента файла, выполняя при необходимости операции чтения и записи в файл (с буферизацией и кешированием).
В процессе отображения адресного пространства память не выделяется, а только резервируется. Поэтому отображение фрагмента размером 1 Гбайт не вызовет переполнение файлов виртуальной памяти. Более того, при отображении файлы виртуальной памяти вообще не используются, так как страницы фрагмента связываются с отображаемым файлом.
Если приложение обращается в отображенный фрагмент для чтения, возникает исключение. Обработчик этого исключения загружает в физическую оперативную память соответствующую страницу из отображенного файла, а затем возобновляет выполнение прерванной команды. В результате в физическую оперативную память загружаются только те страницы, которые нужны приложению.
При записи происходит аналогичный процесс. Если нужной страницы нет в памяти, она подгружается из отображенного файла, затем в нее выполняется запись. Загруженная страница остается в памяти до тех пор, пока не будет вытеснена другой страницей при нехватке физической оперативной памяти. Что же касается записи измененной страницы в файл, то эта запись будет выполнена при закрытии файла, по явному запросу приложения или при выгрузке страницы из физической памяти для загрузки в нее другой страницы.
Заметим, что операционная система Microsoft Windows NT активно работает с файлами, отобажаемыми в память. В частности, при загрузке исполнимого модуля приложения соответствующий exe- или dll-файл отображается на память, а затем ему передается управление. Когда пользователь запускает вторую копию приложения, для работы используется файл, который уже отображается в память. В этом случае соответствующие страницы виртуальной памяти отображаются в адресные пространства обоих приложений. При этом возникает одна интересная проблема, связанная с использованием глобальных переменных.
Представьте себе, что в приложении определены глобальные переменные. При запуске приложения область глобальных переменных загружается в память наряду с исполнимым кодом. Если запущены две копии приложения, то страницы памяти, отведенные для кода и глобальных данных, будут отображаться в адресные пространства двух разных процессов. При этом существует потенциальная возможность конфликта, когда разные работающие копии одного и того же приложения попытаются установить разные значения для одних и тех же глобальных переменных.
Операционная система Microsoft Windows NT выходит из этой ситуации следующим способом. Если она обнаружит, что одна из копий приложения пытается изменить страницу, в которой хранятся глобальные переменные, она создает для нее еще одну копию страницы. Таким образом, между приложениями никогда не возникает интерференции.
Аналогичная методика используется и для страниц, содержащих программный код. Если приложение (например, отладчик) пытается изменить страницу, содержащую исполнимый код, операционная система Microsoft Windows NT создает еще одну копию страницы и отображает ее в адресное пространство соответствующего процесса. Подробнее об этом мы расскажем в разделе, посвященном использованию файлов, отображаемых на память, для передачи данных между различными процессами.