Когда клиент пытается обратиться к файлу на сервере, он должен сначала запросить oplock. Вид доступного клиенту кэширования, определяется типом oplock, предоставляемого сервером. Существует три основных типа oplock.
• Level I oplock предоставляется при монопольном доступе клиента к файлу Клиент, удерживающий для файла oplock этого типа, может кэшировать операции как чтения, так и записи.
• Level II oplock — разделяемая блокировка файла. Клиенты, удерживающие oplock этого типа, могут кэшировать операции чтения, но запись в файл делает Level II oplock недействительным.
• Batch oplock — самый либеральный тип oplock. Он позволяет клиенту не только читать и записывать файл, но и открывать и закрывать его, не запрашивая дополнительные oplock. Batch oplock, как правило, используется только для поддержки выполнения пакетных (командных) файлов, которые могут неоднократно закрываться и открываться в процессе выполнения.
B отсутствие oplock клиент не может осуществлять локальное кэширование ни операций чтения, ни операций записи; вместо этого он должен получать данные с сервера и посылать все изменения непосредственно на сервер.
Проиллюстрировать работу oplock поможет пример на рис. 12-7. Первому клиенту, открывающему файл, сервер автоматически предоставляет Level I oplock. Редиректор на клиентской стороне кэширует файловые данные при чтении и записи в кэше файловой системы локальной машины. Если тот же файл открывает второй клиент, он также запрашивает Level I oplock. Теперь уже два клиента обращаются к одному и тому же файлу, поэтому сервер должен принять меры для согласования представления данных файла обоим клиентам. Если первый клиент произвел запись в файл (этот случай и показан на рис. 12-7), сервер отзывает oplock и больше не предоставляет его ни одному клиенту. После отзыва oplock первый клиент сбрасывает все кэшированные данные файла обратно на сервер.
Если бы первый клиент не произвел запись, его oplock был бы понижен до Level II oplock, т. е. до oplock того же типа, который сервер предоставляет второму клиенту. B этом случае оба клиента могли бы кэшировать операции чтения, но после операции записи любым из клиентов сервер отозвал бы их oplock, и последующие операции были бы некэшируемыми. Однажды отозванный, oplock больше не предоставляется для этого экземпляра открытого файла. Однако, если клиент закрывает файл и повторно открывает его, сервер заново решает, какой oplock следует предоставить клиенту. Решение сервера зависит от того, открыт ли файл другими клиентами и производил ли хоть один из них запись в этот файл.
ЭКСПЕРИМЕНТ: просмотр списка зарегистрированных файловых систем
Диспетчер ввода-вывода, загружая в память драйвер устройства, обычно присваивает имя объекту «драйвер», который создается для представления этого драйвера. Этот объект помещается в каталог Drivers диспетчера объектов. Объекты «драйвер» любого загружаемого диспетчером ввода-вывода драйвера с атрибутом Туре, равным SERVICEFILE _SYSTEM_DRIVER (2), помещаются этим диспетчером в каталог File-System. Таким образом, утилита типа Winobj (wwwsysintemals.com) позволяет увидеть зарегистрированные файловые системы, как показано на следующей иллюстрации. (Заметьте, что некоторые драйверы файловых систем помещают в каталог FileSystem и объекты «устройство».)
Еще один способ увидеть зарегистрированные файловые системы — запустить программу System Information (Сведения о системе). B Windows 2000 запустите оснастку Computer Management (Управление компьютером) и выберите Drivers (Драйверы) в Software Environment (Программная среда) в узле System Information (Сведения о системе); в Windows XP и Windows Server 2003 запустите Msinfo32 и выберите System Drivers (Системные драйверы) в узле Software Environment (Программная среда). Отсортируйте список драйверов, щелкнув колонку Туре (Тип), и драйверы с атрибутом SERVICE_FILE_SYSTEM_DRIVER окажутся в начале списка.
Заметьте: если драйвер регистрируется как SERVICE_FILE_SYSTEM_DRIVER, это еще не означает, что он служит локальным или удаленным FSD. Например, Npfs (Named Pipe File System), присутствующий на только что показанной иллюстрации, на самом деле является драйвером сетевого API — он поддерживает именованные каналы, но реализует закрытое пространство имен и поэтому в какой-то мере подобен драйверу файловой системы. Пространство имен Npfs исследуется в одном из экспериментов в главе 13.
Работа файловой системы
Система и приложения могут обращаться к файлам двумя способами: напрямую (через функции ввода-вывода вроде ReadFile и WriteFile) и косвенно, путем чтения или записи части своего адресного пространства, где находится раздел проецируемого файла (подробнее о проецируемых файлах см. главу 7). Упрощенная схема на рис. 12-8 иллюстрирует компоненты, участвующие в работе файловой системы, и способы их взаимодействия. Как видите, есть несколько путей вызова FSD:
• из пользовательского или системного потока, выполняющего явную операцию файлового ввода-вывода;
• из подсистем записи модифицированных и спроецированных страниц, принадлежащих диспетчеру памяти;
• неявно из подсистемы отложенной записи, принадлежащей диспетчеру кэша;
• неявно из потока опережающего чтения, принадлежащего диспетчеру кэша;
• из обработчика ошибок страниц, принадлежащего диспетчеру памяти.
Явный файловый ввод-вывод
Наиболее очевидный способ доступа приложения к файлам — вызов Windows-функций ввода-вывода, например CreateFile, ReadFile и WriteFile. Приложение открывает файл с помощью CreateFile, а затем читает, записывает и удаляет его, передавая описатель файла, возвращенный CreateFile, другим Windows-функциям. CreateFile, реализованная в Kernel32.dll, вызывает встроенную функцию NtCreateFile и формирует полное имя файла, обрабатывая символы «.» и «…» и предваряя путь строкой «??» (например, ??C: DarylTodo.txt).
Чтобы открыть файл, системный сервис NtCreateFile вызывает функцию ObOpenObjectByName, которая выполняет разбор имени, начиная с корневого каталога диспетчера объектов и первого компонента полного имени («??»). B главе 3 дано подробное описание разрешения имен диспетчером объектов, а здесь мы поясним, как происходит поиск букв диска для томов.
Первое, что делает диспетчер объектов, — транслирует ?? в каталог пространства имен, индивидуальный для сеанса, в котором выполняется данный процесс; на этот каталог ссылается поле DosDevicesDirectory в структуре карты устройств (device map structure) в объекте «процесс». B системах Windows 2000 без Terminal Services поле DosDevicesDirectory ссылается на каталог ?? а в системах Windows 2000 без Terminal Services карта устройств ссылается на индивидуальный для каждого сеанса каталог, где хранятся объекты «символьная ссылка», представляющие все действительные буквы дисков для томов. Однако в Windows XP и Windows Server 2003 в таком каталоге обычно содержатся лишь имена томов для общих сетевых ресурсов, поэтому в этих OC диспетчер объектов, не найдя имя (в данном примере — G) в индивидуальном для сеанса каталоге, переходит к поиску в каталоге, на который ссылается поле GlobalDosDevicesDirectory карты устройств, сопоставленной с индивидуальным для сеанса каталогом. GlobalDosDevicesDirectory всегда указывает на каталог Global?? где Windows XP и Windows Server 2003 хранят буквы дисков для локальных томов. (Подробнее о пространстве имен сеанса см. одноименный раздел в главе 3-)
Символьная ссылка для буквы диска, присвоенной тому, указывает на объект тома в каталоге Device, поэтому диспетчер объектов, распознав объект тома, передает остаток строки с именем в функцию IopParseDevice, зарегистрированную диспетчером ввода-вывода для объектов «устройство». (Ha томах динамических дисков символьная ссылка указывает на промежуточную ссылку, которая в свою очередь указывает на объект тома.) Ha рис. 12-9 показано, как происходит доступ к объектам томов через пространство имен диспетчера объектов. B данном случае (система Windows 2000 без Terminal Services) символьная ссылка ??C: указывает на объект тома DeviceHarddiskVolumel.
Заблокировав контекст защиты вызывающего потока и получив информацию о защите из его маркера, IopParseDevice генерирует пакет запроса ввода-вывода (IRP) типа IRP_MJ_CREATE, создает объект «файл», в котором запоминается имя открываемого файла, и по ссылке в VPB объекта тома находит объект «устройство» смонтированной файловой системы тома. Далее, используя IoCallDriver, она передает IRP драйверу файловой системы, которому принадлежит данный объект «устройство».
Когда FSD получает IRP типа IRP_MJ_CREATE, он ищет указанный файл, проверяет права доступа и, если файл есть и пользователь имеет права на запрошенный вид доступа к файлу, возвращает код успешного завершения. Диспетчер объектов создает в таблице описателей, принадлежащей процессу, описатель объекта «файл», который передается назад по цепочке вызовов, в конечном счете достигая приложения в виде параметра, возвращаемого CreateFile. Если файловой системе не удается создать файл, диспетчер ввода-вывода удаляет созданный для него объект «файл».