Всем привет!

Добро пожаловать в наше увлекательное путешествие в мир операционных систем. В этом уроке мы глубже погрузимся в суть понятия "операционная система" и рассмотрим, почему она является неотъемлемой частью каждого компьютера, ноутбука или смартфона.

Приятного чтения!

Данный урок взят из моего канала в телеграмме, которой я веду в своё свободное время. Цель моего канала провести вас за ручку от начала и до конца, пути изучения программирования и основ Computer Science.

Что такое операционная система?

Операционная система (ОС) — это программное обеспечение, совокупность программ, управляющих ресурсами компьютера и обеспечивающих интерфейс для взаимодействия пользователей с компьютером. Она служит посредником между аппаратным обеспечением и прикладным программным обеспечением, предоставляя программистам удобный интерфейс для создания программ, которые находятся выше уровнем, если бы её не было, то программистам пришлось бы для каждой новой программы реализовывать свои методы для работы с аппаратными компонентами, что очень денежно и время затратно и это неописуемо увеличивает порог вхождения в разработку.

Давайте разберём её основные задачи и функции.

  • Загрузка программ в оперативную память и их выполнение.

  • Управление аппаратными компонентами, такие как: процессор, память, устройства ввода-вывода.

  • Предоставление интерфейса для работы с файловой системы, проще говоря, файлами, папками, с которыми мы привыкли работать.

  • Операционная система контролирует доступ к ресурсам и данным, предотвращая несанкционированный доступ программ к защищенным данным или данным других программ, то есть обеспечение безопасности.

На данный момент у вас должно сложиться примерное понимание, что такое ОС и для чего она нужна, сейчас можно рассмотреть её процесс загрузки, после BIOS или UEFI.

Процесс загрузки ОС

Определение загрузочного устройства: после прохождения POST BIOS определяет, с какого устройства будет производиться загрузка. В зависимости от настроек BIOS это может быть жесткий диск, твердотельный накопитель, USB-память, оптический диск или сетевое устройство.

Поиск загрузчика: загрузчик обычно находится в особом секторе первого физического диска (или другого загрузочного устройства).

Передача управления: когда BIOS определил загрузочное устройство и нашел загрузчик, он передает управление ему. Загрузчик становится активным и начинает свою работу.

Чтение информации о размещении ядра: загрузчик читает информацию о том, где находится ядро операционной системы на выбранном загрузочном устройстве. Эта информация может включать в себя расположение файла ядра, его размер, атрибуты и другие параметры.

Загрузка в оперативную память (RAM): после получения необходимой информации загрузчик загружает содержимое файла ядра в оперативную память (RAM). Обычно это происходит в выделенную область памяти, из которой ядро сможет начать свое выполнение.

Активация ядра операционной системы: после загрузки в память ядро становится активным. Здесь начинается выполнение основного кода операционной системы. Ядро инициализирует необходимые компоненты и базовое окружение, необходимое для работы операционной системы.

Вот мы загрузили наше ядро, но что оно из себя представляет?

Ядро операционной системы — это своего рода "мозг", который управляет ресурсами компьютера и обеспечение взаимодействия между аппаратным и программным обеспечением.

Представьте себе операционную систему как дом: ядро — это фундамент, который поддерживает все остальные части. Ядро обрабатывает запросы от программ, управляет доступом к ресурсам, таким как процессор, память, диски, и обеспечивает координацию между различными компонентами системы.

Существует разные подходы к разработке ядра ОС, вот самые часто встречающие:

Монолитное ядро:

Плюсы:

  • Производительность: внутри монолитного ядра нет накладных расходов на межъядерные коммуникации, что может быть положительным фактором для высокопроизводительных систем.

  • Простота разработки и отладки: одно адресное пространство упрощает разработку и отладку ядра и драйверов.

Минусы:

  • Отсутствие изоляции: проблемы в одной части ядра могут повлиять на всю систему.

  • Сложность в добавление новой функциональности: так как ядро у нас будет всё время расширяться, увеличиваться в размерах, то в какой-то момент появится проблема в добавление новых функций, из-за не модульности ядра.

Микроядерная архитектура:

Плюсы:

  • Легкая модификация и расширение: благодаря модульной структуре микроядра, добавление новых функций и поддержки нового оборудования более гибко.

  • Изоляция ошибок: Проблемы в одном компоненте могут быть изолированы и не влиять на другие части системы.

Минусы:

  • Производительность: из-за необходимости межпроцессного взаимодействия (inter-process communication) могут возникнуть накладные расходы.

Гибридное ядро:

Плюсы:

  • Гибкость: Комбинация монолитных и микроядерных принципов позволяет балансировать производительность и гибкость.

  • Модульность: Возможность выносить некоторые функции за пределы ядра обеспечивает лучшую модульность системы.

Минусы:

  • Сложность проектирования: требует более сложного проектирования и реализации по сравнению с чистым монолитным или микроядерным подходом.

  • Возможные накладные расходы: В зависимости от конкретной реализации, некоторые накладные расходы на межпроцессной коммуникации могут оставаться.

 

Следующую тему, которую я планирую рассмотреть это проблема многозадачности в компьютерах и как с ней справляется ОС.

Многозадачность ОС

Представьте, что ваш компьютер — это поваренная кухня, и задачи на компьютере — это различные блюда, которые вы готовите. В начале развития компьютеров они были похожи на кухни, где повар мог готовить только одно блюдо за раз. Это означает, что, когда он начинал готовить суп, он не мог параллельно готовить салат.

Но в какой-то момент стали осознавать, что если позволить повару использовать несколько кастрюль и сковородок одновременно, то он сможет готовить несколько блюд одновременно, экономя время и делая процесс более эффективным. Так появилась идея многозадачности в мире компьютеров.

Вместо того, чтобы ждать, пока одна задача завершится, мы разрешили компьютеру выполнять несколько задач одновременно. Таким образом, если у вас открыты веб-браузер, текстовый редактор и музыкальный плеер, ваш компьютер обрабатывает все эти задачи «параллельно», почему в скобочках? Потому что, они могут выполнять псевдо параллельно, то есть, если у вас одноядерный процессор, то даже теоретически не может выполняться одновременно 2 программы, в дальнейшем это объяснится подробнее.

Как это работает?

Разделение времени: ОС выделяет маленькие отрезки времени каждой программе, позволяя им поочередно выполняться. Этот отрезок времени называется квант времени.

Планировщик задач: это как умный повар на кухне, который решает, когда переключать внимание между разными задачами. Планировщик определяет, какие задачи важнее, и управляет, сколько времени каждая из них получает для выполнения.

Интервальное переключение: когда ваш компьютер многозадачит, он быстро переключается между задачами, создавая иллюзию одновременного выполнения или если ваш процессор многоядерный, то они действительно могут выполняться параллельно. Например, если вы пишете текст в редакторе, планировщик может быстро переключиться на музыкальный плеер, чтобы он продолжил воспроизведение, а затем вернуться к редактору.

Перед тем как перейти к его разбору, следует сначала узнать, что такое процесс.

Процесс — это программа во время выполнения. Он включает в себя инструкции, данные и ресурсы, необходимые для выполнения программы. Одна программа может создавать много копий процессов, при её многократном запуске, всё они не будут зависеть друг от друга. Например, вы открыли два блокнота, это всё одна программа, но разные процессы и у каждого своё состояние.

У каждого процесса в ОС существует собственная оболочка, своё адресное пространство, оно изолирует процесс от других процессов, что обеспечивает безопасность, ведь мы же не хотим, чтобы какая-то программа влезла в нашу и попортила наши данные, ну если это не было задумано.

Хочу добавить важное замечание, выполняется не сам процесс, а его потоки, сам процесс это как оболочка, абстракция, в которой находятся потоки, использующие одну общую памятью и ресурсы.

Поток – наименьшая единица выполнения внутри процесса. Он представляет собой отдельную сущность, у которой есть свои собственные данные. Все потоки работают в одном общем адресном пространстве процесса. И по этой причине появляется проблема синхронизации данных между потоками, но это уже совсем другая история. Один процесс содержит минимум один поток. Потоки в процессе выполняются параллельно. Во время создания процесса, происходит создание главного потока (main thread), он является прародителем всех остальных потоков, которые будут созданы им с помощью системных вызовов (в дальнейшем тоже рассмотрим, что это). У каждого потока есть его собственный контекст выполнения.

Контекст выполнения потока (Thread Control Block, TCB).

Идентификатор (Thread ID, TID): уникальный идентификатор, с помощью которого можно различать потоки.

Стек (Stack): стек используется для хранения локальных данных и других временных данных. Размер стека всегда ограничен ОС, как правило он не превышает более 2 МБ, но это ограничение можно обойти, попросив ОС сделать его больше. Данные в стеке расположен последовательно в памяти, один за другим. Обычно он расширяется от большего адреса к меньшему, то есть от какого-то начала к началу адресного пространства, если провести аналогию со стеком, то можно представить, что ёмкость для тарелок — это стек. Начало стека — это будет дно этой ёмкости, а верхушка стека — это самая верхняя тарелка, которая в ней лежит. Достать данные из стека возможно только из его верхушки, то есть самого последнего элемента, который в него попал.

Стек работает по принципу Last In, First Out (LIFO), что означает, что последний элемент, добавленный в стек, будет первым, который извлекается, как уже было до этого сказано.

Состояние регистров (Register contents): здесь содержится информация о текущем состояние регистров для данного потока.

Счётчик программ (Program counter): адрес следующей команды, которую должен выполнить процессор, в контексте данного потока.

Адрес на PCB: адрес на блок памяти, в котором хранится вся информация родительском процессе.

Всю информацию о процессе содержится PCB (Process Control Block) блоке, он уникален для каждого процесса.

Давайте рассмотрим, что в нём содержится:

1) Идентификатор (Process Id, PID): у каждого процесса существует свой идентификатор (PID), который присваивается ОС, чтобы в будущем она могла к ним обращаться и различать.

2) Текущее состояние: процесс хранит информацию о состоянии, в котором он находится на данный момент.

Разберём самые основные.

Новый (New): процесс создан, но ещё не был принят операционной системой для выполнения.

Готов к выполнению (Ready): процесс находится в ожидании своей очереди на выполнение. Все необходимые ресурсы доступны, и процесс готов начать выполнение, как только операционная система его выберет.

В выполнении (Running): процесс активно выполняется на процессоре.

В ожидании (Waiting или Blocked): процесс ожидает какого-то события, например, завершения операции ввода-вывода.

Завершенный (Terminated): процесс завершил свое выполнение. Это может произойти по окончании выполнения программы или в результате принудительного завершения операционной системой.

3) Информация о занятых ресурсах: это информация процесса включает в себя данные о том, какие ресурсы, процесс использует в системе (открытые файлы, занятые устройства и т.д.). Эта информация необходима для управления ресурсами и предотвращения конфликтов между процессами.

4) Контекст процесса: это состояние процесса в определенный момент времени, это вся служебная информация необходимая для правильного функционирования процесса, например, информация о адресном пространстве, значение регистров, маркеры безопасности и т.д.

Адресное пространство процесса делится на несколько секций, где каждая секция имеет свое предназначение, деление его на секции позволяет более эффективно управлять памятью процесса, так как каждая секция служит для конкретных целей.

Давайте рассмотрим основные секции процесса:

Текстовая секция (Code Section): эта секция содержит исполняемый код программы, инструкции. Текстовая секция обычно доступна только для чтения, чтобы предотвратить случайные изменения программного кода, что может привести к сбоям.

Секция данных (Data Section): в этой секции хранятся глобальные и статические данные, которые не изменяются во время выполнения программы. Она дополнительно делится на 2 сегмента:

  • Инициализированные данные (Initialized data segment), у которых явно задано значение перед запуском программы.

  • Неинициализированные данные (Uninitialized data segment или BSS - Block Started by Symbol), которые не имеют изначально установленного значение, по умолчанию им присвоено значение 0.

Куча (Heap): Куча представляет собой динамическую область памяти, используемую для выделения динамических данных во время выполнения программы. Процесс может запросить дополнительную память из кучи при необходимости с помощью системного вызова (в дальнейшем рассмотрим, что это), который даст вам адрес памяти, но при этом если эта память процессу не нужна в дальнейшим, то ему следует её освободить (вернуть обратно ОС), если память не очистится, и программа потеряет адрес этого участка памяти, то произойдёт утечка памяти.

Утечка памяти (memory leak) — это ситуация, когда программа не освобождает занимаемую ею динамическую память после того, как она становится ненужной. Но не бойтесь, ОС очистит за вас всю динамическую память, которую вы выделил и забыли вернуть, но это произойдёт только когда процесс полностью завершится, то есть во время выполнения процесса на процесс накладывается обязанность на очищение динамической памяти, которую он запросил.

В отличии от стека, в куче хранятся обычно долгосрочные или данные больших размеров и память, которую вы выделили, нужно вручную возвращать ОС. Данные в куче располагаются не последовательно как в стеке, они могут быть разбросаны в куче по разным местам.

Теперь, когда у нас появилось представление что представляет из себя процесс, можно вернуться к планировщику задач.

Планировщик задач

Планировщик задач (job scheduler) - компонент ответственный за управление исполнением потоков и распределение ресурсов между ними. Его основной задачей является переключение потоков и эффективное управление временем процессора.

Вот основные аспекты, связанные с планировщиком задач:

Таблица процессов: таблица процессов представляет собой 2 столбчатую таблицу, где в 1 колонке хранится сам процесс, а во второй колонке информация о нём, то есть PCB блок, который мы рассмотрели ранее.

Вот так примерно, как всё это выглядит
Вот так примерно, как всё это выглядит

Политики планирования: она определяет то, в каком порядке будут выполняться потоки. Вот некоторые примеры:

  • First-Come, First-Served (FCFS): потоки выполняются в порядке поступления.

  • Shortest Job Next (SJN): выполняется потоки с наименьшим временем выполнения следующим.

  • Round Robin (RR): Каждому потоки предоставляется фиксированный промежуток времени.

  • Приоритетное планирование: потоки выполняются в порядке убывания приоритета, где каждому потоку/процессу присваивается свой приоритет.

Прерывания и блокировки:

Прерывания: это механизм, который позволяет внешним устройствам или программам "прерывать" выполнение текущей задачи процессором для обработки какого-то события.

Существуют два основных типа прерываний.

  • Программные прерывания: инициируются программой или операционной системой для передачи управления другой программе или обработчику.

  • Аппаратные прерывания: связаны с аппаратными событиями, такими как сигналы от внешних устройств. Например, нажатие кнопки клавиатуры, клик мыши, подключение нового устройства.

Блокировка: Поток может быть приостановлен, до тех пор, пока не произойдёт какое-либо определенное событие, такое как ввод-вывод и т.д. Такой подход позволяет более эффективно управлять ресурсами ЦП, когда поток заблокирован, планировщик не будет ему выделять время, пока не произойдёт какое-либо событие или его разблокировка.

Подходы к многозадачности:

Существует два основных типа многозадачности: последовательная и параллельная.

  • Последовательная многозадачность: при последовательной многозадачности задачи выполняются последовательно, но переключение между ними происходит настолько быстро, что для пользователя создается впечатление параллельной работы. Это происходит за счет использования концепции временных интервалов, в течение которых операционная система переключается между различными задачами.

  • Параллельная многозадачность: в случае параллельной многозадачности несколько задач выполняются фактически одновременно, каждая на своем выделенном ядре.

Процесс смены текущего процесса другим, называется переключение контекста процесса.

Переключение контекста процесса — это процесс сохранения текущего состояния процесса (его контекста) и загрузки состояния другого процесса, который должен быть выполнен.

Процесс переключение между потоками называется переключение контекста потока.

Переключение контекста потока — это процесс сохранения текущего состояния потока (его контекста) и загрузки состояния другого потока внутри того же процесса

В чём их различие?

Переключение контекста процесса включает в себя смену адресного пространства процесса, что требует больше времени. Он происходит, когда следующий поток на выполнение находится в другом процессе.

Переключение контекста потока не меняет адресное пространство, все потоки в процессе используют общее адресное пространство. По этой причине переключение контекста потока происходит намного быстрее чем смена контекста процесса. Он произойдёт только тогда, когда следующий поток на выполнение будет находится в том же адресном пространстве, то есть в одном процессе, в котором находился предыдущий поток.

Разобрав основы, как устроена многозадачность в ОС, предлагаю перейти в сторону обеспечения безопасности Операционной системы.

Уровни привилегий

Если бы каждая программа в операционной системе обладала полным набором привилегий, это могло бы привести к серьезным проблемам безопасности и стабильности всей системы. Представьте, что каждое приложение имеет неограниченный доступ к аппаратным ресурсам, файловой системе, сетевым соединениям и другим важным компонентам системы. Такая неограниченная свобода может привести к несанкционированным действиям, конфликтам и сбоям.

Решением это проблемы, стало введение уровней привилегий, который определяют различные уровни доступа, которые пользовательские программы и процессы могут иметь к ресурсам компьютерной системы.

Он обычно представлен двумя режимами: пользовательским режимом и режимом ядра.

Пользовательский режим (User Mode): Программы, выполняющиеся в пользовательском режиме, работают в ограниченном окружении и имеют ограниченные привилегии. В этом режиме программы имеют доступ только к своим собственным данным и ограниченный набор системных возможностей. Обычно, если программа хочет выполнить какую-то привилегированную операцию (например, обращение к аппаратному обеспечению), ей совершить системный вызов.

Режим ядра (Kernel Mode): Режим ядра предоставляет программам полные привилегии и неограниченный доступ ко всем ресурсам системы. Он используется для выполнения ядра операционной системы, которое управляет системными ресурсами, обеспечивает безопасность и обеспечивает выполнение привилегированных операций. Программы, выполняющиеся в режиме ядра, могут выполнять специфические инструкции и имеют полный доступ к аппаратному обеспечению.

