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

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

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

План лекции

Учебные вопросы

  • Архитектура и структура объектов ядра
  • Таблица дескрипторов и счетчик ссылок
  • Наследование и дублирование (DuplicateHandle)
  • Системный файловый ввод-вывод WinAPI
  • Синхронизация потоков (мьютексы, семафоры)

Цели занятия

  • Освоить архитектуру и жизненный цикл объектов ядра
  • Изучить устройство таблицы дескрипторов
  • Освоить функции CreateFileW, ReadFile, WriteFile
  • Разобрать условия Дейкстры для взаимного исключения
  • Изучить работу мьютексов, семафоров и функций ожидания
Объекты ядра и их использование в приложении
Системное программирование

Классификация системных объектов Windows

User и GDI объекты

  • User Objects (user32.dll):
    • Окна (HWND), меню, курсоры, горячие клавиши
    • Привязаны к оконной станции; без безопасности DACL
  • GDI Objects (gdi32.dll):
    • Шрифты (HFONT), кисти, контексты отображения (HDC)
    • Квотируются ОС (до 10 000 объектов на процесс)

Объекты ядра (Kernel Objects)

  • Размещаются монопольно в памяти Ring 0
  • Управляются диспетчером объектов (Object Manager)
  • Защищены дескрипторами безопасности (DACL)
  • Доступны пользовательскому коду только через HANDLE
  • Типы: файлы, сокеты, процессы, потоки, задания, мьютексы, семафоры, события
Объекты ядра и их использование в приложении
Системное программирование

Внутреннее устройство объекта ядра

Заголовок объекта (Object Header)

  • Указатель типа: категория ресурса и допустимые операции
  • Usage Count: глобальный счетчик активных ссылок в ОС
  • Имя объекта: строковый путь в дереве диспетчера объектов
  • Security Descriptor: права доступа (DACL, владелец SID)

Тело объекта (Object Body)

  • Файл (File): текущее 64-битное смещение (offset), кэш
  • Поток (Thread): контекст регистров, состояние, ID
  • Мьютекс (Mutex): ID потока-владельца, счетчик рекурсии
  • Семафор (Semaphore): текущий и максимальный счетчик слотов
Объекты ядра и их использование в приложении
Системное программирование

Таблица дескрипторов процесса (Handle Table)

center

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

Счетчик ссылок (Usage Count) и закрытие дескрипторов

Жизненный цикл объекта ядра

  • Создание: вызов Create* выделяет память в Ring 0, Usage Count = 1, выдает HANDLE
  • Разделение: вызов Open* или наследование добавляет запись в таблицу, Usage Count++
  • Удаление: память освобождается только когда Usage Count == 0

Закрытие дескриптора (CloseHandle)

  • Удаляет запись из таблицы дескрипторов процесса
  • Атомарно уменьшает Usage Count объекта ядра
  • Важное правило:
    • CloseHandle(pi.hProcess) не останавливает процесс!
    • Освобождается лишь ссылка родительского процесса на объект дочернего процесса
Объекты ядра и их использование в приложении
Системное программирование

Наследование и дублирование дескрипторов

Наследование дескрипторов

  1. При создании объекта установить флаг:
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE; // Разрешить

HANDLE h = CreateFileW(L"log.txt", 
    GENERIC_WRITE, 0, &sa, 
    CREATE_ALWAYS, 0, NULL);
  1. При создании процесса разрешить наследование:
CreateProcessW(NULL, cmd, NULL, NULL, 
    TRUE, 0, NULL, NULL, &si, &pi);

Дублирование: DuplicateHandle

Передача дескриптора в работающий процесс:

DuplicateHandle(
    hSourceProcess, // Источник
    hSourceHandle,  // Исходный дескриптор
    hTargetProcess, // Процесс-получатель
    &hTargetHandle, // Новый дескриптор
    0,              // Желаемый доступ
    FALSE,          // Наследование
    DUPLICATE_SAME_ACCESS
);
  • Ядро атомарно вносит запись в таблицу целевого процесса и инкрементирует Usage Count
Объекты ядра и их использование в приложении
Системное программирование

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

Имена объектов ядра

  • Объекты синхронизации и память могут иметь имена
  • Доступны для открытия через OpenMutexW, OpenEventW
  • Пространства имен:
    • Global\имя — доступен во всех пользовательских сессиях (требует SeCreateGlobalPrivilege)
    • Local\имя — изолирован в рамках сессии пользователя

Паттерн Single Instance

Запрет запуска второй копии программы через именованный мьютекс:

