Читать интересную книгу UNIX: взаимодействие процессов - Уильям Стивенс

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 57 58 59 60 61 62 63 64 65 ... 128

После помещения элемента в буфер блокировка с семафора mutex снимается (его значение увеличивается с 0 до 1) и увеличивается значение семафора nstored. Первый раз при выполнении этой команды значение nstored изменится с начального значения 0 до 1.

Потребитель ожидает изменения семафора nstored

57-62 Если значение семафора nstored больше 0, в буфере имеются объекты для обработки. Потребитель изымает один элемент из буфера и проверяет правильность его значения, защищая буфер в момент доступа к нему с помощью семафора mutex. Затем потребитель увеличивает значение семафора nempty, указывая производителю на наличие свободных полей.

Зависание

Что произойдет, если мы по ошибке поменяем местами вызовы Sem_wait в функции consumer (листинг 10.9)? Предположим, что первым запускается производитель (как в решении, предложенном для упражнения 10.1). Он помещает в буфер NBUFF элементов, уменьшая значение семафора nempty от NBUFF до 0 и увеличивая значение семафора nstored от 0 до NBUFF. Затем производитель блокируется в вызове Sem_wait(shared. nempty), поскольку буфер полон и помещать элементы больше некуда.

Запускается потребитель и проверяет первые NBUFF элементов буфера. Это уменьшает значение семафора nstored от NBUFF до 0 и увеличивает значение семафора nempty от 0 до NBUFF. Затем потребитель блокируется в вызове Sem_wait(shared, nstored) после вызова Sem_wait(shared, mutex). Производитель мог бы продолжать работу, поскольку значение семафора nempty уже отлично от 0, но он вызвал Sem_wait(shared, mutex) и его выполнение было приостановлено. 

Это называется зависанием программы (deadlock). Производитель ожидает освобождения семафора mutex, а потребитель не снимает с него блокировку, ожидая освобождения семафора nstored. Но производитель не может изменить nstored, пока он не получит семафор mutex. Это одна из проблем, которые часто возникают с семафорами: если в программе сделать ошибку, она будет работать неправильно.

ПРИМЕЧАНИЕ

Стандарт Posix позволяет функции sem_wait обнаруживать зависание и возвращать ошибку EDEADLK, но ни одна из систем, использовавшихся для написания примеров (Digital Unix 4.0B и Solaris 2.6), не обнаружила ошибку в данном случае.

10.7. Блокирование файлов

Вернемся к задаче о порядковом номере из главы 9. Здесь мы напишем новые версии функций my_lock и my_unlосk, использующие именованные семафоры Posix. В листинге 10.10 приведен текст этих функций.

Листинг 10.10. Блокирование файла с помощью именованных семафоров Posix

//lock/lockpxsem.c

1  #include "unpipc.h"

2  #define LOCK_PATH "pxsemlock"

3  sem_t *locksem;

4  int initflag;

5  void

6  my_lock(int fd)

7  {

8   if (initflag == 0) {

9    locksem = Sem_open(Px_ipc_name(LOCK_PATH), O_CREAT, FILE_MODE, 1);

10   initflag = 1;

11  }

12  Sem_wait(locksem);

13 }

14 void

15 my_unlock(int fd)

16 {

17  Sem_post(locksem);

18 }

Один из семафоров используется для рекомендательной блокировки доступа к файлу и инициализируется единицей при первом вызове функции. Для получения блокировки мы вызываем sem_wait, а для ее снятия — sem_post.

10.8. Функции sem_init и sem_destroy

До сих пор мы имели дело только с именованными семафорами Posix. Как мы уже говорили, они идентифицируются аргументом пате, обычно представляющим собой имя файла в файловой системе. Стандарт Posix описывает также семафоры, размещаемые в памяти, память под которые выделяет приложение (тип sem_t), а инициализируются они системой:

#include <semaphore.h>

int sem_init(sem_t *sem, int shared, unsigned int value);

/* Возвращает –1 в случае ошибки */

int sem_destroy(sem_t *sem);

/* Возвращает 0 в случае успешного завершения, –1 – в случае ошибки */

Размещаемый в памяти семафор инициализируется вызовом sem_init. Аргумент sem указывает на переменную типа sem_t, место под которую должно быть выделено приложением. Если аргумент shared равен 0, семафор используется потоками одного процесса, в противном случае доступ к нему могут иметь несколько процессов. Если аргумент shared ненулевой, семафор должен быть размещен в одном из видов разделяемой памяти и должен быть доступен всем процессам, использующим его. Как и в вызове sem_open, аргумент value задает начальное значение семафора.