Системные вызовы

Когда программа работает в пользовательском режиме, у нее ограниченный доступ к системным ресурсам. Однако, иногда приложениям необходимо выполнять действия, требующие более высоких привилегий, таких как открытие файла, управление памятью или создание процессов или потоков.

В этом случае программе нужен способ запросить операционную систему о выполнении привилегированных действий. Здесь на помощь приходят системные вызовы.

Системные вызовы (system call) представляют собой интерфейс между пользовательским приложением и ядром операционной системы. Ядро операционной системы предоставляет набор функций, которые программы могут вызывать для выполнения определенных операций, таких как ввод-вывод, создание процессов, управление файлами и т. д.

Этот механизм обеспечивает безопасный и контролируемый способ для программ взаимодействовать с привилегированными функциями ОС, минимизируя риски безопасности.

Драйвера

Драйверы – это такая программа, которая обеспечивают связь между операционной системой и аппаратным обеспечением компьютера. Они реализуют удобный интерфейс для правильного использования устройства, служат посредниками между операционной системой и конкретными устройствами, такими как принтеры, клавиатура, мышь, видеокарты, звуковые карты, и другие периферийные устройства.
В ранних операционных системах, драйверы отсутствовали или были встроены в само ядро операционной системы. Эти системы были простыми, и их возможности взаимодействия с аппаратным обеспечением были ограниченными.

С течением времени появлялись всё более сложные и узконаправленные устройства, требующие более специализированного взаимодействия с операционной системой. Для обеспечения совместимости и эффективного использования нового оборудования стали создаваться драйверы. Такой подход расширяет возможности ОС, позволяя ей работать с разными видами устройств.

Виртуальная память

С появлением более сложных и ресурсоемких программ, программам всё требовалось больше и больше памяти для их работы и в какой-то момент требование больше памяти, могло превзойти пределы оперативной памяти, которая была установлена в компьютере. Что же делать, когда оперативная память кончится? Завершать процесс? Находить и завершать не нужные процессы?  Появилась необходимость решить данную проблему и этим решением стала виртуальная память.

Виртуальная память — это технология, предоставляющая программам впечатление о том, что у них есть более доступной оперативной памяти, чем фактически установлено в компьютере. Это достигается за счет комбинирование всей памяти (оперативной памяти и памяти диска), которая доступна, в одно целое. И как раз этот механизм позволяет изолировать процессы друг от друга, представляя им своё собственное адресное пространство, виртуальная память использует механизм адресации. Каждый процесс имеет свою собственную виртуальную адресную область, которая не зависит от физического адресного пространства. Это позволяет программам работать, не зная, где физически расположены их данные в памяти.

Есть несколько методов реализации виртуальной памяти, и их выбор зависит от конкретной архитектуры и требований операционной системы. Вот несколько основных видов реализации виртуальной памяти:

Страничной метод организации:

Одним из наиболее распространенных методов реализации виртуальной памяти является использование страниц. Физическая память и виртуальная память разбиваются на фиксированные блоки, называемые страницами. Размер страницы обычно составляет от нескольких килобайт до нескольких мегабайт, обычно он составляет 4КБ. Фишка этого метода заключается в возможности выгрузки не нужных страниц во вторичную память, а при их надобности снова вернуть обратно.

Для отслеживания соответствия виртуальных страниц физическим страницам используется таблица страниц. Эта таблица содержит записи для каждой виртуальной страницы, указывающие на соответствующую физическую страницу. Физическая страница в этой модели называется, фрейм (Frame). Для каждого процесса в ОС создаётся своя собственная таблица страниц.

Вот из что содержит таблица страниц.

Номер физической страницы (Frame Number): Этот номер указывает на конкретный фрейм, куда отображается соответствующая виртуальная страница. Каждая запись в таблице страниц обычно содержит этот номер.

Информация управления: содержит информацию о разрешения на чтение/запись, доступ, привилегии и другие. Они используются для контроля доступа к данным и управления процессами.

Информация о валидности: содержит информацию, действительна ли запись в таблице. Если страница валидна, то запись считается действительной, и виртуальный адрес может быть преобразован в физический. В противном случае, возникает промах страницы (page fault), и операционная система должна загрузить нужную страницу из вторичной памяти.

Операционная система обрабатывает этот промах, загружая необходимую виртуальную страницу из вторичной памяти (например, с диска) в свободное место оперативной памяти. Затем обновляется таблица страниц.

Если в оперативной памяти нет свободного места для загрузки новой виртуальной страницы из диска, операционная система должна освободить место. Для этого используются алгоритмы замещения страниц. Эти алгоритмы определяют, какую страницу выгрузить во второстепенную память для освобождения места.

Два этих процесса вышеперечисленных называют свопинг(swapping), процесс перемещения страниц из оперативной памяти во вторичную и обратно.

Информация о модификации: содержит информацию, были ли данные в физической странице изменены. Он используется для оптимизации процесса выгрузки страницы во вторичную память при нехватки оперативной памяти.

Давайте рассмотрим, как выглядит структура виртуального адреса в страничной модели.

Предположим, что у нас есть виртуальная память, разбитая на страницы фиксированного размера. Виртуальный адрес будет состоять из двух частей.

Номер страницы (Page Number): эта часть адреса указывает на конкретную страницу в виртуальной памяти, к которой обращается программа. Номер страницы используется для индексации в таблице страниц, где хранится информация о соответствии виртуальных страниц физическим.

Смещение внутри страницы (Offset): это значение указывает на конкретный байт внутри выбранной страницы. Смещение представляет собой смещение от начала страницы и используется для того, чтобы определить, на каком именно месте в странице расположены нужные данные.

Предположим, у нас есть виртуальная память с размером страницы 4 КБ (4096 байт), и виртуальный адрес равен 0x2002, переведём в десятичную форму, для удобства, будет 8194. Разбиваем этот адрес на номер страницы и смещение:

Номер страницы = 8194 / 4096=2 (с округлением)

Смещение внутри страницы = 8194mod4096=2

Таким образом, виртуальный адрес 8194 разбивается на номер страницы 2 и смещение внутри страницы 2. Это указывает на то, что данные находятся во второй странице виртуальной памяти, начиная с адреса 2 внутри этой страницы.

Если уменьшить часть виртуального адреса, отвечающая за номер страницы, то количество страниц уменьшится, а размер одной страницы увеличится, и полностью наоборот, если уменьшить размер одной страницы, то количество возможный страниц, уменьшиться, а размер одной страницы увеличится.

Страница является наименьшей единицей выделения памяти для процесса. Когда программа запрашивает блок виртуальной памяти, ей выделяется целая страница, даже если она не будет использовать всю эту память. Если размер запрошенной памяти не является точным кратным размеру страницы, то часть последней страницы может быть не использована, что приводит к проблеме, которая называется внутренней фрагментации. Например, если размер страницы составляет 4 КБ, а программа запрашивает 10 КБ памяти, ей будет выделено 3 страницы (12 КБ), и 2 КБ останутся неиспользованными, создавая проблему внутренней фрагментации.

Так же есть проблема внешней фрагментации возникает в результате повторных выделений и освобождений блоков памяти. Когда блоки освобождаются, они создают неразмеченные промежутки, которые могут быть слишком маленькими, чтобы удовлетворить будущие запросы на выделение памяти. Например, представьте, что у вас есть куча блоков памяти разного размера. Вы берете и выдаёте блоки несколько раз. В результате возникают маленькие кусочки свободной памяти, которые нельзя использовать для выделения больших блоков. Это и есть внешняя фрагментация - память есть, но она разбросана и невозможно найти последовательный кусок памяти подходящий под наше требование.

Из этого вытекает что, чем больше страниц, тем больше таблица страниц, а это замедляет процесс перевода виртуального адреса в физический, а чем меньше страниц, тем больше потери памяти, вызванные внутренней фрагментации.

Сегментный метод организации:

Сегментная модель памяти – это способ организации виртуальной памяти, в которой адресное пространство делится на несколько логических блоков, называемых сегментами. Каждый сегмент имеет свой собственный размер и базовый адрес в физической памяти. Эта модель была широко использована в ранних компьютерных системах и операционных системах, таких как Intel 8086/8088, и была частично сохранена в более поздних архитектурах x86.

Основные компоненты сегментной модели памяти.

Сегментные регистры: В архитектуре x86, например, существуют сегментные регистры, такие как CS (Code Segment), DS (Data Segment), SS (Stack Segment), и ES (Extra Segment). Каждый из этих регистров хранит базовый адрес соответствующего сегмента.

Таблица сегментов: эта таблица хранит информацию о каждом сегменте в виртуальном адресном пространстве процесса. В этой таблице для каждого сегмента указывается его физический адрес.

Виртуальный адрес: виртуальный адрес в сегментной модели представляется парой: номер сегмента и смещение внутри сегмента. Таким образом, весь виртуальный адрес формируется путем объединения номера сегмента и смещения.

Права доступа: для каждого сегмента формируется свои права доступа, такие как чтение, запись или выполнение

Эти две модели организации памяти очень похожи, но в чём же их отличие?

Блоки:

  • Страничная модель: виртуальное адресное пространство разбивается на фиксированные блоки одинакового размера.

  • Сегментная модель: виртуальное адресное пространство делится на логически связанные сегменты произвольных размеров.

Фрагментация:

  • Страничная модель: может страдать от внутренней фрагментации, когда страницы используются не полностью, что может привести к неэффективному использованию памяти.

  • Сегментная модель: может страдать от внешней фрагментации, когда различные сегменты разбросаны по физической памяти, что может затруднить выделение больших блоков для выполнения задач.

Права доступа:

  • Страничная модель: позволяет применять права доступа (например, чтение, запись, выполнение) на уровне страницы.

  • Сегментная модель: позволяет применять права доступа на уровне сегментов, что может быть более удобным для организации доступа к разным частям программы или данных.

В современных операционных системах (ОС) широко используется комбинированный подход, странично-сегментный подход в организации виртуальной памяти. Этот метод стал популярным из-за ряда преимуществ, которые он предоставляет.

Одной из ключевых причин популярности странично-сегментного подхода является его гибкость. Сегменты позволяют программистам логически группировать данные и инструкции, что облегчает разработку и поддержку приложений. В то же время страничная организация обеспечивает эффективное использование физической памяти, разделяя ее на страницы и загружая их в оперативную память по мере необходимости.

Мы всё говорим о виртуальной памяти, но не заикнулись про компонент, который является связывающим звеном между виртуальной памятью и физической и это Memory Management Unit.

Memory Management Unit (MMU) - это аппаратный компонент, связывающее звено между процессором и оперативной памятью, обычно он встроен в сам процессор, его основная задача - преобразование виртуальных адресов, используемых программами, в физические адреса, связанные с конкретными ячейками оперативной памяти. Операционная система загружает базовый адрес созданной таблицы страниц в регистр MMU. Этот базовый адрес используется MMU для поиска нужной записи в таблице страниц. Если данный компонент выключен, то виртуальный адрес просто приравнивается к физическому, если включен, то производится трансляция. Поскольку таблица страниц может быть большой, поиск соответствия может занимать время. Вместо того, чтобы каждый раз обращаться к таблице страниц, MMU использует буфер, который содержит небольшое количество записей с последними или наиболее часто используемыми соответствиями виртуальных и физических адресов.

Translation Lookaside Buffer (TLB), представляет собой кэш внутри MMU (Memory Management Unit), который используется для ускорения процесса преобразования виртуальных адресов в физические.

Вот так выглядит процесс взаимодействия MMU и TLB

  1. Поиск в TLB: MMU проверяет TLB для определения, есть ли соответствующая запись виртуального и физического адресов. Если такая запись найдена, MMU может использовать физический адрес из TLB без необходимости обращения к полной таблице страниц.

  2. Отсутствие соответствия в TLB: если запись не найдена в TLB, MMU обращается к таблице страниц и, возможно, обновляет TLB с новой записью для будущего использования.

  3. Замена записей в TLB: поскольку TLB имеет ограниченный размер, при появлении новых записей старые могут быть вытеснены.

Вот мы и разобрали основное устройство памяти в ОС. Ещё одну тему, которую я хочу для вас освятить, это как ОС организует пользовательские данные.

Организация пользовательских данных

ОС предоставляет пользователю средства для хранения и организации информации. Основной метод организации данных - использование файлов и файловых систем.

Файл — это набор данных, объединенных под общим именем, которое пользователь или программа может использовать для доступа к этим данным. Файл может содержать текстовую информацию, изображения, звуки, программный код и другие типы данных. Файлы обычно именуются, чтобы их легче было идентифицировать.

Из чего он состоит?

Имя файла (File Name): имя файла — это уникальная метка, по которой файл идентифицируется в файловой системе (дальше рассмотрим, что это). Оно служит для обозначения файла и его различения от других файлов. Имя файла может содержать буквы, цифры и определенные специальные символы в зависимости от правил файловой системы.

Расширение файла (File Extension): расширение файла добавляется к имени файла и представляет тип содержимого файла. Это помогает операционной системе и программам определить, как следует обработать файл. Например, в файле "image.jpg" расширение - "jpg", указывающее, что это файл с изображением в формате JPEG. Расширение файла не гарантирует то, что этот файл именно такого типа, мы можем взять файл audio.mp4 и вручную поменять его расширение в audio.txt.

Размер файла (File Size): размер файла указывает на объем занимаемого им пространства на носителе данных.

Права доступа (File Permissions): права доступа определяют, какие операции пользователи и группы пользователей могут выполнять с файлом. Обычно это права на чтение, запись и выполнение файла.

Время создания, изменения и доступа (File Timestamps): эти временные метки отражают моменты в истории файла. Время создания указывает, когда файл был создан, время изменения - когда последний раз менялось содержимое файла, и время доступа - когда файл был последний раз открывался.

Содержимое файла: фактическое содержимое файла, будь то текст, изображения, исполняемый код или что-то еще.

Файловая система

Файловая система (File System) — это механизм, обеспечивающий организацию и хранение файлов на носителях данных, таких как жесткие диски, SSD-накопители, флеш-диски и др. Она управляет созданием, удалением, чтением и записью файлов, а также предоставляет структуру для организации файлов.

Вот некоторые основные концепции и компоненты файловых систем.

Каталоги (папки): каталоги используются для организации файлов в иерархическую структуру. Они позволяют группировать файлы по определенным критериям, делая управление файлами более структурированным и понятным для нашего восприятия. Каталог тоже может являться файлом, который будет содержать внутри себя другие файлы.

Путевые имена: каждый файл в файловой системе имеет уникальный путь, который указывает на его местоположение в иерархии каталогов. Путь состоит из имен каталогов, разделенных специальным символом.

Разделы и тома: файловая система обычно организуется на уровне разделов или томов. Разделы и тома — это области физического устройства, выделенные для хранения файловой системы.

Форматирование: процесс создания файловой системы на устройстве называется форматированием. В этот момент определяются правила хранения информации, структура каталогов и т. д.

Файловые операции: файловая система предоставляет интерфейс для различных операций с файлами, таких как чтение, запись, удаление, переименование и т. д.

Пример изображение иерархической структуру расположения файлов
Пример изображение иерархической структуру расположения файлов

Существует множество файловых систем, каждая из которых имеет свои особенности и применение. Разбирать их все по отдельности не будем, так как это уже другая тема и займёт много времени.

Ниже представлен обзор нескольких самых популярных файловых систем:

FAT32(File Allocation Table 32):

Ограничения по размеру файла и раздела:

  • Максимальный размер файла: 4 ГБ.

  • Максимальный размер раздела: 2 ТБ.

Поддержка ОС: FAT32 поддерживается практически всеми операционными системами, включая Windows, macOS, Linux и другие.

Преимущества:

  • Простота и совместимость: FAT32 поддерживается множеством устройств и операционных систем.

Недостатки:

  • Ограничение по размеру файла: 4 ГБ может быть недостаточным для современных файлов, таких как видео HD, долгие звуковые файлы и др.

  • Не обеспечивает надежности и безопасности данных, так как не поддерживает механизмы, такие как журналирование.

NTFS(New Technology File System):

Ограничения по размеру файла и раздела:

  • Максимальный размер файла: 16 ЭБ (экзабайт).

  • Максимальный размер раздела: 256 ТБ.

Поддержка ОС: NTFS является стандартной файловой системой для операционных систем Windows, начиная с Windows NT и включая более новые версии.

Преимущества:

  • Поддержка различных функций безопасности, включая шифрование, контроль доступа и журналирование.

  • Поддержка файлов большого размера, подходит для современных потребностей.

Недостатки:

  • Ограниченная поддержка на некоторых не-Windows системах.

EXT4(Fourth Extended File System):

Ограничения по размеру файла и раздела:

  • Максимальный размер файла: 16 ТБ.

  • Максимальный размер файловой системы: 1 экзабайт.

Поддержка ОС: Ext4 является стандартной файловой системой для многих дистрибутивов Linux.

Преимущества:

  • Поддержка журналирования для улучшения безопасности и восстановления данных после сбоев системы.

  • Хорошая производительность при работе с большими файлами и высокой нагрузкой.

  • Поддержка различных функций, таких как расширенные атрибуты, сжатие и др.

Недостатки:

  • Не всегда совместим с другими операционными системами. Требуется дополнительное программное обеспечение для доступа с Windows.

  • В отличие от NTFS, не поддерживает встроенные механизмы шифрования.

Управление ресурсами

Когда вы используете компьютер, ОС играет роль своего рода "хозяина", управляя ресурсами компьютера. Ресурсы могут включать в себя файлы, память, устройства ввода/вывода, и многое другое. Когда программы работают на компьютере, им нужны способы общения с этими ресурсами. Вот где в игру вступает система управления ресурсами в ОС.

Для того чтобы программы могли взаимодействовать с ресурсами, ОС использует структуры данных, называемые дескрипторами.

Дескриптор – это своего рода идентификатор, который предоставляет программе доступ к определенному ресурсу. Например, если у вас есть открытый файл в программе, вашей программе нужен способ сказать ОС, с каким именно файлом она хочет взаимодействовать, при вызове определенного системного вызова мы передаём ОС дескриптор, чтобы она поняла с каким именно ресурсом, мы хотим провести операцию.

Вся информация о дескрипторах содержится в таблице дескрипторов.

Таблица дескрипторов — это таблица, которая ОС использует для отслеживания всех дескрипторов, которые программы создали. Когда программа запросит у ОС новый ресурс (например, открытие файла), ОС предоставит программе дескриптор для доступа к этому ресурсу. Затем ОС записывает этот дескриптор в таблицу дескрипторов.

Таблица дескрипторов содержит не только сами дескрипторы, но и другую информацию, необходимую информацию.

Флаги и атрибуты: каждый дескриптор может иметь связанные с ним флаги или атрибуты, которые указывают на определенные характеристики ресурса или способы его использования. Например, флаг может указывать, можно ли читать или записывать в файл, или активен ли определенный процесс.

Данные ресурсы: в таблице дескрипторов может быть храниться указатель на фактические данные ресурса или на структуру данных, связанную с этим ресурсом. Например, если это файл, указатель может вести к блоку данных на диске.

Права доступа и уровни привилегий: для обеспечения безопасности и контроля над доступом, таблица дескрипторов может включать информацию о правах доступа, которые различные процессы или пользователи имеют доступ к конкретным ресурсам.

Информация о состоянии: дескрипторы могут содержать информацию о текущем состоянии ресурса или связанного с ним процесса. Например, дескриптор файла может указывать, открыт ли файл в данный момент и если он открыт и если вдруг другой процесс захочет удалить этот файл, то он не сможет это совершить, пока данный ресурс будет занят другим процессом.

На этом я считаю можно закончить, мы разобрали самые основные аспекты ОС, взглянули как они могут быть устроены. Важно отметить, что хотя конкретные реализации ОС могут различаться, основные принципы и механизмы остаются схожими. Цель нашего обзора заключалась в предоставлении читателю общего представления о том, как устроены операционные системы, а также в выделении ключевых элементов, формирующих их функциональность.

ОС создаёт нам большую абстракцию от низкоуровневых вещей, позволяя не задумываться как там всё внутри устроено, поэтому она является неотъемлемой частью каждого современного устройства!