HANDLE hMutex = CreateMutexW(NULL, FALSE, 
    L"Local\\MyUniqueAppLock");

if (GetLastError() == ERROR_ALREADY_EXISTS) {
    printf("Программа уже запущена!\n");
    CloseHandle(hMutex);
    return 0; // Завершение копии
}

// Выполнение основного приложения...
CloseHandle(hMutex);
Объекты ядра и их использование в приложении
Системное программирование

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

center

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

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

Системный вызов SetFilePointerEx

LARGE_INTEGER distance, newPos;
distance.QuadPart = 4096; // 64 бита

BOOL bOk = SetFilePointerEx(
    hFile,          // Дескриптор файла
    distance,       // Смещение (64 бита)
    &newPos,        // Новая позиция
    FILE_BEGIN      // Базовая точка
);
  • Файловый объект ядра хранит 64-битный указатель смещения
  • Успешный ReadFile или WriteFile автоматически сдвигает указатель

Точки отсчета (dwMoveMethod)

  • FILE_BEGIN: смещение относительно начала файла (аналог POSIX SEEK_SET)
  • FILE_CURRENT: смещение относительно текущего положения указателя (SEEK_CUR)
  • FILE_END: смещение относительно конца файла (SEEK_END, для дозаписи или вычисления размера)
Объекты ядра и их использование в приложении
Системное программирование

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

Итерационный обход каталогов

WIN32_FIND_DATAW fd;
HANDLE hFind = FindFirstFileW(
    L"C:\\data\\*.*", &fd);

if (hFind != INVALID_HANDLE_VALUE) {
    do {
        if (fd.dwFileAttributes & 
            FILE_ATTRIBUTE_DIRECTORY) {
            wprintf(L"[DIR]  %s\n", fd.cFileName);
        } else {
            ULARGE_INTEGER sz;
            sz.LowPart = fd.nFileSizeLow;
            sz.HighPart = fd.nFileSizeHigh;
            wprintf(L"[FILE] %s (%llu B)\n", 
                    fd.cFileName, sz.QuadPart);
        }
    } while (FindNextFileW(hFind, &fd));
    FindClose(hFind); // Обязательно!
}

Важность вызова FindClose

  • hFind — дескриптор поиска ядра, удерживающий контекст сканирования директории
  • Удерживает внутренние буферы драйвера файловой системы
  • Пропуск FindClose приводит к утечке дескрипторов ядра и блокировке каталога от удаления
Объекты ядра и их использование в приложении
Системное программирование

Проблема состязаний (Race Condition)

center

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

Критическая секция и взаимное исключение

Понятие критической секции

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

Три условия Дейкстры

  1. Взаимное исключение: внутри секции не более одного потока
  2. Прогресс: свободная секция немедленно доступна ждущему потоку
  3. Ограниченное ожидание: конечное время ожидания (без голодания Starvation)
Объекты ядра и их использование в приложении
Системное программирование

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

center

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

Мьютексы (Mutex) в системном API

Особенности мьютекса

  • Владение (Ownership): захватывается и освобождается одним и тем же потоком (TID)
  • Рекурсивность: повторный захват тем же потоком безопасен (счетчик рекурсии)
  • WAIT_ABANDONED: при падении владельца ядро автоматически сигнализирует объект ждущему потоку

Использование мьютекса

HANDLE hMutex = CreateMutexW(NULL, FALSE, NULL);

DWORD dw = WaitForSingleObject(hMutex, INFINITE);
if (dw == WAIT_OBJECT_0) {
    shared_counter++; // Критическая секция
    ReleaseMutex(hMutex);
} else if (dw == WAIT_ABANDONED) {
    // Владелец завершился аварийно!
    ReleaseMutex(hMutex);
}
Объекты ядра и их использование в приложении
Системное программирование

Семафоры (Semaphore) в системном API

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

  • Поддерживает целочисленный счетчик свободных ресурсов от 0 до максимума NN
  • Счетчик > 0: объект сигнален
  • Счетчик == 0: объект несигнален
  • WaitForSingleObject уменьшает счетчик на 1
  • ReleaseSemaphore увеличивает счетчик на указанное число слотов

Сравнение с мьютексом

Характеристика Мьютекс Семафор
Назначение Взаимное исключение Пул из NN ресурсов
Владение Есть (TID) Отсутствует
Освобождение Только владелец Любой поток
Рекурсивность Поддерживается Не поддерживается
При аварии WAIT_ABANDONED Нет защиты
Объекты ядра и их использование в приложении
Системное программирование

Множественное ожидание: WaitForMultipleObjects

