Процессор различает уровни привилегий. Ядро ОС работает в привилегированном режиме: управляет памятью, устройствами, планированием. Приложения — в пользовательском: им недоступны порты ввода-вывода, таблицы страниц и чужая память. Ошибка в приложении поэтому не роняет систему.
Системный вызов — контролируемый вход в ядро. Программа кладёт номер вызова и аргументы в регистры и выполняет специальную инструкцию (syscall на x86-64). Процессор переходит в режим ядра, ядро проверяет аргументы и права, делает работу и возвращает результат. Обычно вызовы спрятаны за библиотекой: open() из libc, FileInputStream в Java, open() в Python — внутри всё равно системные вызовы.
Основные группы: файлы (open, read, write, close, fsync), сеть (socket, connect, accept, send, recv, epoll_wait), память (mmap, brk), процессы (fork, execve, exit, kill), время.
Цена. Сам переход — порядка сотни наносекунд, но после уязвимостей Meltdown/Spectre защита сделала его дороже, а ещё теряются кэши. Если делать системный вызов на каждый байт, программа будет в основном переходить в ядро. Поэтому:
- буферизация —
BufferedWriter, io.BufferedReader, stdio в C копят данные и делают один write на блок; - пакетные вызовы —
readv/writev, sendmmsg, io_uring в Linux ставит запросы в очередь без вызова на каждый; - отображение в память —
mmap вместо read; - vDSO —
gettimeofday выполняется без перехода в ядро.
Диагностика. strace -c -p <pid> считает вызовы и время в них, strace -e trace=network показывает сетевые. Типичная находка — тысячи мелких write без буфера или futex — потоки дерутся за блокировку.