Вызов mmap() позволяет отображать не только обычные файлы, хранящиеся на диске, но и содержимое различных физических и виртуальных устройств, таких как жесткие и оптические диски или /dev/mem.
Файл, на который ссылается дескриптор fd, должен быть открыт с правами доступа, соответствующими значениям аргументов prot и flags. В частности, файл всегда должен быть доступен для чтения, а если в flags указаны флаги PROT_WRITE и MAP_SHARED, то также должен позволять запись. Аргумент offset обозначает начальный байт участка отображаемого файла и должен быть кратным размеру страницы в системе. Если он равен 0, то файл будет отображаться с самого начала. Аргумент length обозначает количество байтов, которое нужно отобразить. Сочетание ffset и length позволяет определить тот участок файла, что будет отображен в память (рис. 45.1).
В Linux страницы файла отображаются при первом доступе. Это значит следующее: изменения, внесенные в участок файла после вызова mmap(), но перед доступом к соответствующей части (то есть странице) отображения, могут быть видны процессу (если страница не была загружена в память до сего момента каким-то другим способом). Данное поведение зависит от конкретной реализации; портируемым приложениям не следует полагаться на то, что ядро поведет себя именно так.
45.4.1. Приватные файловые отображения
Наиболее часто приватные файловые отображения используются в следующих целях.
• Позволить нескольким процессам выполнять одну и ту же программу или открывать доступ (на чтение) к общему текстовому сегменту с помощью разделяемой библиотеки (при этом сегмент отображен из соответствующего участка исходного исполняемого или библиотечного файла).
Исполняемый текстовый сегмент обычно защищен маской PROT_READ | PROT_EXEC, которая позволяет только чтение и выполнение, но при его отображении вместо MAP_SHARED применяется флаг MAP_PRIVATE. Это делается для того, чтобы отладчики или самомодифицирующиеся программы, вносящие изменения в программный код (предварительно откорректировав защиту памяти), не могли повлиять на исходный исполняемый файл или другие процессы.
• Отображать инициализированный сегмент данных исполняемого файла или разделяемой библиотеки. Такие отображения делаются приватными, чтобы изменения, вносимые в содержимое отображенного сегмента данных, не распространялись на исходный файл.
В обоих этих случаях вызов mmap() обычно используется незаметно для программы, так как такие отображения создаются программным загрузчиком и динамическим компоновщиком. Еще один, менее распространенный способ применения приватного файлового отображения заключается в организации обычного ввода для программы. Это похоже на то, как разделяемые файловые отображения используются для ввода/вывода в памяти (о чем пойдет речь в следующем разделе), но в данном случае задействуется только ввод.
Рис. 45.1.
45.4.2. Разделяемые файловые отображения
Когда несколько процессов создают разделяемые отображения одного и того же участка файла, отображенные страницы физической памяти становятся для них общими. Кроме того, изменения содержимого отображений передаются обратно в исходный файл. Фактически файл становится хранилищем страниц этого участка памяти, как показано на рис. 45.2 (данная диаграмма упрощена: опущен тот факт, что отображенные страницы физической памяти обычно не являются смежными).
Разделяемые файловые отображения используются для двух задач: отображения ввода/вывода в память и межпроцессного взаимодействия. Каждый из этих случаев будет рассмотрен ниже.
Рис. 45.2.
Отображение ввода/вывода в память
Поскольку содержимое разделяемого файлового отображения берется из файла, в который возвращаются любые изменения, внесенные в само отображение, можно выполнять файловый ввод/вывод, просто работая с байтами памяти, — все изменения будут автоматически применены ядром к исходному файлу (обычно для преобразования содержимого отображения создается структура данных, соответствующая содержимому файла). Такой подход называется
Отображение ввода/вывода в память имеет два потенциальных преимущества:
• непосредственная работа с памятью вместо применения системных вызовов read() и write() позволяет упростить логику некоторых приложений;
• в ряде случаев это может улучшить производительность ввода/вывода по сравнению с использованием традиционных системных вызовов.
Причины, по которым отображение ввода/вывода в память может привести к улучшению производительности, заключаются в следующем.