fd = open(file, O_CREAT | O_TRUNC | O_WRONLY, (mode_t) 0);
close(fd);
Успешное выполнение вызова open() (если заданного файла не существует) сигнализирует о получении блокировки. Возникшая ошибка (EACCES) говорит о том, что блокировка удерживается другим процессом и следует повторить попытку позже. Кроме ограничений, характерных для ранее описанных методик, такой подход имеет еще один недостаток: его нельзя использовать в программах с привилегиями администратора, так как вызов open() всегда будет завершаться успешно, независимо от прав доступа, установленных для файла.
Блокировки позволяют процессам синхронизировать свои действия с файлами. Linux предоставляет два системных вызова для управления блокировками: flock() (происходит из BSD-систем) и fcntl() (происходит из System V). И хотя оба они доступны в большинстве реализаций UNIX, только fcntl() входит в стандарт SUSv3.
Системный вызов flock() блокирует весь файл целиком. Он может устанавливать блокировки двух типов: разделяемые (совместимые с разделяемыми блокировками, которые удерживаются другими процессами) и эксклюзивные (не дающие другим процессам устанавливать блокировки любых типов).
Системный вызов fcntl() блокирует участок файла, размер которого может варьироваться от одного байта до объема всего файла (блокировка записей). Можно устанавливать блокировки двух типов: для чтения и для записи; по своей семантике они похожи на разделяемые и эксклюзивные блокировки, устанавливаемые вызовом flock(). Если блокирующий запрос на получение блокировки (F_SETLKW) приводит к взаимной блокировке, то ядро выбирает один из вовлеченных процессов и завершает его вызов fcntl() с ошибкой EDEADLK.
Блокировки, установленные с помощью вызовов flock() и fcntl(), не знают друг о друге (если только первый не реализован на основе второго, что встречается в некоторых системах). Они имеют разную семантику касательно наследования при вызове fork() и снятия при закрытии файловых дескрипторов.
Файл /proc/locks (доступный только в Linux) содержит список блокировок, удерживаемых всеми процессами в системе.
Подробное обсуждение блокировки записей вызовом fcntl() можно найти в книгах [Stevens & Rago, 2005] и [Stevens, 1999]. В [Bovet & Cesati, 2005] описаны детали реализации вызовов flock() и fcntl(). [Tanenbaum, 2007] и [Deitel et al., 2004] содержат описание понятия взаимной блокировки, включая способы ее обнаружения, обхода и предотвращения.
51.1. Поэкспериментируйте с запуском нескольких экземпляров программы из листинга 51.1 (t_flock.c), чтобы определить следующие аспекты работы вызова flock().
• Может ли процесс, пытающийся установить эксклюзивную блокировку для файла, зависнуть из-за группы других процессов, устанавливающих для того же файла разделяемые блокировки?
• Представьте: к файлу применена эксклюзивная блокировка и другие процессы ждут своей очереди, чтобы применить к этому файлу разделяемые и эксклюзивные блокировки. Существуют ли какие-то правила, определяющие, какой процесс получит следующую блокировку при снятии предыдущей? Например, имеют ли разделяемые блокировки приоритет перед эксклюзивными или наоборот? Применяются ли они по принципу «первым пришел — первым ушел»?
• Если у вас есть доступ к какой-то другой UNIX-системе, поддерживающей вызов flock(), то попробуйте определить, какие правила в ней действуют.
51.2. Напишите программу, которая определяет, поддерживает ли вызов flock() обнаружение взаимной блокировки в ситуациях, когда с ее помощью блокируются два разных файла из двух разных процессов.
51.3. Напишите программу, проверяющую утверждение относительно семантики наследования и снятия блокировок, приведенное в подразделе 51.2.1.
51.4. Поэкспериментируйте с запуском программ из листингов 51.1 (t_flock.c) и 51.2 (i_fcntl_locking.c), чтобы понять, влияют ли друг на друга блокировки, установленные с помощью вызовов flock() и fcntl(). Если у вас есть доступ к другой реализации UNIX, то проведите тот же эксперимент и в ней.
51.5. В подразделе 51.3.4 отмечалось: в Linux время, требуемое для добавления или проверки наличия блокировки, зависит от местоположения блокировки в списке, относящемся к конкретному файлу. Чтобы проверить это, напишите две программы.
• Первая программа должна применить к файлу, скажем, 40 001 блокировку. Все они должны быть установлены через один байт, то есть 0, 2, 4, 6 и т. д., вплоть до, скажем, 80 000. Выполнив данную процедуру, процесс должен заснуть.
• Пока первая программа бездействует, вторая должна выполнить в цикле, скажем, 10 000 итераций, пытаясь с помощью операции F_SETLK заблокировать один из байтов, блокировка для которого была установлена в предыдущем шаге (эти попытки будут неизменно завершаться неудачей). При каждом выполнении программа должна пытаться заблокировать N * 2 байт файла.