Чтение онлайн

ЖАНРЫ

UNIX: взаимодействие процессов

Стивенс Уильям Ричард

Шрифт:

В такой ситуации лучше пользоваться блокировкой fcntl, которая автоматически снимается по завершении процесса.

2. Если файл открыт каким-либо другим процессом, мы должны еще раз вызвать open, повторяя эти вызовы в бесконечном цикле. Это называется опросом и является напрасной тратой времени процессора. Альтернативным методом является вызов sleep на 1 секунду, а затем повторный вызов open (этапроблема обсуждалась в связи с листингом 7.4).

Эта проблема также исчезает при использовании блокировки fcntl, если использовать команду F_SETLKW. Ядро автоматически приостанавливает выполнение процесса до тех пор, пока ресурс не станет доступен.

3. Создание и удаление файла вызовом open и unlink приводит к обращению к файловой системе, что обычно

занимает существенно больше времени, чем вызов fcntl (обращение производится дважды: один раз для получения блокировки, а второй — для снятия). При использовании fcntl программа выполнила 10000 повторов цикла с увеличением порядкового номера в 75 раз быстрее, чем программа, вызывавшая open и unlink.

Листинг 9.9. Функции блокировки с использованием open с флагами O_CREAT и O_EXCL

//lock/lockopen.c

1 #include "unpipc.h"

2 #define LOCKFILE "/tmp/seqno.lock"

3 void

4 my_lock(int fd)

5 {

6 int tempfd;

7 while ((tempfd = open(LOCKFILE, O_RDWR|O_CREAT|O_EXCL, FILE_MODE)) < 0) {

8 if (errno != EEXIST)

9 err_sys("open error for lock file");

10 /* блокировка установлена кем-то другим, повторяем попытку */

11 }

12 Close(tempfd); /* открыли файл, блокировка установлена */

13 }

14 void

15 my_unlock(int fd)

16 {

17 Unlink(LOCKFILE); /* снимаем блокировку удалением файла */

18 }

Есть еще две особенности файловой системы Unix, которые использовались для реализации блокировок. Первая заключается в том, что функция link возвращает ошибку, если имя новой ссылки уже существует. Для получения блокировки создается уникальный временный файл, полное имя которого содержит в себе его идентификатор процесса (или комбинацию идентификаторов процесса и потока, если требуется осуществлять блокировку между отдельными потоками). Затем вызывается функция link для создания ссылки на этот файл с каким-либо определенным заранее именем. После успешного создания сам файл может быть удален вызовом unlink. После осуществления работы с блокировкой файл с известным именем удаляется командой unlink. Если link возвращает ошибку EEXIST, поток должен попытаться создать ссылку еще раз (аналогично листингу 9.9). Одно из требований к этому методу — необходимо, чтобы и временный файл, и ссылка находились в одной файловой системе, поскольку большинство версий Unix не допускают создания жестких ссылок (результат вызова link) в разных файловых системах.

Вторая особенность заключается в том, что функция open возвращает ошибку в случае существования файла, если указан флаг O_TRUNC и запрещен доступ на запись. Для получения блокировки мы вызываем open, указывая флаги O_CREAT | O_WRONLY | O_TRUNC и аргумент mode со значением 0 (то есть разрешения на доступ к файлу установлены в 0). Если вызов оказывается успешным, блокировка установлена и мы просто удаляем файл вызовом unlink после завершения работы. Если вызов open возвращает ошибку EACESS, поток должен сделать еще одну попытку (аналогично листингу 9.9). Этот трюк не срабатывает, если поток обладает правами привилегированного пользователя.

Урок, который можно извлечь из этих примеров, прост: нужно пользоваться блокировкой fcntl. Тем не менее вы можете столкнуться с программой, в которой используются старые методы блокировки,

и особенно часто это будет встречаться в программах, написанных до широкого распространения реализации с блокировкой fcntl.

9.9. Блокирование в NFS

Аббревиатура NFS расшифровывается как Network File System (сетевая файловая система); эта система подробно обсуждается в главе 29 [22]. Блокировка записей fcntl представляет собой расширение NFS, поддерживаемое большинством ее реализаций. Обслуживается эта блокировка двумя дополнительными демонами: lockd и statd. При вызове fcntl для получения блокировки ядро обнаруживает, что файл находится в файловой системе NFS. Тогда локальный демон lockd посылает демону lockd сервера запрос на получение блокировки. Демон statd хранит информацию о клиентах, установивших блокировку, и взаимодействует с lockd для обеспечения снятия блокировок в случае завершения процессов.

Установка блокировки записи в NFS должна занимать в среднем больше времени, чем для локального файла, поскольку для установки и снятия блокировки требуется передача информации по сети. Для проверки работы блокировки NFS нужно всего лишь изменить имя файла, определяемое константой SEQFILE в листинге 9.2. Если измерить время, требуемое для выполнения 10000 операций по увеличению порядкового номера новой версией программы, оно окажется примерно в 80 раз больше, чем для локального файла. Однако нужно понимать, что в этом случае происходит передача информации по сети и при операциях чтения и записи (для изменения порядкового номера).

ПРИМЕЧАНИЕ

Блокировка записей в NFS была связана с проблемами в течение многих лет, и большинство проблем были следствием плохой реализации. Несмотря на тот факт, что большинство производителей Unix все-таки доделали эту реализацию, использование блокировки fcntl через NFS все еще далеко от совершенства. Не будем делать безответственных утверждений: блокировка fcntl должна работать и в NFS, но будет ли — зависит от реализации демона и сервера. 

9.10. Резюме

Блокирование записей с помощью fcntl предоставляет возможность установки рекомендательной или обязательной блокировки для файла, указываемого с помощью открытого дескриптора. Эти блокировки предназначены для сотрудничества процессов, но не отдельных потоков одного процесса. Термин «запись» используется не вполне корректно, поскольку ядро не различает отдельные записи в файле. Лучше использовать термин «блокировка диапазона», поскольку при установке блокировки или ее снятии указывается именно диапазон байтов в файле. Практически во всех случаях применения этой блокировки она является рекомендательной и используется при совместной работе сотрудничающих процессов, поскольку даже обязательная блокировка не может исключить повреждения данных.

При использовании fcntl не гарантируется, что читающие или пишущие процессы имеют приоритет при ожидании (в отличие от того, что мы реализовали в главе 8 с блокировками чтения-записи). Если это важно для приложения, придется реализовать блокировки самостоятельно (как в разделе 8.4) с тем приоритетом, который требуется.

Упражнения

1. Создайте программу locknone из листингов 9.2 и 9.1 и выполните ее много раз. Убедитесь, что программа не работает и результат непредсказуем.

2. Измените листинг 9.2 так, чтобы стандартный поток вывода не буферизовался. Как это повлияет на работу программы?

3. Продолжайте изменять программу, вызывая putchar для каждого выводимого символа (вместо printf). Как изменится результат?

4. Измените блокировку в функции my_lock из листинга 9.3 так, чтобы устанавливалась блокировка на чтение, а не на запись. Что произойдет?

5. Измените вызов open в программе loopmain.c, указав также флаг O_NONBLOCK. Создайте программу loopfcntlnonb и запустите два экземпляра. Что произойдет? 

Поделиться с друзьями: