Что такое системный вызов и зачем его трассировать

Рейтинг: 78.5% · 14 голосов
Подробный курс по диагностике и производительности Linux для админов и DevOps: с чего начать, методология USE, логи (journalctl, dmesg), процессы, CPU, память и OOM, диск и IO (iostat), сеть (ss, tcpdump), системные вызовы и strace, ltrace, perf, флеймграфы, ftrace, eBPF и bpftrace, lsof, мониторинг. Разбор каждого инструмента с чтением вывода.
Ответить
Аватара пользователя
Dmitry_SRE
Сообщения: 47
Зарегистрирован: 11 май 2026, 05:31

Что такое системный вызов и зачем его трассировать

Сообщение Dmitry_SRE »

Оглавление курса (47)
  1. С чего начать диагностику Linux: методология вместо паники
  2. Первые 60 секунд: экспресс-диагностика нагруженного сервера
  3. Методологии диагностики: USE, RED и здравый смысл
  4. Логи systemd через journalctl: где искать причину
  5. Где лежат логи Linux: /var/log, dmesg и rsyslog
  6. Источники правды: load average, /proc и /sys
  7. Процессы Linux: ps, pstree и состояния процессов
  8. Интерактивный мониторинг: top и htop
  9. Метрики процессов во времени: pidstat
  10. Приоритеты и ограничения: nice, ionice, cgroups
  11. Сигналы и зависшие процессы: kill, и что делать с D-state
  12. Загрузка CPU: user, system, iowait, steal и контекст-свитчи
  13. Диагностика CPU по ядрам: vmstat и mpstat
  14. Профилирование CPU: perf top и поиск пожирателя
  15. Частоты, троттлинг и NUMA: turbostat и numastat
  16. Память Linux: RSS, VSZ, page cache и миф о нехватке памяти
  17. Сколько памяти занято: free, /proc/meminfo и vmstat
  18. Поиск утечек и пожирателей памяти: smem, pmap, smaps
  19. Swap и подкачка: swapon, swappiness, когда своп - это боль
  20. OOM killer: кто и за что убил процесс
  21. Память ядра и slab: slabtop и куда уходит RAM
  22. Подсистема ввода-вывода: путь запроса от приложения до диска
  23. Диагностика диска: iostat и чтение await, %util
  24. Кто грузит диск: iotop и атрибуция io процессам
  25. Файловые системы: df, du, иноды и куда делось место
  26. Глубокая диагностика блочного слоя: blktrace и biolatency
  27. Кеш страниц и грязные данные: dirty pages, fsync, drop_caches
  28. Сетевой стек Linux: путь пакета и где возникают задержки
  29. Сокеты и соединения: ss и netstat
  30. Захват трафика: tcpdump для диагностики сети
  31. Задержки и потери в сети: ping, mtr, диагностика латентности
  32. Сеть как источник проблем: nftables, conntrack, дропы
  33. Что такое системный вызов и зачем его трассировать (вы здесь)
  34. strace: трассировка системных вызовов на практике
  35. strace в бою: почему программа висит, падает или тормозит
  36. ltrace: трассировка вызовов библиотек
  37. Накладные расходы трассировки и безопасные альтернативы
  38. perf: универсальный профайлер Linux
  39. Флеймграфы: визуализация профиля производительности
  40. Трассировка ядра: ftrace и trace-cmd
  41. eBPF: революция в наблюдаемости Linux
  42. Инструменты eBPF на практике: BCC и bpftrace
  43. lsof: открытые файлы, дескрипторы, кто держит файл и порт
  44. Разбор кейса: приложение тормозит - пошаговая диагностика
  45. Непрерывный мониторинг: sar, node_exporter, Grafana, Zabbix
  46. Диагностика в контейнерах: cgroups, лимиты, Docker и Kubernetes
  47. Карта инструментов и сквозной разбор инцидента производительности
Представь: процесс на проде завис, жрёт CPU и молчит. Логов нет, исходников нет, разработчик в отпуске, рестартить страшно. Что делать? Есть метод, который покажет всё, что программа просит у системы прямо сейчас - каждый открытый файл, каждый сетевой коннект, каждое ожидание блокировки. Этот метод стоит на одном фундаменте - на системных вызовах. Без понимания того, как работают системные вызовы в Linux, утилита strace для тебя будет просто потоком непонятных строк. С пониманием - рентгеновским аппаратом. Этот урок - фундамент под весь strace-модуль. Разберёмся раз и навсегда, что такое системный вызов, как он устроен, как читать его трассировку по полям и почему в 2026 году рядом со strace надо знать про eBPF.

User space и kernel space: почему программа сама ничего не может

Начнём с базовой картины мира. В Linux память и права поделены на две зоны. Есть user space (пространство пользователя) - там живут твои программы: nginx, python, postgres, bash. И есть kernel space (пространство ядра) - там живёт само ядро Linux, которое управляет железом: диском, сетевой картой, оперативной памятью, процессором.

Главная мысль урока: обычная программа из user space НЕ может сама читать диск, слать пакеты в сеть или брать память у железа. Процессор физически запрещает ей это - она крутится в непривилегированном режиме (на x86-64 его называют ring 3). Прямой доступ к железу - привилегия ядра (ring 0).

Аналогия. Ты клиент в банке, сидишь за бронированным стеклом. Тебе нужны деньги из хранилища, но сам туда не полезешь - там бронедверь. Ты пишешь заявку и просовываешь её в окошко кассиру. Кассир (ядро) идёт в хранилище (железо), делает работу и возвращает результат. Это окошко, эта заявка через стекло - и есть системный вызов. По-английски system call, коротко syscall.

То есть граница user space / kernel space - это и есть бронированное стекло. Всё, что программе нужно от внешнего мира, она получает только через окошко. Именно поэтому трассировка так ценна: если сидишь у окошка и записываешь все заявки - ты знаешь про программу всё, что она делает наружу. Внутренние вычисления (сложить два числа, прокрутить цикл) идут целиком в user space и в трассировке не видны - и это правильно, нам важна именно граница с внешним миром.

Изображение

Как технически работает syscall в Linux

Теперь чуть глубже под капот, но без страха - это проще, чем кажется. Программа не звонит ядру и не шлёт письмо. Она кладёт данные в регистры процессора и выполняет одну специальную инструкцию.

На современных 64-битных процессорах (x86-64) это инструкция syscall. По шагам:
  • Программа кладёт номер системного вызова в регистр rax. У каждого syscall свой номер, фиксированный для архитектуры. Например, на x86-64: read - 0, write - 1, open - 2, execve - 59, а openat - 257 (номера разбросаны, это не опечатка - они присваивались исторически).
  • Аргументы кладутся в регистры строго по порядку: rdi, rsi, rdx, r10, r8, r9. Это первый, второй и так далее аргументы. Важная деталь: для syscall четвёртый аргумент идёт в r10, а не в rcx, как в обычном вызове функции - сама инструкция syscall затирает rcx и r11, поэтому ядро их не использует под аргументы.
  • Выполняется инструкция syscall. Процессор переключается в режим ядра (ring 0), управление прыгает в заранее заданную точку входа в ядре.
  • Ядро смотрит на номер в rax, находит обработчик, делает работу (читает диск, шлёт пакет) и кладёт результат обратно в rax.
  • Возврат в user space, программа читает результат из rax и продолжает.
Ключевой момент про возврат и ошибки - запомни, он нужен для чтения strace. Результат лежит в rax. Если значение неотрицательное - всё хорошо, это полезный результат (сколько байт прочитано, номер дескриптора). Если значение в диапазоне примерно от -1 до -4095 - это ошибка, а само число равно -errno. То есть -2 значит errno=2 (ENOENT, файл не найден), -13 значит errno=13 (EACCES, нет прав), -111 значит ECONNREFUSED. Библиотека glibc заворачивает это: возвращает в твою программу -1 и кладёт код в переменную errno. Поэтому в C ты пишешь if (fd == -1) и смотришь errno.

Маленькая деталь про имена: open ты пишешь в коде, но glibc на современных ядрах обычно дёргает более новый openat - он умеет относительные пути от дескриптора каталога. Не пугайся, когда увидишь openat вместо open в выводе - это тот же "открыть файл". Есть и ещё более новый, безопасный вариант openat2 (номер 437, доступен с ядра 5.6) - он встречается реже, но смысл тот же.

Зачем трассировать системные вызовы: рентген программы

Теперь главное - зачем это на практике. Раз ВСЁ общение программы с внешним миром идёт через syscall, перехватив их, мы видим всю активность наружу. Не косвенно по логам, а напрямую, по факту.

Что становится видно через linux syscall трассировку:
  • Файлы: какие открывает (openat), читает (read), пишет (write), какие пути не нашлись. Конфиг не подхватился? Увидишь openat с ENOENT на нужном пути.
  • Сеть: куда коннектится (connect), какой IP и порт, что шлёт (sendto, write) и принимает (recvfrom, read). Зависла на запросе к недоступному хосту - увидишь застрявший connect или recvfrom.
  • Память: как выделяется (mmap, brk). Резкий рост числа mmap - возможна утечка или фрагментация.
  • Запуск процессов: что и с какими аргументами запускается (execve, clone).
  • Блокировки и ожидания: futex - примитив, на котором в Linux построены мьютексы и условные переменные в многопоточке. Программа висит на futex - значит ждёт другой поток.
  • Метаданные: stat, fstat, newfstatat - проверка существования и атрибутов файла. ioctl - управление устройствами и терминалом.
И вот почему это мощнейший метод диагностики: он работает, когда у тебя НЕТ исходников. Закрытый бинарник, чужой демон, проприетарный агент - неважно. Ядру всё равно, кто его дёргает, и заявки через окошко видны всегда. Тебе не нужен код, чтобы увидеть, что программа пытается открыть /etc/app/config.yml и получает отказ.

Разберём реальный кусок вывода strace по полям. Запустим простую команду:

Код: Выделить всё

$ strace cat /etc/hostname
execve("/usr/bin/cat", ["cat", "/etc/hostname"], 0x7ffd.. /* 30 vars */) = 0
openat(AT_FDCWD, "/etc/hostname", O_RDONLY) = 3
read(3, "myhost\n", 131072)             = 7
write(1, "myhost\n", 7)                  = 7
read(3, "", 131072)                     = 0
close(3)                                = 0
Читаем строку за строкой. Формат всегда один: имя_вызова(аргументы) = результат.
  • execve(...) = 0 - запустился сам cat. Результат 0 значит успех. Видны путь к бинарю и весь argv.
  • openat(AT_FDCWD, "/etc/hostname", O_RDONLY) = 3 - открыли файл только на чтение. AT_FDCWD значит "путь от текущего каталога". Результат 3 - это файловый дескриптор. Дескрипторы 0, 1, 2 заняты под stdin/stdout/stderr, поэтому первый свой файл обычно получает 3.
  • read(3, "myhost\n", 131072) = 7 - читаем из дескриптора 3. Второй аргумент - что прочитали (strace показывает содержимое, по умолчанию обрезая длинные строки), третий - размер буфера, сколько байт максимум просили. Результат 7 - реально прочитано 7 байт.
  • write(1, "myhost\n", 7) = 7 - пишем в дескриптор 1 (stdout, твой экран) те самые 7 байт. Результат 7 - всё записалось. Если бы вернулось меньше (короткая запись), это был бы повод разбираться.
  • read(3, "", 131072) = 0 - читаем снова, результат 0. Ноль на read означает конец файла, данных больше нет.
  • close(3) = 0 - закрыли дескриптор.
Видишь? Не зная ничего про cat, ты по выводу прочитал весь его жизненный цикл. А теперь строка с ошибкой:

Код: Выделить всё

openat(AT_FDCWD, "/etc/app/config.yml", O_RDONLY) = -1 ENOENT (No such file or directory)
Результат -1, рядом strace расшифровал errno: ENOENT, и даже текст. Вот он, пропавший конфиг - программа искала его по этому пути и не нашла. Диагноз за одну строку. Сравни с EACCES (No such... нет, "Permission denied") - тут путь есть, но прав не хватает. ENOENT и EACCES путают чаще всего, а лечатся они по-разному: первое - не туда смотрит, второе - права/SELinux.

2026: strace это ptrace, а рядом давно живёт eBPF

Важная актуализация, без неё картина неполная. Классический strace работает через старый механизм ptrace: ядро останавливает процесс на входе в каждый syscall и на выходе, отдаёт управление strace, тот декодирует и будит процесс обратно. Это дорого: каждый перехваченный вызов добавляет десятки микросекунд, и под нагрузкой процесс замедляется в разы (на практике от 2x до 100x на syscall-тяжёлых программах). Поэтому держать полный strace на боевом высоконагруженном демоне - плохая идея.

Что есть в 2026 году как современная альтернатива (и куда индустрия ушла):
  • eBPF - технология, позволяющая безопасно запускать мини-программы прямо в ядре и снимать события без остановки процесса. Накладные расходы кардинально ниже ptrace, можно работать на проде долго.
  • bpftrace - высокоуровневый язык трассировки поверх eBPF (наследник идей DTrace). Один однострочник дёргает нужные точки ядра без пауз процесса. Для постоянного наблюдения за syscalls на нагруженной системе сегодня тянутся именно к нему и к BCC-инструментам.
  • perf trace - встроенный в пакет perf аналог strace на базе perf-событий, заметно дешевле классического strace.
Практический вывод на 2026: strace остаётся идеальным инструментом "последней мили" для разовой диагностики одного процесса и для разбора, когда нет исходников - он по-прежнему самый наглядный, и именно с него стоит учиться читать syscalls. Но как только речь про прод под нагрузкой или про длительное наблюдение - бери eBPF-инструменты. Поэтому фундамент этого урока (что такое syscall, как читать "имя(аргументы) = результат") универсален: bpftrace и perf trace показывают ровно те же системные вызовы, просто дешевле снимают. На Astra Linux и RED OS со свежими ядрами eBPF тоже доступен.

Типичные грабли и заблуждения
  • "Системный вызов и функция libc - одно и то же". Нет. printf, malloc, fopen - функции glibc, они живут в user space. Внутри они уже дёргают syscall (write, mmap/brk, openat). Один fopen может породить несколько системных вызовов. Поэтому в strace ты видишь openat, а не fopen, и write, а не printf.
  • "-1 в выводе - всегда катастрофа". Не всегда. Многие программы специально пробуют несколько путей и спокойно ловят ENOENT - это нормальная логика поиска (вспомни, как загрузчик ищет библиотеку в десятке каталогов). Тревожной ошибку делает контекст: EACCES (нет прав), ECONNREFUSED (коннект отбит), ETIMEDOUT (таймаут), EMFILE (кончились дескрипторы). Смотри не на сам факт -1, а на errno и на то, после какой именно ошибки программа сломалась.
  • "Программа зависла на read - значит работает". Скорее наоборот: застряла на read или recvfrom - ждёт данные, которых нет. Висит на futex - ждёт блокировку от другого потока, возможный дедлок. Висит на connect или poll/epoll_wait - сеть не отвечает или нет событий. Последний системный вызов в выводе - это то, чего программа ждёт прямо сейчас. Это первое, на что смотрят при зависании.
  • "strace бесплатен, можно вешать на прод". Нет, ptrace тормозит процесс ощутимо. Для лёгкого разового замера используй сводку strace -c (она дешевле полного лога), а для постоянного наблюдения - eBPF/bpftrace.
  • "Это работает только на x86". Механизм один и тот же на всех архитектурах, меняются инструкция перехода в ядро и таблица номеров. На ARM64 (сейчас это уже массовая серверная платформа) свой номер syscall в регистре x8 и своя таблица, но идея окошка в ядро та же.
Мини-лаба: пощупай syscalls руками прямо сейчас

Открой терминал. strace ставится пакетом strace (sudo apt install strace или sudo dnf install strace).
  • Посмотри полную жизнь команды:

Код: Выделить всё

strace cat /etc/hostname
Найди в выводе свой openat, read, write, close и сопоставь с разбором выше.
  • Посчитай, какие вызовы и сколько раз делает ls. Флаг -c даёт сводку и работает дешевле полного лога:

