1. Архитектура Linux и основы ОС
Kernel space vs user space, syscalls, процессы и потоки, systemd/units, targets и FHS, а также базовая отладка через strace.
Kernel space vs User space
Ядро (kernel) работает в привилегированном режиме (kernel space): доступ к железу, памяти, прерываниям. Код приложений выполняется в user space — без прямого доступа к устройству и с ограничениями памяти. Переход из user space в kernel происходит только через системные вызовы (syscalls); так приложение запрашивает чтение файла, создание процесса, работу с сетью и т.д. Понимание границы помогает при отладке (strace показывает syscalls) и при объяснении, почему «просто так» из процесса нельзя трогать чужие процессы или драйверы.
Системные вызовы (syscalls)
Системный вызов — интерфейс ядра для приложений. Программа выполняет специальную инструкцию (например, syscall на x86-64), передаёт номер вызова и аргументы; ядро выполняет операцию и возвращает результат. Без syscalls приложение не может открыть файл, создать процесс или отправить пакет по сети.
Важные syscalls
| Вызов | Назначение |
|---|---|
| fork | Создать копию текущего процесса (дочерний получает копию памяти и продолжает выполнение). Возвращает в дочернем 0, в родителе — PID дочернего. |
| exec (семейство execve и др.) | Заменить образ текущего процесса новой программой (загружается другой исполняемый файл, старый код больше не выполняется). Обычно после fork в дочернем вызывают exec — так запускают новые программы. |
| read / write | Чтение и запись из файлового дескриптора (файл, сокет, pipe). Вся работа с файлами и сетью в итоге сводится к ним (или к обёрткам вроде send/recv). |
| open / close | Открыть файл (или устройство) и получить дескриптор; закрыть дескриптор. |
Многие «команды» в консоли — это программа в user space, которая делает fork, в дочернем процессе exec нужного бинарника (например, /bin/ls), а родитель ждёт завершения (wait). Понимание fork/exec нужно для скриптов, контейнеров и отладки (почему процесс не видит переменные окружения после exec).
Процессы и потоки
Процесс — экземпляр выполняемой программы: своё адресное пространство (память), файловые дескрипторы, учётные данные. У каждого процесса есть PID, родитель (PPID), состояние (R, S, D, Z и т.д.). Поток (thread) — поток выполнения внутри процесса; потоки одного процесса делят общую память и дескрипторы, но имеют свой стек и свой TID. В Linux потоки реализованы как «лёгкие» процессы (LWP) с общим адресным пространством (clone с CLONE_THREAD). Команды ps, top, htop показывают процессы (и при опциях — потоки).
Init-система: systemd и unit-файлы
После загрузки ядра управление передаётся init — процессу с PID 1. В большинстве современных дистрибутивов это systemd. systemd управляет сервисами, монтированием, сокетами и т.д. через unit-файлы (описание единицы работы).
Типы unit
- service — демон или однократный скрипт.
- socket — сокет для активации по требованию (inetd-подобная схема).
- target — группа unit’ов (аналог «уровня загрузки»).
- mount, timer, path и др.
Unit-файл сервиса
Файлы лежат в /etc/systemd/system/ (локальные) и /usr/lib/systemd/system/ (пакетные). Пример минимального сервиса:
[Unit]
Description=My daemon
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/mydaemon
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
- [Unit] — описание и зависимости: After=network.target значит «запускать после того, как достигнут network.target».
- [Service] — как запускать: Type=simple (процесс остаётся в foreground), ExecStart, политика перезапуска.
- [Install] — WantedBy=multi-user.target означает «включать этот unit при переходе в multi-user.target» (обычная многопользовательская консоль без GUI).
После правки unit выполняют systemctl daemon-reload, затем systemctl start/enable <unit>.
Runlevels и targets
Исторически runlevel — режим работы (0–6): single user, multi-user, reboot и т.д. В systemd им соответствуют targets: цели, к которым система переходит. Важные цели:
- default.target — цель по умолчанию (обычно символическая ссылка на graphical.target или multi-user.target).
- multi-user.target — многопользовательский режим без GUI; сервисы, имеющие
WantedBy=multi-user.target, стартуют при загрузке в эту цель. - graphical.target — то же плюс графическая среда.
Цепочка загрузки: ядро → systemd (PID 1) → default.target → зависимости (например, multi-user.target тянет за собой network.target и сервисы). Просмотр зависимостей: systemctl list-dependencies multi-user.target.
FHS (Filesystem Hierarchy Standard)
FHS — соглашение о размещении файлов в дереве каталогов. Знание структуры нужно для поиска конфигов, логов и бинарников.
| Каталог | Назначение |
|---|---|
/ |
Корень; здесь монтируются остальные разделы. |
/bin, /usr/bin |
Исполняемые файлы (пользовательские команды). |
/sbin, /usr/sbin |
Исполняемые файлы для администрирования. |
/etc |
Конфигурация системы и приложений. |
/var |
Изменяемые данные: логи (/var/log), кэш, очереди, данные приложений. |
/tmp |
Временные файлы (часто очищается при перезагрузке). |
/home |
Домашние каталоги пользователей. |
/usr |
Пользовательские программы и данные (read-only в рамках одной версии ОС). |
/opt |
Дополнительное ПО (часто по одному каталогу на приложение). |
/dev |
Устройства (файлы устройств). |
/proc, /sys |
Виртуальные ФС ядра (процессы, параметры ядра). |
Практика
Проследить запуск до multi-user.target
# От какой цели загружаемся
systemctl get-default
# Зависимости multi-user.target (что входит в «обычную» загрузку)
systemctl list-dependencies multi-user.target
# Дерево с рекурсией (много вывода)
systemctl list-dependencies --all default.target
Так видно, какие unit’ы и в каком порядке задействованы при достижении multi-user.target.
Разобрать unit-файл сервиса
# Где лежит unit и его полное содержимое
systemctl cat nginx.service
# Список всех unit-файлов для nginx
systemctl list-unit-files 'nginx*'
# Зависимости unit’а
systemctl list-dependencies nginx.service
Разбор секций [Unit], [Service], [Install] и правка при необходимости в /etc/systemd/system/<name>.service (или override через systemctl edit).
strace — анализ системных вызовов
# Запуск команды под strace (вывод в stderr)
strace ls /tmp
# Запись в файл
strace -o trace.log ls /tmp
# Только определённые вызовы (например, работа с файлами)
strace -e openat,read,write ls /tmp
# Подключиться к уже работающему процессу
strace -p <PID>
По выводу видно, какие файлы открываются, какие read/write выполняются, где может быть ошибка (например, ENOENT — файл не найден). Полезно при отладке «почему приложение не видит файл» или «на каком вызове зависает».
!!! tip "Практика"
Чтобы уверенно объяснять «как система работает изнутри», полезно один раз пройти цепочку: загрузка ядра → systemd → default.target → multi-user.target → ваши сервисы; и посмотреть на любой демон через strace (open, read, write, poll).
Паттерны и антипаттерны
| Паттерн | Описание |
|---|---|
| Конфиг и override в /etc/systemd | Локальные правки и override — в /etc/systemd/system; не редактировать напрямую файлы в /usr/lib/systemd. |
| После правки unit — daemon-reload | Иначе systemd не подхватит изменения. |
| strace при отладке «не видит файл» | Показать реальный путь и код ошибки из ядра. |
| Антипаттерн | Почему плохо | Что делать |
|---|---|---|
| Type=simple и процесс сразу выходит | systemd считает сервис упавшим. | Type=simple — процесс должен работать в foreground; для кратких задач — Type=oneshot. |
| Забыть WantedBy в [Install] | enable не подхватит unit при загрузке в target. | Указывать WantedBy=multi-user.target (или нужный target) и выполнять systemctl enable. |
Дополнительные материалы
- man 2 syscalls — список системных вызовов
- systemd.service(5)
- systemd.target(5)
- FHS
- strace(1)