Допустим, для файла включена строгая блокировка. Что произойдет, если системный вызов, который передает данные (то есть выполняет операции вроде read() или write()), сталкивается с конфликтующей блокировкой (то есть пытается записать или прочитать данные с участка, заблокированного для чтения или записи)? Ответ зависит от того, в каком режиме был открыт файл — блокирующем или неблокирующем. В первом случае системный вызов блокируется, а во втором — немедленно завершается ошибкой EAGAIN. По похожему принципу работают вызовы truncate() и ftruncate(), когда пытаются добавить или удалить данные с участка, покрытого блокировкой (для чтения или записи), принадлежащей другому процессу.
При открытии файла в блокирующем режиме (то есть если в вызове open() не был указан флаг O_NONBLOCK) системные вызовы для ввода/вывода могут привести к взаимной блокировке. Взгляните на пример, представленный на рис. 51.7: два процесса, открывшие один и тот же файл в блокирующем режиме, получают блокировки для записи на разные участки этого файла и затем каждый из них пытается выполнить запись на участок, заблокированный другим процессом. Ядро выходит из данной ситуации так же, как в случае с двумя вызовами fcntl(), блокирующими друг друга (см. подраздел 51.3.1): выбирает один заблокированный процесс и делает так, чтобы его операция write() завершилась ошибкой EDEADLK.
Любая попытка открыть файл с флагом O_TRUNC сразу же завершается неудачей (ошибкой EAGAIN), если в любой части того же файла другой процесс удерживает блокировку для чтения или записи.
Если к
Рис. 51.7.
Строгая блокировка не настолько удобна, как можно было бы ожидать. К тому же она имеет ряд недостатков.
• Файл, для которого удерживается строгая блокировка, может быть удален другими процессами, поскольку для этого нужно всего лишь нужно обладать подходящими правами доступа к его родительскому каталогу.
• Прежде чем применять строгую блокировку к публично доступному файлу, стоит тщательно все обдумать, так как ее нельзя переопределить даже привилегированным процессом. Злоумышленник может совершить DoS-атаку, постоянно удерживая блокировку (в большинстве случаев файл можно снова сделать доступным, отключив бит установки группового идентификатора, но иногда это не представляется возможным — например, если строгая блокировка привела к зависанию системы).
• Использование строгой блокировки влечет за собой снижение производительности. При каждой операции ввода/вывода, направленной на файл с поддержкой таких блокировок, ядро вынуждено проверять наличие конфликтов. Если файл содержит большое количество блокировок, то эта проверка может существенно замедлить системные вызовы ввода/вывода.
• Строгая блокировка также влияет на архитектуру приложения. Следует учитывать, что любая операция ввода/вывода может вернуть ошибку EAGAIN (для неблокирующего ввода/вывода) или EDEADLK (для блокирующего ввода/вывода).
• Ряд случаев состояния гонки в текущей реализации ядра Linux приводит к тому, что системные вызовы, выполняющие ввод/вывод, в отдельных ситуациях могут завершиться успешно даже при наличии строгих блокировок, которые должны были бы их отклонить.
В целом применения строгих блокировок лучше избегать.
Список блокировок, удерживаемых в системе, можно просмотреть в файле /proc/locks (доступном только в Linux). Ниже показан пример его содержимого (в данном случае в нем находятся сведения о четырех блокировках):
$ cat /proc/locks
1: POSIX ADVISORY WRITE 458 03:07:133880 0 EOF
2: FLOCK ADVISORY WRITE 404 03:07:133875 0 EOF
3: POSIX ADVISORY WRITE 312 03:07:133853 0 EOF