К счастью, существует способ исключить в данном случае ситуацию гонок. Стандарт гарантирует, что при создании набора семафоров поле sem_otime структуры semid_ds инициализируется нулем. (Руководства System V с давних пор говорят об этом, это утверждается и в стандартах XPG3 и Unix 98.) Это поле устанавливается равным текущему времени только при успешном вызове semop. Следовательно, второй процесс в приведенном выше примере должен просто вызвать semctl с командой IPC_STAT после второго вызова semget (строка 6). Затем этот процесс должен ожидать изменения значения sem_otime на ненулевое, после чего он может быть уверен в том, что семафор был успешно проинициализирован другим процессом. Это значит, что создавший семафор процесс должен проинициализировать его значение и успешно вызвать semop, прежде чем другие процессы смогут воспользоваться этим семафором. Мы используем этот метод в листингах 10.37 и 11.6.
11.3. Функция semop
После инициализации семафора вызовом semget с одним или несколькими семафорами набора можно выполнять некоторые действия с помощью функции semop:
#include
int semop(int
/* Возвращает 0 в случае успешного завершения, –1 – в случае ошибки */
Указатель opsptr указывает на массив структур вида
struct sembuf {
short sem_num; /* номер семафора: 0, 1,… nsems-1 */
short sem_op; /* операция с семафором: <0, 0, >0 */
short sem_flg; /* флаги операции: 0, IPC_NOWAIT, SEM_UNDO */
};
Количество элементов в массиве структур sembuf, на который указывает opsptr, задается аргументом nops. Каждый элемент этого массива определяет операцию с одним конкретным семафором набора. Номер семафора указывается в поле sen_num и принимает значение 0 для первого семафора, 1 для второго и т. д., до nsems-1, где nsems соответствует количеству семафоров в наборе (второй аргумент в вызове semget при создании семафора).
ПРИМЕЧАНИЕ
В структуре гарантированно содержатся только три указанных выше поля. Однако в ней могут быть и другие поля, причем порядок их может быть совершенно произвольным. Поэтому не следует статически инициализировать эту структуру кодом наподобие
struct sembuf ops[2] = {
0, 0, 0, /* ждем, пока первый элемент не станет равен нулю */
0, 1, SEM_UNDO /* затем увеличиваем [0] на 1 */
};
Вместо этого следует инициализировать ее динамически, как в нижеследующем примере:
struct sembuf ops[2];
ops[0].sem_num = 0; /* ждем, пока первый элемент не станет равен нулю */
ops[0].sem_op = 0;
ops[0].sem_flg = 0;
ops[1].sem_num = 0; /* затем увеличиваем [0] на 1 */
ops[1].sem_op = 1;
ops[1].sem_flg = SEM_UNDO;
Весь массив операций, передаваемый функции semop, выполняется ядром как одна операция; атомарность при этом гарантируется. Ядро выполняет все указанные операции или ни одну из них. Пример на эту тему приведен в разделе 11.5.
Каждая операция задается значением sem_op, которое может быть отрицательным, нулевым или положительным. Сделаем несколько утверждений, которыми будем пользоваться при дальнейшем обсуждении:
■ semval — текущее значение семафора (рис. 11.1);
■ semncnt — количество потоков, ожидающих, пока значение семафора не станет больше текущего (рис. 11.1);
■ semzcnt — количество потоков, ожидающих, пока значение семафора не станет нулевым (рис. 11.1);
■ semadj — корректировочное значение данного семафора для вызвавшего процесса. Это значение обновляется, только если для данной операции указан флаг SEM_UNDO в поле sem_flg структуры sembuf. Эта переменная создается в ядре для каждого указавшего флаг SEM_UNDO процесса в отдельности; поле структуры с именем semadj не обязательно должно существовать;