Как работают операционные системы ... без операционной системы?


167

Мне действительно любопытно прямо сейчас. Я программист на Python, и этот вопрос меня просто ошеломил: вы пишете ОС. Как вы управляете этим? Он должен быть запущен как-то, и таким образом в другой ОС?

Как приложение может работать без операционной системы? Как вы скажете компьютеру запустить, скажем, C, и выполнить эти команды на экране, если на нем нет операционной системы для запуска?

Связано ли это с ядром UNIX? Если это так, что такое ядро ​​Unix или вообще ядро?

Я уверен, что операционные системы сложнее, но как это работает?


14
Я совершенно уверен, что для этого и нужен BIOS - это действительно маленькая ОС, которая загружает более крупную ОС.
Seseseacat

64
Операционная система удобна , но она вам не нужна для запуска программ на компьютере.
Андрес Ф.

10
Вполне возможно даже написать не-ОС программное обеспечение без ОС. Многие интерпретаторы Forth традиционно работали без ОС (или вы могли бы сказать, что они были ОС). Это даже не так сложно. Если вы знаете C, вам может понравиться писать такую ​​программу (возможно, небольшую игру) как учебное упражнение.
Макс

44
Эта путаница - одна из издержек замечательных, безопасных, сильно абстрагированных вычислительных систем, которые мы используем в наши дни: люди могут быть очень хорошими и компетентными программистами и даже не знать даже основ работы компьютера. Как низко вы хотите пойти? Для очень низкого, но все же выше физики см. Как были запрограммированы первые микропроцессоры? на электронике.
dmckee

2
Программирование было сделано до изобретения текущей концепции ОС. Очевидно, что что-то на этом уровне запускает ОС. ОС загружаются. Это обычно, по крайней мере, упоминается в программе CS 4 года в какой-то момент, так как для большинства требуется курс по теории компьютерных систем.
Рог

Ответы:


263

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

Он начинается с прошивки на материнской плате, которая пытается запустить процессор. Затем он загружает BIOS, похожий на мини-операционную систему, которая запускает и запускает другое оборудование. После этого он ищет загрузочное устройство (диск, компакт-диск и т. Д.) И, найдя его, находит MBR (главную загрузочную запись), загружает его в память и выполняет его. Это тот маленький кусочек кода, который затем знает, как инициализировать и запустить операционную систему (или другие загрузчики, поскольку все усложняется). Именно в этот момент такие вещи, как ядро, будут загружены и начнут работать.

Довольно невероятно, что это работает вообще!


108
+1 за последнее предложение.
CVn

39
Есть причина, по которой это называется «загрузка»; Термин сокращенно означает «самозагрузка», как, например, «подтягивание себя за самозагрузку».
KeithS

5
Было время, когда кто-то должен был вводить или включать код начальной загрузки. Иногда это был простой переход к первой инструкции программы в ПЗУ. В других случаях это был код для чтения с устройства и перехода к первой инструкции программы в данных, которые были прочитаны. Теперь все намного проще.
BillThor


15
@BillThor: Где под «гораздо проще» вы, конечно, подразумеваете «гораздо сложнее». Они просто проще в использовании .
Рафаэль Швейкерт

173

«Чистая металлическая» операционная система не работает внутри чего-либо. Он выполняет полный набор инструкций на физической машине и имеет доступ ко всей физической памяти, всем регистрам устройства и всем привилегированным инструкциям, включая те, которые управляют аппаратным обеспечением поддержки виртуальной памяти.

(Если операционная система работает на виртуальной машине, она может подумать, что она находится в той же ситуации, что и выше. Разница в том, что некоторые вещи эмулируются или каким-то другим образом обрабатываются гипервизором; т. Е. Уровень, на котором работают виртуальные машины. .)

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

Как приложение может работать без операционной системы?

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

Как вы скажете компьютеру запустить, скажем, C, и выполнить эти команды на экране, если на нем нет операционной системы для запуска?

Вы не

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

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

Ответ Билла охватывает начальную загрузку, которая представляет собой процесс перехода от выключенного компьютера к компьютеру, на котором установлена ​​и работает обычная операционная система. Тем не менее, стоит отметить, что когда BIOS выполняет свои задачи, он (как правило) передает полный контроль над оборудованием основной операционной системе и не играет никакой роли - до следующего перезапуска системы. Основная ОС, безусловно, не работает "в" BIOS в общепринятом смысле.

Связано ли это с ядром UNIX? Если да, что такое ядро ​​Unix или вообще ядро?

Да.

Ядро UNIX является ядром операционной системы UNIX. Это часть UNIX, которая выполняет все "голое железо", описанное выше.

Идея «ядра» заключается в том, что вы пытаетесь разделить системное программное обеспечение на основные компоненты (для которых требуется доступ к физическим устройствам, всю память и т. Д.) И неосновные компоненты. Ядро состоит из основного материала.

В действительности, различие между ядром / ядром и не ядром / не ядром является более сложным, чем это. И было много споров о том, что действительно принадлежит ядру, а что нет. (Посмотрите, например, микроядро.)


6
Феноменальный ответ. Я бы дал вам еще несколько голосов, если это возможно.
weberc2

7
Здесь много хороших ответов, поэтому я добавлю это в качестве комментария, поскольку никто до сих пор не упомянул об этом: одна из ключевых функций в ОС позволяет нескольким приложениям запускаться «одновременно» с точки зрения пользователя. Возможность планировать процессы и защищать процессы от изменения поведения друг друга - это, как правило, функции, которые можно найти только в ОС, а не в микропрограмме или BIOS.
Шон Барбо,

2
The idea of a "kernel" is that you try to separate the system software into core stuffЛегко запомнить, отметив, что термин kernelот немецкого Kern, что означает ядро ​​/ ядро.
deed02392

1
Мне

3
«Отход от этого сделал пользователей ПК менее умными». - Не менее умный ... менее компьютерный грамотный. Можно также сказать, что это увеличило количество пользователей ПК.
Стивен К

62

В начале не было питания в процессоре.

И Человек сказал: «Да будет сила», и ЦПУ начал читать с данного адреса в памяти и выполнять инструкцию, которая там присутствовала. Потом следующий и так до конца власти.

Это была загрузка. Его задача состояла в том, чтобы загрузить другую часть программного обеспечения, чтобы получить доступ к среде, где находилось основное программное обеспечение, и загрузить его.

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


58
Этот ответ следует перенести на christianity.stackexchange.com
Coomie

6
что такое «данный адрес», откуда он взялся. Извините за игру Чарльза Дарвина здесь.
Midhat

7
@Midhat - Первый адрес, который выбирает процессор, встроен в него. Обычно это 0.
Mouviciel

17
... А на 7-й день Человек отдыхал со своими играми
канадец Лука

9
@mouviciel Адрес в памяти 0x7C00для любой x86совместимой архитектуры, и вначале он должен быть заполнен BIOS, который обычно загружает первый сектор любого загрузочного устройства, которое он предпочитает ... Хотя хороший ответ: -7
Тобиас Кинцлер

29

Извините за опоздание, но я опишу это так:

  • Материнская плата получает питание.

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

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

    - jkerian в 5:20 25 октября

  • Питание подается на процессор и оперативную память.

  • Процессор загружает (основываясь на внутренней проводке) данные из BIOS. На некоторых машинах BIOS может быть отражен в ОЗУ, а затем запущен оттуда, но это редко встречается.

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

    -Micheal Steil, 17 ошибок Microsoft, допущенных в системе безопасности Xbox ( архив )

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

  • Код BIOS (посредством настроек CMOS, хранящихся в аппаратном обеспечении) использует низкоуровневые команды IDE или SATA для считывания загрузочного сектора каждого диска в порядке, указанном CMOS или пользователем, переопределенным с помощью меню.

  • Первый диск с загрузочным сектором выполняет загрузочный сектор. Этот загрузочный сектор является сборкой, в которой есть инструкции для загрузки большего количества данных с диска, загрузки большего NTLDRобъема GRUB, последующих этапов и т. Д.

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

Затем вы испытываете дружественную панику в ядре, удушающий пингвин, или ваш диск останавливается из-за падения головы. =) В альтернативном сценарии ваше ядро ​​устанавливает таблицы процессов, структуры в памяти и монтирует диски, загружая драйверы, модули и графический интерфейс пользователя или набор служб (если они находятся на сервере). Затем программы выполняются по мере чтения их заголовков, а их сборка заносится в память и отображается соответственно.


2
Следует отметить, что многие вещи, такие как «схемы синхронизации запускаются и стабилизируются при необходимости», на самом деле больше не происходят в аппаратном обеспечении. Огромный объем этой работы на самом деле является чрезвычайно специализированным программным обеспечением, работающим на очень ограниченных подпроцессорах / секвенсорах. - Дружелюбный инженер по прошивке соседей
jkerian

@jkerian Вы не возражаете, что я цитировал ваш комментарий в своем посте?
ζ--

хе, совсем нет.
jkerian

