Программные приложения обычно пишутся на языках программирования высокого уровня, которые предоставляют примитивы обращения к файлам через операционную систему. Эти примитивы позволяют приложению манипулировать файлами в совместимых с приложением блоках, называемых логическими записями (logical record). Например, приложению, работающему с файлом данных о персонале, было бы удобно обращаться к этим данным в терминах логических записей, каждая из которых составлена из информации, относящейся к одному человеку. Временами информация логической записи разделяется на более мелкие блоки, называемые полями (fields). Например, каждую логическую запись в файле персонала можно было бы разделить на такие поля, как имя, адрес, идентификационный номер и т. д.
В отличие от подобной логической структуры, хранение файла на запоминающих устройствах предписывает, что файл должен быть разделен на блоки, являющиеся физическими записями (physical records), совместимыми с используемым устройством хранения. К примеру, файлы, записанные на диски, должны делиться на блоки размером с сектор. Управление файлами в терминах физических записей осуществляется операционной системой. Если приложению необходимо найти часть файла, измеряемую в логических записях, оно обращается к операционной системе, чтобы та произвела нужное обращение. Операционная система считывает достаточное для выполнения запроса количество физических записей, размещая полученные данные в области оперативной памяти, называемой буфером, а затем предоставляет этот буфер приложению (рис. 1). Аналогично, для записи информации приложение передает данные операционной системе. Операционная система хранит их в буфере до тех пор, пока не накопится полная физическая запись1, а затем передает эту запись на запоминающее устройство.
Рисунок 1 - Роль операционной системы при доступе к файлу
Для извлечения файлов, записанных на магнитные диски, операционная система поддерживает списки секторов, предоставленных каждому файлу. В действительности же дисковое пространство выделяется файлу блоками, называемыми кластерами, состоящими из нескольких секторов. Обычно один кластер на ПК состоит из 4-16 секторов, а на запоминающих устройствах большого объема содержатся тысячи кластеров. Чтобы запомнить, какие кластеры (clusters) назначены файлу, на каждом диске операционная система поддерживает таблицу размещения файлов (file allocation table, FAT). В этой таблице для каждого кластера на диске есть отдельная ячейка. Если на диске хранится файл, операционная система записывает номер первого выделенного файлу кластера в каталог, куда записана информация об этом файле (имя, размер, дата создания и т. д.). Затем в ячейку FAT, представляющую первый кластер, операционная система записывает номер следующего кластера, выделенного файлу, а в ячейку FAT для этого кластера записывает номер следующего за ним кластера. Таким образом, начав с каталога, где хранится файл, и следуя описанному пути в FAT, операционная система может восстановить файл в правильном порядке кластеров.
В ранних версиях операционной системы Microsoft Windows использовались FAT с 16-битными записями, то есть можно было пронумеровать только 65536 кластеров. Поскольку размер кластера равнялся 2 Кбайт, одна FAT могла представить только 128 Мбайт дискового пространства — что было вполне достаточно, если в системе установлен жесткий диск объемом до 128 Мбайт. В современных операционных системах используются 32-битные записи, то есть FAT можно применять для хранения адресов файлов на дисках, объемы которых измеряются в терабайтах — эквивалентно 240 байт.
Для выполнения всех условий доступа к файлам операционная система должна хранить информацию о файле, к которому идет обращение. Например, она должна знать, на каком устройстве записан файл, имя файла, расположение буфера, через который передаются данные, и будет ли файл сохранен после того, как приложение завершит работу. Подобная информация содержится в таблице, называемой дескриптором файла (file descriptor) или блоком управления файлом, хранящейся в оперативной памяти. Дескриптор файла создается, когда приложение уведомляет операционную систему, что ей потребуется доступ к файлу, и удаляется, когда приложение сообщает, что файл более не требуется. Процесс создания дескриптора файла называется открытием файла (opening the file); процесс удаления дескриптора файла называется закрытием файла (closing the file).
Перед тем как программа сможет обратиться к файлу через операционную систему, она должна запросить операционную систему открыть файл. Открытие файла обычно запрашивается оператором, значение которого можно записать на псевдокоде так:
Открыть файл document.txt как DocFile для ввода
В данном случае запрашивается открытие документа document.txt. Этот оператор указывает, что файл должен уже существовать на запоминающем устройстве («для ввода», а не «для вывода») и что в программе к этому файлу нужно будет обращаться как к DocFile. В программе используется псевдоним файла (DocFile), а не его настоящее имя по двум причинам. Во-первых, настоящее имя, которое используется в операционной системе, может быть не совместимо с синтаксическими правилами языка программирования высокого уровня. Например, имя файла document.txt не совместимо с языком, в котором идентификаторы не должны содержать точек. Во-вторых, если вы используете псевдонимы, то для того, чтобы программа обратилась к другому файлу, нужно просто написать другое имя в операторе открытия файла, а не искать и изменять каждое упоминание имени файла в программе.
В объектно-ориентированных языках программирования файлы рассматриваются как объекты, и открытие файла производится в контексте определения объекта, который будет представлять этот файл. Так, в объектно-ориентированной среде инструкция определения дескриптора файла будет эквивалентна следующей:
Создать объект DocFile как входной файл document.txt
При этом создается объект под названием DocFile, при помощи которого мы сможем обратиться к файлу document.txt как к файлу, из которого будет получена информация. Далее в программе данные можно прочесть из файла, отправив подходящее сообщение объекту DocFile. Например, оператор, эквивалентный команде:
Отправить сообщение GetCharacter к DocFile для получения Symbol
можно использовать для получения одного символа из файла, представленного объектом DocFile.
Когда программа закончит работу с файлом, она должна приказать операционной системе закрыть файл. Во время закрытия файла операционная система может не ограничиться удалением дескриптора файла. Например, если существует частично заполненная физическая запись, содержащая данные для записи в файл, операционная система должна передать эту запись на запоминающее устройство.
Для закрытия файла программа обычно выполняет инструкцию, эквивалентную следующей:
Закрыть файл DocFile
Закрытие файла в объектно-ориентированной среде выполняется путем отправки соответствующему объекту сообщения, приказывающего закрыть его файл. Например,
Отправить сообщение Close объекту DocFile
отправляет объекту DocFile приказ закрыть его файл.