Выбрать главу

Команда 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.

вернуться

153

В системе GNU/Linux lockf() реализована в виде «оболочки» вокруг fcntl()Примеч. автора.

вернуться

154

Тупик (deadlock) является ситуацией, при которой оба процесса блокируются, причем каждый из них ждёт, пока другой освободит определенный ресурс — Примеч. автора.

вернуться

155

Удачно, что название flock() отличается от lockf(), поскольку их семантика различна. Это также страшно сбивает с толку. Держите свое руководство под рукой. — Примеч. автора.