Как организована и управляется база данных важнейшей системной информации Windows NT.

Марк РусСинович доктор философии в области вычислительной техники Университета Карнеги-Меллона и один из соавторов многих популярных утилит для Windows NT, включая NTFSDOS, Filemon, Regmon и Recover. С ним можно связаться по электронной почте по адресу ma
База данных системной информации Registry служит централизованным хранилищем конфигурационных параметров операционных систем Windows NT и Windows 2000, а также приложений для этих платформ. В нее записываются сведения о различных настройках, аппаратной конфигурации, пользовательских предпочтениях. Существует множество книг и статей, посвященных логике построения Registry. Их авторы описывают, где в этой базе данных находятся значения тех или иных конкретных параметров, каковы их допустимые диапазоны, и на что они влияют.

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

Из этой статьи вы узнаете, каким образом Configuration Manager — тот компонент ядра операционной системы, который реализует Registry — организует хранение принадлежащих этой базе данных дисковых файлов, управляет считыванием и модификацией ключей Registry и их значений из приложений. Кроме того, здесь описаны механизмы, обеспечивающие возможность восстановления Registry после сбоя, включая аварию системы в процессе внесения изменений в эту базу данных. Предполагается, что читатель знаком с логической организацией Registry, понятиями корневого ключа (root key), подключа (subkey) и значения ключа (value).

Ульи

На диске Registry хранится не в одном большом, а в нескольких отдельных файлах, называемых «ульями» (hive). Каждый улей содержит одну древовидную структуру Registry, имеющую свой корневой ключ (т. е. вершину дерева). Подключи и их значения находятся ниже корневого ключа в иерархии. Из сказанного можно было бы сделать вывод, что каждый корневой ключ, представленный в окне редактора Registry (regedit или regedt32), хранится в NT в отдельном улье, однако это не так. Соответствие между ключами, отображаемыми regedit, и содержимым ульев не столь очевидно (см. табл. 1). В действительности ни один из этих корневых ключей не соответствует какому-либо одному улью. Соответствие ульев дисковым файлам представлено в табл. 2. Обратите внимание на отсутствие расширений. В тех случаях, когда имя файла не указано, логические корневые ключи являются объектами без дискового представления. Configuration Manager создает такие ключи для объединения различных ульев в структуру Registry, которая и отображается в окне regedit.

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

Таблица 1. Корневые ключи Regedit

Ключ Описание
HKEY_CLASSES_ROOT Символьная ссылка на ключ HKEY_LOCAL_MACHINESOFTWAREClasses.
HKEY_CURRENT_USER Символьная ссылка на один из ключей ветви, идущей от HKEY_USERS. Каждый такой ключ представляет один из ульев пользовательских параметров
HKEY_LOCAL_MACHINE Временный ключ, не имеющий соответствующего физического улья. Объединяет корневые ключи различных ульев
HKEY_USERS Временный ключ, объединяющий ульи параметров зарегистрированных в данный момент в системе пользователей
HKEY_CURRENT_CONFIG Символьная ссылка на ключ текущего набора конфигурационных параметров аппаратуры, находящийся в ветви ключа HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlIDConfigDBHardware Profiles
HKEY_DYN_DATA Временный ключ, служащий для организации ускоренной выборки данных. Не имеет соответствующего физического улья

Сердце Registry — улей HKEY_ LOCAL_MACHINESYSTEM. В него входит подключ CurrentControlSet Control, содержащий параметры, которые Configuration Manager использует при инициализации Registry. (В частности, значение ключа HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlhivelist используется при поиске файлов ульев.)

Таблица 2. Маршрутные имена ульев в Registry и соответствующие дисковые файлы

Маршрутное имя улья в Registry Маршрутное имя файла улья
HKEY_LOCAL_MACHINESYSTEM winntsystem32configsystem
HKEY_LOCAL_MACHINESAM winntsystem32configsam
HKEY_LOCAL_MACHINESECURITY winntsystem32configsecurity
HKEY_LOCAL_MACHINESOFTWARE winntsystem32configsoftware
HKEY_LOCAL_MACHINEHARDWARE Временный улей
HKEY_LOCAL_MACHINESYSTEMClone Временный улей
HKEY_USERSUserProfile Набор пользовательских параметров; обычно в каталоге winntprofilesuser
HKEY_USERS.DEFAULT winntsystem32configdefault

Для соединения ульев в единое дерево Registry используются ключи специального типа символьная ссылка (symbolic link). Так, например, ключ HKEY_LOCAL_MACHINESAM является ссылкой на корневой ключ улья SAM.

Структура улья

Подобно тому, как файловая система делит пространство диска на кластеры, Configuration Manager логически делит пространство улья на блоки. По определению, они имеют размер 4096 байт (4 Кбайт). При увеличении объема хранимых данных к улью добавляется необходимое число блоков. Первый блок улья называется базовым блоком. В нем хранится глобальная информация, включая сигнатуру regf, по которой файл идентифицируется как файл улья, номер текущей версии в последовательности обновления, временную метку, соответствующую последней операции записи, номер версии формата улья, контрольную сумму, и полное имя улья (например, SystemRootCONFIG SAM). О номере в последовательности обновления и временной метке мы еще поговорим при описании способа записи данных в улей. В Windows NT 4.0 формат улья изменился, так что использовать взятые от нее ульи с предыдущими версиями ОС, включая 3.51, не удастся. Внутри улья Registry данные хранятся в «сотах» (cell). Сот может содержать ключ, значение, дескриптор уровня защиты, список подключей или список значений. Первое поле данных сота содержит идентификатор типа данных (см. табл. 3). Размер сота указывается в его заголовке — поле, предшествующем полям данных. Когда требуется ввести в улей дополнительный сот, и для этого необходимо увеличить его размер, система создает «рамку» (bin), имеющую размер нового сота с округлением вверх до целого числа блоков. Остаточное пространство (от конца сота до конца рамки) рассматривается как свободное и может быть использовано для размещения других сотов. Формат рамки включает сигнатуру hbin и поле, содержащее его длину и смещение относительно начала файла улья.

Таблица 3. Типы размещаемых в сотах данных

Сот Тип данных
Сот ключа Ключ Registry. Такой сот называется также ключевым узлом (key node). Сот ключа содержит сигнатуру (kn для сота ключа и kl для символьной ссылки), временную метку последнего обновления, индекс сота списка подключей данного ключа; индекс сота дескриптора уровня защиты данного ключа; индекс сота строкового ключа, содержащего родовое и собственное имена данного ключа (например, CurrentControlSet)
Сот значения Значение ключа. Такой сот содержит сигнатуру (kv), идентификатор типа значения (например, REG_DWORD или REG_BINARY), имя значения (например, Boot-Execute), а также индекс сота, содержащего данные этого значения
Сот списка подключей Список индексов сотов, содержащих подключи одного родительского ключа
Сот списка значений Список индексов сотов, содержащих значения одного родительского ключа
Сот дескриптора уровня защиты Дескриптор уровня защиты. Такие соты содержат сигнатуру (ks) в заголовке и счетчик числа ссылок (то есть числа ключевых узлов, использующих данный дескриптор уровня защиты). Один сот дескриптора уровня защиты может использоваться несколькими сотами ключей

Администрирование активных компонентов Registry на уровне рамок, а не отдельных сотов, сокращает накладные расходы. В частности, рамки создаются и уничтожаются реже, чем соты, что позволяет Configuration Manager более эффективно управлять распределением памяти. При загрузке улья в память считываются только активные рамки (те, что содержат соты), а пустые — игнорируются. В процессе добавления сотов в улей и их удаления могут образовываться пустые рамки вперемежку с активными. Это явление аналогично фрагментации жесткого диска, возникающей в процессе создания и удаления файлов. Смежные пустые рамки укрупняются, как и смежные пустые соты. Ссылки из одних сотов на другие, образующие структуру улья, используют в качестве координат индексы сотов (cell index), представляющие собой смещения от начала файла улья. Например, сот, описывающий некоторый ключ, всегда содержит ссылку на его родительский ключ. Кроме того, в составе сота ключа присутствует ссылка на сот, содержащий список подчиненных ему по структуре подключей. В свою очередь, сот со списком подключей содержит ссылки на соты этих подключей. Таким образом, чтобы найти подключ некоторого ключа, первым делом следует найти по хранимой в его соте ссылке список его подключей. Затем производится перебор подключей по списку и сравнение их имен с искомым.

Рисунок 1: Пример структуры улья Registry.

Во всех этих сотах, рамках и блоках немудрено запутаться, поэтому приведем простой пример структуры улья Registry. Взгляните на рис. 1. На нем изображены базовый блок и две рамки: одна пустая, а другая — с несколькими сотами. В улье два ключа: корневой ключ Root и его подключ Sub Key. Root имеет два значения: Val1 и Val2. Сот списка подключей содержит ссылку на подключ корневого ключа, а сот списка значений — ссылки на его значения. Свободное пространство во второй рамке представляет собой пустые соты. На рисунке не показаны соты дескрипторов уровня защиты для двух ключей, которые также должны присутствовать в реальном улье.

На рис. 1 представлен экран утилиты DiskProbe из состава инструментария Microsoft Windows NT Server 4.0 Resource Kit при просмотре первой рамки в улье SYSTEM. Обратите внимание на сигнатуру рамки hbin справа вверху экрана. Ниже вы видите также сигнатуру nk, используемую для обозначения сот, содержащих ключи. На диске она хранится в обратной последовательности знаков из-за принятого в архитектуре Intel x86 порядка записи байтов. Данный сот содержит корневой ключ улья SYSTEM, что можно определить по имени System, расположенному еще ниже сигнатуры nk.

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

Карты сотов

Чтобы не обращаться к жесткому диску всякий раз, когда поступает запрос на доступ к Registry, в адресном пространстве ядра ОС хранится по копии каждого улья. В процессе инициализации улья Configuration Manager определяет размеры его файла, выделяет достаточное пространство в подкачиваемом пуле памяти ядра ОС и считывает в него файл улья. Подкачиваемый пул памяти — это область адресного пространства ядра NT, зарезервированная для использования драйверами устройств и самим ядром. Не используемые в данный момент фрагменты этого пула могут выгружаться в специальный файл подкачки.

Экран 1: Исследование рамки улья SYSTEM с помощью утилиты DiskProbe

Если бы размеры ульев никогда не увеличивались, Configuration Manager мог бы выполнять все операции на этой находящейся в памяти версии, так, словно улей не отличается от обычного файла. Расположение сота в памяти можно было бы вычислить, просто добавив к начальному адресу улья его индекс. Именно так поступает загрузчик Ntldr с ульем SYSTEM на одном из первых этапов загрузки операционной системы: Ntldr загружает улей SYSTEM целиком в память в режиме только для чтения и находит нужные соты по их смещению и начальному адресу улья. К сожалению, ульи растут с добавлением в них новых ключей и значений. Системе приходится выделять в своем подкачиваемом пуле дополнительные участки памяти для рамок, содержащих новые ключи и значения. Таким образом, данные Registry не обязательно размещаются в памяти все подряд.

Для работы с не непрерывными буферами, в которых хранятся в оперативной памяти ульи, Configuration Manager использует такую же стратегию, как Memory Manager — для установления соответствия адресных пространств виртуальной и физической памяти. На рис. 2. изображена двухуровневая схема вычисления адресов, содержащих сот блока и рамки по ее индексу (то есть ее смещению от начала улья). Напоминаю, что рамка может состоять из одного или более блоков, а наращивание ульев осуществляется целыми рамками, так что рамка всегда размещается в непрерывном буфере в памяти и все блоки одной рамки оказываются размещены в памяти подряд.

Рисунок 2: Схема использования индекса сота

Configuration Manager использует в схеме установления соответствия измененный индекс сота, состоящий из трех логических полей, — подобно тому, как Memory Manager делит на поля виртуальный адрес. Первое из этих полей NT интерпретирует как номер элемента каталога карт сот улья. Всего элементов в ней 1024, и каждый представляет собой ссылку на карту сотов — таблицу из 512 элементов. Второе поле индекса сота — номер элемента карты сот, содержащего адреса рамки и блока сота. Наконец, последнее поле индекса является смещением сота от начала блока. В процессе инициализации улья Configuration Manager динамически создает карты сот в таком количестве, чтобы общее число элементов в них было не меньше числа блоков в улье, а затем, при изменении размеров улья, добавляет или удаляет карты по мере надобности.

Пространство имен Registry и работа с ним

Configuration Manager определяет тип объекта ключевой объект (key object), используемый для интеграции пространства имен Registry с общим пространством имен ядра ОС. Такой объект с именем REGISTRY присоединяется к корню пространства имен NT и служит точкой входа в пространство имен Registry. Утилита Regedit отображает имена ключей в форме HKEY_LOCAL_MACHINE SYSTEMCurrentControlSet, но подсистема Win32 преобразует их в свое пространство имен (в данном примере: RegistryMACHINESYSTEM CurrentControlSet). Встретив ключевой объект с именем Registry в процессе синтаксического разбора имени, NT Object Manager передает его Configuration Manager для дальнейшего анализа по внутренним деревьям имен ульев.

