Опубликован: 12.12.2007 | Уровень: специалист | Доступ: платный | ВУЗ: Московский физико-технический институт
Лекция 11:

Интерфейс файловой системы

< Лекция 10 || Лекция 11: 123 || Лекция 12 >

Пример применения операции асинхронного чтения из файла

Для того чтобы воспользоваться возможностями асинхронного ввода-вывода, нужно вызвать функцию CreateFile с установленным флагом FILE_FLAG_OVERLAPPED, входящим в состав параметра dwFlagsAndAttrs, и указать: с какой позиции осуществлять чтение (запись), сколько байтов считать (записать) и какое событие должно сигнализировать о том, что операция завершена. Для этого необходимо проинициализировать поля структуры OVERLAPPED в параметре pOverlapped функций ReadFile или WriteFile.

Структура OVERLAPPED
typedef struct _OVERLAPPED { 
    ULONG_PTR  Internal; 
    ULONG_PTR  InternalHigh; 
    DWORD  Offset; 
    DWORD  OffsetHigh; 
    HANDLE hEvent; 
} OVERLAPPED;

Параметр Internal используется для хранения кода возможной ошибки, а параметр InternalHigh - для хранения числа переданных байт. Вначале разработчики Windows не планировали делать их общедоступными - отсюда и такие не содержащие мнемоники имена. Offset и OffsetHigh - соответственно младшие и старшие разряды текущей позиции файла. hEvent специфицирует событие, сигнализирующее окончание операции ввода-вывода.

Прогон программы, осуществляющей асинхронное чтение из уже существующего файла

#include <windows.h>
#include <stdio.h>

void main(void) {

HANDLE hFile, hHeap;
int iRet = 0;
void *pMem;
long BufSize = 512;
DWORD iRead = 10;
char * String;
OVERLAPPED ov = {0};


hFile = CreateFile("MYFILE.TXT", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING,                                                          
                    FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL); 
if (hFile == INVALID_HANDLE_VALUE) printf("Could not open file.");

hHeap = GetProcessHeap();
pMem = HeapAlloc(hHeap, HEAP_ZERO_MEMORY, BufSize);
String = (char *)pMem;

ov.Offset = 3;

ReadFile(hFile, pMem, iRead, &iRead, &ov);
WaitForSingleObject(hFile, INFINITE);
printf("Read %d bytes: %s\n", iRead, String);
printf("Read %d bytes: %s\n", ov.InternalHigh, String);

HeapFree(hHeap, 0, pMem);
CloseHandle(hFile); 
}

В программе проинициализирована структура OVERLAPPED и передана функции ReadFile в качестве параметра. Чтение начинается с 3-й позиции. Узнать число прочитанных байтов можно из ov.InternalHigh - компонента структуры OVERLAPPED. Обратите внимание, что значение переменной iRead, которая должна содержать количество прочтенных байтов, равно 0, так как функция вернула управление до завершения операции ввода-вывода. Обычно это справедливо для первого запуска программы. При последующих запусках, поскольку данные файла находятся в кэше, запрос может успеть выполниться синхронно и значение iRead уже будет равно числу прочитанных байтов.

В программе выбран простейший вариант синхронизации - сигнализация от объекта, управляющего устройством, в данном случае - открытого файла (функции WaitForSingleObject передан описатель открытого файла в качестве параметра). Существуют и другие, более интересные способы синхронизации, например, использование порта завершения ввода-вывода ( IoCompletitionPort ). Более подробно возможности асинхронного ввода-вывода описаны в [ Рихтер ] , [ Рихтер, Кларк ] и [ Харт ] .

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

Операция позиционирования в случае синхронного доступа к файлу

Итак, в случае асинхронного доступа позиция, начиная с которой будет осуществляться операция чтения-записи, содержится в запросе на операцию (параметр структуры OVERLAPPED ). Рассмотрим теперь особенности позиционирования при обычном синхронном вводе-выводе. В этом случае реализуется схема с "сохранением состояния", 64-разрядный указатель текущей для чтения-записи позиции хранится в составе атрибутов объекта "открытый файл" (его не нужно путать с атрибутами файла), описатель которого возвращает функция CreateFile.