Комментарии (13)


  1. segment
    27.11.2023 14:35
    +8

    Пролистал до конца "статьи" и не прогадал, что в конце будет ссылка не телегу. Маркер, что с большой вероятностью это будет просто оберткой над ссылкой.


    1. Lainhard
      27.11.2023 14:35

      Точно! По пофмулировке и структуре первого того предложения с "это" сразу прыгнул в комменты в поисках единомышленников на предмет того, что статья банальна и неинтересна.


  1. uuger
    27.11.2023 14:35
    +15

    Я просто оставлю это здесь


  1. firehacker
    27.11.2023 14:35
    +6

    Каждый сегмент имеет свой собственный размер и базовый адрес в физической памяти.

    Нет, базовый адрес сегмента — это линейный адрес, а не физический. Они не тождественны в общем случае, и только если бит PG в CR0 выключен, линейный адрес будет численно равен физическому.

    Но главное, что поскольку это именно линейный адрес, сегмент может быть и как угодно фрагментирован в физпамяти, а сточки зрения кода это будет непрерывный блок.

    Каждый из этих регистров хранит базовый адрес соответствующего сегмента.

    Нет, не хранит. В реальном режиме он хранит индекс параграфа физпамяти, к которому будет приплюсовано смещение.

    В защищенном режиме он хранит селектор, который состоит из битовых полей, отдельные биты которого задают индекс дескриптора сегмента в глобальной/локальной таблице дескрипторов.

    Но, что это интересно, в исходном ошибочном утверждении таки есть доля правды: с введением защищенного режима у сегментных регистров появилась невидимая пользователю теневая часть, в которую процессор заносит базу, лимит, атрибуты доступа соответствующего сегмента, чтобы затем при каждом обращении к сегменту не читать эту информацию из GDT/LDT. То есть это такой кеш для текущего элемента GDT/LDT.

    Эта модель была широко использована в ранних компьютерных системах и операционных системах, таких как Intel 8086/8088, и была частично сохранена в более поздних архитектурах x86.

    Чего это частично? По мере развития 8086/80186/80286/80386/80486/80586 от сегментов никто не отказывался и их функциональность никак не урезал. Наоборот, появились регистры FS и GS.

    Это только в amd64 решили рубить с плеча, потому что парадигма поменялась: раньше инновации в области процессорных архитектур мотивировали системных арограммистов на интересные решения в системном софте, а теперь тупо спрос рождает предложения.

    Сегментная модель: может страдать от внешней фрагментации, когда различные сегменты разбросаны по физической памяти, что может затруднить выделение больших блоков для выполнения задач.

    Как раз таки не страдает, в отличие от архитектур с flat-моделью памяти. Вот единое адресное пространство процесса очень легко фрагментировать микровыделениями, так, что свободно будет 80%, но нельзя будет выделить непрерывный блок, размером хотя бы 20% от суммарного объема свободной части АП.

    А вот с сегментами такой проблемы нет, она есть только на уровне фрагментации линейного адресного пространства. Ах, если бы инженеры Intel сделали PDBR не в составе CR3, а в составе дескриптора сегмента... в этом случае проблема фрагментации была бы решена на корню.

    Но и без этого, фрагментацию линейного адресного пространства можно было бы решить дефрагментацией: блоки данных можно было в нужные моменты времени сдвигать/раздвигать в линейном АП, чтобы устранить или расширить гэпы. При этом даже копирование данных не пришлось бы делать: достаточно править PDE/PTE, меняя трансляцию линейных адресов в PFN-ы.

    В любом случае, для прикладного кода, который оперирует только селекторами, переезд содержимого сегмента в линейном АП вообще никак не останется замеченным: селектор тот же, смещения те же.

    А вот с фрагментацией плоского АП сделать нельзя абсолютно ничего: нельзя разрозненные блоки памяти сдвинуть вместе, потому что программа держит миллион адресов, указывающих внутрь этих блоков, и все они станут недействительными.


  1. SIISII
    27.11.2023 14:35
    +2

    Во-первых, если писать обзорную статью и говорить о "сферической ОС в вакууме", то останавливаться на сегментации, применяемой в интеловской архитектуре (IA-32 aka x86), нет никакого смысла: рассказывать надо об общих идеях, а не о специфических реализациях. То же относится к конкретным разновидностям файловых систем и т.д. и т.п.

    Во-вторых, останавливаться в обзорной статье на интеловском механизме сегментации смысла нет ещё и потому, что 1) он специфичен для интеловской архитектуры, и в других архитектурах ничего подобного нет; и 2) он оказался крайне неудачным, и им пользовались только потому, что были вынуждены. Когда появился 80386 с его нормальным MMU "как у всех", от сегментации, по сути, отказались и используют плоское адресное пространство. Естественно, технически без сегментации обойтись всё равно невозможно, так как этого требует аппаратура, но сегментные регистры настраиваются ОС так, что используется единое плоское адресное пространство -- т.е. технически сегментация есть, а логически -- нет. Ну а AMD, расширяя архитектуру до 64 битов, в 64-разрядном режиме вообще сегментацию, по большому счёту, выпилила: если память не изменяет, FS и GS можно использовать как дополнительные базовые регистры, но сегментов как таковых, со всеми их атрибутами, уже нет; они сохраняются только в 16/32-разрядных режимах для обеспечения обратной совместимости.

    Ну а в-третьих, в "ранних компьютерных системах" сегментной модели как раз не было. Когда появилась поддержка виртуальной памяти, она сразу была организована по страницам, причём у разных архитектур отличия лишь технические, но не концептуальные. Сегментацию, вполне возможно, придумала Интел -- и ничего хорошего из этого не вышло. (Замечу попутно, что под сегментацией и в статье, и мною понимается то, как это определено в интеловской архитектуре; само слово "сегмент" применяется и в других архитектурах, но имеет совершенно иной смысл. Например, в IBM System/370 таблицы переадресации, лежащие в основе работы MMU, имеют два уровня: на нижнем лежат таблицы страниц, а на верхнем -- таблицы сегментов; в современной IBM z/Architecture уровней уже пять: помимо классических таблиц сегментов и страниц, пришедших с некоторыми расширениями из Системы 370, там реализовали три уровня таблиц регионов, чтобы эффективно управлять всем 64-разрядным виртуальным адресным пространством).


  1. GBR-613
    27.11.2023 14:35

    На мой взгляд, статья никуда не годится.

    Ядро это мозг. Спасибо, объяснили. А что тогда печень и почки? На самом деле, насколько я понимаю, ядро это код, который постоянно загружен в память (в отличие от процессов, которые могут заканчиваться или временно прерываться).

    И хотел бы я знать, как новичок поймёт выражение "у каждого процесса собственная оболочка"?

    И т.д.


    1. SIISII
      27.11.2023 14:35

      На самом деле, ядро даже не обязано целиком быть в памяти. Скажем, в древней OS/360 (вообще, кажется, первая "большая" и "настоящая" ОС в истории) и во вполне современной Винде часть кода ядра находится в памяти постоянно, а часть -- по нужде (хотя механизмы подгрузки/выгрузки кода пространства ядра в этих двух системах кардинально различаются).


      1. GBR-613
        27.11.2023 14:35

        Это вопрос терминологии. Можно при желании обьявить то, что погружается, не-ядром.

        Но вообще разные ОС гораздо больше отличаются друг от друга, чем можно понять из этой статьи. Например, в мейнфремах нет понятий директорий и файл. Наиболее близкое к понятию "файл" там называется dataset. A в AS/400 file это директорий, внутри которого лежит member.

        Кроме того, есть ещё вопрос, что такое вообще ОС. От этого, например, зависит холиварный вопрос "является ли Android разновидностью Linux". Во времена моего детства во всех учебниках было написано: ОС состоит из того, другого, и компиляторов. Сейчас, как-то, все привыкли к тому, что ни одного компилятора на компьютере может и не быть.


        1. SIISII
          27.11.2023 14:35

          Применительно к ядру насчёт терминологии не соглашусь. Точней, объявить-то можно, но вопрос заключается в режиме работы процессора при исполнении этого кода: именно он определяет, ядро это (работа в привилегированном режиме) или не ядро (работа в непривилегированном режиме). В остальном -- да, согласен.


          1. GBR-613
            27.11.2023 14:35

            А разве ядро не может позволить постороннему процессу работать в привилегированном режиме?


            1. SIISII
              27.11.2023 14:35

              В принципе, может. Но обычно такой возможностью не пользуются (и я не знаю, где это возможно, кроме OS/360 с её MODESET). Плюс, ещё в каком адресном пространстве находится исполняемый код (у OS/360 тут тоже неоднозначно: изрядная часть системного по своей сути кода, насколько помню, грузилась в разделы задач пользователей).


              1. GBR-613
                27.11.2023 14:35

                Ну да. Поэтому привилегированный режим это не критерий.
                В реальности, что ядро операционной системы, а что нет, определяет единственно документация самой этой операционной системы.


  1. quarus
    27.11.2023 14:35

    Нет сходимости:

    Если уменьшить часть виртуального адреса, отвечающая за номер страницы, то количество страниц уменьшится, а размер одной страницы увеличится, и полностью наоборот, если уменьшить размер одной страницы, то количество возможный страниц, уменьшиться, а размер одной страницы увеличится.