Прежде чем переходить к описанию потока управления в ходе осуществления типичной операции с Registry, я бы хотел еще поговорить о ключевых объектах и блоках управляющих параметров ключа (key control block). Когда приложение обращается с запросом на открытие или создание ключа Registry, Configuration Manager передает ему дескриптор ключевого объекта, размещенного Configuration Manager в памяти с помощью Object Manager. Сервисы Object Manager дают возможность пользоваться преимуществами его механизмов обеспечения безопасности и учета числа активных ссылок на объект.

Кроме того, Configuration Manager создает для каждого открытого ключа Registry блок управляющих параметров, в число которых входят полное имя ключа с путем, индекс соты ключа и флажок, определяющий, следует ли Configuration Manager удалить этот сот после закрытия последнего указывающего на ключ дескриптора. NT хранит все блоки управляющих параметров в виде двоичного дерева, упорядоченного по именам ключей по алфавиту, что ускоряет процедуру поиска по имени ключа. Ключевой объект содержит указатель на блок управляющих параметров ключа. По каждому запросу приложения на доступ к определенному ключу создается новый ключевой объект, но все они указывают на один блок управляющих параметров.

Поток управления при открытии приложением существующего ключа Registry начинается с обращения к функции интерфейса прикладного программирования Registry API, которой передается в качестве параметра имя ключа и которая запускает механизм синтаксического разбора имен Object Manager. Обнаружив в имени ключевой объект Registry, Object Manager передает его Configuration Manager для дальнейшей обработки. Configuration Manager использует хранимые в оперативной памяти структуры данных улья для поиска ключа с указанным именем. При обнаружении сота с подходящим содержимым Configuration Manager производит поиск по дереву блоков управляющих параметров, и если данный ключ уже открыт (тем же или другим приложением), увеличивает счетчик ссылок; если же нет — создает новый блок управляющих параметров и помещает его в дерево. Затем Configuration Manager выделяет ключевой объект, инициализирует его ссылкой на блок управляющих параметров и возвращает управление Object Manager. Object Manager, в свою очередь, возвращает приложению дескриптор.

При поступлении от приложения запроса на создание нового ключа Registry работа идет следующим образом. Configuration Manager отыскивает сот ключа, который станет для создаваемого ключа родительским. Затем проводится поиск свободного сота достаточно большого размера. Если такового не окажется, Configuration Manager создает новую рамку и новый сот внутри нее. (Оставшееся пространство рамки включается в список свободных сотов.) Затем сот заполняется соответствующей информацией — такой как имя ключа, — и Configuration Manager заносит ее в список подключей родительского ключа (находящийся в своем отдельном соте). Наконец, в соответствующее место сота нового ключа записывается индекс сота его родительского ключа.

Configuration Manager использует счетчик ссылок в блоке управляющих параметров для определения момента, когда этот блок можно будет удалить. После освобождения всех дескрипторов ключа, которому принадлежит блок управляющих параметров, счетчик ссылок оказывается равен нулю, что и служит признаком того, что блок больше не нужен. Если к этому моменту флажок удаления оказывается взведен (в результате того, что некоторое приложение обратилось к функции удаления ключа), Configuration Manager удаляет из улья и сам ключ.

Надежное хранение

Чтобы гарантировать возможность восстановления в случае аварии, каждый хранимый на диске улей Registry имеет помимо основной еще и вторую копию (log hive) — в файле с расширением .log. Заглянув в каталог winntsystem32config, вы увидите там файлы system.log, sam.log и некоторые другие с тем же расширением. При инициализации улья Configuration Manager создает битовый массив, каждый бит которого соответствует одному 512-байтовому сектору (sector) улья. Он называется массивом «грязных» секторов, поскольку если бит равен единице, это означает, что соответствующий сектор улья был модифицирован в памяти, и его необходимо записать в дисковую копию улья. (Если же бит равен 0, соответствующий сектор в памяти идентичен дисковому.)

В случае выполнения некоторых операций (вроде создания нового ключа) или внесения изменений в существующий ключ Configuration Manager помечает измененные сектора улья в массиве «грязных» секторов, а затем назначает время отложенной операции записи, или синхронизации копий улья (hive sync). Системный поток отложенной записи просыпается через 5 секунд после получения запроса на синхронизацию и сбрасывает «грязные» сектора всех ульев на диск. Таким образом одномоментно фиксируются все изменения, произведенные в Registry со времени поступления запроса на синхронизацию копий ульев и до начала осуществления этой операции. Очередная синхронизация производится не раньше чем через 5 секунд после предыдущей.

Если бы операция отложенной записи просто сбрасывала все «грязные» сектора в файл улья, то в случае аварии системы во время ее выполнения этот файл оказывался бы поврежденным и не подлежал бы восстановлению. Во избежание такой неприятности сначала массив «грязных» секторов и сами эти сектора сбрасываются в журнальный файл (.log) улья, размеры которого при необходимости могут быть увеличены. Затем увеличивается номер в последовательности обновления в базовом блоке журнальный файл, и только после этого грязные сектора записываются в основной файл улья. В заключение всей работы увеличивается номер в последовательности обновления и в базовом блоке основного файла улья. Таким образом, если во время синхронизации произойдет авария, при последующей перезагрузке Configuration Manager обнаружит несовпадение номеров последовательности обновления в базовых блоках основной и резервной копий улья и завершит синхронизацию с использованием журнального файла. В результате не только дисковая копия улья окажется полностью корректна, но и никакие из последних изменений не будут потеряны.

Для улья SYSTEM как наиболее важного предусмотрена еще одна, дополнительная степень защиты. Заглянув в каталог winntsystem32 config, вы увидите там целых три файла с именем System: System, System.log и System.Alt. Configuration Manager хранит в файле System.Alt пассивное зеркало улья SYSTEM, которое обновляется в процессе синхронизации точно так же, как его основная дисковая копия. Если в процессе загрузки обнаружится, что основная копия улья SYSTEM неисправна, Configuration Manager попытается использовать альтернативную.

Оптимизации Registry

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

Кроме того, Configuration Manager оптимизирует способ хранения в улье имен ключей и значений. Хотя Registry полностью поддерживает кодировку Unicode, имена, содержащие только символы ASCII, хранятся в этой более компактной кодировке. При считывании имени (например, в процессе поиска) выполняется преобразование из ASCII в Unicode. Хранение имен в коде ASCII может обеспечивать значительное сокращение размеров улья.

Наконец, со временем происходит фрагментация ульев Registry. Так, улей, представленный на рис. 1, фрагментирован, поскольку содержит пустое пространство. Configuration Manager никогда не предпринимает попыток уплотнить улей, но в NT есть механизм, позволяющий выполнить эту операцию. Функция RegSaveKey интерфейса прикладного программирования Win32 (она используется утилитой создания диска аварийного восстановления Emergency Repair Disk, ERD) создает резервную копию улья Registry в отдельном файле. Информация переносится в резервный файл без пробелов, то есть фактически осуществляется его уплотнение. Для замены действующей копии улья резервной используется функция RegReplaceKey API-интерфейса Win32, либо другие функции Win32, используемые процедурой восстановления системы с ERD.

Завершение экскурсии

В третьей бета-версии Win2K ни Configuration Manager, ни способ работы с ульями Registry в памяти и на диске не претерпели существенных изменений. Можно даже загрузить с помощью функции Load Hive редактора regedt32 улей, взятый от Win2K, в NT 4.0. В то же время в новой версии ОС появился ряд оптимизаций, существенно сокращающих расход памяти и повышающих скорость работы. Во-первых, в Win2K блоки управляющих параметров ключей содержат только собственно имена ключей, без полных путей до них. Например, блок, относящийся к ключу RegistrySystem Control, будет содержать имя Control. Дальнейшая оптимизация расхода памяти достигается отделением от блока управляющих параметров блока имени ключа. Для ключей с одинаковыми именами создается по блоку управляющих параметров на каждый, но лишь один на всех блок имени ключа. Для повышения скорости поиска Configuration Manager ведет хэш-таблицу имен ключей, для которых созданы блоки управляющих параметров.

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

И наконец, еще один кэш Delayed Close Table используется для хранения недавно закрытых приложениями блоков управляющих параметров. Configuration Manager обычно освобождает такие блоки, но в Win2K они сохраняются в Delayed Close Table на случай, если приложение попытается повторно открыть недавно закрытый блок. Если свободных мест нет, для добавления очередного закрытого блока сначала удаляется блок, находящийся в таблице дольше всех.