Страницы

Поиск по вопросам

понедельник, 1 октября 2018 г.

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

У меня возник вопрос о том, как работают программы, написанные на низкоуровневых языках. Допустим я составлю программу на си, которая будет читать файл и выводить в консоль его содержимое. Как она будет исполняться?
Я правильно понимаю, что при запуске, её код будет отправлен на исполнение процессором? Код будет последовательно извлекаться процессором из RAM и исполняться команда за командой. Если это так, то как система контролирует права доступа? Как ограничивает исполнение команд, доступных только привилегированному пользователю?
Что помешает коду, запущенному от пользователя без особых прав приказать процессору обратиться напрямую к жесткому диску, достать данные, доступные только root пользователю, обработать и перезаписать их, если код без изменений попадает на выполнение прямо в процессор?
Может быть есть какой-то наблюдатель, следящий за тем, что бы программа не выполняла запретные действия? А если такой наблюдатель есть, неужели доступ к любому оборудованию можно получить только через него?
Например, если мою программу запустит пользователь без прав на чтение определенных системных файлов, как система сможет помешать коду исполняемому процессором, получить доступ к жесткому диску и прочитать/перезаписать эти системные файлы?
Объясните, как работает механизм ограничения прав или поправьте, если я не правильно понимаю как происходит исполнение машинного кода.


Ответ

Если кратко - процессор имеет список команд, которые можно выполнить только на высоких уровнях приоритета. А пользовательская задача запускается операционкой на низком уровне приоритета. Так что при попытке напрямую из задачи полезть не в свою память или при попытке напрямую из задачи полезть к портам, возникнет исключение и управление попадет обратно к операционке.
UPD1:
И да, попытка полезть в чужую память ограничивается не приоритетом команды, а так называемым устройством управления памятью (MMU - Memory Management Unit). Каждой задаче выделяется кусок памяти и в MMU прописываются границы этой памяти. При каждом обращении к памяти MMU аппаратно контролирует эти границы. При попытке записать/считать вне выделенных границ MMU генерирует исключение и управление попадет обратно к операционке. А насчет команд обращения к портам - они таки могут запускаться только из высокоприоритетных задач.
UPD2:
А если программе нужно прочитать файл, как это происходит? Опишите процесс
Так как напрямую к портам контроллера диска задача обратиться не может, то она вынуждена выполнять так называемый системный запрос к операционке. В случае чтения файла программа обращается к драйверу файловой системы (который потом обращается к драйверу диска). Драйвер диска работает на уровне приоритета ядра (или на уровне приоритета драйверов) и ему позволено обращаться к регистрам контроллера диска. Также программа поступает (выполняет системный запрос к операционке, так называемый system call) когда ей надо вывести что-то на экран или обратиться к сети или к порту USB. Так как управление передается операционке, то операционка может проанализировать все уровни привилегий данной программы и выполнить запрос или отказать в выполнении запроса, если привилегий недостаточно или, скажем, системный файл недоступен на уровне этого пользователя. Также операционка организует разделение доступа к данным, например если несколько программ пытаются одновременно писать в один и тот же файл.
UPD3:
И да, уровни приоритета о которых идет речь это не уровни приоритета процессов и потоков в операционке. Это аппаратные уровни приоритета в процессоре. В разных архитектурах это организовано по-разному. Иногда это два уровня - супервизор и пользователь/пользователи. Иногда три уровня - супервизор, драйверы и пользователь/пользователи. А уровни приоритета процессов и потоков в операционке существуют отдельно от аппаратных приоритетов, существующих в процессоре.
UPD4:
Посоветуйте литературу или другие источники, где можно узнать об этом более подробно
Честно говоря, я не знаю, где есть хорошие популярные обзоры по этому вопросу. Но где-то они, безусловно, есть попробуйте погуглить. Сам я довольно много программировал контроллеры на низком уровне без всякой операционки, прямо по "голому" железу. Чтобы это делать, приходилось читать даташиты на процессоры. Оттуда я и почерпнул эту науку. Однако порекомендовать другим этот путь я не могу, ибо типовой даташит на процессор содержит 500-1000-1500 страниц на английском.
Вообще-то для обычного программиста, который не пишет драйверов или не портирует Юникс на новый процессор, вся эта наука довольно бесполезна. Иметь представление о внутреннем устройстве системы, конечно, полезно для общего развития. Но для собственно работы прикладного программиста достаточно знать, что есть ему выделенная память кода, память данных, куча и есть запросы к операционке, которые отрабатываются и возвращают результат и код ошибки.
UPD5:
Я смотрю, ответ набирает популярность. Что же, добавлю еще свои пять копеек. Существуют процессоры, в которых нет аппаратной поддержки управления памятью (то есть нет MMU) и нет разделения режимов работы на режим супервизора и пользователя. Эти процессоры предназначены для работы в так называемых встроенных системах (embedded system), в которых нет поддержки диска, нет аппаратной поддержки свопинга (которую традиционно осуществляет тот же MMU), нет загрузки новых задач во время работы, а все задачи изначально работают в ROM. Таковы, например, PIC процессоры от Microchip, сигнальные процессоры от Analog Device и многие другие. Но есть и процессоры, ориентированные на работу под ОС общего назначения, таких как Windows и Linux с полной поддержкой многозадачности и многопользовательности. В этих процессорах обязательно есть и MMU и разделение на режим супервизора/пользователя. К таковым процессорам относятся прежде всего x86 от Intel, ARM, любимый производителями смартфонов, и еще многие процессоры разной степени известности и коммерческой успешности.
UPD6:
"В случае чтения файла программа обращается к драйверу диска." - тут две ошибки. Во-первых, сначала обращение идет к драйверу ФС. Во-вторых, программа не выбирает к какому драйверу она обращается... – Pavel Mayorov 2 часа назад
@Pavel Mayorov Тут нет никаких ошибок потому что:
В данном случае не важно, что сначала отрабатывает драйвер файловой системы, так как драйвер файловой системы не обращается к контроллеру диска. А вопрос был именно о том, как осуществляется разделение доступа к контроллеру диска. Когда программа осуществляет system call, то она тем самым выбирает, к какому драйверу ОС перенаправит это обращение. То есть выбирая вид системного запроса (к диску, к экрану, к порту USB), программа тем самым выбирает конечный драйвер контроллера, который будет вызван. Безусловно, перед тем как отработает драйвер контроллера, будет выполнено очень много килобайтов кода самой ОС, включая (в случае обращения к диску) контроль прав доступа к файлу, контроль занятости файла другой программой, возможно код сжатия/разжатия диска в случае архивированного тома, возможно код сетевой подсистемы в случае удаленного диска и еще куча всяких чудес включая код виртуальной машины (в случае нахождения программы на виртуальной машине). Но вопрос был не об всех этих чудесах, а о том, как в целом с точки зрения аппаратуры организован в ОС доступ из многих программ к аппаратным ресурсам, которые в системе существуют в единственном экземпляре. И какая аппаратная поддержка ОС существует в современных процессорах.
UPD7:
Тут просили указать литературу по обсуждаемому вопросу. Я вспомнил одно название:
«Архитектура микро-процессора 80286» авторы С. П. Морс, Д. Д. Алберт.
Книга древняя, 1990 года издания. Да и процессор древний. Но там как раз рассказано, как в 80286 организована аппаратная поддержка нормальной ОС, а не MSDOS, которая, как известно, работала в реальном режиме процессора Intel. У меня эта книга была в бумажном виде. Конечно 80286 это немного не то, что потом сделал Интел в 80386 и дальше, но хоть что-то. Да и книга, конечно, коряво написана. А может это перевод такой. Но, как говорится, на безрыбье и рак рыба. Еще важно понимать, что архитектура x86 это не идеал, есть и другие подходы.
UPD8:
Подводя итог можно сказать, что у процессоров, которые предназначены для работы под многозадачными и многопользовательскими дисковыми операционными системами должна быть некоторая периферия для поддержки такой работы, в частности должно быть MMU. Также у таких процессоров должны быть разные аппаратные уровни приоритета исполнения команд и некоторые команды должны быть запрещены для выполнения на том уровне приоритета, на котором запускаются пользовательские задачи. То есть такие процессоры должны иметь то, что называется аппаратной поддержкой ОС. По аналогии с тем, что, например, сигнальные процессоры должны иметь аппаратную поддержку операций с плавающей точкой.
UPD9:
В книге «Архитектура микро-процессора 80286» надо смотреть главу 5. Там описано какие у процессора 80286 есть аппаратные средства для поддержки ОС.

Комментариев нет:

Отправить комментарий