Получить доступ

Что такое системный вызов и чем режим ядра отличается от пользовательского?

MiddleОперационные системы
Ещё спрашивают так:
  • Как программа читает файл на уровне ОС?
  • Почему системные вызовы дорогие?
  • Что показывает strace?
Коротко

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

  • read, write, open, socket, mmap, fork — системные вызовы
  • Переход в ядро и обратно стоит десятки–сотни наносекунд плюс работа самого ядра
  • Буферизация уменьшает их число: тысяча write по байту против одного по килобайту

А ещё strace показывает, какие вызовы делает процесс

Так подсказку видит пользователь sotto на живом собеседовании — за пару секунд, пока вы думаете вслух.

Подробно

Процессор различает уровни привилегий. Ядро ОС работает в привилегированном режиме: управляет памятью, устройствами, планированием. Приложения — в пользовательском: им недоступны порты ввода-вывода, таблицы страниц и чужая память. Ошибка в приложении поэтому не роняет систему.

Системный вызов — контролируемый вход в ядро. Программа кладёт номер вызова и аргументы в регистры и выполняет специальную инструкцию (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 — потоки дерутся за блокировку.

Пример

$ strace -c python3 -c "for i in range(10000): print(i, flush=True)" > /dev/null
% time     seconds  usecs/call     calls    syscall
 92.10    0.021300           2     10000    write
...
# flush=True — по системному вызову на строку; без него — несколько десятков

Что хотят услышать

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

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

  • Считать вызов функции библиотеки и системный вызов одним и тем же по цене.
  • Не знать, что print или write без буфера на каждую строку медленный именно из-за вызовов.

Что спросят дальше

Связанные вопросы

Закрытая бета

Подсказки на собеседовании — в реальном времени

sotto слышит вопрос интервьюера и показывает короткий ответ и план, как в блоке «Коротко». Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем