Операция блокировки части файла необходима для организации совместного доступа к файлу из нескольких процессов. Главная задача блокировки - обеспечение атомарности чтения данных, т.е. гарантия того, что при чтении (возможно несколькими вызовами read()) все данные будут принадлежать одной "версии" файла и не будут перезаписаны во время чтения. Соответственно бываю эксклюзивные блокировки со стороны писателя (я пишу в этот файл, другим в это время читать и писать бессмысленно) и разделяемые блокировки со стороны читателей (мы читаем, пожалуйста, не пишите).
В Unix традиционно используются рекомендательные (advisory) блокировки. Такие блокировки требуют от всех процессов, которые хотят получить доступ к файлу, вызова специальных функций для установки и/или проверки блокировок. Вызовы open(), read() и write() про блокировки ничего не знают, и, в результате, процессы, которые явно не вызывают функции работы с блокировками не узнают об их существовании и могут нарушить логику совместной работы с файлом.
Отдельной проблемой является то, что разные способы установки блокировок никак не взаимодействуют друг с другом. Так блокировка установленная вызовом fcntl() не может быть проверена вызовом flock(). Единственным реальным сценарием применения файловых блокировок в Unix является разработка комплекса программ с заранее прописанным алгоритмом синхронизации доступа к файлам через блокировки.
Хорошая статья на английском.
Самые ранние версии Unix не поддерживали блокировок, поэтому в различных программах можно встретить создание вспомогательного файла с расширением .lck или .lock. Перед записью в файл somefile.txt программа в цикле пытается создать файл somefile.txt.lck с флагами O_CREAT|O_EXCL. Если файл уже существует, то вызов open() возвращает ошибку и цикл продолжается. Если файл удалось успешно создать, то цикл завершается и можно открывать на запись основной файл. В служебный файл блокировки часто пишут PID процесса, чтобы было понятно, кто его создал.
int fd=-1;
while(fd==-1){
fd=open("somefile.lck", O_CREAT|O_EXCL|O_WRONLY,0500);
}
//Пишем в файл блокировки свой PID
char pid[6];
itoa(getpid(), pid, 10);
write(fd,pid,strlen(pid));
//этот файловый дескриптор нам больше не нужен
close(fd);
//основная работа
int mainfd=open("somefile", O_WRONLY,0500);
...
close(mainfd);
//Удаляем файл блокировки
unlink("somefile.lck");
Следующим шагом стало появление в BSD системах вызова flock(), который позволял пометить в ядре файл как заблокированный. Этот вызов не стандартизован POSIX, но поддерживается в Linux и во многих версиях Unix. flock() не поддерживается сетевой файловой системой NFS.
#include <sys/file.h>
int flock(int fd, int operation);
Операции:
flock() возвращает -1, а переменная errno устанавливается в значение EWOULDBLOCK.fcntl()Стандарт POSIX определяет операции с блокировками записей - участков файлов. Блокировки производятся универсальный вызов управления открытыми файлами fcntl() (file control) или через функцию locf(). В Linux locf() это библиотечная обёртка для fcntl().
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, struct flock *lock);
fcntl() - это перегруженный (в смысле C++) вызов ядра с переменным числом параметров. Его поведение определяется значением второго параметра - cmd. За блокировки отвечают команды F_SETLK, F_SETLKW и F_GETLK, которые используются для установки/снятия (SET) и чтения (GET) блокировок файла .
Параметры блокировки задаются/считываются через структуру flock. Блокируемая позиция задаётся параметром, указывающим откуда отсчитывать стартовое смещение, стартовым смещением и размером. Нулевой размер означает блокировку участка файла от стартового смещения и до бесконечности.
struct flock {
...
short l_type; /* Тип блокировки: F_RDLCK,
F_WRLCK, F_UNLCK */
short l_whence; /* Как интерпретировать l_start:
SEEK_SET, SEEK_CUR, SEEK_END */
off_t l_start; /* Начальное смещение для блокировки */
off_t l_len; /* Количество байт для блокировки */
pid_t l_pid; /* PID процесса блокирующего нашу блокировку
(F_GETLK only) */
...
};
Тип блокировки:
Команды:
fcntl() вернёт -1 и установит значение errno в EACCES или EAGAIN.lockf()Функция lockf() в Linux реализуется через fcntl() и представляет из себя упрощённый интерфейс для работы с блокировками.
#include <unistd.h>
int lockf(int fd, int cmd, off_t len);
lockf() работает с участком файла начиная с текущей позиции чтения/записи и длиною len байт если len > 0; c позиции раньше текущей на len и до текущей если len < 0; с текущей позиции до конца файла если len = 0.
Команды:
При программировании в shell можно воспользоваться командой
flock [options] <file|directory> <command> [command args]
Если отбросить подробности, то flock пытается установить блокировку (вызовом flock(), что почти очевидно) на указанный файл или каталог. Если блокировка уже установлена, то flock уходит в "сон". Как только файл становится доступным, flock блокирует его, выполняет указанную команду и завершается, освобождая блокировку.
В Linux и некоторых Unix возможна установка обязательных блокировок, которые приостановят вызовы read() и write() на заблокированных участках файла (документация по Linux). Для этого файловая система должна быть смонтирована с опцией-o mand, а в правах доступа к файлу должны быть одновременно выставлены флаг запрещения прав на выполнение группой и флаг смены группы процесса при выполнении SGID. В буквенной записи это выглядит так: -rw-r-Sr-- (если бы было право на исполнение группе, то была бы показана маленькая 's') .
write(), open() с флагом O_TRUNC и т.п.)read() и вызовы, изменяющие содержимое файла (write() и т.д.)