73: }
74:
75: /* с помощью signal выяснить, когда время истекло */
76: signal(SIGALRM, catch);
77:
78: count = 0;
79: gotAlarm = 0;
80: alarm(1);
81: while (!gotAlarm) {
82: poll(pollFds, numFds, 0);
83: count++;
84: }
85:
86: printf("Вызовов poll() в секунду: %dn", count);
87:
88: alarm(1);
89:
90: count = 0;
91: gotAlarm = 0;
92: alarm(1);
93: while (!gotAlarm) {
94: epoll_wait(epfd, &event, 1, 0);
95: count++;
96: }
97:
98: printf("Вызовов epoll() в секунду: %dn", count);
99:
100: return 0;
101: }
13.2. Отображение в памяти
Операционная система Linux позволяет процессу отображать файлы в их адресное пространство. Такое отображение создает взаимно однозначное соответствие между данными в файле и в отображаемой области памяти. Отображение в памяти обладает рядом преимуществ.
Высокоскоростной доступ к файлам. Нормальные механизмы ввода-вывода, такие как read() и write(), вынуждают ядро копировать данные через буфер ядра, а не непосредственно между файлом, содержащим устройство, и процессом пространства пользователя. Карты памяти устраняют этот промежуточный буфер, сохраняя копию памяти[84].
Исполняемые файлы можно отображать на память программы, позволяя программе динамически загружать новые исполняемые области. Именно так реализуется динамическая загрузка, описанная в главе 27.
Новую память можно распределить отображением части /dev/zero, специального устройства, состоящего из нулей[85], или же через анонимное отображение. Средство Electric Fence, описанное в главе 7, использует этот механизм для распределения памяти.
Новую память, распределенную посредством карт памяти, можно сделать исполняемой, наполняя ее машинными командами, которые затем запускаются. Это свойство используется оперативными (just-in-time) компиляторами.
Файлы могут рассматриваться как память и читаться с использованием указателей, а не системных вызовов. Это существенно упрощает программы, избавляя от необходимости применения вызовов read(), write() и seek().
Отображение в памяти позволяет процессам совместно использовать области памяти, участвующие в создании и уничтожении процесса. Содержимое памяти хранится в отображаемом файле, делая его независимым от процессов.
13.2.1. Выравнивание по страницам
Системная память делится на порции под названием страницы. Размер страницы изменяется в зависимости от архитектуры, и на некоторых процессорах размер страницы может изменяться ядром. Функция getpagesize() возвращает размер (в байтах) каждой страницы системы.
#include <unistd.h>
size_t getpagesize(void);
Для каждой страницы системы ядро сообщает оборудованию, каким образом каждый процесс может получить доступ к странице (например, записать, выполнить или не выполнять никаких действий). Когда процесс пытается получить доступ к странице способом, нарушающим ограничения ядра, это вызывает ошибку сегментации (SIGSEGV), которая обычно приводит к завершению процесса.
Адрес памяти должен быть выровнен по страницам, если это адрес начала страницы. Иначе говоря, адрес должен быть целым, кратным размеру страницы архитектуры. В системе со страницами в 4 Кбайт адреса 0, 4 096, 16 384 и 32 768 являются выровненными по страницам (конечно, это далеко не весь список), потому что первая, вторая, пятая и девятая страницы системы начинаются с указанных адресов.
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 ноль.