Использование в коде

HANDLE h[2] = {hWorker, hShutdown};

DWORD dw = WaitForMultipleObjects(
    2,        // Число объектов (до 64)
    h,        // Массив дескрипторов
    FALSE,    // bWaitAll: FALSE=любой
    INFINITE  // Таймаут ожидания
);

if (dw == WAIT_OBJECT_0) {
    printf("Поток вычислений завершен\n");
} else if (dw == WAIT_OBJECT_0 + 1) {
    printf("Экстренная остановка!\n");
}

Правила ожидания

  • Лимит: до MAXIMUM_WAIT_OBJECTS (64 дескриптора)
  • bWaitAll = TRUE: ждет готовности всех объектов одновременно
  • bWaitAll = FALSE: ждет готовности любого объекта; индекс = dw - WAIT_OBJECT_0
  • При одновременной готовности возвращается наименьший индекс
Объекты ядра и их использование в приложении
Системное программирование

Синхронизация завершения процессов и потоков

Опасность TerminateThread

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

Кооперативная отмена (Event)

  1. Создание: CreateEventW(NULL, TRUE, FALSE, NULL)
  2. Опрос: if (WaitForSingleObject(hStop, 0) == 0) break;
  3. Сигнал: главный поток вызывает SetEvent(hStop)
  4. Ожидание: WaitForSingleObject(hThread, INFINITE)
Объекты ядра и их использование в приложении
Системное программирование

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

Объекты ядра и дескрипторы

  • Память Ring 0, защита через DACL
  • Таблица дескрипторов локальна для каждого процесса
  • Usage Count удаляет объект строго при нуле ссылок
  • Имена в Global\ и Local\ обеспечивают IPC

Синхронизация и ввод-вывод

  • Прямой ввод-вывод CreateFileW без буферов CRT
  • Защита критических секций от состояния гонки
  • Мьютекс: взаимное исключение, владение и WAIT_ABANDONED
  • Семафор (NN ресурсов) и мультиплексор WaitForMultipleObjects
Объекты ядра и их использование в приложении
Системное программирование

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

Вопросы 1–5

  1. В чем архитектурное отличие объектов ядра от объектов User и GDI?
  2. Что происходит с объектом ядра при вызове CloseHandle, если он открыт в другом процессе?
  3. Как паттерн с CreateMutexW и ERROR_ALREADY_EXISTS запрещает второй запуск программы?
  4. Для чего предназначена функция SetFilePointerEx и в чем ее преимущество?
  5. Почему вызов функции FindClose после сканирования каталогов строго обязателен?

Вопросы 6–9

  1. Сформулируйте три фундаментальных условия Дейкстры для взаимного исключения.
  2. Чем семафор принципиально отличается от мьютекса с точки зрения владения потоком?
  3. Что означает код WAIT_ABANDONED и когда он возвращается?
  4. Почему уничтожение потока через TerminateThread считается грубой ошибкой проектирования?
Объекты ядра и их использование в приложении
Системное программирование

Литература и рекомендуемые ресурсы

Учебная литература

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

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

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

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

План ориентирован на освоение объектов ядра как фундаментальной базы Windows API.

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

Заголовок стандартизирован для всех объектов ядра, а тело уникально для каждого ресурса.

Дескриптор HANDLE — это индекс в локальной таблице процесса, а не прямой адрес в памяти.

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

DuplicateHandle работает между любыми процессами при наличии дескрипторов с PROCESS_DUP_HANDLE.

Использование ERROR_ALREADY_EXISTS гарантирует атомарную проверку без состояния гонки.

WinAPI I/O обращается напрямую к драйверу файловой системы без буферизации C runtime.

SetFilePointerEx заменяет устаревшую 32-битную SetFilePointer, предотвращая ошибки переполнения.

Всегда закрывать hFind через FindClose, а не CloseHandle!

Инкремент counter++ на ассемблере x86 состоит из 3 инструкций: mov -> inc -> mov.

Э. Дейкстра сформулировал эти условия в 1965 году как фундаментальные требования к синхронизации.

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

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

Семафор с максимумом 1 (двоичный) не эквивалентен мьютексу из-за отсутствия понятия владельца.

WaitForMultipleObjects является эффективным мультиплексором событий ядра Windows.

Кооперативная отмена через события гарантирует вызов деструкторов и освобождение ресурсов.

Подвести итог: объекты ядра — фундамент как файловой системы, так и многопоточности Windows.

Опросить слушателей по условиям Дейкстры и отличиям мьютекса от семафора.

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