Операционная система UNIX. Руководство программиста

Блокировка и разблокирование сегментов


Блокировка сегментов осуществляется практически тем же способом, что и блокировка файлов. Различие состоит только в начальной точке и длине блокируемого участка. Далее мы рассмотрим интересную и практически важную задачу. Есть два сегмента (в одном или двух файлах), которые должны быть изменены одновременно таким образом, чтобы другие процессы не могли получить доступ к промежуточному состоянию изменяемой информации. (Подобные задачи встречаются, например, при необходимости изменить указатели в двунаправленных списках.) Чтобы решить нашу задачу, необходимо ответить на следующие вопросы:

  • Что нужно блокировать?
  • Если сегментов для блокировки много, в каком порядке блокировать и разблокировать их?
  • Что делать в случае удачной блокировки всех нужных сегментов?
  • Что делать в случае неудачи при попытке блокировки некоторого сегмента?
  • При управлении блокировкой сегментов мы должны планировать свои действия на случай неудачной попытки блокировки. Эти действия в первую очередь зависят от характера информации, хранящейся в тех сегментах, которые мы пытаемся блокировать. Возможны, например, следующие действия:

  • Подождать некоторое время и повторить попытку.
  • Аварийно завершить процедуру и предупредить пользователя.
  • Перевести процесс в состояние ожидания до тех пор, пока не поступит сигнал о снятии блокировки.
  • Скомбинировать перечисленные выше действия.
  • Теперь рассмотрим пример включения элемента в двунаправленный список. Допустим, что сегмент, после которого мы хотим включить новый элемент, уже заблокирован на чтение. Чтобы этот сегмент можно было корректировать, блокировка должна быть снята и уста- новлена снова или ее уровень повышен до блокировки на запись.

    Повышение уровня блокировки (обычно от блокировки на чтение до блокировки на запись) разрешается в том случае, если нет других процессов, заблокировавших на чтение ту же часть файла. Наличие процессов, ожидающих возможности заблокировать на запись ту же часть файла, не мешает повышению уровня: эти процессы останутся в состоянии ожидания. Понижение уровня блокировки с блокировки на запись до блокировки на чтение возможно всегда. В обоих случаях просто изменяется значение вида блокировки. По той причине, что функция lockf(3C) (в соответствии со стандартом /usr/group) не устанавливает блокировки на чтение, изменение уровня блокировки в случае использования этой функции невозможно. Ниже приведен пример блокировки сегмента с последующим изменением ее уровня:




    struct record { . . . /* Данные сегмента */ . . . long prev; /* Указатель на предыдущий сегмент в списке */ long next; /* Указатель на следующий сегмент в списке */ };

    /* Изменение уровня блокировки с использованием fcntl(2). Предполагается, что к тому моменту, когда эта программа будет выполняться, сегменты here и next будут заблокированы на чтение. Если заблокируем на запись here и next, то: блокируем на запись сегмент this; возвращаем указатель на сегмент this. Если любая из попыток блокировки окончится неудачей, то: переустанавливаем блокировку сегментов here и next на чтение; снимаем все остальные блокировки; возвращаем -1. */

    long set3lock (this, here, next) long this, here, next; { struct flock lck;

    lck.l_type = F_WRLCK; /* Блокируем на запись */ lck.l_whence = 0; /* Смещение от начала будет равно l_start */ lck.l_start = here; lck.l_len = sizeof (struct record);

    /* Повышение уровня блокировки сегмента here до блокировки на запись */ if (fcntl (fd, F_SETLKW, &lck) < 0) { return (-1); }

    /* Блокируем сегмент this на запись */ lck.l_start = this; if (fcntl (fd, F_SETLKW, &lck) < 0) { /* Блокировка сегмента this не удалась. Понижаем блокировку сегмента here до уровня чтения */ lck.l_type = F_RDLCK; lck.l_start = here; (void) fcntl (fd, F_SETLKW, &lck); return (-1); }

    /* Повышение уровня блокировки сегмента next до блокировки на запись */ lck.l_start = next; if (fcntl (fd, F_SETLKW, &lck) < 0) { /* Блокировка сегмента next не удалась. Понижаем блокировку сегмента here до уровня чтения...*/ lck.l_type = F_RDLCK; lck.l_start = here; (void) fcntl (fd, F_SETLKW, &lck); /*...и снимаем блокировку сегмента this */ lck.l_type = F_UNLCK; lck.l_start = this; (void) fcntl (fd, F_SETLKW, &lck); return (-1); /* Не смогли заблокировать */ } return (this); }

    Если другие процессы мешают заблокировать нужные сегменты, то процесс перейдет в состояние ожидания, что обеспечивается операцией F_SETLKW. Если же использовать операцию F_SETLK, то в случае невозможности блокировки системный вызов fcntl(2) завершается неудчей. В последнем случае программу нужно было бы изменить для обработки подобной ситуации.



    Теперь рассмотрим сходный пример с использованием функции lockf(3C). Так как она не позволяет выполнять блокировку на чтение, под блокировкой будет пониматься блокировка на запись.

    /* Повышение уровня блокировки с использованием lockf(3C). Предполагается, что в тот момент, когда программа будет выполняться, сегменты here и next не будут заблокированы. Если блокировка удастся, то: блокируем сегмент this; возвращаем указатель на сегмент this. Если любая из попыток блокировки окончится неудачей, то: разблокируем все остальные сегменты; возвращаем -1. */

    #include <unistd.h>

    long set3lock (this, here, next) long this, here, next; { /* Блокируем сегмент here */ (void) lseek (fd, here, 0); if (lockf (fd, F_LOCK, sizeof (struct record)) < 0) { return (-1); }

    /* Блокируем сегмент this */ (void) lseek (fd, this, 0); if (lockf (fd, F_LOCK, sizeof (struct record)) < 0) { /* Блокировка сегмента this не удалась. Разблокируем here */ (void) lseek (fd, here, 0); (void) lockf (fd, F_ULOCK, sizeof (struct record)); return (-1); }

    /* Блокируем сегмент next */ (void) lseek (fd, next, 0); if (lockf (fd, F_LOCK, sizeof (struct record)) < 0) { /* Блокировка сегмента next не удалась. Разблокируем сегмент here...*/ (void) lseek (fd, here, 0); (void) lockf (fd, F_ULOCK, sizeof (struct record)); /*...и разблокируем сегмент this */ (void) lseek (fd, this, 0); (void) lockf (fd, F_ULOCK, sizeof (struct record)); return (-1); /* Не смогли заблокировать */ } return (this); }

    Блокировка снимается тем же способом, что и устанавливается, только в случае системного вызова fcntl(2) в качестве режима блокировки указывается F_UNLCK, а при использовании функции lockf(3C) - F_ULOCK. Разблокирование не может быть предотвращено другим процессом. Разблокирование можно производить только в отношении блокировок, ранее установленных тем же процессом. Разблокирование касается только сегментов, которые были заданы в предыдущем примере структурой lck. Можно производить разблокирование или изменять тип блокировки для части ранее блокированного сегмента, однако это может привести к появлению дополнительного элемента в системной таблице блокировок.




    Содержание раздела