- PVSM.RU - https://www.pvsm.ru -
Меня зовут Андрей Артемьев, я работаю в Microsoft над ядром ОС Windows 10, ранее я работал над Windows 10x (WCOS), XBox, Windows Phone и Microsoft Edge. Я хочу популярно в образовательных целях рассказать о том как работает компьютер на примере клавиатурного ввода и Windows 10. Данный цикл статей рассчитан в первую очередь на студентов технических специальностей. Мы рассмотрим какой путь проходит информация о нажатой клавише от клавиатуры до отображения в Notepad.exe. В виду обширности и междисциплинарности темы в статьях могут быть неточности, о которых сообщайте в комментариях. Какая-то информация может быть устаревшей в виду скорости с которой развивается Windows.
Давайте для начала в общих чертах поговорим об уровнях на которых можно рассматривать компьютер. Каждый уровень основывается на предыдущем. Начнём с самого верха.
Уровень прикладных программ. Здесь находятся все пользовательские приложения, это браузер, Microsoft Office, блокнот, а также веб-сервер IIS, SQL Server, различное middleware вроде Java и .Net и все скриптовые языки, будь то Ruby, PHP или Bat-файлы.
Уровень операционной системы. ОС можно рассматривать как:
Уровень архитектуры компьютера. Он представлен материнской платой, которая имеет определённый форм-фактор, встроенные функции закодированные в микроконтроллерах называемых чип-сетом и порты, через которые можно расширять функционал компьютера подключив графическую карту, сетевую карту, дополнительную оперативную память (RAM), жёсткие диски, клавиатуру и пр. Порты влияют на скорость работы и возможности компьютера, что и будет определять его назначение будь то сервер для обработки тысяч запросов в секунду, планшет для пользования Интернетом или игровой ПК с несколькими видеокартами. ОС абстрагирует особенности материнской платы.
Микроконтроллеры выглядят как на картинке ниже и представляют собой мини-компьютер выполняющий простые программы для низкоуровневых задач, к примеру прочитать данные от клавиатуры и передать их дальше чтобы они достигли в конечном счёте процессора. Как правило программируются на языке С.
Материнскую плату можно рассматривать как колонию микроконтроллеров которые общаются между собой через шины и через них циркулируют данные от подключенных устройств к процессору и обратно. Чип-сет — это своего рода нервная система компьютера. Все чипы на материнской плате были изначально созданы чтобы работать друг с другом. Некоторые из них могут иметь особые функции, к примеру таймер или хранение настроек BIOS. Пожалуй самый важный из них тот что имеет встроенную программу (прошивку, BIOS, UEFI) которая начинает выполняться как только появляется электричество. Она находит жёсткий диск с загрузчиком Windows и передаёт тому управление который в свою очередь запускает исполняемый файл ОС, который можно назвать Windows10.exe, на самом деле NtOsKrnl.exe. BIOS знает что искать благодаря соглашению между производителями железа и операционных систем.
Вокруг материнской платы можно собрать мобильный телефон, игровую приставку, серверную станцию или умное устройство. На картинке ниже распространённые форм-факторы материнских плат.
Уровень микроархитектуры представлен процессором (CPU), это сердце материнки и весь чип-сет нужен для обслуживания CPU. Процессор это компьютер в компьютере, более мощный и продвинутый микроконтроллер которому не нужна прошивка, потому как поток команд подаётся на лету, когда планировщик потоков поменял контекст процессора. Функционал процессора делится на подсистемы, к примеру компонент занимающийся математическими и логическими операциями, математический сопроцессор, кэш. Какие-то из них раньше были отдельным чипом на материнской плате, но сейчас их сделали частью ЦПУ, например контроллер прерывания и микроконтроллер под названием “Северный мост” что увеличило скорость работы.
Микроархитектура это не то же самое что архитектура. Весь функционал CPU разделён на компоненты, которые работают сообща. Эти компоненты и их взаимодействие и есть микроархитектура. На блок-схеме ниже они представлены цветными прямоугольниками и квадратиками.
Архитектура процессора это по сути документ который описывает какой функциональностью он должен обладать для того чтобы соответствовать к примеру архитектуре x86, x64 или ARM применяемой на мобильных устройствах. В этом документе описано какие должны поддерживаться команды, назначения регистров и логика работы. Создатели процессоров Intel, AMD, Эльбрус могут реализовывать эту функциональность как угодно и добавлять к ней новые возможности в виде команд, регистров, флагов, прерываний и если ОС знает о них то может использовать. В терминах ООП архитектура ЦПУ — это интерфейс, а микроархитектура — его реализация.
Логические схемы. Цветные прямоугольники с блок-схемы CPU состоят из логических схем, которые производят свои операции на последовательностях нулей и единиц. Процессор видит все данные и команды в виде битов (0 и 1), по формуле любое десятичное число можно представить в виде последовательности 0 и 1, а вот что значит конкретное число зависит от контекста. Это может быть код, цифра, буква. Арифметическое и логическое устройство (ALU) умеет производить сложение двух чисел через побитовые операции. Побитовые алгоритмы сложения, вычитания, умножения и деления давно известны, разработчикам логической схемы их только надо эффективно реализовать.
Цифровые схемы работают с данными на уровне нулей и единиц, а аналоговые полагаются на эксплуатирование законов физики. Они выполняют простейшие манипуляции над битами, такие как сдвиги, AND, OR, XOR. Через эти примитивы реализуются более сложные операции. На картинке ниже представлен аналоговый оператор AND реализованный через транзисторы. На выходе схемы ненулевое напряжение будет только если оно есть на обоих входных контактах (Вх1 и Вх2), иначе ноль вольт. Точно так же работает оператор && в C#. Цифровые схемы в конечном счёте основаны на аналоговых. Физически логические элементы в логических схемах могут быть реализованы не только электронными но и механическими, гидравлическими, оптическими и другими способами.
Уровень радиоэлементов. Физически аналоговые схемы полагаются на радиоэлементы, которые собственно и эксплуатируют законы физики. Преимущественно это полупроводники, т.е. в определённых условиях они могут проводить электричество, а могут и нет. Диод проводит ток только в одном направлении, если его выпаять, развернуть на 180 и впаять обратно, то ток через него проходить не будет. Транзистор пропускает ток только если есть напряжение на управляющей ножке. Человечество научилось делать транзисторы микроскопическими и потому их можно размещать на маленькой плате миллионами. На картинках ниже полупроводниковые радиоэлементы и обычный транзистор рядом с нано транзистором под электронным микроскопом.
|
Уровень законов физики. И наконец самый нижний уровень — это уровень законов физики которые заключены в полупроводниковые радиоэлементы.
Мы будем много говорить про уровень ОС и чуть меньше про архитектуру компьютера, микроархитектуру, аналоговые схемы и радиоэлементы. К последней части у вас должно быть понимание как это всё работает вместе.
Когда мы проходили в универе программирование на ассемблере у многих студентов был ступор от таких умных слов как “режимы ядра и пользователя”, под которыми на самом деле скрывается хорошо всем известная ролевая система аутентификации, на всех сайтах есть как минимум “Админ” имеющий доступ ко всем страницам и “Пользователь” имеющий ограниченный доступ. Точно так же роль “Ядро” имеет доступ ко всем возможностям CPU, а роль “Пользователь” может вызывать не все команды процессора и не со всеми аргументами. Поверх этой ролевой модели по принципу клиент-серверной архитектуры построена операционная система, где сервер это ядро, которое и реализовывает функционал ОС, а клиент — это пользовательские программы. В мире Web клиент и сервер разделены физически — это два разных компьютера общающихся по сети. В ОС клиент и сервер живут на одной машине и на одном железе. У сервера есть некий API который позволяет клиентам изменять его состояние, к примеру Twitter API позволяет создавать посты, логиниться и загружать ленту твитов в мобильный клиент. У Windows есть Win API, только более громоздкий в виду более широкого круга задач, на сегодняшний день у винды примерно 330 000 API плюс API для UWP apps. Если концепции Твитера более менее всем понятны — пост, пользователь, фид — то концепции ОС могут потребовать некоторого углубления в её внутренности. Поэтому API Windows могут быть трудно понятными без понимания внутреннего устройства ОС.
На самом деле под ядром понимают три разные вещи. Ядро как весь код ОС. Ядро как подсистема которая отвечает за механизмы ОС, такие как планировщик потоков, переключение контекста, обработка прерываний, свап виртуальный памяти на физическую (Kernel) и ядро подсистемы для поддержки других ОС — CSRSS.exe (Windows), PSXSS.exe (POSIX), OS2SS.exe (OS/2) или WSL (Windows SubSystem for Linux). В данном контексте понимается первый смысл — весь код ОС.
Когда на экране появляется окно, то в серверной части ОС (режим ядра) появляется структура данных которая описывает это окно — его положение на экране, размеры, текст заголовка, оконная функция через которую ОС даёт приложению среагировать на события. Поскольку подсистем в ОС много, то и структур данных описывающих один объект может быть несколько, к примеру информация о пользовательском процессе есть в компонентах:
Поддержка POSIX и OS/2 скорее была формальной, потому как для переноса программы с этих ОС нужно было найти исходный код и перекомпилировать его специальным образом чтобы получился exe-файл в котором все вызовы к API ОС перенаправлялись в PSXdll.dll (NETAPI.DLL и DOSCALLS.DLL для OS/2 и User32.dll для Windows приложений), которая была тонкой обёрткой над API Windows. Состояние некоторых API хранилось в процессе подсистемы PSXSS.exe (OS2SS.exe). Поскольку не все API можно сопоставить 1 к 1, то работала такая программа нестабильно. В Windows 10 была добавлена поддержка Linux и она работает совсем по-другому. Подсистема Linux
Что такое компонент? Это логически сгруппированный функционал. Компонентом можно назвать ООП-класс, dll, папку, набор функций с общим префиксом, пространство имён, слой в архитектуре.
Разделение на клиент и сервер реализовано при помощи встроенной функциональности CPU, разделения памяти и программных проверок.
Производители оборудования сотрудничают с разработчиками ОС, поэтому в процессоре есть механизмы созданные с учётом потребностей создателей операционных систем. Во всех современных процессорах реализован механизм ролей пользователя, где под пользователем понимается исполняемый в данный момент код. В веб приложениях роль залогиненого пользователя хранится в какой-то переменной и помимо понятного названия Admin или User имеет Id этой роли который чаще и используется при авторизации, потому как сравнивать числа быстрее и проще чем строки. В процессоре роль текущего пользователя хранится в поле которое называется “кольцо безопасности” (Security Ring), а не “CurrentUser.Role.Id”. В большинстве процессоров это поле принимает четыре значения от 0 до 3. Windows использует 0 для роли которая называется “Режим Ядра”, потому как это самый привилегированный режим и самое большое значение для роли “Режим Пользователя”, потому как это самая ограниченная роль. Остальные роли не используются потому как различие между 0 и 1, 2 и 3 незначительное. Эти роли ограничивают страницы памяти которые могут быть адресованы, нельзя вызывать некоторые инструкции или же нельзя их вызывать с определёнными аргументами. Так же есть ограничения на использование технологии I/O Ports для обмена данными с устройствами такими как клавиатура, но она уже лет 10 не используется. Переключение в режим ядра происходит через команду syscall, которая по индексу находит в массиве указателей на APIs Windows функцию которую надо вызвать. Указатель на этот массив сохраняется в специальном регистре процессора во время загрузки ОС.
Я специально назвал регистр ЦПУ полем. Когда вы пишете программу на C#, то можете добавлять сколько угодно полей в свой класс и давать им понятные названия. У процессоров и микроконтроллеров поля называются регистрами. Когда вы проектируете железку то нужно уже на этом этапе понять сколько регистров (полей) надо, какого размера они должны быть и какой функционал на них возложить, чтобы этого хватало на все случаи жизни для всех мыслимых и немыслимых ситуаций. Представьте, что вам надо написать программу на C#-подобном языке программирования у которого в классе 16 предопределённых полей и вы не можете использовать локальные переменные. Т.е. одно и то же поле может использоваться для хранения залогиненного пользователя, отсортированного массива, временных данных или результата выполнения метода. Имена таких полей должны быть максимально общими, вместо tempData, funcResult будут странные имена EAX, AH, AL, DX и пр. Если бы вы писали на C# эмулятор процессора на архитектуре x86, то код мог бы выглядеть примерно так (DIV — divide, команда целочисленного деления):
Для иллюстрации я добавил проверку на режим ядра. Если происходит ошибка деления на ноль, то процессор уведомляет об этому ОС через прерывание (выбрасывает событие). Во время загрузки ОС предоставляет процессору одномерный массив указателей на функцию, который называется вектор прерываний, где каждому индексу соответствует какое-то событие к примеру деление на ноль. ОС знает архитектуру процессора на котором исполняется и может соответствующим образом упорядочить в массиве указатели на функции. CPU вызывает эту функцию и ОС свой код. К примеру Windows смотрит в реестре установлен ли отладчик и показывает сообщение о том что произошла ошибка и есть возможность запустить дебаггер чтобы посмотреть на место где она произошла. Прерывание называется прерыванием потому как оно вклинивается в нормальный ход выполнения программы и даёт возможность выполнится обработчику прерывания и только после его завершения процессор может вернуться к исполнению программы.
Прерывания могут генерироваться не только процессором но и внешними устройствами (клавиатура, мышь) или программным кодом. Планировщик потоков устанавливает таймер который с интервалами равными одному кванту (по умолчанию около 15мс, в Windows Server больше) генерирует прерывание чтобы по внутреннему алгоритму назначить другой поток на исполнение. Пошаговое исполнение программы в Visual Studio так же полагается на механизм прерываний — у процессора устанавливается флаг, который после каждой команды вызывает прерывание которое обрабатывает Windows Debugging Engine и уведомляет через API Visual Studio.
Разделение памятью реализовано благодаря виртуальной памяти. Ранее я говорил что ОС это менеджер ресурсов и виртуальная машина. Даже если у вас 1Гб RAM 32х битный Windows будет работать так как если бы у вас было 4Гб оперативки, т.е. реально у вас 1Гб, а виртуально 4Гб. Современные компьютеры основаны на теоретической модели машины Тьюринга или же архитектуре фон Неймана (с некоторыми изменениями). Согласно этим моделям память в компьютере это лента состоящая из ячеек размером один байт. Эти ячейки сгруппированы в страницы как правило по 4096 байт (4Кб), потому как:
Windows 10 использует ячейки вначале ленты для пользовательского пространства, а в конце — для самого себя (пространство режима ядра), в котором и хранятся объекты описывающие состояние системы.
64 битный адрес позволяет адресовать 16 экзабайт, это 18,446,744,073,709,551,616 ячеек памяти. Современные процессоры пока что не поддерживают так много RAM и поэтому используют только младшие 48 бит адреса, остальные 16 заполняются старшим разрядом. Поэтому Win x64 попросту не использует часть адресов, которые помечены на рисунке выше чёрным. Но это не значит, что 64х битный Windows “видит” 256 Tb оперативки. Максимум 8TB виртуальной памяти на архитектуре IA64 и 7TB на x64. Предел физической памяти поддерживаемой Windows 10 — 2TB, потому как с большим количеством Винда не тестировалась. Объём поддерживаемой RAM в Windows 10 определяется во многом редакцией ОС, чем дороже тем больше.
Почему ячейки памяти пронумерованы шестнадцатиричными (HEX) числами, а не десятичными или двоичными? Адрес ячейки это не просто порядковый номер, в нём закодировано три числа по которым эту ячейку можно отобразить на физическую память. Первые два это индексы по которым находится конкретная страница виртуальной памяти, а третье число — смещение от начала страницы. CPU и ОС здесь работают в связке — ОС предоставляет структуру данных по которой CPU находит страницу виртуальной памяти и копирует её в физическую. По HEX номеру можно сразу увидеть как выровнен в памяти адрес. Формат двоичного числа слишком громоздкий, 32 бита (или 64) трудны для восприятия. Десятичный формат — показывает слишком мало информации, тогда как HEX удобный компромисс между десятичной и двоичной системами, средами людей и машин.
Программный способ разделения на клиент-сервер (режим ядра-режим пользователя) гораздо скучнее перечисленных выше механизмов. Процессы и потоки могут быть помечены специальными аттрибутами или же мы можем хранить список указателей на потоки/процессы и проверять их в коде. Если вы хоть раз делали авторизацию в веб-приложении, то хорошо понимаете о чём я говорю.
Абстракция или же разбиение на компоненты есть во всех сферах программирования и интуитивно понятно что Windows тоже делится на какие-то компоненты. Под компонентом понимается какая-то единица функциональности — ООП класс, файл, dll, папка. Компоненты на диаграмме ниже могут быть как индивидуальными *.sys, *.dll файлами, так и просто набором API сгруппированным логически через добавление префикса к имени функции, старая часть системы написана на C, а он не поддерживает классы. Новые части пишутся нередко на C++. В укрупнённом виде Винда выглядит так:
Давайте быстренько пробежимся по её компонентам снизу вверх:
На этом первую часть закончу. В следующей посмотрим откуда начинается путь клавиши.
Автор: Андрей Артемьев
Источник [2]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/c-2/342629
Ссылки в тексте:
[1] хостится: https://www.reg.ru/?rlink=reflink-717
[2] Источник: https://habr.com/ru/post/483330/?utm_source=habrahabr&utm_medium=rss&utm_campaign=483330
Нажмите здесь для печати.