Внутренности Linux

Процессы, планировщик, прерывания, системные вызовы, память и изоляция

Вячеслав Федоров

Лекция 3

Разработка и применение ПО

в физических исследованиях

PID

Содержание

От учёта процессов до контейнеров

01

Процессы

02

Планировщик

03

Прерывания

04

Системные вызовы

05

Память процесса

06

Изоляция

Цель лекции

Знание устройства системы как инструмент диагностики

Когда расчёт завис, замедлился или исчерпал память, вопрос состоит не в том, что делать, а в том, что спросить у системы: чем процесс занят, куда уходит его время и в каком состоянии он застрял. Облако — те же процессы, память и сеть, только на чужих компьютерах в другом месте.

Процессы

Учёт в ядре, создание, потоки, взаимодействие, состояния

01

Процессы

Планировщик

Прерывания

Системные вызовы

Память процесса

Изоляция

Процесс как

учётная единица

01

Адресное пространство

собственная виртуальная память: код, данные, куча, стек

02

Открытые файлы

таблица дескрипторов: файлы, каналы, сокеты

03

Учётные данные

от чьего имени работает: UID, GID, возможности

04

Сигналы

маска и назначенные обработчики

05

Потоки

одна или несколько задач планировщика

06

Прочие ресурсы

таймеры, разделяемая память, семафоры

// include/linux/sched.h, упрощённо
struct task_struct {
    unsigned int          __state;
    void                  *stack;
    struct mm_struct      *mm;
    struct files_struct   *files;
    struct signal_struct  *signal;
    /* ... сотни полей */
};

Пояснение

Ресурсы процесса ядро освобождает при его завершении: незакрытый дескриптор существует столько же, сколько сам процесс.

/proc/<pid>:

процесс как каталог

ФайлСодержимое
cmdlineаргументы запуска, разделённые нулевым байтом
environпеременные окружения на момент запуска
exe, cwd, rootисполняемый файл, текущий и корневой каталоги
fd/открытые дескрипторы как символические ссылки
statusимя, состояние, родитель, память, число потоков
maps, smapsкарта виртуальной памяти и её расход
schedстатистика планировщика
task/каталоги потоков процесса

Пояснение

/proc — виртуальная файловая система: файлы не хранятся на диске, ядро формирует их содержимое в момент чтения.

Для физика

С /proc/<pid> начинается разбор странного поведения расчёта: какая программа запущена, с какими аргументами и какие файлы держит открытыми.

Демонстрация:

/proc

01

Фоновый sleep и его PID

02

Содержимое /proc/PID

03

Имя, состояние, родитель, память, потоки

04

Аргументы запуска из cmdline

05

Открытые дескрипторы: терминал

06

Исполняемый файл и текущий каталог

07

После kill: каталога больше нет

Что наблюдается

Дескрипторы 0, 1 и 2 процесса sleep указывают на терминал, из которого он запущен; каталог исчезает вместе с процессом.

root@lab — запись со стенда

Создание процесса:

fork и exec

bash · PID 15

fork()

bash · PID 16 · копия родителя

execve()

nginx · PID 16 · новая программа

родитель ждёт в wait()

страницы общие до первой записи

PID прежний, код и данные новые

Пояснение

Copy-on-write: после fork страницы общие и помечены только для чтения; копия страницы создаётся при первой записи в неё, поэтому fork процесса с гигабайтами данных стоит лишь копирования таблиц страниц.

Пояснение

В ядре fork, vfork, clone и clone3 сводятся к одной функции kernel_clone(): процесс и поток отличаются флагами, указывающими, какие ресурсы разделить.

Демонстрация:

fork и exec

01

strace -f: системные вызовы bash и потомка

02

clone: появление дочернего процесса

03

execve: замена программы на ls

04

wait4: получение кода возврата

05

Дерево процессов в ps --forest

Что наблюдается

Сначала clone создаёт копию bash, затем копия вызывает execve и становится ls; родитель ждёт её в wait4.

root@lab — запись со стенда

Процессы и

потоки

АспектПроцессПоток
Памятьсвоё адресное пространствообщее адресное пространство
Файлысвоя таблица дескрипторовобщая таблица
Стек и регистрысвоисвои у каждого потока
Созданиеclone без разделения ресурсовclone с CLONE_VM, CLONE_SIGHAND, CLONE_THREAD
Аварийное завершениебез влияния на другие процессызавершение всего процесса

Пояснение

Для ядра поток — такая же задача со своей task_struct, как процесс: планировщик распределяет время между потоками.

Для физика

Потоки удобны, когда нужны общие данные без копирования; процессы — когда аварийное завершение одной части не должно приводить к завершению остальных.

Демонстрация:

потоки

01

Python с тремя спящими потоками

02

ps -L: один PID, четыре LWP

03

/proc/PID/task: каталог на поток

04

Threads: 4 в status

Что наблюдается

У потоков общий PID процесса и собственные идентификаторы LWP: ядро учитывает каждый поток как отдельную задачу.

root@lab — запись со стенда

Взаимодействие

процессов

01

Сигнал

уведомление о событии без данных; kill, Ctrl+C

02

Канал

поток байтов в одну сторону; | и mkfifo

03

Разделяемая память

общие страницы, данные не копируются

04

Семафор

счётчик для координации доступа

05

Сокет

двусторонний обмен, в том числе по сети

06

Файл

обмен через файловую систему, блокировки flock

Пояснение

Выбор механизма определяется объёмом данных и тем, кто отвечает за синхронизацию: канал упорядочивает данные сам, разделяемая память оставляет это программе.

Сигналы

№СигналДействие по умолчаниюИсточник
1SIGHUPзавершениезакрытие терминала
2SIGINTзавершениеCtrl+C
3SIGQUITзавершение с дампом памятиCtrl+\
9SIGKILLзавершение без перехватаkill -9
15SIGTERMзавершениеkill без ключей
17SIGCHLDигнорированиезавершение потомка
18SIGCONTпродолжениеfg, bg, kill -CONT
19SIGSTOPостановка без перехватаkill -STOP
20SIGTSTPостановкаCtrl+Z

Важно

Перехватить или игнорировать нельзя только SIGKILL и SIGSTOP. Обработчик прерывает программу в произвольной точке: в нём допустимы лишь функции async-signal-safe.

Демонстрация:

сигналы

01

kill -l: список сигналов

02

Обработчик SIGTERM в trap

03

SIGTERM: аккуратное завершение

04

SIGSTOP и SIGCONT: состояния T и S

05

Безрезультатный trap на SIGKILL

Что наблюдается

SIGTERM обработан сценарием, а SIGKILL завершил процесс с кодом 137 = 128 + 9, не дав выполнить обработчик.

root@lab — запись со стенда

Каналы, общая память,

семафоры

Канал

Процесс 1: write

Буфер ядра, 64 КиБ

Процесс 2: read

Одно направление; при полном буфере запись ждёт читателя. Неименованный — |, именованный — mkfifo.

Разделяемая память

Процесс 1

Общие физические страницы

Процесс 2

Данные не копируются — самый быстрый обмен. Согласованность — забота программы. ipcs, /dev/shm.

Семафор

Несколько процессов

Счётчик: 0 или 1, либо N

Критическая секция

Двоичный работает как мьютекс; считающий допускает не более N участников одновременно.

Демонстрация:

каналы

01

mkfifo: файл типа p

02

Передача строки через FIFO

03

Дескриптор 1 команды ls: pipe:[…]

04

Размер буфера канала: 65536 байт

05

ipcs -m: сегменты System V

Что наблюдается

Канал — объект ядра: в /proc он виден как pipe:[номер], а его буфер по умолчанию вмещает 64 КиБ.

root@lab — запись со стенда

Состояния

процесса

Создан

Завершён

Готов

Выполняется

Ожидание

выбран планировщиком

вытеснен

ввод-вывод, событие

событие произошло

БукваСмысл
Rвыполнение или готовность
Sпрерываемое ожидание
Dнепрерываемое ожидание, нечувствительное даже к SIGKILL
T, tостановка сигналом; t — остановка отладчиком
Zзомби: завершённый процесс с незабранным кодом
Iпростаивающий поток ядра
Xуничтожение; в выводе отсутствует

Демонстрация:

состояния

01

Сводка состояний: I, R, S

02

Родитель без вызова wait

03

Зомби: Z и <defunct>

04

Завершение родителя и исчезновение зомби

Что наблюдается

