Системные вызовы
Как программа просит ядро об услуге — и почему это не обычный вызов функции.
Системный вызов (system call) — это запрос программы к ядру ОС на выполнение привилегированной операции: открыть файл, выделить память, создать процесс, отправить данные в сеть.
Чем системный вызов отличается от обычной функции
Когда вы вызываете обычную функцию, процессор просто прыгает на её адрес — всё в одном режиме. Системный вызов другой: он переключает процессор в режим ядра, передаёт управление ядру, а после выполнения возвращает результат и режим. Это похоже на обращение к «другой власти», а не на простой переход внутри своей программы.
Обычно вы не пишете системные вызовы вручную. Их прячут библиотечные функции. Например, в C функция printf() в итоге дёргает системный вызов write(), а fopen() — open(). Программисту удобнее звать библиотеку, а та уже общается с ядром.
Категории системных вызовов
| Категория | Примеры (Linux) | Зачем |
| Процессы | fork, execve, exit, wait | создание и завершение процессов |
| Файлы | open, read, write, close | работа с файлами |
| Память | mmap, brk, munmap | выделение и освобождение памяти |
| Устройства/сеть | ioctl, socket, send | ввод-вывод и сеть |
| Информация | getpid, time, uname | сведения о системе |
Как выглядит реальный код
Вот фрагмент на C, который открывает файл и читает из него. Это реальный системный код — он не запускается в браузере, поэтому здесь только подсветка. Обратите внимание: open, read, close — это и есть системные вызовы.
int fd = open("data.txt", O_RDONLY); // системный вызов open
char buf[128];
int n = read(fd, buf, sizeof(buf)); // системный вызов read
close(fd); // системный вызов close
А вот как с помощью утилиты strace можно подсмотреть, какие системные вызовы делает любая программа в Linux:
strace -c ls
# покажет таблицу: какие системные вызовы и сколько раз вызвала команда ls
Почему вызовов «много, но дёшево быть не должно»
Каждый системный вызов — это переключение режима, а оно стоит сотни-тысячи тактов. Поэтому хорошие программы стараются делать меньше системных вызовов, но крупнее. Считать файл одним read на 64 КБ быстрее, чем 65536 вызовами по одному байту. Эту экономию обеспечивает буферизация — к ней мы ещё вернёмся.
Модель «запрос — обслуживание»
Симулируем счётчик системных вызовов: программа просит ядро об операциях, ядро их обслуживает. Это игрушечная модель, но она показывает суть «программа просит — ядро делает».
requests = ["open", "read", "read", "read", "write", "close"]
cost = {"open": 50, "read": 30, "write": 40, "close": 20} # условные такты
total = 0
for call in requests:
c = cost[call]
total += c
print(f"syscall {call:6} -> ядро обслужило за {c} тактов")
print(f"Всего системных вызовов: {len(requests)}")
print(f"Суммарная стоимость: {total} тактов")
print(f"Если бы read шёл по байту x100, добавилось бы {30*100} тактов")
Вывод:
syscall open -> ядро обслужило за 50 тактов syscall read -> ядро обслужило за 30 тактов syscall read -> ядро обслужило за 30 тактов syscall read -> ядро обслужило за 30 тактов syscall write -> ядро обслужило за 40 тактов syscall close -> ядро обслужило за 20 тактов Всего системных вызовов: 6 Суммарная стоимость: 200 тактов Если бы read шёл по байту x100, добавилось бы 3000 тактов
Итог
- Системный вызов — запрос к ядру на привилегированную операцию.
- В отличие от обычной функции он переключает процессор в режим ядра.
- Категории: процессы, файлы, память, устройства/сеть, информация.
- Библиотеки (например,
printf) прячут системные вызовы за удобным API. - Системные вызовы дороги — лучше делать их реже, но крупнее.