Марк Митчелл - Программирование для Linux. Профессиональный подход
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
/* Ожидание семафора. Операция блокируется до тех пор, пока
значение семафора не станет положительным, после чего
значение уменьшается на единицу. */
int binary_semaphore_wait(int semid) {
struct sembuf operations[1];
/* Оперируем одним-единственным семафором. */
operations[0].sem_num = 0;
/* Уменьшаем его значение на единицу. */
operations[0].sem_op = -1;
/* Разрешаем отмену операции. */
operations[0].sem_flg = SEM_UNDO;
return semop(semid, operations, 1);
}
/* Установка семафора: его значение увеличивается на единицу.
Эта операция завершается немедленно. */
int binary_semaphore_post(int semid) {
struct sembuf operations[1];
/* оперируем одним-единственным семафором. */
operations[0].sem_num = 0;
/* Увеличиваем его значение на единицу. */
operations[0].sem_op = 1;
/* Разрешаем отмену операции. */
operations[0].sem_flg = SEM_UNDO;
return semop(semid, operations, 1);
}
Флаг SEM_UNDO позволяет решить проблему, возникающую при завершении процесса, которого есть ресурсы, связанные с семафором. Как бы ни завершился процесс — принудительно или естественным образом, — значение семафора автоматически корректируется. "отменяя" эффект операции, выполненной над семафором. Например, если процесс уменьшил значение семафора, а затем был уничтожен командой kill, значение семафора будет снова увеличено.
5.2.4. Отладка семафоров
С помощью команды ipcs -s можно получить информацию о существующих группах семафоров. Команда ipcrm sem позволяет удалить заданную группу, например:
% ipcrm sem 5790517
5.3. Отображение файлов в памяти
Благодаря механизму отображаемой памяти процессы получают возможность общаться друг с другом посредством совместно используемого файла. Схематически это можно представить как совместный доступ к именованному сегменту памяти, хотя технически оба механизма реализованы по-разному.
При отображении файла в памяти формируется связь между файлом и памятью процесса. ОС Linux разбивает файл на страничные блоки и копирует их в страницы виртуальной памяти, чтобы они стали доступны в адресном пространстве процесса. Таким образом, процесс сможет обращаться к содержимому файла как к обычной памяти. При записи данных в соответствующую область памяти содержимое файла будет меняться. Это ускоряет доступ к файлам.
Отображаемую память можно представить как буфер, в который загружается все содержимое файла. Если данные, находящиеся в буфере, модифицируются, они записываются обратно в файл. Операции чтения и записи ОС Linux обрабатывает самостоятельно.
Файлы, отображаемые в памяти, можно использовать не только для организации взаимодействия процессов. О других применениях таких файлов пойдет речь в разделе 5.3.5. "Другие применения функции mmap()".
5.3.1. Отображение в памяти обычного файла
Для отображения обычного файла в памяти процесса предназначена функция mmap(). Ее первым аргументом является адрес, который будет соответствовать началу отображаемого файла в адресном пространстве процесса. Если задать значение NULL, ОС Linux выберет первый доступный адрес. Второй аргумент — это длина отображаемой области в байтах. Третий аргумент задает степень защиты диапазона отображаемых адресов. Он может содержать объединение битовых констант PROT_READ, PROT_WRITE и PROT_EXEC, соответствующих разрешению на чтение, запись и выполнение соответственно. Четвертый аргумент содержит дополнительные флаги. Пятый аргумент — это дескриптор открытого файла. В последнем аргументе задается смещение от начала файла, с которого начинается отображаемая область. Можно перенести в память весь файл или только часть его, должным образом корректируя начальное смещение и длину отображаемой области.
Ниже перечислены дополнительные флаги, задаваемые в четвертом аргументе.
■ MAP_FIXED. При наличии этого флага ОС Linux использует значение первого аргумента как точный адрес размещения отображаемого файла. Этот адрес должен соответствовать началу страницы.
■ MAP_PRIVATE. Изменения, вносимые в отображаемую память, записываются не в присоединенный файл, а в частную копию файла, принадлежащую процессу. Другие процессы не узнают об этих изменениях. Данный режим не совместим с режимом MAP_SHARED.
■ MAP_SHARED. Изменения, вносимые в отображаемую память, немедленно фиксируются в файле, минуя буфер. Этот режим используется при организации взаимодействия процессов и не совместим с режимом MAP_PRIVATE.
При успешном завершении функция возвращает указатель на начало области памяти. В противном случае возвращается флаг MAP_FAILED.
По окончании работы с отображаемым файлом его необходимо освободить с помощью функции munmap(). Ей передается начальный адрес и длина отображаемой области. ОС Linux автоматически освобождает отображаемые области при завершении процесса.
5.3.2. Примеры программ
В этом разделе рассматриваются две программы, в которых иллюстрируются чтение и запись файлов, отображаемых в памяти. Первая программа (листинг 5.5) генерирует случайное число и записывает его в отображаемый файл. Вторая программа (листинг 5.6) читает число из файла, выводит его на экран, а затем умножает на 2 и записывает обратно в файл. Обе программы принимают имя файла из командной строки.
Листинг 5.5. (mmap-write.c) Запись случайного числа в файл, отображаемый в памяти#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#define FILE_LENGTH 0x100
/* получение случайного числа в диапазоне [low,high]. */
int random_range(unsigned const low, unsigned const high) {
unsigned const range = high - low + 1;
return
low + (int)(((double)range) * rand() / (RAND_MAX + 1.0));
}
int main (int argc, char* const argv[]) {
int fd;
void* file_memory;
/* Инициализация генератора случайных чисел. */
srand(time(NULL));
/* подготовка файла, размер которого будет достаточен для
записи беззнакового целого числа. */
fd = open(argv[1], O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
lseek(fd, FILE_LENGTH+1, SEEK_SET);
write(fd, "", 1);
lseek(fd, 0, SEEK_SET);
/* Создание отображаемой области. */
file_memory =
mmap(0, FILE_LENGTH, PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
/* Запись случайного числа в отображаемую память. */
sprintf((char*)file_memory,
"%dn", random_range(-100, 100));
/* Освобождение памяти (не обязательно, так как программа
завершается). */
munmap(file_memory, FILE_LENGTH);
return 0;
}
Программа mmap-write пытается открыть файл и, если он не существует, создает его. Третий аргумент функции open() указывает на то, что файл доступен для чтения/записи. Поскольку длина файла неизвестна, с помощью функции lseek() мы убеждаемся в том, что файл имеет достаточную длину для записи беззнакового целого числа, а затем возвращаемся в начало файла.
Программа закрепляет файл за областью памяти и закрывает его дескриптор, так как в нем больше нет необходимости. После этого программа записывает случайное число в отображаемую память, т.е. в файл, и освобождает память. В принципе, вызывать функцию munmap() нет необходимости, так как ОС Linux автоматически освободит память при завершении программы.
Листинг 5.6. (mmap-read.c) Чтение случайного числа из файла, отображаемого в памяти#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#define FILE_LENGTH 0x100
int main(int argc, char* const argv[]) {
int fd;
void* file_memory;
int integer;
/* Открытие файла. */
fd = open(argv[1], O_RDWR, S_IRUSR | S_IWUSR);
/* Создание отображаемой области. */
file_memory =
mmap(0, FILE_LENGTH, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
close(fd);
/* Чтение целого числа и вывод его на экран. */
sscanf(file_memory, "%d", &integer);