Зомби остаётся в таблице процессов, пока его код возврата не заберут; после завершения родителя это делает init, и запись исчезает.

root@lab — запись со стенда

Демонстрация:

состояние D

01

ФС в файле, смонтированная через loop

02

fsfreeze -f: запись заморожена

03

dd пишет в замороженную ФС

04

Состояние D, ожидание percpu_rwsem_wait

05

kill -9: процесс по-прежнему в D

06

fsfreeze -u: SIGKILL срабатывает, код 137

Что наблюдается

Процесс в D не реагирует даже на SIGKILL: сигнал срабатывает, только когда ожидание заканчивается.

root@lab — запись со стенда

Планировщик

Приоритеты, политики, CFS и EEVDF, привязка к ядрам

02

Процессы

Планировщик

Прерывания

Системные вызовы

Память процесса

Изоляция

Задача

планировщика

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

01

Ограниченные вводом-выводом

короткие вспышки счёта между ожиданиями: оболочка, редактор, веб-сервер; важна быстрая реакция

02

Ограниченные процессором

считают непрерывно: расчёт, компиляция, сжатие; важна пропускная способность

Пояснение

Отзывчивость и пропускная способность противоречат друг другу: частые переключения сокращают задержку, но тратят время на смену контекста и вытесняют данные из кеша.

Приоритеты и

политики

Реального времени: 0–99 · SCHED_FIFO, SCHED_RR

Обычные: 120 + nice

0

99

100

139

в ядре меньшее число — более высокий приоритет

ПолитикаДля чегоКак задать
SCHED_OTHER (NORMAL)почти все процессы: справедливое деление времениnice -n 10, renice
SCHED_BATCH, SCHED_IDLEфоновый счёт, не мешающий интерактивным задачамchrt -b, chrt -i
SCHED_FIFO, SCHED_RRжёсткое время реакции: управление, сбор данныхchrt -f 10, chrt -r 10
SCHED_DEADLINEпериодические задачи с бюджетом и срокомchrt -d

FIFO

и

RR

01

SCHED_FIFO

кванта нет: задача работает, пока не заблокируется, не отдаст процессор сама (sched_yield) или её не вытеснит задача с более высоким приоритетом; вытесненная остаётся в начале своей очереди

02

SCHED_RR

то же, но с квантом: по его истечении задача уходит в конец очереди своего приоритета, и задачи равного приоритета сменяют друг друга по кругу; квант — /proc/sys/kernel/sched_rr_timeslice_ms, 100 мс

Важно

Зациклившаяся задача SCHED_FIFO занимает ядро целиком. От полного зависания систему страхует ограничение: задачам реального времени достаётся не больше 950 мс из каждой секунды (sched_rt_runtime_us).

Эволюция

планировщика

до 2.4

O(N)

при каждом переключении перебор всех готовых задач и одна общая очередь на все процессоры

2.6.0–2.6.22

O(1)

140 очередей по приоритетам и битовая маска непустых; очередь на каждый процессор

2.6.23–6.5

CFS

справедливое деление по виртуальному времени; красно-чёрное дерево: вставка за O(log N), выбор за O(1)

с 6.6

EEVDF

та же справедливость плюс виртуальные сроки: задачам с короткими квантами — быстрая реакция

Пояснение

Каждая замена была вызвана практикой: O(N) не справлялся с тысячами задач, эвристики интерактивности O(1) оказались хрупкими, а CFS плохо различал задачи, которым нужна быстрая реакция.

CFS:

виртуальное время

40

20

60

10

30

70

самый левый — запускается следующим

01

vruntime

полученное процессорное время, умноженное на 1024 и делённое на вес

02

Вес по nice

1024 при nice 0, каждый шаг меняет вес примерно в 1,25 раза

03

Пример

nice 0 и nice 10 на одном ядре: 1024 : 110, то есть 90 % и 10 %

04

Выбор

задача с наименьшим vruntime — самый левый узел дерева

EEVDF:

виртуальные сроки

01

Отставание (lag)

разница между положенной задаче долей времени и полученной

02

Допуск

к выполнению допускаются задачи, получившие не больше положенного: lag ≥ 0

03

Виртуальный срок

момент допуска плюс квант, делённый на вес задачи

04

Выбор

из допущенных — задача с самым ранним виртуальным сроком

Пояснение

Задача с коротким квантом получает ранний срок и быстрее реагирует, не получая при этом больше своей доли. Квант по умолчанию — base_slice_ns, на стенде 1,4 мс.

Десятилетие

потерянных ядер

EuroSys, 2016

14–23 %

потеря пропускной способности СУБД в тесте TPC-H

Четыре ошибки балансировки нагрузки между ядрами: ядра простаивали секундами, пока готовые потоки ждали в чужих очередях.

Многократное замедление научных приложений с интенсивной синхронизацией; на 13 % дольше сборка ядра.

Для физика

Ускорение от добавленных ядер проверяют измерением: загрузку каждого ядра показывает mpstat -P ALL.

Привязка к

ядрам

taskset -c 0,2 ./calc              # запуск на ядрах 0 и 2
taskset -cp 1,3 12345              # смена ядер у процесса
numactl --cpunodebind=0 --membind=0 ./calc
nice -n 10 ./batch; renice -n 5 -p 12345
chrt -f 10 ./daq                   # SCHED_FIFO, приоритет 10

Пояснение

Привязка сохраняет прогретый кеш и держит процесс рядом с его памятью на многосокетной машине.

Пояснение

Частотой ядер управляет регулятор cpufreq (scaling_governor); на виртуальной машине стенда его нет.

Пояснение

Если готовых задач нет, выполняется поток простоя: он переводит ядро в энергосберегающее состояние (cpuidle).

Демонстрация:

nice и привязка

01

chrt -m: политики и диапазоны

02

Два бесконечных цикла на ядре 0

03

nice 0 и nice 10: 91 % и 9,6 %

04

Почти равные vruntime

05

base_slice_ns: квант EEVDF 1,4 мс

Что наблюдается

Доли процессора соответствуют весам 1024 и 110, а vruntime обеих задач растёт одинаково: справедливость измеряется во взвешенном времени.

root@lab — запись со стенда

Прерывания

Как устройства сообщают о событиях и кто их обрабатывает

03

Процессы

Планировщик

Прерывания

Системные вызовы

Память процесса

Изоляция

Прерывания и

исключения

Устройство

Контроллер прерываний

Процессор прерывает задачу

Обработчик по номеру

Возврат к задаче

Прерывание — асинхронно

Приходит от устройства в любой момент: таймер, сетевая карта, диск, USB. Номер линии (IRQ) указывает на обработчик драйвера. Контроллер — APIC на x86, GIC на ARM.

Исключение — синхронно

Вызвано текущей инструкцией. Отказ (fault): промах по странице, после обработки инструкция повторяется. Ловушка (trap): точка останова, продолжение со следующей. Авария (abort): неисправимая ошибка.

Верхняя и

нижняя половины

Прерывание

Верхняя половина: сразу, прерывания на ядре запрещены; подтверждение, чтение данных, планирование остального

Нижняя половина: позже, прерывания разрешены; основная работа

01

SoftIRQ

фиксированный набор: таймеры, сеть, блочные устройства; при нагрузке — поток ksoftirqd

02

Tasklet

создаётся драйвером на основе softirq; один tasklet не выполняется на двух ядрах сразу; устаревает

03

Очередь работ

выполняется потоком ядра kworker: может спать и блокироваться ценой задержки

Прерывания

на практике

cat /proc/interrupts                      # счётчики по линиям и ядрам
cat /proc/softirqs                        # мягкие прерывания
cat /proc/irq/24/smp_affinity_list        # на каких ядрах обрабатывается
echo 2 > /proc/irq/24/smp_affinity_list   # перенос на ядро 2
ethtool -C eth0 rx-usecs 50               # объединение прерываний

Пояснение

MSI и MSI-X: устройство сообщает о событии записью в память; у каждой очереди сетевой карты своё прерывание и своё ядро.

Пояснение

Объединение прерываний (coalescing): устройство накапливает события и сообщает о них одним прерыванием — прерываний меньше, задержка больше.

Для физика

Быстрый АЦП или сетевая карта дают десятки тысяч прерываний в секунду: их распределяют по ядрам, иначе перегружается одно.

Демонстрация:

прерывания

01

/proc/interrupts: таймер, UART, virtio

02

Счётчик таймера за секунду

03

/proc/softirqs: мягкие прерывания

04

Потоки ядра ksoftirqd и kworker

Что наблюдается

За секунду таймер прервал каждое ядро больше сотни раз; отложенную работу выполняют потоки ksoftirqd и kworker.

root@lab — запись со стенда

Системные вызовы

Как программа обращается к ядру и во что это обходится

04

Процессы

Планировщик

Прерывания

Системные вызовы

Память процесса

Изоляция

Системный

вызов

Программа: write()

libc: номер и аргументы в регистры

Инструкция перехода в ядро

Таблица вызовов: sys_write

Проверка и работа, результат

АрхитектураИнструкцияНомер вызоваАргументы
x86-64syscallraxrdi, rsi, rdx, r10, r8, r9
x86, 32 битаint 0x80, sysentereaxebx, ecx, edx, esi, edi, ebp
ARM64svc #0x8x0–x5

Пояснение

Для номера за пределами таблицы вызов возвращает -ENOSYS. В исходниках вызовы определяются макросами SYSCALL_DEFINEn, а функции ядра называются sys_<имя>.

Ядро в

адресном пространстве

0x0000000000000000

Пользовательская часть: код, данные, куча, библиотеки, стек

ARM64: до 0x0000ffffffffffff

неканонические адреса

Ядро: в каждом процессе, доступно только в режиме ядра

0xffffffffffffffff

01

Старшие адреса — ядру

на x86-64 с 0xffff800000000000, на ARM64 с 0xffff000000000000

02

Контекст процесса

в режиме ядра код работает от имени процесса: может заснуть и быть вытеснен

03

Защита

из пользовательского режима страницы ядра недоступны; после Meltdown на уязвимых процессорах ядро их не отображает (KPTI)

Цена

системного вызова

ОперацияПорядок времени
Обращение к оперативной памятиоколо 100 нс
Простой системный вызов, например getpid0,1–1 мкс
Переключение контекста между процессами1–10 мкс
Чтение страницы с NVMeдесятки–сотни мкс
Чтение с жёсткого дискаединицы миллисекунд

01

Объединение

writev, буфер вместо write на каждое число

02

mmap

работа с файлом как с массивом, без read и write

03

vDSO

clock_gettime и gettimeofday без перехода в ядро

04

Подсчёт

strace -c, perf trace -s

Демонстрация:

системные вызовы

01

strace -c: сводка вызовов ls

02

openat, read, write при cat /etc/hostname

03

Номера вызовов на ARM64

04

vDSO в карте памяти

Что наблюдается

Даже ls делает больше сотни системных вызовов; номера на ARM64 свои (write — 64, на x86-64 — 1), а vDSO есть в каждом процессе.

root@lab — запись со стенда

Память процесса

Адресное пространство, промахи по страницам, OOM и NUMA

05

Процессы

Планировщик

Прерывания

Системные вызовы

Память процесса

Изоляция

Адресное

пространство

младшие адреса

Код (text)

Данные (data)

BSS: неинициализированные

Куча ↓ (malloc)

свободно

Отображения: библиотеки, mmap, общая память

свободно

Стек ↑

старшие адреса, далее — ядро

Карта памяти awk на стенде, /proc/self/maps:

Адрес и праваЧто это
00400000 r-xp gawkкод
004d0000 rw-p gawkданные
004d2000 rw-pBSS
1efe4000 rw-p [heap]куча
e528… rw-pанонимные отображения, библиотеки
[vvar], [vdso]страницы ядра для vDSO
ffffea94… rw-p [stack]стек

Пояснение

C и C++: инициализированные глобальные — data, неинициализированные — BSS, malloc — куча или mmap для крупных блоков, локальные переменные — стек.

Сколько памяти

у процесса

ПоказательЧто учитывает
VIRT, VSZвсё отображённое, включая ни разу не тронутые страницы
RES, RSSстраницы, находящиеся в физической памяти
SHRразделяемые: библиотеки, общая память, файлы
Анонимнаякуча и стек, без файла на диске
Страничный кешпрочитанные файлы; освобождается при нехватке

pmap -XX 12345
cat /proc/12345/smaps_rollup
free -h

Пояснение

Выделение — ещё не использование: malloc гигабайта увеличивает VIRT, а RES растёт только по мере записи в страницы.

Промахи

по странице

Обращение к странице

Нет записи в таблице страниц

Исключение: обработчик ядра

Повтор инструкции

01

Малый промах (minor)

страница уже в памяти или выделяется сразу, без чтения с диска: ядро лишь дописывает таблицу — доли микросекунды

02

Большой промах (major)

страницы в памяти нет: чтение с диска или из подкачки — от сотен микросекунд до миллисекунд

ps -o min_flt,maj_flt,cmd -p 12345
/usr/bin/time -v ./calc

Демонстрация:

память

01

Карта памяти awk: код, куча, стек, vDSO

02

VSZ и RSS оболочки

03

200 МиБ: 52 тысячи малых промахов

04

free -h: память и кеш

Что наблюдается

Строка в 200 МиБ дала около 52 тысяч малых промахов — по одному на страницу в 4 КиБ; с диска ничего не читалось, поэтому больших нет.

root@lab — запись со стенда

Нехватка

памяти

01

OOM killer

памяти не осталось: ядро выбирает процесс с наибольшим oom_score и завершает его сигналом SIGKILL

02

oom_score_adj

от −1000 (исключён из выбора) до 1000 (первый кандидат); /proc/<pid>/oom_score_adj

03

Лимит cgroup

memory.max: OOM срабатывает внутри группы, остальная система его не замечает

04

Подкачка

swappiness 60 по умолчанию; для расчётного узла обычно вредна: расчёт в подкачке замедляется на порядки

Пояснение

NUMA: у каждого сокета своя память, обращение к чужой дороже — numactl --hardware, numactl --membind. На стенде один узел.

Демонстрация:

OOM в cgroup

01

Scope systemd с MemoryMax=100M

02

Строка в 300 МиБ: Killed

03

Код 137 = 128 + SIGKILL

04

Запись OOM в журнале ядра

Что наблюдается

OOM сработал внутри cgroup: завершён только процесс группы, остальная система лимита не заметила.

root@lab — запись со стенда

Изоляция

Cgroups ограничивают потребление, пространства имён — видимость

06

Процессы

Планировщик

Прерывания

Системные вызовы

Память процесса

Изоляция

Уровни

изоляции

chroot: свой корень ФС

ulimit: лимиты процесса

systemd и cgroups: лимиты служб

Контейнеры: LXC, Docker, Porto

Виртуальные машины: KVM, Xen

слева направо: изоляция сильнее, накладные расходы больше; контейнеры делят одно ядро, виртуальные машины — нет

Пояснение

Изоляция защищает соседей: ошибка или чрезмерное потребление ресурсов одним сервисом не должны задевать остальные на той же машине.

Пояснение

Контейнер собирается из двух механизмов ядра: пространства имён ограничивают то, что процесс видит, cgroups — то, сколько он потребляет.

Cgroups:

сколько можно потребить

/sys/fs/cgroup/
├── system.slice/       # службы
│   ├── cron.service
│   └── ssh.service
├── user.slice/         # пользователи
└── cgroup.controllers

Пояснение

Потомки процесса наследуют его группу; лимит на узле дерева действует на всё поддерево. Контроллеры для потомков включаются записью в cgroup.subtree_control.

РесурсФайлы
процессор (cpu)cpu.max, cpu.weight
память (memory)memory.max, memory.high
ввод-вывод (io)io.max
процессы (pids)pids.max
ядра, узлы (cpuset)cpuset.cpus, cpuset.mems
заморозка группыcgroup.freeze

Демонстрация:

cgroups

01

Служба cron в своей cgroup

02

Контроллеры и счётчики группы

03

Scope с CPUQuota=20%

04

Бесконечный цикл: 20 %

05

cpu.max: 20000 из 100000

Что наблюдается

Лимит задаётся записью в файл группы: cpu.max «20000 100000» означает 20 мс процессорного времени на каждые 100 мс.

root@lab — запись со стенда

Пространства

имён

ПространствоЧто подменяетunshare
PIDномера процессов--pid
Networkинтерфейсы, адреса, маршруты, порты--net
Mountсмонтированные файловые системы--mount
UTSимя узла и домена--uts
IPCобъекты System V и очереди сообщений POSIX--ipc
Userидентификаторы пользователей и групп--user
Cgroupвидимый корень дерева cgroups--cgroup
Timeсмещения часов MONOTONIC и BOOTTIME--time

