Основы организации операционных систем Microsoft Windows

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

Разбить на страницы
Показывать лекцию целиком

Введение

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

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

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

В данном курсе основное внимание будет сосредоточено на NTFS - базовой файловой системе ОС Windows. Вначале будет рассмотрен интерфейс, то есть вопросы структуры, именования, защиты файлов; операции над файлами; организация файлового архива при помощи каталогов. В следующей лекции будут проанализированы проблемы реализации файловой системы, способы выделения дискового пространства и связывания его с именем файла, обеспечение производительной работы файловой системы и ряд других вопросов, интересующих разработчиков системы.

Основные функции для работы с файлами

Предметное изучение интерфейса файловой системы лучше начать с описания простейшей программы чтения и записи в файл, которая использует основные ( CreateFile, ReadFile и WriteFile ) операции для работы с файлами.

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

Следующая программа открывает существующий файл, считывает из него 10 байтов с начала файла и записывает в файл фразу "some bytes to write", начиная с 11-й позиции. Для буфера выделяется память из стандартной кучи процесса (см. лекцию 9).

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

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

Именование файлов

Имя любого абстрактного объекта - одна из его важнейших характеристик. Когда процесс создает файл, он дает ему имя. После завершения процесса файл продолжает существовать и через свое имя может быть доступен другим процессам. Для создания файла и присвоения ему имени в ОС Windows используют Win32-функцию CreateFile.

Имя файла задается параметром lpFileName - указателем на строку, заканчивающуюся нулем. В соответствии со стандартом POSIX ОС Windows оперирует длинными (до 255 символов) именами. Если быть более точным, максимальная длина полного имени файла при создании файла равна MAX_PATH. Значение MAX_PATH определено как 260, но система позволяет преодолеть это ограничение и использовать имена файлов длиной до 32000 символов в формате Unicode.

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

Типы файлов

ОС Windows поддерживает типизацию файлов. Основные типы файлов: регулярные (обычные) файлы и директории (справочники, каталоги).

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

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

Далее речь пойдет, главным образом, об обычных файлах.

Прикладные программы, работающие с файлами, как правило, распознают тип файла по его имени в соответствии с общепринятыми соглашениями. Например, файлы с расширениями .c, .pas - текстовые файлы, хранящие программы на Си и Паскале, а файлы с расширениями .exe - исполняемые, и т.д. Связь имен с обрабатывающими программами реализована в реестре.

Атрибуты файлов

Кроме имени ОС часто связывает с каждым файлом и другую информацию, например, дату модификации, размер и т.д. Эти другие характеристики файлов называются атрибутами. В ОС Windows понятие атрибута трактуется шире. Считается, что файл - это не просто последовательность байтов, а совокупность атрибутов, и данные файла являются лишь одним из атрибутов - так называемый неименованный поток данных. Есть и другие (именованные) потоки данных, которые нужно указывать через двоеточие. Именованные потоки данных можно "увидеть" при помощи таких команд, как echo и more. Например, если выполнить следующие интерактивные команды

>Echo  содержимое файла > MyFile:Stream1
>more <  MyFile:Stream1

то на экране должны появиться слова "содержимое файла".

Вот далеко не полный перечень атрибутов файла в NTFS:

  • Стандартная информация - флаговые биты (только чтение, архивный), временные штампы и т.д.
  • Имя файла. Имя файла хранится в кодировке Unicode. Имена файлов могут повторяться в формате MS-DOS.
  • Описатель защиты.
  • Данные. Неименованный и именованные потоки данных.
  • Список атрибутов - расположение дополнительных записей MFT, если одной записи о файле оказалось недостаточно.
  • Идентификатор объекта - 64-разрядный идентификатор файла, уникальный для данного тома. Файл может быть открыт не по имени, а по этому идентификатору.
  • Информация о точке повторного разбора (см. следующую лекцию), которая используется для символьных ссылок и монтирования устройств.
  • Информация о томе.
  • Информация об индексировании, используемая для каталогов.
  • Данные EFS (Encryption File System), используемые для шифрования.
  • Имя файла тоже является одним из атрибутов. Атрибуты хранятся в виде пары: <наименование атрибута, значение атрибута> в записи о файле в главной файловой таблице MFT (см. следующую лекцию).

    Часть атрибутов файла можно определить при его создании (через параметры функции CreateFile ) или позже при помощи SetFileAttributes, сославшись на файл по имени. Можно также специфицировать атрибуты защиты файла при помощи параметра lpSecurityAttributes. Если же значение lpSecurityAttributes равно NULL, то соответствующие атрибуты файла будут содержать параметры так называемой стандартной защиты (подробнее об этом часть V).

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

    Прогон программы получения атрибутов файла

    #include <windows.h>
    #include <stdio.h>
    
    void main(void) {
    
    DWORD dwFileAttributes;
    
    dwFileAttributes = GetFileAttributes("tmp");
    if(dwFileAttributes == -1) printf(" GetFileAttributes Error\n");
    
    if (dwFileAttributes  FILE_ATTRIBUTE_NORMAL) 
     printf("This file is normal\n");
    if (dwFileAttributes  FILE_ATTRIBUTE_DIRECTORY) 
     printf("This file is directory\n");
    if (dwFileAttributes  FILE_ATTRIBUTE_REPARSE_POINT) 
     printf("This file is reparse point\n");
    }

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

    Рекомендуется самостоятельно написать программу, где применяется функция SetFileAttributes, например, устанавливается флаг "FILE_ATTRIBUTE_READONLY" для атрибутов указанного файла.

    Организация файлов и доступ к ним. Понятие об асинхронном вводе-выводе

    Для хранения файлов обычно используются устройства прямого доступа (диски), которые позволяют обращаться напрямую к любому блоку диска. Это обеспечивает произвольный доступ к байтам файла, поскольку номер блока однозначно определяется текущей позицией внутри файла. Таким образом, файловая подсистема ОС Windows имеет дело с файлами, байты которых могут быть считаны в любом порядке. Такие файлы называется файлами прямого доступа. Непосредственное обращение к любому байту внутри файла предполагает наличие операции позиционирования, целью которой является задание текущей позиции для считывания или записи. Поскольку файл может иметь большой размер, указатель текущей позиции - 64-разрядное число, для задания которого обычно используются два 32-разрядных.

    Известно, что операции ввода-вывода являются относительно медленными. Чтобы избавить центральный процессор от ожидания выполнения операции ввода-вывода, в системе организована обработка асинхронных событий, в частности, прерываний, для оповещения процессора о завершении операции ввода-вывода. Однако если на уровне ОС операции ввода-вывода являются асинхронными, на уровне пользовательской программы они еще долго оставались синхронными и блокирующими. В результате процесс, инициировавший операцию ввода-вывода, переходил в состояние ожидания. Примером синхронного ввода-вывода служит приведенный выше программный фрагмент, где операторы, следующие за вызовами функций ReadFile и WriteFile, не могут выполняться до тех пор, пока операция ввода-вывода не завершена.

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

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

    Для того чтобы воспользоваться возможностями асинхронного ввода-вывода, нужно вызвать функцию 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 уже будет равно числу прочитанных байтов.

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

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

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

    Итак, в случае асинхронного доступа позиция, начиная с которой будет осуществляться операция чтения-записи, содержится в запросе на операцию (параметр структуры 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.

    Разделы диска. Операция монтирования

    В ОС Windows принято разбивать диски на логические диски (это низкоуровневая операция), иногда называемые разделами (partitions). Бывает, что, наоборот, объединяют несколько физических дисков в один логический диск. На разделе или логическом диске хранится корневой каталог данного и все вложенные в него каталоги, а задание пути к файлу начинается с имени логического диска или "буквы" диска.

    Имена логических дисков хранятся в каталоге "\??" пространства имен объектов, которые и осуществляют связь логического диска и реального устройства. Указав букву диска, прикладная программа получает доступ к его файловой системе.

    По аналогии с Unix операционная система Windows позволяет пользователю создать точку монтирования - связать какой-либо пустой каталог с каталогом логического диска. В случае успешного завершения операции содержимое этих каталогов будет соответствовать друг другу.

    Эксперимент. Монтирование логического диска с помощью штатной утилиты mountvol

    Чтобы смонтировать логический диск, нужно выполнить команду

    >mountvol  [<диск>:]<путь> <имя тома>

    Здесь параметр <путь> задает имя пустого каталога, а имя тома задается в виде \\?\Volume{код_GUID}\, где GUID - глобальный уникальный идентификатор.

    Например

    >mountvol  f:\tmp1   \\?\Volume\{2eca078d-5cbc-43d3-aff8-7e8511f60d0e}\}

    Имена глобальных уникальных идентификаторов и их связь с буквами диска можно узнать, дав команду

    >mountvol /?

    Монтирование также можно выполнить с помощью панели управления дисками системной панели управления, если выбрать пункт "изменение буквы диска и пути диска".

    Наконец, смонтировать диск можно программным образом с помощью Win32-функции SetVolumeMountPoint.

    Защита файлов

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

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

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

    В новых версиях NTFS дескрипторы защиты всех файлов хранятся в отдельном файле метаданных \$Secure, который описывается 9-й записью главной файловой таблицы тома MFT (консолидированная защита).

    Более подробно защита от несанкционированного доступа описана в части V данного курса.

    Заключение

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

    Страницы:

    Введение

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

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

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

    В данном курсе основное внимание будет сосредоточено на NTFS - базовой файловой системе ОС Windows. Вначале будет рассмотрен интерфейс, то есть вопросы структуры, именования, защиты файлов; операции над файлами; организация файлового архива при помощи каталогов. В следующей лекции будут проанализированы проблемы реализации файловой системы, способы выделения дискового пространства и связывания его с именем файла, обеспечение производительной работы файловой системы и ряд других вопросов, интересующих разработчиков системы.

    Основные функции для работы с файлами

    Предметное изучение интерфейса файловой системы лучше начать с описания простейшей программы чтения и записи в файл, которая использует основные ( CreateFile, ReadFile и WriteFile ) операции для работы с файлами.

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

    Следующая программа открывает существующий файл, считывает из него 10 байтов с начала файла и записывает в файл фразу "some bytes to write", начиная с 11-й позиции. Для буфера выделяется память из стандартной кучи процесса (см. лекцию 9).

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

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

    Именование файлов

    Имя любого абстрактного объекта - одна из его важнейших характеристик. Когда процесс создает файл, он дает ему имя. После завершения процесса файл продолжает существовать и через свое имя может быть доступен другим процессам. Для создания файла и присвоения ему имени в ОС Windows используют Win32-функцию CreateFile.

    Имя файла задается параметром lpFileName - указателем на строку, заканчивающуюся нулем. В соответствии со стандартом POSIX ОС Windows оперирует длинными (до 255 символов) именами. Если быть более точным, максимальная длина полного имени файла при создании файла равна MAX_PATH. Значение MAX_PATH определено как 260, но система позволяет преодолеть это ограничение и использовать имена файлов длиной до 32000 символов в формате Unicode.

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

    Типы файлов

    ОС Windows поддерживает типизацию файлов. Основные типы файлов: регулярные (обычные) файлы и директории (справочники, каталоги).

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

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

    Далее речь пойдет, главным образом, об обычных файлах.

    Прикладные программы, работающие с файлами, как правило, распознают тип файла по его имени в соответствии с общепринятыми соглашениями. Например, файлы с расширениями .c, .pas - текстовые файлы, хранящие программы на Си и Паскале, а файлы с расширениями .exe - исполняемые, и т.д. Связь имен с обрабатывающими программами реализована в реестре.

    Атрибуты файлов

    Кроме имени ОС часто связывает с каждым файлом и другую информацию, например, дату модификации, размер и т.д. Эти другие характеристики файлов называются атрибутами. В ОС Windows понятие атрибута трактуется шире. Считается, что файл - это не просто последовательность байтов, а совокупность атрибутов, и данные файла являются лишь одним из атрибутов - так называемый неименованный поток данных. Есть и другие (именованные) потоки данных, которые нужно указывать через двоеточие. Именованные потоки данных можно "увидеть" при помощи таких команд, как echo и more. Например, если выполнить следующие интерактивные команды

    >Echo  содержимое файла > MyFile:Stream1
    >more <  MyFile:Stream1

    то на экране должны появиться слова "содержимое файла".

    Вот далеко не полный перечень атрибутов файла в NTFS:

  • Стандартная информация - флаговые биты (только чтение, архивный), временные штампы и т.д.
  • Имя файла. Имя файла хранится в кодировке Unicode. Имена файлов могут повторяться в формате MS-DOS.
  • Описатель защиты.
  • Данные. Неименованный и именованные потоки данных.
  • Список атрибутов - расположение дополнительных записей MFT, если одной записи о файле оказалось недостаточно.
  • Идентификатор объекта - 64-разрядный идентификатор файла, уникальный для данного тома. Файл может быть открыт не по имени, а по этому идентификатору.
  • Информация о точке повторного разбора (см. следующую лекцию), которая используется для символьных ссылок и монтирования устройств.
  • Информация о томе.
  • Информация об индексировании, используемая для каталогов.
  • Данные EFS (Encryption File System), используемые для шифрования.
  • Имя файла тоже является одним из атрибутов. Атрибуты хранятся в виде пары: <наименование атрибута, значение атрибута> в записи о файле в главной файловой таблице MFT (см. следующую лекцию).

    Часть атрибутов файла можно определить при его создании (через параметры функции CreateFile ) или позже при помощи SetFileAttributes, сославшись на файл по имени. Можно также специфицировать атрибуты защиты файла при помощи параметра lpSecurityAttributes. Если же значение lpSecurityAttributes равно NULL, то соответствующие атрибуты файла будут содержать параметры так называемой стандартной защиты (подробнее об этом часть V).

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

    Прогон программы получения атрибутов файла

    #include <windows.h>
    #include <stdio.h>
    
    void main(void) {
    
    DWORD dwFileAttributes;
    
    dwFileAttributes = GetFileAttributes("tmp");
    if(dwFileAttributes == -1) printf(" GetFileAttributes Error\n");
    
    if (dwFileAttributes  FILE_ATTRIBUTE_NORMAL) 
     printf("This file is normal\n");
    if (dwFileAttributes  FILE_ATTRIBUTE_DIRECTORY) 
     printf("This file is directory\n");
    if (dwFileAttributes  FILE_ATTRIBUTE_REPARSE_POINT) 
     printf("This file is reparse point\n");
    }

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

    Рекомендуется самостоятельно написать программу, где применяется функция SetFileAttributes, например, устанавливается флаг "FILE_ATTRIBUTE_READONLY" для атрибутов указанного файла.

    Организация файлов и доступ к ним. Понятие об асинхронном вводе-выводе

    Для хранения файлов обычно используются устройства прямого доступа (диски), которые позволяют обращаться напрямую к любому блоку диска. Это обеспечивает произвольный доступ к байтам файла, поскольку номер блока однозначно определяется текущей позицией внутри файла. Таким образом, файловая подсистема ОС Windows имеет дело с файлами, байты которых могут быть считаны в любом порядке. Такие файлы называется файлами прямого доступа. Непосредственное обращение к любому байту внутри файла предполагает наличие операции позиционирования, целью которой является задание текущей позиции для считывания или записи. Поскольку файл может иметь большой размер, указатель текущей позиции - 64-разрядное число, для задания которого обычно используются два 32-разрядных.

    Известно, что операции ввода-вывода являются относительно медленными. Чтобы избавить центральный процессор от ожидания выполнения операции ввода-вывода, в системе организована обработка асинхронных событий, в частности, прерываний, для оповещения процессора о завершении операции ввода-вывода. Однако если на уровне ОС операции ввода-вывода являются асинхронными, на уровне пользовательской программы они еще долго оставались синхронными и блокирующими. В результате процесс, инициировавший операцию ввода-вывода, переходил в состояние ожидания. Примером синхронного ввода-вывода служит приведенный выше программный фрагмент, где операторы, следующие за вызовами функций ReadFile и WriteFile, не могут выполняться до тех пор, пока операция ввода-вывода не завершена.

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

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

    Для того чтобы воспользоваться возможностями асинхронного ввода-вывода, нужно вызвать функцию 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 уже будет равно числу прочитанных байтов.

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

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

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

    Итак, в случае асинхронного доступа позиция, начиная с которой будет осуществляться операция чтения-записи, содержится в запросе на операцию (параметр структуры 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.

    Разделы диска. Операция монтирования

    В ОС Windows принято разбивать диски на логические диски (это низкоуровневая операция), иногда называемые разделами (partitions). Бывает, что, наоборот, объединяют несколько физических дисков в один логический диск. На разделе или логическом диске хранится корневой каталог данного и все вложенные в него каталоги, а задание пути к файлу начинается с имени логического диска или "буквы" диска.

    Имена логических дисков хранятся в каталоге "\??" пространства имен объектов, которые и осуществляют связь логического диска и реального устройства. Указав букву диска, прикладная программа получает доступ к его файловой системе.

    По аналогии с Unix операционная система Windows позволяет пользователю создать точку монтирования - связать какой-либо пустой каталог с каталогом логического диска. В случае успешного завершения операции содержимое этих каталогов будет соответствовать друг другу.

    Эксперимент. Монтирование логического диска с помощью штатной утилиты mountvol

    Чтобы смонтировать логический диск, нужно выполнить команду

    >mountvol  [<диск>:]<путь> <имя тома>

    Здесь параметр <путь> задает имя пустого каталога, а имя тома задается в виде \\?\Volume{код_GUID}\, где GUID - глобальный уникальный идентификатор.

    Например

    >mountvol  f:\tmp1   \\?\Volume\{2eca078d-5cbc-43d3-aff8-7e8511f60d0e}\}

    Имена глобальных уникальных идентификаторов и их связь с буквами диска можно узнать, дав команду

    >mountvol /?

    Монтирование также можно выполнить с помощью панели управления дисками системной панели управления, если выбрать пункт "изменение буквы диска и пути диска".

    Наконец, смонтировать диск можно программным образом с помощью Win32-функции SetVolumeMountPoint.

    Защита файлов

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

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

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

    В новых версиях NTFS дескрипторы защиты всех файлов хранятся в отдельном файле метаданных \$Secure, который описывается 9-й записью главной файловой таблицы тома MFT (консолидированная защита).

    Более подробно защита от несанкционированного доступа описана в части V данного курса.

    Заключение

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

    Вернуться к учебному плану