Текущая позиция смещается на конец считанной или записанной последовательности байтов в результате операций чтения или записи. Кроме того, можно установить текущую позицию при помощи Win32-функции SetFilePointer. Например, операция

SetFilePointer(hFile,17 , NULL, FILE_BEGIN);

устанавливает указатель текущей позиции на 17-й байт с начала файла.

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

С другой стороны, получив описатель открытого файла, можно его продублировать при помощи Win32- функции DuplicateHandle. В этом случае два разных описателя будут ссылаться на один и тот же объект с одним и тем же указателем текущей позиции.

Написание, компиляция и прогон программы, осуществляющей перемещение указателя текущей позиции внутри открытого файла

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

Директории. Логическая структура файлового архива

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

Формат каталога

Рис. 11.1. Формат каталога

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

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

Иерархическая древовидная структура файловой системы

Рис. 11.2. Иерархическая древовидная структура файловой системы

Поскольку имена файлов, находящихся в разных каталогах, могут совпадать, уникальность имени файла на диске обеспечивается добавлением к собственному имени файла списка вложенных каталогов, содержащих данный файл. Так образуется хорошо известное абсолютное или полное имя (pathname), например, \Games\Heroes\heroes.exe. Таким образом, использование древовидных каталогов минимизирует сложность назначения уникальных имен.

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

В системе поддерживается большое количество Win32-функций для манипуляции с каталогами, их полный перечень имеется в MSDN. В частности, для создания каталогов можно использовать функцию CreateDirectory. Вновь созданная директория включает записи с именами '.' и '..', однако считается пустой. Для работы с текущим каталогом можно использовать функции GetCurrentDirectory и SetCurrentDirectory. Работа с этими функциями проста и не нуждается в специальных разъяснениях.

Прогон программы, задача которой создать каталог на диске и сделать его текущим

#include <windows.h>
#include <stdio.h>

void main(void) {

int iRet = 0;
char Buf[512];
int bufSize = 512;

iRet = GetCurrentDirectory(bufSize, Buf);
 printf("iRet = %d, current directory %s\n", iRet, Buf);

iRet = CreateDirectory("f:\\tmp1", NULL);
 if(!iRet) printf("CreateDirectory error\n");

iRet = SetCurrentDirectory("f:\\tmp1");
 if(!iRet) printf("SetCurrentDirectory error\n");

iRet = GetCurrentDirectory(bufSize, Buf);
 printf("iRet = %d, current directory %s\n", iRet, Buf);
}

Приведенная программа выводит на экран название текущего каталога, создает каталог "tmp1" на диске "F:" , делает его текущим и выводит на экран его название в качестве текущего каталога.

Самостоятельное упражнение

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

< Лекция 10 || Лекция 11: 123 || Лекция 12 >
Ирина Оленина
Ирина Оленина
Николай Сергеев
Николай Сергеев

Здравствуйте! Интересует следующий момент. Как осуществляется контроль доступа по тому или иному адресу с точки зрения обработки процессом кода процесса. Насколько я понял, есть два способа: задание через атрибуты сегмента (чтение, запись, исполнение), либо через атрибуты PDE/PTE (чтение, запись). Но как следует из многочисленных источников, эти механизмы в ОС Windows почти не задействованы. Там ключевую роль играет менеджер памяти, задающий регионы, назначающий им атрибуты (PAGE_READWRITE, PAGE_READONLY, PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE, PAGE_NOACCESS, PAGE_GUARD: их гораздо больше, чем можно было бы задать для сегмента памяти) и контролирующий доступ к этим регионам. Непонятно, на каком этапе может включаться в работу этот менеджер памяти? Поскольку процессор может встретить инструкцию: записать такие данные по такому адресу (даже, если этот адрес относится к региону, выделенному менеджером памяти с атрибутом, например, PAGE_READONLY) и ничего не мешает ему это выполнить. Таким образом, менеджер памяти остается в стороне не участвует в процессе...

Алихан Ергалиев
Алихан Ергалиев
Россия
Иван Иванов
Иван Иванов
Украина, Черкаси