Пояснение

Процесс попадает в пространства имён при создании (clone), может перейти в новые (unshare) или присоединиться к существующим (setns); их список — /proc/<pid>/ns.

Контейнер и

под

Контейнер

процесс nginx

свои: mnt, pid, uts, ipc, net, cgroup

cgroup с лимитами, корень ФС из образа

Под Kubernetes

nginx: свои mnt, pid

mysql: свои mnt, pid

общие: net, ipc, uts — один адрес, localhost между контейнерами

общая cgroup пода и лимиты каждого контейнера

Пояснение

Контейнер — не отдельная машина, а обычный процесс хоста в своём наборе пространств имён и cgroup; ps на хосте его видит.

Демонстрация:

пространства имён

01

Пространства имён оболочки в /proc/$$/ns

02

unshare --pid: оболочка с PID 1

03

unshare --uts: своё имя узла

04

lsns: пространства имён системы

Что наблюдается

В новом PID-пространстве оболочка получает номер 1 и видит только себя и своих потомков; имя узла, изменённое в UTS-пространстве, снаружи прежнее.

root@lab — запись со стенда

Заключение

01

Процесс — учётная единица ядра; всё, что о нём известно, видно в /proc/<pid>.

02

Состояние процесса — первый вопрос при зависании: R, S, D, T и Z требуют разных действий.

03

Планировщик делит время по весам; nice, chrt и taskset меняют долю, политику и набор ядер.

04

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

05

Контейнер — процесс в своих пространствах имён с лимитами cgroups, а не отдельная машина.

При странном поведении программы не гадают, а спрашивают систему: /proc, strace, perf.

Домашнее задание

Работа с долгоиграющими процессами

01

Скрипт с шебангом #!/usr/bin/bash и бесконечным циклом: echo «I am still alive», sleep 1

02

Запуск на сервере по ssh

03

Продолжение работы после отключения от сервера

04

(*) Работа в фоне

05

(*) Чтение stdout и stderr

06

(*) Перенаправление вывода в файлы

Требования к сдаче

Способ, благодаря которому процесс переживает отключение, и объяснение: какой сигнал приходит при закрытии сессии и что с ним происходит

Вывод ps после повторного входа, доказывающий, что процесс жив

(*) Файлы с выводом и способ читать их у работающего процесса, не останавливая его

Репозиторий: github.com/phys-dev/linux-structure-task · главы «Внутреннее устройство Linux» и «Инструменты Linux»

Типичные

ошибки

01

Запуск через & без отвязки

при обрыве соединения оболочка получает SIGHUP и пересылает его заданиям

02

Проверка в той же сессии

живучесть доказывает только ps после повторного входа

03

nohup без перенаправления

вывод уходит в nohup.out текущего каталога, а не туда, где его ищут

04

Объяснение без сигнала

требуется назвать SIGHUP и объяснить, что с ним делает выбранный способ

05

Остановка процесса ради чтения

tail -f по файлу вывода не мешает работающему процессу

06

Сразу kill -9

процесс лишается возможности завершиться аккуратно; сначала SIGTERM

Источники

1

Книга курса: глава «Внутреннее устройство Linux»

phys-dev.github.io/soft-dev-book

2

Лекция «Внутренности Linux», КИТ 2024, Young&&Yandex; А. Мичурин

youtube.com/watch?v=kJG2V48L-IE

3

Love R. Linux Kernel Development. – 3rd ed. – Addison-Wesley, 2010

4

Kerrisk M. The Linux Programming Interface. – No Starch Press, 2010

man7.org/tlpi

5

Lozi J.-P. et al. The Linux Scheduler: a Decade of Wasted Cores. – EuroSys, 2016

doi.org/10.1145/2901318.2901326

6

EEVDF Scheduler — документация ядра

docs.kernel.org/scheduler/sched-eevdf.html

7

Control Group v2 — документация ядра

docs.kernel.org/admin-guide/cgroup-v2.html

8

Страницы руководства: proc(5), signal(7), sched(7), syscall(2), namespaces(7), cgroups(7)

Спасибо за внимание

Вопросы

phys-dev.github.io/soft-dev-book

exit