Арнольд Роббинс - Linux программирование в примерах
Значение F_UNLCK для l_type снимает блокировку. В общем, это простейший способ снять те самые блоки, которые были установлены ранее, но можно «расщепить» блок, освободив диапазон байтов в середине ранее установленного более крупного блока. Например:
struct employee { /* что угодно */ }; /* Описание сотрудника */
struct flock lock; /* Структура блока */
...
/* Заблокировать сотрудников 6-8 */
lock.l_whence = SEEK_SET; /* Абсолютное положение */
lock.l_start = 5 * sizeof(struct employee); /* Начало 6-й структуры */
lock.l_len = sizeof(struct employee) * 3; /* Заблокировать 3 записи */
/* ...установка блокировки (см. следующий раздел)... */
/* Освобождение записи 7: предыдущий блок расщепляется на два: */
lock.l_whence = SEEK_SET; /* Абсолютное положение */
lock.l_start = 6 * sizeof(struct employee); /* Начало 7-й структуры */
lock.l_len = sizeof(struct employee) * 1; /* Разблокирование 1-й записи */
/* ...снятие блокировки (см. следующий раздел)... */
14.2.2.2. Установка и снятие блокировок
После заполнения структуры struct flock следующим шагом является запрос блокировки. Этот шаг осуществляется с помощью соответствующего значения аргумента cmd функции fcntl():
F_GETLK Узнать, можно ли установить блокировку.
F_SETLK Установить или снять блокировку.
F_SETLKW Установить блокировку, подождав, пока это будет возможным.
Команда F_GETLK является командой «Мама, можно мне?» Она осведомляется, доступна ли описанная struct flock блокировка. Если она доступна, блокировка не устанавливается; вместо этого операционная система изменяет поле l_type на F_UNLCK. Другие поля остаются без изменений.
Если блокировка недоступна, операционная система заполняет различные поля сведениями, описывающими уже установленные блокировки, которые препятствуют установке новой. В этом случае l_pid содержит PID процесса, владеющего соответствующей блокировкой.[152] Если блокировка уже установлена, нет другого выбора, кроме ожидания в течение некоторого времени и новой попытки установки блокировки или вывода сообщения об ошибке и отказа от дальнейших попыток.
Команда F_SETLK пытается установить указанную блокировку. Если fcntl() возвращает 0, блокировка была успешно установлена. Если она возвращает -1, блокировку установил другой процесс. В этом случае в errno устанавливается либо EAGAIN (попытайтесь снова позже) или EACCESS (нет доступа). Возможны два значения, чтобы удовлетворить старым системам.
Команда F_SETLKW также пытается установить указанную блокировку. Она отличается от F_SETLK тем, что будет ждать, пока установка блокировки не окажется возможной.
Выбрав соответствующее значение для аргумента cmd, передайте его в качестве второго аргумента fcntl() вместе с указателем на заполненную структуру struct flock в качестве третьего аргумента:
struct flock lock;
int fd;
/* ...открыть файл, заполнить struct flock... */
if (fcntl(fd, F_SETLK, &lock) < 0) {
/* Установить не удалось, попытаться восстановиться */
}
Функция lockf()[153] предоставляет альтернативный способ установки блокировки в текущем положении файла.
#include <sys/file.h> /* XSI */
int lockf(int fd, int cmd, off_t len);
Дескриптор файла fd должен быть открыт для записи. len указывает число блокируемых байтов: от текущего положения (назовем его pos) до pos + len байтов, если len положительно, или от pos - len до pos - 1, если len отрицательно. Команды следующие:
F_LOCK Устанавливает исключительную блокировку диапазона. Вызов блокируется до тех пор, пока блокировка диапазона не станет возможной.
F_TLOCK Пытается установить блокировку. Это похоже на F_LOCK, но если блокировка недоступна, F_TLOCK возвращает ошибку.
F_ULOCK Разблокирует указанный раздел. Это может вызвать расщепление блокировки, как описано выше.
F_TEST Проверяет, доступна ли блокировка. Если доступна, возвращает 0 и устанавливает блокировку. В противном случае возвращает -1 и устанавливает в errno EACCESS.
Возвращаемое значение равно 0 в случае успеха и -1 при ошибке, с соответствующим значением в errno. Возможные значения ошибок включают:
EAGAIN Файл заблокирован, для F_TLOCK или F_TEST.
EDEADLK Для F_TLOCK эта операция создала бы тупик.[154]
ENOLCK Операционная система не смогла выделить блок.
Полезна комбинация F_TLOCK и EDEADLK: если вы знаете, что тупик не может возникнуть никогда, используйте F_LOCK. В противном случае, стоит обезопасить себя и использовать F_TLOCK. Если блокировка доступна, она осуществляется, но если нет, у вас появляется возможность восстановления вместо блокирования в ожидании, возможно, навечно.
Завершив работу с заблокированным участком, его следует освободить. Для fcntl() возьмите первоначальную struct lock, использованную для блокирования, и измените поле l_type на F_UNLCK. Затем используйте F_SETLK в качестве аргумента cmd:
lock.l_whence = ... ; /* Как раньше */
lock.l_start = ... ; /* Как раньше */
lock.l_len = ... ; /* Как раньше */
lock.l_type = F_UNLCK; /* Разблокировать */
if (fcntl(fd, F_SETLK, &lock) < 0) {
/* обработать ошибку */
}
/* Блокировка была снята */
Код, использующий lockf(), несколько проще. Для краткости мы опустили проверку ошибок:
off_t curpos, len;
curpos = lseek(fd, (off_t)0, SEEK_CUR); /* Получить текущее положение */
len = ... ; / * Установить соответствующее число блокируемых байтов */
lockf(fd, F_LOCK, len); / * Осуществить блокировку */
/* ...здесь использование заблокированного участка... */
lseek(fd, curpos, SEEK_SET); / * Вернуться к началу блокировки */
lockf(fd, F_ULOCK, len); /* Разблокировать файл */
Если вы не освободите блокировку явным образом, операционная система сделает это за вас в двух случаях. Первый случай, когда процесс завершается (либо при возвращении из main(), либо с использованием функции exit(), которую мы рассматривали в разделе 9.1.5.1 «Определение статуса завершения процесса»). Другим случаем является вызов close() с дескриптором файла: больше об этом в следующем разделе.
14.2.2.3. Предостережения по поводу блокировок
Имеется несколько предостережений, о которых нужно знать при блокировках файлов:
• Как описано ранее, вспомогательная блокировка является именно этим. Не взаимодействующий процесс может делать все, что хочет, за спиной (так сказать) процесса, осуществляющего блокировку.
• Эти вызовы не следует использовать в сочетании с библиотекой <stdio.h>. Эта библиотека осуществляет свое собственное буферирование. Хотя вы можете получить с помощью fileno() дескриптор нижележащего файла, действительное положение в файле может быть не там, где вы думаете. В общем, стандартная библиотека ввода/вывода не понимает блокировок файлов.
• Держите в уме, что блокировки после fork не наследуются порожденными процессами, но они остаются на своем месте после exec.
• Вызов close() с любым открытым для файла дескриптором удаляет все блокировки файла процессом, даже если другие дескрипторы для файла остаются открытыми.
То, что close() работает таким образом, является неудачным, но поскольку так была реализована первоначальная блокировка в fcntl(), POSIX ее стандартизует. Стандартизация такого поведения позволяет избежать порчи существующего кода для Unix.
14.2.3. Блокирование BSD: flock()
4.2 BSD представило свой собственный механизм блокировки, flock()[155]. Функция объявлена следующим образом:
#include <sys/file.h> /* Обычный */
int flock(int fd, int operation);
Дескриптор fd представляет открытый файл. Имеются следующие операции:
LOCK_SH Создает совместную блокировку. Может быть несколько совместных блокировок.
LOCK_EX Создает исключительную блокировку. Может быть лишь одна такая блокировка.
LOCK_UN Удаляет предыдущую блокировку.
LOCK_NB При использовании побитового ИЛИ с LOCK_SH или LOCK_EX позволяет избежать блокирования функции, если блокировка файла невозможна.
По умолчанию запросы блокировки файла будут блокировать функцию (не давать ей вернуться), если существует конкурирующая блокировка. Запрашивающая функция возвращается, когда конкурирующая блокировка файла снимается и осуществляется запрошенная функцией блокировка файла. (Это предполагает, что по умолчанию имеется возможность возникновения тупика.) Чтобы попытаться заблокировать файл без блокирования функции, добавьте посредством побитового ИЛИ значение LOCK_NB к имеющемуся значению operation.