Системные вызовы: syscall в Linux

Урок показывает, как программа просит ядро об услугах: вывести текст, прочитать ввод, завершиться.

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

Почему нельзя «просто вывести»

Программа не имеет прямого доступа к экрану, диску или сети — этим управляет ядро ради безопасности. Чтобы что-то вывести, программа делает системный вызов: складывает номер услуги и аргументы в регистры и выполняет команду syscall, передавая управление ядру.

Соглашение системных вызовов

У syscall свои правила (отличаются от обычных функций номером в rax и регистром r10 вместо rcx):

РегистрЧто содержит
raxномер системного вызова
rdi1-й аргумент
rsi2-й аргумент
rdx3-й аргумент

Несколько важных номеров в Linux x86-64: 1 — write, 0 — read, 60 — exit.

Вывод строки на экран

Чтобы напечатать «Hi», вызываем write (rax=1) в стандартный вывод (rdi=1):

section .data
    msg db "Hi", 10        ; текст и перевод строки
    len equ $ - msg        ; длина = текущий адрес минус адрес msg

section .text
    global _start
_start:
    mov rax, 1             ; write
    mov rdi, 1             ; stdout
    mov rsi, msg           ; адрес строки
    mov rdx, len           ; длина
    syscall                ; ядро печатает строку

    mov rax, 60            ; exit
    mov rdi, 0             ; код 0
    syscall

Как работает под капотом

Промоделируем «диспетчер» системных вызовов на Python: по номеру в rax выбираем действие — ровно как ядро.

def syscall(rax, rdi=0, rsi="", rdx=0):
    if rax == 1:                 # write
        print("WRITE:", rsi[:rdx])
    elif rax == 60:              # exit
        print("EXIT с кодом", rdi)

syscall(1, rdi=1, rsi="Hi\n", rdx=3)   # write(stdout, "Hi\n", 3)
syscall(60, rdi=0)                       # exit(0)

Вывод:

WRITE: Hi

EXIT с кодом 0

Ядро по номеру в rax понимает, какую услугу запросили, и выполняет её. Все функции вроде printf в C под капотом в итоге доходят до такого же системного вызова write.

Частые ошибки

  • Перепутать r10 и rcx. У syscall четвёртый аргумент в r10, а не в rcx (команда syscall портит rcx).
  • Указать неверную длину строки. Слишком большая rdx выведет лишний мусор из памяти.
  • Забыть про exit. Без явного exit после кода исполнение «провалится» дальше в мусор и упадёт.

Итог

  • syscall — это просьба к ядру об услуге (вывод, ввод, завершение).
  • Номер вызова кладут в rax, аргументы — в rdi/rsi/rdx.
  • В Linux x86-64: write=1, read=0, exit=60.
  • Высокоуровневый вывод (printf, print) в итоге сводится к write через syscall.
Проверьте себя
1. В какой регистр кладут номер системного вызова?
Ardi
Brsi
Crax
Drsp
2. Какой номер у системного вызова exit в Linux x86-64?
A0
B1
C60
D127
3. Зачем вообще нужны системные вызовы?
AЧтобы ускорить арифметику
BЧтобы программа просила ядро о действиях, которые сама делать не вправе (вывод, файлы)
CЧтобы сократить код
DЧтобы заменить функции