В [Stevens & Rago, 2005] утверждается: первая реализация блокировки файлов в UNIX датируется 1980 годом, а также отмечается, что вызов fcntl(), на котором мы сосредоточимся в этой главе, появился в 1984 году в System V Release 2.
В данной главе мы рассмотрим два разных программных интерфейса для блокировки файлов:
• вызов flock(), блокирующий весь файл целиком;
• вызов fcntl(), блокирующий отдельные участки файла.
Первый изначально появился в системе BSD, а второй — в System V.
Общий принцип использования этих вызовов выглядит следующим образом.
1. Блокировка файла.
2. Выполнение ввода/вывода.
3. Разблокировка файла, чтобы другой процесс мог его заблокировать.
Рис. 51.1.
Обычно блокировка применяется в сочетании с вводом/вывод, но также можно применять ее в качестве средства синхронизации. Процессы могут соблюдать соглашение, в соответствии с которым частичная или полная блокировка файла сигнализирует о доступе процесса к какому-то другому общему ресурсу (например, к участку разделяемой памяти).
Функции библиотеки stdio выполняют буферизацию в пользовательском пространстве, в связи с чем следует быть осторожными, применяя их вместе с методиками блокировки, описанными в данной главе. Дело в том, что буфер ввода может заполниться до создания блокировки, а буфер вывода может быть сброшен до ее удаления. Существует несколько способов, позволяющих избежать этих проблем:
• выполнять файловый ввод/вывод с помощью read() и write() (и аналогичных системных вызовов) вместо библиотеки stdio;
• сбрасывать поток stdio сразу же после создания блокировки, повторяя эту процедуру перед ее удалением;
• полностью отключить буферизацию ввода/вывода, используя вызов setbuf() или аналогичный (возможно, ценой ухудшения производительности).
В оставшейся части данной главы блокировка будет разделяться на необязательную и строгую. По умолчанию она является
Вызов flock() является лишь подмножеством вызова fcntl(), но мы все равно остановимся на нем отдельно, поскольку он до сих пор используется в ряде приложений и имеет некоторые семантические отличия, касающиеся наследования и удаления блокировок.
#include
int flock(int
Возвращает 0 при успешном завершении или -1 при ошибке
Системный вызов flock() блокирует весь файл целиком. Сам файл, который нужно заблокировать, передается аргументу fd в виде открытого дескриптора. Аргумент operation обозначает одну из операций: LOCK_SH, LOCK_EX или LOCK_UN (табл. 51.1).
По умолчанию вызов flock() блокируется, если другой процесс уже поместил несовместимую блокировку на заданный файл. Чтобы это предотвратить, можно применить к значениям аргумента operation побитовое ИЛИ (|). В таком случае, если другой процесс удерживает несовместимую блокировку для того же файла, то вызов flock() не заблокируется, а сразу же вернет -1 и укажет в переменной errno ошибку EWOULDBLOCK.
Таблица 51.1. Значения аргумента operation из вызова flock()
Значение — Описание
LOCK_SH — Применяет разделяемую блокировку к файлу, на который указывает fd
LOCK_EX — Применяет эксклюзивную блокировку к файлу, на который указывает fd
LOCK_UN — Разблокирует файл, на который указывает fd
LOCK_NB — Выполняет неблокирующий запрос блокировки
Разделяемую блокировку файла может одновременно удерживать неограниченное количество процессов. Для сравнения, эксклюзивную блокировку в любой отдельный момент времени может удерживать только один процесс (иными словами, это не дает другим процессам создавать любые блокировки — как разделяемые, так и эксклюзивные). В табл. 51.2 собраны правила совместимости для блокировок, созданных с помощью flock(). Предполагается, что процесс А первым разместил блокировку, а данные правила определяют, сможет ли затем процесс Б выполнить ее.
Таблица 51.2. Совместимость типов блокировки вызова flock()
Процесс А — Процесс Б
LOCK_SH — Да
LOCK_EX — Нет
LOCK_SH — Нет
LOCK_EX — Нет
Процесс может разместить эксклюзивную или разделяемую блокировку вне зависимости от режима доступа (чтение, запись или чтение + запись).