52

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

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

Я прочитал похожий вопрос. Он не объяснил, является ли машинный код, сгенерированный после компиляции, инструкцией непосредственно для ЦП, или ему потребуется снова пройти через ядро, чтобы создать правильную инструкцию для ЦПУ. Т.е. что происходит после загрузки машинного кода в память? Пройдет ли оно через ядро или напрямую пообщается с процессором?

11 ответов11

85

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

Требуется ли для запуска исполняемого файла ядро ОС?

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

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


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

Правильный.

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

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


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

Каждая инструкция выполняется процессором.
Неподдерживаемая или недопустимая инструкция (например, процесс имеет недостаточные привилегии) вызовет немедленное исключение, и ЦП вместо этого выполнит подпрограмму для обработки этого необычного условия.

Функция printf() не должна использоваться в качестве примера "простого исходного кода".
Перевод с объектно-ориентированного языка программирования высокого уровня на машинный код может быть не таким тривиальным, как вы предполагаете.
Затем вы выбираете одну из самых сложных функций из библиотеки времени выполнения, которая выполняет преобразование данных и ввод-вывод.

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


... что происходит после загрузки машинного кода в память?

Это зависит от окружающей среды.

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

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

Пройдет ли оно через ядро или напрямую пообщается с процессором?

Машинный код выполняется процессором.
Они не "проходят через ядро", но и не "общаются с процессором".
Машинный код (состоящий из кода операции и операндов) является инструкцией для ЦП, которая декодируется, и операция выполняется.

Возможно, следующая тема, которую вы должны изучить, - это режимы процессора.

38

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

Все это работает непосредственно на процессоре, вы просто переходите через его слои, чтобы что-то делать.

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

Фактический код вашей программы выполняется на ЦПУ, но ветви, которые код создает для печати чего-либо на экране, проходят через код для функции C printf , через различные другие системы и интерпретаторы, каждая из которых выполняет свою собственную обработку, чтобы отработать просто как hello world! на самом деле печатается на вашем экране.

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

Есть много чего еще, но давайте будем проще ...

  1. В вашей терминальной программе вы запускаете свою программу для печати hello world!
  2. Терминал видит, что программа написала (через процедуры вывода C) hello world! на консоль
  3. Терминальная программа переходит к диспетчеру окон рабочего стола, говоря: « hello world! написано на меня, вы можете поставить его в положение x , y пожалуйста?"
  4. Менеджер окон рабочего стола подходит к ядру: «Одна из моих программ хочет, чтобы ваше графическое устройство поместило какой-то текст в эту позицию, доберитесь до этого, чувак!"
  5. Ядро передает запрос драйверу графического устройства, которое форматирует его так, чтобы видеокарта могла его понять.
  6. В зависимости от того, как подключена видеокарта, необходимо вызвать другие драйверы устройств ядра, чтобы вытолкнуть данные на физические шины устройств, такие как PCIe, обрабатывая такие вещи, как проверка выбора правильного устройства и возможность передачи данных через соответствующий мост или преобразователи
  7. Аппаратное обеспечение отображает вещи.

Это огромное упрощение только для описания. Здесь будут драконы.

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

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

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

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

Все это обрабатывается слоями на слоях кода.

21

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

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

Кроме того, современные компьютеры (в отличие, скажем, от 1401) поддерживают подключение самых разных устройств ввода-вывода, а не только тех, которые IBM продавала вам в прежние времена. Ваш компилятор и компоновщик не может знать обо всех возможностях. Например, ваша клавиатура может быть подключена через PS/2 или USB. ОС позволяет устанавливать специфичные для устройства "драйверы устройств", которые знают, как общаться с этими устройствами, но предоставляют общий интерфейс для класса устройств для ОС. Таким образом, ваша программа и даже ОС не должны делать ничего другого для получения нажатий клавиш с USB на клавиатуру PS/2 или для доступа, скажем, к локальному диску SATA, к USB-устройству хранения или к хранилищу, находящемуся где-то далеко. на NAS или SAN. Эти детали обрабатываются драйверами устройств для различных контроллеров устройств.

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

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

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

10

Я думаю, что многие ответы неправильно понимают вопрос, который сводится к следующему:

Компилятор выводит машинный код. Этот машинный код выполняется непосредственно процессором или он "интерпретируется" ядром?

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

  1. Когда присутствует ОС, прикладным программам обычно запрещено выполнять определенные инструкции или получать доступ к определенным ресурсам. Например, если приложение выполняет инструкцию, которая изменяет таблицу системных прерываний, ЦПУ вместо этого перейдет к обработчику исключений ОС, чтобы приложение-нарушитель было завершено. Кроме того, приложениям обычно не разрешается чтение / запись в память устройства. (Т. Е. "Общение с оборудованием".) Доступ к этим специальным областям памяти - это то, как ОС взаимодействует с такими устройствами, как графическая карта, сетевой интерфейс, системные часы и т.д.

  2. Ограничения, накладываемые ОС на приложения, достигаются благодаря специальным функциям ЦП, таким как режимы привилегий, защита памяти и прерывания. Хотя любой процессор, который вы найдете в смартфоне или ПК, имеет эти функции, некоторые процессоры не имеют. Этим ЦП действительно нужны специальные ядра, которые "интерпретируют" код приложения для достижения желаемых функций. Очень интересным примером является Gigatron, компьютер с 8 инструкциями, который вы можете собрать из микросхем, который имитирует компьютер с 34 командами.

  3. Некоторые языки, такие как Java, "компилируются" в нечто, называемое байт-кодом, которое на самом деле не является машинным кодом. Хотя в прошлом их интерпретировали для запуска программ, в наши дни обычно используется то, что называется компиляцией Just-in-Time, поэтому они в конечном итоге запускаются непосредственно на ЦП как машинный код.

  4. Запуск программного обеспечения на виртуальной машине раньше требовал, чтобы ее машинный код был "интерпретирован" программой, называемой гипервизором. Из-за огромного промышленного спроса на виртуальные машины производители процессоров добавили функции, такие как VTx, в свои процессоры, чтобы позволить большинству команд гостевой системы выполняться непосредственно процессором. Однако при запуске программного обеспечения, разработанного для несовместимого ЦП на виртуальной машине (например, эмуляция NES), машинный код необходимо будет интерпретировать.

5

Когда вы компилируете свой код, вы создаете так называемый "объектный" код, который (в большинстве случаев) зависит от системных библиотек (например, printf ), затем ваш код оборачивается компоновщиком, который добавит загрузчик программ, соответствующий вашей конкретной операционной системе. может распознавать (именно поэтому вы не можете запустить программу, скомпилированную для Windows на Linux, например) и знать, как развернуть код и выполнить. Таким образом, ваша программа в виде мяса внутри бутерброда и может быть съедена только в комплекте.

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

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

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

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

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

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

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

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

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

3

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

По сути, только системные вызовы идут в ядро. Все, что связано с вводом-выводом или выделением / освобождением памяти, обычно в конечном итоге приводит к системному вызову. Некоторые инструкции могут быть выполнены только в режиме ядра и вызовут исключение ЦП. Исключения вызывают переход в режим ядра и переход к коду ядра.

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

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

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


Требуется ли для запуска исполняемого файла ядро ОС?

Зависит от типа исполняемого файла.

Ядра, помимо обеспечения совместного доступа к ОЗУ и аппаратному обеспечению, также выполняют функцию загрузчика.

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

Эти исполняемые файлы также ссылаются на библиотеки (Windows .dll или Linux совместно используемые объекты .so файлы) - их код должен быть включен.

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

  • Можете ли вы включить код, который выполняет работу загрузчика?

Конечно. Вы должны убедить ОС как-то запустить ваш необработанный код без обработки каких-либо метаданных. Если ваш код вызывает API ядра, он все равно не будет работать.

  • Что если он не вызывает API ядра?

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

  • Что делать, если вы запускаете его из режима ядра.

Тогда это будет работать.


3

TL; DR No.

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

Аналогичным образом, игры для Sega Genesis не имели ОС, предоставляемой Sega для вызова. Вы только что создали свою игру на ассемблере 68K, написав прямо на голое железо.

Или где я порезался, делая встраиваемые работы на Intel 8051. Опять же, когда все, что у вас есть, это 2716 eprom с размером 2k * 8, у вас нет места для операционной системы.

Конечно, это предполагает очень широкое использование слова приложения. В качестве риторического вопроса стоит спросить себя, действительно ли эскиз Arduino является приложением.

3

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

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

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

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

2

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

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

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

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

2

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

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

Все это привело к перемещению большого количества вспомогательного кода из отдельных программ в "операционную систему" со стандартным способом вызова вспомогательного кода из пользовательских программ.

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

Были написаны микроядра, которые предоставляют именно то, что нужно для запуска конкретной программы без полноценной операционной системы. Это имеет некоторые преимущества для опытных пользователей, отдавая большинство других. Вы можете прочитать об этом страницу в Википедии - https://en.wikipedia.org/wiki/Microkernel - если хотите узнать больше.

Я экспериментировал с микроядром, способным работать на виртуальной машине Java, но позже обнаружил, что сладкое место для этого - Docker.

1

В типичных настольных операционных системах, само ядро является исполняемым. (В Windows есть ntoskrnl.exe ; в Linux есть vmlinux и т.д.) Если вам нужно ядро для запуска исполняемого файла, то эти ОС не могут существовать.

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

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .