TGViewer
Грокаем C++ Грокаем C++ @grokaemcpp · 9.36K subscribers
Post #1119 4.04K
​​Что происходит при системном вызове?
#опытным

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

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

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

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

Будем все рассматривать на примере linux, как самой популярной серверной ОС.

0️⃣ В коде все начинается с какой-нибудь обертки для системного вызова

Оператор<< для плюсового объекта потока принимает высокоуровневые объекты и вызывает сискол write, чтобы записать данные в поток.

Но даже если вы напрямую из кода вызываете write, то вы вызываете обертку POSIX API, которая уже сама дергает одноименный системный вызов. Без оберток напрямую сисколл можно вызывать только ассемблерными вставками и прочей магией.

int main() {
write(1, "Hello\n", 6);
return 0;
}


1️⃣ Подготовка данных в пользовательском пространстве

Функция обертка при необходимости делает некий препроцессинг аргументов(например форматирование в функции printf).

После копирует номер системного вызова и его готовые аргументы в определённые регистры процессора, где ядро ожидает их найти. В архитектуре x86-64 для Linux это делается так:
- В rax кладется номер системного вызова.
- В rdi, rsi, rdx, r10, r8, r9 кладутся аргументы (не больше шести).

Дальше обёртка выполняет специальную инструкцию, которая инициирует переход в ядро. Например, syscall (в 64-битном режиме x86-64).

2️⃣ Переключение в режим ядра

Как и инструкция call, syscall делает какую-то работу. Только в отличие от call, она минимальна:

👉🏿 сохраняет адрес возврата (следующую инструкцию) в регистр %rcx;

👉🏿 сохраняет регистр флагов RFLAGS в %r11;

👉🏿 загружает новый указатель инструкций из MSR-регистра IA32_LSTAR_MSR (там лежит адрес функции entry_SYSCALL_64 - входной точки для всех сисколов).

После выполнения инструкции syscall ядро получает доступ к привилегированным ресурсам: управлению памятью, прерываниями, устройствами ввода-вывода.

3️⃣ Обработка в ядре

Управление передаётся функции entry_SYSCALL_64 - единой точки входа в ядро. Здесь начинается основная работа.

И в первую очередь происходит смена контекста - ядро сохраняет значения регистров текущего потока и заменяет пользовательский стек на стек ядра. Это нужно для лучшей защиты от злонамеренного изменения стека ядрённых вызовов.

После нужно найти конкретный обработчик. Ядро извлекает номер системного вызова из %rax и проверяет, не выходит ли он за допустимые пределы. После номер вызова используется как индекс в глобальной таблице ядра – sys_call_table. Это массив указателей на функции-обработчики.

Перед выполнением операции ядро проверяет, есть ли у текущего процесса достаточно прав (например, на запись в файл). И затем вызывается нужная функция, внутри которой и выполняется полезная логика сискола.

4️⃣ Возврат в пользовательский режим

После того как обработчик завершил работу происходит следующее:

👉🏿 Сохранение результата – возвращаемое значение обработчика помещается в регистр %rax. Если произошла ошибка – возвращается отрицательное число (код ошибки).

👉🏿 Восстановление контекста – ядро восстанавливает сохранённые пользовательские регистры и стек.

👉🏿 Обратный переход – выполняется инструкция sysret. Она использует сохранённые в %rcx и %r11 значения, чтобы восстановить адрес возврата и флаги, переключает процессор обратно в пользовательский режим и передаёт управление коду, следующему за syscall.

И самый главный вопрос здесь: а какой оверхэд приходится на обеспечение системного вызова(не учитывая его логику)? В следующем посте обсудим это.

Be privileged. Stay cool.

#OS #howitworks
  • 🔥 21
  • ❤ 16
  • 👍 10
  • ❤‍🔥 2
More from @grokaemcpp
  1. Oct 8, 2026​​Strict weak ordering #опытным На первый взгляд, всё выглядит рабочим: мы создаём 40 зака…
  2. Oct 7, 2026​​Где-то баг... #опытным Вот вам код: struct Order { int price; int id; }; int main() { st…
  3. Oct 5, 2026Откуда spurious wakeup на кондваре? #опытным У кондваров есть метод std::condition_variabl…
  4. Oct 1, 2026​​Stacktrace. Tips #опытным Чтобы полноценно работать со стандартными трейсами, нужно знат…
  5. Sep 28, 2026​​Stacktrace #опытным Одна из проблема исключений - непонятно, откуда оно прилетело. Ну да…
  6. Sep 25, 2026​​std::spanstream #опытным Радостная весть для всех, кто пользуется iostreams! В C++23 доб…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →