Операционные Системы
.pdfЛабораторная работа №7. Файловый ввод-вывод.
Цель занятия
Знакомство с подсистемой ввода-вывода системы Windows NT.
Научиться
-Создавать объект ядра «файл».
-Выполнять синхронные операции над файлами.
-Выполнять асинхронные операции над файлами с использованием:
Синхронизации с объектом ядра «файл»
Синхронизации с объектом ядра «событие»
Синхронизации с использованием оповестительного ввода-вывода
(alterable I/O).
Синхронизация с использованием портов завершения ввода-вывода.
-Использовать объект «проекция файла»
Теория
Модель системы ввода-вывода Windows согласуется с концепцией большинства ОС, где все устройства представлены прикладному программисту как файл. ОС пытается, насколько это, возможно, скрыть от разработчиков различия между устройствами. Открыв устройство, вы используете одни и те же функции для чтения и записи независимо от того, с каким устройством вы обмениваетесь информацией.
Для осуществления ввода-вывода, прежде всего надо открыть устройство и получить его описатель. Способ получения описателя зависит от типа устройства, но, как правило, используется функция CreateFile.
Таблица 11. Функции для открытия различных устройств.
Устройство |
Функция для открытия устройства |
|
|
|
|
Файл |
CreateFile(Полный путь файла или UNC); |
|
|
|
|
Каталог |
CreateFile(Полный путь каталога или UNC); |
|
|
|
|
Логический диск |
CreateFile(\\.\x:); x – имя логического диска |
|
|
|
|
Физический диск |
CreateFile(\\.\PHYSICALDRIVEx); x – номер |
|
|
|
|
Последовательный порт |
CreateFile(COMx); x – номер |
|
|
|
|
Параллельный порт |
CreateFile(LPTx); x – номер |
|
|
|
|
Сервер почтового ящика |
CreateMailslot(\\.\mailslot\имя); |
|
|
|
|
Клиент почтового ящика |
CreateFile(\\сервер\mailslot\имя); |
|
|
|
71 |
Сервер именованного канала |
CreateNamedPipe(\\.\Pipe\Имя); |
|
|
|
|
Клиент именованного канала |
CreateFile(\\сервер\pipe\имя); |
|
|
|
|
Консоль |
CreateConsoleScreenBuffer, GetStdHandle |
|
|
|
|
Сокет |
socket, accept или AcceptEx |
|
|
|
|
HANDLE CreateFile( |
|
|
LPCTSTR lpFileName, |
// |
Имя файла |
DWORD dwDesiredAccess, |
// |
Режим доступа |
DWORD dwShareMode, |
// Режим |
|
разделения |
|
|
LPSECURITY_ATTRIBUTES lpSecurityAttributes, // |
||
Дескриптор защиты |
|
|
DWORD dwCreationDisposition, |
// Параметры |
|
создания |
|
|
DWORD dwFlagsAndAttributes, |
// Атрибуты |
|
файла |
|
|
HANDLE hTemplateFile |
// Описатель |
|
шаблона |
|
|
); |
|
|
LPCTSTR lpFileName указывает тип и имя устройства. Для дисковых файлов указывается полный путь либо UNC путь. Максимальная длина пути равна MAX_PATH (260), однако можно преодолеть это ограничение, указав перед именем “\\?\” это позволит указать имя в формате UNICODE, длиной до 32 000 символов, но каждый компонент пути ограничен
MAX_PATH.
DWORD dwDesiredAccess определяет, как будет производиться обмен данными с устройством.
|
Значение |
|
|
Описание |
|
|
|
|
|
|
|
|
|
|
0 |
Считывание |
или |
запись |
на |
устройство |
|
|
производиться не будут. Файл открыт только для |
||||
|
|
изменения |
конфигурационных |
параметров |
||
|
|
устройства. |
|
|
|
|
|
|
|
||||
|
GENERIC_READ |
Разрешает доступ к устройству только для чтения |
||||
|
|
|
|
|
|
|
|
|
72 |
|
|
|
|
GENERIC_WRITE |
Разрешает доступ к устройству только для записи |
|
|
GENERIC_READ| |
Разрешает доступ для чтения и записи. |
GENERIC_WRITE |
|
|
|
DWORD dwShareMode определяет привилегии при совместном доступе к устройству.
Значение |
Описание |
|
|
0 |
Требуется монопольный доступ к устройству |
|
|
FILE_SHARE_READ |
Другие процессы не могут записывать на |
|
устройство |
|
|
FILE_SHARE_WRITE |
Другие процессы не могут читать с устройства |
|
|
FILE_SHARE_READ | |
Другие процессы могут и писать и читать с |
FILE_SHARE_WRITE |
устройства |
|
|
LPSECURITY_ATTRIBUTES lpSecurityAttributes – дескриптор защиты, который применим только в файловой системе NTFS. Для защиты по умолчанию NULL.
DWORD dwCreationDisposition задаѐт параметры открытия дисковых файлов
|
Значение |
|
|
Описание |
||
|
|
|
|
|||
|
CREATE_NEW |
|
Функция должна создать новый файл. Если файл |
|||
|
|
|
|
существует, функция не выполняется. |
||
|
|
|
|
|||
|
CREATE_ALWAYS |
|
Функция создаѐт новый файл в независимости от |
|||
|
|
|
|
его существования. |
|
|
|
|
|
|
|||
|
OPEN_EXISTING |
|
Функция открывает существующий файл. Если он |
|||
|
|
|
|
отсутствует, то функция не выполняется. |
||
|
|
|
|
|||
|
OPEN_ALWAYS |
|
Функция откроет существующий файл или |
|||
|
|
|
|
создаст новый, если его нет. |
|
|
|
|
|
|
|||
|
TRUNCATE_EXISTING |
|
Функция открывает существующий файл и |
|||
|
|
|
|
обнуляет его размер. |
|
|
|
|
|
|
|
|
|
DWORD |
dwFlagsAndAttributes |
используется |
для двух целей: |
|||
позволяет |
устанавливать |
флаги для |
оптимизации |
взаимодействия с |
||
|
|
|
|
|
|
|
|
|
|
|
73 |
|
|
устройством, а, так же, если устройство является файлом, – установить его атрибуты. Ниже даны некоторые, наиболее важные, флаги и атрибуты.
Значение |
Описание |
|
|
FILE_FLAG_NO_BUFFERING |
Этот флаг указывает, что доступ к файлу |
|
должен производиться без буферизации. |
|
Флаг налагает некоторые ограничения: |
|
Смещения при доступе к файлу должны |
|
быть кратны сектору дискового тома. |
|
Число считываемых и записываемых байт |
|
должно быть кратно размеру сектора. |
|
Адрес буфера должен быть кратен |
|
размеру сектора. |
|
|
FILE_FLAG_SEQUENTIAL_S |
Позволяет подсистеме I/O оптимизировать |
CAN |
последовательный доступ к данным файла. |
|
|
|
|
FILE_FLAG_RANDOM_ACCESS |
Позволяет подсистеме I/O оптимизировать |
|
доступ к данным файла по случайным |
|
смещениям. |
|
|
FILE_FLAG_WRITE_THROUGH |
Определяет сквозное кэширование записи |
|
в файл. |
|
|
FILE_FLAG_OVERLAPPED |
Этот флаг задаѐт асинхронный обмен |
|
данными с файлом. |
|
|
FILE_ATTRIBUTE_NORMAL |
Файл не имеет атрибутов |
|
|
FILE_ATTRIBUTE_READONLY |
Файл только для чтения |
|
|
…Другие атрибуты файла |
|
|
|
HANDLE hTemplateFile – описатель уже открытого файла или NULL. Указывает файл, атрибуты которого должны быть назначены создаваемому файлу.
Возвращаемое значение
Вслучае удачи возвращает описатель файла или устройства, который закрывается функцией CloseHandle.
Вслучае ошибки возвращается значение INVALID_HANDLE_VALUE (0xFFFFFFF)
74
Пример 22. Вызов функции создания файла.
// Открытие существующего файла для записи и асинхронного доступа без //буферизации
HANDLE h = CreateFile("C:\\Sample.txt", GENERIC_WRITE,
FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_NO_BUFFERING |
FILE_FLAG_OVERLAPPED, NULL);
// Открытие логического диска A:
HANDLE h = CreateFile("\\\\.\\A:", GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0,
NULL);
// Открытие последовательного порта
HANDLE h = CreateFile("COM1", GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0,
NULL);
// Проверка результата функции if (h != INVALID_HANDLE_VALUE)
{
…
CloseHandle(h);
} else printf(―Ошибка!‖);
Функции работы с файлами
В системе Windows 2000 адресация к данным файла производится через 64-битный указатель, что, теоретически, ограничивает размер файла 264 байт (18 экзабайт). В настоящий момент только файловая система NTFS поддерживает 64-разрядную адресацию. Системы FAT16 и FAT32 определяют максимальный фала в 232 (4 ГБ) байт. Указатель создаѐтся при открытии файла, и указывает на начальную позицию, с которой будет выполняться следующая операция чтения или записи (только при синхронном доступе к файлу!). Каждая операция чтения или записи сдвигает файловый указатель на число считанных или записанных байт.
75
Таблица 12. Получение размера файла.
DWORD GetFileSize
(
HANDLE hFile, // описатель файла
LPDWORD lpFileSizeHigh // старшие 32 разряда
);
HANDLE hFile описатель файла
LPDWORD lpFileSizeHigh – старшие 32 разряда размера файла. Можно передать NULL, если не интересует.
Возвращаемое значение
младшие 32 разряда размера файла
в случае ошибки возвращается INVALID_FILE_SIZE
Пример
HANDLE hFile = CreateFile(…);
DWORD dwLow, dwHigh; |
|
|
__int64 |
Size; |
|
dwLow = |
GetFileSize(hFile, &dwHigh); |
|
Size = dwHigh; Size<<=32; Size+=dwLow; |
||
|
|
|
Таблица 13. Перемещение указателя файла. |
|
|
|
|
|
DWORD SetFilePointer( |
|
|
HANDLE hFile, |
// Описатель файла |
|
LONG lDistanceToMove, // младшие 32 бита смещения |
||
|
|
// указателя |
PLONG lpDistanceToMoveHigh, // старшие 32 бита |
||
|
|
// смещения указателя |
DWORD dwMoveMethod // Стартовая точка для |
||
смещения |
|
|
); |
|
|
Стартовая точка для смещения:
FILE_BEGIN – смещать относительно начала файла. FILE_END – смещать относительно конца файла.
FILE_CURRENT – смещать относительно текущего значения указателя.
Возвращаемое значение
76
Новое смещение в файле в случае удачи.
INVALID_SET_FILE_POINTER в случае ошибки. Дополнительно требуется проверить результат функции, что GetLastError() != 0.
Пример
HANDLE hFile = CreateFile(…);
if (SetFilePointer(hFile, 1024, NULL, FILE_BEGIN) = INVALID_SET_FILE_POINTER)
{
if (GetLastError()!=0) printf(―Ошибка!‖); else printf(―OK!‖);
}
Таблица 14. Установка маркера конца файла.
BOOL SetEndOfFile(HANDLE hFile)
HANDLE hFile – описатель файла
Возвращаемое значение
TRUE в случае удачи.
Пример
Установка размера файла в 1 МБ
HANDLE hFile = CreateFile(…);
SetFilePointer(hFile,1024*1024,NULL, FILE_BEGIN); SetEndOfFile(hFile);
SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
Синхронный ввод-вывод
Синхронный ввод-вывод осуществляется функциями ReadFile и WriteFile. При этом вызывающий поток блокируется до завершения операции. Операции с файлами происходят синхронно, если при его создании не был установлен флаг FILE_FLAG_OVERLAPPED.
Таблица 15. Функция ReadFile.
BOOL ReadFile(
HANDLE hFile, // описатель файла
LPVOID lpBuffer,// буфер для считывания данных
DWORD nNumberOfBytesToRead, // сколько байт читать LPDWORD lpNumberOfBytesRead,// истинное значение
// считанных байт
LPOVERLAPPED lpOverlapped // указатель на // структуру OVERLAPPED
77
);
HANDLE hFile – описатель файла
LPVOID lpBuffer – буфер, куда будут помещены считанные данные. DWORD nNumberOfBytesToRead – число байт для чтения. Должно быть меньше или равно размеру буфера.
LPDWORD lpNumberOfBytesRead – указатель на переменную типа
DWORD для помещения результата об истинном значении считанных байт.
LPOVERLAPPED lpOverlapped – указатель на структуру OVERLAPPED,
применяющаяся при асинхронном доступе к файлу. Для синхронного доступа указывается NULL.
Возвращаемое значение
Если запрашиваемая синхронная операция успешно завершена, то TRUE. При ошибке FALSE.
Если запрашиваемая асинхронная операция успешно завершена как синхронная, то TRUE
Если запрашиваемая асинхронная операция не завершена, то FALSE.
Требуется проверить, что GetLastError() = ERROR_IO_PENDING, что говорит о выполняющейся асинхронной операции. Если это не так, то результат ввода-вывода закончился с ошибкой.
Таблица 16. Функция WriteFile
BOOL WriteFile(
//описатель файла
HANDLE hFile,
//буфер для записи данных
LPVOID lpBuffer,
//сколько байт писать
DWORD nNumberOfBytesToWrite,
//истинное значение записанных байт
LPDWORD lpNumberOfBytesWritten,
//указатель на структуру OVERLAPPED
LPOVERLAPPED lpOverlapped );
HANDLE hFile – описатель файла
LPVOID lpBuffer – буфер, откуда брать данные для записи данные. DWORD nNumberOfBytesToRead – число байт для записи. Должно быть меньше или равно размеру буфера.
LPDWORD lpNumberOfBytesRead – указатель на переменную типа
DWORD для помещения результата об истинном значении записанных байт.
LPOVERLAPPED lpOverlapped – указатель на структуру
78
OVERLAPPED, применяющейся при асинхронном доступе к файлу. Для синхронного доступа указывается NULL.
Возвращаемое значение
Если запрашиваемая синхронная операция успешно завершена, то TRUE. При ошибке FALSE.
Если запрашиваемая асинхронная операция успешно завершена как синхронная, то TRUE.
Если запрашиваемая асинхронная операция не завершена, то FALSE.
Требуется проверить, что GetLastError() == ERROR_IO_PENDING, что говорит о выполняющейся асинхронной операции. Если это не так, то результат ввода-вывода закончился с ошибкой.
Пример 23. Пример синхронной операции с файлом.
#define BUFSIZE 4096
//Исходный файл (должен существовать) const char szSrc[] = "С:\\Sample.txt";
//Конечный файл
const char szTgt[] = "С:\\New.txt"; int _tmain()
{
//Всегда будем пользоваться VirtualAlloc для буфера char* Buf = (char*) VirtualAlloc(NULL, BUFSIZE,
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
// Открытие заданного файла для чтения
HANDLE hSrc = CreateFile(szSrc, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
// Создание файла для записи
HANDLE hTgt = CreateFile(szTgt, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, 0, NULL);
// Если файлы открыты
if ((hSrc != INVALID_HANDLE_VALUE) && (hTgt != INVALID_HANDLE_VALUE))
{
79
//результат чтения из файла
DWORD dwResult = 0;
//читаем из исходного файла
if (ReadFile(hSrc, Buf, BUFSIZE, &dwResult, NULL))
{
// устанавливаем длину целевого файла.
SetFilePointer(hTgt, 1024+dwResult, NULL, FILE_BEGIN);
SetEndOfFile(hTgt);
//Будем писать не с 0, а с позиции 1024 в файле
SetFilePointer(hTgt, 1024, NULL, FILE_BEGIN);
//Записываем число байт, считанных ReadFile
if (!(WriteFile(hTgt, Buf, dwResult, &dwResult, NULL)))
printf("Ошибка! записи"); } else printf("Ошибка");
//Закрываем файлы
CloseHandle(hSrc);
CloseHandle(hTgt);
//Освобождаем буфер
VirtualFree(Buf, 0, MEM_RELEASE);
}else printf("Ошибка: один из файлов не открыт");
}
Асинхронный ввод-вывод
При асинхронном запросе ввода-вывода поток, выдавший запрос поток не блокируется до окончания выполнения, а продолжает выполнять другие задачи. В какой-то момент времени поток (или другой поток) достигнет такой точке в коде, где ему понадобится результат ввода-вывода, – т.е. он должен синхронизироваться с операцией ввода-вывода. На данный момент это решается рядом способов, которые рассмотрены ниже.
Что бы получить асинхронный доступ к устройству, нужно вызвать функцию CreateFile с установленным флагом FILE_FLAG_OVERLAPPED.
80