Добавлен: 29.10.2018
Просмотров: 48013
Скачиваний: 190
11.3. Структура системы
981
вательского режима, из которых для Windows особенно важны три типа: подсистемы
среды, DLL и процессы служб.
Мы уже описали модель подсистем Windows; не будем вдаваться в дальнейшие под-
робности — только упомянем, что в исходном проекте NT подсистемы рассматривались
как способ поддержки «персонажей» нескольких операционных систем через единое
базовое программное обеспечение (работающее в режиме ядра). Возможно, это была
попытка избежать соревнования операционных систем за одну и ту же платформу
(как это происходило между VMS и Berkeley UNIX на компьютерах VAX компании
DEC). Или, возможно, это происходило потому, что в компании Microsoft никто не
знал, станет ли OS/2 успешной в качестве интерфейса программирования (и поэтому
они хеджировали свои риски). В любом случае OS/2 утратила значение, а последо-
вавший за ней Win32 API (который был разработан для совместного использования
с Windows 95) стал доминирующим.
Второй ключевой аспект проекта пользовательского режима Windows — это динами-
чески связываемые библиотеки (Dynamic Link Library (DLL)), являющиеся кодом,
который связывается с исполняемыми программами во время выполнения (а не
во время компиляции). Совместно используемые библиотеки не являются новой
концепцией, они используются большинством современных операционных систем.
В Windows почти все библиотеки — это DLL, начиная с системной библиотеки ntdll.dll
(которая загружается в каждый процесс) и заканчивая библиотеками высокого уровня
(с обычными функциями), которые предназначены для того, чтобы сделать возможным
интенсивное повторное использование кода разработчиками приложений.
DLL повышают эффективность системы (позволяя процессам совместно использовать
код), снижают время загрузки программ с диска (часто используемый код хранится
в памяти), увеличивают удобство эксплуатации системы (позволяя обновлять библио-
течный код операционной системы без необходимости перекомпиляции или повторной
сборки всех тех приложений, которые его используют).
В то же время совместно используемые библиотеки приносят проблему версий и уве-
личивают сложность системы, поскольку внесенные в совместно используемую библи-
отеку изменения, предназначенные для одной конкретной программы, могут выявить
латентные ошибки других приложений или вовсе нарушить их работу (из-за изменений
в реализации), — эта проблема в мире Windows называется адом DLL (DLL hell).
Концепция реализации DLL проста. Вместо того чтобы компилятор выдавал код, ко-
торый будет напрямую вызывать процедуры из того же исполняемого образа, вводится
уровень косвенного обращения — таблица IAT (Import Address Table — таблица адресов
импорта). Когда загружается исполняемый модуль, в нем производится поиск списка
тех DLL, которые также должны загружаться (на самом деле это целый граф, потому
что в этих DLL есть список других DLL, нужных для их работы). Необходимые DLL
загружаются, и IAT заполняется для всех них.
Действительность более сложна. Есть еще одна проблема: представляющий связи
между DLL граф может иметь циклы или демонстрировать недетерминированное
поведение, так что составление списка DLL для загрузки может оказаться невыпол-
нимым. Кроме того, в Windows библиотеки DLL имеют возможность выполнить код
либо при загрузке в процесс, либо при создании нового потока. Вообще-то это сделано
для того, чтобы они могли выполнить инициализацию или выделить область хранения
для потока, но многие DLL в этих процедурах attach выполняют большое количество
982
Глава 11. Изучение конкретных примеров: Windows 8
вычислений. Если какой-то из вызываемых в процедуре attach функций нужно изучить
список загруженных DLL, то может получиться взаимоблокировка, которая подвесит
процесс.
DLL применяются не только для совместного использования кода. Они реализуют
модель хостинга (hosting) для расширения приложений. Internet Explorer может ска-
чивать и подключаться к DLL, которые называются элементами управления ActiveX
(ActiveX controls). На другом конце Интернета веб-серверы также подгружают дина-
мический код, чтобы добиться наилучшего отображения страниц. Приложения типа
Microsoft Office связываются с DLL и выполняют их код, чтобы Office мог использо-
ваться как платформа для создания других приложений. Программирование в стиле
СОМ
(Сomponent Оbject Model — модель компонентных объектов) позволяет про-
граммам динамически находить и загружать код, который создан для предоставления
конкретного опубликованного интерфейса, что приводит к хостингу (размещению)
DLL внутри процессов почти во всех приложениях, которые используют СОМ.
Вся эта динамическая загрузка кода приводит к еще большему усложнению операци-
онной системы, поскольку управление версиями библиотек — это не просто сопостав-
ление исполняемых модулей и правильных версий DLL, иногда приходится загружать
в процесс несколько версий одной и той же DLL (компания Microsoft называет это
загрузкой «бок о бок» — side-by-side). Одна программа может размещать две разные
динамические библиотеки, причем они обе могут пожелать загрузить одну и ту же
библиотеку Windows (и потребовать свою конкретную версию этой библиотеки).
Более удачным решением было бы размещение кода в отдельном процессе. Однако
размещение кода вне процесса приводит к снижению производительности и во многих
случаях усложняет модель программирования. Компании Microsoft еще предстоит раз-
работать хорошее решение для всех этих сложностей пользовательского режима. Они
заставляют тосковать по относительной простоте режима ядра.
Одна из причин того, что режим ядра проще, чем пользовательский режим, состоит
в том, что он поддерживает относительно небольшое количество возможностей рас-
ширения помимо модели драйверов устройств. В Windows функциональность системы
расширяется за счет служб пользовательского режима. Это решение хорошо работало
для подсистем, и еще лучше оно работает тогда, когда реализуется всего несколько
новых служб, а не полный «персонаж» операционной системы. Между службами,
реализованными в процессах ядра и процессах пользовательского режима, имеется
относительно немного различий. И ядро и процесс предоставляют частные адресные
пространства, где структуры данных могут быть защищены и запросы служб могут
отслеживаться.
Однако службы в процессах ядра и службы в пользовательских процессах могут суще-
ственно различаться по производительности. На современном оборудовании переход
из режима ядра в пользовательский режим — процесс медленный, но он все же быстрее,
чем два таких перехода (когда вам нужно переключиться в другой процесс и обратно).
Кроме того, межпроцессный обмен имеет меньшую пропускную способность.
Код режима ядра может (с большой осторожностью) обращаться к данным по адресам
пользовательского режима, передаваемым как параметры системных вызовов. Для
служб пользовательского режима либо эти данные должны копироваться в процесс
службы, либо нужно играть с отображением памяти туда и обратно (в Windows это
делается при помощи средств ALPC).
11.4. Процессы и потоки в Windows
983
Возможно, в будущем аппаратные издержки переходов между адресными простран-
ствами и режимами защиты будут снижены (или даже станут несущественными).
Проект Singularity подразделения Microsoft Research (Fandrich et al., 2006) использу-
ет технологии времени выполнения (подобные используемым в C# и Java) для того,
чтобы сделать защиту полностью программным вопросом. Не требуется никакого
аппаратного переключения между адресными пространствами или режимами защиты.
Windows интенсивно использует процессы служб пользовательского режима для рас-
ширения функциональности системы. Некоторые из этих служб строго привязаны
к работе компонентов режима ядра. Таков lsass.exe, являющийся локальной службой
аутентификации, управляющей объектами маркеров (которые представляют собой
идентификацию пользователя), а также управляющий используемыми файловой
системой ключами шифрования. Диспетчер Plug-and-Play пользовательского режима
отвечает за определение правильного драйвера (который нужно использовать при
обнаружении нового аппаратного устройства), его инсталляцию и выдачу ядру указа-
ния о его загрузке. Многие средства сторонних разработчиков (такие, как антивирусы
и средства управления цифровыми правами) реализованы как комбинация драйверов
режима ядра и служб пользовательского режима.
В Windows диспетчер задач taskmgr.exe имеет вкладку, в которой указаны работающие
в системе службы. В одном процессе (svchost.exe) можно увидеть несколько работа-
ющих служб. Windows делает так для многих служб этапа загрузки (для уменьшения
времени запуска системы). Службы можно комбинировать в одном процессе, если они
могут безопасно работать с одинаковыми атрибутами системы безопасности.
Индивидуальные службы внутри любого совместно используемого процесса службы
загружаются как DLL. Они обычно совместно используют пул потоков Win32, так что
для всех резидентных служб требуется минимальное количество потоков.
Службы являются частыми источниками уязвимостей в системе, поскольку к ним
можно получить удаленный доступ (это зависит от настроек сетевого экрана TCP/IP
и настроек IP Security), и не все пишущие службы программисты достаточно осторож-
ны (они не всегда проверяют передаваемые через RPC параметры и буферы).
Количество постоянно работающих в Windows служб просто потрясающее. Причем
лишь немногие из этих служб вообще получают запросы (хотя если они их получают, то
это, скорее всего, атака с попыткой использовать уязвимость). В результате все больше
служб Windows по умолчанию выключается (особенно в версиях Windows Server).
11.4. Процессы и потоки в Windows
Для управления процессором и группировки ресурсов Windows имеет несколько кон-
цепций. В следующих разделах мы их изучим и обсудим некоторые из соответствую-
щих вызовов Win32 API, а также покажем, как они реализованы.
11.4.1. Фундаментальные концепции
В Windows процессы являются контейнерами для программ. Они содержат вирту-
альное адресное пространство, описатели объектов режима ядра, а также потоки. Как
контейнеры для потоков они содержат также общие ресурсы, используемые для вы-
984
Глава 11. Изучение конкретных примеров: Windows 8
полнения потоков, такие как указатель на структуру квоты, совместно используемый
объект маркера, а также параметры по умолчанию (используемые для инициализации
потоков), включая приоритет и класс планирования. Каждый процесс имеет системные
данные пользовательского режима, называемые PEB (Process Environment Block —
блок среды процесса). РЕВ включает список загруженных модулей (EXE и DLL), об-
ласть памяти со строками окружения, текущий рабочий каталог, а также данные для
управления кучами процесса (и множество разнообразного хлама из Win32, который
накопился со временем).
Потоки — это абстракции ядра для планирования процессора в Windows. Каждому по-
току присваивается приоритет (в зависимости от значения приоритета его процесса).
Потоки могут быть аффинизированными (affinitized), чтобы они выполнялись только
на определенных процессорах. Это помогает параллельным программам, работающим
на многоядерном микропроцессоре или нескольких процессорах, распределять на-
грузку явным образом. Каждый поток имеет два отдельных стека вызовов: один для
выполнения в пользовательском режиме, другой для режима ядра. Есть также блок
TEB
(Thread Environment Block — блок среды потока), который хранит специфичные
для потока данные пользовательского режима, в том числе области хранения для по-
тока
(Thread Local Storage) и поля для Win32, локализации языка и культуры, а также
прочие специальные поля, которые были добавлены различными средствами.
Помимо РЕВ и ТЕВ существует еще одна структура данных, которую режим ядра ис-
пользует совместно со всеми процессами, — совместно используемые данные поль-
зователя
(user shared data). Это страница, в которую ядро может вести запись, а про-
цессы пользовательского режима могут из нее только читать. Она содержит некоторые
поддерживаемые ядром значения, такие как различные формы времени, информация
о версиях, количество физической памяти, а также большое количество флагов (со-
вместно используемых различными компонентами пользовательского режима, такими
как СОМ, службы терминалов, отладчики). Эта совместно используемая страница
применяется исключительно для оптимизации производительности, поскольку все
эти значения можно получить и при помощи системного вызова в режим ядра. Однако
системные вызовы гораздо более дорогие, чем простое обращение к памяти, поэтому
для некоторых поддерживаемых системой полей (таких, как время) использовать эту
страницу очень разумно. Другие поля (такие, как текущий часовой пояс) меняются
редко (исключение — компьютеры на борту самолета), однако использующий их код
должен часто запрашивать эти поля, чтобы определить, не изменились ли они. И это
все работает, как бы уродливо оно ни выглядело, наряду со многими другими изъянами,
снижающими производительность.
Процессы
Процессы создаются из объектов сегментов, каждый из которых описывает объект
памяти, основанный на дисковом файле. При создании процесса создающий процесс
получает описатель, который позволяет ему модифицировать новый процесс через
отображение сегментов, выделение виртуальной памяти, запись параметров и данных
окружения, дублирование дескрипторов файлов в свою таблицу описателей, создание
потоков. Это отличается от того, как процессы создаются в системах UNIX, и демон-
стирует разницу между UNIX и Windows.
UNIX была спроектирована для 16-битных однопроцессорных систем, которые приме-
няли подкачку для совместного использования памяти процессами. В таких системах
11.4. Процессы и потоки в Windows
985
использование процесса как единицы параллельности и операции fork для создания
процессов было просто блестящей идеей. Для выполнения нового процесса в небольшом
количестве памяти (при отсутствии аппаратных средств виртуальной памяти) при-
ходилось процессы из памяти выгружать на диск. Первоначально fork в системе UNIX
была реализована при помощи простой выгрузки родительского процесса и передачи
его физической памяти дочернему процессу. Эта операция была почти «бесплатной».
В отличие от тех времен, на момент написания командой Катлера системы NT обычной
аппаратной средой были 32-битные многопроцессорные системы с аппаратной вирту-
альной памятью, которая использовала от 1 до 16 Мбайт физической памяти. Наличие
нескольких процессоров позволяет выполнять части программ одновременно, поэто-
му NT применяла процессы как контейнеры для совместного использования памяти
и ресурсов объектов, а потоки — как единицу параллельности (для планирования).
Конечно, те системы, которые появятся в течение нескольких последующих лет, не
будут похожи ни на одну из этих двух целевых систем. У них будет 64-битное адрес-
ное пространство с десятками (или сотнями) процессорных ядер и десятки или сотни
гигабайт физической памяти. К тому же эта память может радикально отличаться от
нынешней оперативной памяти. Сегодня оперативная память теряет свое содержимое
при отключении электропитания, но память, основанная на фазовых изменениях, по-
явление которой уже ожидается, сохраняет свои значения (подбно дискам) даже после
отключения электропитания. Также ожидается, что жесткие диски будут заменены
устройствами флеш-памяти и другими энергонезависимыми системами хранения,
будет более широкой поддержка виртуализации, всеобъемлющая сетевая поддержка,
а также поддержка инноваций в области синхронизации (наподобие транзакционной
памяти). Windows и UNIX будут продолжать приспосабливаться к новым аппаратным
средствам, но самое интересное — наблюдать за тем, какие новые операционные систе-
мы разрабатываются специально для использующих эти достижения систем.
Задания и волокна
Windows может объединять процессы в задания, группирующие процессы, чтобы
ограничить содержащиеся в них потоки, — использовать совместное квотирование
ресурсов или маркер ограниченного доступа (restricted token), который не позволяет
потокам обращаться ко многим системным объектам. Самым важным свойством за-
даний (в плане управления ресурсами) является то, что с того момента, как процесс
оказался в задании, все созданные (в этих процессах) потоками процессы также будут
находиться в этом задании. Выхода нет. В полном соответствии со своим названием за-
дания были предназначены для таких ситуаций, которые скорее напоминали пакетную
обработку заданий, чем обычные интерактивные вычисления.
Процесс может находиться внутри только одного задания (максимум). Это разумно,
поскольку трудно определить, как можно ограничить процесс несколькими квотами
или маркерами ограниченного доступа. Однако это означает, что если несколько служб
в системе попытаются использовать задания для управления процессами, то получатся
конфликты (если они попытаются управлять одними и теми же процессами). Напри-
мер, административный инструмент, ограничивающий использование ресурсов (за счет
размещения процессов в заданиях), будет сбит с толку, если процесс сначала вставит
себя в свое собственное задание (или если инструмент безопасности уже поместил
процесс в задание с маркером ограниченного доступа — для ограничения его доступа
к системным объектам). В результате задания в Windows применяются редко.