Добавлен: 29.10.2018
Просмотров: 63295
Скачиваний: 230

10.6. Файловая система UNIX
851
в использовании абсолютного пути. В действительности это не совсем так. При со-
здании каталога в нем автоматически создаются две записи, «
.
» и «
..
». Первая запись
обозначает сам каталог. Вторая является ссылкой на родительский каталог, то есть
каталог, в котором данный каталог числится как запись. Таким образом, из каталога
/usr/fred
к файлу Лизы
x
можно обратиться еще и при помощи использования пути
../lisa/x
.
Кроме обычных файлов Linux поддерживает также символьные специальные файлы
и блочные специальные файлы. Символьные специальные файлы используются для
моделирования последовательных устройств ввода-вывода, таких как клавиатуры
и принтеры. Если процесс откроет файл
/dev/tty
и прочитает из него, то он получит
введенные с клавиатуры символы. Если открыть файл
/dev/lp
и записать в него дан-
ные, то эти данные будут распечатаны на принтере. Блочные специальные файлы
(обычно с такими именами, как
/dev/hd1
) могут использоваться для чтения и записи
необработанных дисковых разделов, минуя файловую систему. При этом поиск байта
номер k, за которым последует чтение, приведет к чтению k-го байта из соответству-
ющего дискового раздела, игнорируя i-узел и файловую структуру. Необработанные
блочные устройства используются для страничной подкачки и свопинга программами
установки файловой системы (например, mkfs), а также программами, исправляющими
поврежденные файловые системы (например, fsck).
На многих компьютерах установлено по два и более жестких диска. Например, на
мэйнфреймах в банках часто бывает необходимо иметь по 100 и более дисков (чтобы
хранить огромные базы данных). Даже у персональных компьютеров часто имеется по
меньшей мере два диска — жесткий диск и дисковод для оптических дисков (напри-
мер, DVD). При наличии у компьютера нескольких дисков возникает необходимость
в управлении ими.
Одно из решений заключается в том, чтобы создать отдельную файловую систему
на каждом диске и управлять ими по отдельности. Например, рассмотрим ситуацию,
изображенную на рис. 10.15, а. Здесь показан жесткий диск, который мы будем на-
зывать
C:
, а также DVD, который мы будем называть
D:
. У каждого есть собственный
корневой каталог и файлы. При таком решении пользователь должен помимо каталогов
указывать также устройство (если оно отличается от используемого по умолчанию).
Например, чтобы скопировать файл
x
в каталог
d
(предполагая, что по умолчанию вы-
бирается диск
C:
), следует ввести команду
cp D:/x /a/d/x
Такой подход применяется в нескольких операционных системах, включая Windows 8,
которая унаследовала его у MS-DOS, появившейся в прошлом веке.
Применяемое в операционной системе Linux решение заключается в том, чтобы по-
зволить смонтировать один диск в дерево файлов другого диска. В нашем примере
мы можем смонтировать DVD в каталог
/b
, получая в результате файловую систему,
показанную на рис. 10.15, б. Теперь пользователь видит единое дерево файлов и уже
не должен думать о том, какой файл на каком устройстве находится. В результате при-
веденная ранее команда примет вид
cp /b/x /a/d/x
то есть все будет выглядеть так, как если бы файл копировался из одного каталога
жесткого диска в другой каталог того же диска.

