Previous slide Next slide Toggle fullscreen Toggle overview view Open presenter view
Системное программирование
Тема 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
Типы: файлы, сокеты, процессы, потоки, задания, мьютексы, семафоры, события
Объекты ядра и их использование в приложении
Системное программирование
Внутреннее устройство объекта ядра
Указатель типа: категория ресурса и допустимые операции
Usage Count: глобальный счетчик активных ссылок в ОС
Имя объекта: строковый путь в дереве диспетчера объектов
Security Descriptor: права доступа (DACL, владелец SID)
Тело объекта (Object Body)
Файл (File): текущее 64-битное смещение (offset), кэш
Поток (Thread): контекст регистров, состояние, ID
Мьютекс (Mutex): ID потока-владельца, счетчик рекурсии
Семафор (Semaphore): текущий и максимальный счетчик слотов
Объекты ядра и их использование в приложении
Системное программирование
Таблица дескрипторов процесса (Handle Table)
Объекты ядра и их использование в приложении
Системное программирование
Счетчик ссылок (Usage Count) и закрытие дескрипторов
Жизненный цикл объекта ядра
Создание: вызов Create* выделяет память в Ring 0, Usage Count = 1, выдает HANDLE
Разделение: вызов Open* или наследование добавляет запись в таблицу, Usage Count++
Удаление: память освобождается только когда Usage Count == 0
Закрытие дескриптора (CloseHandle)
Удаляет запись из таблицы дескрипторов процесса
Атомарно уменьшает Usage Count объекта ядра
Важное правило:
CloseHandle(pi.hProcess) не останавливает процесс!
Освобождается лишь ссылка родительского процесса на объект дочернего процесса
Объекты ядра и их использование в приложении
Системное программирование
Наследование и дублирование дескрипторов
Наследование дескрипторов
При создании объекта установить флаг:
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 );
При создании процесса разрешить наследование:
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
Объекты ядра и их использование в приложении
Системное программирование
Позиционирование указателя файла: SetFilePointerEx
Системный вызов SetFilePointerEx
LARGE_INTEGER distance, newPos;
distance.QuadPart = 4096 ;
BOOL bOk = SetFilePointerEx(
hFile,
distance,
&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)
Объекты ядра и их использование в приложении
Системное программирование
Критическая секция и взаимное исключение
Понятие критической секции
Участок кода, обращающийся к разделяемому ресурсу (памяти, файлу, очереди)
Одновременный доступ искажает данные (Race Condition)
Требует алгоритмической синхронизации
Три условия Дейкстры
Взаимное исключение: внутри секции не более одного потока
Прогресс: свободная секция немедленно доступна ждущему потоку
Ограниченное ожидание: конечное время ожидания (без голодания Starvation)
Объекты ядра и их использование в приложении
Системное программирование
Концепция сигнального состояния объектов ядра
Объекты ядра и их использование в приложении
Системное программирование
Мьютексы (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 до максимума N N N
Счетчик > 0: объект сигнален
Счетчик == 0: объект несигнален
WaitForSingleObject уменьшает счетчик на 1
ReleaseSemaphore увеличивает счетчик на указанное число слотов
Сравнение с мьютексом
Характеристика
Мьютекс
Семафор
Назначение
Взаимное исключение
Пул из N N N ресурсов
Владение
Есть (TID)
Отсутствует
Освобождение
Только владелец
Любой поток
Рекурсивность
Поддерживается
Не поддерживается
При аварии
WAIT_ABANDONED
Нет защиты
Объекты ядра и их использование в приложении
Системное программирование
Использование в коде
HANDLE h[2 ] = {hWorker, hShutdown};
DWORD dw = WaitForMultipleObjects(
2 ,
h,
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)
Создание: CreateEventW(NULL, TRUE, FALSE, NULL)
Опрос: if (WaitForSingleObject(hStop, 0) == 0) break;
Сигнал: главный поток вызывает SetEvent(hStop)
Ожидание: WaitForSingleObject(hThread, INFINITE)
Объекты ядра и их использование в приложении
Системное программирование
Резюме лекции
Объекты ядра и дескрипторы
Память Ring 0, защита через DACL
Таблица дескрипторов локальна для каждого процесса
Usage Count удаляет объект строго при нуле ссылок
Имена в Global\ и Local\ обеспечивают IPC
Синхронизация и ввод-вывод
Прямой ввод-вывод CreateFileW без буферов CRT
Защита критических секций от состояния гонки
Мьютекс: взаимное исключение, владение и WAIT_ABANDONED
Семафор (N N N ресурсов) и мультиплексор WaitForMultipleObjects
Объекты ядра и их использование в приложении
Системное программирование
Вопросы для самопроверки
Вопросы 1–5
В чем архитектурное отличие объектов ядра от объектов User и GDI?
Что происходит с объектом ядра при вызове CloseHandle, если он открыт в другом процессе?
Как паттерн с CreateMutexW и ERROR_ALREADY_EXISTS запрещает второй запуск программы?
Для чего предназначена функция SetFilePointerEx и в чем ее преимущество?
Почему вызов функции FindClose после сканирования каталогов строго обязателен?
Вопросы 6–9
Сформулируйте три фундаментальных условия Дейкстры для взаимного исключения.
Чем семафор принципиально отличается от мьютекса с точки зрения владения потоком?
Что означает код WAIT_ABANDONED и когда он возвращается?
Почему уничтожение потока через 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.
Опросить слушателей по условиям Дейкстры и отличиям мьютекса от семафора.
Завершить лекцию, объявить тему следующего занятия.