Блокировка файлов и сегментов СОДЕРЖАНИЕ 1. Введение 2. Терминология 3. Блокировка файлов и сегментов 3.1. Открытие файлов для блокировки сегментов 3.2. Блокировка файлов 3.3. Блокировка и разблокирование сегментов 3.4. Получение информации о блокировке 3.5. Управление тупиковыми ситуациями 4. Выбор слабой или сильной блокировки 4.1. Сильная блокировка. Некоторые предупреждения 4.2. Блокировка сегментов и развитие системы UNIX 1. ВВЕДЕНИЕ В текущей версии системы UNIX реализованы два вида блокировки сегментов и файлов: слабая и сильная. Назначение этих средств - дать возможность программам, одновременно обрабатывающим одни и те же данные, синхронизировать свою работу. Поскольку подобный режим обработки данных характерен для многопользовательских приложений, необходимость общего решения проблемы синхронизации признана такими авторитетными в области стандартов организация- ми, как /usr/group, которая объединяет многочисленных пользова- телей системы UNIX во многих странах. Слабая блокировка файлов и сегментов может быть использована для взаимной синхронизации процессов. При использовании сильной блокировки стандартные подпрограммы и системные вызовы ввода/ вывода учитывают выполнение протокола блокировок. Таким обра- зом, сильная блокировка обеспечивает дополнительный контроль очередности обращений к данным за счет небольшой потери эффек- тивности. Далее приводится описание использования возможностей блокировки сегментов и файлов. Рассматриваются примеры корректного исполь- зования средств блокировки сегментов. Мы старались рассеять возможные заблуждения относительно уровня защиты данных, кото- рый обеспечивается блокировкой сегментов: она должна рассматри- ваться в качестве средства синхронизации, а не как механизм обеспечения безопасности данных. В связи с тем, что ниже содержатся ссылки на описания системно- го вызова fcntl(2), библиотечной функции lockf(3C), а также на команды и структуры данных fcntl(5), Вам, прежде чем продолжать чтение, следует ознакомиться с соответствующими разделами Спра- вочника программиста. 2. ТЕРМИНОЛОГИЯ Прежде чем перейти к описанию средств блокировки сегментов, оп- ределим некоторые термины. Сегмент Последовательный участок файла. Операционная система UNIX не поддерживает структуру сегментов файла. Если это нужно, Вы должны делать это сами в программах, ра- ботающих с файлами. Взаимодействующие процессы Процессы, которые работают совместно для достижения не- которой заранее определенной общей цели. Процессы, сов- местно использующие файлы, должны запрашивать разреше- ние на доступ к файлу перед его использованием. Права доступа должны быть правильно установлены, чтобы не дать возможности доступа к этим файлам из других про- цессов. Термин процесс далее неизменно будет применять- ся в отношении процессов такого рода. Блокировка на чтение (разделяемая) Используется для того, чтобы установить ограниченный доступ к сегментам файла. Когда сегмент заблокирован на чтение, другие процессы также могут заблокировать на чтение весь сегмент или его часть. Никакие другие про- цессы, однако, не должны (к моменту блокировки на чте- ние) блокировать на запись пересекающийся с сегментом участок файла, и не смогут сделать это позже. Если про- цесс заблокировал сегмент на чтение, то можно считать, что никакие процессы не будут записывать или изменять этот сегмент в то же самое время. Тем не менее данный способ блокировки позволяет многим процессам читать данный сегмент одновременно. Такая блокировка необходи- ма, например, когда организуется поиск в файле, а зап- росы на блокировку на запись (монопольную) могут при- вести к нежелательным осложнениям. Блокировка на запись (монопольная) Используется для получения полного контроля над досту- пом к сегменту файла. Когда сегмент блокируется моно- польно, никакие другие процессы не имеют возможности заблокировать на чтение или запись весь сегмент или его часть. Если процесс заблокировал сегмент на запись, можно считать, что никакие процессы не будут записывать или читать данный сегмент в это же самое время. Слабая блокировка Данная форма блокировки сегментов не взаимодействует с подсистемой ввода-вывода [то есть с системными вызовами creat(2), open(2), read(2) и write(2)]. Контроль над сегментом устанавливается запросом на соответствующую блокировку, выдаваемым до операций ввода-вывода. Если соответствующие запросы делают все процессы, обрабаты- вающие файл, доступ к файлу будет управляться взаимо- действием этих запросов. Слабая блокировка обеспечивает выполнение протокола блокировки сегментов для всех ис- пользующих ее процессов; она не означает дополнительных проверок доступности во время любого запроса на ввод/ вывод. Сильная блокировка Данная форма блокировки сегментов взаимодействует с подсистемой ввода-вывода. Контроль блокировки сегментов осуществляется системными вызовами creat(2), open(2), read(2) и write(2). Если сегмент заблокирован, то дос- туп к нему со стороны других процессов ограничивается в соответствии с типом блокировки. Управление блокировкой по-прежнему должно осуществляться путем явной выдачи соответствующего запроса на нее перед операциями ввода- вывода, однако перед каждой операцией ввода-вывода сис- темой будут делаться дополнительные проверки, чтобы обеспечить выполнение протокола блокировки сегментов. Сильная блокировка обеспечивает дополнительный контроль очередности обращений к данным за счет некоторой потери эффективности. 3. БЛОКИРОВКА ФАЙЛОВ И СЕГМЕНТОВ В операционной системе UNIX с каждым файлом связан режим досту- па, определяющий, кто может читать, записывать или выполнять данный файл. Режим доступа может быть изменен либо владельцем файла, либо суперпользователем. Режим доступа к каталогу, в ко- тором находится файл, также влияет на возможность работы с фай- лом. Например, если режим доступа к каталогу позволяет каждому записывать в него, то любой файл из этого каталога может быть удален любым пользователем, даже таким, для которого режим дос- тупа к файлу не позволяет читать, записываать или выполнять этот файл. Если информацию стоит защитить, ее стоит защитить основательно. Если Ваша программа будет использовать блокировку сегментов, то необходимо обеспечить и соответствующие режимы доступа к Вашим каталогам и файлам. Блокировка сегмента, в том числе и сильная, будет защищать только ту часть файла, которая заблокирована. Если не предпринять соответствующие меры предос- торожности, другая часть файла может быть испорчена. Только заранее определенное множество программ и/или пользова- телей должно иметь возможность читать или записывать в базу данных (файл). Указанной цели можно легко достичь, если взвести бит переустановки идентификатора группы [см. chmod(1)] для программ, работающих с базой данных. После этого только опреде- ленное множество программ, поддерживающих протокол блокировки сегментов, смогут иметь доступ к файлам. Примером подобной за- щиты файлов является утилита mail(1), хотя в данном случае бло- кировка сегментов не используется. Порождаемые этой утилитой файлы непрочитанных писем могут читать и изменять только адре- саты и сама команда mail(1). 3.1. Открытие файлов для блокировки сегментов Первое требование для блокировки файла или сегмента файла сос- тоит в том, чтобы иметь корректный дескриптор файла. Если будет устанавливаться блокировка на чтение, файл должен быть открыт как минимум на чтение. Соответственно, для блокировки на запись необходим доступ на запись. В следующем примере файл открывает- ся как на чтение, так и на запись. #include #include #include int fd; /* дескриптор файла */ char *filename; main (argc, argv) int argc; char *argv []; { extern void exit (), perror (); /* Получение имени файла базы данных из командной строки и открытие файла на чтение и запись. */ if (argc < 2) { (void) fprintf (stderr, "Usage: %s file\n", argv[0]); exit (2); } filename = argv [1]; fd = open (filename, O_RDWR); if (fd < 0) { perror (filename); exit (2); } . . . Теперь файл открыт для выполнения как блокировок, так и функций ввода/вывода. Попробуем теперь установить блокировку. 3.2. Блокировка файлов Есть несколько способов блокировки файла. Конкретный выбор за- висит, в частности, от того, как оставшаяся часть программы бу- дет использовать блокировку. Нужно также учесть проблемы эффек- тивности и мобильности программы. Далее будут описаны два мето- да: один использует системный вызов fcntl(2), другой - библио- течную функцию lockf(3C) (удовлетворяющую стандарту /usr/ group). Блокировка всего файла на самом деле является частным случаем блокировки сегмента, поскольку и концепция, и результат блоки- ровки одинаковы. Файл блокируется начиная с байта со смещением ноль и кончая максимально возможным размером файла, что нужно для предотвращения блокировок каких-либо сегментов файла. В этом случае значение длины блокировки устанавливается в ноль. Далее приведен фрагмент исходного текста, использующего для достижения цели системный вызов fcntl(2): #include . . . #define MAX_TRY 10 int try; struct flock lck; try = 0; /* Заполним структуру, нужную для блокировки сегмента. Адрес структуры передается системному вызову fcntl */ lck.l_type = F_WRLCK; /* Блокировка на запись */ lck.l_whence = 0; /* Смещение от начала будет равно l_start */ lck.l_start = 0L; /* Блокировка от начала файла */ lck.l_len = 0L; /* и до конца */ /* Делаем не более MAX_TRY попыток блокировки */ while (fcntl (fd, F_SETLK, &lck) < 0) { if (errno == EAGAIN || errno == EACCES) { /* Могут быть и другие ошибки, при которых надо повторить попытку */ if (++try < MAX_TRY) ( (void) sleep (2); continue; } (void) fprintf (stderr, "Файл занят\n"); return; } perror("fcntl"); exit(2); } . . . Приведенная часть исходного текста пытается заблокировать файл. Попытки повторяются несколько раз до тех пор, пока не случится одно из событий: Файл удалось заблокировать. Возникла ошибка. Попытки прекращены из-за того, что их количество превы- сило MAX_TRY. Чтобы выполнить эту же задачу, используя функцию lockf(3C), ис- ходный текст должен иметь следующий вид: #include #define MAX_TRY 10 int try; try = 0; /* Устанавливаем указатель текущей позиции в начало файла */ lseek (fd, OL, O); /* Делаем не более MAX_TRY попыток блокировки */ while (lockf (fd, F_TLOCK, 0L) < 0) { if (errno == EAGAIN || errno == EACCES) { /* Могут быть и другие ошибки, при которых надо повторить попытку */ if (++try < MAX_TRY) { (void) sleep (2); continue; } (void) fprintf (stderr, "Файл занят\n"); return; } perror("lockf"); exit(2); } . . . Надо заметить, что пример с использованием lockf(3C) выглядит проще, однако пример с fcntl(2) демонстрирует дополнительную гибкость. При использовании fcntl(2) можно установить тип бло- кировки и начало блокируемого сегмента просто путем присваива- ния нужных значений компонентам структуры. В случае lockf(3C) просто устанавливается блокировка на запись (монопольная); для того, чтобы указать начало блокируемого сегмента, нужен допол- нительный системный вызов [lseek(2)]. 3.3. Блокировка и разблокирование сегментов Блокировка сегментов осуществляется практически тем же спосо- бом, что и блокировка файлов. Различие состоит только в началь- ной точке и длине блокируемого участка. Далее мы рассмотрим ин- тересную и практически важную задачу. Есть два сегмента (в од- ном или двух файлах), которые должны быть изменены одновременно таким образом, чтобы другие процессы не могли получить доступ к промежуточному состоянию изменяемой информации. (Подобные зада- чи встречаются, например, при необходимости изменить указатели в двунаправленных списках.) Чтобы решить нашу задачу, необходи- мо ответить на следующие вопросы: Что нужно блокировать? Если сегментов для блокировки много, в каком порядке блокировать и разблокировать их? Что делать в случае удачной блокировки всех нужных сег- ментов? Что делать в случае неудачи при попытке блокировки не- которого сегмента? При управлении блокировкой сегментов мы должны планировать свои действия на случай неудачной попытки блокировки. Эти действия в первую очередь зависят от характера информации, хранящейся в тех сегментах, которые мы пытаемся блокировать. Возможны, нап- ример, следующие действия: Подождать некоторое время и повторить попытку. Аварийно завершить процедуру и предупредить пользовате- ля. Перевести процесс в состояние ожидания до тех пор, пока не поступит сигнал о снятии блокировки. Скомбинировать перечисленные выше действия. Теперь рассмотрим пример включения элемента в двунаправленный список. Допустим, что сегмент, после которого мы хотим включить новый элемент, уже заблокирован на чтение. чтобы этот сегмент можно было корректировать, блокировка должна быть снята и уста- новлена снова или ее уровень повышен до блокировки на запись. Повышение уровня блокировки (обычно от блокировки на чтение до блокировки на запись) разрешается в том случае, если нет других процессов, заблокировавших на чтение ту же часть файла. Наличие процессов, ожидающих возможности заблокировать на запись ту же часть файла, не меШает повышени ю уровн: эти процессы останутся в состоянии ожидания. Понижение уровня блокировки с блокировки на запись до блокировки на чтение возможно всегда. В обоих слу- чаях просто изменяется значение вида блокировки. По той причи- не, что функция lockf(3C) (в соответствии со стандартом /usr/ group) не устанавливает блокировки на чтение, изменение уровня блокировки в случае использования этой функции невозможно. Ниже приведен пример блокировки сегмента с последующим изменением ее уровня: struct record { . . . /* Данные сегмента */ . . . long prev; /* Указатель на предыдущий сегмент в списке */ long next; /* Указатель на следующий сегмент в списке */ }; /* Изменение уровня блокировки с использованием fcntl(2). Предполагается, что к тому моменту, когда эта программа будет выполняться, сегменты here и next будут заблокированы на чтение. Если заблокируем на запись here и next, то: блокируем на запись сегмент this; возвращаем указатель на сегмент this. Если любая из попыток блокировки окончится неудачей, то: переустанавливаем блокировку сегментов here и next на чтение; снимаем все остальные блокировки; возвращаем -1. */ long set3lock (this, here, next) long this, here, next; { struct flock lck; lck.l_type = F_WRLCK; /* Блокируем на запись */ lck.l_whence = 0; /* Смещение от начала будет равно l_start */ lck.l_start = here; lck.l_len = sizeof (struct record); /* Повышение уровня блокировки сегмента here до блокировки на запись */ if (fcntl (fd, F_SETLKW, &lck) < 0) { return (-1); } /* Блокируем сегмент this на запись */ lck.l_start = this; if (fcntl (fd, F_SETLKW, &lck) < 0) { /* Блокировка сегмента this не удалась. Понижаем блокировку сегмента here до уровня чтения */ lck.l_type = F_RDLCK; lck.l_start = here; (void) fcntl (fd, F_SETLKW, &lck); return (-1); } /* Повышение уровня блокировки сегмента next до блокировки на запись */ lck.l_start = next; if (fcntl (fd, F_SETLKW, &lck) < 0) { /* Блокировка сегмента next не удалась. Понижаем блокировку сегмента here до уровня чтения...*/ lck.l_type = F_RDLCK; lck.l_start = here; (void) fcntl (fd, F_SETLKW, &lck); /*...и снимаем блокировку сегмента this */ lck.l_type = F_UNLCK; lck.l_start = this; (void) fcntl (fd, F_SETLKW, &lck); return (-1); /* Не смогли заблокировать */ } return (this); } Если другие процессы мешают заблокировать нужные сегменты, то процесс перейдет в состояние ожидания, что обеспечивается опе- рацией F_SETLKW. Если же использовать операцию F_SETLK, то в случае невозможности блокировки системный вызов fcntl(2) завер- шается неудчей. В последнем случае программу нужно было бы из- менить для обработки подобной ситуации. Теперь рассмотрим сходный пример с использованием функции lockf(3C). Так как она не позволяет выполнять блокировку на чтение, под блокировкой будет пониматься блокировка на запись. /* Повышение уровня блокировки с использованием lockf(3C). Предполагается, что в тот момент, когда программа будет выполняться, сегменты here и next не будут заблокированы. Если блокировка удастся, то: блокируем сегмент this; возвращаем указатель на сегмент this. Если любая из попыток блокировки окончится неудачей, то: разблокируем все остальные сегменты; возвращаем -1. */ #include long set3lock (this, here, next) long this, here, next; { /* Блокируем сегмент here */ (void) lseek (fd, here, 0); if (lockf (fd, F_LOCK, sizeof (struct record)) < 0) { return (-1); } /* Блокируем сегмент this */ (void) lseek (fd, this, 0); if (lockf (fd, F_LOCK, sizeof (struct record)) < 0) { /* Блокировка сегмента this не удалась. Разблокируем here */ (void) lseek (fd, here, 0); (void) lockf (fd, F_ULOCK, sizeof (struct record)); return (-1); } /* Блокируем сегмент next */ (void) lseek (fd, next, 0); if (lockf (fd, F_LOCK, sizeof (struct record)) < 0) { /* Блокировка сегмента next не удалась. Разблокируем сегмент here...*/ (void) lseek (fd, here, 0); (void) lockf (fd, F_ULOCK, sizeof (struct record)); /*...и разблокируем сегмент this */ (void) lseek (fd, this, 0); (void) lockf (fd, F_ULOCK, sizeof (struct record)); return (-1); /* Не смогли заблокировать */ } return (this); } Блокировка снимается тем же способом, что и устанавливается, только в случае системного вызова fcntl(2) в качестве режима блокировки указывается F_UNLCK, а при использовании функции lockf(3C) - F_ULOCK. Разблокирование не может быть предотвраще- но другим процессом. Разблокирование можно производить только в отношении блокировок, ранее установленных тем же процессом. Разблокирование касается только сегментов, которые были заданы в предыдущем примере структурой lck. Можно производить разбло- кирование или изменять тип блокировки для части ранее блокиро- ванного сегмента, однако это может привести к появлению допол- нительного элемента в системной таблице блокировок. 3.4. Получение информации о блокировке Имеется возможность выявить процесс, мешающий установить блоки- ровку. С помощью полученной информации можно узнать, какие бло- кировки действуют для файла. Пусть блокировка устанавливается как в предыдущем примере, но при обращении к fcntl(2) использу- ется операция F_GETLK. Если запрашиваемую блокировку установить нельзя, информация о первой мешающей этому блокировке будет возвращена процессу через структуру, адрес которой был передан системному вызову fcntl(2). В результате данные, переданные fcntl(2), будут заменены информацией о противодействующей бло- кировке. Эта информация включает два ранее не упоминавшихся элемента данных: l_pid и l_sysid. Они используются только опе- рацией F_GETLK. (Для систем, не поддерживающих распределенную архитектуру, значение l_sysid игнорируется.) Два упомянутых по- ля полностью идентифицируют процесс, установивший блокировку. Если блокировка, запрашиваемая системным вызовом fcntl(2) с ис- пользованием операции F_GETLK, может быть установлена, значени- ем поля l_type становится F_UNLCK, а остальные поля в структуре не изменяются. Используем описанную возможность для вывода ин- формации о всех сегментах файла, заблокированных другими про- цессами. Заметим, что если на один сегмент файла установлено несколько блокировок, то только первая из них будет найдена. struct flock lck; /* Получение и вывод информации о сегментах файла, блокированных на запись */ (void) printf ("ид-р сис. ид-р проц. тип начало длина\n"); lck.l_whence = 0; lck.l_start = 0L; lck.l_len = 0L; do { lck.l_type = F_WRLCK; (void) fcntl (fd, F_GETLK, &lck); if (lck.l_type != F_UNLCK) { (void) printf("%9d %9d %c %6d %5d\n", lck.l_sysid, lck.l_pid, (lck.l_type == F_WRLCK) ? 'W' : 'R', lck.l_start, lck.l_len); /* Если эта блокировка заведомо покрывает остаток файла, нет нужды выявлять другие блокировки */ if (lck.l_len == 0) break; /* Иначе поищем новую блокировку после найденной */ lck.l_start += lck.l_len; } } while (lck.l_type != F_UNLCK); Системный вызов fcntl(2) с операцией F_GETLK всегда завершается успешно (то есть процесс не будет приостановлен и вызов не за- вершится аварийно), если, конечно, значения переданных парамет- ров корректны. Функция lockf(3C) с операцией F_TEST также может быть использо- вана для выявления процесса, мешающего установить блокировку. Однако эта функция не способна определить заблокированный учас- ток сегмента и то, какой процесс заблокировал его. Программа, использующая lockf(3C) для проверки факта блокировки файла, приведена ниже: /* Выявление заблокированного сегмента */ /* Установка на начало файла */ (void) lseek (fd, 0, 0L); * Нулевой размер тестируемого сегмента означает проверку до конца файла */ if (lockf (fd, F_TEST, 0L) < 0) { switch (errno) { case EACCES: case EAGAIN: (void) printf ("Файл блокирован другим процессом\n"); break; case EBADF: /* Некорректные аргументы lockf */ perror ("lockf"); break; default: (void) printf ( "lockf: непредусмотренная ошибка <%d>\n",errno); break; } } Процесс, порожденный в результате выполнения системного вызова fork(2), наследует копии дескрипторов файлов, открытых процес- сом-предком. Кроме того, порождающий и порожденный процессы (для каждого файла) разделяют общий указатель текущей позиции в файле. Если процесс-предок изменит указатель файла, то измене- ние затронет и порожденный процесс. Такое положение влечет важ- ные следствия при использовании блокировки сегментов. Если зна- чение поля l_whence равно 1, то l_start указывает смещение на- чала блокируемого сегмента относительно указателя текущей пози- ции в файле. Если и порождающий, и порожденный процессы блоки- руют сегменты одного и того же файла, то существует вероят- ность, что при этом будет использоваться указатель текущей по- зиции, измененный другим процессом. Эта проблема существует и в случае использования функции lockf(3C) и является следствием требований стандарта /usr/group на блокировку сегментов. Если системный вызов fork(2) используется в программе наряду с бло- кировкой сегментов, то порожденный процесс при использовании любого метода блокировки должен закрыть и вновь открыть файл. В результате будет создан новый указатель текущей позиции в фай- ле, которым можно будет манипулировать, не сталкиваясь с отме- ченной проблемой. Другое решение состоит в том, чтобы использо- вать системный вызов fcntl(2) со значением поля l_whence 0 или 2, что делает блокировку независимой от указателя текущей пози- ции. 3.5. Управление тупиковыми ситуациями Система блокировки сегментов позволяет в некоторой степени вы- являть и обходить тупиковые ситуации. Эти средства обеспечивают тот же уровень защиты, что и соответствующая стандарту /usr/ group функция lockf(3C). Средства обнаружения тупиковых ситуа- ций работают лишь для процессов, блокирующих сегменты и файлы на одном компьютере. Тупиковые ситуации потенциально могут встречаться только тогда, когда операционная система собирается приостановить процесс, обратившийся к системе блокировки. Сис- тема осуществляет поиск циклов в графе процессов, зависимых по блокировке. Если появится цикл, это приведет к бесконечному ожиданию возможности заблокировать сегмент. При выявлении по- добной ситуации попытка блокировки заканчивается неудачей и значение errno будет равно соответствующему номеру ошибки, свя- занному с тупиковой ситуацией. Если нужно, чтобы процесс не ис- пользовал системный способ обнаружения тупиковых ситуаций, сле- дует при блокировке вместо F_GETLKW использовать F_GETLK. 4. ВЫБОР СЛАБОЙ ИЛИ СИЛЬНОЙ БЛОКИРОВКИ По причинам, описанным ниже (см. Сильная блокировка. Некоторые предупреждения), использование сильной блокировки не рекоменду- ется. Будет ли задействован контроль блокировки при любых сис- темных вызовах, обслуживающих ввод/вывод, выясняется в момент этих вызовов в зависимости от режима доступа к файлу [см. chmod(2)]. Чтобы такой контроль осуществлялся, файл должен быть обычным, со взведенным битом переустановки идентификатора груп- пы и без права на выполнение членами группы. Если эти условия не выполняются, все блокировки сегментов будут слабыми. Сильная блокировка может быть обеспечена следующим исходным текстом: #include #include int mode; struct stat buf; . . . if (stat (filename, &buf) < 0) { perror ("программа"); exit (2); } /* Получение текущего режима доступа */ mode = buf.st_mode; /* Удаление разрешения на выполнение членами группы */ mode &= (S_IEXEC >> 3); /* Взведение бита переустановки идентификатора группы */ mode |= S_ISGID; if (chmod (filename, mode) < 0) { perror ("программа"); exit (2); } . . . С файлами, в которых должны блокироваться сегменты, не должно быть связано каких бы то ни было прав на выполнение. Это требо- вание вызвано тем, что операционная система не поддерживает протокол блокировки сегментов при выполнении файла. Помимо рассмотренного способа, сильная блокировка файла может быть легко установлена с использованием утилиты chmod(1): chmod +l файл Утилита ls(1) также была изменена с тем, чтобы при запросе с опцией -l отображать установленный режим блокировки. В резуль- тате выполнения запроса ls -l f1 будет выведена строка вида -rw---l--- 1 abc other 1576 Dec 3 11:44 f1 4.1. Сильная блокировка. Некоторые предупреждения Сильная блокировка защищает только те сегменты файла, которые явно заблокированы. Доступность других частей определяется имеющимися правами. Если несколько операций ввода/вывода необходимо выпол- нить как единое целое, процесс должен явно блокировать все нужные сегменты перед тем, как выполнять ввод/вы- вод. Поэтому для всех программ, выполняющих подобную обработку, достаточно слабой блокировки. Как указывалось выше, произвольные программы не должны иметь неограниченных прав доступа к файлам, информация в которых важна настолько, что нужно применять блоки- ровку на уровне сегментов. Слабая блокировка сегментов более эффективна по той причине, что проверка блокировки не будет осуществлять- ся при каждой операции ввода/вывода. 4.2. Блокировка сегментов и развитие системы UNIX В настоящее время проведена работа по реализации блокировки файлов и сегментов в распределенной среде UNIX. Компьютер, на котором выполняется процесс, запрашивающий блокировку, может не совпадать с компьютером, на котором находится блокируемый файл. В этом случае работающие на различных компьютерах процессы мо- гут блокировать сегменты файлов, размещенных на одном из этих компьютеров или даже на другом компьютере. Блокировки на сег- менты файла фиксируются на том компьютере, где расположен файл. Важно отметить, что механизм выявления/предотвращения тупиковых ситуаций определен только для блокировок, с которыми работают в пределах одного компьютера. Поэтому, чтобы механизм управления тупиковыми ситуациями работал, процесс должен в каждый данный момент времени работать с блокировками, зафиксированными ровно на одном компьютере. Если для процесса требуется вести блоки- ровки на нескольких компьютерах сразу, рекомендуется не приме- нять операций блокировки с ожиданием и выявлять тупиковые ситу- ации самостоятельно. Если процесс использует операции блокиров- ки с ожиданием, то он должен обеспечить механизм выхода по вре- мени таким образом, чтобы не "зависать" в ожидании получения возможности блокировки.