13.2.2. Установка отображения в памяти
13.2.2. Установка отображения в памяти
Новые карты памяти создаются с помощью системного вызова mmap().
#include <sys/mman.h>
caddr_tmmap(caddr_t address, size_t length , int protection, int flags,
int fd, off_t offset);
Параметр address указывает, где именно в памяти необходимо отображать данные. Обычно address — это NULL, который означает, что для процесса не имеет значения местонахождение новой карты, и позволяет ядру выбрать любой адрес. Если адрес указан, он должен быть выровнен по страницам и в данный момент не использоваться. Если запрашиваемая карта будет конфликтовать с другой картой или не будет выровнена по страницам, mmap() может дать сбой.
Второй параметр, length, сообщает ядру, какую часть файлов следует отображать в памяти. Можно успешно отобразить больше памяти, чем количество данных в наличии у файла, но попытка доступа к нему может привести к SIGSEGV[86].
Процесс проверяет, какие типы доступа разрешены новой области памяти. Это должно быть одно или несколько значений из табл. 13.2, объединенных с помощью битового "ИЛИ", либо PROT_NONE, если доступ к отображаемой области запрещен. Файл может отображаться только для типов доступа, которые также были запрошены при изначальном открытии файла. Например, файл, открытый как O_RDONLY, не может быть отображен для записи с помощью PROT_WRITE.
Таблица 13.2. Флаги защиты mmap()
Флаг Описание PROT_READ Из отображаемой области можно читать. PROT_WRITE В отображаемую область можно записывать. PROT_EXEC Отображаемую область можно выполнять.Принудительное применение определенной защиты ограничено аппаратной платформой, на которой работает программа. Во многих архитектурах не разрешено выполнение кода в области памяти, если из нее запрещено чтение. При таком оборудовании отображение области с помощью PROT_EXEC эквивалентно ее отображению с помощью PROT_EXEC | PROT_READ.
По этой причине на флаги защиты памяти, передаваемые в mmap(), следует полагаться лишь как на обеспечивающие минимальную защиту.
В flags определяются другие атрибуты отображаемой области. В табл. 13.3 описаны все флаги. Многие флаги, поддерживаемые Linux, нестандартны, но могут быть полезны при особых условиях. В табл. 13.3 приведены различия между стандартными флагами mmap() и дополнительными флагами Linux. Во всех вызовах mmap() должен быть специфицирован MAP_PRIVATE или MAP_SHARED; остальные флаги устанавливать необязательно.
Таблица 13.3. Флаги mmap()
Флаг POSIX? Описание MAP_ANONYMOUS Да Игнорировать fd, создать анонимную карту. MAP_FIXED Да Сбой в случае недопустимого адреса (address). MAP_PRIVATE Да Запись приватна для процесса. MAP_SHARED Да Запись копируется в файл. MAP_DENYWRIТЕ Нет Не разрешать нормальную запись в файл. MAP_GROWSDOWN Нет Расширить область памяти сверху вниз. MAP_LOCKED Нет Блокировать страницы в памяти.MAP_ANONYMOUS Вместо отображения файла возвращается анонимное отображение. Оно ведет себя подобно обычному отображению, но без участия физического файла. Хотя эту область памяти нельзя ни использовать совместно с другими процессами, ни автоматически сохранять в файле, анонимное отображение позволяет процессам распределять новую память для индивидуального использования. Такое отображение часто применяется реализациями malloc(), а также еще несколькими специализированными приложениями. Параметр fd игнорируется при использовании этого флага. MAP_FIXED Если карту нельзя поместить по запрашиваемому адресу, mmap() завершается неудачей. Если этот флаг не определен, ядро попытается разместить карту по указанному адресу, но если это не удастся, то отобразит ее на альтернативный адрес. Если адрес, переданный в address, уже использовался mmap(), элемент, отображаемый в этой области, будет замещен новой картой памяти. Это означает, что лучше передавать только те адреса, которые были возвращены предыдущими вызовами в mmap(); если применяются произвольные адреса, может быть перезаписана область памяти, используемая системными библиотеками. MAP_PRIVATE Модификации области памяти должны быть индивидуальными для процесса. Их не следует совместно использовать с другими процессами, которые отображают этот же файл (процессами, отличающимися от связанных процессов, которые ответвляются после создания карты памяти), а также отражать в самом файле. Должен использоваться флаг MAP_SHARED или MAP_PRIVATE. Если область памяти незаписываемая, тип используемого флага не имеет значения. MAP_SHARED Изменения в области памяти копируются обратно в файл, который был отображен и использован совместно с другими процессами, отображающими этот же файл. (Для записи изменений в область памяти следует установить PROT_WRITE; иначе область памяти будет постоянной). Должен использоваться флаг MAP_SHARED или MAP_PRIVATE. MAP_DENYWRITE Обычно системные вызовы для нормального доступа к файлам (например, write()) могут модифицировать отображенный файл. Однако если область запускается, это будет не самым лучшим решением. Указание MAP_DENYWRITE приводит к тому, что операции записи файлов, отличные от тех, что совершаются через карту памяти, будут возвращать etxtbsy. MAP_GROWSDOWN Попытка немедленного доступа к памяти, расположенной непосредственно перед отображаемой областью, обычно вызывает SIGSEGV. Этот флаг заставляет ядро расширять область для младших адресов памяти по страницам, если процесс пытается получить доступ к памяти на младшей смежной странице, и продолжает процесс обычным образом. Это разрешает ядру автоматически расширять стеки процессов на платформах, на которых стеки расширяются сверху вниз (наиболее распространенный случай). Это специфичный для платформы флаг, применяемый обычно только для системного кода. Единственным ограничением для MAP_GROWSDOWN является ограничение размеров стека, рассматриваемое в главе 10. Если ограничение не установлено, ядро расширит отображенный сегмент, несмотря на то, выгодно ли это. Однако оно не будет расширять сегмент поверх остальных отображаемых областей. MAP_GROWSUP Этот флаг работает так же, как и MAP_GROWSDOWN, но предназначен для тех редких платформ, на которых стеки расширяются снизу вверх, что означает расширение области со старших, а не младших адресов. (В ядре версии 2.6.7 только архитектура parisc имеет стеки, расширяющиеся снизу вверх.) Как и MAP_GROWSDOWN, этот флаг зарезервирован для системного кода с установленным ограничением на размер стека. MAP_LOCKED Область блокируется в памяти. Это означает, что она никогда не будет подлежать страничному обмену. Это важно для систем реального времени (mlock(), рассматриваемый далее в этой главе, предоставляет еще один метод блокирования памяти). Обычно это может установить только привилегированный пользователь; обычным пользователям не разрешено блокировать страницы в памяти. Некоторые системы Linux допускают ограниченное распределение заблокированной памяти непривилегированными пользователями, и эта возможность, вероятно, вскоре будет добавлена к стандартному ядру Linux.
За флагами следует файловый дескриптор, fd, для файла, который предстоит отобразить в памяти. Если применялся флаг MAP_ANONYMOUS, его значение игнорируется. Последний параметр определяет, где именно в файле должно начаться отображение. Он должен быть целым числом, кратным размеру страницы. Большинство приложений начинают отображение с начала файла, указывая в качестве offset ноль.
Системный вызов mmap() возвращает адрес, который должен храниться в указателе. Если произошла ошибка, он возвращает адрес, эквивалентный -1. Для проверки этого необходимо привести тип константы -1 к caddr_t, а не к int. Это гарантирует, что результат будет верным независимо от размеров указателей и целых чисел.
Ниже приведена программа, действующая подобно команде cat и ожидающая отдельного имени в качестве аргумента командной строки. Она открывает этот файл, отображает его в памяти и записывает целый файл на стандартное устройство вывода одним вызовом write(). Полезно сравнить этот пример с простой реализацией cat из главы 11. Код примера также иллюстрирует, что карты памяти остаются на месте после закрытия отображаемого файла.
1: /* map-cat.с */
2:
3: #include <errno.h>
4: #include <fcntl.h>
5: #include <sys/mman.h>
6: #include <sys/stat.h>
7: #include <sys/types.h>
8: #include <stdio.h>
9: #include <unistd.h>
10:
11: int main(int argc, const char ** argv) {
12: int fd;
13: struct stat sb;
14: void * region;
15:
16: if ( fd = open(argv[1], O_RDONLY)) < 0) {
17: perror("open");
18: return 1;
19: }
20:
21: /* Вызвать fstat для файла, чтобы узнать, сколько необходимо памяти для его отображения */
22: if (fstat(fd, &sb)) {
23: perror("fstat");
24: return 1;
25: }
26:
27: /* можно было бы также отобразить как MAP_PRIVATE, поскольку
28: запись в эту память не планируется */
29: region = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
30: if (region == ((caddr_t) -1)) {
31: perror("mmap");
32: return 1;
33: }
34:
35: close(fd);
36:
37: if (write(1, region, sb.st_size) != sb.st_size) {
38: perror("write");
39: return 1;
40: }
41:
42: return 0;
43: }