
- •1.1 Основы программирования в операционной системе Windows
- •1.1.1 Вызов функций api
- •1.1.2 Структура программы
- •1.2 Вопросы системного программирования в Windows
- •1.2.1 Страничная и сегментная адресация.
- •1.2.2 Адресное пространство процесса.
- •2.1 Управление процессами
- •2.2 Процессы и потоки в Windows
- •2.3 Создание процессов
- •2.4 Определение исполняемого образа и командной строки
- •2.5 Идентификация процессов
- •3.1 Создание потока. Функция CreateThread
- •3.2. Завершение потока
- •3.3 Другие функции работы с потоками
- •3.4 Структура context
- •3.5 Приоритеты потоков
- •4.1 Объект critical_section
- •4.2 Мьютексы
- •4.3 Семафоры
- •5.1 События
- •7.1 Кучи
- •7.2 Управление памятью кучи
- •Другие функции для работы с кучей
- •Резюме по управлению кучей
- •Отображение адресного пространства процесса в объекты отображения
- •Что такое импорт
- •Явная загрузка dll
- •Явное подключение экспортируемого идентификатора
- •10.1 Управление файлами и каталогами Создание и открытие файлов
- •10.2 Управление каталогами
- •10.3 Другие методы получения атрибутов файлов и каталогов
- •11.1 Блокировка файлов
- •11.2 Реестр
- •12.1 Стандартные устройства и консольный ввод-вывод
- •12.2 Асинхронный ввод-вывод и порты завершения
- •Параметры
- •Цели системы безопасности
- •Параметры
- •Аварийное завершение
- •Использование именованных каналов
- •Параметры
- •Наблюдение за сообщениями в именованном канале
- •Параметры
1.1.2 Структура программы
Рассмотрим классическую структуру программы под Windows. В такой программе имеется главное окно, а следовательно, и процедура главного окна. В целом, в коде программы можно выделить следующие секции:
Регистрация класса окон
Создание главного окна
Цикл обработки очереди сообщений
Процедура главного окна
Конечно, в программе могут быть и другие разделы, но данные разделы образуют основной скелет программы. Разберем эти разделы по порядку.
Регистрация класса окон
Регистрация класса окон осуществляется с помощью функции RegisterClassA, единственным параметром которой является указатель на структуру WNDCLASS, содержащую информацию об окне (см. пример ниже).
Создание окна
На основе зарегистрированного класса с помощью функции Create WindowExA (или Create WindowA) можно создать экземпляр окна. Как можно заметить, это весьма напоминает объектную модель программирования.
Цикл обработки очереди сообщений
Вот как выглядит этот цикл на языке Си:
while (GetMessage (&msq, NULL, 0, 0))
{
/ / разрешить использование клавиатуры,
/ / путем трансляции сообщений о виртуальных клавишах
/ / в сообщения о алфавитно-цифровых клавишах
TranslateMessage (&msq);
/ / вернуть управление Windows и передать сообщение дальше
/ / процедуре окна
DispatchMessage (&msg);
}
Функция GetMessage() «отлавливает» очередное сообщение из ряда сообщений данного приложения и помещает его в структуру MSG.
Что касается функции TranslateMessage, то ее компетенция касается сообщений WM_KEYDOWN и WM_KEYUP, которые транслируются в WM_CHAR и WM_DEDCHAR, а также WM_SYSKEYDOWN и WM_SYSKEYUP, преобразующиеся в WM_SYSCHAR и WM_SYSDEADCHAR. Смысл трансляции заключается не в замене, а в отправке дополнительных сообщений. Так, например, при нажатии и отпускании алфавитно-цифровой клавиши в окно сначала придет сообщение WM_KEYDOWN, затем WM_KEYUP, а затем уже WM_CHAR.
Как можно видеть, выход из цикла ожиданий имеет место только в том случае, если функция GetMessage возвращает 0. Это происходит только при получении сообщения о выходе (сообщение WM_QUIT). Таким образом, цикл ожидания играет двоякую роль: определенным образом преобразуются сообщения, предназначенные для какого-либо окна, и ожидается сообщение о выходе из программы.
Процедура главного окна
Вот прототип функции окна на языке С:
LRESULT CALLBACK WindowFunc (HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam)
Смысл параметров:
hwnd – идентификатор окна,
message – идентификатор сообщения,
wParam и lParam – параметры, уточняющие смысл сообщения (для каждого сообщения могут играть разные роли или не играть никаких).
Все четыре параметра имеют тип DWORD.
1.2 Вопросы системного программирования в Windows
1.2.1 Страничная и сегментная адресация.
Семейство микропроцессоров Intel ведет свое начало с микропроцессора Intel 8086. В настоящее время во всю работает уже седьмое поколение. Но были в этой восходящей лестнице и две ступени, сыгравшие огромную роль в развитии компьютеров на базе микропроцессоров Intel. Это микропроцессор 80286 (защищенный режим) и микропроцессор 80386 (страничная адресация).
До появления микропроцессора 80286 микропроцессоры использовались в реальном режиме адресации. В реальном режиме для программирования использовался логический адрес, состоящий из двух 16-битных компонент: сегмента и смещения. Сегментный адрес мог храниться в одном из четырех сегментных регистров CS, DS, SS, ES. Смещение хранилось в одном из индексных регистров DI, SI, BX, BP, SP. При обращении к памяти логический адрес подвергался преобразованию, заключающемуся в том, что к смещению прибавлялся сегментный адрес, сдвинутый на четыре бита влево. В результате получался 20-битный адрес, который, как легко заметить, мог охватывать всего около 1 Мб памяти. Операционная система MS DOS и была изначально рассчитана для работы в таком адресном пространстве. Получаемый 20-битный адрес назывался линейным, и при этом фактически совпадал с физическим адресом ячейки памяти. Разумеется, с точки зрения развития операционных систем это был тупик. Должна быть, по крайней мере, возможность расширять память, и не просто расширять, а сделать все адресное пространство равноправным. Выход был найден с введением так называемого защищенного режима.
Гениальность подхода заключалась в том, что на первый взгляд ничего не изменилось. По-прежнему логический адрес формировался при помощи сегментных регистров и регистров, где хранилось смещение. Однако сегментные регистры хранили теперь не сегментный адрес, а так называемый селектор, часть которого (13 бит) представляла собой индекс в некоторой таблице, называемой дескрипторной. Индекс указывал на дескриптор, в котором хранилась полная информация о сегменте. Размер дескриптора был достаточен для адресации уже гораздо большего объема памяти.
На рис.1. схематически представлен алгоритм преобразования логического адреса в линейный адрес. Таблица дескрипторов или таблица базовых адресов могла быть двух типов: глобальная (GDT) и локальная (LDT). Тип таблицы определялся вторым битом содержимого сегментного регистра. На расположение глобальной таблицы и ее размер указывал регистр GDTR. В глобальной дескрипторной таблице должны храниться дескрипторы сегментов, занятых операционной системой. Адрес локальной таблицы дескрипторов хранился в регистре LDTR. Предполагалось, что локальных дескрипторных таблиц может быть несколько – одна для каждой запущенной задачи. Тем самым уже на уровне микропроцессора закладывалась поддержка многозадачности. Размер регистра GDTR составляет 48 бит. 32 бита – адрес глобальной таблицы, 16 бит – размер.
Кроме глобальной дескрипторной таблицы, предусматривалась еще одна общесистемная таблица – дескрипторная таблица прерываний (IDT). Она содержит дескрипторы специальных системных объектов, которые называются шлюзами и определяют точки входа процедур обработки прерываний и особых случаев. Положение дескрипторной таблицы прерываний определяется содержимым регистра IDTR, структура которого аналогична регистру GDTR.
Размер регистра LDTR составляет всего 10 байт. Первые 2 байта адресуют локальную дескрипторную таблицу не напрямую, а посредством глобальной дескрипторной таблицы, т. е. играют роль селектора для каждой вновь создаваемой задачи. Таким образом, в глобальную дескрипторную таблицу должен быть добавлен элемент, определяющий сегмент, где будет храниться локальная дескрипторная таблица данной задачи. Переключение же между задачами может происходить всего лишь сменой содержимого регистра LDTR. Отсюда, кстати, вытекает то, что, если задача одна собирается работать в защищенном режиме, ей незачем использовать локальные дескрипторные таблицы и регистр LDTR.
Дескриптор сегмента содержал, в частности, поле доступа, которое определяло тип индексируемого сегмента (сегмент кода, сегмент данных, системный сегмент и т. д.). Здесь же можно, например, указать, что данный сегмент доступен только для чтения. Учитывалась также возможность, что сегмент может отсутствовать в памяти, т. е. временно находиться на диске. Тем самым закладывалась возможность виртуальной памяти.
Подытожим, что же давал нам защищенный режим.
Возможность для каждой задачи иметь свою систему сегментов. В микропроцессоре закладывалась возможность быстрого переключения между задачами. Кроме того, предполагалось, что в системе будут существовать сегменты, принадлежащие операционной системе.
Предполагалось, что сегменты могут быть защищены от записи.
В поле доступ можно также указать уровень доступа. Всего возможно четыре уровня доступа. Смысл уровня доступа заключался в том, что задача не может получить доступ к сегменту, у которого уровень доступа выше, чем у данной задачи.
Наконец, в данной схеме была сразу заложена возможность виртуальной памяти, т. е. памяти, формируемой с учетом возможности того, что сегмент может временно храниться на диске. С учетом такой возможности логическое адресное пространство может составлять весьма внушительные размеры.
Обратимся опять к рис.1. Из схемы видно, что результатом преобразования является линейный адрес. Но если для микропроцессора 80286 линейный адрес можно отождествить с физическим адресом, для микропроцессора 80386 это уже не так.
Начиная с микропроцессора 80386 появился еще один механизм преобразования адресов – это страничная адресация. Чтобы механизм страничной адресации заработал, старший бит системного регистра CRO должен быть равен 1.
Обратимся к рис.2. Линейный адрес, получаемый путем дескрипторного преобразования, делится на три части. Старшие 10 бит адреса используются как индекс в таблице, которая называется каталог таблиц страниц. Расположение каталога страниц определяется содержимым регистра CR3. Каталог состоит из дескрипторов. Максимальное количество дескрипторов 1024. Самих же каталогов может быть бесчисленное множество, но в данный момент работает каталог, на который указывает регистр CR3.
Средние
10 бит линейного адреса предназначены
для индексации таблицы страниц, которая
содержит 1024 дескриптора страниц, которые,
в свою очередь, определяют физический
адрес страниц. Размер страницы составляет
4 Кб. Легко сосчитать, какое адресное
пространство может быть охвачено одним
каталогом таблиц страниц. Это составляет
1024*1024*1024*4 байт, т. е. порядка 4 Гбайт.
Младшие 12 бит определяют смещение внутри страницы. Как легко заметить, это как раз составляет 4 Кб (4095 байта). Для каждого процесса должен существовать свой каталог таблиц страниц. Переключение между процессами можно осуществлять посредством изменения содержимого регистра CR3. Однако, это не совсем рационально, так как требует большого объема памяти. В реальной ситуации для переключения между процессами производится изменение каталога таблиц страниц.
Обратимся теперь к структуре дескрипторов страниц (дескриптор таблицы страниц имеет ту же самую структуру).
Биты 12-31 – адрес страницы, который в дальнейшем складывается со смещением, предварительно сдвигаясь на 12 бит.
Биты 9-11 – для использования операционной системой.
Биты 7-8 – зарезервированы и должны быть равны 0.
Бит 6 – устанавливается, если была осуществлена запись в каталог или страницу.
Бит 5 – устанавливается перед чтением и записью на страницу.
Бит 4 – запрет кэширования.
Бит 3 – бит сквозной записи.
Бит 2 – если значение этого бита равно 0, то страница относится к супервизору, если 1, то страница относится к рабочему процессу. Этим устанавливается два уровня доступа.
Бит 1 – если бит установлен, то запись на страницу разрешена.
Бит 0. Если бит установлен, то страница присутствует в памяти. Страницы, содержащие данные сбрасываются на диск и считываются, когда происходит обращение к ним. Страницы, содержащие код, на диск не сбрасываются, но могут подкачиваться из соответствующих модулей на диске. Поэтому память, занятая с этими страницами, также может рационально использоваться.