Я изучаю ЦП и знаю, как он читает программу из памяти и выполняет ее инструкции. Я также понимаю, что ОС разделяет программы в процессах, а затем чередует каждую из них так быстро, что вы думаете, что они выполняются одновременно, но на самом деле каждая программа запускается отдельно в ЦП. Но если ОС также представляет собой набор кода, работающего в ЦП, как она может управлять процессами?
Я размышлял, и единственное объяснение, которое я мог придумать, это: когда ОС загружает программу из внешней памяти в ОЗУ, она добавляет свои собственные инструкции в середине оригинальных инструкций программы, и затем программа выполняется, программа Можно позвонить в ОС и сделать некоторые вещи. Я считаю, что есть инструкция, которую ОС добавит в программу, которая позволит процессору вернуться к коду ОС через некоторое время. Кроме того, я считаю, что когда ОС загружает программу, она проверяет, есть ли какие-то запрещенные инструкции (которые могут перейти к запрещенным адресам в памяти), и затем удаляет их.
Я думаю о чем-то? Я не студент CS, но на самом деле, студент математики. Если возможно, я бы хотел хорошую книгу об этом, потому что я не нашел никого, кто объяснял бы, как ОС может управлять процессом, если ОС также представляет собой набор кода, выполняющегося в ЦП, и не может работать в то же время. время программы. Книги говорят только о том, что ОС может управлять вещами, но теперь как.
источник
Ответы:
Нет. Операционная система не возится с кодом программы, внедряющим в нее новый код. Это будет иметь ряд недостатков.
Это займет много времени, так как ОС придется сканировать весь исполняемый файл, внося свои изменения. Обычно часть исполняемого файла загружается только по мере необходимости. Кроме того, вставка стоит дорого, так как вы должны убрать кучу вещей с дороги.
Из-за неразрешимости проблемы остановки невозможно знать, куда вставить ваши инструкции «Перейти назад к ОС». Например, если код включает в себя что-то вроде этого
while (true) {i++;}
, вам определенно нужно вставить ловушку внутри этого цикла, но условие цикла (true
здесь) может быть произвольно сложным, поэтому вы не можете решить, как долго он будет повторяться. С другой стороны, было бы очень неэффективно вставлять хуки в каждый цикл: например, возврат к ОС воfor (i=0; i<3; i++) {j=j+i;}
время сильно замедлял бы процесс. И по той же причине вы не можете обнаружить короткие петли, чтобы оставить их в покое.Из-за неразрешимости проблемы остановки невозможно узнать, изменили ли инъекции кода смысл программы. Например, предположим, что вы используете указатели функций в вашей C-программе. Внедрение нового кода будет перемещать расположение функций, поэтому, когда вы вызываете один из них через указатель, вы переходите в неправильное место. Если бы программист был достаточно болен, чтобы использовать вычисленные переходы, они тоже потерпели бы неудачу.
Он будет играть в ад с любой антивирусной системой, поскольку он также изменит код вируса и испортит все ваши контрольные суммы.
Вы можете обойти проблему остановки, смоделировав код и вставив хуки в любой цикл, который выполняется более определенного фиксированного числа раз. Однако это потребовало бы чрезвычайно дорогого моделирования всей программы, прежде чем ее можно было выполнить.
На самом деле, если вы хотите внедрить код, компилятор будет естественным местом для этого. Таким образом, вам нужно будет сделать это только один раз, но это все равно не сработает по второй и третьей причинам, приведенным выше. (И кто-то может написать компилятор, который не подыгрывает.)
Существует три основных способа, которыми ОС восстанавливает контроль над процессами.
В кооперативных (или не упреждающих) системах есть
yield
функция, которую процесс может вызвать, чтобы вернуть управление ОС. Конечно, если это ваш единственный механизм, вы полагаетесь на процессы, ведущие себя хорошо, и процесс, который не дает результата, будет загружать процессор до его завершения.Чтобы избежать этой проблемы, используется прерывание по таймеру. Процессоры позволяют ОС регистрировать обратные вызовы для всех различных типов прерываний, которые реализует процессор. ОС использует этот механизм для регистрации обратного вызова для прерывания по таймеру, которое периодически запускается, что позволяет ей выполнять свой собственный код.
Каждый раз, когда процесс пытается прочитать файл или каким-либо другим образом взаимодействовать с оборудованием, он просит операционную систему выполнить для него работу. Когда операционная система просит что-то сделать, она может решить приостановить этот процесс и запустить другой. Возможно, это звучит немного по-макиавеллиевски, но это правильная вещь: дисковый ввод / вывод медленный, поэтому вы можете также позволить процессу B работать, пока процесс A ожидает, когда вращающиеся куски металла переместятся в нужное место. Сетевой ввод / вывод еще медленнее. Клавиатурный ввод / вывод является ледниковым, потому что люди не являются гигагерцовыми существами.
источник
1
, ЦП останавливает все, что он делает, и начинает обрабатывать прерывание (что в основном означает «сохранить состояние и перейти к адресу в памяти»). Сама обработка прерываний не являетсяx86
или каким-либо другим кодом, она буквально зашита. После прыжка он снова выполняет (любой)x86
код. Потоки - это более высокая абстракция.Несмотря на то, что ответ Дэвида Ричерби является хорошим, он как бы бросает взгляд на то, как современные операционные системы блокируют существующие программы. Мой ответ должен быть точным для архитектуры x86 или x86_64, единственной широко используемой для настольных компьютеров и ноутбуков. Другие архитектуры должны иметь аналогичные методы для достижения этой цели.
Когда операционная система запускается, она устанавливает таблицу прерываний. Каждая запись таблицы указывает на фрагмент кода в операционной системе. Когда происходят прерывания, которые контролируются процессором, он смотрит на эту таблицу и вызывает код. Существуют различные прерывания, такие как деление на ноль, неверный код и некоторые определенные операционной системой.
Так пользовательский процесс обращается к ядру, например, хочет ли он читать / записывать на диск или что-то еще, что контролируется ядром операционной системы. Операционная система также установит таймер, который вызывает прерывание, когда оно завершается, поэтому исполняемый код принудительно изменяется из пользовательской программы в ядро операционной системы, и ядро может выполнять другие действия, например ставить в очередь другие программы для запуска.
Из памяти, когда это происходит, ядро операционной системы должно сохранять то, где был код, а когда ядро завершает делать то, что ему нужно, оно восстанавливает предыдущее состояние программы. Таким образом, программа даже не знает, что она была прервана.
Процесс не может изменить таблицу прерываний по двум причинам. Во-первых, он работает в защищенной среде, поэтому, если он попытается вызвать определенный код защищенной сборки, процессор вызовет другое прерывание. Вторая причина - виртуальная память. Местоположение таблицы прерываний находится в диапазоне от 0x0 до 0x3FF в реальной памяти, но с пользовательскими процессами это расположение обычно не отображается, и попытка чтения не отображенной памяти вызовет другое прерывание, поэтому без защищенной функции и возможности записи в реальную память пользовательский процесс не может изменить его.
источник
Ядро ОС получает управление от запущенного процесса из-за обработчика прерывания тактовой частоты процессора, а не путем внедрения кода в процесс.
Вы должны прочитать о прерываниях, чтобы получить больше разъяснений о том, как они работают и как ядра ОС обрабатывают их и реализуют различные функции.
источник
Там является метод , подобный тому , что вы описали: кооперативная многозадачность . ОС не вставляет инструкции, но каждая программа должна быть написана для вызова функций ОС, которые могут выбрать запуск другого из взаимодействующих процессов. Это имеет недостатки, которые вы описываете: сбой одной программы уничтожает всю систему. Windows до 3.0 включительно работала так; 3.0 в "защищенном режиме" и выше не сделал.
Упреждающая многозадачность (нормальный вид в наши дни) зависит от внешнего источника прерываний. Прерывания отменяют обычный поток управления и обычно сохраняют регистры где-нибудь, поэтому ЦП может сделать что-то еще, а затем прозрачно возобновить программу. Конечно, операционная система может изменить регистр «когда вы оставите прерывания возобновить здесь», поэтому она возобновляется внутри другого процесса.
(Некоторые системы действительно перезаписывают инструкции ограниченным образом при загрузке программы, называемые «thunking», и процессор Transmeta динамически перекомпилируется в свой собственный набор команд)
источник
Многозадачность не требует ничего, как внедрение кода. В операционной системе, такой как Windows, есть компонент кода операционной системы, называемый планировщиком, который использует аппаратное прерывание, запускаемое аппаратным таймером. Это используется операционной системой для переключения между различными программами и самой собой, что заставляет все наше человеческое восприятие происходить одновременно.
По сути, операционная система программирует аппаратный таймер так, что он срабатывает очень часто ... возможно, 100 раз в секунду. Когда таймер отключается, он генерирует аппаратное прерывание - сигнал, который указывает ЦПУ прекратить то, что он делает, сохранить свое состояние в стеке, изменить его режим на более привилегированный и выполнить код, который он найдет в специально предназначенном для этого месте. место в памяти. Этот код является частью планировщика, который решает, что делать дальше. Это может быть возобновление какого-либо другого процесса, и в этом случае ему придется выполнить так называемый «переключение контекста», заменив все его текущее состояние (включая таблицы виртуальной памяти) на состояние другого процесса. Возвращаясь к процессу, он должен восстановить весь контекст этого процесса,
«Специально обозначенное» место в памяти не должно быть известно никому, кроме операционной системы. Реализации различаются, но суть в том, что процессор будет реагировать на различные прерывания, выполняя поиск в таблице; местоположение таблицы находится в определенном месте в памяти (определяется аппаратной схемой ЦП), содержимое таблицы задается операционной системой (как правило, во время загрузки), и «тип» прерывания будет определять, какая запись в таблице должен использоваться как «подпрограмма обработки прерывания».
Ничто из этого не включает «внедрение кода» ... оно основано на коде, содержащемся в операционной системе в сочетании с аппаратными функциями ЦП и поддерживающей его схемой.
источник
Я думаю, что наиболее близким примером из того, что вы описываете, является один из методов, используемых VMware. Полная виртуализация с использованием бинарного перевода .
VMware действует как слой под одной или несколькими одновременно исполняемыми операционными системами на одном и том же оборудовании.
Большинство выполняемых инструкций (например, в обычных приложениях) можно виртуализировать с использованием аппаратного обеспечения, но само ядро ОС использует инструкции, которые не могут быть виртуализированы, потому что, если машинный код предполагаемой ОС был выполнен без изменений, он "вырвался бы из строя". «управления хостом VMware. Например, гостевая ОС должна работать в наиболее привилегированном защитном кольце и настраивать таблицу прерываний. Если бы это было разрешено, VMware потерял бы контроль над оборудованием.
VMware переписывает эти инструкции в коде ОС перед его выполнением, заменяя их переходами в код VMware, который имитирует желаемый эффект.
Таким образом, этот метод несколько аналогичен тому, что вы описываете.
источник
Существует множество случаев, когда операционная система может «внедрить код» в программу. Версии системы Apple Macintosh на базе 68000 создают таблицу всех точек входа сегмента (расположенных непосредственно перед статическими глобальными переменными, IIRC). Когда программа запускается, каждая запись в таблице состоит из команды прерывания, за которой следует номер сегмента и смещение в сегмент. Если прерывание выполнено, система посмотрит на слова после инструкции прерывания, чтобы увидеть, какой сегмент и смещение требуются, загрузит сегмент (если это еще не сделано), добавит начальный адрес сегмента к смещению и затем замените ловушку переходом на этот недавно вычисленный адрес.
В более старых программах для ПК, хотя технически это и не делалось «ОС», для кода часто создавались команды прерываний вместо математических инструкций сопроцессора. Если математический сопроцессор не был установлен, обработчик ловушек будет эмулировать его. Если сопроцессор был установлен, то при первом получении прерывания обработчик заменит инструкцию прерывания на инструкцию сопроцессора; будущие исполнения того же кода будут напрямую использовать инструкцию сопроцессора.
источник