Внедряет ли операционная система свой собственный машинный код при открытии программы?

32

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

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

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

Возврат Сумода
источник
7
См .: Переключение контекста ОС выполняет переключение контекста на приложение. Затем приложение может запросить службы ОС, которые возвращают контекст в ОС. Когда приложение завершает работу, контекст переключается обратно на ОС.
Парень кодер
4
Смотрите также "системный вызов".
Рафаэль
1
Если комментарии и ответы не отвечают на ваш вопрос к вашему пониманию или удовлетворению, то, пожалуйста, попросите дополнительную информацию в качестве комментария и объясните, что вы думаете, или где вы потерялись, или что конкретно вам нужно более подробно.
Guy Coder
2
Я думаю, что прерывание , перехват (прерывания), аппаратный таймер (с перехватом планирования- обработки) и разбиение на страницы (частичный ответ на ваше замечание о запрещенной памяти) - это основные ключевые слова, которые вам нужны. ОС должна очень тесно взаимодействовать с процессором, чтобы выполнять код только при необходимости. Таким образом, большая часть мощности процессора может быть использована на реальных вычислениях, а не на управлении ими.
Палек

Ответы:

35

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

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

  2. Из-за неразрешимости проблемы остановки невозможно знать, куда вставить ваши инструкции «Перейти назад к ОС». Например, если код включает в себя что-то вроде этого while (true) {i++;}, вам определенно нужно вставить ловушку внутри этого цикла, но условие цикла ( trueздесь) может быть произвольно сложным, поэтому вы не можете решить, как долго он будет повторяться. С другой стороны, было бы очень неэффективно вставлять хуки в каждый цикл: например, возврат к ОС во for (i=0; i<3; i++) {j=j+i;}время сильно замедлял бы процесс. И по той же причине вы не можете обнаружить короткие петли, чтобы оставить их в покое.

  3. Из-за неразрешимости проблемы остановки невозможно узнать, изменили ли инъекции кода смысл программы. Например, предположим, что вы используете указатели функций в вашей C-программе. Внедрение нового кода будет перемещать расположение функций, поэтому, когда вы вызываете один из них через указатель, вы переходите в неправильное место. Если бы программист был достаточно болен, чтобы использовать вычисленные переходы, они тоже потерпели бы неудачу.

  4. Он будет играть в ад с любой антивирусной системой, поскольку он также изменит код вируса и испортит все ваши контрольные суммы.

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

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

Существует три основных способа, которыми ОС восстанавливает контроль над процессами.

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

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

  3. Каждый раз, когда процесс пытается прочитать файл или каким-либо другим образом взаимодействовать с оборудованием, он просит операционную систему выполнить для него работу. Когда операционная система просит что-то сделать, она может решить приостановить этот процесс и запустить другой. Возможно, это звучит немного по-макиавеллиевски, но это правильная вещь: дисковый ввод / вывод медленный, поэтому вы можете также позволить процессу B работать, пока процесс A ожидает, когда вращающиеся куски металла переместятся в нужное место. Сетевой ввод / вывод еще медленнее. Клавиатурный ввод / вывод является ледниковым, потому что люди не являются гигагерцовыми существами.

Дэвид Ричерби
источник
5
Можете ли вы развить в себе последние 2 пункта? Мне интересно этот вопрос, и я чувствую, что объяснение здесь пропущено. Мне кажется, что вопрос в том, «как ОС извлекает процессор из процесса», а ваш ответ говорит: «ОС справляется с этим». но как? Возьмите бесконечный цикл в первом примере: как он не зависает на компьютере?
BiAiB
3
Некоторые ОС делают, большинство ОС, по крайней мере, связываются с кодом для «связывания», поэтому программу можно загрузить по любому адресу
Ян Рингроз
1
@BiAiB Ключевое слово здесь - «прерывание». Процессор - это не просто то, что обрабатывает заданный поток инструкций, он также может прерываться асинхронно из отдельного источника - что наиболее важно для нас, ввода-вывода и прерывания тактового генератора. Поскольку только код пространства ядра может обрабатывать прерывания, Windows может быть уверена, что сможет «украсть» работу из любого запущенного процесса в любое время, когда захочет. Обработчики прерываний могут выполнять любой код, который они хотят, в том числе «хранить регистры ЦП где-нибудь и восстанавливать их отсюда (другой поток)». Чрезвычайно упрощенный, но это переключение контекста.
Луаан
1
Добавление к этому ответу; стиль многозадачности, упомянутый в пунктах 2 и 3, называется «многозадачность с вытеснением», имя относится к способности ОС вытеснять запущенный процесс. Совместная многозадачность часто использовалась в старых операционных системах; в Windows по крайней мере вытесняющая многозадачность не была введена до Windows 95. Я читал, по крайней мере, об одной используемой сегодня промышленной системе управления, которая все еще использует Windows 3.1 исключительно для совместной работы в режиме реального времени.
Джейсон C
3
@BiAiB На самом деле, вы не правы. Настольные ЦП не выполняют код последовательно и синхронно начиная с i486. Однако даже у более старых процессоров все еще были асинхронные входы - прерывания. Представьте себе запрос аппаратного прерывания (IRQ), похожий на вывод самого ЦП - когда он получает 1, ЦП останавливает все, что он делает, и начинает обрабатывать прерывание (что в основном означает «сохранить состояние и перейти к адресу в памяти»). Сама обработка прерываний не является x86или каким-либо другим кодом, она буквально зашита. После прыжка он снова выполняет (любой) x86код. Потоки - это более высокая абстракция.
Луаан
12

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

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

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

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

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

Programmdude
источник
4
Прерывания не определяются операционной системой, они предоставляются аппаратно. И большинство современных архитектур имеют специальные инструкции для вызова операционной системы. Для этого i386 использовал прерывание (сгенерированное программным обеспечением), но это не сделано для его преемников.
vonbrand
2
Я знаю, что прерывания определяются процессором, но ядро ​​устанавливает указатели. Я, возможно, объяснил это плохо. Я также думал, что linux использовал int 9 для общения с ядром, но, возможно, сейчас есть и лучшие способы.
Programmdude
Это довольно вводящий в заблуждение ответ, хотя представление о том, что упреждающие планировщики управляются прерываниями таймера, является правильным. Для начала стоит отметить, что таймер аппаратный. Также, чтобы уточнить, что процесс «сохранить ... восстановить» называется переключением контекста и включает в себя, среди прочего, сохранение всех регистров ЦП (включая указатель инструкций). Кроме того, процессы могут эффективно изменять таблицы прерываний, это называется «защищенным режимом», который также определяет виртуальную память, и существует с 286 года - указатель на таблицу прерываний хранится в регистре с возможностью записи.
Джейсон С
(Кроме того, даже таблица прерываний реального режима была перемещена - не привязана к первой странице памяти - начиная с 8086 года.)
Джейсон С
1
Этот ответ пропускает критическую деталь. Когда прерывание срабатывает, процессор не переключается на ядро ​​напрямую. Вместо этого он сначала сохраняет существующие регистры, затем переключается на другой стек, и только потом вызывается ядро. Вызов ядра со случайным стеком из случайной программы был бы довольно плохой идеей. Кроме того, последняя часть вводит в заблуждение. Вы не получите прерывание, «пытающееся» прочитать не отображенную память; это просто невозможно. Вы читаете с виртуальных адресов, а у не отображенной памяти просто нет виртуального адреса.
MSalters
5

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

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

Анкур
источник
Не только прерывание часов: любое прерывание. А также инструкция по смене режима.
Жиль "ТАК - перестань быть злым"
3

Там является метод , подобный тому , что вы описали: кооперативная многозадачность . ОС не вставляет инструкции, но каждая программа должна быть написана для вызова функций ОС, которые могут выбрать запуск другого из взаимодействующих процессов. Это имеет недостатки, которые вы описываете: сбой одной программы уничтожает всю систему. Windows до 3.0 включительно работала так; 3.0 в "защищенном режиме" и выше не сделал.

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

(Некоторые системы действительно перезаписывают инструкции ограниченным образом при загрузке программы, называемые «thunking», и процессор Transmeta динамически перекомпилируется в свой собственный набор команд)

pjc50
источник
AFAICR 3.1 также сотрудничал. В Win95 была реализована вытесняющая многозадачность. Защищенный режим в основном обеспечивал изоляцию адресного пространства (что улучшает стабильность, но в основном по несвязанным причинам).
Цао
Thunking не переписывает и не вводит код в приложение. Измененный загрузчик основан на ОС, а не на продукте приложения. Интерпретирующие языки, которые скомпилированы, например, с использованием JIT-компиляторов, не модифицируют код и не вводят ничего в код. Они переводят исходный код в исполняемый файл. Опять же, это не то же самое, что внедрение кода в приложение.
Дейв Гордон,
Transmeta взяла в качестве источника исполняемый код x86, а не язык интерпретации. И я думал , что один случай , в котором код будет впрыскивается: работает под отладчиком. Системы X86 обычно перезаписывают инструкцию в точке останова с помощью «INT 03», которая попадает в отладчик. При возобновлении восстанавливается исходный код операции.
pjc50
Отладка - это не то, как кто-либо запускает приложение; за то, что разработчик приложения. Так что я не думаю, что это действительно помогает ОП.
Дейв Гордон,
3

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

По сути, операционная система программирует аппаратный таймер так, что он срабатывает очень часто ... возможно, 100 раз в секунду. Когда таймер отключается, он генерирует аппаратное прерывание - сигнал, который указывает ЦПУ прекратить то, что он делает, сохранить свое состояние в стеке, изменить его режим на более привилегированный и выполнить код, который он найдет в специально предназначенном для этого месте. место в памяти. Этот код является частью планировщика, который решает, что делать дальше. Это может быть возобновление какого-либо другого процесса, и в этом случае ему придется выполнить так называемый «переключение контекста», заменив все его текущее состояние (включая таблицы виртуальной памяти) на состояние другого процесса. Возвращаясь к процессу, он должен восстановить весь контекст этого процесса,

«Специально обозначенное» место в памяти не должно быть известно никому, кроме операционной системы. Реализации различаются, но суть в том, что процессор будет реагировать на различные прерывания, выполняя поиск в таблице; местоположение таблицы находится в определенном месте в памяти (определяется аппаратной схемой ЦП), содержимое таблицы задается операционной системой (как правило, во время загрузки), и «тип» прерывания будет определять, какая запись в таблице должен использоваться как «подпрограмма обработки прерывания».

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

Zenilogix
источник
2

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

VMware действует как слой под одной или несколькими одновременно исполняемыми операционными системами на одном и том же оборудовании.

Большинство выполняемых инструкций (например, в обычных приложениях) можно виртуализировать с использованием аппаратного обеспечения, но само ядро ​​ОС использует инструкции, которые не могут быть виртуализированы, потому что, если машинный код предполагаемой ОС был выполнен без изменений, он "вырвался бы из строя". «управления хостом VMware. Например, гостевая ОС должна работать в наиболее привилегированном защитном кольце и настраивать таблицу прерываний. Если бы это было разрешено, VMware потерял бы контроль над оборудованием.

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

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

Дэниел Уорвикер
источник
2

Существует множество случаев, когда операционная система может «внедрить код» в программу. Версии системы Apple Macintosh на базе 68000 создают таблицу всех точек входа сегмента (расположенных непосредственно перед статическими глобальными переменными, IIRC). Когда программа запускается, каждая запись в таблице состоит из команды прерывания, за которой следует номер сегмента и смещение в сегмент. Если прерывание выполнено, система посмотрит на слова после инструкции прерывания, чтобы увидеть, какой сегмент и смещение требуются, загрузит сегмент (если это еще не сделано), добавит начальный адрес сегмента к смещению и затем замените ловушку переходом на этот недавно вычисленный адрес.

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

Supercat
источник
Метод FP все еще используется на процессорах ARM, которые, в отличие от процессоров x86, все еще имеют варианты без FP. Но это редко, так как большинство ARM используется в выделенных устройствах. В таких средах, как правило, известно, будет ли у процессора возможность FP.
MSalters
Ни в одном из этих случаев ОС не вводила код в приложение. Чтобы ОС могла внедрить код, ей потребуется лицензия производителя программного обеспечения для «модификации» приложения, которое она не получает. ОС не вводят код.
Дэйв Гордон,
Можно сказать, что @DaveGordon захваченные инструкции представляют собой ОС, внедряющую код в приложение.
Жиль "ТАК - перестань быть злым"
@MSalters Захваченные инструкции обычно происходят на виртуальных машинах.
Жиль "ТАК - перестань быть злым"