Часть вторая.
Внутренняя кухня Windows 2000.
"Ничто не дается даром в этом мире, и приобретение знания - труднейшая из всех задач, с какими человек может столкнуться".
Карлос Кастанеда, "Учение дона Хуана"
Итак: что хорошего предлагает нам Windows 2000? В первую очередь, это, конечно, защищенность данных и отказоустойчивость. Звучит хорошо, но вдумчивого и любопытного читателя должно интересовать, что же стоит за общими словами и заявлениями рекламных представителей "Майкрософт". "Да, масштабируемость да, высоко-производительность да, повышенная надежность... все это мы уже слышали тысячи раз, и тысячи раз потом снимали лапшу с ушей и имели на деле то, что имели... - может сказать он. - А что же по существу?" - и будет совершенно прав. Голые слова, не подкрепленные фактами, так и останутся бесполезным сотрясанием воздуха. По существу же, мы имеем операционную систему, в которой разработчики, по крайней мере, попытались применить на практике очень разумные идеи, повышающие стабильность системы и ее помехозащищенность. Данные и код операционной системы изолированы от пользовательских приложений целым рядом специальных методов. Для предотвращения доступа приложений к критически важным данным операционной системы и устранения риска их модификации Windows 2000 использует два режима доступа к процессору: пользовательский и режим ядра. Стоит заметить, что хотя на самом деле в архитектуре процессоров x86 предоставлено четыре уровня привилегий (так называемых колец), на которых могут работать процессоры, "Майкрософт" поступила, как всегда, достаточно оригинально, и решила использовать только два из них: нулевой уровень для исполнения в режиме ядра и третий - для пользовательского режима. С другой стороны, разработчиков тоже можно понять: мечта о мировом господстве на рынке операционных систем не дает им покоя, поэтому NT разрабатывалась так, чтобы работать не только на процессорах фирмы Intel, но также, например, на Digital Alpha или IBM PowerPC, которые могли иметь только два уровня привилегий. Как уже упоминалось, Windows 2000 основана на концепции микроядра (microkernel), а значит, разработчики ОС постарались минимизировать объем кода, исполняемый в режиме супервизора, то есть в режиме, при котором код обладает возможностью прямого доступа ко всему аппаратному обеспечению и ко всей памяти. Естественно, что в таком случае только данный код может управлять работой всех устройств и предоставлять программам базовые сервисы, такие, как, например, выделение (или отказ в выделении) памяти, доступ к дисковой подсистеме и т. д. От работоспособности микроядра полностью зависит работоспособность компьютера в целом, поэтому к нему предъявляются достаточно высокие требования:
- высокая надежность,
- высокая производительность,
- небольшой объем кода (это особенно актуально для ОС реального времени).
Все эти показатели, за исключением, быть может, объема кода, у Win2000, на наш взгляд, находятся на достаточно хорошем уровне, хотя мнения могут быть разными, так как оценка, по сути, является в большой мере субъективной.
Концепция микроядра подразумевает, что фактически все компоненты операционной системы, за исключением небольшого базового программного модуля (который и является микроядром), работают в пользовательском режиме на равных условиях со всеми остальными программами, работающими в системе. Очевидно, что отказоустойчивость всей системы при таком подходе возрастает, так как сбой одного из компонентов приводит к минимальному ущербу, при этом всегда остается возможность этот компонент перезапустить. Грубо говоря, если только ядро осталось после сбоя "живым", нажатие Ctrl+Alt+Del приведет к появлению окна "Безопасность Windows", с помощью функций которого можно решить большинство проблем, причем это произойдет немедленно, так как соответствующий поток процесса ядра при обычных условиях имеет наивысший приоритет. Если же вы обладаете правами администратора, у вас есть очень простой способ "подвесить" систему: нужно всего-то написать программу, которая будет к некоторому числу добавлять по единичке, пока не будет достигнуто значение, скажем, 1045 (те, кто хоть раз занимался программированием, меня поймут).
После чего эта программа запускается, и ей ручками присваивается приоритет 31 - REALTIME_PRIORITY_CLASS и любой текущий исполняемый поток, имеющий, соответственно, меньший приоритет, прерывается до истечения отведенного ему кванта времени, а код и данные операционной системы блокируются. Все очень просто, но если уж компьютер завис, то винить надо будет себя, а не "Майкрософт". Кстати, на этом примере видно, что нужно быть очень внимательным при установке высоких приоритетов и режима реального времени программе - если уж она "зависнет", то наступит крах системы.
Но вернемся к микроядру. Кроме всего прочего, система, основанная на концепции микроядра, позволяет производить более гибкое конфигурирование, так как любой из компонентов ОС можно легко заменить на другой, поддерживающий точно такой же программный интерфейс. На самом деле, для увеличения производительности Windows 2000 использует некий гибрид микроядра и монолитного дизайна, так как некоторые ее базовые компоненты, например, диспетчер процессов и диспетчер виртуальной памяти, все-таки функционируют в режиме ядра - это позволяет им взаимодействовать между собой не через медленные механизмы межпроцессорной передачи данных (передачи данных между несколькими программными процессами), а используя общую память. Вторая причина включения дополнительного кода в режим ядра кроется в том, что процессор, занятый выполнением некоторой задачи, реализуемой соответствующим процессом (или, как говорят, работающий в определенном контексте), работает с текущими значениями процессорных регистров, а также указателей на используемую память (стек, данные, исполняемый код). Эти значения различны для различных потоков, поэтому, прежде чем перейти на выполнение другой программы, реализуемой другим процессом, процессор должен сохранить текущий контекст, а затем загрузить контекст нового процесса, на выполнение которого он переключается. Данная процедура называется переключением контекста и может занимать существенное время. Использование гибридного дизайна позволяет снизить количество переключений контекста. Хотя, теоретически и это несколько, снижает надежность системы, все равно она оказывается не в пример больше, чем у windows 9x. За счет полностью реализованного режима виртуальной машины микроядра обеспечиваются многие дополнительные возможности, недоступные, скажем, Windows 98. Это, в первую очередь, обеспечение приложениям индивидуального адресного пространства для каждого процесса и предоставление уровня защищенности не хуже, чем по стандарту C-2/Е-3, здесь ключевую роль играет то, что Windows 2000 отказывает процессам, обращающимся к оборудованию и памяти, которые им не принадлежат. Достигается это достаточно простыми методами, например, каждая страница в виртуальной памяти помечается тэгом, определяющим, в каком режиме должен работать процессор для чтения или записи данной страницы памяти. Таким образом, страницы системной памяти оказываются недоступными для всех процессов, не работающих в режиме ядра. При попытке выполнения любой запрещенной операции центральный процессор автоматически генерирует исключение, и управление передается микроядру NT. Микроядро определяет, разрешено вызвавшей исключение программе выполнять такую операцию, или нет. Если операция признана несанкционированной, управление передается программе Dr.Watson, которая оповещает о возникшей ошибке пользователя, а система в этот момент быстренько завершает работу приложения, попытавшегося выполнить незаконное с точки зрения ОС действие. Естественно, подобный алгоритм работы - это не особая и сверхгениальная разработка талантливых программистов из "Майкрософт", а хорошая реализация тех аппаратных возможностей "железа", которые появились в современных микропроцессорах.
Для того чтобы обратиться к оборудованию или памяти, а также совершить некоторые другие операции, недоступные в пользовательском режиме, приложения (а также многие подсистемы самой Windows 2000, которые не работают в режиме ядра) обращаются к системным вызовам микроядра ОС, которые также называют естественным API. В состав этого API входит более 250 функций, обращение к которым осуществляется при помощи системных вызовов, основанных на программных исключениях. Все вызовы API обслуживаются как системными службами NT, так и модулем NT Executive - "сердцем" операционной системы, над которым так дорожит и трясется "Майкрософт".
Модуль NT Executive представляет собой несколько программных потоков, которые закоренелые программисты-системщики почему-то предпочитают называть рабочими потоками естественно, все эти потоки выполняются в режиме ядра. Код практически всех подсистем этого модуля находится в файле ntoskrnl.exe, кроме, разве что, подсистемы win32, код которой расположен в файле win32k.sys, и уровня абстракции оборудования HAL (Hardware Abstraction Layer), который содержится в файле halxxxxx.dll, где xxxxx зависит от типа используемого компьютера. NT Executive сосредотачивает все самые важные части ОС:
- Ядро - это основная часть Windows 2000, в реализации которой кроется вся мощность и красота платформы NT. Прежде всего, ядро отвечает за выделение памяти для приложений и распределение процессорного времени, то есть, фактически, за реализацию многозадачности. Для этого в состав ядра входит так называемый планировщик потоков (threads scheduler), который назначает каждому из потоков один из 32 уровней приоритета. Уровень 0 зарезервирован для системы. Уровни от 1 до 15 назначаются исполняемым программам, а уровни от 16 до 31 могут назначаться только администраторами. Планировщик делит все процессорное время на кванты фиксированного размера (как менять размер кванта, еще будет рассказано в следующих частях нашей статьи), при этом каждый программный поток выполняется только в течение отведенного ему времени, и если к окончанию кванта он не освобождает процессор, планировщик в принудительном порядке приостанавливает этот поток и меняет контекст процессора, настраивая его на выполнение другого потока, обладающего тем же приоритетом. Ядро также осуществляет всю работу, связанную с обработкой программных и аппаратных прерываний.
- Уровень абстракции оборудования (HAL) - это прослойка между операционной системой и аппаратурой компьютера. HAL имеет свой собственный API, предназначенный для транслирования обращений всех приложений и драйверов в конкретные команды процессора, учитывающие его внутреннюю структуру и нюансы работы. HAL и ядро написаны на языке низкого уровня, в то время как остальные компоненты ОС реализованы на языке C и C++, поэтому именно они отвечают за переносимость NT на системы с другой архитектурой. На данный момент Windows 2000 есть в версиях как для процессоров PC совместимых компьютеров (Pentium и старше), так и для нового перспективного семейства 64-битных процессоров EPIC (Itanium, Itanium-2).
- Диспетчер ввода-вывода (I/O Manager) - интегрирует добавляемые в систему драйверы устройств в операционную систему Windows 2000.
- Диспетчер объектов - служит для управления всеми разделяемыми ресурсами компьютера. В момент обращения приложения или службы к какому-либо ресурсу, диспетчер объектов сопоставляет этому ресурсу объект и отдает приложению дескриптор этого объекта. Используя дескриптор, приложение взаимодействует с объектом, совершая в его отношении различные операции. Монитор системы безопасности следит при этом за тем, чтобы с объектом выполнялись только разрешенные действия.
- Диспетчер процессов - предоставляет интерфейс, при помощи которого другие компоненты Windows NT Executive, а также приложения пользовательского режима могут манипулировать процессами и потоками. Во время работы диспетчер процессов сопоставляет каждому процессу и потоку идентификатор процесса (PID - Process Identifier) и потока (TID - Thread Identifier) соответственно, а также карту адресов и таблицу дескрипторов.
- Диспетчер виртуальной памяти - служит для управления и организации подсистемы памяти, позволяет создавать карты адресов для процессов и следит за корректностью использования адресного пространства приложениями (то есть, следит за общим доступом к памяти и осуществляет защиту страниц в режиме копирования при записи). Диспетчер виртуальной памяти также обеспечивает возможность отображения файлов на память, используемую для загрузки в оперативную память исполняемых файлов и файлов динамических библиотек. Еще в Windows NT4.0 существовала так называемая Защищенная Модель памяти, которая (в отличие от DOS-подобных ОС) позволяла изолировать каждый процесс друг от друга и, что самое главное, от кода самой ОС. Диспетчер виртуальной памяти представляет физическую память для пользовательских приложений таким образом, что каждому процессу выделяются отдельные 4Гб виртуального адресного пространства, из которых младшие 2Гб используются непосредственно процессом по своему усмотрению, а старшие 2Гб - отводятся под нужды системы, причем они - общие для всех процессов. При этом каждый процесс "думает", что он - единственный, запущенный в системе. Но здесь кроется некоторое затруднение: многие 16-разрядные программы при своей работе обмениваются данными между собой напрямую, используя одни и те же участки памяти, а это неприемлемо для Windows NT. В этом случае такие программы все же запускаются и предоставляют разделяемую память, где они могут обмениваться данными. Эта память является нестраничной областью физической памяти, которая может быть спроецирована на виртуальное адресное пространство нескольких процессов, и таким образом они смогут взаимодействовать друг с другом. Как мы видим, самое главное, чем занимается диспетчер виртуальной памяти - это видно из его названия - организация логической памяти, размер которой больше размера физической, установленной на компьютере. Этот трюк достигается благодаря тому, что страницы памяти, к которым долго не было обращений и которые не имеют атрибута неперемещаемых, сбрасываются диспетчером в файл pagefile.sys на винчестер и удаляются из оперативной памяти, освобождая ее для других приложений. В момент, когда происходит обращение к данным, находящимся в перемещенной на винчестер странице, диспетчер виртуальной памяти незаметно для приложения копирует страницу обратно в оперативную память, и только затем обеспечивает доступ к ней. Этот механизм обеспечивает выделение дополнительной памяти программам, которые нуждаются в ней, и при этом следит за тем, чтобы все работающие в системе программы обладали достаточным объемом физической памяти для того, чтобы продолжать функционирование.
- Диспетчер кэша - применение кэшированного чтения и записи позволяет существенно ускорить работу таких устройств, как винчестеры и CD-ромы. При этом наиболее востребованные файлы дублируются диспетчером кэша в оперативной памяти компьютера, и обращение к ним обслуживается с использованием этой копии, а не оригинала, расположенного на сравнительно медленном долговременном носителе. Кэш в Windows 2000 является единым для всех логических дисков, вне зависимости от используемой файловой системы, при этом теперь используется файлово-ориентированный, а не блочно-ориентированный алгоритм работы. Кроме того, он является динамическим, а это значит, что диспетчер управляет его размерами в зависимости от доступного объема свободной физической памяти в каждый конкретный момент.
- Win32 User и GDI - выполняют все функции, связанные с пересылкой системных сообщений и отображением информации на экране. До Windows NT 4 эти серверные компоненты функционировали в пользовательском режиме в качестве одной из составных частей подсистемы Win32.
- Другие (монитор обращений к системе безопасности, механизм локального вызова процедур, диспетчер конфигурации, диспетчер PnP, диспетчер электропитания, аппаратные драйверы и т. д.).
Остальные компоненты операционной системы выполняются в пользовательском режиме, часто их называют подсистемами. Например, в состав Windows 2000 входит подсистема безопасности и подсистемы, предоставляющие программам функции таких распространенных операционных сред, как DOS, OS/2, Win16, POSIX, а также Win32. Основное назначение операционной среды - предоставления интерфейса, а также эмуляция работы операционных систем, отличающихся от Windows 2000. Проще говоря, операционная среда добавляет в систему Windows 2000 дополнительные возможности и новые системные вызовы, отличающиеся от вызовов естественного API-ядра. Подсистемы операционных сред загружаются только в случае, если возникнет необходимость их использования. Каждая подсистема выполняется как отдельный процесс пользовательского режима. Таким образом, каждая подсистема защищена от сбоев, вызванных другими подсистемами, именно благодаря этому платформа Windows 2000 функционирует более стабильно, чем операционные системы линейки Windows 9x. Что кажется наиболее удивительным, Win32 API не является родным для Windows 2000, а реализован так же, как и, скажем, поддержка DOS API. Это замечание, однако, не совсем верно: естественный API очень похож на Win32 API, тем не менее, с ним несколько сложнее работать (между некоторыми функциями Win32 и некоторыми функциями естественного API может существовать взаимнооднозначное соответствие, однако часть функций Win32 для выполнения своей задачи обращаются к нескольким функциям естественного API-ядра), кроме того, он не документирован. Поэтому, хоть приложению никто и ничто не помешает действовать в обход интерфейсов, предоставляемых операционными средами, и обращаться к естественному API напрямую, компания "Майкрософт" крайне не рекомендует использовать такую возможность.
Понимания всего выше сказанного, на наш взгляд, вполне достаточно для того, чтобы начинать изучать модель безопасности NT, обсуждению которой мы посвятим следующую часть нашей статьи.