В командной строке мы указываем параметр, отключающий блокировку (-n), и три операции, каждая из которых уменьшает одно из значений набора семафоров. Первая операция завершается успешно (мы можем вычесть 1 из значения первого элемента набора, потому что до вычитания оно равно 1), вторая операция также проходит (вычитаем 2 из значения второго семафора, равного 2), но третья операция выполнена быть не может (мы не можем вычесть 4 из значения третьего семафора, потому что оно равно 3). Поскольку последняя операция последовательности не может быть выполнена и поскольку мы отключили режим блокирования процесса, функция возвращает ошибку EAGAIN. Если бы мы не указали флаг отключения блокировки, выполнение процесса было бы приостановлено до тех пор, пока операция вычитания не стала бы возможной. После этого мы проверяем, чтобы ни одно из значений семафоров набора не изменилось. Хотя первые две операции и могли бы быть выполнены, ни одна из трех на самом деле произведена не была, поскольку последняя операция была некорректной. Атомарность semop и означает, что выполняются либо все операции, либо ни одна из них.
Теперь продемонстрируем работу флага SEM_UNDO:
solaris % semsetvalues /tmp/rich 1 2 3
solaris % semops –u /tmp/rich -1 –2 –3
solaris % semgetvalues /tmp/rich
semval[0] = 1
semval[1] = 2
semval[2] = 3
solaris % semops /tmp/rich -1 –2 –3
solaris % semgetvalues /tmp/rich
semval[0] = 0
semval[1] = 0
semval[2] = 0
Сначала мы заново устанавливаем значения семафоров в наборе равными 1, 2 и 3 с помощью программы semsetvalues, а затем запускаем программу semops с операциями –1, –2, –3. При этом все три значения семафоров становятся нулевыми, но, так как мы указали параметр –u при вызове semops, для всех трех операций устанавливается флаг SEM_UNDO. При этом значения semadj для элементов набора семафоров становятся равными 1, 2 и 3 соответственно. После завершения программы semops эти значения добавляются к значениям семафоров, в результате чего их значения становятся равными 1, 2 и 3, как будто мы и не запускали программу. В этом мы убеждаемся, запустив semgetvalues. Затем мы снова запускаем semops, но уже без параметра –u, и убеждаемся, что при этом значения семафоров становятся нулевыми и остаются таковыми даже после выхода из программы.
11.6. Блокирование файлов
С помощью семафоров System V можно реализовать еще одну версию функций my_lock и my_unlock из листинга 10.10. Новый вариант приведен в листинге 11.6.
//lock/locksvsem.c
1 #include "unpipc.h"
2 #define LOCK_PATH "/tmp/svsemlock"
3 #define MAX_TRIES 10
4 int semid, initflag;
5 struct sembuf postop, waitop;
6 void
7 my_lock (int fd)
8 {
9 int oflag, i;
10 union semun arg;
11 struct semid_ds seminfo;
12 if (initflag == 0) {
13 oflag = IPC_CREAT | IPC_EXCL | SVSEM_MODE;
14 if ((semid = semget(Ftok(LOCK_PATH, 0), 1, oflag)) >= 0) {
15 /* этот процесс создал семафор первым, он же его и инициализирует */
16 arg.val = 1;
17 Semctl(semid, 0, SETVAL, arg);
18 } else if (errno == EEXIST) {
19 /* семафор создан другим процессом, убедимся, что он проинициализирован */
20 semid = Semget(Ftok(LOCK_PATH, 0), 1, SVSEM_MODE);
21 arg.buf = &seminfo
22 for (i = 0; i < MAX_TRIES; i++) {
23 Semctl(semid, 0, IPC_STAT, arg);
24 if (arg.buf->sem_otime != 0)
25 goto init;
26 sleep(1);
27 }
28 err_quit("semget OK, but semaphore not initialized");