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

Тема 2. Процессы и задания в прикладном программном интерфейсе

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

План лекции

Основные вопросы

  • Процесс и виртуализация ресурсов (ЦПУ, память)
  • Граф состояний и переключение контекста
  • Создание процессов: fork/exec и CreateProcess
  • Управление группами: cgroups v2 и Job Objects
  • Модели потоков (1:1, N:1, M:N) и асинхронный I/O

Цели лекции

  • Освоить принципы распределения виртуальной памяти
  • Изучить механизмы fork/exec и CreateProcess
  • Разобраться в накладных расходах переключения контекста
  • Понять отличия процесса от потока на уровне ядра
  • Овладеть интерфейсами cgroups, Job Objects и IOCP
Процессы и задания в прикладном программном интерфейсе
Системное программирование

Концепция процесса и виртуализация ресурсов

Программа против процесса

  • Программа: пассивный бинарный образ на диске (ELF / PE)
  • Процесс: активный экземпляр программы в оперативной памяти
  • Процесс владеет виртуальной памятью, потоками и дескрипторами

Виртуализация ресурсов ОС

  • Виртуализация ЦПУ (Time-Sharing):
    • Иллюзия непрерывного владения процессором
    • Квантование времени (кванты 1–20 мс)
  • Виртуализация памяти:
    • Изолированное адресное пространство
    • Аппаратная трансляция адресов через MMU
Процессы и задания в прикладном программном интерфейсе
Системное программирование

Анатомия виртуального адресного пространства

Сегменты в оперативной памяти

  • Секция .text: код программы (Read-Only + Execute)
  • Секции данных: .data (инициализированные) и .bss (нули)
  • Куча (Heap): динамическое выделение (malloc, HeapAlloc)
  • Memory Mapping: динамические библиотеки (DLL/so), mmap
  • Стек (Stack): локальные переменные, кадры вызовов

Направления роста областей

  • Стек растет вниз (к младшим адресам)
  • Куча растет вверх (к старшим адресам)
  • Область ядра изолирована в старших адресах
  • Разрыв между стеком и кучей защищен от переполнения
Процессы и задания в прикладном программном интерфейсе
Системное программирование

Структура памяти процесса

center

Процессы и задания в прикладном программном интерфейсе
Системное программирование

Управляющие структуры процесса в ядре

Linux: struct task_struct

  • Идентификаторы: pid, tgid (PID процесса), real_parent
  • Память: указатель mm_struct *mm на таблицы страниц
  • Файлы: указатель files_struct *files на открытые дескрипторы
  • Сигналы: обработчики и маски блокировки
  • Планировщик: состояние, приоритет, счетчик тактов

Windows: EPROCESS и PEB

  • KPROCESS: базовый приоритет ядра, регистр CR3
  • EPROCESS: дескрипторы, маркер доступа, квоты
  • PEB (User Mode): список загруженных DLL, командная строка, переменные окружения
Процессы и задания в прикладном программном интерфейсе
Системное программирование

Жизненный цикл и состояния процесса

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

  • Создание (New): выделение структур PCB/EPROCESS
  • Готовность (Ready): процесс в очереди планировщика
  • Выполнение (Running): инструкции исполняются на CPU
  • Ожидание (Blocked): ожидание ввода-вывода или мьютекса
  • Завершен (Terminated): выход, ожидание сбора статуса

Причины смены состояний

  • Истечение кванта времени →\rightarrow возврат в Ready
  • Блокирующий системный вызов →\rightarrow переход в Blocked
  • Завершение ввода-вывода →\rightarrow возврат в Ready
Процессы и задания в прикладном программном интерфейсе
Системное программирование

Граф переходов состояний процесса

center

Процессы и задания в прикладном программном интерфейсе
Системное программирование

Переключение контекста (Context Switch)

Алгоритм переключения

  1. Сохранение регистров общего назначения и векторных (xsave)
  2. Сохранение указателей стека (RSP) и инструкции (RIP)
  3. Смена адресного пространства: запись адреса каталога страниц в регистр CR3
  4. Восстановление сохраненного состояния регистров нового процесса
  5. Выход в Ring 3 (sysretq / iretq)

Накладные расходы (Cache Penalty)

  • Инвалидация TLB: сброс кэша трансляции адресов
  • Промахи аппаратных кэшей L1/L2/L3 (холодный кэш)
  • Оптимизация процессора: использование тегов PCID
Процессы и задания в прикладном программном интерфейсе
Системное программирование

Создание процессов в Linux: fork() и Copy-On-Write

Системный вызов fork()

  • Дублирует вызывающий процесс
  • Создает новый уникальный PID
  • Копирует таблицу файловых дескрипторов
  • fork() вызывается один раз, а возвращает дважды:
    • Родителю: PID дочернего процесса
    • Дочернему процессу: 0

Оптимизация Copy-On-Write (COW)

  • Физические страницы памяти не дублируются
  • Страницы помечаются флагом Read-Only в MMU
  • При попытке записи ядро перехватывает #PF
  • Выделяется отдельный физический фрейм 4 КБ
  • Исключаются лишние аллокации памяти
Процессы и задания в прикладном программном интерфейсе
Системное программирование

Жизненный цикл fork, execve и waitpid

center

Процессы и задания в прикладном программном интерфейсе
Системное программирование

Замещение образа execve() и сбор статуса waitpid()

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void) {
    pid_t pid = fork();
    if (pid == 0) {
        // Дочерний процесс: заменяем образ утилитой ls
        char *argv[] = {"ls", "-la", "/tmp", NULL};
        execve("/bin/ls", argv, NULL);
        perror("Ошибка execve");
        exit(127);
    } else if (pid > 0) {
        // Родительский процесс: ждем завершения и забираем код
        int status;
        waitpid(pid, &status, 0);
        if (WIFEXITED(status))
            printf("Потомок завершился, код: %d\n", WEXITSTATUS(status));
    }
    return 0;
}
Процессы и задания в прикладном программном интерфейсе
Системное программирование

Проблема процессов-зомби и процессов-сирот

Процессы-зомби (Zombie / Defunct)

  • Потомок завершил исполнение (exit)
  • Память освобождена, но запись в таблице ядра осталась
  • Родитель не вызвал waitpid() для сбора статуса
  • Опасность: исчерпание пула доступных PID
  • Решение: обработка сигнала SIGCHLD

Процессы-сироты (Orphan Processes)

  • Родитель завершился раньше своего потомка
  • Потомок автоматически переподчиняется процессу init (PID 1 / systemd)
  • init периодически вызывает wait(), очищая ресурсы
Процессы и задания в прикладном программном интерфейсе
Системное программирование

Создание процессов в Windows: CreateProcessW()

Одноэтапное создание

  • Windows не использует связку fork + exec
  • Создание процесса выполняется функцией CreateProcessW
  • Сразу указывается исполняемый файл и командная строка
  • Создаются структуры EPROCESS и первичный поток

Структуры управления

  • STARTUPINFOW: параметры окна, перенаправление дескрипторов ввода-вывода
  • PROCESS_INFORMATION: возвращает дескрипторы и идентификаторы:
    • hProcess и dwProcessId
    • hThread и dwThreadId
Процессы и задания в прикладном программном интерфейсе
Системное программирование

Практика CreateProcessW в среде Windows

STARTUPINFOW si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));

WCHAR cmd[] = L"notepad.exe C:\\log.txt";

if (CreateProcessW(NULL, cmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
    // Ожидание завершения дочернего процесса
    WaitForSingleObject(pi.hProcess, INFINITE);

    DWORD exitCode;
    GetExitCodeProcess(pi.hProcess, &exitCode);
    printf("Процесс завершился с кодом: %lu\n", exitCode);

    // Обязательное закрытие обоих полученных дескрипторов
    CloseHandle(pi.hThread);
    CloseHandle(pi.hProcess);
}
Процессы и задания в прикладном программном интерфейсе
Системное программирование

Иерархия и группы процессов в Linux

Идентификаторы и группы

  • PID (Process ID): уникальный номер процесса
  • PPID (Parent PID): номер родительского процесса
  • PGID (Process Group ID): объединяет группу процессов
    • Сигнал группе: kill(-pgid, SIGINT)
  • SID (Session ID): сессия терминала (setsid())

Контрольные группы (cgroups v2)

  • Иерархическое ограничение системных ресурсов:
    • memory.max — жесткий лимит оперативной памяти
    • cpu.max — лимит процессорного времени
    • io.max — ограничение дискового ввода-вывода
  • Базовый механизм контейнеризации (Docker, Podman)
Процессы и задания в прикладном программном интерфейсе
Системное программирование

Объекты-задания (Job Objects) в Windows

Возможности Job Objects

  • Объединение группы процессов в один системный объект
  • Создание: HANDLE hJob = CreateJobObjectW(NULL, name);
  • Привязка: AssignProcessToJobObject(hJob, hProcess);
  • Завершение группы: TerminateJobObject(hJob, code);

Квотирование и контроль

  • JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE:
    • Автоматическое уничтожение дочерних процессов при закрытии родителя
  • Лимиты памяти: на отдельный процесс и на всю группу
  • Ограничение суммарного процессорного времени
  • Ограничение числа активных процессов
Процессы и задания в прикладном программном интерфейсе
Системное программирование

Многопоточность: процесс против потока

Процесс (Контейнер ресурсов)

  • Единое виртуальное адресное пространство
  • Общая секция кода, глобальные данные и куча
  • Таблица открытых файлов и сетевых сокетов
  • Токены безопасности и права доступа

Поток (Единица исполнения)

  • Индивидуальный стек вызовов (User Call Stack)
  • Собственный набор регистров процессора (RIP, RSP, RAX...)
  • Локальная память потока (Thread-Local Storage, TLS)
  • Приоритет и маска диспетчеризации (Affinity)
Процессы и задания в прикладном программном интерфейсе
Системное программирование

Разграничение ресурсов между процессом и потоками

center

Процессы и задания в прикладном программном интерфейсе
Системное программирование

Архитектурные модели многопоточности

Модель 1:1 (Ядерные потоки)

  • Каждый поток пользователя поддерживается ядром
  • Linux (NPTL / clone), Windows Threads
  • Истинный параллелизм на многоядерных ЦПУ
  • Блокировка одного потока не останавливает другие

Модели N:1 и M:N

  • Модель N:1 (User-level / Fibers):
    • Переключение в User Space без участия ядра
    • Блокирующий вызов блокирует весь процесс
  • Модель M:N (Гибридная):
    • Рантаймы Go (Goroutines), Rust (Tokio)
Процессы и задания в прикладном программном интерфейсе
Системное программирование

Сравнение моделей реализации потоков

center

Процессы и задания в прикладном программном интерфейсе
Системное программирование

Системные API управления потоками

POSIX Threads (Linux)

#include <pthread.h>

void* worker(void* arg) {
    // Код потока
    return NULL;
}

pthread_t tid;
pthread_create(&tid, NULL, worker, NULL);
pthread_join(tid, NULL); // ожидание
  • Системный вызов clone() с флагами CLONE_VM, CLONE_FILES, CLONE_THREAD

Windows API

#include <windows.h>
#include <process.h>

unsigned __stdcall Worker(void* arg) {
    return 0;
}

HANDLE h = (HANDLE)_beginthreadex(
    NULL, 0, Worker, NULL, 0, NULL);
WaitForSingleObject(h, INFINITE);
CloseHandle(h);
  • Использование _beginthreadex вместо CreateThread для корректной работы CRT
Процессы и задания в прикладном программном интерфейсе
Системное программирование

Синхронный против асинхронного ввода-вывода

Синхронный ввод-вывод

  • Поток вызывает read() / ReadFile()
  • Ядро переводит поток в состояние Blocked
  • Поток простаивает до завершения физического чтения
  • Проблема: модель "один поток на клиента" не масштабируется на 100 000 соединений (C10K problem)

Асинхронный ввод-вывод

  • Поток инициирует операцию и немедленно продолжает работу
  • Контроллер устройства читает данные через DMA в фоновом режиме
  • По завершении ядро помещает уведомление в очередь завершения
  • Пул потоков обрабатывает готовые результаты
Процессы и задания в прикладном программном интерфейсе
Системное программирование

Высокопроизводительные асинхронные механизмы

Windows: Порты завершения (IOCP)

  • Создание порта: CreateIoCompletionPort
  • Привязка дескрипторов с флагом OVERLAPPED
  • Пул потоков ожидает события вызовом GetQueuedCompletionStatus
  • Ядро будит потоки строго по числу физических ядер процессора

Linux: Интерфейс io_uring

  • Две кольцевые очереди в разделяемой памяти:
    • SQ (Submission Queue): запросы приложения
    • CQ (Completion Queue): результаты от ядра
  • Исполнение пакетных операций без системных вызовов
  • Поддержка Kernel Polling (IORING_SETUP_SQPOLL)
Процессы и задания в прикладном программном интерфейсе
Системное программирование

Концепция удаленного запуска приложений

Удаленный вызов процедур (RPC)

  • Прозрачный вызов процедуры на удаленном узле сети
  • Клиентский стаб (Stub) сериализует аргументы (Marshalling)
  • Сетевой транспорт передает запрос на сервер
  • Серверный скелетон выполняет процедуру и возвращает ответ
  • Примеры: MS-RPC в Windows, gRPC, ONC RPC

Сетевые терминалы и оркестрация

  • SSH (Secure Shell): запуск через удаленный псевдотерминал pty
  • WinRM (Windows Remote Management): протокол управления на базе WS-Management
  • Автоматизированное развертывание и оркестрация процессов
Процессы и задания в прикладном программном интерфейсе
Системное программирование

Резюме лекции

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

  • Процесс выступает контейнером виртуальных ресурсов (адресное пространство, куча, дескрипторы).
  • Адресное пространство структурировано на секции .text, .data, .bss, Heap и Stack с разграничением прав MMU.
  • Смена контекста требует сохранения регистров и смены таблицы страниц в CR3, что вызывает инвалидацию кэша TLB.

Управление и параллелизм

  • Создание процессов: раздельный механизм fork (с Copy-On-Write) + execve в Linux против единого CreateProcessW в Windows.
  • Групповой контроль: подсистемы cgroups v2 и Job Objects обеспечивают квотирование ресурсов и уничтожение брошенных задач.
  • Потоки (1:1): разделяют общую память процесса, обладая индивидуальными стеками и регистрами.
  • Масштабируемый I/O: асинхронные интерфейсы IOCP и io_uring устраняют блокировки потоков.
Процессы и задания в прикладном программном интерфейсе
Системное программирование

Вопросы для самопроверки

Вопросы 1–4

  1. В чем заключается фундаментальная разница между виртуализацией процессора и виртуализацией оперативной памяти?
  2. В каком направлении в виртуальном адресном пространстве процесса растут куча и стек?
  3. Каким образом механизм Copy-On-Write предотвращает избыточное копирование памяти при системном вызове fork()?
  4. Чем процесс-зомби отличается от процесса-сироты и как операционная система утилизирует сирот?

Вопросы 5–8

  1. Какие дескрипторы возвращает функция CreateProcessW() в Windows и почему оба требуют вызова CloseHandle()?
  2. Для решения каких задач системный инженер объединяет процессы в объект-задание (Job Object)?
  3. Какие ресурсы являются общими для всех потоков процесса, а какие уникальны для каждого потока?
  4. За счет чего интерфейс io_uring в Linux обеспечивает более высокую скорость ввода-вывода по сравнению с классическими системными вызовами?
Процессы и задания в прикладном программном интерфейсе
Системное программирование

Полезные ресурсы

Учебные издания

  • Современные операционные системы (4-е изд.) — Таненбаум Э., Бос Х. СПб.: Питер, 2021. 1119 с.
  • Операционные системы (2-е изд.) — Гордеев А. В. СПб.: Питер, 2009. 415 с.
  • Устройство и функционирование OC Windows — Коньков К. А. М.: Бином, 2008. 208 с.
  • Методические указания к лабораторным работам — Бизюк А. Н., Соколова А. С. ВГТУ, 2024.

Документация и справочники

Процессы и задания в прикладном программном интерфейсе

Лекция №2. Цель: сформировать детальное понимание концепций процесса и потока, организации виртуальной памяти, переключения контекста, жизненного цикла и системных интерфейсов управления задачами в Linux и Windows. Связь с предыдущими темами: опирается на Тему 1 (системные вызовы, кольца защиты Ring 0/Ring 3, обработка системных ошибок). Связь с практической работой: является теоретической базой лабораторной работы №1 "Процессы и задания в прикладном программном интерфейсе".

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

Пояснить студентам: один и тот же бинарный файл может быть одновременно запущен сотнями независимых процессов.

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

Показать студентам нулевую страницу внизу схемы — именно обращение к ней вызывает падение при разыменовании NULL.

Отметить, что структура PEB доступна в Ring 3 без системных вызовов через сегментный регистр GS на x64.

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

Проследить циклическое переключение между Ready и Running под управлением аппаратного таймера.

Объяснить студентам: переключение контекста между процессами дороже переключения между потоками одного процесса именно из-за перезаписи CR3 и сброса TLB.

Подчеркнуть гениальность механизма COW: если за fork сразу следует execve, копирования памяти практически не происходит.

Обратить внимание на сигнал SIGCHLD, отправляемый ядром родителю при выходе потомка.

Разобрать типовые макросы WIFEXITED и WEXITSTATUS для безопасного извлечения кода завершения.

Предостеречь слушателей: накопление зомби на сервере блокирует создание любых новых процессов в ОС.

Подчеркнуть: функция CreateProcessW принимает 10 параметров, позволяя гибко настроить окружение дочернего процесса.

Обратить особое внимание: не закрытый дескриптор pi.hThread или pi.hProcess приведет к утечке дескрипторов ядра.

Объяснить студентам концепцию конвейера: команды cat file | grep | wc имеют один общий PGID.

Показать пользу Job Objects: предотвращение зависания брошенных фоновых процессов при сбое управляющей программы.

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

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

Сравнить: почему ОС общего назначения остановились на 1:1, а современные рантаймы строят поверх M:N.

Пояснить компромиссы: модель 1:1 требует больше памяти под ядерный стек, но аппаратно параллельна.

Обратить внимание: в Windows при использовании функций Си (printf, malloc) обязательно использовать _beginthreadex.

Пояснить, почему современные серверные архитектуры строятся исключительно на асинхронном неблокирующем вводе-выводе.

Отметить, что io_uring на сегодня является наиболее производительным механизмом ввода-вывода в ОС семейства Linux.

Указать, что системные службы Windows активно взаимодействуют между машинами именно через RPC.

Подвести итог занятия: процесс дает изоляцию, поток дает скорость вычислений.

Опросить слушателей по ключевым понятиям: разница fork/exec и CreateProcess, роль COW.

Завершить лекцию, объявить тему следующего занятия.