Прямой доступ к данным файла
Цель лекции: изучить организацию ввода-вывода в файлы на нижнем уровне, научиться решать задачи с использованием прямого доступа к данным файла на языке C++.
Ввод-вывод низкого уровня
Файл представляет собой именованную область внешней памяти, в которой хранится информация в виде последовательности байтов. С такой точки зрения можно рассматривать любой произвольный файл, поэтому доступ к его содержимому иногда удобно организовать на нижнем уровне с помощью средств прямого доступа к данным.
Рассмотренные ранее средства обмена с файлами позволяют записывать и считывать данные только последовательно. Операции чтения-записи всегда производятся, начиная с текущей позиции в потоке. Начальная позиция устанавливается при открытии потока и может соответствовать начальному или конечному байту потока в зависимости от режима открытия файла. При этом данные потока буферизируются и выполняется форматирование передаваемой информации.
Функции ввода-вывода низкого уровня (прямого доступа) осуществляют обмен с файлами или периферийными устройствами путем прямого обращения к соответствующим функциям операционной системы (системным вызовам). Отличительные особенности средств прямого доступа к файлам следующие.
- Они не предоставляют возможности буферизации информации при пересылке.
- Они не обеспечивают преобразования данных из внутреннего машинного представления в текстовый формат.
- Они дают возможность перемещать указатель текущей позиции в потоке на нужный байт.
- При низкоуровневом открытии файла с ним связывается файловый дескриптор. Дескриптор является целым значением, характеризующим размещение информации об открытом файле во внутренних таблицах операционной системы. Дескриптор используется при последующих операциях с файлом.
Основные функции низкого уровня
Функции низкого уровня, прототипы которых входят в стандартную библиотеку <io.h>, обычно применяются при разработке собственных подсистем ввода-вывода. Большинство функций этого уровня переносимы в рамках некоторых систем программирования на язык С или С++.
Функция открытия файла для чтения-записи
Для начала работы с файлом его необходимо открыть с помощью функции open.
int open(const char *filename, int oflags [,int sflags]);
где filename – указатель на строку символов, представляющую собой допустимое имя файла, в которое может входить спецификация файла (включает обозначение логического устройства, путь к файлу и собственно имя файла). При указании полного пути в качестве разделителя используется символ "слэш" ('/'), а не "обратный слэш" ('\'), как принято. Это объясняется использованием символа "обратный слэш" в управляющих последовательностях в С++;
oflags – доступный тип операций, представляющий собой одну или несколько целочисленных констант, объявленных в файле <fcntl.h>. Если задана больше чем одна константа, тогда выполняется их объединение при помощи логического оператора ИЛИ ( | ). Состав доступных констант зависит от операционной системы. В таблице перечислены константы режима, встречающиеся практически во всех операционных системах.
sflags – необязательный параметр, который определяет тип доступа к файлу и представляет собой одну или несколько целочисленных констант, объявленных в файле <sys\stat.h>. Если задана больше чем одна константа, тогда выполняется их объединение при помощи логического оператора ИЛИ ( | ). Данный параметр применяется совместно с константой O_CREAT типа операций. Если открываемый файл существует, ТипДоступа игнорируется.
Константа | Описание |
---|---|
S_IWRITE | Разрешена запись |
S_IREAD | Разрешено чтение |
S_IREAD|S_IWRITE | Разрешены чтение и запись |
В случае успешного открытия файла данная функция возвращает неотрицательное целое значение, которое соответствует логическому номеру файла, а указатель устанавливается на начало файла. Максимальное число одновременно открытых файлов определяется константой HANDLE_MAX. При возникновении ошибки открытия файла функция возвращает значение -1.
Функция открытия файла для разделенного доступа
Семантика разделения означает, что файловая система должна определить алгоритм работы, который применяется, когда несколько клиентов одновременно обращаются к одному файлу. Важно, чтобы все изменения, сделанные одним клиентом, были бы видны другим клиентам, когда они выполняют следующий системный вызов на чтение или запись в один и тот же файл. Открытие файлов для разделенного доступа к ним выполняется с помощью функции sopen.
int sopen(const char *filename, int oflags, int shflags [,int sflags]);
где параметры filename, oflags, sflags имеют тот же смысл, что и в функции open.
shflags - устанавливаемый тип разделенного доступа к файлу, представляющий собой одну из целочисленных констант, объявленных в файле <fcntl.h>.
Константа | Описание |
---|---|
SH_COMPAT | устанавливается режим совместимости |
SH_DENYRW | доступ по чтению и записи в файле не разрешен |
SH_DENYWR | доступ по записи в файле не разрешен |
SH_DENYRD | доступ по чтению в файле не разрешен |
SH_DENYNO | доступ по чтению и записи разрешен |
В случае успешного открытия файла данная функция возвращает неотрицательное целое значение, которое соответствует логическому номеру файла, а указатель устанавливается на начало файла. При возникновении ошибки открытия файла функция возвращает значение -1.
Функция создания файла
Функция open предоставляет доступ к существующему файлу или создает его заново, а функция creat создает в файловой системе новый объект.
int creat(const char *filename, int sflags);
где параметры filename, sflags имеют тот же смысл, что и в функции open.
Если прежде в файловой системе не существовало объекта с указанным именем или полной спецификацией, будет создан новый файл с указанным именем и указанными правами доступа к нему. Если же такой файл уже существовал, размер файла усекается до 0 (освобождаются все существующие блоки данных и устанавливается размер файла равным 0). Созданный ранее файл должен при этом позволять производить запись в него, чтобы можно было создать "новый" файл с тем же самым именем.
Функция закрытия файла
После завершения работы с файлом его необходимо закрыть. Для этого используется функция close.
close(int fd);
где fd - дескриптор открытого файла.
Ядро выполняет операцию закрытия, используя дескриптор файла и информацию из соответствующих записей в таблице файлов и таблице индексов. Если счетчик ссылок в записи таблицы файлов имеет значение, большее, чем 1, то это означает, что на запись в таблице файлов делают ссылку другие пользовательские дескрипторы (например, при разделенном доступе). В этом случае счетчик ссылок уменьшается на 1. Если счетчик ссылок в таблице файлов имеет значение, равное 1, операционная система освобождает запись в таблице и индекс в памяти, ранее выделенный системной функцией open.
Когда выполнение функции close завершается, запись в таблице пользовательских дескрипторов файла становится пустой. Попытки процесса использовать данный дескриптор заканчиваются ошибкой до тех пор, пока дескриптор не будет переназначен другому файлу в результате выполнения другой функции. Когда выполнение программного кода завершается, система проверяет наличие активных пользовательских дескрипторов файла данного сеанса и закрывает каждый из них. Таким образом, ни один сеанс выполнения кода не может оставить файл открытым после своего завершения.
Функция чтения из файла
Чтение из файла на нижнем уровне осуществляется побайтно, без буферизации, с помощью функции read.
int read(int fd, char *buffer,int count);
где fd - дескриптор файла, возвращаемый функцией open ;
buffer – адрес структуры данных, определенной пользователем, где будут размещаться считанные данные в случае успешного завершения выполнения функции read ;
count - количество байтов, которые определяет пользователь для считывания.
Функция возвращает количество фактически прочитанных байтов. В процессе выполнения функции ядро операционной системы обращается в таблице файлов к записи, которая соответствует значению пользовательского дескриптора файла, следуя за указателем. Затем оно устанавливает значения нескольких параметров ввода-вывода в адресном пространстве, тем самым устраняя необходимость в их передаче в качестве параметров функции. При выполнении режима ввода-вывода "чтение" формируются значения следующих параметров:
- устанавливается флаг, свидетельствующий о том, что ввод-вывод направляется в адресное пространство структуры пользователя;
- значению поля счетчика байтов присваивается количество байтов, которые должны быть прочитаны;
- устанавливается адрес пользовательского буфера данных;
- определяется значение смещения (из таблицы файлов), равное смещению в байтах внутри файла до места, откуда начинается ввод-вывод.
После считывания данные из блока копируются по назначенному адресу в структуру пользователя. При этом корректируются параметры ввода-вывода в адресном пространстве в соответствии с количеством прочитанных байтов: увеличивается значение смещения в байтах внутри файла и адрес структуры пользователя, куда будет доставлена следующая порция данных. Одновременно уменьшается число байтов, которые необходимо прочитать, чтобы выполнить запрос пользователя. Если запрос на чтение полностью не выполнен, происходит циклическое повторение описанных действий. Цикл завершается при достижении хотя бы одного из условий:
- запрос на чтение выполнен полностью;
- в файле больше нет данных (достигнут конец файла);
- операционная система обнаружила ошибку при чтении данных с диска или при копировании данных в структуру пользователя.
В процессе чтения данных происходит коррекция значения смещения в таблице файлов в соответствии с количеством фактически прочитанных байтов; поэтому успешное выполнение операций чтения выглядит как последовательное считывание данных из файла.