Уильям Стивенс - UNIX: взаимодействие процессов
33-37 Мы устанавливаем размер созданного файла, записывая в него заполненную нулями структуру. Поскольку мы знаем, что только что созданный файл имеет размер 0, для установки его размера мы вызываем именно write, но не ftruncate, потому что, как мы отмечаем в разделе 13.3, Posix не гарантирует, что ftruncate срабатывает при увеличении размера обычных файлов.
Отображение содержимого файла в память38-42 Файл отображается в память вызовом mmap. Этот файл будет содержать текущее значение структуры типа sem_t, хотя, поскольку мы только что отобразили файл в память, мы обращаемся к нему через указатель, возвращаемый mmap, и никогда не вызываем read или write.
Инициализация структуры sem_t43-57 Мы инициализируем три поля структуры sem_t: взаимное исключение, условную переменную и значение семафора. Поскольку именованный семафор Posix может совместно использоваться всеми процессами с соответствующими правами, которым известно его имя, при инициализации взаимного исключения и условной переменной необходимо указать атрибут PTHREAD_PROCESS_SHARED. Чтобы осуществить это для взаимного исключения, нужно сначала проинициализировать атрибуты, вызвав pthread_mutexattr_init, затем установить атрибут совместного использования потоками, вызвав pthread_mutexattr_setpshared, а затем проинициализировать взаимное исключение вызовом pthread_mutex_init. Аналогичные действия придется выполнить и для условной переменной. Необходимо аккуратно уничтожать переменные, в которых хранятся атрибуты, при возникновении ошибок.
Инициализация значения семафора58-61 Наконец мы помещаем в файл начальное значение семафора. Предварительно мы сравниваем его с максимально разрешенным значением семафора, которое может быть получено вызовом sysconf (раздел 10.13).
Сброс бита user-execute62-67 После инициализации семафора мы сбрасываем бит user-execute. Это указывает на то, что семафор был успешно проинициализирован. Затем мы закрываем файл вызовом close, поскольку он уже был отображен в память и нам не нужно держать его открытым.
В листинге 10.29 приведен текст второй половины функции sem_open. Здесь возникает ситуация гонок, обрабатываемая так же, как уже обсуждавшаяся в связи с листингом 5.19.
Листинг 10.29. Функция sem_open: вторая половина//my_pxsem_mmap/sem_open.с
69 exists:
70 if ((fd = open(pathname, O_RDWR)) < 0) {
71 if (errno == ENOENT && (oflag & O_CREAT))
72 goto again;
73 goto err;
74 }
75 sem = mmap(NULL, sizeof(mysem_t), PROT_READ | PROT_WRITE,
76 MAP_SHARED, fd, 0);
77 if (sem == MAP_FAILED)
78 goto err;
79 /* удостоверимся, что инициализация завершена */
80 for (i = 0; i < MAX TRIES; i++) {
81 if (stat(pathname, &statbuff) == –1) {
82 if (errno == ENOENT && (oflag & O_CREAT)) {
83 close(fd);
84 goto again;
85 }
86 goto err;
87 }
88 if ((statbuff.st_mode & S_IXUSR) == 0) {
89 close(fd);
90 sem->sem_magic = SEM_MAGIC;
91 return(sem);
92 }
93 sleep(1);
94 }
95 errno = ETIMEDOUT;
96 goto err;
97 pthreaderr:
98 errno = i;
99 err:
100 /* не даем вызовам unlink и munmap изменить код errno */
101 save_errno = errno;
102 if (created)
103 unlink(pathname);
104 if (sem != MAP_FAILED)
105 munmap(sem, sizeof(mysem_t));
106 close(fd);
107 errno = save_errno;
108 return(SEM_FAILED);
109 }
Открытие существующего семафора69-78 Здесь мы завершаем нашу работу, если либо не указан флаг O_CREAT, либо он указан, но семафор уже существует. В том и в другом случае мы открываем существующий семафор. Мы открываем файл вызовом open для чтения и записи, а затем отображаем его содержимое в адресное пространство процесса вызовом mmap.
ПРИМЕЧАНИЕ
Теперь легко понять, почему в Posix.1 сказано, что «обращение к копиям семафора приводит к неопределенным результатам». Если именованный семафор реализован через отображение файла в память, он отображается в адресное пространство всех процессов, в которых он открыт. Это осуществляется функцией sem_open для каждого процесса в отдельности. Изменения, сделанные одним процессом (например, изменение счетчика семафора), становятся доступны другим процессам через отображение в память. Если мы сделаем свою собственную копию структуры sem_t, она уже не будет общей для всех процессов. Хотя нам и может показаться, что вызовы срабатывают (функции для работы с семафором не будут возвращать ошибок, по крайней мере до вызова sem_close, которая не сможет отключить отображение для копии отображенного файла), с другими процессами мы при этом взаимодействовать не сможем. Однако заметьте (табл. 1.4), что области памяти с отображаемыми файлами передаются дочерним процессам при вызове fork, поэтому создание копии семафора ядром при порождении нового процесса проблем не вызовет.
Удостоверимся, что семафор проинициализирован79-96 Мы должны подождать, пока семафор не будет проинициализирован (если несколько потоков пытаются создать семафор приблизительно одновременно). Для этого мы вызываем stat и проверяем биты разрешений файла (поле st_mode структуры stat). Если бит user-execute снят, структура успешно проинициализирована.
Возврат кодов ошибок97-108 При возникновении ошибки нужно аккуратно вернуть ее код.
Функция sem_close
В листинге 10.30 приведен текст нашей функции sem_close, которая просто вызывает munmap для отображенного в память файла. Если вызвавший процесс продолжит пользоваться указателем, который был ранее возвращен sem_open, он получит сигнал SIGSEGV.
Листинг 10.30. Функция sem_close//my_pxsem_mmap/sem_close. с
1 #include "unpipc.h"
2 #include "semaphore.h"
3 int
4 mysem_close(mysem_t *sem)
5 {
6 if (sem->sem_magic != SEM_MAGIC) {
7 errno = EINVAL;
8 return(-1);
9 }
10 if (munmap(sem, sizeof(mysem_t)) == –1)
11 return(-1);
12 return(0);
13 }
Функция sem_unlink
Текст функции sem_unlink приведен в листинге 10.31. Она просто удаляет файл, через который реализован данный семафор, вызывая функцию unlink.
Листинг 10.31. Функция sem_unlink//my_pxsem_mmap/sem_unlink.с
1 #include "unpipc.h"
2 #include "semaphore.h"
3 int
4 mysem_unlink(const char *pathname)
5 {
6 if (unlink(pathname) == –1)
7 return(-1);
8 return(0);
9 }
Функция sem_post
В листинге 10.32 приведен текст функции sem_post, которая увеличивает значение семафора, возобновляя выполнение всех процессов, заблокированных в ожидании этого события.
Листинг 10.32. Функция sem_post//my_pxsem_mmap/sem_post.с
1 #include "unpipc.h"
2 #include "semaphore.h"
3 int
4 mysem_post(mysem_t *sem)
5 {
6 int n;
7 if (sem->sem_magic != SEM_MAGIC) {
8 errno = EINVAL;
9 return(-1);
10 }
11 if ((n = pthread_mutex_lock(&sem->sem_mutex)) != 0) {
12 errno = n;
13 return(-1);
14 }
15 if (sem->sem_count == 0)
16 pthread_cond_signal(&sem->sem_cond);
17 sem->sem_count++;
18 pthread_mutex_unlock(&sem->sem_mutex);
19 return(0);
20 }
11-18 Прежде чем работать со структурой, нужно заблокировать соответствующее взаимное исключение. Если значение семафора изменяется с 0 на 1, нужно вызвать pthread_cond_signal, чтобы возобновилось выполнение одного из процессов, зарегистрированных на уведомление по данной условной переменной.
Функция sem_wait
В листинге 10.33 приведен текст функции sem_wait, которая ожидает изменения значения семафора с 0 на положительное, после чего уменьшает его на 1.
Листинг 10.33. Функция sem_wait//my_pxsem_mmap/sem_wait.с
1 #include "unpipc.h"
2 #include "semaphore.h"
3 int
4 mysem_wait(mysem_t *sem)
5 {
6 int n;
7 if (setn->sem_magic != SEM_MAGIC) {
8 errno = EINVAL;
9 return(-1);
10 }
11 if ((n = pthread_mutex_lock(&sem->sem_mutex)) != 0) {
12 errno = n;
13 return(-1);
14 }
15 while (sem->sem_count == 0)
16 pthread_cond_wait(&sem->sem_cond, &sem->sem_mutex);
17 sem->sem_count--;
18 pthread_mutex_unlock(&sem->sem_mutex);
19 return(0);
20 }
11-18 Прежде чем работать с семафором, нужно заблокировать соответствующее взаимное исключение. Если значение семафора 0, выполнение процесса приостанавливается в вызове pthread_cond_wait до тех пор, пока другой процесс не вызовет pthread_cond_signal для этого семафора, изменив его значение с 0 на 1. После того как значение становится ненулевым, мы уменьшаем его на 1 и разблокируем взаимное исключение.