После завершения работы с размещаемым в памяти семафором его можно уничтожить, вызвав sem_destroy.

ПРИМЕЧАНИЕ 1

Функции sem_open не требуется параметр, аналогичный shared; не требуется ей и атрибут, аналогичный PTHREAD_PROCESS_SHARED (упоминавшийся в связи с взаимными исключениями и условными переменными в главе 7), поскольку именованный семафор всегда используется совместно несколькими процессами.

ПРИМЕЧАНИЕ 2

Обратите внимание, что для размещаемого в памяти семафора нет ничего аналогичного флагу O_CREAT: функция sem_init всегда инициализирует значение семафора. Следовательно, нужно быть внимательным, чтобы вызывать sem_init только один раз для каждого семафора. (Упражнение 10.2 иллюстрирует разницу в этом смысле между именованным и размещаемым в памяти семафорами.) При вызове sem_init для уже инициализированного семафора результат непредсказуем.

ПРИМЕЧАНИЕ 3

Удостоверьтесь, что вы понимаете фундаментальную разницу между sem_open и sem_init. Первая возвращает указатель на переменную типа sem_t, причем выделение места под переменную и ее инициализация выполняются этой же функцией. Напротив, первый аргумент sem_init представляет собой указатель на переменную типа sem_t, место под которую должен был заранее выделить вызывающий. Функция sem_init только инициализирует эту переменную.

ПРИМЕЧАНИЕ 4

Стандарт Posix.1 предупреждает, что для обращения к размещаемым в памяти семафорам можно использовать только указатель, являющийся аргументом при вызове sem_init. Использование копий этого указателя может привести к неопределенным результатам.

Функция sem_init возвращает –1 в случае ошибки, но она не возвращает 0 в случае успешного завершения. Это действительно странно, и примечание в Обосновании Posix. 1 говорит, что в будущих версиях функция, возможно, начнет возвращать 0 в случае успешного завершения. 

Размещаемый в памяти семафор может быть использован в тех случаях, когда нет необходимости использовать имя, связываемое с именованным семафором. Именованные семафоры обычно используются для синхронизации работы неродственных процессов. Имя в этом случае используется для идентификации семафора.

В связи с табл. 1.1 мы говорили о том, что семафоры, размещаемые в памяти, обладают живучестью процесса, но на самом деле их живучесть зависит от типа используемой разделяемой памяти. Размещаемый в памяти семафор не утрачивает функциональности до тех пор, пока память, в которой он размещен, еще доступна какому-либо процессу.

■ Если размещаемый в памяти семафор совместно используется потоками одного процесса (аргумент shared при вызове sem_init равен 0), семафор обладает живучестью процесса и удаляется при завершении последнего.

■ Если размещаемый в памяти семафор совместно используется несколькими процессами (аргумент shared при вызове seminit равен 1), он должен располагаться в разделяемой памяти, и в этом случае семафор существует столько, сколько существует эта область памяти. Вспомните, что и разделяемая память Posix, и разделяемая память System V обладают живучестью ядра (табл. 1.1). Это значит, что сервер может создать область разделяемой памяти, инициализировать в ней размещаемый в памяти семафор Posix, а затем завершить работу. Некоторое время спустя один или несколько клиентов могут присоединить эту область к своему адресному пространству и получить доступ к хранящемуся в ней семафору.

Предупреждаем, что нижеследующий код не работает так, как ожидается:

sem_t mysem;

Sem_init(&mysem, 1.0); /* 2-й аргумент 1 –> используется процессами */

if (Fork() == 0) { /* дочерний процесс */

 …

 Sem_post(&mysem);

}

Sem_wait(&mysem); /* родительский процесс: ожидание дочернего */

Проблема тут в том, что семафор не располагается в разделяемой памяти (см. раздел 10.12). Память, как правило, не делится между дочерним и родительским процессами при вызове fork. Дочерний процесс запускается с копией памяти родителя, но это не то же самое, что разделяемая память.

Пример

В качестве иллюстрации перепишем наш пример решения задачи производителей и потребителей из листингов 10.8 и 10.9 для использования размещаемых в памяти семафоров Posix. В листинге 10.11 приведен текст новой программы.

1 ... 57 58 59 60 61 62 63 64 65 ... 128
На этом сайте Вы можете читать книги онлайн бесплатно русская версия UNIX: взаимодействие процессов - Уильям Стивенс.

Оставить комментарий