TL; DR
Инструкция syscall
сама по себе действует как прославленный переход, это аппаратно поддерживаемый способ эффективного и безопасного перехода из непривилегированного пользовательского пространства в ядро.
Инструкция syscall
переходит к точке входа ядра, которая отправляет вызов.
До x86_64 использовались два других механизма: инструкция int
и инструкция sysenter
.
Они имеют разные точки входа (все еще присутствующие сегодня в 32-разрядных ядрах и в 64-разрядных ядрах, которые могут запускать 32-разрядные программы пользовательского пространства).
Первый использует механизм прерываний x86 и может быть перепутан с диспетчеризацией исключений (которая также использует механизм прерываний).
Однако исключения являются ложными событиями, в то время как int
используется для генерации программного прерывания, опять же, прославленного скачка.
Язык C не занимается системными вызовами, он использует среду выполнения C для выполнения всех взаимодействий со средой будущей программы.
Среда выполнения C реализует вышеупомянутые взаимодействия через механизм, специфичный для среды.
Могут быть различные уровни программных абстракций, но в конце API-интерфейсы ОС вызываются.
Термин API используется для обозначения контракта, строго говоря, использование API не требует вызова части кода ядра (тенденция заключается в реализации некритических функций в пользовательском пространстве для ограничения эксплуатируемый код), здесь нас интересует только подмножество API, которое требует переключения привилегий.
В Linux ядро предоставляет набор служб, доступных из пространства пользователя, эти точки входа называются системными вызовами .
В Windows службы ядра (доступ к которым осуществляется по тому же механизму аналогов Linux) считаются частными в том смысле, что они не обязаны быть стабильными во всех версиях.
Вместо этого в качестве точек входа используется набор экспортируемых функций DLL / EXE (например, ntoskrnl.exe, hal.dll, kernel32.dll, user32.dll), которые в свою очередь используют службы ядра через (частный) системный вызов.
Обратите внимание, что в Linux большинство системных вызовов имеют оболочку POSIX, поэтому можно использовать эти оболочки, которые являются обычными функциями C, для вызова системного вызова.
Базовый ABI отличается, как и для отчетов об ошибках; Обертка переводит между двумя мирами.
Среда выполнения C вызывает API-интерфейсы ОС, в случае Linux системные вызовы используются напрямую, поскольку они являются общедоступными (в том смысле, что они стабильны в разных версиях), в то время как для Windows обычные библиотеки DLL, такие как kernel32.dll, являются помечены как зависимости и используются.
Мы дошли до того, что программе пользовательского режима, являющейся частью среды выполнения C (Linux) или частью DLL API (Windows), необходимо вызывать код в ядре.
Архитектура x86 исторически предлагала различные способы сделать это, например, call gate .
Другой способ - через инструкцию int , у нее есть несколько преимуществ:
- Это то, что BIOS и DOS делали в свое время.
В реальном режиме целесообразно использовать инструкции int
, поскольку номер вектора (например, 21h
) легче запомнить, чем удаленный адрес (например, 0f000h:0fff0h
).
- Сохраняет флаги.
- Его легко настроить (настройка ISR относительно проста).
С модернизацией архитектуры у этого механизма появился большой недостаток: он медленный.
До введения инструкции sysenter
(обратите внимание, sysenter
, а не syscall
) не было более быстрой альтернативы (вентиль вызова был бы одинаково медленным).
С появлением Pentium Pro / II [1] была введена новая пара инструкций sysenter
и sysexit
для ускорения системных вызовов.
Linux начал использовать их начиная с версии 2.5 и, по-моему, до сих пор используется в 32-битных системах.
Я не буду объяснять весь механизм инструкции sysenter
и сопутствующего VDSO , необходимого для его использования, нужно лишь сказать, что он был быстрее, чем механизм int
(я не могу найдите статью от Энди Глеу, где он говорит, что sysenter
оказался медленным на Pentium III, я не знаю, как он работает в настоящее время).
С появлением x86-64 ответ AMD на sysenter
, то есть пару syscall
/ sysret
, стал де-факто способом переключения из режима пользователя в режим ядра.
Это связано с тем, что sysenter
на самом деле быстрый и очень простой (он копирует rip
и rflags
в rcx
и r11
соответственно, маски rflags
и переходит на адрес, установленный в IA32_LSTAR
).
64-битные версии Linux и Windows используют syscall
.
Напомним, что ядро может быть передано через три механизма:
- Программные прерывания.
Это было int 80h
для 32-битной Linux (до 2.5) и int 2eh
для 32-битной Windows.
- Через
sysenter
.
Используется 32-битными версиями Linux начиная с 2.5.
- Через
syscall
.
Используется в 64-битных версиях Linux и Windows.
Вот хорошая страница, чтобы привести ее в лучшую форму .
Среда выполнения C обычно представляет собой статическую библиотеку, предварительно скомпилированную, которая использует один из трех методов, описанных выше.
Инструкция syscall
передает управление точке входа ядра (см. entry_64.s ) напрямую.
Это инструкция, которая просто делает это, она не реализована ОС, она используется ОС .
Термин исключение перегружен в CS, C ++ имеет исключения, также как и Java и C #.
ОС может иметь механизм захвата исключений, не зависящий от языка (в Windows он когда-то назывался SEH , теперь переписан).
Процессор также имеет исключения.
Я считаю, что мы говорим о последнем значении.
Исключения отправляются через прерывания, они являются своего рода прерыванием.
Само собой разумеется, что, хотя исключения являются синхронными (они происходят в определенных точках воспроизведения), они являются «нежелательными», они являются исключительными, в том смысле, что программисты склонны избегать их, а когда они происходят, это происходит из-за ошибки, необработанного угла случай или плохая ситуация.
Таким образом, они не используются для передачи управления ядру (они могли бы).
Вместо этого использовались программные прерывания (тоже синхронные); механизм почти такой же (исключения могут иметь код состояния, помещенный в стек ядра), но семантика другая.
Мы никогда не защищали нулевой указатель, не обращались к неотображенной странице или тому подобное для вызова системного вызова, вместо этого мы использовали инструкцию int
.