Что такое системный вызов
Перейти к содержимому

Что такое системный вызов

Эволюция системных вызовов архитектуры x86

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

System call

Каждый раз, когда мы хотим что-то отобразить на мониторе, записать в устройство, считать с файла, нам приходится обращаться к ядру ОС. Именно ядро ОС отвечает за любое общение с железом, именно там происходит работа с прерываниями, режимами процессора, переключениями задач… Чтобы пользователь программой не смог завалить работу всей операционной системы, было решено разделить пространство памяти на пространство пользователя (область памяти, предназначенная для выполнения пользовательских программ) и пространство ядра, а также запретить пользователю доступ к памяти ядра ОС. Реализовано это разделение в x86-семействе аппаратно при помощи сегментной защиты памяти. Но пользовательской программе нужно каким-то образом общаться с ядром, для этого и была придумана концепция системных вызовов.

Системный вызов — способ обращения программы пользовательского пространства к пространству ядра. Со стороны это может выглядеть как вызов обычной функции со своим собственным calling convention, но на самом деле процессором выполняется чуть больше действий, чем при вызове функции инструкцией call. Например, в архитектуре x86 во время системного вызова как минимум происходит увеличение уровня привилегий, замена пользовательских сегментов на сегменты ядра и установка регистра IP на обработчик системного вызова.

Программист обычно не работает с системными вызовами напрямую, так как системные вызовы обернуты в функции и скрыты в различных библиотеках, например libc.so в Linux или же ntdll.dll в Windows, с которыми и взаимодействует прикладной разработчик.

Теоретически, реализовать системный вызов можно при помощи любого исключения, хоть при помощи деления на 0. Главное — это передача управления ядру. Рассмотрим реальные примеры реализаций исключений.

Способы реализации системных вызовов

Выполнение неверной инструкции.

Ранее, ещё на 80386 это был самый быстрый способ сделать системный вызов. Для этого обычно применялась бессмысленная и неверная инструкция LOCK NOP, после исполнения которой процессором вызывался обработчик неверной инструкции. Это было больше 20 лет назад и, говорят, этим приёмом обрабатывались системные вызовы в корпорации Microsoft. Обработчик неверной инструкции в наши дни используется по назначению.

Call gates

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

  • Call gates
  • Trap gates (для исключений, вроде int 3, требующих выполнения участка кода)
  • Interrupt gates (аналогичен trap gates, но с некоторыми отличиями)
  • Task gates (полагалось, что будут использоваться для переключения задач)

Нам интересны только call gates, так как именно через них планировалось реализовывать системные вызовы в x86.

Call gate реализован при помощи инструкции call far или jmp far и принимает в качестве параметра call gate-дескриптор, который настраивается ядром ОС. Является достаточно гибким механизмом, так как возможен переход и на любой уровень защитного кольца, и на 16-битный код. Считается, что call gates производительней прерываний. Этот способ использовался в OS/2 и Windows 95. Из-за неудобства использования в Linux механизм так и не был реализован. Со временем совсем перестал использоваться, так как появились более производительные и простые в обращении реализации системных вызовов (sysenter/sysexit).

Системные вызовы, реализованные в Linux

В архитектуре x86-64 ОС Linux существует несколько различных способов системных вызовов:

  • int 80h
  • sysenter/sysexit
  • syscall/sysret
  • vsyscall
  • vDSO

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

  • Включается защита от чтения/записи/исполнения кода пользовательского пространства.
  • Заменяется пользовательский стек на стек ядра, сохраняются callee-saved регистры.
  • Выполняется обработка системного вызова
  • Восстановление стека, регистров
  • Отключение защиты
  • Выход из системного вызова

Рассмотрим немного подробнее каждый системный вызов.

int 80h

Изначально, в архитектуре x86, Linux использовал программное прерывание 128 для совершения системного вызова. Для указания номера системного вызова, пользователь задаёт в eax номер системного вызова, а его параметры располагает по порядку в регистрах ebx, ecx, edx, esi, edi, ebp. Далее вызывается инструкция int 80h, которая программно вызывает прерывание. Процессором вызывается обработчик прерывания, установленный ядром Linux ещё во время инициализации ядра. В x86-64 вызов прерывания используется только во время эмуляции режима x32 для обратной совместимости.

В принципе, никто не запрещает пользоваться инструкцией в расширенном режиме. Но вы должны понимать, что используется 32-битная таблица вызовов и все используемые адреса должны помещаться в 32-битное адресное пространство. Согласно SYSTEM V ABI [4] §3.5.1, для программ, виртуальный адрес которых известен на этапе линковки и помещается в 2гб, по умолчанию используется малая модель памяти и все известные символы находятся в 32-битном адресном пространстве. Под это определение подходят статически скомпилированные программы, где и возможно использовать int 80h. Пошаговая работа прерывания подробно описана на stackoverflow.

В ядре обработчиком этого прерывания является функция entry_INT80_compat и находится в arch/x86/entry/entry_64_compat.S

Или в расширенном режиме (программа работает так как компилируется статически)

sysenter/sysexit

Спустя некоторое время, ещё когда не было x86-64, в Intel поняли, что можно ускорить системные вызовы, если создать специальную инструкцию системного вызова, тем самым минуя некоторые издержки прерывания. Так появилась пара инструкций sysenter/sysexit. Ускорение достигается за счёт того, что на аппаратном уровне при выполнении инструкции sysenter опускается множество проверок на валидность дескрипторов, а так же проверок, зависящих от уровня привилегий [3] §6.1. Также инструкция опирается на то, что вызывающая её программа использует плоскую модель памяти. В архитектуре Intel, инструкция валидна как для режима совместимости, так и для расширенного режима, но у AMD данная инструкция в расширенном режиме приводит к исключению неизвестного опкода [3]. Поэтому в настоящее время пара sysenter/sysexit используется только в режиме совместимости.

В ядре обработчиком этой инструкции является функция entry_SYSENTER_compat и находится в arch/x86/entry/entry_64_compat.S

Несмотря на то, что в реализации архитектуры от Intel инструкция валидна, в расширенном режиме скорее всего такой системный вызов никак не получится использовать. Это из-за того, что в регистре ebp сохраняется текущее значение стека, а адрес верхушки независимо от модели памяти находится вне 32-битного адресного пространства. Это всё потому, что Linux отображает стек на конец нижней половины каноничного адреса пространства.

Разработчики ядра Linux предостерегают пользователей от жесткого программирования sysenter из-за того, что ABI системного вызова может измениться. Из-за того, что Android не последовал этому совету, Linux пришлось откатить свой патч для сохранения обратной совместимости. Правильно реализовывать системный вызов нужно используя vDSO, речь о которой будет идти далее.

syscall/sysret

Так как именно AMD разработали x86-64 архитектуру, которая и называется AMD64, то они решили создать свой собственный системный вызов. Инструкция разрабатывалась AMD, как аналог sysenter/sysexit для архитектуры IA-32. В AMD позаботились о том, чтобы инструкция была реализована как в расширенном режиме, так и в режиме совместимости, но в Intel решили не поддерживать данную инструкцию в режиме совместимости. Несмотря на всё это, Linux имеет 2 обработчика для каждого из режимов: для x32 и x64. Обработчиками этой инструкции является функции entry_SYSCALL_64 для x64 и entry_SYSCALL_compat для x32 и находится в arch/x86/entry/entry_64.S и arch/x86/entry/entry_64_compat.S соответственно.

Кому интересно более подробно ознакомиться с инструкциями системных вызовов, в мануале Intel [0] (§4.3) приведён их псевдокод.

Для тестирования следующего примера потребуется ядро с конфигурацией CONFIG_IA32_EMULATION=y и компьютер AMD. Если же у вас компьютер фирмы Intel, то можно запустить пример на виртуалке. Linux может без предупреждения изменить ABI и этого системного вызова, поэтому в очередной раз напомню: системные вызовы в режиме совместимости правильнее исполнять через vDSO.

Непонятна причина, по которой AMD решили разработать свою инструкцию вместо того, чтобы расширить инструкцию Intel sysenter на архитектуру x86-64.

vsyscall

При переходе из пространства пользователя в пространство ядра происходит переключение контекста, что является не самой дешёвой операцией. Поэтому, для улучшения производительности системных вызовов, было решено их обрабатывать в пространстве пользователя. Для этого было зарезервировано 8 мб памяти для отображения пространства ядра в пространство пользователя. В эту память для архитектуры x86 поместили 3 реализации часто используемых read-only вызова: gettimeofday, time, getcpu.

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

Для того, чтобы пример работал, необходимо, чтобы в ядре была включена поддержка vsyscall: CONFIG_X86_VSYSCALL_EMULATION=y

Linux не отображает vsyscall в режиме совместимости.

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

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

Первый способ — при помощи замены адреса функции на системный вызов syscall. В таком случае виртуальный системный вызов функции gettimeofday на x86-64 выглядит следующим образом:

Где 0x60 — код системного вызова функции gettimeofday.

Второй же способ немного интереснее. При вызове функции vsyscall генерируется исключение Page fault, которое обрабатывается Linux. ОС видит, что ошибка произошла из-за исполнения инструкции по адресу vsyscall и передаёт управление обработчику виртуальных системных вызовов emulate_vsyscall (arch/x86/entry/vsyscall/vsyscall_64.c).

Реализацией vsyscall можно управлять при помощи параметра ядра vsyscall. Можно как отключить виртуальный системный вызов при помощи параметра vsyscall=none , задать реализацию как при помощи инструкции syscall syscall=native , так и через Page fault vsyscall=emulate .

vDSO (Virtual Dynamic Shared Object)

Чтобы исправить основной недостаток vsyscall, было предложено реализовать системные вызовы в виде отображения динамически подключаемой библиотеки, к которой применяется технология ASLR. В «длинном» режиме библиотека называется linux-vdso.so.1, а в режиме совместимости — linux-gate.so.1. Библиотека автоматически подгружается для каждого процесса, даже статически скомпилированного. Увидеть зависимости приложения от неё можно при помощи утилиты ldd в случае динамической компоновки библиотеки libc.

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

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

Для режима совместимости:

Правильнее всего искать функции vDSO при помощи извлечения адреса библиотеки из вспомогательного вектора AT_SYSINFO_EHDR и последующего парсинга разделяемого объекта. Пример парсинга vDSO из вспомогательного вектора можно найти в исходном коде ядра: tools/testing/selftests/vDSO/parse_vdso.c

Или если интересно, то можно покопаться и посмотреть, как парсится vDSO в glibc:

  1. Парсинг вспомогательных векторов: elf/dl-sysdep.c
  2. Парсинг разделяемой библиотеки: elf/setup-vdso.h
  3. Установка значений функций: sysdeps/unix/sysv/linux/x86_64/init-first.c, sysdeps/unix/sysv/linux/x86/gettimeofday.c, sysdeps/unix/sysv/linux/x86/time.c

Согласно System V ABI AMD64 [4] вызовы должны происходить при помощи инструкции syscall. На практике же к этой инструкции добавляются вызовы через vDSO. Поддержка системных вызовов в виде int 80h и vsyscall остались для обратной совместимости.

Сравнение производительности системных вызовов

С тестированием скорости системных вызовов всё неоднозначно. В архитектуре x86 на выполнение одной инструкции влияет множество факторов таких как наличие инструкции в кэше, загруженность конвейера, даже существует таблица задержек для данной архитектуры [2]. Поэтому достаточно сложно определить скорость выполнения участка кода. У Intel есть даже специальный гайд по замеру времени для участка кода [1]. Но проблема в том, что мы не можем замерить время согласно документу из-за того, что нам нужно вызывать объекты ядра из пользовательского пространства.

Поэтому было решено замерить время при помощи clock_gettime и тестировать производительность вызова gettimeofday, так как он есть во всех реализациях системных вызовов. На разных процессорах время может отличаться, но в целом, относительные результаты должны быть схожи.

Программа запускалась несколько раз и в итоге бралось минимальное время исполнения.
Тестирование int 80h, sysenter и vDSO-32 производилось в режиме совместимости.

О системе
cat /proc/cpuinfo | grep «model name» -m 1 — Intel® Core(TM) i7-5500U CPU @ 2.40GHz
uname -r — 4.14.13-1-ARCH

Таблица Результатов

Реализация время (нс)
int 80h 498
sysenter 338
syscall 278
vsyscall emulate 692
vsyscall native 278
vDSO 37
vDSO-32 51

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

Все текущие сравнения производительности были произведены с патчем KPTI, исправляющим уязвимость meltdown.

Бонус: Производительность системных вызовов без KPTI

Патч KPTI был разработан специально для исправления уязвимости meltdown. Как известно, данный патч замедляет производительность ОС. Проверим производительность с выключенным KPTI (pti=off).

Таблица результатов с выключенным патчем

Реализация Время (нс) Увеличение времени исполнения после патча (нс) Ухудшение производительности после патча (t1 — t0) / t0 * 100%
int 80h 317 181 57%
sysenter 150 188 125%
syscall 103 175 170%
vsyscall emulate 496 196 40%
vsyscall native 103 175 170%
vDSO 37 0 0%
vDSO-32 51 0 0%

Переход в режим ядра и обратно в среднем после патча стал занимать примерно на 180 нс. больше времени, видимо это и есть цена сброса TLB-кэша.

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

Системный вызов

Систе́мный вы́зов (англ.  system call ) в программировании и вычислительной технике — обращение прикладной программы к ядру операционной системы для выполнения какой-либо операции.

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

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

Ссылки

  • Технологии в операционных системах

Wikimedia Foundation . 2010 .

Полезное

Смотреть что такое «Системный вызов» в других словарях:

МИРО-СИСТЕМНЫЙ АНАЛИЗ — текст И. Валлерстайна , опубликованный в 1987. Согласно Валлерстайну, миро системный анализ это не теория о социальном мире или его части. Это протест против способов, которыми было структурировано социальное научное исследование при его… … Социология: Энциклопедия

санкционированный вызов — Вызов системы, программы или данных, разрешенный данному пользователю. Как правило, реализуется путем ввода и проверки пароля. [Домарев В.В. Безопасность информационных технологий. Системный подход.] Тематики защита информации EN authorized call … Справочник технического переводчика

Интерфейс системный — Интерфейс (от англ. interface поверхность раздела, перегородка) совокупность средств и методов взаимодействия между элементами системы. В зависимости от контекста, понятие применимо как к отдельному элементу (интерфейс элемента), так и к… … Википедия

GNU Assembler — (GAS) Тип ассемблер Разработчик Фонд свободного ПО Написана на C Операционная система кроссплатформенное ПО Аппаратная платформа кроссплатформенное ПО Последняя версия 2.20.1 (3 ма … Википедия

TTY-абстракция — Стиль этой статьи неэнциклопедичен или нарушает нормы русского языка. Статью следует исправить согласно стилистическим правилам Википедии. У этого термина существуют и другие значения, см. Tty. Подсистема TTY, или TTY абстракция  это одна из … Википедия

Библиотека потоков POSIX — (англ. Native POSIX Thread Library, англ. NPTL) набор программного кода, позволяющего ядру операционной системы Linux достаточно эффективно выполнять программы, использующие потоки POSIX. В настоящее время интегрирована в библиотеку… … Википедия

SIGSYS — Описание: Неправильный аргумент системного вызова По умолчанию: завершение процесса коды SA SIGINFO (не имеет специфических кодов) В POSIX системах, SIGSYS  сигнал, предназначенный для посылки программе, при попытке передать… … Википедия

Fork — У этого термина существуют и другие значения, см. Форк (значения). В Unix системах, fork()  системный вызов, создающий новый процесс( потомок), который является почти точной копией процесса( родителя), выполняющего этот вызов. Между… … Википедия

fork — У этого термина существуют и другие значения, см. Форк (значения). В Unix системах, fork()  системный вызов, создающий новый процесс (потомок), который является практически полной копией процесса родителя, выполняющего этот вызов. Между… … Википедия

Chroot — операция изменения корневого каталога в Unix подобных операционных системах. Программа, запущенная с изменённым корневым каталогом, будет иметь доступ только к файлам, содержащимся в данном каталоге. Поэтому, если нужно обеспечить программе… … Википедия

10) Системный вызов в ОС

Что такое системный вызов в операционной системе?

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

Системный вызов предлагает услуги операционной системы пользовательским программам через API (интерфейс прикладного программирования). Системные вызовы являются единственными точками входа в систему ядра.

Из этого руководства по операционной системе вы узнаете:

Пример системного вызова

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

В интерактивной системе этот тип выполнения программы требует некоторых системных вызовов ОС.

  • Первый звонок – написать подсказку на экране
  • Во-вторых, чтобы прочитать с клавиатуры, символы, которые определяют два файла.

Как работает системный вызов?

Вот шаги для системного вызова:

Как вы можете видеть на приведенной выше диаграмме.

Шаг 1) Процессы, выполняемые в пользовательском режиме до тех пор, пока системный вызов не прервет его.

Шаг 2) После этого системный вызов выполняется в режиме ядра в приоритетном порядке.

Шаг 3) По завершении выполнения системного вызова управление возвращается в режим пользователя.,

Шаг 4) Выполнение пользовательских процессов возобновляется в режиме ядра.

Зачем вам системные вызовы в ОС?

Ниже приведены ситуации, которые требуют системных вызовов в ОС:

  • Чтение и запись из файлов требуют системных вызовов.
  • Если файловая система хочет создать или удалить файлы, требуются системные вызовы.
  • Системные вызовы используются для создания и управления новыми процессами.
  • Сетевые подключения требуют системных вызовов для отправки и получения пакетов.
  • Доступ к аппаратным устройствам, таким как сканер, принтер, требуется системный вызов.

Типы системных вызовов

Вот пять типов системных вызовов, используемых в ОС:

  • Контроль процесса
  • Управление файлами
  • Управление устройством
  • Информационное обслуживание
  • связи

Контроль процесса

Эти системные вызовы выполняют задачу создания процесса, завершения процесса и т. Д.

  • Конец и Прервать
  • Загрузить и выполнить
  • Создать процесс и завершить процесс
  • Ожидание и подписанное событие
  • Выделить и освободить память

Управление файлами

Системные вызовы управления файлами обрабатывают задания по обработке файлов, такие как создание файла, чтение, запись и т. Д.

  • Создать файл
  • Удалить файл
  • Открыть и закрыть файл
  • Читать, писать и перемещать
  • Получить и установить атрибуты файла

Управление устройством

Управление устройствами выполняет работу с устройствами, такими как чтение из буферов устройств, запись в буферы устройств и т. Д.

  • Запрос и релиз устройства
  • Логически подключать / отключать устройства
  • Получить и установить атрибуты устройства

Информационное обслуживание

Он обрабатывает информацию и ее передачу между ОС и программой пользователя.

  • Получить или установить время и дату
  • Получить атрибуты процесса и устройства

Связь:

Эти типы системных вызовов специально используются для межпроцессного взаимодействия.

  • Создание, удаление коммуникационных соединений
  • Отправить, получить сообщение
  • Помогите ОС перенести информацию о статусе
  • Подключите или отсоедините удаленные устройства

Правила передачи параметров для системного вызова

Вот общие общие правила для передачи параметров в системный вызов:

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

Важные системные вызовы, используемые в ОС

Подождите()

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

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

вилка ()

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

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

убийство():

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

Выход():

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *