• Обычные операции read() и write() подразумевают двойную передачу данных: сначала между файлом и кэшем буфера ядра, а затем — между этим кэшем и буфером в пользовательском пространстве. Второго этапа можно избежать, если задействовать mmap(). При вводе данные доступны пользовательскому процессу сразу, как только ядро отобразит соответствующие блоки файла в память. При выводе этому процессу всего лишь нужно изменить содержимое памяти, а диспетчер памяти ядра сам автоматически обновит исходный файл.
• Помимо экономии на передаче данных между пространством ядра и пользователя, mmap() может также снизить потребление памяти. При использовании операций read() и write() данные хранятся в двух буферах, один из которых находится в пользовательском пространстве, а другой принадлежит ядру. Вызову mmap() нужен только один общий буфер, разделяемый между пространствами ядра и пользователя. Кроме того, этот единый буфер ядра может применяться несколькими процессами, которые выполняют ввод/вывод с одним и тем же файлом, что еще больше снижает потребление памяти.
Выгода от улучшенной производительности отображения ввода/вывода в большинстве случаев проявляется при выполнении множества операций произвольного доступа к большому файлу. При последовательном доступе вызов mmap() почти (или совсем) не имеет существенных преимуществ по сравнению с операциями read() и write(), при условии, что используется буфер достаточных размеров, который позволяет избежать большого количества системных вызовов для ввода/вывода. Причина заключается вот в чем: независимо от применяемой методики все содержимое файла будет считано с диска в память всего один раз и по сравнению с этим выгода от пониженного потребления памяти и отказа от передачи данных между пространством пользователя и ядра является несущественной.
Отображение ввода/вывода в память может иметь некоторые недостатки. В случае с чтением/записью небольшого объема данных издержки от применения этой методики (то есть отображение, отказы страницы, удаление отображения и обновление буфера ассоциативной трансляции в физической памяти) могут превысить затраты на использование операций read() и write(). Кроме того, иногда ядру не удается эффективно выполнить обратную запись в изменяемое отображение (в данном случае производительность можно повысить за счет вызовов msync() или sync_file_range()).
Межпроцессное взаимодействие на основе разделяемого файлового отображения
Поскольку все процессы, отображающие один и тот же участок файла, получают доступ к общим страницам физической памяти, разделяемое файловое отображение можно также использовать для организации (быстрого) межпроцессного взаимодействия. Данный подход отличается тем, что изменения, вносимые в содержимое выбранного участка, автоматически применяются к исходному файлу. Это может пригодиться в случаях, когда содержимое разделяемой памяти должно сохраняться при перезапуске приложения или всей системы.
Пример программы
В листинге 45.2 приводится простой пример использования вызова mmap() для создания разделяемого файлового отображения. Сначала данная программа отображает файл, заданный в виде первого аргумента командной строки. Затем она выводит значение строки, находящейся в начале отображенного участка. В завершение второй аргумент командной строки, если он был указан, копируется на соответствующий участок разделяемой памяти.
Применение данной программы показано на примере следующей сессии командной оболочки. Для начала создадим 1024-байтный файл, заполненный нулями:
$ dd if=/dev/zero of=s.txt bs=1 count=1024
1024+0 records in
1024+0 records out
Теперь воспользуемся нашей программой, чтобы отобразить этот файл и скопировать строку в отображенный участок:
$ ./t_mmap s.txt hello
Current string=
Copied "hello" to shared memory
Программа не вывела ничего, что касалось бы текущей строки, поскольку исходное значение отображенных файлов начинается с нулевого байта (то есть со строки нулевой длины).
Теперь опять попытаемся отобразить файл и скопировать на отображенный участок новую строку:
$ ./t_mmap s.txt goodbye
Current string=hello
Copied "goodbye" to shared memory
В завершение выведем файл (по восемь символов в строке), чтобы проанализировать его содержимое:
$ od — c — w8 s.txt
0000000 g o o d b y e nul
0000010 nul nul nul nul nul nul nul nul
*
0002000
Наша программа достаточно тривиальна, так что не использует никакого механизма для синхронизации доступа к отображенному файлу со стороны нескольких процессов. Однако реальные приложения обычно без этого не обходятся. Существует целый ряд методик, включая семафоры (см. главу 49) и блокировку файлов (см. главу 51).
Системный вызов msync(), применяемый в листинге 45.2, будет рассмотрен в разделе 45.5.