Код: Выделить всё

strace -c ls /
Получишь таблицу с колонками: % time (доля времени), seconds, usecs/call (микросекунд на вызов), calls (сколько раз), errors (сколько с ошибкой), syscall (имя). Посмотри, на какой вызов уходит больше всего времени и кто вызывается чаще всего - это и есть начало профилирования.
  • Поймай ошибку специально - открой несуществующий файл и отфильтруй только openat:

Код: Выделить всё

strace -e trace=openat cat /no/such/file 2>&1 | grep ENOENT
Увидишь строку с = -1 ENOENT. Это тот самый "пропавший конфиг" в лабораторных условиях. Обрати внимание на 2>&1 - strace пишет в stderr, и без перенаправления grep ничего не поймает.
  • Подцепись к уже запущенному процессу (то, о чём спрашивают на проде). Возьми PID любого своего процесса и приклейся к нему, ограничив только сетью; Ctrl+C отцепит strace, процесс продолжит жить:

Код: Выделить всё

strace -p <PID> -e trace=network -f
Флаг -f важен: он следует за дочерними потоками и процессами (forked), без него половину активности многопоточного демона ты не увидишь.

Контрольные вопросы
  • Почему обычная программа не может сама прочитать файл с диска и что ей для этого нужно сделать?
  • Что лежит в регистре rax до инструкции syscall и что в нём оказывается после возврата из ядра? Как по rax отличить успех от ошибки?
  • Ты видишь openat(...) = -1 ENOENT и рядом openat(...) = -1 EACCES. В чём разница и почему чинятся они по-разному?
  • Программа зависла, последняя строка strace - futex(...). О чём это говорит и куда копать дальше? Почему для долгого наблюдения за этим на проде лучше взять bpftrace, а не strace?
Что запомнить

Системный вызов - единственное окошко из user space в kernel space, через которое программа просит ядро поработать с железом: файлами, сетью, памятью. Технически это инструкция syscall с номером в rax и аргументами в rdi, rsi, rdx, r10, r8, r9; результат тоже в rax, а отрицательное значение - это -errno. Раз через окошко проходит всё общение программы с внешним миром, его трассировка - рентген: видны все файлы, коннекты и ожидания даже без исходников. Формат вывода прост - имя(аргументы) = результат, и читать его ты теперь умеешь. Классический strace делает это через ptrace и потому тормозит процесс - для разовой диагностики он идеален, а на проде под нагрузкой в 2026 берут eBPF-инструменты (bpftrace, perf trace), которые показывают те же системные вызовы дешевле. На этом фундаменте дальше мы и построим работу со strace.
👍2 ❤️1 🔥1 😄 🤔1
Аватара пользователя
grpcandy
Сообщения: 1
Зарегистрирован: 21 май 2026, 20:55

Re: Что такое системный вызов и зачем его трассировать

Сообщение grpcandy »

Вот теперь дошло, почему в strace видно openat, а не fopen - спасибо за разбор по регистрам. А futex реально пугал, думал какая-то экзотика, оказалось просто ожидание блокировки между потоками.
👍 ❤️ 🔥 😄 🤔
Аватара пользователя
leew11
Сообщения: 1
Зарегистрирован: 14 май 2026, 15:24

Re: Что такое системный вызов и зачем его трассировать

Сообщение leew11 »

Спрашивал раньше боюсь вешать strace на прод. Тут прям ответ: для разового разбора -c или -p с фильтром, а для долгого наблюдения bpftrace. Пошёл щупать perf trace, спасибо что про x2..x100 честно написали.
👍 ❤️ 🔥 😄 🤔
Ответить
← Предыдущая глава
Сеть как источник проблем: nftables, conntrack, дропы
Следующая глава →
strace: трассировка системных вызовов на практике

Все главы курса «Диагностика и производительность Linux: логи, strace, perf и eBPF»

Поделиться темой: ✈ Telegram VK
Похожие запросы: что такое системный вызов и зачем трассироватьЧто такое Git и зачем нужен контроль версий

Вернуться в «Диагностика и производительность Linux: логи, strace, perf и eBPF»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость