03. Объекты ядра и их использование в приложении

Лекция №3. Объекты ядра и их использование в приложении

Курс: Системное программирование (2026–2027)
Учебная программа: 2025, регистрационный № УП-46/2025Пп/уч
Специальность переподготовки: 9-09-0612-02 «Программное обеспечение информационных систем»
Квалификация: Инженер-программист
Тема по программе: Тема 3. Объекты ядра и их использование в приложении (4 академических часа)
Формируемые компетенции: СП-23, СП-24


Введение и цели занятия

📌 Слайд 1: Тема 3. Объекты ядра и их использование в приложении

На предыдущих лекциях мы изучили границу системных вызовов и базовые абстракции операционной системы — процессы и потоки. Однако для практической разработки надежного системного программного обеспечения недостаточно просто запустить параллельные потоки. Когда несколько потоков одновременно обращаются к файлам, сокетам или разделяемой оперативной памяти, возникает проблема несогласованности данных (состояние гонки, Race Condition).

Для безопасного управления разделяемыми ресурсами, межпроцессного взаимодействия и координации потоков операционная система предоставляет фундаментальный механизм — объекты ядра (Kernel Objects).

Цель настоящей лекции — детально изучить архитектуру объектов ядра, внутреннее устройство таблиц дескрипторов, жизненный цикл файлов на системном уровне API, а также примитивы взаимного исключения и синхронизации (мьютексы, семафоры, функции ожидания).

📌 Слайд 2: План лекции

Учебные вопросы лекции:

  1. Архитектура и жизненный цикл объектов ядра (Kernel Objects).
  2. Таблица дескрипторов процесса (Process Handle Table), счетчик ссылок и наследование.
  3. Именованные объекты ядра и пространства имен.
  4. Системные интерфейсы работы с файлами и сканирования каталогов.
  5. Проблема состязаний (Race Condition) и концепция критической секции.
  6. Механизмы синхронизации: сигнальное состояние, мьютексы и семафоры.
  7. Синхронизация жизненного цикла процессов и потоков через функции ожидания.

1. Архитектура и концепция объектов ядра

📌 Слайд 3: Классификация системных объектов

В операционной системе Windows все ресурсы, с которыми взаимодействует программист, делятся на три категории:

  1. Объекты пользователя (User Objects): элементы графического интерфейса пользователя — окна (HWND), меню (HMENU), курсоры, горячие клавиши. Создаются библиотекой user32.dll. Дескрипторы окон глобальны для оконной станции, не имеют дескрипторов безопасности ядра и автоматически освобождаются при завершении процесса.
  2. Объекты графической подсистемы (GDI Objects): шрифты (HFONT), перья (HPEN), кисти (HBRUSH), контексты отображения (HDC), растровые изображения (HBITMAP). Управляются библиотекой gdi32.dll.
  3. Объекты ядра (Kernel Objects): структуры данных, монопольно размещаемые в оперативной памяти ядра (Ring 0) и управляемые непосредственно исполнительной подсистемой ОС. К ним относятся:
    • файлы и устройства (File Objects),
    • процессы и потоки (Process, Thread Objects),
    • объекты-задания (Job Objects),
    • примитивы синхронизации: мьютексы (Mutex), семафоры (Semaphore), события (Event), таймеры ожидания (Waitable Timer),
    • проекции файлов в память (File Mapping Objects),
    • межпроцессные каналы (Pipes) и почтовые ящики (Mailslots).

📌 Слайд 4: Внутреннее устройство объекта ядра

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

Каждый объект ядра состоит из двух логических частей:

  1. Заголовок объекта ядра (Object Header): стандартизированная структура диспетчера объектов (Object Manager), присутствующая у всех объектов ядра без исключения:
    • Указатель типа (Type Object Pointer): определяет категорию ресурса (Файл, Мьютекс, Процесс).
    • Счетчик ссылок (Usage Count / Reference Count): количество активных ссылок на данный объект во всей операционной системе.
    • Дескриптор безопасности (Security Descriptor, SD): список контроля доступа (DACL), определяющий, каким пользователям и группам (SID) разрешено открывать, читать, модифицировать или удалять данный объект.
  2. Тело объекта ядра (Object Body): специфические данные конкретного ресурса. Например, для файлового объекта тело содержит текущее смещение чтения/записи (offset) и указатель на кэш; для объекта потока — контекст регистров ЦПУ и состояние планировщика; для мьютекса — идентификатор потока-владельца (Owner TID).

center


2. Таблица дескрипторов процесса и счетчик ссылок

📌 Слайд 5: Таблица дескрипторов процесса (Handle Table)

Поскольку прикладной процесс в Ring 3 не может оперировать прямыми адресами памяти ядра, операционная система предоставляет косвенный механизм адресации — дескрипторы (Handles).

В структуре ядра EPROCESS каждого процесса выделяется внутренняя Таблица дескрипторов (Handle Table). Она представляет собой массив записей, каждая из которых содержит:

  • Указатель на объект ядра в памяти Ring 0.
  • Маску предоставленных прав доступа (Access Mask): битовый набор разрешенных операций (например, GENERIC_READ, SYNCHRONIZE). При каждом вызове функции API ядро сверяет запрошенную операцию с маской доступа дескриптора.
  • Флаг наследования (Inheritance Flag): определяет, будет ли данный дескриптор скопирован в таблицу дочернего процесса при создании через CreateProcessW.

Сам тип HANDLE в коде на языке Си — это не числовой дескриптор файла и не физический указатель, а смещение (индекс, кратный 4 байтам) внутри таблицы дескрипторов текущего процесса. Значение HANDLE локально: одно и то же числовое значение (например, 0x00000004) в двух разных процессах ссылается на совершенно разные объекты ядра.

📌 Слайд 6: Счетчик ссылок (Usage Count) и закрытие CloseHandle

Объекты ядра существуют независимо от процессов, которые их создали. Жизненный цикл объекта ядра регулируется механизмом подсчета ссылок (Usage Count):

  1. Создание объекта: когда процесс вызывает функцию создания ресурса (например, CreateFileW или CreateMutexW), ядро выделяет память под объект и инициализирует счетчик ссылок Usage Count = 1. В таблицу дескрипторов процесса добавляется запись, а функция возвращает дескриптор HANDLE.
  2. Дублирование и разделение: если другой процесс открывает тот же именованный мьютекс через OpenMutexW или наследует дескриптор, в его таблицу дескрипторов вносится новая запись, указывающая на тот же физический объект ядра, а Usage Count увеличивается до 2.
  3. Закрытие дескриптора: при вызове CloseHandle(h) ядро удаляет соответствующую запись из таблицы дескрипторов вызывающего процесса и атомарно декрементирует Usage Count.
  4. Уничтожение объекта: объект ядра удаляется из оперативной памяти ядра только тогда, когда его счетчик ссылок становится равным нулю.

Важно: Закрытие дескриптора процесса CloseHandle(pi.hProcess) не приводит к остановке самого процесса! Процесс продолжает выполняться на процессоре. Закрытие дескриптора лишь сообщает ядру, что данный родительский процесс больше не планирует опрашивать дочерний процесс через этот HANDLE. Пока процесс активен, ядро само удерживает внутреннюю ссылку на объект процесса, поэтому Usage Count не упадет до нуля до фактического завершения процесса.

📌 Слайд 7: Наследование и дублирование дескрипторов

1. Механизм наследования дескрипторов

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

  • При создании объекта передать указатель на структуру SECURITY_ATTRIBUTES с установленным флагом bInheritHandle = TRUE:
1SECURITY_ATTRIBUTES sa;
2sa.nLength = sizeof(sa);
3sa.lpSecurityDescriptor = NULL;
4sa.bInheritHandle = TRUE; // Разрешить наследование
5
6HANDLE hFile = CreateFileW(L"log.txt", GENERIC_WRITE, 0, &sa, 
7                           CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  • При запуске дочернего процесса передать флаг bInheritHandles = TRUE в функцию CreateProcessW:
1CreateProcessW(NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);

При этом ядро дублирует записи с установленным флагом наследования в таблицу дескрипторов дочернего процесса под теми же числовыми индексами HANDLE и инкрементирует счетчик Usage Count соответствующих объектов ядра.

2. Дублирование через DuplicateHandle

Если процессу требуется передать дескриптор в уже работающий процесс без родственных связей (наследования), применяется системный вызов DuplicateHandle():

1DuplicateHandle(
2    hSourceProcess,      // Дескриптор исходного процесса
3    hSourceHandle,       // Исходный дескриптор
4    hTargetProcess,      // Дескриптор целевого процесса
5    &hTargetHandle,      // Выходной дескриптор для целевого процесса
6    0,                   // Желаемый доступ
7    FALSE,               // Наследование в целевом процессе
8    DUPLICATE_SAME_ACCESS// Скопировать маску доступа без изменений
9);

3. Именованные объекты ядра и пространства имен

📌 Слайд 8: Именованные объекты ядра и пространства имен

Многие объекты ядра (мьютексы, семафоры, события, проекции файлов) могут создаваться с символическими строковыми именами (Named Objects). Имя объекта регистрируется в глобальном дереве диспетчера объектов ядра Windows.

Именованные объекты ядра служат основным средством обнаружения ресурсов при межпроцессном взаимодействии (IPC):

  • Процесс A создает объект вызовом CreateMutexW(NULL, FALSE, L"Global\\MySystemSyncMutex");.
  • Процесс B подключается к уже созданному объекту вызовом OpenMutexW(SYNCHRONIZE, FALSE, L"Global\\MySystemSyncMutex");.

Пространства имен объектов:

  • Global\: объекты ядра доступны во всех сессиях пользователей операционной системы (включая системные службы и службы терминалов RDP). Требуются повышенные привилегии администратора SeCreateGlobalPrivilege.
  • Local\: объекты ядра изолированы в рамках текущей пользовательской сессии (сессии входа в систему).

Паттерн «Единственный экземпляр приложения» (Single Instance):

Классическая задача системного программирования — запретить запуск второй копии программы на одном компьютере:

 1#include <windows.h>
 2#include <stdio.h>
 3
 4int main(void) {
 5    // Создаем именованный мьютекс
 6    HANDLE hMutex = CreateMutexW(NULL, FALSE, L"Local\\MyUniqueApplicationLock");
 7    
 8    // Проверяем, существовал ли объект в ядре до нашего вызова
 9    if (GetLastError() == ERROR_ALREADY_EXISTS) {
10        printf("Приложение уже запущено! Завершение второй копии.\n");
11        CloseHandle(hMutex);
12        return 0;
13    }
14
15    printf("Приложение успешно запущено в единственном экземпляре.\n");
16    // Основной цикл программы...
17    getchar();
18
19    CloseHandle(hMutex);
20    return 0;
21}

4. Системные интерфейсы работы с файлами

📌 Слайд 9: Системный ввод-вывод: CreateFileW, ReadFile, WriteFile

Файл в операционной системе Windows является объектом ядра. В отличие от стандартной библиотеки Си (fopen, fread, использующих буферизацию в пространстве пользователя), системные функции WinAPI обращаются напрямую к драйверу файловой системы.

Создание и открытие: CreateFileW

Универсальная функция, открывающая файлы, каталоги, физические диски, COM-порты и именованные каналы:

 1HANDLE hFile = CreateFileW(
 2    L"C:\\data\\records.dat",     // Путь к файлу
 3    GENERIC_READ | GENERIC_WRITE, // Желаемый доступ (чтение/запись)
 4    FILE_SHARE_READ,              // Режим совместного доступа
 5    NULL,                         // Атрибуты безопасности по умолчанию
 6    OPEN_ALWAYS,                  // Открыть если есть, создать если нет
 7    FILE_ATTRIBUTE_NORMAL,        // Атрибуты файла
 8    NULL                          // Шаблонный файл
 9);
10
11if (hFile == INVALID_HANDLE_VALUE) {
12    DWORD err = GetLastError();
13    // Обработка ошибки...
14}

Чтение и запись: ReadFile и WriteFile

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

1char buffer[1024];
2DWORD bytesRead;
3BOOL bSuccess = ReadFile(hFile, buffer, sizeof(buffer), &bytesRead, NULL);
4
5DWORD bytesWritten;
6bSuccess = WriteFile(hFile, buffer, bytesRead, &bytesWritten, NULL);

📌 Слайд 10: Позиционирование указателя файла: SetFilePointerEx

Файловый объект ядра хранит 64-битный указатель текущей позиции чтения/записи (File Offset). Каждое успешное чтение или запись автоматически смещает этот указатель вперед.

Для произвольного перемещения по файлу в WinAPI применяется функция SetFilePointerEx:

 1LARGE_INTEGER distance;
 2LARGE_INTEGER newPosition;
 3
 4// Смещение на 4096 байт от начала файла
 5distance.QuadPart = 4096;
 6
 7BOOL bResult = SetFilePointerEx(
 8    hFile,          // Дескриптор открытого файла
 9    distance,       // Смещение (структура LARGE_INTEGER, 64 бита)
10    &newPosition,   // Переменная для записи новой позиции
11    FILE_BEGIN      // Точка отсчета
12);

Методы точки отсчета:

  • FILE_BEGIN: смещение отсчитывается от начала файла (эквивалент SEEK_SET в POSIX lseek).
  • FILE_CURRENT: смещение относительно текущей позиции (SEEK_CUR).
  • FILE_END: смещение от конца файла (SEEK_END, используется для определения размера файла или дозаписи в конец).

📌 Слайд 11: Сканирование файловой системы: FindFirstFileW и FindClose

Для обхода файлов и подкаталогов в Windows применяется специализированная итерационная модель:

 1#include <windows.h>
 2#include <stdio.h>
 3
 4void ScanDirectory(LPCWSTR pathMask) {
 5    WIN32_FIND_DATAW findData;
 6    HANDLE hFind = FindFirstFileW(pathMask, &findData);
 7
 8    if (hFind == INVALID_HANDLE_VALUE) {
 9        printf("Файлы не найдены или каталог недоступен.\n");
10        return;
11    }
12
13    do {
14        // Проверяем, является ли запись подкаталогом
15        if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
16            wprintf(L"[КАТАЛОГ] %s\n", findData.cFileName);
17        } else {
18            // Вычисляем размер 64-битного файла
19            ULARGE_INTEGER fileSize;
20            fileSize.LowPart = findData.nFileSizeLow;
21            fileSize.HighPart = findData.nFileSizeHigh;
22            wprintf(L"[ФАЙЛ]    %s (%llu байт)\n", findData.cFileName, fileSize.QuadPart);
23        }
24    } while (FindNextFileW(hFind, &findData));
25
26    // Обязательное закрытие дескриптора поиска ядра
27    FindClose(hFind);
28}

Важно: Функция FindClose(hFind) является критически необходимой. Дескриптор поиска hFind удерживает контекст сканирования директории и выделенные буферы ядра файловой системы. Ошибка вызова FindClose приводит к утечке системных дескрипторов.

center


5. Проблема состязаний и критические секции

📌 Слайд 12: Проблема состязаний (Race Condition)

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

Рассмотрим элементарную операцию инкремента переменной в языке Си: counter++: На уровне машинного кода архитектуры x86/x64 эта строчка распадается на три независимые инструкции:

  1. mov eax, [counter] — чтение значения из ОЗУ в регистр процессора;
  2. inc eax — инкремент значения в регистре;
  3. mov [counter], eax — запись обновленного значения обратно в ОЗУ.

Если планировщик ядра переключит контекст процессора между шагами 2 и 3, второй поток считает старое значение из памяти. В результате оба потока запишут одно и то же число, и один инкремент будет безвозвратно потерян.

Состояние гонки (Race Condition) — это ошибка проектирования многопоточной программы, при которой результат вычислений зависит от непредсказуемого порядка переключения потоков планировщиком ОС.

center

📌 Слайд 13: Концепция критической секции и взаимного исключения

Участок программного кода, обращающийся к разделяемому ресурсу (общей памяти, файлу, очереди), называется критической секцией (Critical Section).

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

  1. Взаимное исключение (Mutual Exclusion): ни в какой момент времени в критической секции не может находиться более одного потока одновременно.
  2. Прогресс (Progress): если ни один поток не находится в критической секции, любой ожидающий поток должен получить возможность войти без бесконечной задержки.
  3. Ограниченное ожидание (Bounded Waiting): время ожидания потока на входе в критическую секцию должно быть конечным; система не должна допускать «голодания» (Starvation) отдельных потоков.

6. Механизмы синхронизации: сигнальное состояние, мьютексы и семафоры

📌 Слайд 14: Концепция сигнального состояния объектов ядра

В операционной системе Windows все примитивы синхронизации построены на концепции бинарного флага ядра: объект может находиться либо в сигнальном (Signaled), либо в несигнальном (Nonsignaled) состоянии.

Поведение функции ожидания WaitForSingleObject(hObject, timeout):

  • Если объект находится в сигнальном состоянии, функция немедленно завершается успешно (возвращает WAIT_OBJECT_0), а поток продолжает исполнение.
  • Если объект находится в несигнальном состоянии, ядро переводит вызывающий поток в состояние ожидания (Blocked) и исключает его из очереди планировщика. Поток спит, пока объект не перейдет в сигнальное состояние либо пока не истечет указанный таймаут.

center

📌 Слайд 15: Мьютексы (Mutex) в системном API

Мьютекс (Mutex, от Mutual Exclusion) — это объект ядра, предназначенный для обеспечения взаимоисключающего доступа к ресурсу.

Особенности объекта-мьютекса в Windows:

  1. Состояние: мьютекс находится в сигнальном состоянии, когда он свободен (никому не принадлежит). При захвате потоком мьютекс переходит в несигнальное состояние.
  2. Владение (Thread Ownership): ядро строго отслеживает идентификатор потока (TID), владеющего мьютексом. Освободить мьютекс вызовом ReleaseMutex() может только тот поток, который его захватил.
  3. Потоко-рекурсивность (Recursion Count): если поток, уже владеющий мьютексом, повторно вызывает WaitForSingleObject на этот же мьютекс, вызов не приводит к взаимной блокировке (Deadlock). Ядро просто инкрементирует внутренний счетчик рекурсии. Чтобы мьютекс стал доступен другим потокам, поток-владелец должен вызвать ReleaseMutex ровно столько же раз.
  4. Брошенный мьютекс (Abandoned Mutex): если поток захватил мьютекс и неожиданно завершился (падение, ExitThread, TerminateThread), не вызвав ReleaseMutex, операционная система автоматически переводит мьютекс в сигнальное состояние, а следующему ждущему потоку возвращает специальный код WAIT_ABANDONED. Это предотвращает вечную блокировку системы.
 1HANDLE hMutex = CreateMutexW(NULL, FALSE, NULL);
 2
 3// Захват мьютекса
 4DWORD dwWait = WaitForSingleObject(hMutex, INFINITE);
 5if (dwWait == WAIT_OBJECT_0) {
 6    // Безопасная работа с общими данными в критической секции
 7    counter++;
 8    // Обязательное освобождение мьютекса
 9    ReleaseMutex(hMutex);
10}

📌 Слайд 16: Семафоры (Semaphore) в системном API

Семафор (Semaphore) — это объект ядра, поддерживающий счетчик доступных ресурсов в диапазоне от 0 до заданного максимума $N$.

Принцип работы семафора:

  • Если счетчик семафора строго больше 0, семафор находится в сигнальном состоянии.
  • Если счетчик семафора равен 0, семафор переходит в несигнальное состояние (ресурсы исчерпаны).

Каждый вызов WaitForSingleObject на семафор при значении счетчика $>0$ атомарно уменьшает счетчик на 1. Функция ReleaseSemaphore(hSem, count, &prevCount) атомарно увеличивает счетчик на указанное число.

 1// Семафор с начальным счетчиком 3 и максимальным 3 (пул из 3 подключений)
 2HANDLE hSem = CreateSemaphoreW(NULL, 3, 3, NULL);
 3
 4// Захват одного слота из пула
 5WaitForSingleObject(hSem, INFINITE);
 6
 7// Использование ресурса...
 8
 9// Возврат слота в пул (+1 к счетчику)
10ReleaseSemaphore(hSem, 1, NULL);

Отличия семафора от мьютекса:

ХарактеристикаМьютекс (Mutex)Семафор (Semaphore)
НазначениеВзаимное исключение (1 поток)Ограничение доступа к пулу ресурсов ($N$ потоков)
Понятие владельцаЕсть (только владелец может освободить)Нет (любой поток может вызвать ReleaseSemaphore)
Рекурсивный захватПоддерживается (счетчик рекурсии)Не поддерживается (каждый вызов уменьшает счетчик)
Обработка падения потокаСигнализирует WAIT_ABANDONEDНет защиты от завершения потока

📌 Слайд 17: Функция множественного ожидания WaitForMultipleObjects

В реальных приложениях потоку часто требуется ожидать наступления событий от нескольких объектов ядра одновременно (например, завершение рабочего потока или сигнал аварийной остановки программы).

Для этого применяется функция WaitForMultipleObjects:

 1HANDLE handles[2];
 2handles[0] = hWorkerThread; // Дескриптор потока
 3handles[1] = hShutdownEvent;// Дескриптор события завершения
 4
 5DWORD dwIndex = WaitForMultipleObjects(
 6    2,              // Число объектов в массиве (максимум MAXIMUM_WAIT_OBJECTS = 64)
 7    handles,        // Массив дескрипторов
 8    FALSE,          // bWaitAll: FALSE = ждать ЛЮБОЙ объект, TRUE = ждать ВСЕ объекты
 9    INFINITE        // Таймаут
10);
11
12if (dwIndex == WAIT_OBJECT_0) {
13    printf("Рабочий поток успешно завершил вычисления.\n");
14} else if (dwIndex == WAIT_OBJECT_0 + 1) {
15    printf("Получен сигнал экстренной остановки сервиса!\n");
16}

7. Синхронизация жизненного цикла процессов и потоков

📌 Слайд 18: Синхронизация завершения процессов и потоков

Объекты процессов и потоков сами по себе являются объектами синхронизации ядра:

  • Пока процесс или поток выполняется, его объект находится в несигнальном состоянии.
  • В момент вызова ExitProcess / ExitThread ядро навсегда переводит объект в сигнальное состояние.

Безопасное завершение потоков (Cancellation)

В системном программировании считается грубой ошибкой использование принудительного уничтожения потоков функцией TerminateThread():

  • TerminateThread() мгновенно обрывает выполнение потока на текущей машинной инструкции.
  • Если поток в этот момент владел памятью кучи, критической секцией или ресурсом ввода-вывода, ресурс останется заблокированным навсегда, приводя к зависанию всей программы.

Правильный паттерн завершения: использование флага остановки на базе объекта-события (Event):

  1. Главный поток создает событие: HANDLE hStopEvent = CreateEventW(NULL, TRUE, FALSE, NULL);
  2. Фоновый поток в рабочем цикле периодически проверяет сигнал: if (WaitForSingleObject(hStopEvent, 0) == WAIT_OBJECT_0) break;
  3. Для остановки главный поток вызывает SetEvent(hStopEvent) и ожидает корректного выхода фонового потока через WaitForSingleObject(hThread, INFINITE).
  4. Фоновый поток корректно освобождает все ресурсы, закрывает файлы и вызывает return.

📌 Слайд 19: Резюме лекции

Основные итоги:

  1. Объекты ядра размещаются в памяти Ring 0, защищены от прямой модификации прикладным кодом и имеют заголовок со счетчиком ссылок (Usage Count) и дескриптором безопасности.
  2. Дескрипторы HANDLE представляют собой локальные индексы во внутренней таблице дескрипторов процесса. Они требуют обязательного освобождения через CloseHandle().
  3. Именованные объекты ядра регистрируются в пространствах имен Global\ и Local\, обеспечивая межпроцессную синхронизацию и реализацию паттерна Single Instance.
  4. Файловый ввод-вывод на уровне WinAPI (CreateFileW, ReadFile, WriteFile, SetFilePointerEx) обеспечивает прямой небуферизованный доступ к данным; сканирование каталогов выполняется через триаду FindFirstFileW / FindNextFileW / FindClose.
  5. Состояние гонки (Race Condition) исключается защитой критических секций примитивами взаимного исключения.
  6. Мьютекс обеспечивает строгое владение ресурсом одним потоком с поддержкой рекурсии и защиты от бросания (WAIT_ABANDONED).
  7. Семафор регулирует доступ к пулу из $N$ ресурсов без привязки к потоку-владельцу.
  8. Функции ожидания (WaitForSingleObject, WaitForMultipleObjects) переводят поток в эффективный сон ядра до перехода объектов в сигнальное состояние.

📌 Слайд 20: Вопросы для самопроверки

  1. Чем объекты ядра отличаются от объектов пользователя (User Objects) и графических объектов (GDI Objects)?
  2. Что происходит с объектом ядра, если один процесс закрывает дескриптор вызовом CloseHandle, но этот же объект открыт во втором процессе?
  3. Каким образом с помощью функции CreateMutexW и кода ошибки ERROR_ALREADY_EXISTS гарантировать работу приложения в единственном экземпляре?
  4. Почему для позиционирования в файлах большого размера применяется функция SetFilePointerEx, а не устаревшая SetFilePointer?
  5. Почему после сканирования файлов директории функцией FindNextFileW необходимо обязательно вызывать FindClose?
  6. Что такое критическая секция и какие три условия Дейкстры обязана удовлетворять корректная синхронизация?
  7. В чем заключается принципиальная разница между объектом-мьютексом и объектом-семафором?
  8. Что означает код возврата WAIT_ABANDONED функции WaitForSingleObject и почему он возникает?
  9. Почему принудительное завершение потока функцией TerminateThread считается опасной практикой?

📌 Слайд 21: Литература и рекомендуемые ресурсы

Литература по курсу:

  • Современные операционные системы (4-е изд.) — Таненбаум Э., Бос Х. СПб.: Питер, 2021. 1119 с.
  • Устройство и функционирование OC Windows. Практикум к курсу «Операционные системы» — Коньков К. А. М.: Бином, 2008. 208 с.
  • Операционные системы (2-е изд.) — Гордеев А. В. СПб.: Питер, 2009. 415 с.
  • Системное программирование: методические указания по выполнению лабораторных работ — Бизюк А. Н., Соколова А. С. Витебск: УО «ВГТУ», 2024.
  • Справочник Windows API: Объекты ядра и синхронизация — https://learn.microsoft.com/en-us/windows/win32/sync/synchronization
  • Справочник Windows API: Управление файлами — https://learn.microsoft.com/en-us/windows/win32/fileio/file-management
← 02. Процессы и задания в прикладном … 04. Организация параллельной обработки с … →