Previous slide Next slide Toggle fullscreen Toggle overview view Open presenter view
Системное программирование
Тема 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): локальные переменные, кадры вызовов
Направления роста областей
Стек растет вниз (к младшим адресам)
Куча растет вверх (к старшим адресам)
Область ядра изолирована в старших адресах
Разрыв между стеком и кучей защищен от переполнения
Процессы и задания в прикладном программном интерфейсе
Системное программирование
Структура памяти процесса
Процессы и задания в прикладном программном интерфейсе
Системное программирование
Управляющие структуры процесса в ядре
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
Процессы и задания в прикладном программном интерфейсе
Системное программирование
Граф переходов состояний процесса
Процессы и задания в прикладном программном интерфейсе
Системное программирование
Переключение контекста (Context Switch)
Алгоритм переключения
Сохранение регистров общего назначения и векторных (xsave)
Сохранение указателей стека (RSP) и инструкции (RIP)
Смена адресного пространства: запись адреса каталога страниц в регистр CR3
Восстановление сохраненного состояния регистров нового процесса
Выход в 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
Процессы и задания в прикладном программном интерфейсе
Системное программирование
Замещение образа 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 ) {
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)
Процессы и задания в прикладном программном интерфейсе
Системное программирование
Разграничение ресурсов между процессом и потоками
Процессы и задания в прикладном программном интерфейсе
Системное программирование
Архитектурные модели многопоточности
Модель 1:1 (Ядерные потоки)
Каждый поток пользователя поддерживается ядром
Linux (NPTL / clone) , Windows Threads
Истинный параллелизм на многоядерных ЦПУ
Блокировка одного потока не останавливает другие
Модели N:1 и M:N
Модель N:1 (User-level / Fibers):
Переключение в User Space без участия ядра
Блокирующий вызов блокирует весь процесс
Модель M:N (Гибридная):
Рантаймы Go (Goroutines), Rust (Tokio)
Процессы и задания в прикладном программном интерфейсе
Системное программирование
Сравнение моделей реализации потоков
Процессы и задания в прикладном программном интерфейсе
Системное программирование
Системные 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
В чем заключается фундаментальная разница между виртуализацией процессора и виртуализацией оперативной памяти?
В каком направлении в виртуальном адресном пространстве процесса растут куча и стек?
Каким образом механизм Copy-On-Write предотвращает избыточное копирование памяти при системном вызове fork()?
Чем процесс-зомби отличается от процесса-сироты и как операционная система утилизирует сирот?
Вопросы 5–8
Какие дескрипторы возвращает функция CreateProcessW() в Windows и почему оба требуют вызова CloseHandle()?
Для решения каких задач системный инженер объединяет процессы в объект-задание (Job Object)?
Какие ресурсы являются общими для всех потоков процесса, а какие уникальны для каждого потока?
За счет чего интерфейс 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.
Завершить лекцию, объявить тему следующего занятия.