Выбрать главу
4.9. Резюме

Чтобы выполнить ввод/вывод в отношении обычного файла, сначала нужно получить его дескриптор, воспользовавшись системным вызовом open(). Затем ввод/вывод выполняется с помощью системных вызовов read() и write(). После завершения всех операций ввода-вывода следует высвободить дескриптор файла и связанные с ним ресурсы, воспользовавшись системным вызовом close(). Эти системные вызовы могут применяться для выполнения ввода-вывода в отношении всех типов файлов.

Поскольку для всех типов файлов и драйверов устройств реализован один и тот же интерфейс ввода-вывода, позволяющий получить универсальный ввод/вывод, то программа, как правило, может быть использована с любым типом файла, без использования кода, специфичного для типа файла.

Для каждого открытого файла ядро хранит файловое смещение, определяющее место, с которого будут осуществляться следующие чтение или запись. Файловое смещение косвенным образом обновляется при чтении и записи. Используя вызов lseek(), можно явным образом установить позицию файлового смещения в любое место файла или даже за его конец. Запись данных в позицию, находящуюся дальше предыдущего конца файла, приводит к созданию дыры в файле. Чтение из файловой дыры возвращает байты, содержащие нули.

Системный вызов ioctl() предлагает для устройства и файла разнообразные операции, которые не вписываются в стандартную модель файлового ввода-вывода.

4.10. Упражнения

4.1. Команда tee считывает свой стандартный ввод, пока ей не встретится символ конца файла, записывает копию своего ввода на стандартное устройство вывода и в файл, указанный в аргументе ее командной строки. (Пример использования этой команды будет показан при рассмотрении FIFO-устройств в разделе 44.7.) Реализуйте tee, используя системные вызовы ввода-вывода. По умолчанию tee перезаписывает любой существующий файл с заданным именем. Укажите ключ командной строки — a (tee — a file), который заставит tee добавлять текст к концу уже существующего файла.

4.2. Напишите программу, похожую на cp, которая при использовании для копирования обычного файла, содержащего дыры (последовательности нулевых байтов), будет также создавать соответствующие дыры в целевом файле.

5 Только если файла с таким именем еще не было в текущем каталоге. Иначе эта команда лишь обновит время последнего обращения к файлу. — Примеч. пер.

5. Файловый ввод-вывод: дополнительные сведения

В этой главе мы продолжим рассматривать файловый ввод-вывод. Возвращаясь к системному вызову open(), мы познакомимся с концепцией атомарности. Она подразумевает, что действия в рамках системного вызова выполняются в виде единого непрерываемого шага — это неотъемлемое требование для корректной работы многих системных вызовов.

Будет представлен еще один многоцелевой системный вызов, имеющий отношение к файлам, — fcntl(). Мы рассмотрим один из примеров его использования: извлечение и установку флагов состояния открытого файла.

Затем будет описана структура данных ядра, которая применяется для представления файловых дескрипторов и открытых файлов. Понимая взаимоотношения между этими структурами, вы сможете разобраться в некоторых тонкостях файлового ввода-вывода, рассматриваемых в последующих главах. На основе этой модели будет объяснен порядок создания дубликатов дескрипторов файлов.

Затем будут перечислены некоторые системные вызовы, предоставляющие расширенные функциональные возможности чтения и записи. Они могут позволить нам выполнять ввод/вывод в конкретном месте файла без изменения файлового смещения и перемещать данные между несколькими буферами в программе.

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

Поскольку многими системными программами используются временные файлы, будут также перечислены некоторые библиотечные функции, позволяющие создавать и использовать временные файлы с произвольно создаваемыми уникальными именами.

5.1. Атомарность и состояние гонки