852
Глава 10. Изучение конкретных примеров: Unix, Linux и Android
DVD
Рис. 10.15. Файловые системы: а — раздельные; б — после монтирования
Другое интересное свойство файловой системы Linux — блокировка (locking) . В не-
которых приложениях два и более процесса могут одновременно использовать один
и тот же файл, что может привести к условиям гонки. Одно из решений данной про-
блемы заключается в том, чтобы создать в приложении критические области. Но если
эти процессы принадлежат независимым пользователям, которые даже не знакомы
друг с другом, то такой способ координации действий, как правило, очень неудобен.
Рассмотрим, например, базу данных, состоящую из многих файлов в одном или не-
скольких каталогах, доступ к которым получают никак не связанные между собой
пользователи. С каждым каталогом или файлом можно связать семафор и достичь
взаимного исключения, заставляя процессы выполнять операцию down на соответству-
ющем семафоре перед доступом к данным. Недостаток такого решения заключается
в том, что при этом недоступным становится весь каталог или файл — даже если нужна
всего одна запись.
По этой причине стандарт POSIX предоставляет гибкий и детальный механизм, по-
зволяющий процессам за одну неделимую операцию блокировать даже единственный
байт файла (или целый файл). Механизм блокировки требует от вызывающей стороны
указать блокируемый файл, начальный байт и количество байтов. Если операция за-
вершается успешно, то система создает запись в таблице, в которой указывается, что
определенные байты файла (например, запись базы данных) заблокированы.
Стандартом определены два типа блокировки: блокировка с монополизацией (exclusive
locks) и блокировка без монополизации (shared locks) . Если часть файла уже имеет
блокировку без монополизации, то повторная попытка установки блокировки без моно-
полизации на это место файла разрешается, но попытка установить блокировку с моно-
полизацией будет отвергнута. Если же какая-либо область файла содержит блокировку
с монополизацией, то любые попытки заблокировать любую часть этой области файла
будут отвергаться, пока не будет снята блокировка. Для успешной установки блокировки
необходимо, чтобы каждый байт в блокируемой области был доступен.
При установке блокировки процесс должен указать, будет ли он блокироваться в том
случае, когда блокировку установить не удается. Если процесс выбрал блокирование,
то он блокируется до тех пор, пока с запрашиваемой области файла не будет снята
блокировка, после чего процесс активизируется и блокировка устанавливается. Если
процесс решил не блокироваться при невозможности установить блокировку, то

