Что такое системный вызов и чем режим ядра отличается от пользовательского?
- Как программа читает файл на уровне ОС?
- Почему системные вызовы дорогие?
- Что показывает strace?
Программы работают в пользовательском режиме без прямого доступа к железу. Всё, что требует привилегий — файлы, сеть, память, процессы, — делается системным вызовом в ядро.
- read, write, open, socket, mmap, fork — системные вызовы
- Переход в ядро и обратно стоит десятки–сотни наносекунд плюс работа самого ядра
- Буферизация уменьшает их число: тысяча write по байту против одного по килобайту
А ещё strace показывает, какие вызовы делает процесс
Подробно
Процессор различает уровни привилегий. Ядро ОС работает в привилегированном режиме: управляет памятью, устройствами, планированием. Приложения — в пользовательском: им недоступны порты ввода-вывода, таблицы страниц и чужая память. Ошибка в приложении поэтому не роняет систему.
Системный вызов — контролируемый вход в ядро. Программа кладёт номер вызова и аргументы в регистры и выполняет специальную инструкцию (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
После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.