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

Процессы

Современный сервер PostgreSQL под нагрузкой - это 100+ процессов. Chrome в браузере - 30-50 процессов. Сбой одного процесса Chrome не роняет остальные вкладки. PostgreSQL-воркер, упавший на плохом запросе, не уносит весь кластер. Изоляция через процессы - архитектурное решение, которое делает современные системы стабильными. Понять процесс - значит понять, почему это работает именно так.

  • **Chrome**: каждая вкладка - отдельный процесс (Site Isolation). Падение Flash-плагина в 2015-м роняло страницу, но не браузер - именно благодаря изоляции процессов
  • **Nginx**: master-процесс управляет конфигурацией, worker-процессы (по одному на CPU-ядро) обрабатывают запросы. 1 million connections с минимальными context switches
  • **PostgreSQL**: backend-процесс на каждое соединение. При 1000 соединениях - 1000 PCB в памяти ядра. Именно поэтому PgBouncer (connection pooler) критичен
  • **Docker контейнеры** - это процессы с namespace isolation. `docker run` вызывает fork() + clone() с флагами CLONE_NEWPID, CLONE_NEWNET и другими
  • **Node.js cluster** модуль: master делает fork() для каждого worker - те же системные вызовы, что и в C-программах 40 лет назад

Цели урока

  • Объяснить процесс: программа + execution context + resources + address space
  • Перечислить состояния (New, Ready, Running, Waiting, Terminated) и переходы между ними
  • Знать содержимое PCB: PID, state, PC, registers, memory pointers, open FDs
  • Применять fork/exec/wait, понять copy-on-write и зачем разделение fork и exec
  • Оценить cost: process create ~1ms, context switch ~5µs, exit ~100µs

Деннис Ритчи и рождение процессов в Unix

В 1969 году Кен Томпсон и Деннис Ритчи писали Unix на PDP-7 в Bell Labs. Концепция процесса была революционной: вместо того чтобы запускать одну программу за раз, система поддерживала несколько «живых» программ одновременно, переключаясь между ними. fork() появился как лаконичное решение проблемы создания процессов - скопировать себя, потом заменить копию на нужную программу через exec(). Эта комбинация fork+exec прожила 55 лет и до сих пор лежит в основе каждого запуска терминальной команды.

Концепция процесса

**Процесс** - это программа в стадии выполнения. Не просто код на диске, а живая сущность с собственной памятью, регистрами процессора и состоянием. Chrome на компьютере - это не одна программа. Это 30-50 процессов: отдельный для каждой вкладки, отдельный для GPU, отдельный для сетевых операций. Сбой одной вкладки не роняет остальные именно потому, что процессы изолированы.

Каждый процесс имеет собственное **адресное пространство**, которое включает: **код программы** (text section), **данные** (data section), **стек** (stack) для временных переменных и **кучу** (heap) для динамической памяти.

**Ключевое отличие:** Программа - пассивная сущность (файл на диске), процесс - активная (выполняется в памяти). Одна программа может порождать множество процессов. Nginx запускает один master-процесс и N worker-процессов - по одному на каждое ядро CPU.

Операционная система управляет процессами через специальную структуру данных - **Process Control Block (PCB)**, которая хранит всю информацию о процессе. В Linux это структура `task_struct` размером около 1.7 КБ. Десятки тысяч процессов в системе - десятки тысяч таких структур в памяти ядра.

Что НЕ является частью адресного пространства процесса?

Состояния процесса

В течение своей жизни процесс проходит через несколько **состояний**. На типичном сервере с 4 ядрами может быть 2000 процессов - но только 4 одновременно находятся в состоянии Running. Остальные ждут: очереди на CPU, завершения I/O операций, сигналов.

**Пять основных состояний:** - **New** - процесс создаётся - **Ready** - процесс готов к выполнению, ждёт процессора - **Running** - процесс выполняется на процессоре - **Waiting** - процесс ждёт события (I/O, сигнала) - **Terminated** - процесс завершил выполнение

**Переходы между состояниями:** - **New -> Ready:** ОС завершила инициализацию процесса - **Ready -> Running:** Планировщик выбрал процесс для выполнения - **Running -> Ready:** Истекло время кванта (time slice) или пришло прерывание - **Running -> Waiting:** Процесс запросил I/O операцию - **Waiting -> Ready:** I/O операция завершена, процесс снова готов - **Running -> Terminated:** Процесс завершил работу

**Context Switch** - переключение контекста происходит при смене процесса на процессоре. ОС сохраняет состояние старого процесса и загружает состояние нового. Это затратная операция: 0.1-1 мкс, включая сброс TLB (Translation Lookaside Buffer). На сервере с тысячами процессов и частыми I/O это может занимать 5-10% CPU только на переключения. Node.js и Nginx построены на event loop именно чтобы минимизировать context switches.

Важно понимать: в системе обычно много процессов в состоянии **Ready**, но только один (или несколько на многоядерных системах) может быть в состоянии **Running** на каждом ядре процессора.

Процесс завершил чтение файла с диска. В какое состояние он переходит?

Process Control Block (PCB)

**Process Control Block (PCB)** - структура данных в ядре ОС, которая содержит всю информацию о процессе. PCB - это «паспорт» процесса для операционной системы. Без PCB невозможно контекстное переключение: ядро не знало бы, что сохранить и что восстановить.

**Основные поля PCB:** - **Process ID (PID)** - уникальный идентификатор процесса - **Process State** - текущее состояние (New, Ready, Running, Waiting, Terminated) - **Program Counter** - адрес следующей инструкции - **CPU Registers** - значения всех регистров процессора - **Memory Management Info** - таблицы страниц, границы сегментов - **Accounting Info** - время CPU, приоритет, лимиты - **I/O Status** - открытые файлы, устройства

Когда происходит **переключение контекста** (context switch), ОС должна: 1. Сохранить состояние текущего процесса в его PCB 2. Загрузить состояние нового процесса из его PCB 3. Переключить адресное пространство (таблицы страниц)

В Linux PCB называется `task_struct` и занимает около 1.7 КБ памяти. При 100 000 процессов - 170 МБ только на PCB. Команда `ps aux` показывает информацию из PCB всех процессов: PID, состояние, время CPU, использование памяти.

Что происходит с PCB при переключении контекста с процесса A на процесс B?

Операции с процессами

Операционная система предоставляет механизмы для **создания** и **завершения** процессов. Процессы образуют иерархию: каждый процесс (кроме init с PID=1) имеет родителя. В Linux эта иерархия видна через `pstree` - systemd (PID 1) является прародителем всего.

**Создание процесса в UNIX/Linux:** Процесс-родитель создаёт процесс-потомок системным вызовом **fork()**. Новый процесс получает копию адресного пространства родителя через механизм copy-on-write - страницы физически не копируются пока не будет записи.

**Как работает fork():** 1. Создаётся копия PCB родительского процесса 2. Выделяется новое адресное пространство (copy-on-write) 3. Дочерний процесс получает копию данных родителя 4. fork() возвращает 0 в дочернем процессе и PID дочернего в родительском

**Важно:** После exec() новая программа полностью заменяет код, данные и стек процесса. PID остаётся прежним, но выполняется уже другая программа. Это позволяет shell запускать любые команды не создавая новый PID с нуля.

**Завершение процесса:** Процесс завершается явно (вызов exit()) или при возврате из main(). ОС освобождает ресурсы: память, файлы, освобождает PCB. Если родитель не вызвал wait(), процесс становится **zombie** - PCB висит в памяти, хотя процесс уже завершён. На production-серверах утечка zombie-процессов - реальная проблема.

После fork() дочерний процесс начинает выполнение с начала программы (с main)

После fork() и родительский, и дочерний процессы продолжают выполнение с точки ПОСЛЕ вызова fork()

fork() не перезапускает программу, а создаёт копию процесса в текущем состоянии. Оба процесса продолжают с той же инструкции, но с разными возвращаемыми значениями fork() (0 в дочернем, PID в родительском).

Что вернёт fork() в дочернем процессе?

Ключевые идеи

  • **Процесс** - программа в стадии выполнения с собственной памятью, регистрами и состоянием. Программа - пассивный файл, процесс - активная сущность
  • **Адресное пространство**: text (код) + data (глобальные) + heap (динамическая память) + stack (локальные переменные). Регистры CPU - не часть address space, но часть контекста
  • **Состояния**: New -> Ready -> Running -> Waiting -> Terminated. Context switch (0.1-1 мкс) происходит при смене процесса на CPU
  • **PCB (task_struct в Linux)** - 1.7 КБ на процесс, хранит всё: PID, состояние, регистры, указатели на memory/files. Основа для context switch
  • **fork()** создаёт копию через copy-on-write, **exec()** заменяет программу, **wait()** ожидает дочерний. Zombie-процессы - реальная проблема на серверах без wait()
  • **Chrome, Nginx, PostgreSQL, Docker** - все используют изоляцию процессов как архитектурный примитив стабильности

Связанные темы

Процессы - фундамент, на котором строится всё остальное в операционных системах:

  • Потоки (Threads) — Легковесные процессы внутри одного адресного пространства. Разделяют память, но имеют свои стеки и регистры.
  • Планирование CPU — Алгоритмы выбора процесса из очереди Ready: FCFS, SJF, Round Robin, CFS (Linux).
  • Синхронизация — Мьютексы, семафоры и атомарные операции для координации процессов и потоков.
  • Управление памятью — Как ОС выделяет и изолирует адресные пространства процессов через виртуальную память.

Вопросы для размышления

  • Почему переключение контекста затратно? Что именно происходит с TLB при смене процесса?
  • В чём преимущество copy-on-write при fork() перед полным копированием памяти?
  • Почему в UNIX процессы создаются через fork() + exec(), а не одним системным вызовом?
  • Что такое zombie-процесс и почему он опасен на production-сервере?

Связанные уроки

  • os-01-intro — Базовые концепции ОС и роль ядра в управлении ресурсами
  • os-03-threads — Потоки - облегчённые процессы, разделяющие адресное пространство
  • os-04-scheduling — Планировщик выбирает процессы из очереди Ready
  • os-05-sync — Синхронизация нужна когда несколько процессов работают с общими данными
  • arch-04-cpu — Контекстное переключение - операция на уровне регистров процессора
  • os-07-memory — Виртуальная память изолирует адресные пространства процессов
  • db-25-connection-pooling
Процессы

0

1

Войти