☰ Конспект ← Курс
ЛЕКЦІЯ 1

ТРИ МЕЖІ КУРСУ: ПРОЦЕС, АДРЕСНИЙ ПРОСТІР, СИСТЕМНІ ВИКЛИКИ

Де закінчується програма й починається машина

ВТФК · Системне програмування · 2026

План лекції

  • Що таке системне програмування
  • Три модулі курсу — три межі
  • Процес як абстракція ОС
  • Народження процесу: fork → execve → wait
  • Системний виклик — єдина легальна межа
  • Адресний простір процесу
  • Живе демо: ps і /proc

Фокус: одна ідея — межа середовища виконання

Що таке системне програмування

  • Код, що керує ресурсами машини напряму: пам'ять, процеси, мережа, залізо
  • Ціна помилки вища, ніж у прикладному ПЗ: витік пам'яті, збій ядра, дірка в ізоляції
  • Курс — не «вивчити мову», а побачити межу між програмою і системою

Мотивація: чому це важливо саме зараз

Три модулі — три межі

  • Модуль 1 · Docker — межа між процесом і ОС: namespaces, cgroups
  • Модуль 2 · Rust — межа між програмою і власною пам'яттю: ownership, borrow checker
  • Модуль 3 · WebAssembly — межа між кодом і чужим середовищем виконання
ПРОЦЕС межа середовища ПАМʼЯТЬ межа безпеки ІНСТРУКЦІЇ межа набору Docker · namespaces Rust · borrow checker asm → WebAssembly ТРИ МЕЖІ МІЖ ПРОГРАМОЮ І МАШИНОЮ

Файл на диску ↔ процес у пам'яті

Файл програми (/bin/sleep)
  • Пасивні байти: код, таблиці символів, метадані ELF
  • Нічого не робить, доки лежить на диску
  • Ідентифікатор — шлях у файловій системі
  • Один файл → скільки завгодно процесів
execve()
Процес
  • Активне виконання, яке ядро супроводжує
  • PID, адресний простір, дескриптори, UID/GID
  • Живе в ілюзії «я сам на машині»
  • Народжується через execve(), завершується з кодом

Ілюзію «я сам на машині» підтримує ядро: планувальник і MMU

З чого складається процес у ядрі (task_struct)

PID і станrunning · sleeping · stopped · zombie
Батько (PPID)дерево процесів від PID 1
Адресний простірtext · data · heap · stack; таблиця сторінок
Дескриптори0 stdin · 1 stdout · 2 stderr · далі файли, сокети
Облікові даніUID, GID, capabilities — що дозволено
Простори іменякий «світ» бачить процес → Лекція 2

Усе це видно через каталог /proc/<pid>/

Процес під мікроскопом: /proc

bash
sleep 300 &
PID=$!                # напр. 3101
cat /proc/$PID/status | head -5
cat /proc/$PID/maps | head -5
ls /proc/$PID/fd

/proc — файлова проекція внутрішнього стану ядра для кожного процесу

/proc/<pid>/ — що де лежить

ФайлЩо показуєЧастина task_struct
statusстан, PPID, пам'ять, UID/GIDстан, батько, облікові дані
mapsрегіони адресного простору і праватаблиця сторінок (mm)
fd/відкриті дескриптори як символьні посиланнятаблиця файлів
cmdlineаргументи запускуобраз програми
ns/простори імен процесу→ Лекція 2
cgroupгрупа керування ресурсами→ Лекція 2

Файли генеруються ядром «на льоту» при читанні — це не диск

/proc/<pid>/maps докладно

bash
cat /proc/$PID/maps
# приклад рядка:
# 55f2a1000000-55f2a1001000 r--p 00000000 08:01 123 /bin/sleep
# 7f9c4b000000-7f9c4b028000 r-xp 00000000 08:01 456 /lib/x86_64-linux-gnu/libc.so.6

Кожен рядок — один регіон адресного простору: адреси, права rwx, файл-джерело

Народження процесу: fork → execve → wait

  1. 1
    fork() / clone()копія батька; дитина отримує власний PID
  2. 2
    execve() у дитиніобраз пам'яті замінено новою програмою; PID той самий
  3. 3
    wait4() у батькаshell блокується, доки дитина працює
  4. 4
    exit() дитиникод завершення повертається батькові
  5. 5
    Zombie → прибранопісля wait ядро звільняє task_struct

Так shell запускає кожну команду: ls, cat, docker

Хто є хто після clone() і execve()

НАРОДЖЕННЯ ПРОЦЕСУ: CLONE → EXECVE → WAIT БАТЬКО · PID 4021 bash PID 4021 bash wait4() батько живий увесь час ДИТИНА · PID 4022 bash (копія) той самий код /bin/ls образ підмінено clone() execve() exit(0) clone: copy-on-write — сторінки спільні, поки ніхто не пише · execve: PID той самий, новий лише вміст clone — копіяexecve — підмінаwait4 — код

На демо покажіть: PID дитини той самий до і після execve — змінився лише вміст пам'яті

fork() ↔ execve()

fork()
  • Створює НОВИЙ процес — копію поточного
  • Повертає двічі: 0 дитині, PID батькові
  • Пам'ять спільна через copy-on-write
  • Код лишається той самий
+
execve()
  • Процес той самий — PID не змінюється
  • Повертає лише при помилці
  • Пам'ять повністю замінена новим образом
  • Код — уже інша програма

Два незалежні кроки замість одного «запустити програму» — дизайн Unix

clone() і copy-on-write

  • Сучасний Linux реалізує fork() через гнучкіший clone() з прапорцями
  • Copy-on-write: сторінки батька й дитини спільні до першого запису
  • Перший запис копіює одну конкретну сторінку, а не всю пам'ять
  • Прапорці CLONE_NEWPID, CLONE_NEWNET… — основа контейнерів у Лекції 2

strace: fork, execve наживо

bash
strace -f -e trace=fork,vfork,clone,execve,wait4 bash -c "ls > /dev/null"

Видно послідовність clone() (нова дитина) → execve() (заміна образу) → wait4() (батько чекає)

Системний виклик (*syscall*) — єдина легальна межа

  • Диск і мережу програма читає лише через syscall: read, write, openat, execve, clone
  • Межа привілеїв: ядро не довіряє user space і робить небезпечне саме
  • Контейнер, компілятор Rust, Wasm-рантайм — зрештою про те, які syscalls дозволені

Кільця захисту (Ring 0 / Ring 3)

  • Ring 0 (kernel mode) — привілейований режим: ядро, драйвери, планувальник
  • Ring 3 (user mode) — звичайні програми без привілейованих інструкцій
  • Привілейована інструкція з Ring 3 → апаратний виняток (general protection fault)
  • Інструкція syscall — контрольований «місток» з Ring 3 у Ring 0 і назад
USER SPACE · кільце 3 ваша програма read() write() KERNEL SPACE · кільце 0 ядро драйвери, ФС syscall єдина межа кожен доступ до заліза проходить через syscall-шлюз

Межа в цифрах

450+
системних викликів
ядро Linux на x86-64
2
кільця, що реально використовує Linux
Ring 0 · Ring 3
1
інструкція переходу
syscall — і назад sysret
0
прямих доступів до заліза з Ring 3
диск, мережа, MMU — лише через ядро

libc ховає syscalls за printf, malloc, fopen — але знизу завжди вони

Побачити syscalls наживо

bash
strace -c ls > /dev/null
# short summary: скільки викликів read/write/openat/execve...

strace показує реальний діалог програми з ядром

Живе демо: анатомія процесу

  1. Запустити sleep 300 & і зберегти PID
  2. Показати процес у ps aux | grep sleep
  3. cat /proc/$PID/status — стан, пам'ять, батько
  4. cat /proc/$PID/maps — сегменти адресного простору
  5. ls /proc/$PID/fd — відкриті дескриптори

Очікувано: Студенти бачать: процес у ps — це рядок, а в /proc — цілий каталог зі структурою пам'яті й дескрипторів

Адресний простір процесу

  • text — код програми (тільки читання, виконання)
  • data/bss — глобальні змінні
  • heap — динамічна пам'ять, росте вгору
  • stack — локальні змінні й виклики функцій, росте вниз
  • Кожен процес бачить СВІЙ простір 0…2^64 — ізоляція за замовчуванням
ВІРТУАЛЬНИЙ АДРЕСНИЙ ПРОСТІР Ядро (недоступне)Стек ↓⋮ вільно ⋮Купа ↑ (heap)BSS / DataText (код) 0x7fff… 0x0000…

Сегменти адресного простору

СегментВмістПраваРосте
textмашинний код програмиr-x
data / bssглобальні змінніrw-
heapmalloc / new; brk, mmaprw-↑ вгору
shared libslibc.so та інші, через mmapr-x
stackлокальні змінні, адреси поверненняrw-↓ вниз

Ті самі регіони — рядки у /proc/<pid>/maps; права видно в другій колонці

Чому Docker — перша межа курсу

  • Docker не віртуалізує залізо — ізолює одне ядро для кількох процесів
  • Найдешевша й найближча до звичайного процесу форма ізоляції
  • Розуміння процесу з цієї лекції — основа для namespaces у Лекції 2
Мета абстракції — не бути розпливчастим, а створити новий семантичний рівень, на якому можна бути абсолютно точним.

— Едсгер Дейкстра, «The Humble Programmer» (1972)

Наперед: перший контейнер

bash
docker run --rm -it ubuntu:24.04 ps aux
# у контейнері PID 1 — це наш процес, а не системний init хоста

Анонс Л2: той самий процес, інший PID-простір

Підводні камені: процес і syscalls

  • PID — лише ключ пошуку; реальний стан живе в task_struct ядра
  • fork() не копіює пам'ять миттєво — copy-on-write відкладає до першого запису
  • strace бачить лише межу user↔kernel, а не виклики бібліотечних функцій
  • /proc — живий, не знімок: значення змінюються між двома cat

Підсумок

  • Процес — активна абстракція ядра: PID, адресний простір, дескриптори, права
  • Системний виклик — єдина легальна межа між програмою і машиною
  • Курс тричі повертається до цієї межі: Docker, Rust, WebAssembly
  • Наступного разу: як ядро приховує одні процеси від інших (namespaces)

🥚 У прикладах /proc фоновий процес має PID 3101 — шістнадцятково 0xC1D. Це не випадковість: таке число трапиться в курсі ще не раз.

VTFK · TERMLINK · L01← → · space · f — на весь екран