10.6. Файловая система UNIX
853
системный вызов немедленно делает возврат, а в коде состояния указывается, была
блокировка успешной или нет. Если нет, то вызывающая сторона должна решить, что
делать дальше (например, подождать и попробовать опять).
Заблокированные области могут перекрываться. На рис. 10.16, а мы видим, что про-
цесс A установил блокировку без монополизации на байты с 4-го по 7-й в некотором
файле. Затем процесс B устанавливает блокировку без монополизации на байты с 6-го
по 9-й (рис. 10.16, б). Наконец, процесс C блокирует байты со 2-го по 11-й. Пока это
блокировки без монополизации, они могут существовать одновременно.
Теперь посмотрим, что произойдет, если процесс попытается получить блокировку
с монополизацией на байт 9 (рис. 10.16, в), блокируясь при неудаче блокировки. Две
предыдущие блокировки перекрываются с этой блокировкой. Поэтому вызывающая
сторона будет заблокирована и останется заблокированной до тех пор, пока оба про-
цесса (B и C) не снимут свои блокировки.
Рис. 10.16. Файл: а — с одной блокировкой; б — добавление второй блокировки;
в — третья блокировка
10.6.2. Вызовы файловой системы в Linux
Многие системные вызовы имеют отношение к файлам и файловой системе. Сначала
мы рассмотрим системные вызовы, работающие с отдельными файлами. Затем изучим
те системные вызовы, которые оперируют каталогами или всей файловой системой
в целом. Для создания нового файла можно использовать системный вызов creat.
(Когда Кена Томпсона однажды спросили, что бы он поменял, если бы у него была воз-
можность во второй раз разработать операционную систему UNIX, он ответил, что на

854
Глава 10. Изучение конкретных примеров: Unix, Linux и Android
этот раз вместо creat он назвал бы этот системный вызов create.) В качестве параметров
этому системному вызову следует задать имя файла и режим защиты. Так, команда
fd = creat("abc", mode);
создает файл
abc
с режимом защиты, указанным в mode. Биты mode определяют круг
пользователей, которые могут получить доступ к файлу, а также уровень предостав-
ляемого им доступа. Вопросы защиты файлов и доступа к ним будут рассмотрены
в дальнейшем.
Системный вызов creat не только создает новый файл, но и открывает его для записи.
Чтобы последующие системные вызовы могли получить доступ к файлу, успешный
системный вызов creat возвращает небольшое неотрицательное целое число, называ-
емое дескриптором файла (file descriptor) (fd в приведенном ранее примере). Если
системный вызов выполняется с уже существующим файлом, то длина этого файла
уменьшается до 0, а все его содержимое теряется. Файлы можно создавать также при
помощи вызова open с соответствующими аргументами.
Теперь продолжим изучение основных вызовов файловых систем, перечисленных
в табл. 10.9. Чтобы прочитать данные из существующего файла или записать данные
в существующий файл, его нужно сначала открыть с помощью open или creat. Этому
системному вызову следует указать имя файла, а также режим, в котором он должен
быть открыт: для чтения, для записи либо и для того и для другого. Также можно указать
различные дополнительные параметры. Как и creat, системный вызов open возвращает
дескриптор файла, который может быть использован для чтения или записи. Затем файл
может быть закрыт при помощи вызова close, после чего дескриптор файла можно ис-
пользовать повторно (для последующего creat или open). Системные вызовы creat и open
всегда возвращают наименьший неиспользуемый в данный момент дескриптор файла.
Таблица 10.9. Некоторые системные вызовы для работы с файлами. В случае ошиб-
ки возвращаемое значение s равно –1, fd — дескриптор файла, position — смещение
в файле. Параметры должны быть понятны без пояснений
Системный вызов
Описание
fd=creat(name, mode)
Один из способов создания нового файла
fd=open(file, how, ј
Открыть файл для чтения, записи либо и того и другого
одновременно
s=close(fd)
Закрыть открытый файл
n=read(fd, buffer, nbytes)
Прочитать данные из файла в буфер
n=write(fd, buffer, nbytes)
Записать данные из буфера в файл
position=lseek(fd, offset, whence)
Переместить указатель в файле
s=stat(name, &buf)
Получить информацию о состоянии файла
s=fstat(fd, &buf)
Получить информацию о состоянии файла
s=pipe(&fd[0])
Создать канал
s=fcntl(fd, cmd, ...)
Блокировка файла и другие операции
Когда программа начинает выполнение стандартным образом, файловые дескрипторы
0, 1 и 2 уже открыты для стандартного ввода, стандартного вывода и стандартного пото-
ка сообщений об ошибках соответственно. Таким образом, фильтр (например, програм-

10.6. Файловая система UNIX
855
ма sort) может просто читать свои входные данные из файла с дескриптором 0, а писать
выходные данные в файл с дескриптором 1, не заботясь о том, что это за файлы. Работа
этого механизма обеспечивается оболочкой, которая проверяет, чтобы эти дескрипторы
соответствовали нужным файлам (прежде чем программа начнет свою работу).
Чаще всего программы используют системные вызовы read и write. У обоих вызовов по
три параметра: дескриптор файла (указывающий, с каким из открытых файлов будет
производиться операция чтения или записи), адрес буфера (сообщающий, куда по-
ложить данные или откуда их взять), а также счетчик (указывающий, сколько байтов
следует передать). Вот и все. Очень простая схема. Пример типичного вызова:
n = read(fd, buffer, nbytes)
Хотя большинство программ читают и записывают файлы последовательно, некоторым
программам необходимо бывает иметь доступ к произвольной части файла. С каждым
открытым файлом связан указатель, который обозначает текущую позицию в файле.
При последовательном чтении (или записи) он указывает на следующий байт, который
будет прочитан (или записан). Например, если перед чтением 1024 байтов указатель
был установлен на 4096-й байт, то после успешного системного вызова read он будет
автоматически перемещен на 5120-й байт. Указатель в файле можно переместить с по-
мощью системного вызова lseek, что позволяет при последующих системных вызовах
read (или write) читать данные из файла (или писать их в файл) в произвольной пози-
ции файла и даже за концом файла. Этот системный вызов назван lseek, чтобы не путать
его с теперь уже устаревшим, использовавшимся ранее на 16-разрядных компьютерах
системным вызовом seek.
У системного вызова lseek три параметра: первый — дескриптор файла, второй — по-
зиция в файле, а третий сообщает, относительно чего эта позиция — начала файла,
текущей позиции или конца файла. Возвращаемое системным вызовом lseek значение
представляет собой абсолютную позицию в файле после того, как указатель был из-
менен. Забавно, что системный вызов lseek — это единственный системный вызов из
относящихся к файловой системе, который никогда не вызывает перемещения блока
головок диска, так как все, что он делает, — это обновление текущей позиции в файле,
представляющей собой просто число в памяти.
Для каждого файла операционная система Linux хранит такие сведения, как тип
(режим) файла (обычный, каталог, специальный файл), его размер, время последней
модификации, и другую информацию. Программы могут получить эту информацию
при помощи системного вызова stat. Первый параметр представляет собой имя файла.
Второй является указателем на структуру, в которую следует поместить запрошенную
информацию. Поля этой структуры перечислены в табл. 10.10. Системный вызов
fstat — это то же самое, что и системный вызов stat, с той лишь разницей, что он ра-
ботает с уже открытым файлом (имя которого может быть неизвестно), а не с путем.
Таблица 10.10. Поля структуры, возвращаемой системным вызовом stat
Устройство, на котором располагается файл
Номер i-узла (идентифицирует файл на устройстве)
Режим файла (включая информацию о защите)
Количество ссылок файла