BIOS не является операционной системой. BIOS - это сокращение от Basic Input / Output System, и именно это делает BIOS. Это позволяет программистам использовать ресурсы низкого уровня с предоставленными производителем драйверами. Когда ОС переходит в защищенный (32-битный) или длинный (64-битный) режим, BIOS перестает быть доступным, и ОС использует свои собственные драйверы, которые в основном заменяют функциональные возможности, предоставляемые BIOS на «более низких» уровнях. Современные операционные системы, например Linux и Windows, используют BIOS только для определения доступных разделов ОЗУ и загрузки своего более продвинутого загрузчика, который может загружать необходимые драйверы.
Ханнес Карппила

1
@HannesKarppila Обновлено; Сейчас ему около четырех лет, и я больше не активен на этом сайте.
ζ--

15

Есть много хороших ответов, но я хотел бы добавить следующее: вы упомянули, что вы пришли из Python. Python - это не интерпретируемый (или «сшитый» или любой другой, по крайней мере, в типичных случаях использования CPython) язык. Это означает, что у вас есть другое программное обеспечение (интерпретатор Python), которое просматривает исходный код и каким-то образом выполняет его. Это прекрасная модель, которая позволяет довольно хорошие языки высокого уровня, абстрагированные от реального оборудования. Недостатком является то, что вам всегда нужно это программное обеспечение переводчика в первую очередь.

Такое программное обеспечение интерпретатора, как правило, написано на языке, который компилируется в машинный код, например, C или C ++. Машинный код - это то, что процессор может обрабатывать. Что может сделать процессор - это прочитать несколько байтов из памяти и в зависимости от значений байтов запустить определенную операцию. Таким образом, одна байтовая последовательность - это команда для загрузки некоторых данных из памяти в регистр, другая последовательность для добавления двух значений, другая для сохранения значения из регистра обратно в основную память и в ближайшее время (регистр - это специальная область памяти, которая является частью процессора, где он может работать лучше всего), большинство этих команд довольно низко на этом уровне. Человек, который может читать эти инструкции машинного кода, является кодом ассемблера. Этот машинный код, по сути, является тем, что хранится в файлах .exe или.com в Windows или в бинарных файлах Linux / Unix.

Теперь, если компьютер запущен, он тупой, хотя у него есть проводка, которая будет читать такие инструкции машинного кода. На ПК это обычно (в настоящее время) микросхема EEPROM на материнской плате, содержащая BIOS (базовая выходная система ввода-вывода), эта система мало что может сделать, она может облегчить доступ к некоторому оборудованию и т. Д., А затем выполнить ключевую операцию: перейти к загрузите и скопируйте первые несколько байтов (также как основная загрузочная запись, MBR) в память, а затем скажите ЦПУ «здесь, ваша программа», тогда ЦП обработает эти байты как машинный код и выполнит его. Как правило, это какой-то загрузчик операционной системы, который загружает ядро ​​с некоторыми параметрами, а затем передает управление этому ядру, которое затем загружает все его драйверы для доступа ко всему аппаратному обеспечению, загружает некоторую программу для настольного компьютера или оболочки или что-то еще и позволяет пользователю войти в систему и использовать систему.


6
"interpiled"? Я никогда не слышал этот термин раньше.
Брайан Оукли

3
Этот термин использовался около 5 лет назад для описания «современных» интерпретаторов, у которых есть отдельная фаза компиляции, которая отделена от исполнения. Понятия не имею, сохранился ли этот термин где-нибудь ;-)
Йоханнес

1
"ninterpreted"? Я никогда не слышал этот термин раньше.
Коул Джонсон

12

Вы спрашиваете: «Как приложение может работать без операционной системы?». Простой ответ: «ОС - это не приложение». Хотя ОС может быть создана с помощью тех же инструментов, что и приложение, и изготовлена ​​из одного и того же сырья, это не одно и то же. ОС не должна играть по тем же правилам, что и приложение.

OTOH, вы можете думать о реальном оборудовании и прошивке как о «ОС», в которой работает «приложение» ОС. Аппаратное обеспечение - очень простая ОС - оно знает, как выполнять инструкции, написанные в машинном коде, и знает, что при запуске оно должно посмотреть на очень специфический адрес памяти для своей первой инструкции. Итак, он запускается, а затем сразу же запускает ту самую первую инструкцию, затем вторую и так далее.

Итак, ОС - это просто машинный код, который существует в известном месте и может напрямую взаимодействовать с оборудованием.


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

6

Ответ на ваш вопрос требует знания того, как выглядит нативный (для процессора) код и как он интерпретируется процессором.

Обычно весь процесс компиляции основан на переводе того, что вы пишете на C, Pascal или даже Python (используя pypy) и C #, в то, что понимает процессор, т.е. простые инструкции, такие как «сохранить что-то под [адресом памяти]», «добавить числа, хранящиеся в регистрах eax». и ebx "," вызвать функцию foo "," сравнить eax с 10 ". Эти инструкции, выполняемые одна за другой, делают то, что вы хотели сделать со своим кодом.

Теперь подумайте: вам не нужна ОС для выполнения этого нативного кода! Все, что вам нужно, это загрузить этот код в память и сообщить процессору, что он есть, и вы хотите, чтобы он был выполнен. Не беспокойтесь об этом, хотя. Это то, о чем должен беспокоиться BIOS - он загружает ваш код (только один и один сектор) сразу после запуска процессора под физическим адресом 0x7C00. Затем CPU начинает выполнять этот один сектор (512 B) вашего кода. И вы можете делать все, что вы себе представляете! Без, конечно, никакой поддержки со стороны ОС. Это потому, что вы операционная система. Круто, да? Нет стандартной библиотеки, нет наддува, нет python, нет программ, нет драйверов! Вы должны написать все самостоятельно.

А как вы общаетесь с железом? Ну, у вас есть два варианта:

  1. Вы остаетесь в «Реальном режиме» - режиме исполнения ЦП с 1 МБ памяти (и даже меньше), без дополнительных функций ЦП, таких как расширения ЦП, защита памяти, многозадачность; 16-битный исполняемый код, древний режим адресации ... Но с некоторыми подпрограммами, предоставляемыми BIOS, включая простой вывод на экран, поддержку клавиатуры, дисковый ввод-вывод и управление питанием. Одним словом, вы вернулись во времена MS-DOS и 16-битных процессоров.
  2. Вы переходите в «Защищенный режим» со всеми функциями вашего процессора, всей установленной памятью и так далее. Но в защищенном режиме вы полностью одиноки, и вы должны делать ВСЕ самостоятельно (и вы взаимодействуете с оборудованием, используя инструкции «in» и «out» для ввода / вывода данных в порты ввода / вывода и используя прерывания. И отображение в памяти I / О). Должен ли я сказать, что каждая ОС, начиная с Windows 95 и самой первой Linux, выбирает эту опцию?

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

Для лучшего понимания я могу дать вам один совет: попробуйте написать свою собственную ОС. Даже если на экране будет написано «Привет, мир».


3

Существуют некоторые различия в работе операционной системы, которые сильно зависят от системы. Чтобы быть полезным, система должна иметь некоторое предсказуемое поведение при запуске, например, «начать выполнение по адресу X». Для систем, в которых энергонезависимое хранилище (например, флэш-память) отображается в пространстве их программ, это довольно просто, поскольку вы просто должны поместить код запуска в нужное место в пространстве программы процессора. Это чрезвычайно распространено для микроконтроллеров. Некоторые системы должны получить свои программы запуска из какого-либо другого места перед его выполнением. Эти системы будут иметь некоторые операции, встроенные (или почти встроенные) в них. Есть некоторые процессоры, которые получают свой код запуска через i2c из другого чипа,

Системы, использующие семейство процессоров x86, обычно используют многоступенчатый процесс загрузки, который является довольно сложным из-за его эволюции и проблем обратной совместимости. Система выполняет некоторые микропрограммы (называемые BIOS - Basic Input / Output System или аналогичные), которые находятся в некоторой энергонезависимой памяти на материнской плате. Иногда некоторые или все эти прошивки копируются (перемещаются) в ОЗУ для ускорения их выполнения. Этот код был написан со знанием того, какое оборудование будет доступно и пригодно для загрузки.

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

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

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

Код ОС обычно пишется в смеси С и сборки. Самый первый код для ядра ОС, вероятно, всегда находится в сборке и выполняет такие вещи, как настройка стека, на который опирается код C, а затем вызывает функцию C. Также там будет другая рукописная сборка, потому что некоторые операции, которые должна выполнять ОС, часто не выражаются в C (например, стеки переключения / замены контекста). Часто специальные флаги должны передаваться компилятору C, чтобы он не полагался на стандартные библиотеки, используемые большинством программ на C, и не ожидал, чтоint main(int argc, char *argv[])в программе. Кроме того, необходимо использовать специальные опции компоновщика, которые большинство разработчиков приложений никогда не используют. Это может привести к тому, что программа ядра будет ожидать загрузки по определенному адресу или настроить так, чтобы в определенных местах были внешние переменные, даже если эти переменные никогда не объявлялись в каком-либо коде C (это полезно для операций ввода-вывода с отображением в памяти или другие специальные ячейки памяти).

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


3

Чтобы понять, как работают операционные системы, может быть полезно разделить их на две категории: те, которые просто предоставляют сервисы приложениям по запросу, и те, которые используют аппаратные функции в ЦП, чтобы приложения не делали то, что им не следует делать. MS-DOS был в прежнем стиле; все версии Windows начиная с 3.0 были последним стилем (по крайней мере, при запуске чего-то более мощного, чем 8086).

Оригинальный IBM PC под управлением PC-DOS или MS-DOS был бы примером прежнего стиля «ОС». Если бы приложение хотело отобразить символ на экране, было бы несколько способов сделать это. Он может вызвать подпрограмму, которая попросит MS-DOS отправить ее на «стандартный вывод». В этом случае MS-DOS проверит, перенаправлен ли вывод, и, если нет, вызовет подпрограмму, хранящуюся в ПЗУ (в коллекции подпрограмм IBM, называемой Базовая система ввода / вывода), которая будет отображать символ на Положение курсора и переместить курсор («написать телетайп»). Эта подпрограмма BIOS будет хранить пару байтов где-нибудь в диапазоне от 0xB800: 0 до 0xB800: 3999; аппаратное обеспечение на цветном графическом адаптере будет повторно выбирать пары байтов в этом диапазоне, используя первый байт каждой пары для выбора формы символа, а второй - для выбора цвета переднего плана и фона. Байты извлекаются и обрабатываются в красный, зеленый и синий сигналы в последовательности, которая приводит к четкому отображению текста.

Программы на IBM PC могут отображать текст с помощью подпрограммы «стандартный вывод» DOS, либо с помощью подпрограммы BIOS «записать телетайп», либо путем непосредственного сохранения его для отображения в памяти. Многие программы, которым нужно было отображать много текста, быстро выбрали последний подход, поскольку он может быть буквально в сотни раз быстрее, чем использование подпрограмм DOS. Это было не потому, что процедуры DOS и BIOS были исключительно неэффективными; если дисплей не был закрыт, его можно было записать только в определенное время. Процедура BIOS для вывода символа была разработана таким образом, чтобы ее можно было вызывать в любое время; таким образом, каждый запрос должен начинаться заново, ожидая подходящего времени для выполнения операции записи. Напротив, код приложения, который знал, что ему нужно сделать, мог организовать себя вокруг имеющихся возможностей для написания дисплея.

Ключевым моментом здесь является то, что хотя DOS и BIOS предоставляли средства для вывода текста на дисплей, в таких способностях не было ничего особенно «волшебного». Приложение, которое хотело записать текст на дисплей, могло бы делать это так же эффективно, по крайней мере, если бы аппаратное обеспечение дисплея работало так, как ожидало приложение (если кто-то установил монохромный адаптер дисплея, который был похож на CGA, но имел свою символьную память расположенный по адресу 0xB000: 0000-0xB000: 3999), BIOS будет автоматически выводить символы там; приложение, которое было запрограммировано для работы либо с MDA, либо с CGA, могло бы делать то же самое, но приложение, которое было запрограммировано только для CGA, было бы абсолютно бесполезным для MDA).

На более новых системах все немного по-другому. Процессоры имеют различные режимы «привилегий». Они запускаются в наиболее привилегированном режиме, где код может делать все, что захочет. Затем они могут переключиться в ограниченный режим, где доступны только выбранные диапазоны памяти или средств ввода / вывода. Код не может напрямую переключаться из ограниченного режима обратно в привилегированный режим, но процессор определил точки входа в привилегированном режиме, и код ограниченного режима может попросить процессор запустить код на одной из этих точек входа в привилегированном режиме. Кроме того, существуют точки входа в привилегированном режиме, связанные с рядом операций, которые будут запрещены в ограниченном режиме. Предположим, например, что кто-то хотел запустить несколько приложений MS-DOS одновременно, каждое из которых имеет собственный экран. Если бы приложения могли писать напрямую в контроллер дисплея со скоростью 0xB800: 0, не было бы никакого способа предотвратить перезапись одного приложения на экране другого приложения. С другой стороны, ОС может запускать приложение в ограниченном режиме и перехватывать любые обращения к памяти дисплея; если он обнаружил, что приложение, которое должно было находиться в «фоновом режиме», пыталось записать 0xB800: 160, он мог бы сохранить данные в некоторой памяти, которую он выделил как экранный буфер фонового приложения. Если позднее это приложение переключится на передний план, буфер можно будет скопировать на реальный экран. ОС может запускать приложение в ограниченном режиме и перехватывать любые обращения к памяти дисплея; если он обнаружил, что приложение, которое должно было находиться в «фоновом режиме», пыталось записать 0xB800: 160, он мог бы сохранить данные в некоторой памяти, которую он выделил как экранный буфер фонового приложения. Если позднее это приложение переключится на передний план, буфер можно будет скопировать на реальный экран. ОС может запускать приложение в ограниченном режиме и перехватывать любые обращения к памяти дисплея; если он обнаружил, что приложение, которое должно было находиться в «фоновом режиме», пыталось записать 0xB800: 160, он мог бы сохранить данные в некоторой памяти, которую он выделил как экранный буфер фонового приложения. Если позднее это приложение переключится на передний план, буфер можно будет скопировать на реальный экран.

Ключевыми моментами, на которые следует обратить внимание, являются: (1) хотя часто бывает удобно иметь стандартный набор подпрограмм для выполнения различных стандартных сервисов, таких как отображение текста, они не делают ничего, чего не могло бы сделать приложение, работающее в «привилегированном режиме» правильно ли он запрограммирован для работы с установленным оборудованием; (2) хотя большинство приложений, работающих сегодня, будут лишены своей операционной системой непосредственного выполнения таких операций ввода-вывода, программа, которая запускается в привилегированном режиме, выполняет все, что хочет, и может устанавливать любые правила, которые она хочет для ограниченного режима. программы.


2

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

Я просто добавлю к его ответу, что вы можете взглянуть на «Элементы вычислительных систем» . Это книга и некоторые инструменты, которые объясняют, как взаимодействуют компьютер, операционная система и компиляторы. Уникальность в том, что он дает вам инструменты для очень быстрой разработки вашей собственной операционной системы в имитируемой среде, игнорируя многие детали, необходимые для реальной, чтобы вы могли понять концепции . Это делает большую работу, позволяя вам увидеть лес вместо деревьев.

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


1

Вы пишете ОС. Он должен быть запущен как-то, и таким образом в другой ОС?

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

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


Чтобы дать очень упрощенный пример того, как это работает:

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

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

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


Современное высококлассное оборудование чрезвычайно сложно. Руководства, дающие все их детали программирования, представляют собой дверные проемы с тысячами страниц; например, последнее руководство по процессору Intel состоит из семи томов, общим объемом более 4000 страниц, и это только для процессора. Большинство других компонентов предоставляют блоки памяти или порты ввода-вывода, которые операционная система может указать ЦП на сопоставление с адресами в пределах своего адресного пространства. Некоторые из этих компонентов представляют еще больше вещей за несколькими портами ввода-вывода или адресами памяти; Например, RTC (часы реального времени, компонент, который сохраняет время компьютера, пока он выключен) предоставляет несколько сотен байтов памяти за парой портов ввода / вывода, и это очень простой компонент, восходящий к оригинальный ПК / АТ. Такие вещи, как жесткие диски имеют отдельные процессоры, с которой операционная система общается с помощью стандартизированных команд. Графические процессоры еще сложнее.

Несколько человек в комментариях выше предложили Arduino. Я согласен с ними, это гораздо проще понять - ATmega328, который делает все на Arduino Uno, за исключением представления USB-разъема в качестве последовательного порта, имеет руководство, содержащее всего несколько сотен страниц. На Arduino вы работаете непосредственно на оборудовании, между которыми нет операционной системы; всего несколько небольших библиотечных подпрограмм, которые вы не должны использовать, если не хотите.


1

Runnable примеры

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

Код всех примеров ниже представлен на этом репозитории GitHub .

Загрузочный сектор

На x86 самое простое и низкоуровневое средство , которое вы можете сделать, - это создать главный загрузочный сектор (MBR) , который является типом загрузочного сектора , и затем установить его на диск.

Здесь мы создаем один с одним printfвызовом:

printf '\364%509s\125\252' > main.img
sudo apt-get install qemu-system-x86
qemu-system-x86_64 -hda main.img

Результат:

введите описание изображения здесь

Проверено на Ubuntu 18.04, QEMU 2.11.1.

main.img содержит следующее:

  • \364в восьмеричном == 0xf4в шестнадцатеричном виде: кодировка для hltинструкции, которая сообщает ЦП прекратить работу.

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

    Мы используем восьмеричное, потому что \xшестнадцатеричные числа не указаны в POSIX.

    Мы могли бы легко получить эту кодировку с помощью:

    echo hlt > a.asm
    nasm -f bin a.asm
    hd a
    

    но 0xf4кодировка также задокументирована в руководстве Intel, конечно.

  • %509sпроизвести 509 мест. Необходимо заполнить файл до байта 510.

  • \125\252в восьмеричном == с 0x55последующим 0xaa: магические байты, необходимые для оборудования. Это должны быть байты 511 и 512.

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

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

Работать на реальном оборудовании

Эмуляторы забавны, но аппаратная часть - реальная сделка.

Обратите внимание, что это опасно, и вы можете по ошибке стереть диск: делайте это только на старых машинах, которые не содержат критически важных данных! Или, что еще лучше, такие доски, как Raspberry Pi, см. Пример ARM ниже.

Для типичного ноутбука вы должны сделать что-то вроде:

  • Запишите образ на USB-накопитель (уничтожит ваши данные!):

    sudo dd if=main.img of=/dev/sdX
    
  • подключите USB к компьютеру

  • включи

  • скажи ему загрузиться с USB.

    Это означает, что прошивка выбирает USB перед жестким диском.

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

    Часто можно настроить порядок поиска в этих меню.

Например, на моем старом Lenovo Thinkpad T430, UEFI BIOS 1.16, я вижу:

Привет, мир

Теперь, когда мы создали минимальную программу, давайте перейдем к привету.

Очевидный вопрос: как сделать IO? Несколько вариантов:

  • попросите прошивку, например, BIOS или UEFI, чтобы сделать, если для нас
  • VGA: специальная область памяти, которая выводится на экран при записи в. Может использоваться в защищенном режиме.
  • написать драйвер и поговорить напрямую с оборудованием дисплея. Это «правильный» способ сделать это: более мощный, но более сложный.
  • последовательный порт . Это очень простой стандартизированный протокол, который отправляет и получает символы с хост-терминала.

    Источник .

    К сожалению, он не представлен на большинстве современных ноутбуков, но является наиболее распространенным способом разработки плат разработки, см. Примеры ARM ниже.

    Это действительно позор, так как такие интерфейсы действительно полезны для отладки ядра Linux, например .

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

Здесь мы сделаем пример BIOS, поскольку он проще на x86. Но учтите, что это не самый надежный метод.

main.S

.code16
    mov $msg, %si
    mov $0x0e, %ah
loop:
    lodsb
    or %al, %al
    jz halt
    int $0x10
    jmp loop
halt:
    hlt
msg:
    .asciz "hello world"

link.ld

SECTIONS
{
    . = 0x7c00;
    .text :
    {
        __start = .;
        *(.text)
        . = 0x1FE;
        SHORT(0xAA55)
    }
}

Собрать и связать с:

gcc -c -g -o main.o main.S
ld --oformat binary -o main.img -T linker.ld main.o

Результат:

введите описание изображения здесь

Проверено на: Lenovo Thinkpad T430, UEFI BIOS 1.16. Диск создан на хосте Ubuntu 18.04.

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

  • .code16: говорит ГАЗ выводить 16-битный код

  • cli: отключить программные прерывания. Это может заставить процессор начать работать снова послеhlt

  • int $0x10: вызывает ли BIOS. Это то, что печатает символы один за другим.

Важные флаги ссылок:

  • --oformat binary: выводить сырой двоичный код сборки, не деформируйте его внутри ELF-файла, как в случае с обычными исполняемыми файлами пользовательского пространства.

Используйте C вместо сборки

Поскольку C компилируется в сборку, использование C без стандартной библиотеки довольно просто, вам просто нужно:

  • скрипт компоновщика для размещения вещей в памяти в нужном месте
  • флаги, которые говорят GCC не использовать стандартную библиотеку
  • маленькая точка входа в сборку, которая устанавливает требуемое состояние C для main, в частности:
    • стек
    • обнулить BSS

TODO: ссылка на пример с x86 на GitHub. Вот ARM, который я создал .

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

Несколько возможностей, без перехода на полноценную ОС, такую ​​как Linux, включают:

  • Newlib

    Подробный пример по адресу: https://electronics.stackexchange.com/questions/223929/c-standard-libraries-on-bare-metal/223931

    В Newlib вы должны сами реализовывать системные вызовы, но вы получаете очень минимальную систему, и их очень легко реализовать.

    Например, вы можете перенаправить printfна системы UART или ARM или внедрить exit()с помощью полухостинга .

  • встроенные операционные системы, такие как FreeRTOS и Zephyr .

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

    Их можно рассматривать как своего рода предварительно реализованный Newlib.

РУКА

В ARM общие идеи совпадают. Я загрузил:

Для Raspberry Pi https://github.com/dwelch67/raspberrypi выглядит как самый популярный учебник, доступный сегодня.

Некоторые отличия от x86:

  • IO делается путем написания магических адресов напрямую, там нет inи outинструкций.

    Это называется IO с отображением памяти .

  • для некоторого реального оборудования, такого как Raspberry Pi, вы можете самостоятельно добавить прошивку (BIOS) в образ диска.

    Это хорошо, поскольку делает обновление прошивки более прозрачным.

Прошивка

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

На самом деле сначала запускается так называемая прошивка , то есть программное обеспечение:

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

Хорошо известные прошивки включают в себя:

  • BIOS : старая прошивка для x86. SeaBIOS - это стандартная реализация с открытым исходным кодом, используемая QEMU.
  • UEFI : преемник BIOS, лучше стандартизированный, но более способный и невероятно раздутый.
  • Coreboot : благородная попытка с открытым исходным кодом

Прошивка делает такие вещи, как:

  • Зацикливайтесь на каждом жестком диске, USB, сети и т. д., пока не найдете что-нибудь загрузочное.

    Когда мы запускаем QEMU, -hdaговорит, что main.imgэто жесткий диск, подключенный к оборудованию, и

    hda является первым, который будет испытан, и он используется.

  • загрузите первые 512 байт в адрес памяти RAM 0x7c00, поместите туда RIP процессора и дайте ему поработать

  • показывать на дисплее такие вещи, как меню загрузки или вызовы печати BIOS

Прошивка предлагает функциональность, подобную ОС, от которой зависит большинство ОС. Например, подмножество Python было портировано для работы в BIOS / UEFI: https://www.youtube.com/watch?v=bYQ_lq5dcvM

Можно утверждать, что прошивки неотличимы от ОС, и что прошивка - это единственное «истинное» программирование на «голое железо», какое только можно сделать.

Как говорит этот разработчик CoreOS :

Сложная часть

Когда вы включаете компьютер, микросхемы, составляющие чипсет (северный мост, южный мост и SuperIO), еще не инициализированы должным образом. Даже несмотря на то, что ПЗУ BIOS удалено от ЦП настолько, насколько это возможно, оно доступно ЦП, поскольку должно быть, иначе ЦП не будет иметь никаких инструкций для выполнения. Это не означает, что ПЗУ BIOS полностью сопоставлено, обычно нет. Но достаточно для отображения процесса загрузки. Любые другие устройства, просто забудьте об этом.

Когда вы запускаете Coreboot в QEMU, вы можете экспериментировать с более высокими уровнями Coreboot и с полезными нагрузками, но QEMU предлагает небольшую возможность экспериментировать с кодом запуска низкого уровня. Во-первых, RAM просто работает с самого начала.

Начальное состояние после BIOS

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

Поэтому сделайте себе одолжение и используйте некоторый код инициализации, например, следующий: https://stackoverflow.com/a/32509555/895245

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

Обратите внимание, что некоторые эмуляторы лучше, чем реальное оборудование, и дают вам хорошее начальное состояние. Затем, когда вы работаете на реальном оборудовании, все ломается.

GNU GRUB Multiboot

Загрузочные сектора просты, но не очень удобны:

  • Вы можете иметь только одну ОС на диск
  • код загрузки должен быть очень маленьким и вмещаться в 512 байт. Это можно решить с помощью вызова BIOS int 0x13 .
  • Вы должны сделать много запуска самостоятельно, например перейти в защищенный режим

Именно по этим причинам GNU GRUB создал более удобный формат файлов, называемый multiboot.

Минимальный рабочий пример: https://github.com/cirosantilli/x86-bare-metal-examples/tree/d217b180be4220a0b4a453f31275d38e697a99e0/multiboot/hello-world

Я также использую его в репозитории GitHub examples, чтобы иметь возможность легко запускать все примеры на реальном оборудовании, не перегружая USB миллион раз. На QEMU это выглядит так:

введите описание изображения здесь

Если вы подготовите свою ОС как мультизагрузочный файл, GRUB сможет найти его в обычной файловой системе.

Это то, что делает большинство дистрибутивов, помещая образы ОС под /boot.

Мультизагрузочные файлы - это в основном файл ELF со специальным заголовком. Они указаны GRUB по адресу: https://www.gnu.org/software/grub/manual/multiboot/multiboot.html.

Вы можете превратить мультизагрузочный файл в загрузочный диск с помощью grub-mkrescue.

Эль Торито

Формат, который можно записать на компакт-диски: https://en.wikipedia.org/wiki/El_Torito_%28CD-ROM_standard%29

Также возможно создать гибридное изображение, которое работает на ISO или USB. Это может быть сделано с grub-mkrescue( например ), а также выполняется ядром Linux на make isoimageиспользование isohybrid.

Ресурсы

  • http://wiki.osdev.org является отличным источником по этим вопросам.
  • https://github.com/scanlime/metalkit - это более автоматизированная / общая система компиляции с открытым исходным кодом, которая предоставляет крошечный настраиваемый API
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.