- Как собрать ядро windows
- Руководство по созданию ядра для x86-системы. Часть 1. Просто ядро
- Как загружается x86-система?
- Что нам нужно?
- Задаём точку входа на ассемблере
- Ядро на Си
- Связующая часть
- Grub и Multiboot
- Строим ядро
- Настраиваем grub и запускаем ядро
- Ядро Windows
- Объекты ядра.
- Эксперимент: просмотр KPCR и KPRCB.
Как собрать ядро windows
Доброго времени суток.
Странно что мало кто интересовался сборкой собственного ядра под Windows.
Кажется мало кто вчитывался в документацию к среде PE и дополнительных сред,совместными силами попробуем разобраться в возможности сборки собственного ядра системы Windows.
Структура операционной системы
Перед изучением структуры операционных систем следует рассмотреть режимы работы процессоров.
Современные процессоры имеют минимум два режима работы – привилегированный (supervisor mode) и пользовательский (user mode).
Отличие между ними заключается в том, что в пользовательском режиме недоступны команды процессора, связанные с управлением аппаратным обеспечением, защитой оперативной памяти, переключением режимов работы процессора. В привилегированном режиме процессор может выполнять все возможные команды.
Приложения, выполняемые в пользовательском режиме, не могут напрямую обращаться к адресным пространствам друг друга – только посредством системных вызовов.
Все компоненты операционной системы можно разделить на две группы – работающие в привилегированном режиме и работающие в пользовательском режиме, причем состав этих групп меняется от системы к системе.
Основным компонентом операционной системы является ядро (kernel). Функции ядра могут существенно отличаться в разных системах; но во всех системах ядро работает в привилегированном режиме (который часто называется режим ядра, kernel mode).
Термин «ядро» также используется в разных смыслах. Например, в Windows термин «ядро» (NTOS kernel) обозначает совокупность двух компонентов – исполнительной системы (executive layer) и собственно ядра (kernel layer) .
Существует два основных вида ядер – монолитные ядра (monolithic kernel) и микроядра (microkernel). В монолитном ядре реализуются все основные функции операционной системы, и оно является, по сути, единой программой, представляющей собой совокупность процедур. В микроядре остается лишь минимум функций, который должен быть реализован в привилегированном режиме: планирование потоков, обработка прерываний, межпроцессное взаимодействие. Остальные функции операционной системы по управлению приложениями, памятью, безопасностью и пр. реализуются в виде отдельных модулей в пользовательском режиме.
Ядра, которые занимают промежуточные положение между монолитными и микроядрами, называют гибридными (hybrid kernel).
Примеры различных типов ядер:
монолитное ядро – MS-DOS, Linux, FreeBSD;
микроядро – Mach, Symbian, MINIX 3;
гибридное ядро – NetWare, BeOS, Syllable.
Добавлено через 58 минут 42 секунды
Несколько примеров можно взять тут http://www.intuit.ru/en/studies/cour. e/16572?page=1
Добавлено через 1 час 2 минуты 49 секунд
Документация по wrk Оффтоп
Руководство по созданию ядра для x86-системы. Часть 1. Просто ядро
Давайте напишем простое ядро, которое можно загрузить при помощи бутлоадера GRUB x86-системы. Это ядро будет отображать сообщение на экране и ждать.
Как загружается x86-система?
Прежде чем мы начнём писать ядро, давайте разберёмся, как система загружается и передаёт управление ядру.
В большей части регистров процессора при запуске уже находятся определённые значения. Регистр, указывающий на адрес инструкций (Instruction Pointer, EIP), хранит в себе адрес памяти, по которому лежит исполняемая процессором инструкция. EIP по умолчанию равен 0xFFFFFFF0. Таким образом, x86-процессоры на аппаратном уровне начинают работу с адреса 0xFFFFFFF0. На самом деле это — последние 16 байт 32-битного адресного пространства. Этот адрес называется вектором перезагрузки (reset vector).
Теперь карта памяти чипсета гарантирует, что 0xFFFFFFF0 принадлежит определённой части BIOS, не RAM. В это время BIOS копирует себя в RAM для более быстрого доступа. Адрес 0xFFFFFFF0 будет содержать лишь инструкцию перехода на адрес в памяти, где хранится копия BIOS.
Так начинается исполнение кода BIOS. Сперва BIOS ищет устройство, с которого можно загрузиться, в предустановленном порядке. Ищется магическое число, определяющее, является ли устройство загрузочным (511-ый и 512-ый байты первого сектора должны равняться 0xAA55).
Когда BIOS находит загрузочное устройство, она копирует содержимое первого сектора устройства в RAM, начиная с физического адреса 0x7c00; затем переходит на адрес и исполняет загруженный код. Этот код называется бутлоадером.
Бутлоадер загружает ядро по физическому адресу 0x100000. Этот адрес используется как стартовый во всех больших ядрах на x86-системах.
Все x86-процессоры начинают работу в простом 16-битном режиме, называющимся реальным режимом. Бутлоадер GRUB переключает режим в 32-битный защищённый режим, устанавливая нижний бит регистра CR0 в 1. Таким образом, ядро загружается в 32-битном защищённом режиме.
Заметьте, что в случае с ядром Linux GRUB видит протоколы загрузки Linux и загружает ядро в реальном режиме. Ядро самостоятельно переключается в защищённый режим.
Что нам нужно?
- x86-компьютер;
- Linux;
- ассемблер NASM;
- gcc;
- ld (GNU Linker);
- grub;
Исходники можно найти на GitHub.
Задаём точку входа на ассемблере
Как бы не хотелось ограничиться одним Си, что-то придётся писать на ассемблере. Мы напишем на нём небольшой файл, который будет служить исходной точкой для нашего ядра. Всё, что он будет делать — вызывать внешнюю функцию, написанную на Си, и останавливать поток программы.
Как же нам сделать так, чтобы этот код обязательно был именно исходной точкой?
Мы будем использовать скрипт-линковщик, который соединяет объектные файлы для создания конечного исполняемого файла. В этом скрипте мы явно укажем, что хотим загрузить данные по адресу 0x100000.
Вот код на ассемблере:
Первая инструкция, bits 32 , не является x86-ассемблерной инструкцией. Это директива ассемблеру NASM, задающая генерацию кода для процессора, работающего в 32-битном режиме. В нашем случае это не обязательно, но вообще полезно.
Со второй строки начинается секция с кодом.
global — это ещё одна директива NASM, делающая символы исходного кода глобальными. Таким образом, линковщик знает, где находится символ start — наша точка входа.
kmain — это функция, которая будет определена в файле kernel.c . extern значит, что функция объявлена где-то в другом месте.
Затем идёт функция start , вызывающая функцию kmain и останавливающая процессор инструкцией hlt . Именно поэтому мы заранее отключаем прерывания инструкцией cli .
В идеале нам нужно выделить немного памяти и указать на неё указателем стека ( esp ). Однако, похоже, что GRUB уже сделал это за нас. Тем не менее, вы всё равно выделим немного места в секции BSS и переместим на её начало указатель стека. Мы используем инструкцию resb , которая резервирует указанное число байт. Сразу перед вызовом kmain указатель стека ( esp ) устанавливается на нужное место инструкцией mov .
Ядро на Си
В kernel.asm мы совершили вызов функции kmain() . Таким образом, наш «сишный» код должен начать исполнение с kmain() :
Всё, что сделает наше ядро — очистит экран и выведет строку «my first kernel».
Сперва мы создаём указатель vidptr , который указывает на адрес 0xb8000. С этого адреса в защищённом режиме начинается «видеопамять». Для вывода текста на экран мы резервируем 25 строк по 80 ASCII-символов, начиная с 0xb8000.
Каждый символ отображается не привычными 8 битами, а 16. В первом байте хранится сам символ, а во втором — attribute-byte . Он описывает форматирование символа, например, его цвет.
Для вывода символа s зелёного цвета на чёрном фоне мы запишем этот символ в первый байт и значение 0x02 во второй. 0 означает чёрный фон, 2 — зелёный цвет текста.
Вот таблица цветов:
В нашем ядре мы будем использовать светло-серый текст на чёрном фоне, поэтому наш байт-атрибут будет иметь значение 0x07.
В первом цикле программа выводит пустой символ по всей зоне 80×25. Это очистит экран. В следующем цикле в «видеопамять» записываются символы из нуль-терминированной строки «my first kernel» с байтом-атрибутом, равным 0x07. Это выведет строку на экран.
Связующая часть
Мы должны собрать kernel.asm в объектный файл, используя NASM; затем при помощи GCC скомпилировать kernel.c в ещё один объектный файл. Затем их нужно присоединить к исполняемому загрузочному ядру.
Для этого мы будем использовать связывающий скрипт, который передаётся ld в качестве аргумента.
Сперва мы зададим формат вывода как 32-битный Executable and Linkable Format (ELF). ELF — это стандарный формат бинарных файлов Unix-систем архитектуры x86. ENTRY принимает один аргумент, определяющий имя символа, являющегося точкой входа. SECTIONS — это самая важная часть. В ней определяется разметка нашего исполняемого файла. Мы определяем, как должны соединяться разные секции и где их разместить.
В скобках после SECTIONS точка (.) отображает счётчик положения, по умолчанию равный 0x0. Его можно изменить, что мы и делаем.
Смотрим на следующую строку: .text : < *(.text) >. Звёздочка (*) — это специальный символ, совпадающий с любым именем файла. Выражение *(.text) означает все секции .text из всех входных файлов.
Таким образом, линковщик соединяет все секции кода объектных файлов в одну секцию исполняемого файла по адресу в счётчике положения (0x100000). После этого значение счётчика станет равным 0x100000 + размер полученной секции.
Аналогично всё происходит и с другим секциями.
Grub и Multiboot
Теперь все файлы готовы к созданию ядра. Но остался ещё один шаг.
Существует стандарт загрузки x86-ядер с использованием бутлоадера, называющийся Multiboot specification. GRUB загрузит наше ядро, только если оно удовлетворяет этим спецификациям.
Следуя им, ядро должно содержать заголовок в своих первых 8 килобайтах. Кроме того, этот заголовок должен содержать 3 поля, являющихся 4 байтами:
- магическое поле: содержит магическое число 0x1BADB002 для идентификации ядра.
- поле flags: нам оно не нужно, установим в ноль.
- поле checksum: если сложить его с предыдущими двумя, должен получиться ноль.
Наш kernel.asm станет таким:
Строим ядро
Теперь мы создадим объектные файлы из kernel.asm и kernel.c и свяжем их, используя наш скрипт.
Эта строка запустит ассемблер для создания объектного файла kasm.o в формате ELF-32.
Опция «-c» гарантирует, что после компиляции не произойдёт скрытого линкования.
Это запустит линковщик с нашим скриптом и создаст исполняемый файл, называющийся kernel.
Настраиваем grub и запускаем ядро
GRUB требует, чтобы имя ядра удовлетворяло шаблону kernel- . Поэтому переименуйте ядро. Своё я назвал kernel-701.
Теперь поместите его в директорию /boot. Для этого понадобятся права суперпользователя.
В конфигурационном файле GRUB grub.cfg добавьте следующее:
Не забудьте убрать директиву hiddenmenu , если она есть.
Перезагрузите компьютер, и вы увидите список ядер с вашим в том числе. Выберите его, и вы увидите:
Это ваше ядро! В следующей части добавим систему ввода / вывода.
Ядро Windows
Ядро состоит из набора функций, находящихся в файле Ntoskrnl.exe. Этот набор предоставляет основные механизмы (службы диспетчеризации потоков и синхронизации), используемые компонентами исполняющей системы, а также поддержкой архитектурно-зависимого оборудования низкого уровня (например, диспетчеризацией прерываний и исключений), которое имеет отличия в архитектуре каждого процессора.
Код ядра написан главным образом на C, с ассемблерным кодом, предназначенным для задач, требующих доступа к специальным инструкциям и регистрам процессора, доступ к которым из кода на языке C затруднен.
Подобно различным вспомогательным функциям, ряд функций ядра документированы в WDK (и их описание может быть найдено при поиске функций, начинающихся с префикса Ke), поскольку они нужны для реализации драйверов устройств.
Объекты ядра.
Ядро предоставляет низкоуровневую базу из четко определенных, предсказуемых примитивов и механизмов операционной системы, позволяющую высокоуровневым компонентам исполняющей системы выполнять свои функции. Само ядро отделено от остальной исполняющей системы путем реализации механизмов операционной системы и уклонения от выработки политики. Оно оставляет почти все политические решения, за исключением планирования и диспетчеризации потоков, реализуемых ядром, за исполняющей системой.
За пределами ядра исполняющая система представляет потоки и другие ресурсы совместного использования в виде объектов. Эти объекты требуют некоторых издержек, например, на дескрипторы для управления ими, на проверки безопасности для их защиты и на ресурсные квоты, выделяемые при их создании.
В ядре, реализующем набор менее сложных объектов, называемых «объектами ядра», подобные издержки исключены, что помогает ядру управлять основной обработкой и поддерживать создание исполняющих объектов. Большинство объектов уровня исполнения инкапсулируют один или несколько объектов ядра, принимая их, определенные в ядре свойства.
Один набор объектов ядра, называемых «управляющими объектами», определяет семантику управления различными функциями операционной системы.
В этот набор включены объекты асинхронного вызова процедур — APC, отложенного вызова процедур — deferred procedure call (DPC), и несколько объектов, используемых диспетчером ввода-вывода, например, объект прерывания.
Еще один набор объектов ядра, известных как «объекты-диспетчеры», включает возможности синхронизации, изменяющие или влияющие на планирование потоков. Объекты-диспетчеры включают поток ядра, мьютекс (называемый среди специалистов «мутантом»), событие, пару событий ядра, семафор, таймер и таймер ожидания. Исполняющая система использует функции ядра для создания экземпляров объектов ядра, работы с ними и создания более сложных объектов, предоставляемых в пользовательском режиме.
Область ядра, относящаяся к управлению процессором, и блок управления (KPCR и KPRCB).
Для хранения специфических для процессора данных ядром используется структура данных, называемая областью, относящейся к управлению процессором, или KPCR (KernelProcessorControlRegion). KPCR содержит основную информацию, такую как процессорная таблица диспетчеризации прерываний (interrupt dispatch table, IDT), сегмент состояния задачи (task-state segment, TSS) и таблица глобальных дескрипторов (globaldescriptortable, GDT). Она также включает состояние контроллера прерываний, которое используется вместе с другими модулями, такими как ACPI-драйвер и HAL.
Для обеспечения простого доступа к KPCR ядро хранит указатель на эту область в регистре fs на 32-разрядной системе Windows и в регистре gs на Windows-системе x64. На системах IA64 KPCR всегда находится по адресу 0xe0000000ffff0000.
KPCR также содержит вложенную структуру данных, которая называется блоком управления процессором (kernelprocessorcontrolblock, KPRCB). В отличие от области KPCR, которая документирована для драйверов сторонних производителей и для других внутренних компонентов ядра Windows, KPRCB является закрытой структурой, используемой только кодом ядра, который находится в файле Ntoskrnl.exe.
В этом блоке содержится:
- информация о планировании (такая как текущий, следующий и приостановленный потоки, предназначенные для выполнения на процессоре);
- предназначенная для процессора база данных диспетчера (включающая готовые очереди для каждого приоритетного уровня);
- DPC-очередь;
- информация о производителе центрального процессора и идентификационная информация (модель, степпинг, скорость, биты особенностей);
- информация о топологии центрального процессора и о технологии доступа к неоднородной памяти — NUMA (информация об узле, о количестве логических процессоров в каждом ядре и т. д.);
- информация о размерах кэш-памяти;
- информация об учете времени (такая как время DPC и обработки прерывания) и многое другое.
В KPRCB также содержится вся статистика процессора, такая как статистика ввода-вывода, статистика диспетчера кэша, статистика DPC и статистика диспетчера памяти. И наконец, KPRCB иногда используется для хранения структур выравнивания границ кэша для каждого процессора, необходимых для оптимизации доступа к памяти, особенно на NUMA-системах. Например, система невыгружаемого и выгружаемого пула со стороны выглядит как списки, хранящиеся в KPRCB.
Эксперимент: просмотр KPCR и KPRCB.
Содержимое KPCR и KPRCB можно просмотреть, используя команды отладки ядра !pcr и !prcb. Без пометок отладчик по умолчанию покажет информацию для центрального процессора 0; но вы можете указать центральный процессор, добавив после команды его номер (например, !pcr 2).
В следующем примере показано, как выглядит вывод команд !pcr и !prcb.
Если в системе имелись задержанные DPC-вызовы, эта информация также будет отображена на экране.