Причина, по которой была введена команда F_GETLK, — необходимость получения информации о блокировке в том случае, когда F_SETLK возвращает ошибку. Мы можем узнать, кто и каким образом заблокировал ресурс (на чтение или на запись). Но и в этом случае мы должны быть готовы к тому, что F_GETLK вернет результат F_UNLCK, поскольку между двумя вызовами другой процесс мог освободить ресурс.
Структура flock описывает тип блокировки (чтение или запись) и блокируемый диапазон. Как и в 1 seek, начальный сдвиг представляет собой сдвиг относительно начала файла, текущего положения или конца файла, и интерпретируется в зависимости от значения поля l_whence (SEEK_SET, SEEK_CUR, SEEK_END).
Поле l_len указывает длину блокируемого диапазона. Значение 0 соответствует блокированию от l_start до конца файла. Существуют, таким образом, два способа заблокировать файл целиком:
1. Указать l_whence = SEEK_SET, l_start = 0 и l_len = 0.
2. Перейти к началу файла с помощью lseek, затем указать l_whence = SEEK_CUR, l_start = 0 и l_len = 0.
Чаще всего используется первый метод, поскольку он предусматривает единственный вызов (fcntl — см. также упражнение 9.10).
Блокировка может быть установлена либо на чтение, либо на запись, и для конкретного байта файла может быть задан только один тип блокировки. Более того, на конкретный байт может быть установлено несколько блокировок на чтение, но только одна блокировка на запись. Это соответствует тому, что говорилось о блокировках чтения-записи в предыдущей главе. Естественно, при попытке установить блокировку на чтение для файла, открытого только на запись, будет возвращена ошибка.
Все блокировки, установленные конкретным процессом, снимаются при закрытии дескриптора файла этим процессом и при завершении его работы. Блокировки не наследуются дочерним процессом при вызове fork.
ПРИМЕЧАНИЕ
Снятие блокировок при завершении процесса обеспечивается только для блокировок записей fcntl и (в качестве дополнительной возможности) для семафоров System V. Для других средств синхронизации (взаимных исключений, условных переменных, блокировок чтения-записи и семафоров Posix) автоматическое снятие при завершении процесса не предусматривается. Об этом мы говорили в конце раздела 7.7.
Блокировка записей не должна использоваться со стандартной библиотекой ввода-вывода, поскольку функции из этой библиотеки осуществляют внутреннюю буферизацию. С заблокированными файлами следует использовать функции read и write, чтобы не возникало неожиданных проблем.
Пример
Вернемся к нашему примеру из листинга 9.2 и перепишем функции my_lock и my_unlock из листинга 9.1 так, чтобы воспользоваться блокировкой записей Posix. Текст этих функций приведен в листинге 9.3.
//lock/lockfcntl.c
1 #include "unpipc.h"
2 void
3 my_lock(int fd)
4 {
5 struct flock lock;
6 lock.l_type = F_WRLCK;
7 lock.l_whence = SEEK_SET;
8 lock.l_start = 0;
9 lock.l_len = 0; /* блокирование всего файла на запись */
10 Fcntl(fd, F_SETLKW, &lock);
11 }
12 void
13 my_unlock(int fd)
14 {
15 struct flock lock;
16 lock.l_type = F_UNLCK;
17 lock.l_whence = SEEK_SET;
18 lock.l_start = 0;
19 lock.l_len = 0; /* разблокирование всего файла */
20 Fcntl(fd. F_SETLK, &lock);
21 }
Обратите внимание, что мы устанавливаем блокировку на запись, что гарантирует единственность изменяющего данные процесса (см. упражнение 9.4). При получении блокировки мы используем команду F_SETLKW, чтобы приостановить выполнение процесса при невозможности установки блокировки.
ПРИМЕЧАНИЕ
Зная определение структуры flock, приведенное выше, мы могли бы проинициализировать структуру my_lock как
static struct flock lock = { F_WRLCK, SEEK_SET, 0, 0, 0 };
но это неверно. Posix определяет только обязательные поля структуры, а реализации могут менять их порядок и добавлять к ним дополнительные.