На прошлом занятии разобрали функциональную архитектуру.
Информационная архитектура
- Управление процессами
- Дескриптор процесса PCB (Process Control Block)
- Идентификация
- Ресурсы
- История
- Очереди планирования
- Дескриптор процесса PCB (Process Control Block)
- Управление памятью
- Виртуальная память
- Защита памяти
- Управление файлами
- Карта размещения блоков (в памяти)
- Каталог имён
- Управление внешними устройствами
- Драйверы
- Plug & Play
- Защита данных и администрирование
- БД для идентификации, аутентификации, авторизации
- Аудит (журнал)
- Пользовательский интерфейс
- CLI
- GUI
Компьютер кроме как с информацией ни с чем работать не может. Любой физический объект должен быть представлен как информационный объект.
Подсистема управления процессами
Нас интересует не сам код приложения — код статичен. А вот запущенная программа — живая: запрашивает память, открывает сокеты, общается с другими процессами. У неё всё время меняется нагрузка. Она может уйти в сон или активизироваться по приходу данных.
Поэтому появилось понятие процесса.
Процесс — это программа в момент выполнения, от момента запуска до завершения. Как спектакль: пьеса (код) одна, а каждый спектакль (процесс) — разный.
PCB (Process Control Block)
Процесс существует в системе, пока существует его дескриптор. Удалили PCB — процесс умер. В Linux максимум 65536 процессов ($2^{16}$) — это и техническое ограничение, и безопасность: чтобы один пользователь не мог создать миллион процессов и положить систему.
Что внутри PCB?
1. Идентификаторы
- В любой ОС можно получить PID — Process ID.
- В Linux есть Parent PID — древовидная структура процессов.
- В Windows тоже есть Parent PID, но он может не использоваться.
- Идентификатором может быть UID пользователя.
- В Linux есть ещё эффективный UID (Effective UID).
2. Ресурсы
Подкаталог FD (File Descriptors) — ссылки на объекты типа файлов. В нём всегда есть 3 стандартных:
0— stdin1— stdout2— stderr
В этом и заключается прозрачность Linux. Когда пишем cat /proc/.../status, самого файла status не существует — cat делает запрос к ядру, ядро достаёт свою структуру, преобразует в текст и возвращает текст.
3. История Используется для статистических алгоритмов. Если процессор работал так последние 10 секунд — предположим, и дальше будет работать так же. Происходит предсказание поведения процесса.
Очереди планирования
Если на ресурс претендуют несколько процессов:
Можем реализовать систему супермаркета — кто первый, тот и получает.
Можем делать осмысленно — пропускать вперёд тех, у кого мало «покупок».
Планировщик Linux — красно-чёрное дерево.
Планировщик Windows — двумерный массив.
Это не значит, что один плохо, а другой хорошо. У них разные алгоритмы. Для ввода/вывода — тоже свои очереди.
Управление памятью
Виртуальная память. В момент компиляции вы не знаете реальных свободных физических адресов. Нужно подменять адреса. Хранить маппинг виртуальных и физических адресов для каждого процесса нужно — но это кушает память, которую мы и хотим сэкономить. TLB-кэш: можно сделать эффективно по памяти, но вычисление адресов будет медленным. Либо наоборот.
Защита памяти. Нужно где-то хранить сведения «свой/чужой». Нужно «окрасить» все объекты памяти цветами по количеству процессов и проверять цвет при доступе. Легко красить большими кусками памяти, но тогда — проблема дефрагментации. Как хранить? Линейный массив? Об этом — позже.
Управление файлами
На хранилище раньше были человекочитаемые данные, на каком-то этапе перешли к машиночитаемым.
Фон Нейман сказал, что ОЗУ линейно адресуемо. Но хранилище — как правило, многомерное. У HDD — 3-мерная цилиндрическая система координат (номер пластины, номер дорожки, номер сектора). Это нужно отображать в линейную систему. А на SSD адресация уже двумерная — и мы можем перетянуть файл с 3D-HDD на 2D-SSD просто мышкой.
Карта размещения блоков и каталог имён
1 файл — 1 каталог (родитель) в традиционных ФС.
А в Linux можно создать файл, принадлежащий нескольким каталогам одновременно — это пощупаем в 3-й лабе. Получается не дерево, а направленный граф.
В Linux любой каталог — это файл. Поэтому в Linux нельзя создать файл с тем же именем, что и каталог рядом.
В Linux защиты от дурака нет: «если ты root — ты крут».
Команда ln создаёт жёсткую ссылку. На каталог жёсткую ссылку создать нельзя — даже под root’ом будет access denied. Только со специальным флагом и под root’ом можно, но в man к этому флагу — огромный дисклеймер: «все ошибки человечества после этого будут на твоей совести». Это сделано, чтобы избежать бесконечного цикла из ссылок.
Управление внешними устройствами
Драйвер (дословно — «водитель, управляющий»). Работа с железом — привилегия ядра.
Как ядро узнает, что вы подключили веб-камеру? Производитель должен написать код под конкретную ОС, который будет работать с камерой. Нужны:
- протокол взаимодействия,
- система команд.
Программисты драйверов должны знать спецификацию ядра.
Семейства драйверов — плохо: в них страдает параметризация. Скачиваешь общий драйвер для линейки принтеров Brothers — он берёт минимальные общие параметры (минимальная скорость, без переворачивания листов), даже если у вас всё это есть.
Раньше (80-е, конец 90-х) драйверы вручную линковались внутри кода ядра, после чего нужно было пересобирать ядро. Пару раз упадёшь — и установка одного драйвера растягивалась на несколько суток. Любой сисадмин обязан был знать C.
Дальше Microsoft (которому надо было продавать ОС домохозяйкам, не знающим C) развивает игровую индустрию, делает упор на железо, и появляется Plug & Play. Консорциум производителей договорился: при подключении устройство сообщает свой UID; по UID находим драйвер в базе/облаке/на диске, и автоматический механизм интегрирует драйвер с ядром. Драйверы умели вешаться на универсальные интерфейсы. Но всё равно для защищённой области памяти нужна была перезагрузка.
Работало это всё криво — администраторы называли систему Plug & Pray.
Защита данных и администрирование
БД для аутентификации.
- Идентификация — сохранили хэш.
- Аутентификация — проверили хэш.
- Авторизация — сопоставить логического пользователя (UID) и ресурс (файл, процесс).
Ресурсов много, пользователей много. Матрица доступа — огромная и разреженная. Алгоритмы на разреженных матрицах обычно неэффективны — $O(n^2)$.
- У Linux — мандатно-дискреционный доступ. При первом обращении он долго «думает», дальше проверяется наличие мандата.
- У Microsoft — Active Directory.
Аудит (журнал). ОС живёт в открытом мире, среди пользователей с естественным интеллектом. Они всегда победят искусственный — у них есть фантазия. ИИ может обобщать чужие фантазии, но придумать действительно новую атаку — нет. «Генерал всегда готовится к прошедшей войне».
Аудит позволяет посмотреть, как это происходило, кто что сделал. Можно ловить аномалии — например, пользователь поменял пароль 42 раза за 20 минут. Возможно, паранойя; а возможно — перебирает и сохраняет результаты хэш-функции.
Пользовательский интерфейс
CLI — Command Line Interface.
GUI. Нельзя запускать с root’а (точнее, можно, но с дисклеймером «не валяй дурака»). Нужно запускать от обычного пользователя. Но GUI должен взаимодействовать с ядром — иначе через SSH нельзя было бы перезагрузить сервер. Используется механизм с битом SUID.
На сервера никогда не ставят GUI.
Windows и современная macOS имеют сильно интегрированный GUI.
Системная архитектура
Код в режиме ядра резидентен — находится в оперативной памяти по фиксированным адресам. Виртуализация памяти ему не нужна, код ядра минует маппинг виртуальных адресов — это сильно быстрее.
Если весь функционал ОС запихать в ядро — всё будет очень быстро, но память сожрётся. И не факт, что весь код нужен в моменте. Если в ядре оставить только часто вызываемое — производительность и безопасность плачут.
Что такое надёжность? Во время тестов — просто всё проверить. Обратная сторона монолита — микроядро. Протестировать 10 тысяч строк микроядра легко.
5 принципов разработки ОС
Модульная организация. Даже если это монолит — это не помойка кода. Любой аллокатор памяти, планировщик — это модуль. Помогает независимо разрабатывать и планировать высокоуровневую архитектуру.
Функциональная избыточность. Проектируем то, чем будут пользоваться не только сейчас, но и потом.
Когда проектировали файловую систему ext2, заложили возможность адресовать до 2 ТБ. В начале 90-х это казалось избыточным — диски были по 10 МБ. Но к концу 2000-х 2 ТБ стало нормой. Беда была в другом — нельзя было создать большой файл, потому что поле для размера было ограничено.
Функциональная избирательность. Должен быть механизм отключения излишней функциональности.
Windows XP рассчитывалась на 256 МБ ОЗУ, но медиана была 128 МБ. ОС кушала много памяти. В сети быстро появились «колотушки» — твики regedit, отключавшие ненужное.
Параметрическая универсальность. Не хардкодить константы (no magic numbers 🙈).
Концепция многоуровневой вычислительной системы. Даже минимальный монолит имеет 3 слоя. Нельзя в один компонент заложить и аппаратно-зависимое, и программное — иначе любое изменение потребует переделывать всё. Нужно абстрагировать аппаратную часть от программной.