Document 142778

advertisement
МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РЕСПУБЛИКИ КАЗАХСТАН
КАЗАХСКИЙ НАЦИОНАЛЬНЫЙ ТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ ИМЕНИ
К.И.САТПАЕВА
Институт информационных технологий
Кафедра Программное обеспечение систем и сетей
УЧЕБНО-МЕТОДИЧЕСКИЙ КОМПЛЕКС
ДИСЦИПЛИНЫ СТУДЕНТА
по дисциплине Системное программирование
для специальности 050602 – Информатика
Алматы 2008
Учебно-методический комплекс по дисциплине «Системное программирование» для
студентов КазНТУ имени К.И.Сатпаева по специальности 050602 – Информатика
Составитель: Мустафина Бахытжан Мухамеджановна – ст. преп. кафедры «ПОСиС».
Алматы: Изд-во КазНТУ, 2008 г. – с._______
Аннотация
Дисциплина «Системное программирование» является базовой при изучении
основных принципов и методов программирования в современных ОС. Дается толкование
основных понятий: объекты ядра, процесс, поток, приоритеты, атрибуты безопасности, кучи,
мьютексы, семафоры, события. Основное внимание уделяется системным службам ядра,
включая файловую систему, управление процессами и потоками, взаимодействие между
процессами и синхронизацию. Приводится описание системных функций современных ОС.
Рассматриваются основные свойства наиболее важных функций и показывается как
применять их в реальных программных ситуациях. Даются базовые понятия
основополагающих знаний об основных методах, средствах, принципах программирования в
Win32. Разработанный учебно-методический комплекс дисциплины (УМК ДС) ставит своей
целью обеспечение студентов учебно-методическими материалами для качественного и
эффективного усвоения курса «Системное программирование». УМК включает тезисное
содержание лекций, задания к лабораторным занятиям и СРС, список контрольных вопросов
и задач для домашнего задания, которые приведены в конце каждой темы лекций,
лабораторных заданий и СРС. Для текущего и итогового контроля знаний студентов в УМК
приведены вопросы. Рекомендуемая литература состоит из учебников нового поколения
(2000-2007гг) и методических разработок преподавателя кафедры ПОСиС, написанных на
основе практического опыта преподавания данной дисциплины.
© Казахский национальный технический университет
имени К.И.Сатпаева, 2008
2
1. УЧЕБНАЯ ПРОГРАММА ДИСЦИПЛИНЫ – SYLLABUS
1.1 Данные о преподавателях:
Преподаватель, ведущий занятия
Мустафина Бахытжан Мухамеджановна
ст. преподаватель кафедры ПОСиС
.
Контактная информация _____257 –71 –92________
Время пребывания на кафедре____________________
1.2 Данные о дисциплине:
Название Cистемное программирование
Количество кредитов___3_____________
Место проведения __1010 ГУК_______
курс
семестр
кредиты
Выписка из учебного плана
Академических часов в неделю
лекции
лаборат.
СРС
СРСП
Всего Форма
занятия
контроля
4
5
7
8
9
10
2
1
3
3
9 экзамен
1
2
3
3
5
3
*Примечания:
Каждый кредит сопровождается двумя часами СРС.
СРСП составляет не более 50% от СРС
1.3 Пререквизиты
Дисциплины, необходимые для изучения данной:
- информатика;
- языки и технология программирования.
1.4 Постреквизиты
Дисциплины, в которых используются знания изучаемой дисциплины:
- системное программное обеспечение;
- технологии распределенных систем;
- системы искусственного интеллекта.
1.5 Краткое описание дисциплины
Цели и задачи дисциплины – приобретение студентами основополагающих знаний об
основных теоретических и практических аспектах системного программирования на уровне
разработки программ, позволяющих с наименьшими затратами получать современные
программы со сложной логической структурой.
Задачей
дисциплины
Системное
программирование
является
получение
систематизированных знаний о составе и принципах управления ВМ, системами и сетями, о
назначении составных частей операционных систем, принципах функционирования
различных элементов операционных систем и их взаимодействии, порождении и отработки
процессов в системе.
По завершении изучения дисциплины студент должен:
- отчетливо понимать основные концепции системного программирования;
- уметь разрабатывать программы, охватывающие вопросы системного программного
обеспечения;
1.6 Перечень и виды заданий и график их выполнения
- перечень и виды заданий (тематика курсовых проектов (работ), перечень расчетнографических заданий, типовых расчетов и др.)
- список рекомендуемой литературы;
3
- сроки выполнения;
- формы контроля (тесты, экспресс-опрос, отчет, реферат, доклад и др.).
График составляется тьютором по нижеприведенной форме и доводится до сведения
обучающихся с начала учебного семестра
Виды заданий и сроки их выполнения
Тема работы
Ссылки на
рекомендуемую
литературу с
указанием
страниц
1
2
3
4
Текущий Лабораторная Разработка
Методические
контроль работа №1
консольного
указания к
приложения.
выполнению
лабораторных
работ
Текущий Лабораторная Разработка
Методические
контроль работа №2
пользовательского
указания к
интерфейса.
выполнению
лабораторных
работ
Текущий Лабораторная Использование
Методические
контроль работа №3
ресурсов.
указания к
выполнению
лабораторных
работ
Текущий Лабораторная Управление
Методические
контроль работа №4
файлами.
указания к
выполнению
лабораторных
работ
Текущий Лабораторная Создание
Методические
контроль работа №5
динамических
указания к
библиотек.
выполнению
лабораторных
работ
Текущий Лабораторная Управление
Методические
контроль работа №6
процессами,
указания к
потоками.
выполнению
лабораторных
работ
Текущий Лабораторная Исследование
Методические
контроль работа №7
структуры PEуказания к
формата
выполнению
лабораторных
работ
Рубежный Контрольная
контроль работа
Итоговый Экзамен
Все изученные темы
контроль (устный)
Виды
контроля
Вид
работы
4
Сроки
сдачи
5
1 неделя
3 неделя
5 неделя
7 неделя
10 неделя
12 неделя
14 неделя
8, 15
неделя
1. Виды контроля состоят из текущего и рубежного с указанием их порядкового номера. Не
допускается планирование более двух видов контроля в течение одной недели.
2. К видам работ относятся лабораторные, семестровые работы, коллоквиумы, рефераты,
отчеты, доклады и т.д.
3. При указании темы работы сокращение слов не допускается
4. При заполнении столбца 4 указать порядковый номер литературы и в квадратных скобках
указать нумерацию страницы. (Например, 1осн. [10-13])
1.
2.
3.
4.
5.
6.
7.
8.
9.
1.
2.
3.
1.7 Список литературы
Список основной литературы
Джонсон М. Харт. Системное программирование в среде Win32. – М.: Издательский дом
“Вильямс”, 2001.
Джеффри Рихтер. Windows. Создание эффективных Win32- приложений с учетом
специфики 64-разрядной версии Windows.-СПб., М., Харьков, Минск: “Русская
редакция”, “Питер”, 2001 (Серия: для профессионалов).
Ал Вильямс. Системное программирование в Windows 2000. – СПб.: Питер, 2001.
Пирогов В.Ю. Ассемблер для Windows. - СПб.:БХВ-Петербург, 2005.
Финогенов К.Г. Win32. Основы программирования. - М.: ДИАЛОГ-МИФИ, 2002.
Румянцев П.В. Азбука программирования в Win32 API. – М.: Горячая линия – телеком,
2001.
Румянцев П.В. Работа с файлами в Win32. – М.: Горячая линия – телеком, 2001.
Ганеев Р.М. Проектирование интерфейса пользователя средствами Win32 API. – М.:
Горячая линия – телеком, 2001.
Ричард Саймон. Windows 2000 API. Энциклопедия программиста: Пер. с англ. –
К.:Издательство “ДиаСофт”, 2001.
Список дополнительной литературы
Хелен Кастер. Основы Windows NT и NTFS: Пер. с англ. -М.: Изд. Отдел Русская
редакция «TOO-Channel Trading Ltd», 1996.
Ресурсы Windows NT: Пер с англ. - СПб.: BHV - Санкт-Петербург. 1996.
Джон Д. Рули и др. Сети Windows NT 4.0. Пер. с англ. -Киев: Издательская группа BHV,
1997.
1.8 Контроль и оценка знаний.
№ варианта
Вид итогового
контроля
1.
Экзамен
Виды контроля
%
Итоговый контроль
Рубежный контроль
Текущий контроль
100
100
100
Календарный график сдачи всех видов контроля
по дисциплине «Системное программирование»
2
3
4
5
6
7
8
9
10 11 12
Недели
1
13
14 15
Виды
Л1 СР1 Л2 СР2 Л3 СР3 Л4 РК СР4 Л5 СР5 Л6 СР6 Л7 РК
контроля
Нед.
кол-во
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
конт-ля
Виды контроля: Л – лабораторная работа, СР - самостоятельная работа,
РК – рубежный контроль.
Студент допускается к сдаче итогового контроля при наличии суммарного рейтинга.
Итоговая оценка по дисциплине определяется по шкале.
5
Оценка
Отлично
Хорошо
Удовлетворительно
Неудовлетворительно
Оценка знаний студентов
Буквенный
В процентах %
эквивалент
А
95-100
А90-94
В+
85-89
В
80-84
В75-79
С+
70-74
С
65-69
С60-64
D+
55-59
D
50-54
F
0-49
В баллах
4
3,67
3,33
3,0
2,67
2,33
2,0
1,67
1,33
1,0
0
Перечень вопросов для проведения контроля по модулям и промежуточной аттестации
Вопросы для проведения контроля по 1 модулю:
1. Классическая структура программы для Windows.
2. Плоская модель памяти FLAT. Адресное пространство процесса.
3. Параметры главной функция WinMain().
4. Состав функции WinMain().
5. Сообщения Windows.
6. Консольные приложения.
7. Понятия “процесс” и “поток”.
8. Создание процессов и потоков.
9. Синхронизация процессов и потоков.
10. Цели и средства синхронизации.
11. Приоритеты потоков.
12. Программирование приоритетов
13. Динамическое изменение уровня приоритета потока
14. API функции для работы с виртуальной памятью.
15. API функции для работы с кучей.
Вопросы для проведения контроля по 2 модулю:
1. API функции для работы с отображаемыми в память файлами
2. DLL и адресное пространства процесса.
3. Явная загрузка DLL.
4. Операции, выполняемые системой при вызове потоком функции LoadLibrary
5. Операции, выполняемые системой при вызове потоком функции FreeLibrary
6. Динамическая локальная память потока
7. Файловые системы Win32.
8. Базовые операции с файлами.
9. API функции для работы с файлами.
10. API функции для управления файлами и каталогами.
11. API функции для управления разделами реестра
12. API функции для управления параметрами реестра
13. Блоки Try и Except.
14. API функции, связанные с безопасностью объектов Win32.
15. API функции, обеспечивающие межсетевое взаимодействие.
Вопросы для подготовки к промежуточной аттестации
1. Unicode- ANSI-функции в Windows.
2. Механизмы, позволяющие процессам совместно использовать объекты ядра
3. Параметры функция CreateProcess().
6
4. Что происходит при завершении процесса?
5. Параметры функции CreateThread().
6. Что происходит при завершении потока?
7. Внутреннее устройство потока
8. Приостановка и возобновление потока
9. Структура CONTEXT
10. Стандартная куча процесса.
1.9 Политика и процедура курса
Все виды аудиторных занятий (лекции, лабораторные занятия, СРСП) подлежат
обязательному посещению всеми студентами. В случае пропуска лекции или СРСП по
уважительной причине (что должно быть подтверждено документально) разрешается
переписать содержание лекции или СРСП у студентов группы, а задания и консультации по
пропущенным занятиям получить у преподавателя индивидуально в офисное время. Любые
пропуски лабораторных работ подлежат обязательно отработке в лаборатории. Защита всех
лабораторных работ происходит строго индивидуально по программным кодам
выполненных заданий.
Поскольку вся работа студентов в течении семестра основана на рейтингово-балльной
системе, студент должен своевременно выполнять и защищать работы строго по
календарному графику. Сдача видов контроля осуществляется в той последовательности, как
она логически выстроена и запланирована при изучении курса.
СОДЕРЖАНИЕ АКТИВНОГО РАЗДАТОЧНОГО МАТЕРИАЛА
Тематический план курса
Изучение дисциплины «Системное программирование» предполагает обязательные
лекционные (2 кредита) и лабораторные (1 кредит) занятия, а также самостоятельную работу
студентов (СРС и СРСП) и выполнение курсового проекта с последующей защитой. Такие
комплексные занятия обеспечивают усвоение курса, способствуют приобретению
студентами фундаментальных знаний по новой отрасли программирования.
Тематический план дисциплины с указанием наименований тем и количества
академических часов по всем видам занятий (по темам) приведен в следующей таблице.
2
№
1
1
2
3
4
5
6
7
8
9
10
11
12
Распределение часов по видам занятий
Количество академических часов
Наименование темы
лекци лаборатор СРСП СРС
и
ные
2
3
4
5
6
Введение. Вопросы системного
2
1/2
3
программирования.
Управление процессами
2
2
1/2
3
Потоки и планирование
2
1/2
3
Синхронизация потоков
2
2
1/2
3
Дополнительные методы синхронизации потоков
2
1/2
3
Управление памятью. Использование
2
2
1/2
3
виртуальной памяти
Динамически распределяемая память
2
1/2
3
Отображаемые в память файлы
2
2
1/2
3
Динамические библиотеки
2
1/2
3
Использование файловой системы
2
2
1/2
3
Дополнительные методы работы с файлами и
2
1/2
3
каталогами и реестр
Использование ввода-вывода
2
2
1/2
3
7
13
14
15
Безопасность объектов Win32
Структурная обработка исключений
Межпроцессное взаимодействие
Всего часов
2
2
2
30
3
15
1/2
1/2
1/2
45
3
3
3
45
2.2 Тезисы лекционных занятий
Лекция1. Тема: Введение. Вопросы системного программирования.
1.1 Основы программирования в операционной системе Windows
Рассмотрим два момента, которые крайне важны для начала программирования в
среде Windows — это вызов системных функций (API-функций) и возможные структуры
программ для Windows. Пирогов выделяет шесть типов структур программ, которые условно
можно назвать следующим образом:
– классическая структура — имеет одно главное окно;
– диалоговая структура — главным окном является диалоговое окно;
– консольный тип — главным окном является консольное окно (создаваемое или
наследуемое);
– безоконная структура — это Windows -приложение, не имеющее главного окна;
– сервисы — программы, имеющие специальную структуру и играющие особую роль в
операционной системе;
– драйверы — имеющие особую структуру программы для управления внешними
устройствами.
Рассмотрим первую, классическую структуру.
Итак, начнем с нескольких общих положений о программировании в Windows.
1. Программирование в Windows основывается на использовании функций API (Application
Program Interface, Программный интерфейс приложения). Взаимодействие с внешними
устройствами и ресурсами операционной системы будет происходить посредством таких
функций.
2. Список функций АРI и их описание лучше всего брать из файла WIN32.HLP, который
поставляется, например, с пакетом Borland C++. Подробнейшее описание по функциям
API и по программированию в среде Windows в целом содержится в документации к
Visual Studio.NET.
3. Главным элементом программы в среде Windows является окно. Для каждого окна
определяется своя процедура обработки сообщений.
4. Окно может содержать элементы управления: кнопки, списки, окна редактирования и др.
Эти элементы, по сути, также являются окнами, но обладающими особыми свойствами.
События, происходящие с этими элементами (и самим окном), приводят к приходу
сообщений в процедуру окна (вызов процедуры с определенными параметрами,
определяющими событие).
5. Операционная система Windows использует линейную адресацию памяти. Другими
словами, всю память можно рассматривать как один сегмент.
6. Следствием пункта 5 является то, что мы фактически не ограничены в объеме данных,
кода или стека (объеме локальных переменных). Сегменты в тексте программы
позволяют задать отдельным фрагментам кода (секциям) определенные свойства: запрет
на запись, общий доступ и т. д.
7. Операционная система Windows является многозадачной средой. Каждая задача имеет
свое адресное пространство и свою очередь сообщений. Более того, даже в рамках одной
программы может быть осуществлена многозадачность — любая процедура может быть
запущена как самостоятельная задача.
1.1.1 Вызов функций API
Начнем с того, как можно вызвать функции API. Выберем любую функцию API,
например, MessageBox:
8
int MessageBox (HWND hwnd, LPCTSTR 1pText, LPCTSTR 1pCaption, UINT uType);
Данная функция выводит на экран окно с сообщением и кнопкой (или кнопками)
выхода. Смысл параметров:
hWnd – дескриптор окна, в котором будет появляться окно-сообщение,
lpText – текст, который будет появляться в окне,
lpCaption – текст в заголовке окна,
uType – тип окна, в частности можно определить количество кнопок выхода.
Теперь о типах параметров. Все они в действительности 32-битные целые числа:
HWND – 32-битное целое,
LPCTSTR – 32-битный указатель на строку,
UINT – 32-битное целое.
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. Это происходит только при получении сообщения о
9
выходе (сообщение 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 бит – размер.
10
Кроме глобальной дескрипторной таблицы, предусматривалась еще одна
общесистемная таблица – дескрипторная таблица прерываний (IDT). Она содержит
дескрипторы специальных системных объектов, которые называются шлюзами и определяют
точки входа процедур обработки прерываний и особых случаев. Положение дескрипторной
таблицы прерываний определяется содержимым регистра IDTR, структура которого
аналогична регистру GDTR.
Размер регистра LDTR составляет всего 10 байт. Первые 2 байта адресуют локальную
дескрипторную таблицу не напрямую, а посредством глобальной дескрипторной таблицы, т.
е. играют роль селектора для каждой вновь создаваемой задачи. Таким образом, в
глобальную дескрипторную таблицу должен быть добавлен элемент, определяющий сегмент,
где будет храниться локальная дескрипторная таблица данной задачи. Переключение же
между задачами может происходить всего лишь сменой содержимого регистра LDTR.
Отсюда, кстати, вытекает то, что, если задача одна собирается работать в защищенном
режиме, ей незачем использовать локальные дескрипторные таблицы и регистр LDTR.
Логический адрес
Селектор (16 бит)
сегментные регистры
Смещение (32 бита)
регистры EBX,ESI,EDI,EBP,ESP
32 бита
Таблица базовых
адресов
GDT либо LDT
32 бита
+
Линейный адрес
Рис.1.– Схема преобразования логического адреса в линейный адрес в
защищенном режиме адресации
Дескриптор сегмента содержал, в частности, поле доступа, которое определяло тип
индексируемого сегмента (сегмент кода, сегмент данных, системный сегмент и т. д.). Здесь
же можно, например, указать, что данный сегмент доступен только для чтения. Учитывалась
также возможность, что сегмент может отсутствовать в памяти, т. е. временно находиться на
диске. Тем самым закладывалась возможность виртуальной памяти.
Подытожим, что же давал нам защищенный режим.
1. Возможность для каждой задачи иметь свою систему сегментов. В
микропроцессоре закладывалась возможность быстрого переключения между
задачами. Кроме того, предполагалось, что в системе будут существовать
сегменты, принадлежащие операционной системе.
2. Предполагалось, что сегменты могут быть защищены от записи.
3. В поле доступ можно также указать уровень доступа. Всего возможно четыре
уровня доступа. Смысл уровня доступа заключался в том, что задача не может
получить доступ к сегменту, у которого уровень доступа выше, чем у данной
задачи.
11
4. Наконец, в данной схеме была сразу заложена возможность виртуальной памяти,
т. е. памяти, формируемой с учетом возможности того, что сегмент может
временно храниться на диске. С учетом такой возможности логическое адресное
пространство может составлять весьма внушительные размеры.
Обратимся опять к рис.1. Из схемы видно, что результатом преобразования является
линейный адрес. Но если для микропроцессора 80286 линейный адрес можно отождествить с
физическим адресом, для микропроцессора 80386 это уже не так.
Начиная с микропроцессора 80386 появился еще один механизм преобразования
адресов – это страничная адресация. Чтобы механизм страничной адресации заработал,
старший бит системного регистра CRO должен быть равен 1.
Обратимся к рис.2. Линейный адрес, получаемый путем дескрипторного
преобразования, делится на три части. Старшие 10 бит адреса используются как индекс в
таблице, которая называется каталог таблиц страниц. Расположение каталога страниц
определяется содержимым регистра CR3. Каталог состоит из дескрипторов. Максимальное
количество дескрипторов 1024. Самих же каталогов может быть бесчисленное множество, но
в данный момент работает каталог, на который указывает регистр CR3.
Линейный адрес
10 бит
Каталог
таблиц
страниц
10 бит
Выбор
страницы
12 бит
Таблица
страниц
+
CR3
Физический
адрес
Рис.2 – Преобразование линейного адреса в физический с
учетом страничной адресации
Средние 10 бит линейного адреса предназначены для индексации таблицы страниц,
которая содержит 1024 дескриптора страниц, которые, в свою очередь, определяют
физический адрес страниц. Размер страницы составляет 4 Кб. Легко сосчитать, какое
адресное пространство может быть охвачено одним каталогом таблиц страниц. Это
составляет 1024*1024*1024*4 байт, т. е. порядка 4 Гбайт.
Младшие 12 бит определяют смещение внутри страницы. Как легко заметить, это как
раз составляет 4 Кб (4095 байта). Для каждого процесса должен существовать свой каталог
таблиц страниц. Переключение между процессами можно осуществлять посредством
изменения содержимого регистра CR3. Однако, это не совсем рационально, так как требует
12
большого объема памяти. В реальной ситуации для переключения между процессами
производится изменение каталога таблиц страниц.
Обратимся теперь к структуре дескрипторов страниц (дескриптор таблицы страниц
имеет ту же самую структуру).
Биты 12-31 – адрес страницы, который в дальнейшем складывается со смещением,
предварительно сдвигаясь на 12 бит.
Биты 9-11 – для использования операционной системой.
Биты 7-8 – зарезервированы и должны быть равны 0.
Бит 6 – устанавливается, если была осуществлена запись в каталог или страницу.
Бит 5 – устанавливается перед чтением и записью на страницу.
Бит 4 – запрет кэширования.
Бит 3 – бит сквозной записи.
Бит 2 – если значение этого бита равно 0, то страница относится к супервизору, если
1, то страница относится к рабочему процессу. Этим устанавливается два уровня доступа.
Бит 1 – если бит установлен, то запись на страницу разрешена.
Бит 0. Если бит установлен, то страница присутствует в памяти. Страницы,
содержащие данные сбрасываются на диск и считываются, когда происходит обращение к
ним. Страницы, содержащие код, на диск не сбрасываются, но могут подкачиваться из
соответствующих модулей на диске. Поэтому память, занятая с этими страницами, также
может рационально использоваться.
1.2.2 Адресное пространство процесса.
В предыдущем разделе мы говорили о страничной и сегментной адресации. Как же
эти две адресации уживаются в Windows? Оказывается, все очень просто. В сегментные
регистры загружаются селекторы, базовые адреса которых равны нулю, а размер сегмента
составляет 4 гигабайта. После этого о существовании сегментов и селекторов можно забыть,
хотя для микропроцессора этот механизм по-прежнему работает. Основным же механизмом
формирования адреса становятся страничные преобразования. Такая модель памяти и
называется плоской (FLAT). Логическая адресация в такой модели определяется всего одним
32-битным смещением. Наши программы пишутся в плоской модели памяти. При этом вся
область памяти, адресуемая 32-битным адресом, находится в нашем распоряжении. Только
адрес этот является логическим адресом, который, в свою очередь, подвергается
страничному преобразованию, а вот в какую физическую ячейку памяти он попадает,
ответить уже весьма затруднительно.
На рис.3. представлено логическое адресное пространство процесса. Особо обратите
внимание на разделенные (совместно используемые) области памяти (области 2,4,5). Что это
значит? А значит это только одно: эти области памяти проецируются на одно и тоже
физическое пространство.
Рассмотрим назначение областей по порядку.
- Область 1. Эта область заблокирована. Она предназначена для выявления нулевых
указателей. Особенно это относится к языку Си, где функция malloc может возвращать
нулевой указатель (т.е. NULL). Попытка записать по этому адресу приведет к
соответствующему сообщению об ошибке операционной системы.
- Область 2. Эта область пространства использовалась в операционных системах
серии Windows 9х. В операционных системах семейства Windows NT она входит в область 3.
Для DOS и 16-битных приложений здесь отводится свое адресное пространство.
Следующая область адресного пространства (область 3), между 4 Мбайт и 2 Гбайт (в
Windows 2000 и выше область начинается с 1 Мбайт), является адресным пространством
процесса. Процесс занимает эту область пространства под код, данные, а также специфичные
для него динамические библиотеки. Это неразделяемая область. Есть, однако, исключения.
Можно определить отдельные разделяемые секции. Это значит, что некоторые страницы из
этого логического пространства будут отображаться в одну физическую область у разных
процессов.
13
Область 4. Закрытый раздел, используемый для внутренней реализации операционной
системы.
Область 5 содержит в себе файлы, отображаемые в память, системные динамические
библиотеки, а также динамическую память для 16-битных приложений. Для операционной
системы Windows 2000 и выше эта область входит в следующую шестую область.
Область 6. Последняя часть адресного пространства отведена под системные
компоненты. Удивительно, но в Windows 9х эта область не защищена от доступа обычных
программ. В операционных системах семейства NT эта область недоступна исполняемым
процессам.
4 Гбайт
Код операционной системы, в том числе
драйверы устройств
6
3 Гбайт
Системные DLL
Файлы, отображаемые в памяти
Область для динамической памяти
16 битных приложений
2 Гбайт
5
Закрытый раздел 64 Кбайт
4
Память текущего процесса
3
4 Мбайт
Область динамической памяти
16-битных процессов
и операционная система MS DOS
2
1 Мбайт
Область памяти для выявления нулевых
указателей
1
0
Рис.3. – Адресное пространство процесса.
Основная литература: [4] – 31-54, 551 - 559 c.
Контрольные вопросы:
1. Структура программы под Windows
2. В каком случае происходит выход из цикла обработки сообщений?
3. Виды дескрипторных таблиц
4. Какую информацию включает адресное пространство процесса?
Лекция 2. Тема: Управление процессами
2.1 Управление процессами
Процесс содержит собственное независимое виртуальное адресное пространство с
кодом и данными, защищенными от других процессов. Каждый процесс, в свою очередь,
включает в себя один или более независимо выполняющихся потоков. Процесс может
создавать внутри себя новые потоки и новые независимые процессы, а также управлять
сообщением и синхронизацией объектов.
Создавая процессы и управляя ими, приложения могут одновременно выполнять
задачи по обработке файлов, производить вычисления и взаимодействовать с другими
14
системами в сети. Возможно даже использование нескольких процессоров для ускорения
обработки.
2.2 Процессы и потоки в Windows
Каждый процесс содержит один или более потоков. Поток в Windows— основная
единица выполнения. Планирование потоков проводится на основе обычных факторов:
доступности ресурсов, таких как процессоры и физическая память, приоритетов,
справедливости распределения ресурсов и т.д. Windows 2000 и NT поддерживают симметричную многопроцессорную обработку, поэтому потоки могут распределяться по
отдельным процессорам.
С точки зрения программиста, каждый процесс в Win32 включает компоненты,
перечисленные ниже.
• Один или несколько потоков.
• Виртуальное адресное пространство, отличное от адресных пространств других
процессов, за исключением случаев явного разделения памяти. Отметим, что
отображаемые в память файлы разделяют физическую память, но разные процессы для
доступа к отображенному файлу будут использовать разные виртуальные адреса.
• Один или более сегментов кода, включая код DLL.
• Один или более сегментов данных, содержащих глобальные переменные.
• Строки окружения с информацией о переменных окружения, таких как текущий путь
поиска.
• Память кучи процесса.
• Такие ресурсы, как открытые дескрипторы и другие кучи.
Все потоки процесса совместно используют код, глобальные переменные, строки
окружения и ресурсы. Каждый поток планируется независимо. Поток включает описанные
ниже элементы.
• Стек вызовов процедур, прерываний, обработчиков исключений и автоматических
данных.
• Локальная память потока (Thread Local Storage — TLS) — массивы указателей, которые
дают процессу возможность выделять память для создания собственного уникального
окружения данных.
• Параметр стека, полученный от потока, создавшего данный, и обычно уникальный для
каждого потока.
• Структура контекста, управляемая ядром, со значениями аппаратных регистров.
На рис. 2.1 показан процесс с несколькими потоками. Это схематическое изображение, на котором не показаны действительные адреса памяти и которое не отражает
масштаб.
2.3 Создание процессов
Функция CreateProcess — основная функция для управления процессами в Win32. Она
создает процесс с одним потоком. Так как процесс требует наличия кода, в вызове функции
CreateProcess необходимо указывать имя исполняемого файла программы.
Часто принято говорить о родительских и дочерних процессах, но такие отношения в
действительности не поддерживаются в Win32. Просто удобно называть процесс, который
порождает дочерний процесс, родительским.
Функция CreateProcess для обеспечения гибкости и мощности имеет десять параметров. На первый случай можно просто использовать их значения по умолчанию.
В указанную при вызове структуру возвращаются два дескриптора: для процесса и
для потока. Функция CreateProcess создаст новый процесс с первичным потоком. В примерах
программ оба дескриптора, когда потребность в них исчезает, всегда аккуратно закрываются
во избежание утечки ресурсов. Например, если перед закрытием дескриптора потока не
завершить этот поток, функция CloseHandle лишь удалит ссылку на поток.
BOOL CreateProcess ( LPCTSTR lpszImageName, LPTSTR
lpszCommandLine, LPSECUR ITY_ATTRIBUTES lpsaProcess,
15
LPSECUR ITY_ATTRIBUTES lpsaThread, BOOL fInheritHandles,
DWORD fdwCreate, LPVOID lpvEnvironment, LPCTSTR lpszCurDir,
LPSTARTUP INFO lpsiStart Info, LPPROCESS_INFORM ATION lppiProcInfo);
Возвращаемое значение: TRUE только в случае, если процесс и поток были успешно
созданы.
Процесс
Код
Глобальные переменные
Память кучи процесса
Ресурсы процесса
Открытые файлы
Кучи
…
Блок окружения
Поток 1
Поток N
Локальная область памяти потока
Локальная область памяти потока
Стек
Стек
Рис. 2.1. Процесс и его потоки
Параметры
lpszImageName и lpszCommandLine (последний имеет тип LPTSTR, а не LPCTSTR)
указывают исполняемую программу и параметры командной строки, как показано в
следующем разделе.
lpsaProcess и lpsaThread указывают на структуры атрибутов безопасности процесса и
потока. В случае если их значение NULL, используются атрибуты по умолчанию.
Параметр fInheritHandles определяет, должен ли новый процесс наследовать копии
открытых наследуемых дескрипторов (файлов, отображений и т.д.) процесса, вызвавшего
функцию. Унаследованные дескрипторы имеют те же атрибуты, что и их оригиналы.
В параметре fdwCreate можно объединять несколько флагов.
• CREATE_SUSPENDED — первичный поток находится в состоянии ожидания и будет запущен только после вызова функции ResumeThread.
• DETACHED_PROCESS
и CREATE_NEW_CONSOLE — взаимоисключающие
флаги; нельзя установить оба сразу. Первый флаг создает процесс без консоли, а второй
предоставляет консоль новому процессу. Если ни один из этих флагов не установлен,
процесс наследует консоль родителя.
• CREATE_NEW_PROCESS_GROUP определяет новый процесс как корневой для новой группы процессов. Если процессы группы совместно используют одну консоль, то все
они получают сигнал управления консолью (<Ctrl+C > или < Ctrl+Break >).
16
Некоторые флаги управляют приоритетом потоков нового процесса. Пока просто
будем использовать приоритет родительского процесса (не указывая ничего) или значение
NORMAL_PRIORITY_CLASS.
lpvEnvironment указывает блок окружения для нового процесса. Если этот параметр
имеет значение NULL, то используется окружение родительского процесса. Блок окружения
содержит строки имен и значений, такие как путь поиска.
lpszCurDir определяет диск и каталог для нового процесса. Если его значение NULL, то
будет использован рабочий каталог родительского процесса.
lpsiStartInfo указывает вид основного окна программы и дескрипторы стандартных
устройств для нового процесса. Используйте информацию родителя, полученную вызовом
функции GetStartupInfo. Перед вызовом функции CreateProcess структуру STARTUPINFO
можно также обнулить, а затем для указания стандартных дескрипторов ввода, вывода и
сообщений об ошибках установить стандартные значения соответствующих полей (hStdIn,
hStdOut и hStdErr) структуры STARTUPINFO. Для того чтобы это сработало, присвойте
другому
полю
структуры
STARTUPINFO
—
dwFlags
—
значение
STARTF_USESTDHANDLES, а затем установите все дескрипторы, необходимые новому
процессу. Убедитесь, что дескрипторы могут быть унаследованы и в функции CreateProcess
установлены флаги fInheritProcess.
Параметр lppiProcInfo определяет структуру, в которую будут помещены дескрипторы и идентификаторы для созданных процесса и потока. Структура
PROCESS_INFORMATION определена так:
typedef struct _PROCESS_INFORMATION { HANDLE hProcess;
HANDLE hThread; DWORD dwProcessId; DWORD dwThreadId;
} PROCESS_INFORMATION;
Для чего же процессам и потокам нужны дескрипторы в дополнение к идентификаторам (ID)? В течение всего времени существования объекта его идентификатор уникален
для всех процессов, тогда как данный процесс может иметь несколько дескрипторов,
содержащих различные атрибуты, например права доступа. Поэтому одним функциям
управления процессами требуются идентификаторы, а другим — дескрипторы. Кроме того,
дескриптор процесса необходим функциям общего назначения, работа которых основана на
использовании дескрипторов. Примечание: как и в случае дескрипторов файлов,
дескрипторы процессов и потоков должны быть закрыты после использования.
Дополнительное примечание, новый процесс получает переменные окружения,
рабочий каталог и другую информацию при вызове функции CreateProcess. Как только этот
вызов выполнен, любые изменения в родительском процессе уже не влияют на дочерний
процесс. Например, родитель после вызова функции CreateProcess может изменить свой
рабочий каталог, но рабочий каталог дочернего процесса не изменится, если только сам
дочерний процесс не изменит его. Эти два процесса полностью независимы.
2.4 Определение исполняемого образа и командной строки
Параметры lpszImageName и lpszCommandLine определяют имя исполняемого образа,
руководствуясь приведенными ниже правилами.
• Если значение параметра lpszImageName не NULL, то это имя исполняемого файла. В
случае если оно содержит пробелы, можно использовать кавычки.
• В противном случае имя исполняемого файла — первый элемент параметра
lpszCortmandLine.
Обычно указывают только параметр lpszComanandLine, присваивая параметру
lpszImageName значение NULL. Тем не менее существуют подробные правила для параметра
lpszImageName.
• Если параметр lpszImageName имеет значение не NULL то он указывает имя загрузочного
файла. Указывайте полный путь и имя файла. Можете указать только имя файла, тогда
будут использованы текущие диск и каталог без дополнительного поиска. В имя файла
включайте его расширение ( . ЕХЕ, . ВАТ и т.п.).
17
• Если параметр lpszImageName имеет значение NULL то за имя программы принимается
первый элемент параметра lpszCommandLine, отделенный пробелом. Если это имя не
содержит полного пути, происходит поиск в определенной последовательности.
1. Каталог образа текущего процесса.
2. Текущий каталог.
3. Системный каталог Windows, полученный посредством вызова функции
4. Каталог
Windows,
полученный
посредством
вызова
функции
5. Каталоги, указанные в переменной окружения РАТН.
Новый процесс может получить командную строку, используя обычный механизм
argv, или выполнить функцию GetCommandLine для получения всей командной строки в
одной строковой переменной.
Отметим, что командная строка не является константой. Это следует из того факта,
что параметры argv для главной программы тоже не константы. Программа может изменять
свои параметры, поэтому желательно делать изменения в копии строки параметров.
Не требуется создавать новый процесс с таким же определением переменной препроцессора UNICODE, что и родительский. Возможны все комбинации. Для разработки переносимого кода полезно использовать макрос _tmain.
2.5 Идентификация процессов
Процесс может получить идентификатор и дескриптор нового дочернего процесса из
структуры PROCESS_INFORMATION. Закрытие дескриптора дочернего процесса, конечно, не
уничтожает его, таким образом только уничтожается возможность доступа к нему
родительского процесса. Для получения описания текущего процесса используется пара
функций.
HANDLE GetCurrentProcess (VOID)
DWORD GetCurrentProcessId (VOID)
Функция GetCurrentProcess в действительности возвращает псевдодескриптор,
который не может быть унаследован. Это значение используется в случаях, когда процессу
требуется собственный дескриптор. Создавайте настоящий дескриптор процесса из его ID,
используя в вызове функции OpenProcess значение, возвращенное функцией
GetCurrentProcessId .
HANDLE OpenProcess ( DWORD fdwAccess, BOOL fInherit,
DWORD IDProcess)
Возвращаемое значение: дескриптор процесса или NULL в случае ошибки.
Параметры
fdwAccess определяет параметры доступа процесса к дескриптору. Приведем некоторые из его возможных значений.
• Флаг SYNCHRONIZE разрешает другим процессам ждать завершения этого процесса,
используя функции ожидания, которые описаны в этой главе ниже.
• PROCESS_ALL_ACCESS — установлены все флаги доступа.
• Флаг PROCESS_TERMINATEделает возможным завершение процесса вызовом
функции TerminateProcess.
• Флаг PROCESS_QUERY_INFORMATION разрешает использовать дескриптор функциями
GetExitCodeProcess и GetPriorityClass для получения информации о процессе. .,
fInherit определяет, может ли новый дескриптор быть унаследован. IDProcess
содержит идентификатор процесса, которому необходим дескриптор. И наконец,
выполняющийся процесс может определить имя своего загрузочного файла с помощью
функции GetModuleFileName, используя в качестве параметра hModule указатель NULL.
Вызов этой функции из динамической библиотеки возвратит \ имя файла DLL, а не файла .
ЕХЕ, используемого библиотекой.
18
Копирование дескрипторов
Родительскому и дочернему процессам может потребоваться различный доступ к
объекту, который определяется дескриптором, унаследованным дочерним процессом. Также
процессу может потребоваться его настоящий наследуемый дескриптор вместо
псевдодескриптора, создаваемого функцией GetCurrentProcess для использования дочерним
процессом. Чтобы решить эту проблему, родительский процесс может создать копию
дескриптора с необходимыми правами доступа и видом наследования. Ниже представлена
функция для копирования дескрипторов.
BOOL DuplicateHandle( HANDLE hSourceProcess, HANDLE hSource,
HANDLE hTargetProcess, LPHANDLE lphTarget,
DWORD fdwAccess, BOOL fInherit, DWORD fdwOptions);
После выполнения функции указатель lphTarget ссылается на копию исходного
дескриптора hSource. Параметр hSource содержит дескриптор процесса, определяемого
параметром hSourceProcess, и должен иметь права доступа PROCESS_DUP_HANDLE. Новый
дескриптор, на который указывает переменная lphTarget, является действительным в целевом
процессе hTargetProcess. Включая процесс, вызвавший функцию, здесь использованы три
процесса. Вызывающий функцию процесс часто является также целевым и исходным, а
дескриптор получают от функции GetCurrentProcess. Отметим, что возможно создание
дескриптора в другом процессе; если вы это сделаете, то понадобится механизм для того,
чтобы проинформировать его о новом экземпляре дескриптора.
Функция DuplicateHandle может быть использована для любого типа дескриптора.
Если параметр fdwAccess не подавлен флагом DUPLICATE_SAME_ACCESS в переменной
fdwOptions, то он может принимать множество значений (см. встроенную справку MSDN).
Значение параметра fdwOptions может быть любой комбинацией следующих флагов:
• DUPLICATE_CLOSE_SOURCE — исходный дескриптор будет закрыт;
• DUPLICATE_SAME_ACCSESS— параметр fdwAccess будет проигнорирован.
Далее необходимо научиться определять, был ли процесс завершен.
Выход из процесса и его завершение
После того как процесс закончен, он вызывает функцию ExitProcess с кодом
завершения.
Эта функция ничего не возвращает, вызвавший ее процесс и все его потоки завершаются.
Для определения кода завершения другой процесс может использовать функцию
GetExitCodeProcess.
BOOL GetExitCodeProcess ( HANDLE hProcess,
LPDWORD lpdwExitCode);
Процесс, определенный переменной hProcess, должен иметь права доступа
PROCESS_QUERY_INFORMATION (см. функцию OpenProcess). Параметр lpdwExitCode
указывает на переменную типа DWORD, в которую будет помещено значение. Одно из
возможных значений — STILL_ACTIVE; оно указывает, что процесс не был завершен.
Один процесс может завершить другой, если его дескриптор имеет флаг доступа
PROCESS_TERMINATE. Функция, завершающая процесс, также определяет код выхода из
процесса.
BOOL TerminateProcess ( HANDLE hProcess, UINT uExitCode)
Ожидание завершения процесса
Простейший и наиболее ограниченный способ синхронизации с другим процессом
состоит в ожидании его полного завершения. Здесь представлены функции общего
назначения Win 32 для ожидания, обладающие некоторыми интересными свойствами.
• Эти функции могут быть использованы для большого числа различных типов
объектов; дескрипторы процессов — это лишь первое их применение.
•
Функции могут ожидать завершения одного процесса, первого из нескольких
указанных или всех процессов в группе.
19
• Для них может быть использована рекомендательная блокировка по времени.
Ниже показаны две функции ожидания общего назначения, которые наиболее часто
будут использоваться далее.
Параметр lphObjects указывает на дескриптор одного процесса (типа hObject) или на
массив различных объектов. Значение параметра cObjects (размер массива) не должно
превышать константу MAXIMUM_WAIT_OBJECTS (в настоящее время ее значение 64
определено в заголовочном файле WINNT.H).
Основная литература: [1] – 167 - 179 c.
Контрольные вопросы:
1. На основе каких факторов проводится планирование потоков?
2. Какие компоненты включает каждый процесс в Win32?
3. Какие элементы включает поток?
4. Параметры API-функции OpenProcess().
Лекция 3. Тема: Потоки и планирование
Поток (thread) определяет последовательность исполнения кода в процессе. При
инициализации процесса система всегда создает первичный поток. Большинство
приложений обходится единственным, первичным потоком. Однако процессы могут
создавать дополнительные потоки, что позволяет им эффективнее выполнять свою работу.
Каждый поток начинает выполнение с некоей входной функции. В первичном потоке
таковой является WinMain. Если необходимо создать вторичный поток, в нем тоже должна
быть входная функция.
Функция потока может выполнять любые задачи. Рано или поздно она закончит свою
работу и вернет управление. В этот момент поток остановится, память, отведенная под его
стек, будет освобождена, а счетчик пользователей его объекта ядра «поток» уменьшится на
1. Когда счетчик обнулится. этот объект ядра будет разрушен.
Функция потока должна возвращать значение, которое будет использоваться как код
завершения потока. Функции потоков должны по мере возможности обходиться своими
параметрами и локальными переменными.
3.1 Создание потока. Функция CreateThread
HANDLE CreateThread ( PSECUR ITY_ATTRIBUTES psa;
DWORD cbStack; PTHREAD_START_ROUTINE pfnStartAddr;
PVOID pvParam; DWORD fdwCreate; PDWORD pdwThreadID);
При каждом вызове этой функции система создает объект ядра «поток». Это не сам
поток, а компактная структура данных, которая используется операционной системой для
управления потоком и хранит статистическую информацию о потоке. Система выделяет
память под стек потока из адресного пространства процесса. Новый поток выполняется в
контексте того же процесса, что и родительский поток. Поэтому он получает доступ ко всем
описателям объектов ядра, всей памяти и стекам всех потоков в процессе. За счет этого
потоки в рамках одного процесса могут легко взаимодействовать друг с другом.
Параметры
psa - является указателем на структуру SECURITY_ATTRIBUTES.
cbStack - этот параметр определяет, какую часть адресного пространства поток сможет
использовать под свой стек.
pfnStartAddr и pvParam - параметр pfnStartAddr определяет адрес функции потока, с
которой должен будет начать работу создаваемый поток, а параметр pvParam идентичен
параметру pvParam функции потока.
20
fdwCreate - этот параметр определяет дополнительные флаги, управляющие созданием
потока (0 – исполнение потока начинается немедленно; CREATE_SUSPENDED – система
создает поток, инициализирует его и приостанавливает до последующих указаний).
pdwThreadID - это адрес переменной типа DWORD, в которой функция возвращает
идентификатор, приписанный системой новому потоку
3.2. Завершение потока
Поток можно завершить четырьмя способами:
- функция потока возвращает управление (рекомендуемый способ);
- поток самоуничтожается вызовом функции ExitThread (нежелательный способ);
- один из потоков данного или стороннего процесса вызывает функцию TerminateThread
(нежелательный способ);
- завершается процесс, содержащий данный поток (тоже нежелательно).
Функцию потока следует проектировать так, чтобы поток завершался только после
того, как она возвращает управление.
Поток можно завершить принудительно, вызвав:
VOID ExitThread ( DWORD dwExitCode );
При этом освобождаются все ресурсы операционной системы, выделенные данному
потоку. В параметр dwExitCode Вы помещаете значение, которое система рассматривает как
код завершения потока.
Вызов следующей функции также завершает поток:
BOOL TeminateThread ( HANDLE hThread, DWORD dwExitCode ) ;
В отличие от ExitThread, которая уничтожает только вызывающий поток, эта функция
завершает поток, указанный в параметре hThread. После того как поток будет уничтожен,
счетчик пользователей его объекта ядра «поток» уменьшится на 1.
3.3 Другие функции работы с потоками
Потоки часто обращаются к API-функциям, которые меняют среду выполнения.
Например, потоку может понадобиться изменить свой приоритет или приоритет процесса. В
Windows предусмотрены функции, позволяющие легко ссылаться на объекты ядра текущего
процесса и потока: HANDLE GetCurrentProcess(), HANDLE GetCurrentThread().
Поток может запросить все временные показатели своего процесса, вызвав
GetProcessTimes:
GetProcessTimes(GetCurrentProcess(), &ftCreationTime, &ftExitTime,
&ftKernelTime, &ftUserTime);
Аналогичным образом поток может выяснить собственные временные показатели,
вызвав GetThreadTimes:
GetThreadTimes(GetCurrentThread(),&ftCreationTime , &ftExitTime,
&ftKernelTime, &ftUserTime);
Функции, с помощью которых поток может выяснить такой идентификатор собственный или своего процесса:
DWORD GetCurrentProcessId, DWORD GetCurrentThreadId .
Приостановка и возобновление потоков
В объекте ядра «поток» имеется переменная счетчик числа простоев данного потока.
При вызове CreateProcess или CreateThread он инициализируется значением, равным 1,
которое запрещает системе выделять новому потоку процессорное время.
После того как поток полностью инициализирован, CreateProcess или
CreateThread проверяет, не передан ли ей флаг CREATE_SUSPENDED, и, если да,
возвращает управление, оставив поток в приостановленном состоянии.
HANDLE OpenThread ( DWORD dwDesiredAccess, BOOL bInheritHandle,
21
DWORD dwThreadID).
Функция находит объект ядра «поток» по идентификатору, указанному в
dwThreadID, увеличивает его счетчик пользователей на 1 и возвращает описатель объекта.
Получив описатель, можно передать его в SuspendThread (или ResumeThread ).
Функция Sleep. Поток может сообщить системе не выделять ему процессорное время
на определенный период, вызвав:
void Sleep(DWORD dwMilliseconds).
Эта функция приостанавливает поток на dwMilliseconds миллисекунд. Вызывая
Sleep, поток добровольно отказывается от остатка выделенного ему кванта времени.
Система прекращает выделять потоку процессорное время на период, примерно
равный заданному
Переключение потоков
Функция SwitchToThread позволяет подключить к процессору другой поток (если
он есть): BOOL SwitchToThread().
Система проверяет, есть ли поток, которому не хватает процессорного времени. Если
нет, SwitchToThread немедленно возвращает управление, а если да, планировщик отдает
ему дополнительный квант времени (приоритет этого потока может быть ниже, чем у
вызывающего). По истечении этого кванта планировщик возвращается в обычный режим
работы.
SwitchToThread позволяет потоку, которому не хватает процессорного времени,
отнять этот ресурс у потока с более низким приоритетом. Она возвращает FALSE, если на
момент ее вызова в системе нет ни одного потока, готового к исполнению; в ином случае —
ненулевое значение.
Определение периодов выполнения потока
BOOL GetThreadTimes(HANDLE hThread, PFILETIME pftCreationTime,
PFILETIME pftExitTime, PFILETIME pftKernelTime,
PFILETME pftUserTime)
3.4 Структура CONTEXT
В структуре CONTEXT хранятся данные о состоянии регистров с учетом специфики
конкретного процессора. Она используется системой для выполнения различных внутренних
операций. Ее элементы четко соответствуют регистрам процессора.
Windows позволяет заглянуть внутрь объекта ядра «поток» и получить сведения о
текущем состоянии регистров процессора. Для этого предназначена функция:
BOOL GetThreadContext(HANDLE hThread, PCONTEXT pContext).
3.5 Приоритеты потоков
Каждому потоку присваивается уровень приоритета от 0 (самый низкий) до 31
(самый высокий). Пока в системе имеются планируемые потоки с приоритетом 31, ни один
поток с более низким приоритетом процессорного времени не получает. Такая ситуация
называется «голоданием» (starvation). Система всегда старается, чтобы процессоры были
загружены работой, и они простаивают только в отсутствие планируемых потоков.
Потоки с более высоким приоритетом всегда вытесняют потоки с более низким
приоритетом независимо от того, исполняются последние или нет.
При загрузке системы создается особый поток, поток обнуления страниц (zero page
thread), которому присваивается нулевой уровень приоритета. Он обнуляет свободные
страницы в оперативной памяти при отсутствии других потоков, требующих внимания со
стороны системы.
Windows поддерживает шесть классов приоритета: idle (простаивающий), below
normal (ниже обычного), normal (обычный), above normal (выше обычного), high
22
(высокий) и realtime (реального времени). Самый распространенный класс приоритета,
естественно, normal; его использует 99% приложений.
REAL_TIME - потоки в этом процессе обязаны немедленно реагировать на события,
обеспечивая выполнение критических по времени задач. Такие потоки вытесняют даже
компоненты операционной системы.
HIGH - потоки в этом процессе тоже должны немедленно реагировать па события,
обеспечивая выполнение критических по времени задач. Этот класс присвоен, например.
Task Manager, что дает возможность пользователю закрывать больше неконтролируемые
процессы
ABOVE_NORMAL - класс приоритета, промежуточный между NORMAL и HIGH.
NORMAL потоки в этом процессе не предъявляют особых требований к выделению
им процессорного времени.
BELOW_NORMAL - класс приоритета, промежуточный между normal и idle
IDLE - потоки в этом процессе выполняются, когда система не занята другой работой.
Этот класс приоритета обычно используется для утилит, работающих в фоновом режиме,
экранных наставок и приложений, собирающих статистическую информацию.
Относительный приоритет потока
TIME_CRITICAL - поток выполняется с приоритетом 31 в классе REAL_TIME и с
приоритетом 15 в других классах.
HIGHEST - поток выполняется с приоритетом на два уровня выше обычного для
данного класса.
ABOVE_NORMAL - поток выполняется с приоритетом на один уровень выше
обычного для данного класса.
NORMAL поток выполняется с обычным приоритетом процесса
для данного класса.
BELOW_Normal - поток выполняется с приоритетом на один уровень ниже обычного
для данного класса.
LOWEST - поток выполняется с приоритетом на два уровня ниже обычного для
данного класса.
IDLE - поток выполняется с приоритетом 16 в классе REAL_TIME и с приоритетом 1
в других классах.
Итак, Вы присваиваете процессу некий класс приоритета и можете изменять
относительные приоритеты потоков в пределах процесса.
В общем случае поток с высоким уровнем приоритета должен быть активен как
можно меньше времени. При появлении у него какой-либо работы он тут же получает
процессорное время. Выполнив минимальное количество команд, он должен снова вернуться
в ждущий режим. С другой стороны, поток с низким уровнем приоритета может оставаться
активным и занимать процессор довольно долго.
В системе предусмотрена возможность изменения класса приоритета самим
выполняемым процессом вызовом функции SetPriorit yClass.
BOOL SetPriorit yClass( HANDLE hProcess, D WORD fdwPriorit y).
Эта функция меняет класс приоритета процесса, определяемого описателем
hРгоcess, в соответствии со значением параметром fdwPriorit y.
Парная ей функция GetPriorit yClass позволяет узнать класс приоритета любого
процесса: DWORD GetPriorit yC lass(HANDLE hProcess ).
Динамическое изменение уровня приоритета потока
Уровень приоритета, получаемый комбинацией относительного приоритета потока и
класса приоритета процесса, которому принадлежит данный поток, называют базовым
23
уровнем приоритета потока. Иногда система изменяет уровень приоритета потока. Обычно
это происходит в ответ на некоторые события, связанные с вводом-выводом.
Процессор исполняет поток в течение отведенного отрезка времени, а по его
истечении система снижает приоритет потока на 1, до уровня 14. Далее потоку вновь
выделяется квант процессорного времени, по окончании которого система опять снижает
уровень приоритета потока на 1. И теперь приоритет потока снова соответствует его
базовому уровню.
Текущий уровень приоритета не может быть ниже базового. Кроме того, драйвер
устройства, «разбудивший» поток, сам устанавливает величину повышения приоритета.
Система повышает приоритет только тех потоков, базовый уровень которых находится в
пределах 1-15. Именно поэтому данный диапазон называется «областью динамического
приоритета» (dynamic priorit y range ). Система не допускает динамического повышения
приоритета потока до уровней реального времени (более 15). Поскольку потоки с такими
уровнями обслуживают системные функции, это ограничение не дает приложению нарушить
работу операционной системы. И, кстати, система никогда не меняет приоритет потоков с
уровнями реального времени (от 16 до 31).
Функции, позволяющие отключать этот механизм:
BOOL SetProcessPriorit yBoost ( HANDLE hProcess,
BOOL DisablePriorityBoost);
BOOL SetThreadPriorit yBoost ( HANDLE hThread,
BOOL DisablePriorityBoost );
SetProcessPriorit yBoost заставляет систему включить или отключить изменение
приоритетов всех потоков в указанном процессе, a SetThreadPriorit yBoost действует
применительно к отдельным потокам. Эти функции имеют свои аналоги, позволяющие
определять, разрешено или запрещено изменение приоритетов:
BOOL GetProcessPriorit yBoost ( HANDLE hProcess,
PBOOL pDisablePriorit yBoost);
BOOL GetThreadPriorit yBoost ( HANDLE hThr ead,
PBOOL pDisablePriorit yBoost);
Каждой из этих двух функций передается описатель нужного процесса или потока и
адрес переменной типа BOOL, в которой и возвращается результат.
Основная литература: [1] – 199 - 220 c.
Контрольные вопросы:
1. Назначение счетчика пользователей объектов ядра
2. Для чего используется флаг CREATE_SUSPENDED?
3. Какие данные хранятся в структуре CONTEXT?
4. В каком диапазоне находятся уровни приоритетов потоков?
Лекция 4. Тема: Синхронизация потоков
Потоки могут значительно упростить разработку и реализацию программы, а также
повышают ее быстродействие, но использование потоков требует внимательного отношения
к защите общих ресурсов от их совместного изменения. Объекты синхронизации могут
использоваться как для обеспечения синхронизации потоков одного процесса, так и для
синхронизации потоков различных процессов.
Необходимость синхронизации потоков
Во многих случаях возникает необходимость в координации выполнения двух и более
потоков в течение существования каждого из них. Например, несколько потоков могут
осуществлять доступ к одной и той же переменной или группе переменных, что вызывает
24
взаимное исключение. В других случаях поток не может продолжить работу до тех пор, пока
другой поток не дошел до некоторой точки.
Объекты синхронизации потоков
Win32 предоставляет четыре объекта, разработанных для синхронизации потоков и
процессов. Три из них — мьютексы, семафоры и события — являются объектами ядра и
имеют дескрипторы. События используются и для других целей, таких как асинхронный
ввод-вывод.
Четвертый объект - CRITICAL_SECTION. Благодаря своей простоте и
преимуществам в быстродействии объекты CRITICAL_SECTION являются наиболее предпочтительным механизмом, если только они соответствуют требованиям программы.
4.1 Объект CRITICAL_SECTION
Критическая секция — это часть кода, которая одновременно может выполняться
только одним потоком; выполнение такой секции одновременно более чем одним потоком
может привести к непредсказуемым и неверным результатам.
В качестве простого механизма для реализации идеи критической секции Win32
предоставляет объект CRITICAL_SECTION.
Объекты типа CRITICAL_SECTTON (критическая секция — КС) могут быть
инициализированы и удалены, но они не имеют дескрипторов и не разделяются другими
процессами. Переменная должна быть определена как имеющая тип CRITICAL_SECTION.
Потоки входят в КС и покидают ее, но одновременно в КС может находиться только один
поток. Однако поток может входить в КС и покидать ее в нескольких местах программы.
Для инициализации и удаления переменной типа CRITICAL_SECTION
используйте следующие функции:
VOID InitializeCriticalSection (
LPCR ITICAL_SECTION lpcsCritical Section)
VOID DeleteCriticalSection (
LPCR ITICAL_SECTION lpcsCriticalSection)
Функция EnterCriticalSection блокирует поток, если в данной секции
находится другой поток. С ожидающего потока снимается блокировка, когда другой
поток выполняет функцию LeaveCritcicalSection . Мы говорим, что поток "владеет" КС
с того момента, когда он получает управление от функции EnterCriticalSecti on, по тот
момент, как функция LeaveCriticalSection отдает владение КС. Всегда отдавайте
владение КС; если этого не сделать, другие потоки вынуждены будут ожидать вечно,
даже если владеющий поток будет завершен.
Мы часто будем говорить, что КС "блокирована" или "разблокирована", а вхождение
в КС — то же самое, что и ее блокировка.
VOID EnterCriticalSection (
LPCR ITICAL_SECTION lpcsCriticalSecticn)
VOID LeaveCriticalSection (
LPCR ITICAL _ SECTION lpcsCriticalSecticn)
Если поток уже владеет КС, он может войти в нее снова без блокировки.
Поддерживается счетчик, поэтому поток должен покинуть КС столько же раз, сколько он
вошел в нее, чтобы освободить КС для других потоков.
Выход из КС, которой данный поток не владеет, может привести к непредсказуемым
результатам, включая блокировку потока.
Для функции EnterCriticalSection время ожидания не ограничивается, поток будет
заблокирован до того момента, когда поток, владеющий КС, выйдет из нее. Можно провести
проверку или опрос, чтобы узнать, владеет ли КС какой-нибудь другой поток, используя
функцию TryEnterCriticalSection.
BOOL TryEnterCriticalSection (
LPCR ITICAL_SECTION lpcsCriticalSection)
Возвращенное значение TRUE показывает, что вызвавший функцию поток теперь
владеет КС, а значение FALSE показывает, что КС уже занята другим потоком.
25
Объекты CRITICAL_SECTION имеют преимущество в том, что не являются объектами ядра и располагаются в пользовательском пространстве.
Управление счетчиком попыток
Если при выполнении функции EnterCriticalSection поток определяет, что КС уже
занята, он обычно входит в ядро и блокируется до освобождения объекта
CRITICAL_SECTION, что требует временных затрат. Но вы можете заставить поток сделать
еще одну попытку перед блокировкой. Для управления счетчиком попыток существуют две
функции: SetCriticalSectionSpinCount, которая позволяет динамически изменять
счетчик, и InitializeCriticalSectionAndSpinCount, которая служит заменой функции
InitializeCriticalSection .
Объект CRITICAL_SECTION является мощным механизмом синхронизации, хотя и
не имеет всей необходимой функциональности. Он не предоставляет возможности передачи
сигнала другому потоку и не дает возможности установить максимальное время ожидания
(тайм-аут).
4.2 Мьютексы
Объект мьютекс (от английского mutual exclusion — взаимное исключение) предоставляет более широкие возможности, чем критические секции кода. Мьютексу может быть
присвоено имя и дескриптор, поэтому такие объекты могут быть использованы для
межпроцессной синхронизации потоков в отдельных процессах. Например, два процесса,
разделяющие память отображенного файла, могут использовать мьютексы для
синхронизации доступа к разделенной памяти.
Мьютекс аналогичен объекту КС, но, в дополнение к возможности разделения
процессами, мьютексы позволяют использовать тайм-аут и переходят в сигнальное
состояние, когда процесс завершается и оставляет мьютекс. Поток вступает во владение
мьютексом (или "блокирует" мьютекс), выполняя ожидание для дескриптора мьютекса
(функции WaitForSingleObject или WaitForMultipleObjects ), и освобождает его
функцией ReleaseMutex.
Поток может получать определенный мьютекс несколько раз; он не будет
блокировать мьютекс, если уже владеет им. В конечном счете поток должен столько же раз
и освободить мьютекс.
Для работы с мьютексами используются функции Win32 CreateMutex, Release Mutex и OpenMutex.
HANDLE CreateMutex ( LPSECURITY_ATTRIBUTES lpsa,
BOOL fInitialOwner, LPCTSTR lpszMutexName)
Значение TRUE флага fInitialOwner позволяет вызывающему функцию потоку
немедленно вступить во владение новым мьютексом. Эта элементарная операция защищает
другие потоки от захвата владения мьютексом до того, как это сделает создающий его поток.
Как видно из имени, этот флаг игнорируется, если мьютекс уже существует.
Параметр lpszMutexName определяет имя мьютекса, которое, в отличие от имен
файлов, чувствительно к регистру символов. Если это параметр имеет значение NULL,
мьютекс будет безымянным. События, мьютексы, семафоры, объекты отображения файлов
разделяют одно пространство имен. Поэтому все объекты синхронизации должны иметь
разные имена. Имя ограничивается длиной в 260 символов.
Возвращенное значение NULL сообщает об ошибке.
Функция OpenMutex используется для открытия существующего именованного
мьютекса. Она позволяет потокам разных процессов синхронизироваться так, как будто они
принадлежат одному процессу. Создание мьютекса одним процессом должно предшествовать открытию его другим. Семафоры, события, отображения файлов также имеют
функции создания и открытия. Всегда предполагается, что один процесс, например сервер,
сначала выполняет функцию Create для создания именованного объекта, а другой процесс
вызывает функцию Open, которая приводит к сбою, если данный именованный объект еще
26
не создан. Если же порядок не имеет значения, все процессы могут использовать вызов
функции Create.
Функция ReleaseMutex освобождает мьютекс, которым владеет вызывающий поток. Если поток не владеет данным мьютексом, функция не выполняется.
BOOL ReleaseMutex (HANDLE hMutex)
Покинутые мьютексы
Если поток завершается, не освободив мьютекс, которым он владеет, мьютекс становится покинутым, а его дескриптор переходит в сигнальное состояние. Функция
WaitForSingleObject возвратит значение WAIT_ABANDONED_0, а функция
WaitForMultipleObjects использует значение WAIT_ABANDONED_0 как базовое для
того, чтобы сообщить, что дескрипторы в сигнальном состоянии представляют покинутые
мьютексы.
Переход дескрипторов покинутых мьютексов в сигнальное состояние — очень
удобное свойство, недоступное для КС. Появление покинутых мьютексов говорит о том, что
в коде, возможно, есть ошибки, так как потоки должны программироваться таким образом,
чтобы отдавать ресурсы перед завершением. Возможно также, что поток был завершен
другим потоком.
4.3 Семафоры
Семафоры поддерживают счетчик и объект-семафор переходит в сигнальное
состояние, когда значение этого счетчик больше нуля. При нулевом значении счетчика
объект находится в несигнальном состоянии.
Потоки или процессы выполняют обычное ожидание, используя одну из функций
ожидания. Когда ожидающий поток освобожден, счетчик семафора уменьшается на 1.
Функции для работы с семафорами — CreateSemaphore, OpenSemaphore и
ReleaseSemaphore, которые могут увеличить счетчик на 1 или большее число. Эти
функции похожи на свои аналоги для мьютексов.
HANDLE CreateS emaphore ( LPSECURITY_ATTRIBUTES
lpsa,
LONG cSem Initial, LONG cSemMax, LPCTSTR lpszSemName)
Параметр cSemMax, который должен быть равен 1 или большему числу, указывает
максимальное значение для семафора. Параметр cSem Initial, имеющий значение в
промежутке от 0 до cSemMax, является начальным, и значение семафора никогда не
должно выходить за указанные пределы. Об ошибке сообщает возвращенное функцией
значение NULL.
Уменьшить счетчик на 1 можно любой функцией ожидания, но освобождение семафора может увеличить его значение на любое число вплоть до максимума.
BOOL ReleaseSemaphore ( HANDLE hSemaphore,
LONG cReleaseCount, LP LONG lpPreviousCount)
Отметим, что вы можете определить и предыдущее значение счетчика, но если такой
необходимости нет, указатель lpPreviousCount может иметь значение NULL.
Счетчик освобождения должен быть больше нуля, но если это заставит семафор
превысить максимально возможное значение, то функция не выполнится, возвратит значение
FALSE и счетчик останется не измененным. Освобождение семафора с большим значением
счетчика является способом получения текущего значения его счетчика в одной операции
(конечно же, это значение немедленно может изменить другой поток).
Мнение о семафоре как о мьютексе с максимальным значением 1 было бы ошибочным, так как семафор не может иметь владельца. Семафор может освободить любой
поток, а не только тот, который выполнит ожидание. Более того, так как нет владения
семафором, он не может быть покинут.
Использование семафоров
Классическое приложение семафоров относится к использованию их счетчиков как
представления числа доступных ресурсов, таких как число сообщений, ожидающих в
очереди. Максимум семафора соответствует максимальной длине очереди. Таким образом,
27
производитель поместит в буфер и вызовет функцию ReleaseSemaphore, обычно со
значением счетчика освобождения 1. Потоки-потребители будут выполнять ожидание для
семафора, получая сообщения и уменьшая счетчик семафора.
Другое важное применение описано - семафор используется для ограничения числа
рабочих потоков, которые реально выполняются в любой момент, снижая таким образом
состязание потоков за ресурсы и в некоторых случаях повышая быстродействие.
Другое возможное применение семафоров для контроля числа потоков, которые
необходимо пробудить. Все потоки могут создаваться не в приостановленном состоянии,
после чего они немедленно начнут ожидание для семафора, инициализированного нулем.
Поток-хозяин, вместо того чтобы продолжать выполнение приостановленных потоков,
должен будет просто вызвать функцию ReleaseSemaphore со значением счетчика, которое
является числом потоков, после чего начнется работа этих потоков.
Основная литература: [1] – 228-246 c.
Контрольные вопросы:
1. Назовите объекты синхронизации потоков.
2. Какие объекты могут быть использованы для межпроцессной синхронизации потоков в
отдельных процессах?
Лекция 5. Тема: Дополнительные методы синхронизации потоков
5.1 События
Последний объект ядра для синхронизации — события. Этот объект используется
для информирования других потоков о том, что произошло некоторое событие, например
стало доступно новое сообщение.
Важная дополнительная возможность, предоставляемая событиями, —
освобождение нескольких потоков от совместного ожидания, когда в сигнальное
состояние перешел один дескриптор. События делятся на сбрасываемые
автоматически и вручную, это свойство события устанавливается при вызове функции
CreateEvent.
- Событие, сбрасываемое вручную, может сигнализировать одновременно не
скольким потокам, выполняющим ожидание события.
- События с автоматическим сбросом посылают сигналы единственному потоку,
выполняющему ожидание события, и сбрасываются автоматически.
Событиями используются функции CreateEvent, OpenEvent, SetEvent,
ResetEvent и PulseEvent.
HANDLE CreateEvent ( LPSECUR ITY_ATTR IBUTES lpsa,
BOOL fManualReset, BOOL fInitialState, LPCTSTR lpszEventName)
При установленном в значение TRUE параметре fManualReset создается событие с
ручным сбросом. Аналогично, если событие первоначально должно находиться в сигнальном состоянии, параметр fInitialState необходимо установить в значение TRUE.
Чтобы открыть существующее событие, возможно, из другого процесса, следует
воспользоваться функцией OpenEvent.
Для управления событиями используются следующие три функции:
BOOL SetEvent (HANDLE hEvent)
BOOL ResetEvent (HANDLE hEvent;
BOOL PulseEvent (HANDLE hEvent)
Поток может перевести событие в сигнальное состояние, использовав функцию
SetEvent. Если событие сбрасывается автоматически, единственный (возможно, из многих)
ожидающий поток освобождается, а событие автоматически переводится в несигнальное
состояние. Если для данного события не выполняет ожидание ни один из потоков, оно
остается в сигнальном состоянии до момента, когда поток начнет ожидание, после чего он
немедленно освобождается. Отметим, что тот же результат можно получить, использовав
семафор с максимальным значением счетчика 1.
28
Если же, с другой стороны, событие сбрасывается вручную, оно остается в сигнальном состоянии до тех пор, пока поток не вызовет функцию ResetEvent для
данного события. В этот промежуток времени все ожидающие потоки освобождаются и
возможно, что поток начнет ожидание и будет освобожден до сброса события.
Функция PulseEvent освобождает все события, выполняющие ожидание для данного события с ручным сбросом, которое затем сбрасывается автоматически. В случае
события с автосбросом функция PulseEvent освобождает единственное ожидающее
событие, если таковое существует.
Отметим, что функция ResetEvent используется только после того, как событие с
ручным сбросом будет переведено в сигнальное состояние функцией SetEvent. Будьте
осторожны при использовании функции WaitForMultipleObjects для ожидание всех
событий, которые должны перейти в сигнальное состояние. Ожидающий поток будет
освобожден, только когда все события одновременно окажутся в сигнальном состоянии, а
некоторые из событий могут быть сброшены из сигнального состояния до освобождения
ожидающего потока.
Обзор: четыре модели использования событий
Комбинирование устанавливаемых вручную и автоматически событий с
функциями SetEvent и PulseEvent дает четыре различных пути использования событий.
Предупреждение: при неверном использовании события могут привести к
возникновению состояния гонок, зависаниям и другим незаметным и трудно выявляемым
ошибкам. В таблице описаны четыре возможные ситуации.
Событие с автоматическим сбросом похоже на дверь с пружиной, которая ее захлопывает, тогда как событие с ручным сбросом не имеет пружины, и поэтому так и
остается открытым. Если продолжить аналогию, то функция PulseEvent открывает
дверь и немедленно закрывает ее после того, как один (для автосброса) или все (для сброса
вручную) ожидающие потоки пройдут через дверь. Функция SetEvent открывает дверь и
оставляет ее открытой.
Резюме по использованию событий
События с автосбросом
События с ручным сбросом
Освобождается один поток. Если ни Все потоки, ожидающие в данный
Функция
один поток в данный момент не вы- момент, освобождаются. Событие
SetEvent
полняет ожидание для данного события, остается в сигнальном состоянии до тех
первый же поток, который начнет пор, пока не будет сброшено каким-то
ожидание в дальнейшем, будет не- потоком
медленно освобожден. Событие будет
сброшено автоматически
Освобождается один поток, но
Функция
только в случае, если поток в данный
PulseEvent момент выполняет ожидание для
данного события
Все ожидающие в данный момент потоки, если такие есть, освобождаются, а
событие затем сбрасывается. Если нет
ни одного ожидающего потока, событие
остается в состоянии сигнала до тех пор,
пока поток не начнет ожидание для него
5.2 Дополнительные сблокированные функции
Далее приведено еще несколько функций, которые позволяют выполнять
элементарные операции для сравнения и обмена пар переменных.
Сблокированный обмен записывает одну переменную в другую:
LONG InterlockedExchange ( LPLONG Target, LONG Value)
Функция возвращает текущее значение переменной *Target и присваивает ей
значение Value. Переменная Target должна выравниваться по словам.
Функция InterlockedExchangeAdd добавляет к первому значению второе.
29
LONG InterlockedExchangeAdd ( PLONG Addend, LONG Increment)
Переменная Increment прибавляется к переменной *Addend, и возвращается
исходное значение *Addend. Эта функция позволяет выполнить атомарную операцию
увеличения переменной на 2 (или большее число), что невозможно сделать
последовательными вызовами функции InterlockedIncrement .
Последняя функция, InterlockedCompareExchange , аналогична функции
InterlockedExchange, за исключением того, что обмен возможен только в том случае, если
равенство выполняется.
PVOID InterlockedCompareExchange ( PVOID *Destination,
PVOID Exchange, PVOID Comparand)
Эта функция выполняет в атомарной операции следующие действия (причина использования типа pvoid для двух последних параметров непонятна):
Temp = *Destination;
if (*Destination == Comparand) *Destination = Exchange;
return Temp;
Одно из применений этой функции — блокировка для реализации критической
секции кода. Переменная *Destination является "переменной блокировки", для которой 1
значит "разблокировано", а 0 — "заблокировано". Переменная Exchange должна иметь
значение 0, a Comparand — значение 1. Вызвавший функцию поток получает во "владение"
критическую секцию, если функция возвращает значение 1. Иначе он должен "заснуть" или
"зациклиться" — выполнять бесполезный цикл некоторое время, а затем попробовать снова.
Это зацикливание — именно то, что делает функция EnterCriticalSection во время
ожидания для объекта CRITICAL_SECTION с ненулевым значением счетчика цикла.
Основная литература: [1] – 246- 258 c.
Контрольные вопросы:
1. Виды событий
2. Четыре модели использования событий
3. Назначение сблокированных функций
Лекция 6. Тема: Управление памятью. Использование виртуальной
памяти
Архитектура управления памятью.
Каждый процесс Win32 имеет собственное виртуальное адресное пространство
размером 4Гбайт (232 байт). Win32 делает доступной процессу по меньшей мере его
половину (2Гбайт). Остальная часть виртуального адресного пространства предназначена
для совместно используемых данных и кода, системного кода, драйверов и т.п.
Обзор управления памятью.
Операционная система управляет всеми элементами отображения виртуальной памяти
в физическую, механизмом подкачки страниц, обменом страниц по запросу и т.д. Вот
краткое резюме.
- Система имеет сравнительно малый объем физической памяти.
- Каждый процесс – а пользовательских и системных процессов может быть несколько –
имеет собственное виртуальное адресное пространство, которое может намного
превышать доступную физическую память
- Операционная система отображает виртуальные адреса в физические.
- Большая часть виртуальных страниц не будет присутствовать в физической памяти,
поэтому ОС следит за страничными ошибками (обращениями к страницам,
отсутствующим в оперативной памяти) и загружает информацию с диска – либо из
системного файла подкачки, либо из обычного файла. Страничные ошибки, хотя и
незаметны программисту, влияют на быстродействие, поэтому программы должны
проектироваться таким образом, чтобы их количество было сведено к минимуму.
30
На рис. 6.1 показан механизм управления памятью в API Win32, основанный на
диспетчере виртуальной память (Virtual Memory Manager). API виртуальной памяти
Win32 (функции VirtualAlloc, VirtualFree, VirtualLock, VirtualUnlock и т.д.
работает с целыми страницами. API “кучи” Win32 может работать с единицами память,
определяемыми пользователем.
Функция VitualAlloc распределяет ряда страниц в виртуальном адресном
пространстве, а функция VitualAllocEx распределяет ряд страниц в виртуальном адресном
пространстве указанного процесса.
LPVOID Vitual Alloc ( LPVOID lpvAddress, DWORD dwSize,
DWORD dwAllocationType, DWORD dwProtect).
LPVOID VitualAllocEx ( HANDLE hProcess, LPVOID lpvAddress,
DWORD dwSize, DWORD dwAllocationType, DWORD dwProtect).
Программа Win32
Библиотека С: malloc, free
API кучи: HeapCreate,
HeapDestroy,
HeapAlloc, HeapFree
MMF API:
Create FileMapping,
CreateViewOfFile
API виртуальной памяти
Ядро Win32 с диспетчером виртуальной памяти
Диск и
файловая
система
Физическая
память
Рис.6.1. Архитектура управления памятью Win32
Функция VirtualFree освобождает ряд страниц в виртуальном адресном
пространстве. А функция VirtualFreeEx освобождает ряд страниц в виртуальном адресном
пространстве указанного процесса.
BOOL VirtualFree( LPVOID lpvAddress, DWORD dwSize, DWORD dwFreeType)
BOOL VirtualFreeEx(HAND LE hProcess, LPVOID lpvAddress, DWORD dwSize,
DWORD dwFreeType)
Функция VirtualLock блокирует область виртуального адресного пространства в
памяти. А функция VirtualUnLock дает возможность разблокировать указанный ряд
страниц в виртуальном адресном пространстве процесса, позволяя системе выгружать эти
страницы в файл подкачки по мере необходимости.
BOOL VirtualLock( LPVOID lpvAddress, DWORD dwSize),
BOOL VirtualUnlock( LPVOID lpvAddress, DWORD dwSize)
Функция VirtualProtect изменяет установки защиты доступа к области выделенных
страниц в виртуальном адресном пространстве. Функция VirtualProtectEx может
31
действовать в виртуальном адресном пространстве других процессов, тогда как
VirtualProtect действует толька в рамках вызывающего процесса.
BOOL VirtualProtect (LPVOID lpvAddress, DWORD dwSize, DWORD dwNewProtect,
DWORD pdwOldProtect)
BOOL VirtualProtectEx(HANDLE hProcess , LPVOID lpvAddress, DWORD dwSize,
DWORD dwNewProtect, DWORD pdwOldProtect)
Основная литература: [1] – 132 –134 c., [9] – 340 –372 c.
Контрольные вопросы:
1. Механизм управления памятью в API Win32.
2. Основные API функции работы с виртуальной памятью.
3. Что понимается под страничными ошибками?
Лекция 7. Тема: Динамически распределяемая память
7.1 Кучи
Win32 поддерживает области памяти в виде куч (heaps). Процесс может содержать
несколько куч, и из них разработчик выделяет память.
Часто достаточно одной кучи, но по приведенным ниже причинам используется и
много куч. Если достаточно одной кучи, просто используйте функции библиотеки С для
управления памятью (malloc, free, calloc, realloc ).
Кучи являются объектами Win32, поэтому они имеют дескрипторы. Дескриптор кучи
необходимо знать, когда выделяется память. Каждый процесс имеет собственную кучу,
принятую по умолчанию, к которой обращается функция malloc, а следующая функция
возвращает ее дескриптор.
HANDLE GetProcessHeap (VOID)
Возвращаемое значение: дескриптор кучи процесса; NULL в случае неудачи.
Заметим, что в данном случае для указания на ошибку возвращается значение NULL,
а не INVALID_HANDLE_VALUE, как в функции CreateFile.
Программа также может создавать особые кучи. Это относится к тем случаям, когда
необходимы отдельные кучи для резервирования отдельных структур данных. Ниже
описаны преимущества отдельных куч.
• Справедливость распределения. Ни один поток не может получить больше памяти, чем
зарезервировано для его кучи. В частности, утечка памяти, вызванная программой, не
учитывающей свободные и больше не используемые элементы данных, повлияет только на
поток процесса.
• Многопоточное быстродействие. Благодаря предоставлению каждому потоку отдельной
кучи соревнование между потоками сокращается, что может существенно повысить
быстродействие.
•
Эффективность резервирования. Резервирование элементов данных фиксированного
размера в маленькой куче значительно более эффективно, чем резервирование множества
элементов разных размеров в одной большой куче. Также снижается фрагментация памяти.
Кроме того, выделение каждому потоку отдельной кучи упрощает синхронизацию, что дает
дополнительный выигрыш.
• Эффективность освобождения памяти. Вся куча и все структуры данных, размещенные в
ней, могут быть освобождены одним вызовом функции. При этом также освобождается и вся
выделенная, но потерянная память в куче.
• Эффективная локализация ссылок. Расположение структуры данных в маленькой куче
гарантирует, что ее элементы будут сосредоточены в сравнительно небольшом количестве
страниц, что потенциально сокращает страничные ошибки при обработке структуры.
Ценность этих преимуществ изменяется в зависимости от приложения, и многие
программисты будут пользоваться только кучей процесса и библиотекой С. В любом случае,
следующие две функции создают и уничтожают кучи.
32
Начальный размер кучи, который может быть нулевым и всегда округляется до числа,
кратного размеру страницы, определяет объем физической памяти (в файле подкачки),
первоначально отведенный куче. Когда программа выходит за границы начального размера,
автоматически выделяются дополнительные страницы вплоть до максимального предела.
Так как файл подкачки является ограниченным ресурсом, отложенное выделение удобно в
случаях, когда размер кучи заранее не известен. Ненулевое значение переменной
dwMaximumSize определяет предел для динамического повышения объема кучи. Куча
процесса также будет динамически расти.
HANDLE HeapCreate ( DWORD flOptions,
SIZE_T dwInitialSize, SIZE_T dwMaximumSize);
Возвращаемое значение: дескриптор кучи или NULL в случае ошибки.
Два поля размера имеют тип SIZE_T, а не DWORD. Тип SIZE_T определен таким образом,
что может быть 32-разрядным или 64-разрядным беззнаковым целым, в зависимости от
флагов компилятора (_WIN32 или _WIN64). Тип SIZE_T был введен для обеспечения
возможности перехода к WIN64.
Переменная flOptions является комбинацией двух флагов.
• HEAP_GENERATE_EXCEPTIONS— при этом значении неудачные попытки выделения
памяти вызывают исключения, которые будут обработаны структурным обработчиком
исключений (Structured Exception Handler — SEH,). Функция HeapCreate сама по себе
не вызывает исключений; если этот флаг установлен, исключение вызывают при неудаче
такие функции, как HeapAlloc.
• HEAP_NO_SERIALIZE — установка этого флага в некоторых случаях позволяет получить небольшое повышение быстродействия. Следует сказать также несколько слов о
dwMaximumSize.
• Если значение dwMaximumSize. не равно нулю, виртуальное адресное пространство
выделяется и в том случае, когда весь указанный объем выделить невозможно. Это
максимальный размер кучи, которая называется невозрастающей. Данная опция
ограничивает размер кучи, возможно, для достижения справедливости распределения
ресурсов, о которой упоминалось ранее.
• С другой стороны, если значение dwMaximumSize. — нуль, то куча является возрастающей за пределы начального размера. Эта граница определяется доступным
виртуальным адресным пространством, часть которого может быть предоставлена другим
кучам, и пространством файла подкачки.
Отметим, что кучи не имеют атрибутов безопасности, так как они недоступны извне
процесса.
Для уничтожения всей кучи используйте функцию HeapDestroy. Это другое исключение из общего правила о том, что функция CloseHandle применяется для удаления
всех ненужных дескрипторов.
BOOL HeapDestroy (HANDLE hHeap);
Переменная hHeap должна указывать на кучу, созданную функцией HeapCreate.
Будьте осторожны, не уничтожьте кучу процесса (полученную функцией GetProcessHeap).
Уничтожение кучи освобождает пространство виртуальной памяти и физическую память в
файле подкачки. Разумеется, грамотно спроектированные программы должны освобождать
кучи, которые больше не используются.
Уничтожение кучи — это также быстрый способ освободить структуру данных без
необходимости уничтожать каждый элемент отдельно, хотя экземпляры объектов С++ таким
образом не будут уничтожены, поскольку их деструкторы не вызываются. Уничтожение
кучи имеет ряд преимуществ.
1. Нет необходимости писать код для поэлементного обхода структуры.
2. Нет необходимости освобождать каждый отдельный элемент.
3. Система не тратит время на поддержку кучи с того момента, когда все структуры данных
освобождаются одним системным вызовом.
33
7.2 Управление памятью кучи
Выделение блоков памяти из кучи осуществляется путем указания дескриптора кучи,
размера блока и нескольких флагов.
LPVOID HeapAlloc ( HANDLE hHeap, DWORD dwFlags, S IZE_ T dwBytes)
Возвращаемое значение: указатель на выделенный блок памяти или NULL при
неудаче (кроме случаев,- когда задано генерирование исключений).
Параметры функции HeapAlloc
hHeap — дескриптор кучи, из которой должен быть выделен блок памяти. Этот дескриптор должен быть получен функциями GetProcessHeap или HeapCreate.
dwFlags представляет собой комбинацию трех флагов.
• HEAP_GENERATE_EXCEPTIONS И HEAP_NO_SERIALIZE имеют то же значение, что и
для функции HeapCreate. Первый флаг можно не указывать, если он был установлен
функцией HeapCreate для данной кучи. Второй флаг используется при выделении памяти
из кучи процесса.
• HEAP_ZERO_MEMORY указывает на то, что выделенная память будет заполнена нулями;
в противном случае содержимое памяти будет неопределенным.
dwBytes — размер блока памяти, который должен быть выделен. Для невозрастающих куч его предел составляет 0x7FFF8 (примерно 0,5 Мбайт).
Освобождение памяти из кучи выполняется элементарно.
BOOL HeapFree ( HANDLE hHeap, DWORD dwFlags, LPVOID lpMe m)
dwFlags должен быть равен нулю или константе HEAP_NO_SERIALIZE.
lpMem должен иметь значение, возвращенное функцией HeapAlloc или HeapReAlloc,
а hHeap — это, конечно, дескриптор кучи, из которой был выделен блок lpMem.
Для изменения размера блоки памяти могут быть выделены заново.
LPVOID HeapReAlloc ( HANDLE hHeap, DWORD dwFlags,
LPVOID l pMem, S IZE_T dwBytes)
Возвращаемое значение: указатель на выделенный заново блок. При неудаче
возвращает NULL или вызывает исключение.
Параметры функции HeapReAlloc
Первый параметр hHeap упоминался выше. dwFlags указывает некоторые важные
управляющие флаги.
• HEAP_GENERATE_EXCEPTIONS и HEAP_NO_SERIAL_S1ZE, как в предыдущих
случаях.
• HEAP_ZERO_MEMORY — инициализируется только заново выделяемая память (когда
значение dwByte больше размера исходного блока). Содержимое исходного блока не
изменяется.
• HEAP_REALLOC_IN_PLACE_ONLY указывает на то, что блок не может быть перемешен.
При увеличении размера блока память должна выделяться по адресу, следующему сразу
после существующего блока.
lpMem указывает на существующий блок в куче hHeap, который должен быть выделен
заново.
dwBytes — новый размер блока, который может быть больше или меньше текущего.
Обычно возвращаемый указатель — тот же, что и значение lpMem. Если же блок был
перемещен (это разрешается, когда флаг HEAP_REALLOC_IN_PLACE_ONLY опущен), то
возвращенное значение будет другим. Следите за обновлением всех ссылок на блок.
Размер выделенного блока определяется функцией НеарSize (на самом деле имя
этой функции должно быть BlockSize, так как она относится к блоку, а не к куче) с
указанием дескриптора кучи и указателя на блок.
DWORD HeapSize ( HANDLE hHeap, DWORD dwFlags,
LPCVOID lpMem)
Возвращаемое значение:.размер блока или нуль при неудаче.
Флаг HEAP_NO_SER IALIZE
34
В функциях HeapCreate, HeapAlloc и HeapReAlloc может быть указан флаг
HEAP_NO_SERIALIZE. Его применение позволяет получить небольшой прирост быстродействия, производительности, так как функции не предоставляют потокам взаимные
исключения при доступе к куче. Применение этого флага безопасно в немногих описанных
ниже ситуациях.
• Программа не использует потоки, или, точнее, процесс имеет только один поток.
• Каждый поток имеет собственную кучу или группу куч, к которым не обращаются другие
потоки.
• Программа имеет собственный механизм взаимных исключений для предотвращения
одновременного доступа к куче нескольких потоков, использующих функции HeapAlloc и
HeapReAlloc. Для тех же целей в Windows 2000/NT доступны функции HeapLock и
HeapUnlock.
Флаг HEAP_GENERATE__EXCEPTIONS
Вызов исключений в случае неудачного выделения памяти делает ненужными раздражающие проверки ошибок после каждого выделения. Более того, исключение или
обработчик завершения может очистить память, которая была выделена. Возможны два кода
исключения.
1. STATUS_NO_MEMORY указывает на то, что система не может создать блок заданного
размера. Причиной может быть фрагментация памяти, невозрастающая куча, которая
достигла своего предела, или даже исчерпание всей памяти возрастающими кучами.
2. STATUS_ACCESS_VIOLATION указывает на то, что куча была повреждена. Например,
программа могла произвести запись в память за границами выделенного блока.
Другие функции для работы с кучей
Функция HeapCompact делает попытку объединения, или дефрагментации, смежных свободных блоков в куче; HeapValidate пробует определить повреждение кучи;
HeapWalk перебирает блоки в куче, а GetProcessHeap получает все дескрипторы кучи,
действительные для данного процесса.
Функции HeapLock и HeapUnlock позволяют потоку упорядочить доступ к куче.
Резюме по управлению кучей
Обычная последовательность действий при работе с кучей такова.
1. Получить дескриптор кучи с помощью функций CreateHeap или GetProcessHeap
2. Выделить блоки в куче, используя HeapAlloc.
3. По необходимости освободить все или некоторые блоки функцией HeapFree.
4. Уничтожить кучу и закрыть дескриптор функцией HeapDestroy.
Основная литература: [3] – 193-206 c.
Контрольные вопросы:
1. Преимущества использования отдельных куч.
2. Последовательность действий при работе с кучей
3. API функции для работы с кучей.
Лекция 8. Тема: Отображаемые в память файлы
Динамическая память для кучи должна физически резервироваться в файле подкачки.
Часть операционной системы, управляющая памятью, регулирует перемещение страниц
между физической памятью и файлом подкачки, а также отображает в него виртуальное
адресное пространство процесса. Когда процесс завершается, физическое пространство в
файле освобождается.
Возможность отображения файлов в память Win32 также применима и для отображения в память обычных файлов. При этом проявляется ряд преимуществ.
• Нет необходимости выполнять прямой ввод-вывод из файла (запись и чтение).
• Структуры данных, созданные в памяти, могут быть сохранены в файле для дальнейшего
использования в той же или другой программе. Будьте осторожны при использовании
указателей.
35
• Удобные и эффективные алгоритмы для работы в памяти (сортировка, деревья поиска,
обработка строк и т.д.) могут обрабатывать данные файла, даже если его размер намного
превышает объем доступной физической памяти. При этом в случае больших файлов
быстродействие будет определяться характером подкачки страниц.
• В некоторых случаях может быть значительно повышена производительность обработки
файлов.
• Отсутствует потребность в уцравлении буферами и данными файла, которые они содержат.
Эту работу эффективно и надежно выполняет операционная система.
• Несколько процессов могут совместно использовать одну область памяти, отображая свои
виртуальные адресные пространства в один файл или в файл подкачки (межпроцессное
разделение памяти — главная предпосылка для отображения в файл подкачки).
• Нет необходимости расходовать пространство файла подкачки.
Сама операционная система использует отображение в память для реализации
библиотек динамической компоновки (DLLs), а также для загрузки исполняемых (.ЕХЕ)
файлов и их выполнения.
Объекты отображения файлов
Первый этап — создание для открытого файла объекта отображения файла, который имеет дескриптор, и последующее отображение адресного пространства процесса на
весь файл или на его часть. Объекты отображения файлов могут получать имена, поэтому
они доступны другим процессам для разделения памяти. Также отображаемый объект имеет
защиту, атрибуты безопасности и размер.
HANDLE CreateFileMapping ( HANDLE hFile,
LPSECUR ITY_ATTRIBUTES lpsa, DWORD fdwProtect,
DWORD dwMaximumSizeHigh, DWORD dwMaximumSizeLow,
LPCTSTR lpszMapName)
Возвращаемое значение: дескриптор отображения файла или NULL при неудаче.
hFile — дескриптор открытого файла с флагами защиты, совместимыми с параметром fdwProtect. Значение (типа HANDLE) 0XFFFFFFFF (эквивалентно константе
INVALID_HANDLE_VALUE) указывает на файл подкачки, и вы можете использовать это
значение для межпроцессного разделения памяти без создания отдельного файла.
Тип LPSECURITY_ATTRIBUTES позволяет защитить объект отображения. Параметр
определяет доступ к отображенному файлу с помощью описанных ниже флагов. Для
специальных целей разрешены дополнительные флаги; например, флаг SEC_IMAGE
определяет исполняемый образ (подробнее это описано во встроенной документации).
• Установленный флаг PAGE_READONLY означает, что программа может только читать
страницы в отображенной области и не может записывать или исполнять их. Файл hFile
должен быть открыт с правом доступа GENERIC_READ.
• Флаг PAGE_READWRITE предоставляет полный доступ к объекту, если файл hFile
открыт с правами доступа GENERIC_READ и GENERIC_WRITE.
• Флаг PAGE_WRITECOPY определяет, что при изменении содержимого отображенной
памяти собственная (для данного процесса) копия записывается в файл подкачки, а не в
исходный файл. Отладчик может использовать этот флаг при определении точек останова в
разделяемом коде. Результат будет различным в Windows 2000/NT и Windows 9x
Параметры dwMaximumSizeHigh и dwMaximumSizeLow определяют размер
объекта отображения. Если указан нуль, используется текущий размер; обязательно определяйте размер при использовании файла подкачки. Если ожидается, что размер файла
увеличится, используйте ожидаемый размер, и при необходимости будет немедленно
установлен нужный размер файла. Не отображайте область файла за указанной границей —
объект отображения не может расти.
lpszMapName указывает имя объекта отображения, что позволяет другим процессам
совместно использовать объект. Регистр символов в имени не учитывается. Если разделение
памяти не используется, указывайте значение NULL.
36
Об
ошибке
сообщает
возвращаемое
значение
NULL
(a
не
INVALID_HANDLE_VALUE).
Указав имя существующего объекта отображения, можно получить дескриптор
отображения файла. Имя должно быть получено предшествующим вызовом функции
CreateFileMapping. Два процесса могут совместно использовать память, разделяя
отображение файла. Первый процесс создает отображение файла, а следующий открывает
это отображение, используя имя. Если названного объекта не существует, открыть его не
удастся..
HANDLE OpenFileMapping ( DWORD dwDesiredAccess,
BOOL bInheritHandle, LPCTSTR lpName);
Возвращаемое значение: дескриптор отображения файла или NULL при неудаче.
dwDesiredAccess использует тот же набор флагов, что и параметр функции
CreateFileMapping. Значение lpName — имя, полученное вызовом функции
CreateFileMapping. Параметр blnheritHandle - наследование дескриптора.
Функция CloseHandle, как и ожидалось, уничтожает дескрипторы отображения.
Отображение адресного пространства процесса в объекты отображения
Следующий этап — выделение виртуального адресного пространства и отображение
его в файл через объект отображения. С точки зрения программиста, такое выделение памяти
аналогично действию функции HeapAlloc, но намного грубее, более крупными частями.
Возвращается указатель на выделенный блок (или образ файла); отличие состоит в том, что
выделенный блок отображается в указанный пользователем файл, а не в файл подкачки
обмена. Объект отображения файла играет ту же роль, что и куча при использовании
функции HeapAlloc.
LP VO ID M A P V IE W O F F ILE ( HANDLE hMapObject, DWORD fdwAccess,
DWORD dwOffsetHigh, DWORD dwOffsetLow, S IZE_T cbMap);
Возвращаемое значение: начальный адрес блока (образ файла) или NULL. при
неудаче.
Параметры
hMapObject указывает объект отображения файла, полученный от функций
CreateFileMapping или OpenFileMapping. Значение параметра fdwAccess должно
соответствовать правам доступа объекта отображения. возможны три значения флагов:
FILE_MAP_WRITE, FILE_MAP_READ и FILE_MAP_ALL_ACCESS (поразрядное "или"
двух предыдущих флагов).
Параметры dwOffsetHigh и dwOffsetLow определяют начальное положение области отображения. Начальный адрес должен быть кратным 64К. Для отображения от начала
файла используйте нулевое значение смещения.
cbMap указывает размер отображаемой области в байтах. Его нулевое значение при
вызове функции MapViewOfFile указывает на то, что весь файл должен быть отображен.
Функция MapViewOfFileEx аналогична, за исключением того, что необходимо
указывать начальный адрес в памяти. Этот адрес, например, может быть адресом начала
массива в области данных программы. При успешном вызове этой функции Windows
обеспечивает возможность использования данного базового адреса всеми процессами. Это
значит, что виртуальное адресное пространство заимствуется у всех процессов. В Windows
2000/NT возникает ошибка в случае, если процесс уже отобразил затребованное
пространство.
Освобождение образа файла выполняется так же, как и освобождение памяти, выделенной в куче, т.е. функцией HeapFree..
BOOL UnmapViewOfFile ( LPVOID lpBaseAddress )
Функция FlushViewOfFile заставляет систему записывать "грязные" (измененные)
страницы на диск. Обычно процесс, который получает доступ к файлу с помощью
отображения, и процесс, использующий для этого стандартные функции ввода-вывода, не
будут иметь согласованных образов файла. Выполнение файлового ввода-вывода без
37
буферизации не поможет, так как отображенная память не будет немедленно записываться в
файл.
Основная литература: [1] – 145- 158 c, [2] – 409-461 c.
Контрольные вопросы:
1. Преимущества применения отображаемых в память файлов.
2. Стандартная последоватальность действий для отображения файлов
3. Ограничения отображения файлов
Лекция 9. Тема: Динамические библиотеки
В DLL содержатся все функции Windows API. Три самые важные DLL: Kernel32.dll
(управление памятью, процессами и потоками), User32.dll (поддержка пользовательского
интерфейса, в том числе, функции, связанные с созданием окон и передачей сообщений) и
GDI32.dll (графика и вывод текста).
В Windows есть другие DLL:
AdvAPI32.dll – содержит функции для защиты объектов, работы с реестром и
регистрации событий
ComDlg32.dll – стандартные диалоговые окна (вроде FileOpen и FileSave)
ComCtl32.dll – поддерживает стандартные элементы управления
DLL нужно применять для реализации следующих возможностей:
1. Расширение функциональности приложения.
2. Возможность использования разных языков программирования.
3. Более простое управление проектом.
4. Экономия памяти..
5. Разделение ресурсов..
6. Упрощение локализации.
7. Решение проблем, связанных с особенностями различных платформ..
8. Реализация специфических возможностей.
DLL и адресное пространство процесса.
DLL представляет собой набор модулей исходного кода, в каждом их которых
содержится определенное число функций, вызываемых приложением или другим DLL.
Причем в DLL обычно нет кода, предназначенного для обработки циклов выборки
сообщений и создания окон.
Файлы с исходным кодом компилируются и компонуются также, как и при создании
EXE-файла, но при компоновке нужно указать ключ /DLL.
Чтобы приложение (или другая DLL) могла вызывать функции, содержащиеся в DLL,
образ ее файла нужно сначала спроецировать на адресное пространство вызывающего
процесса. Это выполняется за счет неявного связывания при загрузке, либо за счет явного – в
период выполнения. Теперь все функции DLL доступны всем потокам этого процесса. Когда
поток вызывает из DLL какую-то функцию, та считывает свои параметры из списка потока и
размещает в этом стеке собственные локальные переменные. Кроме того, любые созданные
кодом объекты принадлежат вызывающему потоку или процессу – DLL ничем не владеет.
При проецировании образа DLL-файла на адресное пространство процесса система создает
также экземпляры глобальных и статических переменных.
Неявное связывание EXE – и DLL – модулей
Неявное связывание (implicit linking) - самый распространенный метод.
Исполняемый модуль (EXE) импортирует функции и переменные из DLL, а DLL– модули
экспортирует их в исполняемый модуль. DLL – также может импортировать функции и
переменные их других DLL.
Создание DLL-модуля
DLL может экспортировать переменные, функции или С++ классы в другие модули.
При разработке DLL сначала создается заголовочный файл, который включается во
все модули исходного кода нашей DLL. Кроме того, его нужно поставлять вместе со своей
38
DLL, чтобы другие разработчики могли включать его в свои модули исходного кода,
которые импортируют наши функции или переменные. Единый заголовочный файл,
используемый при сборке DLL и любых исполняемых модулей, существенно облегчает
поддержку приложения.
При компиляции исходного файла DLL MYLIBAPI определяется как __declspec
(dllexport) до включения заголовочного файла MyLib.h. Такой модификатор означает, что
данная переменная, функция или C++ класс экспортируется из DLL.
Также следует обратить внимание, что в файле MyLibFile1.cpp перед экспортируемой
переменной или функцией не ставится идентификатор MYLIBAPI. Он здесь не нужен:
проанализировав заголовочный файл, компилятор запоминает, какие переменные и функции
являются экспортируемыми.
Идентификатор MYLIBAPI включает extern. Модификатор extern не даёт
компилятору искажать имена переменных или функции, и они становятся доступными
исполняемым модулям, написанным на С, С++ или любом другом языке программирования.
Этим модификатором можно пользоваться только в коде С++, но ни в коем случае ни в коде
на стандартном С.
Мы рассмотрели, как используется заголовочный файл в исходных файлах DLL.
А в исходных файлах ЕХЕ-модуля MYLIBAPI определять не надо: включая заголовочный
файл, вы определяете этот идентификатор как – DLLSPEC(DLLIMPORT), и при помещении
исходного кода ЕХЕ-модуля компилятор поймёт, что переменные и функции импортируются
из DLL.
Что такое экспорт
Если перед переменной, прототипом функции или С++ классом указан модификатор –
_deсlspec(dllexport), компилятор Microsoft C/C++ встраивает в конечный obj-файл
дополнительную информацию. Она понадобится при сборке DLL из OBJ-файлов.
Обнаружив такую информацию, компоновщик создает LIB-файл со списком
идентификаторов, экспортируемых из DLL. Этот LIB-файл нужен при сборке любого EXEмодуля, ссылающегося на такие идентификаторы. Компоновщик также вставляет в конечный
DLL-файл таблицу экспортируемых идентификаторов – раздел экспорта, в котором
содержится список (в алфавитном порядке) идентификаторов экспортируемых функций,
переменных и классов. Туда же помещается относительный виртуальный адрес (relative
virtual address, RVA) каждого идентификатора внутри DLL-модуля.
Создание EXE-модуля
Вот пример исходного кода EXE-модуля, который импортирует идентификаторы,
экспортируемые DLL, и ссылается на них внутри в процессе выполнения.
//Модуль: MyExeFilel .cpp
//Сюда включаются стандартные заголовочные файлы Windows и библиотеки С
#include <windows.h>
//включаем экспортируемые структуры данных, идентификаторы, функции и переменные
#include “MyLib \MyLib.h”
….
int WINAP I WinMain(HINSTANC E hinstExe, HINSTANCE, LPTSTR
pszCmdLine, int){
int nLeft=10, nRight=25;
TCHAR sz[100];
wsprintf(sz, TEXT(“%d + %d = %d”), nLeft, nRight, Add(nLeft,
nRight));
MessageBox(NULL, sz, TEXT(“Calculation”), MB_OK);
wsprintf(sz, TEXT(“The result from the last A dd is: %d”), g_nResult);
MessageBox(NULL, sz, TEXT(“Last Result”), MB_OK);
return (0);
}
39
Создавая фалы исходного кода для EXE-модуля, нужно включить в них заголовочный
файл DLL, иначе импортируемые идентификаторы окажутся неопределенными, и
компилятор выдаст массу предупреждений об ошибках.
MYLIBAPI в исходных файлах EXE-модуля до заголовочного файла DLL не
определяется. Поэтому при компиляции приведенного выше кода MYLIBAPI за счет
заголовочного файла MyLib.h будет определен как __declspec(dllimport). Встречая такой
модификатор перед именем переменной, функции или С++ класса, компилятор понимает,
что данный идентификатор импортируется из какого-то DLL –модуля.
Далее компоновщик собирает все OBJ-модули в конечный EXE-модуль. Для этого он
должен знать, в каких DLL содержаться импортируемые идентификаторы, на которые есть
ссылки в коде. Информацию об этом он получает из передаваемого ему LIB-файла (в
котором указан список идентификаторов, экспортируемых DLL)
Что такое импорт
Импортируя идентификатор, необязательно прибегать к __declspec(dllimport ) –
можно использовать стандартное ключевое слово extern языка С. Но компилятор создаст
чуть более эффективный код, если ему будет заранее известно, что идентификатор, на
который мы ссылаемся, импортируется из LIB-файла DLL –модуля.
Разрешая ссылки на импортируемые идентификаторы, компоновщик создаст в
конечном EXE-модуле раздел импорта (import section).
В нем перечисляются DLL, необходимые этому модулю, и идентификаторы, на
которые есть ссылки из всех используемых DLL.
Выполнение EXE-модуля
При запуске EXE-файла загрузчик операционной системы создает для его процесса
виртуальное пространство и проецирует на него исполняемый модуль. Далее загрузчик
анализирует раздел импорта и пытается спроецировать все необходимые DLL на адресное
пространство процесса.
Поскольку в разделе импорта указано только имя DLL (без пути), загрузчику
приходится самому искать ее на дисковых устройствах в компьютере пользователя. Поиск
DLL осуществляется в следующей последовательности:
1. Каталог, содержащий EXE-файл
2. Текущий каталог процесса
3. Системный каталог Windows
4. Основной каталог Windows
5. Каталоги, указанные в переменной окружения PATH
Проецируя DLL-модули на адресное пространство, загрузчик проверяет в каждом из них
раздел импорта. Если у DLL есть раздел импорта (что обычно и бывает), загрузчик
проецирует следующий DLL-модуль. При этом загрузчик ведет учет загружаемых DLL, и
проецирует их только один раз, даже если загрузки этих DLL требуют и другие модули.
Найдя и спроецировав на адресное пространство процесса все необходимые DLLмодули, загрузчик настраивает ссылки на импортируемые идентификаторы. Для этого вновь
просматривает разделы импорта в каждом модуле, проверяя наличие указанного
идентификатора в соответствующей DLL.
Обнаружив идентификатор, загрузчик отыскивает его RVA и прибавляет к
виртуальному адресу, по которому данная DLL размещена в адресное пространство
процесса, а затем сохраняет полученный адрес в разделе импорта EXE-модуля. И с этого
момента ссылка в коде на импортируемый идентификатор приводит к выборке его адреса из
раздела импорта вызывающего модуля, открывая таким образом доступ к импортируемой
переменной, функции или функции-члену С++ класса. Как только динамические связи будут
установлены, первичный поток процесса начинает выполняться.
Загрузчик всех этих DLL и настройка ссылок занимает какое-то время. Чтобы
сократить время загрузки приложения нужно модифицировать базовые адреса EXE- и DLLмодулей и провести их связывание.
40
Явная загрузка DLL и связывание идентификаторов
Чтобы поток мог вызвать функцию из DLL-модуля, DLL надо спроецировать на
адресное пространство процесса, которому принадлежит этот поток. Делается это двумя
способами:
1. Код приложения просто ссылается на идентификаторы, содержащиеся в DLL, и тем
самым заставляет загрузчик неявно загружать (и связывать) нужную DLL при запуске
приложения.
2. Явная загрузка и связывание требуемой DLL в период выполнения приложения.
Иначе говоря, поток явно загружает DLL в адресное пространство процесса, получает
виртуальный адрес необходимой DLL-функции и взывает ее по этому адресу.
Изящество этого подхода в том, что все происходит в уже выполняемом приложении.
Явная загрузка DLL
В любой момент поток может спроецировать DLL на адресное пространство процесса,
вызвав одну из двух фунеций:
HINSTANCE LoadLibrary (PCTSTR pszDllPathName);
HINSTANCE LoadLibraryEx( PCTSTR pszDllPath Name, HANDLE hFile,
DWORD dwFlags );
Обе функции ищут образ DLL-файла и пытаются спроецировать его на адресное
пространство вызывающего процесса. Значение типа HINSTANCE, возвращаемое этими
функциями, сообщает адрес виртуальной памяти, по которому спроецирован образ файда.
При ошибке возвращается NULL (может посмотреть GetLastError).
Функция LoadLibraryEx : параметр hFile зарезервирован для использования в
будуших версиях, и должен быть NULL. В параметре dwFlags можно передать либо 0, либо
комбинацию
флагов:
DONT_RESOLVE_DLL_REFERENCES,
LOAD_LIBRARY_AS
_DATAFILE и LOAD_WITH_ALTERED_SEARCH_PATH
Явная выгрузка DLL
Если необходимость в DLL отпадает, ее можно выхрузить из адресного пространство
процесса, вызвав функцию:
BOOL FreeLibrary (HINSTANCE hinstDll);
DLL можно выгрузить и с помощью другой функции:
VOID FreeLibraryAndExitThread ( HINSTANCE hinstDll, DWORD
dwExitCode);
Функции LoadLibrary и LoadLibraryEx увеличивают счетчик числа пользователей
указанной библиотеки, а FreeLibrary и FreeLibraryAndExitThread его уменьшают. Так
при первом вызове LoadLibrary для загрузки DLL система проецирует образ DLL-файла на
адресного пространство вызывающего процесса и присваивает единицу счетчику числа
пользователей этой DLL. Если поток того же процесса вызывает LoadLibrary для той же
DLL еще раз, DLL больше не проецируется; система просто увеличивает счетчик числа ее
пользователей.
Чтобы выгрузить DLL из адресного пространство процесса, FreeLibrary придется
теперь вызывать дважды: первый вызов уменьшит счетчик до 1, второй – до 0. Обнаружив,
что счетчик обнулен, система отключит DLL
Система поддерживает в каждом процессе свой счетчик DLL.
Чтобы определить спроецирована ли DLL на адресное пространство процесса, поток
может вызвать функцию GetModuleHandle:
HINSTANCE GetModuleHandle(PCTSTR pszModuleName);
Например, следующий код загружает MyLib.dll, только если она еще не
спроецирована на адресное пространство процесса:
HINSTANCE hinstDll=GetModuleHandle(“MyLib”);
if (hinstDll==NULL){
hinstDll=LoadLibrary(“MyLib”);
}
41
Если у нас имеется значение HINSTANCE для DLL, можно определить полное
(вместе с путем) имя DLL или EXE с помощью GetModuleFileName:
DWORD GetModuleFileName( HINSTANCE hinstModule,
PTSTR pszPathName, DWORD cchPath);
Первый параметр этой функции – значение типа HINSTANCE нужной DLL (или
EXE). Второй параметр, pszPathName, задает адрес буфера, в который она запишет полное
имя файла. Третий – (cchPath) определяет размер буфера символах.
Явное подключение экспортируемого идентификатора
Поток получает адрес экспортируемого идентификатора из явно загружаемой DLL
вызовом GetProcAddress:
FARPROC GetProcAddress( HINSTANCE hinstDll,
PCSTR pszS ymbolName);
Параметр hinstDll – описатель, возвращенный LoadLibrary или GetModuleHandle и
относящийся к DLL, которая содержит нужный идентификатор
Параметр pszSumbolName разрешается указывать в двух формах.
Во-первых, как адрес строки с нулевым символом в конце, содержащей имя
интересующей нас функции:
FARPROC pfn = GetProcAddress ( hinstDll, ”SomeFuncInDll”);
Тип PCSTR указывает, что функция GetProcAddress принимает только ANSI
строки. Идентификаторы функций и переменных в разделе экспорта DLL всегда хранятся
как ASCII-строки
Вторая форма параметра pszSumbolName позволяет указывать порядковый номер
нужной функции:
FARPROC pfn = GetProcAddress ( hinstDll, MAKEINTRESOURSE(2));
Здесь подразумевается, что нам известен порядковый номер (2) искомого
идентификатора, присвоенный ему автором данной DLL. Microsoft не рекомендует
пользоваться порядковыми номерами.
Функции ввода/вывода
В DLL может быть лишь одна функция ввода/вывода. Система вызывает ее в
информационных целях, и обычно она используется DLL для инициализации и очистки
ресурсов в конкеретных процессах и потоках. Если вашей DLL подобные уведомления не
нужны, эту функцию можно не использовать (Пример – DLL, содержащая только ресурсы)
Пример использования функции:
BOOL WINAP I DllMain (HINSTANCE hinstDll, DWORD fdwReason,
PVOID fImpLoad){
switch (fwReason){
case DLL_PROCESS_ATTACH:
//DLL проецируется на адресное пространство п роцесса
break;
case DLL_THREAD_ATTACH:
//создается поток
break;
case DLL_THREAD_DETACH:
//поток корректно завершается
break;
case DLL_PROCESS_DETACH:
//DLL отключается от адресного пространство процесса
break;
}
return(TRUE); //использ уется только для DLL_PROCESS_ATTACH
Параметр hinstDll - описатель экземпляра DLL. (Виртуальный адрес проекции файла
DLL на адресное пространство процесса)
42
Параметр fImpLoad – отличен от 0, если DLL загружена неявно, и равен 0, если
DLL загружена явно.
Параметр fdwReason – сообщает о причине, по которой система вызвала эту
функцию.
Он
принимает
одно
из
значений:
DLL_PROCESS_ATTACH,
DLL_PROCESS_DETACH, DLL_THREAD_ATTACH, DLL_THREAD_DETACH
В документации Platform SDK утверждается, что DllMain должна выполнять лишь
простые виды инициализации – настройку локальной памяти потока, создание объектов
ядра, открытие файлов и т.д.
Из DllMain функции нельзя вызывать LoadLibrary(Ex ) и FreeLibrary, так как это
может привести к взаимной блокировке.
Нужно избегать обращений из DllMain к функциям, импортируемым их других DLL
Основная литература: [1] – 158 - 166 c, [2] – 475-526 c.
Контрольные вопросы:
1. Как создается DLL модуль?
2. Для чего используются разделы экспорта и импорта?
3. Как создается EXE модуль, импортирующий идентификаторы из DLL?
Лекция 10. Тема: Использование файловой системы
Файлами обычно называют взаимосвязанные блоки данных на запоминающем
устройстве, обозначенные именем, а в API Win32 файлами считают именованные каналы,
ресурсы связи, дисковые устройства, потоки ввода или вывода на консоль или обычные
файлы. По принципу организации все эти типы файлов являются одинаковыми, но
отличаются друг от друга своими дополнительными свойствами и ограничениями. Файловые
функции API Win32 позволяют приложениям получать доступ к файлам независимо от
основополагающей файловой системы или типа устройства. Однака предоставляемые при
этом возможности зависят от файловой системы, устройства или операционной системы.
10.1 Управление файлами и каталогами
Создание и открытие файлов
Для создания и открытия файлов всех типов служит единственная функция API Win32 –
CreateFile . В приложении можно указать, будет ли выполняться чтение из файла, запись в файл
или и то и другое. Можно также указать, предусматривается ли совместное использование этого
файла для чтения, записи либо для того и другого.
HANDLE CreateFile ( LPCTSTR lpszName, DWORD fdwAccess,
DWORD fdwShareMode, LPSECUR ITY_ATTR IBUTES lpsa,
DWORD
fdwCreate,
DWORD
fdwAttrsAndFlags,
HANDLE
hTemplateFile)
Возвращаемое
значение:
дескриптор
объекта
открытого
файла
или
INVALID_HANDLE_FAILURE в случае неудачи.
Параметры
lpszName – указатель на строку, содержащую имя файла, канала или другого
именованного объекта, который требуется открыть или создать
fdwAccess определяет режим доступа для чтения или записи значениями
GENERIC_READ и GENERIC_WRITE соответственно. Могут использоваться другие имена
констант. Эти значения можно объединить с помощью поразрядного “или”.
fdwShareMode – это объединение поразрядным “или” сочетание приведенных
ниже значений:
0 – совместный доступ к файлу не допускается.
FILE_SHARE_READ – другие процессы, включая тот, что сделал данный вызов,
могут открывать этот файл для параллельного чтения.
FILE_SHARE_WRITE – разрешается параллельная запись в файл.
Используя блокировку или другие механизмы, программист должен не допустить
параллельных модификаций одного и того же файла.
lpsa указывает на структуру SECURITY_ATTRIBUTES.
43
fdwCreate определяет, надо ли создать новый файл, заменить существующий файл и
т.д. Отдельные значения можно объединить оператором поразрядного “или”.
CREATE_NEW – завершается неудачей, если указанный файл уже существует; иначе
создается новый файл.
CREATE_ALWAYS – существующий файл заменяется новым.
OPEN_EXISTING – неудача, если файл не существует.
OPEN_ALWAYS – файл открывается и создается, если он не существует.
TRUNCATE_ EXISTING – устанавливается нулевая длина файла. В fdwAccess должен
быть задан по крайней мере доступ GENERIC_WRITE.
fdwAttrsAndFlags определяет атрибуты и флаги. Существует 16 флагов и
атрибутов. Атрибуты – это характеристики файла, а не открытого дескриптора HANDLE;
они игнорируются, когда открывается существующий флаг. Ниже перечислены некоторые
важные атрибуты и флаги.
FILE_ATTRIBUTE_NORMAL – может использоваться, только если не установлены
никакие другие атрибуты (однако флаги могут быть установлены).
FILE_ATTRIBUTE_ READONLY – приложения не могут не писать в файл, ни
удалять его.
FILE_FLAG_DELITE_ON_CLOSE – полезен для временных файлов. Файл удаляется,
когда закрывается его последний открытый дескриптор.
FILE_FLAG_OVERLAPPED – важен для асинхронного ввода-вывода. В Windows он
имеет значение NULL всегда, кроме устройств последовательного ввода-вывода.
Еще несколько дополнительных флагов определяют способ обработки файла и
позволяют Win32 оптимизировать быстродействие и целостность файла.
FILE_FLAG_WRITE_THROUGH – промежуточные кэши немедленно записываются в
файл на диске.
FILE_FLAG_NO_BUFFERING – в пространстве пользователя не выполняется
буферизация и кэширование, и данные передаются непосредственно в буфера
программы и из них.
FILE_FLAG_RANDOM_ACCESS – файл предназначен для произвольного доступа, и
Windows будет пытаться оптимизировать кэширование файла.
FILE_FLAG_SEQUENTIAL_SCAN – файл предназначен для последовательного
доступа, и Windows соответственно оптимизирует кэширование. Эти два режима
доступа не обязательны.
hTemplateFile – дескриптор файла, открытого с GENERIC_READ, определяющий
расширенные атрибуты для вновь создаваемого файла, причем значения fdwAttrsAndFlags
игнорируются. Обычно этот параметр равен NULL. Параметр hTemplateFile игнорируется,
когда открывается существующий файл. С помощью этого параметра можно сделать так,
чтобы атрибуты нового файла были такими же, как у существующего.
Закрытие файлов.
Дескрипторы закрываются и системные ресурсы освобождаются почти для всех
объектов одной универсальной функцией. При закрытии дескриптора также уменьшается на
единицу счетчик ссылок объекта.
BOOL CloseHandle (HANDLE hObject)
Возвращаемое значение: TRUE, если функция выполняется успешно; иначе FALSE.
Чтение файлов.
BOOL ReadFile ( HANDLE hFile, LPVOID lpBuffer,
DWORD nNumberOf BytesToRead, LPDWORD lpNumber OfBytesRead,
LPOVERLAPPED lpOverlapped)
Возвращаемое значение: TRUE, если чтение завершается успешно.
Параметры.
hfile – дескриптор файла с доступом GENERIC_READ
lpBuffer указывает на буфер памяти для получения входных данных.
44
nNumberOfBytesToRead – количество байтов, которые нужно прочитать из
файла.
lpNumberOfBytesRead – указывает на фактическое число байтов, прочитанное
функцией ReadFile.
lpOverlapped указывает на структуру OVERLAPPED.
Запись в файл.
BOOL WriteFile ( HANDLE hFile, CONST VOID *lpBuffer,
DWORD nNumberOf BytesToWrite, LPDWORD lpNumberOf BytesWritten,
LPOVERLAPPED lpOverlapped)
Возвращаемое значение: TRUE, если функция завершается успешно, иначе FALSE.
Win32 содержит множество функций управления файлами.
Для удаления файла:
BOOL DeleteFile (LPCTSTR lpszFileName)
Копирование файла:
BOOL CopyFile ( LPCTSTR lpszExistingFile, LPCTSTR lpszN ewFile,
BOOL fFaillfExists )
CopyFile копирует определенный по имени существующий файл и присваивает
копии указанное новое имя. Если файл с новым именем уже существует, он будет заменен,
только если fFail IfExists равно FALSE.
Еще две функции служат для переименования, или "перемещения", файла. Эти
функции также могут работать с каталогами.
BOOL MoveFile ( LPCTSTR lpszExisting, LPCTSTR lpszNew) ;
BOOL MoveFileEx ( LPCTSTR lpszExisting, LPCTSTR lpSzNew,
DWORD fdwFlags)
MoveFile завершается неудачно, если новый файл уже существует; для
существующих файлов следует применять MoveFileEx .
Параметры
lpszExisting определяет имя существующего файла или каталога.
lpszNew определяет имя нового файла или каталога, который в MoveFile не должен
существовать. Новый файл может находиться в другой файловой системе или на другом
диске, но новые каталоги должны быть на том же диске. Если этот параметр имеет значение
NULL, существующий файл удаляется.
fdwFlags определяет следующие опции:
• MOVEFILE_REPLACE_EXISTING — используется для замены существующего файла;
• MOVEFILE_WRITETHROUGH — гарантирует, что функция не возвращает управление,
пока скопированный файл не будет переписан из промежуточного буфера на диск;
• MOVEFILE_COPY_ALLOWED — когда новый файл находитсяна другом томе,
перемещение осуществляется путем выполнения CopyFile и DeleteFile;
• MOVEFILE_DELAY_UNTIL_REBOOT — этот флаг, который не может применяться
вместе с MOVEFILE_COPY_ALLOWED, разрешен только для администраторов и задерживает фактическое перемещение файла до перезапуска системы.
Перемещение (переименование) файлов связано с несколькими важными ограничениями.
• Поскольку в Windows 9x не реализована функция MoveFileEx , ее необходимо
заменять последовательностью CopyFile и DeleteFile. Это означает, что в какой-то
момент времени будут существовать две копии, что может вызвать проблемы при почти
заполненном диске или большом файле. Этот способ влияет на атрибуты файла иначе,
чем "настоящее" перемещение.
• В именах файлов или каталогов не допускаются подстановочные знаки. Следует
указывать конкретное имя.
10.2 Управление каталогами
Создание или удаление каталога выполняется двумя простыми функциями.
BOOL CreateDirectory ( LPCTSTR lpszPath,
45
LPSECUR ITY___ATTRIBUTES lpsa) ;
BOOL RemoveDirectory (LPCTSTR lpszPath)
lpszPath указывает на строку с завершающим нулем, содержащую имя каталога, который
должен быть создан или удален. Второй параметр (атрибуты безопасности) пока должен
быть равен NULL. Удалить можно только пустой каталог.
Эта функция устанавливает каталог:
BOOL SetCurrentDirectory (LPCTSTR lpszCurDir)
lpszCurDir — путь к новому текущему каталогу. Это может быть относительный путь или
полностью уточненный путь.
Следующая функция возвращает в буфер, заданный программистом, полностью
уточненный путь.
DWORD GetCurrentDirectory (DWORD cchCurDir, LPTSTR lpszCurDir)
Возвращаемое значение: длина строки возвращенного пути или требуемый размер
буфера, если он недостаточно велик; нуль при неудаче.
Параметры
cchCurDir — длина в символах (не в байтах) буфера для имени каталога. Длина должна
учитывать завершающий нулевой символ. lpszCurDir указывает на буфер, в который
записывается строка пути.
Возможности файловых систем не ограничиваются последовательной обработкой;
они также могут обеспечивать прямой доступ, блокировку файлов, работу с каталогами и
управление атрибутами файлов.
Указатели файлов
Windows для каждого открытого файла поддерживает указатель файла,
обозначающий позицию текущего байта в файле. Следующая операция WriteFile или
ReadFile начинает последовательное перемещение данных в файл или из файла с этой
позиции и увеличивает указатель файла на количество переданных байтов. При открытии
файла функцией CreateFile указатель обнуляется, указывая на начало файла, и
продвигается вперед с каждой последующей операцией чтения или записи. Первое, что
необходимо для прямого доступа к файлу, — это возможность установить указатель файла
на произвольную позицию. Для этого предназначена функция SetFilePointer .
Данная функция демонстрирует, как Windows работает с 64-разрядной системой
NTFS. Но эта методика не всегда хорошо подходит для SetFilePointer, поэтому
SetFilePointer лучше всего применять для небольших файлов.
DWORD SetFilePointer ( HANDLE hFile, LONG lDistanceToMove,
PLONG lpDistanceToMoveHigh, DWORD dwMoveMethod);
Возвращаемое значение: младшая часть (DWORD без знака) нового указателя
файла. Старшая часть нового указателя передается значению DWORD, на которое указывает
lpDistanceToMoveHigh
(если это не пустой указатель). В случае, ошибки функция
возвращает значение 0xFFFFFFFF.
Параметры
hFile — дескриптор открытого файла с доступом для чтения или записи (или для обеих
операций).
lDistanceToMove — значение, на которое перемещается указатель (типа LONG сo знаком),
или позиция файла (без знака), в зависимости от значения dwMoveMethod.
lpDistanceToMoveHigh указывает на старшую часть значения перемещения. Если этот
указатель равен NULL, функция может работать только с файлами, длина которых не
превышает 232-2. Этот параметр используется также для возврата старшей части указателя
файла. Младшая часть — это возвращаемое значение функции.
dwMoveMethod определяет один из следующих режимов перемещения:
• FILE_BEGIN — позиция нового указателя отсчитывается от начала файла, и
DistanceToMove интерпретируется как число без знака.
46
• FILE_CURRENT — указатель перемещается вперед или назад от текущей позиции
на расстояние, определяемое DistanceToMove, которое интерпретируется как
число со знаком. Положительное значение соответствует перемещению вперед.
• FILE_END — позиция отсчитывается назад или вперед от конца файла.
Эту функцию можно применять для определения длины файла, указывая нулевое
перемещение с конца файла.
Метод представления 64-разрядных позиций вызывает трудности, поскольку функция
может возвращать как позицию в файле, так и код ошибки. Например, предположим, что
позиция в файле равна 232-1 (т.е. 0xFFFFFFFF) И ЧТО В вызове функции также указана
старшая часть перемещения. Выяснить, что возвращает функция — действительную
позицию в файле или указание на ошибку, можно с помощью GetLastError, которая при
отсутствии ошибки возвращает NO_ERROR. ЭТО объясняет, почему длина файлов
ограничивается значением 232 - 2 байт, когда старшая часть перемещения не указана.
Другая причина затруднений состоит в том, что старшая и младшая части
перемещения разделены и обрабатываются по-разному. Младшая часть адреса передается по
значению и возвращается как значение функции, а старшая передается по ссылке как в
функцию, так и из нее.
Атрибуты файлов и работа с каталогами
В каталоге можно искать файлы и другие каталоги, удовлетворяющие указанному
образцу имени, а также получать атрибуты файлов. Для этого необходим дескриптор
поиска, который возвращается функцией FindFirstFile. Для получения нужных файлов
служит функция FindNextFile, а для завершения поиска — FindClose.
HANDLE FindFirstFile ( LPCSTSTR lpszSearchFile,
LPW IN32__FIND_DATA lpffd)
Возвращаемое значение:
дескриптор поиска.
INVALID_HANDLE_VALUE
указывает на неудачу.
FindFirstFile ищет соответствие имени как среди файлов, так и среди подкаталогов.
Возвращаемый дескриптор используется в последующем поиске.
Параметры
lpszSearchFile указывает на каталог или полное имя, которое может содержать
подстановочные знаки (? и *).
lpffd указывает на структуру WIN32_FIND_DATA, которая содержит информацию о
найденном файле или каталоге.
Структура WIN32_FIND_DATA определена следующим образом:
typedef struct _WIN32_FIND_DATA { DWORD dwFileAttributes;
FILETIME ftCreationTime; FILETIME ftLastAcces sTime;
FILETIME ft LastWriteTime; DWORD nFileSizeHigh;
DWORD nFileSizeLow; DWORD dwReserved0;
DWORD dwReserved1; TCHAR cFileName [MAX_PATH];
TCHAR cAlternateFiiename [14];
} WIN32_FIND_DATA;
dwFileAttributes можно проверить на соответствие значениям, приведенным в описании
CreateFile. Далее следуют три значения времени (время создания файла, время последнего
доступа и последней записи). Поля размера файла, содержащие его текущую длину, не
требуют пояснений.
cFileName — это не полное имя, а собственно имя файла.
cAlternateFile — версия имени файла для DOS формата 8.3 (включая точку). Это то же
сокращение имени файла, которое отображается в проводнике Windows. Оба имени —
строки с завершающим нулем.
Часто требуется найти в каталоге файлы, имена которых удовлетворяют образцу,
содержащему подстановочные знаки ? и *. Для этого нужно получить из FindFirstFile
47
дескриптор поиска, который содержит информацию об искомом имени, и вызвать функцию
FindNextFile.
BOOL FindNextFile ( HANDLE hFindFile, LPWIN32_FIND_DATA lpffd)
Функция FindNextFile возвращает FALSE либо при недопустимых параметрах, либо
если соответствие данному образцу уже нельзя найти. В последнем случае Get LastError
возвращает ERROR_NO_MORE_FILES.
Когда поиск закончен, закройте дескриптор поиска. Не используйте для этого функцию CloseHandle. Для закрытия дескрипторов поиска предназначена функция:
BOOL FindClose (HANDLE hFindFile)
Функция GetFileInformationByHandle позволяет получить ту же информацию для
определенного файла, указав его открытый дескриптор.
10.3 Другие методы получения атрибутов файлов и каталогов
Функции FindFirstFile и FindNextFile позволяют получать следующую
информацию об атрибутах файла: флаги атрибутов, три штампа времени и размер файла.
Для работы с атрибутами есть несколько других функций, включая ту, что позволяет их
устанавливать, и они могут работать непосредственно с дескриптором открытого файла без
просмотра каталога или указания имени файла. Три таких функции, GetFileSize,
GetFileSizeEx и SetEndOfFile, были описаны выше.
Другие функции служат для получения остальных атрибутов. Например, для
получения штампов времени открытого файла предназначена следующая функция:
BOOL GetFileTime ( HANDLE hFile, LPFILETIME lpftCreation,
LPFILETIME lpftLastAccess, LP FILETTMF lpftLastWrite)
Значения времени здесь и в структуре WIN32_FIND_DATA — 64-разрядные целые
числа без знака, указывающие 100-наносекундных единиц (107 единиц в секунду),
прошедших с исходного времени (1 января 1601 г.), в форме всеобщего скоординированного
времени (Universal Coordinated Time — UTC). Существует несколько удобных функций
для работы со временем.
• FileTimeToS ystemTime выражает время файла в более привычных единицах — от
лет до секунд и миллисекунд. Эта форма более пригодна, например, для отображения
или вывода времени.
• SystemTimeToFileTime
производит
обратное
преобразование:
времени,
выраженного в этих единицах, во время файла.
• CompareFileTime определяет, новее ли один файл, чем другой (-1), старше ли (+1)
или их возрасты равны (0).
• SetFileTime изменяет штампы времени; временные параметры, которые не
надо изменять, указываются в вызове функции как 0. NTFS поддерживает все
три временных параметрах, но FAT дает точный результат только для времени
последнего доступа.
• FileTimeToLocalFileTime и LocalFileTimeToFileTime преобразуют UTC в
местное время и обратно.
Функция GetFileType позволяет различать дисковые файлы, символьные файлы
(фактически это устройства наподобие принтеров и консолей) и каналы. Здесь файл также
указывается своим дескриптором.
Функция GetFileAttributes по указанному имени файла и каталога возвращает
только атрибуты.
DWORD GetFileAttributes (LPCTSTR lpszFileName)
Возвращаемое значение: атрибуты файла или 0xFFFFFFFF в случае неудачи.
Атрибуты можно проверить на соответствие комбинациям определенных значений.
Некоторые атрибуты, такие как флаг временного файла, устанавливаются при вызове
CreateFile. Значения атрибутов могут быть следующими:
48
FILE_ATTRIBUTE_DIRECTORY
FILE_ATTRIBUTE_NORMAL
FILE_ATTRIBUTE_READONLY
FILE_ATTRIBUTE_TEMPORARY
Функция SetFileAttributes изменяет эти атрибуты в указанном по имени файле.
Имена временных файлов
Следующая функция создает имена для временных файлов. Имя может относиться к
любому указанному каталогу и должно быть уникальным.
GetTempFileName выдает уникальное имя файла с расширением .tmp в указанном
каталоге и может создавать такой файл.
UINT GetTempFileName ( LPCTSTB lpszPath, LPCTSTR lpszPrefix,
UINT uUnique, LPCTSTR lpszTempFile)
Возвращаемое
значение:
уникальное
числовое
значение,
на
основе
которого создается имя файла. Если uUnique отлично от нуля, возвращается uUnique.
При неудаче возвращаемое значение — нуль.
Параметры
lpszPath — каталог для временного файла. Символ "." обычно обозначает текущий
каталог. Можно также указать каталог, предназначенный для временных файлов. Его имя
выдает функция Win32 GetTempPath.
lpszPrefix — префикс имени временного файла. Допускаются только символы ANSI.
Значение uUnique обычно равно нулю; в этом случае функция генерирует уникальное
четырехразрядное число и создает файл. Если это значение отлично от нуля, файл не
создается; это надо сделать с помощью CreateFile, возможно, указав
FILE_FLAG_DELETE_ON_CLOSE.
lpszTempFile указывает на буфер, который содержит имя временного файла. Длина
буфера в байтах должна быть не меньше МАХ_РАТН. Полученное в результате полное имя
— конкатенация пути, префикса, четырехразрядного шестнадцатеричного числа и
расширения .tmp
Основная литература: [1] – 36 - 74 c.
Контрольные вопросы:
1. Функции управления файлами.
2. Функции управления каталогами.
3. Параметры функции CreateFile()
Лекция 11. Тема: Дополнительные методы работы с файлами и
каталогами. Реестр
11.1 Блокировка файлов
Важный вопрос в любой системе с несколькими процессами — координация и
синхронизация доступа к совместно используемым объектам, например файлам.
Win32 может блокировать файлы полностью или частично, т.е. так, чтобы никакой
другой процесс (выполняемая программа) не мог обращаться к блокированной области
файла. Заблокированный файл может быть открыт только для чтения (совместный доступ)
или для чтения и записи (монопольный доступ). Следует подчеркнуть, что блокировка
связана с процессом. Любая попытка обратиться к части файла (с помощью ReadFile или
WriteFile) в нарушение существующей блокировки потерпит неудачу, так как блокировка
обязательна на уровне процесса. Любая попытка получить конфликтную блокировку также
будет неудачной, даже если процесс уже имеет блокировку. Блокировка файлов — это
ограниченная форма синхронизации параллельных процессов и потоков.
Наиболее общая функция блокировки LockFileEx реализована только в Windows
2000/NT. Менее общая функция LockFile может применяться в Windows 9x.
LockFileEx принадлежит к классу функций расширенного ввода-вывода; для
указания 64-разрядных позиций в файле и размера блокируемой области требуется
49
структура перекрытия, которая уже применялась ранее для указания позиции в функциях
ReadFile и WriteFile.
BOOL LockFileEx ( HANDLE hFile, DWORD dwFlags,
DWORD dwReserved,
DWORD nNumberOf BytesToLockLow,
DWORD nNumberOfBytesToLockHigh, LPOVER LAPPED lpOverlapped)
Функция LockFileEx блокирует область байтов в открытом файле для совместного
(несколько читающих процессов) или монопольного (один читающий и пишущий процесс)
доступа.
Параметры
hFile — дескриптор открытого файла. Дескриптор должен иметь права доступа либо
GENERIC_READ, либо GENERIC_READ и GENERIC_WRITE.
dwFlags определяет режим блокировки и устанавливает, следует ли ожидать, пока
блокировка не станет возможна.
• LOCKFILE_EXCLUSIVE_LOCK обозначает требование на монопольную блокировку
с возможностью чтения/записи. Если этот флаг не установлен, запрашивается
совместная блокировка (с возможностью чтения).
• LOCKFILE_FAIL_IMMEDIATELY указывает, что функция должна завершиться не
медленно, возвратив значение FALSE, если блокировку нельзя установить. Если
этот флаг не установлен, вызов функции блокируется, пока блокировка файла
не станет возможна.
dwReserved должен быть равен нулю. Следующие два параметра задают длину области
байтов и поэтому здесь не поясняются.
lpOverlapped указывает на структуру типа OVERLAPPED, содержащую начало области
байтов. Структура OVERLAPPED содержит три компонента данных, которые должны быть
заданы (остальные компоненты игнорируются); первые два определяют позицию начала
блокируемой области.
• DWORD offset (именно так, а не OffsetLow).
• DWORD OffsetHigh.
• HANDLE hEvent должен быть равен нулю.
Блокировка файла снимается с помощью функции UnlockFileEx ; параметры для нее
указываются те же самые, кроме dwFlags.
BOOL UnlockFileEx ( HANDLE hFile, DWORD dwReserved,
DWORD nNumberOfBytesToLockLow,
DWORD nNumberOfBytesToLockHigh,
LPOVERLAPPED lpOverlapped)
При блокировке файлов следует учитывать несколько факторов.
• Снятие блокировки должно относиться точно к такой же области, что и
предшествующая блокировка. Не допускается, например, разблокировать две
заблокированных ранее области или часть заблокированной области. Попытка
разблокировать область, которая не точно соответствует существующей блокировке,
потерпит неудачу; функция возвратит FALSE, И системное сообщение укажет,
что блокировки не существует.
• Блокировки не могут пересекаться с ранее блокированными областями файла,
если при этом возникает конфликт.
• Допускается блокировать область, выходящую за пределы файла. Этот подход
может быть полезен, когда процесс или поток расширяет файл.
• Блокировки не наследуются вновь создаваемым процессом.
В табл. 11.1 показана логика блокировки в случае, когда вся область или ее часть уже
имеют блокировку. Эта логика действует и тогда, когда предыдущая блокировка
принадлежит этому же процессу.
50
Таблица 11.1. Логика требования блокировки
Требуемый тип блокировки
Существующая блокировка Совместная блокировка
Монопольная блокировка
Нет
Совместная (одна или
несколько)
Исключительная
Предоставляется
Предоставляется
Предоставляется
Предоставляется
Не предоставляется
Не предоставляется
В табл. 11.2 показана логика, применяемая в том случае, когда процесс пытается
выполнить чтение или запись в области файла с одной либо несколькими блокировками,
принадлежащими разным процессам, во всей или в части области, допускающей
чтение/запись. Неудачное чтение или запись может принимать форму частично завершенной
операции, если блокирована только часть записи.
Таблица 11.2. Блокировки и операции ввода-вывода
Существующая
блокировка
Операция ввода-вывода
Чтение
Запись
Нет
Проходит успешно
Проходит успешно
Совместная (одна Проходит успешно. Вызывающий Неудача
или несколько)
процесс не обязательно должен
владеть блокировкой
Исключительная Проходит успешно, если бло- Проходит успешно, если блокировкой владеет вызывающий кировкой владеет вызывающий
процесс. В противном случае — процесс. В противном случае —
неудача
неудача
Операции чтения и записи обычно имеют форму функций ReadFile и WriteFile или
их расширенных версий ReadFileEx и WriteFileEx . Для диагностики неудачи чтения или
записи вызывается GetLastError.
Еще одна форма ввода-вывода — обращение к памяти, в которую отображается файл.
Конфликты блокировки не обнаруживаются при обращении к памяти; они проявляются,
когда вызывается функция MapViewOfFile. Эта функция делает часть файла доступной
для процесса, а потому в этот момент следует проверить блокировку.
LockFile — это ограниченная специальная версия функции блокировки. Она
обеспечивает рекомендательную блокировку (advisory locking). В этом случае
предоставляется только монопольный доступ и функция возвращает управление немедленно.
Иначе говоря, LockFile не блокируется. Установлена ли блокировка, можно проверить по
возвращаемому значению.
Снятие блокировки файла
За каждым успешным вызовом LockFileEx должно следовать одно соответствующее
ему обращение к UnlockFileEx (это правило распространяется также на LockFile и
UnlockFile). Если программа не может снять блокировку или задерживает ее дольше, чем
необходимо, другие программы не смогут продолжать работу или же это негативно повлияет
на их быстродействие. Следовательно, программы должны быть аккуратно спроектированы
и реализованы так, чтобы блокировки снимались как можно скорее; логики, которая
заставляла бы программу обходить операцию разблокировки, следует избегать.
Удобный способ убедиться, что блокировка снята, предоставляют обработчики
завершения.
51
11.2 Реестр
Реестр — это централизованная иерархическая база данных конфигурации
приложений и системы. Доступ к реестру осуществляется через разделы реестра, которые
аналогичны каталогам файловой системы. Раздел может содержать другие разделы или пары
"параметр-значение", которые можно уподобить именам файлов и их содержимому.
Пользователь или администратор могут просматривать и редактировать содержимое
реестра с помощью редактора реестра, который вызывается командой REGEDIT32. Кроме
того, программы могут управлять реестром через функции API, описанные в этом разделе.
Пары "параметр-значение" в реестре содержат, в частности, следующую
информацию:
• номер версии и номер выпуска операционной системы, а также имя зарегистрированного пользователя;
• информацию подобного рода обо всех правильно установленных приложениях;
• информацию о типе процессора компьютера, количестве процессоров, систем
ной памяти и т.д.;
• пользовательскую информацию, такую как основной каталог и настройки приложений;
• данные безопасности, например имена учетных записей пользователей;
• установленные службы;
• соответствия между расширениями файлов и выполняемыми программами, которые
применяются оболочкой интерфейса пользователя, когда он щелкает на значке файла;
например, расширение .doc может соответствовать Microsoft Word;
• соответствия между сетевыми адресами и именами компьютеров.
Разделы реестра
Можно заметить аналогию между каталогами файловой системы и разделами
реестра. Каждый раздел может содержать другие разделы, а также последовательность пар
"параметр-значение". Подобно тому как к файловой системе можно обращаться с помощью
полных имен файлов, к реестру можно обращаться по именам разделов. Точками входа в
реестр служат несколько стандартных разделов.
1. HKEY_LOCAL_MACHINE хранит физическую информацию о машине, а также
данные об установленном программном обеспечении. Для каждой установленной
программы создаются подразделы вида
SOFTWARE\Название_компании\Название_продукта\Версия.
2. HKEY_USERS содержит данные конфигурации пользователей.
3. HKEY_CURRENT_CONFIG содержит текущие параметры настройки, такие как раз
решающая способность монитора и шрифты.
4. HKEY_CLASSES_ROOT содержит подразделы, определяющие соответствие между
расширениями файлов и классами и приложениями, используемыми оболочкой
для доступа к объектам с этими расширениями. В этом разделе также записаны
все данные, необходимые для объектной модели компонентов Microsoft (COM).
5. HKEY_CURRENT_USER. Сюда относится информация данного пользователя,
включая переменные окружения, принтеры и пользовательские настройки
приложений.
Управление реестром
Функции управления реестром могут получать и устанавливать значения параметров,
а также создавать новые подразделы и пары "параметр—значение". Для указания
существующих разделов и получения новых применяются дескрипторы разделов типа
HKEY. Значения имеют определенный тип; существует несколько возможных типов,
например строки, двойные слова (DWORD) и расширяемые строки, параметры которых
могут заменяться переменными окружения.
52
Управление разделами
Функция RegOpenKeyEx открывает подраздел. Начиная с одного из стандартных
зарезервированных разделов, можно обойти весь реестр и получить дескриптор любого
нужного подраздела.
LONG RegOpenKeyEx { HKEY hKey, LPCTSTR lpSubKey, DWORD
ulOptions, REGSAM samDesired, PHKEY phkResult)
Параметры
hKey идентифицирует дескриптор открытого в данный момент раздела или одного из
стандартных зарезервированных разделов;
phkResult указывает на переменную типа HKEY, которая принимает дескриптор вновь
открытого раздела.
lpSubKey
—
имя
подраздела.
Оно
может
содержать
путь,
например
Microsoft \WindowsNT\CurrentVersion. Значение NULL вызывает открытие нового
раздела-дубликата hKey. Значение ulOptions должно быть равно нулю.
samDesired — маска доступа, описывающая атрибуты безопасности нового раздела.
Возможные значения — KEY_ALL_ACCESS, KEY_WRITE, KEY_QUERY_VALUE И
KEY_ENUMERATE_SUBKEYS.
Возвращаемое значение — это обычно ERROR_SUCCESS. Любое другое значение
указывает на ошибку. Для закрытия открытого раздела служит функция RegCloseKey, в
единственном параметре которой указывается дескриптор раздела.
Чтобы получить имена подразделов, следует указать дескриптор раздела в функции
RegEnumKeyEx .
Существуют также функции, предназначенные для получения пар "параметрзначение": RegEnumValue и RegQueryValueEx . Функция RegSetValueEx записывает
типизированные данные в поле значения открытого раздела.
RegEnumKeyEx выводит подразделы открытого раздела реестра подобно тому, как
FirstFile и FindNextFile выводят содержимое каталога. Эта функция выдает имя раздела,
строку класса и время последнего изменения.
LONG RegEnumKeyEx ( HKEY hKey, DWORD dwIndex,
LPTSTR lpMame, LPDWORD lpcbName, LPDWORD lpReserved,
LPTSTR lpClass, LPDWORD lpcbClass,
PFILETIME lpftLastWriteTime)
Параметр dwIndex при первом вызове должен быть равен нулю, а при каждом
последующем — увеличиваться на единицу. Имя раздела и его размер, а также строка класса
и ее размер возвращаются обычным способом. Функция возвращает ERROR_SUCCESS или
значение ошибки.
Можно также создавать новые разделы. Для этого предназначена функция
RegCreateKeyEx . Разделам можно присваивать атрибуты безопасности таким же образом,
как каталогам и файлам.
LONG RegCreateKeyEx ( HKEY hKey, LPCTSTR lpSubKey,
DWORD Reserved, LPTSTR lpClass, DWORD dwOptions,
REGSAM samDesired, LPSE CUR ITY_ATTR IBUTES lpSecurit yAttributes,
PHKEY phkResult, LPDWORD lpdwDisposition)
Параметры
lpSubKey — имя нового подраздела в открытом разделе, обозначенном дескриптором
hKey.
lpClass — класс или тип объекта раздела, описывающий данные, которые он
представляет. Возможные значения включают, в частности, REG_SZ (строку с
завершающим нулем) и REG_DWORD (двойное слово).
dwOptions — либо нуль, либо одно из взаимно исключающих значений:
53
REG_OPTION_VOLATILE или REG_OPTION_NON_VOLATILE. Постоянная (nonvolatile) информация реестра хранится в файле и восстанавливается при перезапуске
системы. Непостоянные разделы хранятся в памяти и не восстанавливаются.
samDesired — то же значение, что и в RegOpenKeyEx .
lpSecurityAttributes может иметь значение NULL или указывать на атрибуты
безопасности. Возможные права доступа выбираются из значений, указанных для
samDesired .
lpdwDisposition указывает на значение DWORD, которое отмечает, существовал ли
раздел
раньше
(REG_OPENED_EXISTING_KEY)
или
он
создан
вновь
(REG_CREATED_NEW_KEY).
Для удаления раздела служит функция RegDeleteKey. Два ее параметра — дескриптор открытого раздела и имя подраздела.
Управление параметрами
Вывести параметры указанного открытого раздела можно с помощью функции
RegEnumValue. Следует указать индекс параметра: при первом вызове — нуль, при
последующих — с увеличением на единицу. Функция выдает строку с именем параметра и
его длиной. Также возвращается значение параметра и его тип.
LONG RegEnumValue ( HKEY hKey, DWORD dwIndex,
LPTSTR lpValueName, LPDWORD lpcbValueName,
LPDWORD lpReserved, LPDWORD lpType,
LPBYTE lpData, LPDWORD lpcbData)
Текущее значение параметра возвращается в буфере, на который указывает lpData.
Размер результата можно получить в lpcbData.
Тип данных, на который указывает lpType, может иметь множество значений,
включая REG_BINARY, REG_DWORD, REG_SZ (строку) и REG_EXPAND_SZ
(расширяемую строку с параметрами, заменяемыми переменными окружения). Список всех
типов можно получить во встроенной справке.
Возвращаемое значение функции позволяет узнать, выведены ли все параметры.
Значение равно ERROR_SUCCESS, если найден правильный параметр.
Функция RegQueryValueEx подобна приведенной выше за исключением того, что в
ней указывается имя параметра, а не индекс. Эту функцию можно использовать, если
известны имена параметров, в противном случае раздел следует просматривать с помощью
RegEnumValueEx .
Чтобы установить значение параметра в открытом разделе, применяется функция
RegSetValueEx . В ней указываются имя параметра, тип значения и само значение.
LONG RegSetValueEx ( HKEY hKey, LPCTSTR lpValueName,
DWORD Reserved,
DWORD dwType, CONST BYTE *lpData,
CONST cbData)
Наконец, для удаления параметров служит функция RegDeleteValue.
Основная литература: [1] – 62 - 89 c.
Контрольные вопросы:
1. Назначение блокировки файлов.
2. Назначение реестра
3. API функции для работы с реестром
Лекция 12. Тема: Использование ввода-вывода
12.1 Стандартные устройства и консольный ввод-вывод
Win32 имеет три стандартных устройства для ввода, вывода и сообщения об ошибках.
Имеется функция для получения дескрипторов стандартных устройств.
HANDLE GetStdHandle (DWORD nStdHandle)
Возвращаемое значение: допустимый дескриптор, если функция завершается
успешно; иначе INVALID_HANDLE_VALUE.
54
Параметр GetStdHandle nStdHandle должен иметь одно из следующих значений:
- STD_INPUT_HANDLE;
- STD_OUTPUT_HANDLE;
- STD_ERROR_HANDLE.
Стандартные устройства обычно назначены консоли и клавиатуре. Стандартный вводвывод можно перенаправлять.
GetStdHandle не создает новый дескриптор стандартного устройства и не дублирует
прежний. Последовательные вызовы с одним и тем же параметром возвращают одно и то же
значение дескриптора.
Закрытие дескриптора стандартного устройства делает это
устройства недоступным для последующего использования. Поэтому примеры программ
часто открывают стандартное устройства, работают с ним, но не закрывают.
BOOL SetStdHandle (DWORD nStdHandle, HANDLE hHandle)
Возвращаемое значение: TRUE или FALSE, в зависимости от успеха или неудачи.
Параметры SetStdHandle. Возможное значение nStdHandle – те же, что и в
GetStdHandle. Параметр hHandle определяет открытый файл, который должен быть
стандартным устройством.
Обычно метод перенаправления стандартного ввода-вывода в пределах процесса
состоит в том, чтобы использовать последовательность SetStdHandle и GetStdHandle.
Полученный в результате дескриптор используется в последующих операциях ввода-вывода.
Два имени файлов зарезервированы для консольного ввода (с клавиатуры) и
консольного вывода: CONINS и CONOUTS. Консоль можно использовать независимо от
любого перенаправления этих стандартных устройств; для этого нужно просто открыть
дескрипторы CONINS или CONOUTS с помощью CreateFile.
Для консольного ввода-вывода можно применить ReadFile и WriteFile, но лучше
использовать специальные функции ReadConsole и WriteConsole. Основные их
преимущества состоят в том, что эти функции обрабатывают универсальные символы
(TCHAR), а не байты, а также учитывают режим консоли, установленный функцией
SetConsoleMode.
BOOL SetConsoleMode ( HANDLE hConsole, DWORD fdevMode)
Возвращаемое значение: TRUE, если функция завершается успешно.
Параметры SetConsoleMode .
nConsole идентифицирует буфер ввода или экрана, который должен иметь атрибут доступа
GENERIC_WRITE, даже если соответствующее устройство допускает только ввод.
fdevMode определяет режим обработки символов. Значение каждого флага указывает,
применяется ли этот флаг к консольному вводу или к выводу. При создании буфера
установлены все флаги, кроме ENABLE_WINDOW_INPUT.
- ENABLE_LINE_INPUT функция чтения (ReadConsole) завершается, когда
встречается символ возврата каретки.
- ENABLE_ECHO_INPUT читаемые символы дублируются на экране.
- ENABLE_PROCESSED_INPUT
система
обрабатывает
символы
возврата
(backspace), возврата каретки и перевода строки.
- ENABLE_ PROCESSED _OUTPUT система обрабатывает символы возврата
(backspace), табуляции, звукового сигнала,возврата каретки и перевода строки.
- ENABLE_WRAP_AT_EOL_OUTPUT разрешается перенос строк как при обычном,
так и при дублированном выводе.
Если функция SetConsoleMode терпит неудачу, режим не изменяется и
возвращается значение FALSE. Код ошибки, как обычно, возвращает Get LastError.
Функции ReadConsole и WriteConsole подобны ReadFile и WriteFile.
BOOL ReadConsole (HANDLE hConsoleInput, LPVOID lpv Buffer,
DWORD cchToRead, LPDWORD lpcchRead, LPVOID lpvReserved)
Возвращаемое значение: TRUE, если функция завершается успешно.
55
Параметры функции почти такие же, как в ReadFile. Два параметра выражаются в
универсальных символах, а не в байтах, а lpvReserved должен иметь значение NULL.
Процесс может иметь только одну консоль. Консоль бывает необходима во многих
случаях, например при разработке сервера или приложения GUI, чтобы отображать
состояние или отладочную информацию. Для этого предназначены две простые функции без
параметров.
BOOL FreeConsole (VOID)
BOOL AllocConsole (VOID)
FreeConsole отделяет процесс от его консоли. AllocConsole создает новую
консоль, связанную со стандартным вводом, выводом и выдачей ошибок процесса.
AllocConsole завершается неудачно, если процесс уже имеет консоль; чтобы избежать этой
проблемы, поместите перед этой функцией вызов FreeConsole.
12.2 Асинхронный ввод-вывод и порты завершения
Если поток должен ждать, пока ввод-вывод не завершится, то такие операции вводавывода являются синхронными с потоком. А в случае асинхронного ввода-вывода поток
продолжает свое выполнение, не ожидая завершения операции ввода-вывода.
В Win32 существует три метода выполнения асинхронного ввода-вывода:
• Многопоточный ввод-вывод. Каждый поток в процессе или совокупности процессов
выполняет обычный синхронный ввод-вывод, в то время как другие потоки могут
продолжать работу.
• Собственно асинхронный ввод-вывод (или ввод-вывод с перекрытием). После начала
чтения, записи или другой операции ввода-вывода поток продолжает выполнение.
Если потоку для продолжения нужны результаты ввода-вывода, он ожидает сигнала
дескриптора либо указанного события.
• Процедуры завершения (или расширенный ввод-вывод). Когда операция ввода-вывода
завершается, система вызывает в потоке указанную процедуру завершения.
Ввод-вывод с перекрытием
В первую очередь для асинхронного ввода-вывода, с перекрытием или расширенного,
следует установить атрибут перекрытия дескриптора файла или другого объекта. Для этого
в вызове CreateFile или другой функции, которая создает файл, именованный объект или
другой дескриптор, указывается флаг FILE_FLAG_OVERLAPPED.
Структуры перекрытия в функции LockFileEx можно использовать для вводавывода с перекрытием. Эти структуры — необязательные параметры четырех функций
ввода-вывода (ReadFile, WriteFile, TransactNamedPipe , ConnectNamedPipe ),
которые потенциально могут блокировать их при завершении операции.
Если указать FILE_FLAG_OVERLAPPED В структуре fdwAttrsAndFlags (для
CreateFile) или в структуре fdwOpenMode (для CreateNamedPipe), канал или файл
должен использоваться только в асинхронном режиме. Ввод-вывод с перекрытием не
действует для анонимных каналов.
Структуры перекрытия
Структура перекрытия (указываемая, например, в параметре lpOverlapped функции
ReadFile) содержит следующие данные:
• позицию в файле (64-разрядную), на которой должно начаться чтение или запись;
• событие (с ручным сбросом), которое будет сгенерировано при завершении операции.
Структура перекрытия выглядит следующим образом:
typedef struct_OVERLAPPED { DWORD Internal; DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh; HANDLE hEvent;
} OVERLAPPED
Позиция в файле (указатель) занимает два элемента — offset и OffsetHigh, хотя
старшая часть обычно равна нулю. Элементы Internal и InternalHigh зарезервированы
для системы и не должны использоваться.
56
hEvent — дескриптор события (созданный функцией CreateEvent). Событие может
быть именованным или неименованным, но обязательно должно иметь ручной сброс. hEvent
может быть равен NULL; в этом случае программа может ожидать сигнала дескриптора
файла, который также является объектом синхронизации. Когда hEvent равен NULL,
система сообщает о завершении работы по дескриптору файла, т.е. дескриптор файла
становится объектом синхронизации.
Это событие немедленно сбрасывается (переводится в пассивное состояние)
системой, когда программа делает вызов операции ввода-вывода. Когда операция вводавывода завершается, событие становится активным.
Отмена операций ввода-вывода с перекрытием
Функция Cancel IO, возвращающая логическое значение, отменяет незавершенные
операции асинхронного ввода-вывода на указанном дескрипторе (эта функция имеет только
один параметр). Все операции, которые вызывающий поток начал на этом дескрипторе,
отменяются. На операции, начатые другими потоками, это не влияет. Код ошибки
отмененных операций — ERROR_OPERATION_ABORTED.
Расширенный ввод-вывод с процедурами завершения
Вместо того чтобы заставлять поток ожидать сигнала завершения от события или
дескриптора, система может вызвать указанную пользователем процедуру завершения, когда
операция ввода-вывода закончится. Эта процедура завершения может запустить следующую
операцию ввода-вывода и выполнить любое другое действие.
Как программа может задать процедуру завершения? Существует семейство
расширенных функций ввода-вывода, отличающихся суффиксом Ех, которые имеют
дополнительный параметр для адреса процедуры завершения. Расширенные функции чтения
и записи — ReadFileEx и WriteFileEx. Кроме того, следует использовать одну из пяти
ожидающих функций предупреждения.
• WaitForSingleObjectEx
• WaitForMultipleObjectsEx
• SleepEx
• SignalObjectAndWait
• MsgWaitForMultipleObjectsEx
Расширенный ввод-вывод иногда называется вводом-выводом с предупреждением.
Порты завершения ввода-вывода
Порты завершения ввода-вывода объединяют возможности ввода-вывода с
перекрытием и возможности независимых потоков и наиболее полезны в серверных
программах.
Порты завершения ввода-вывода позволяют создавать ограниченное количество
потоков сервера в группе потоков при наличии очень большого числа дескрипторов
именованных каналов (или сокетов). Дескрипторы не связаны с отдельными потокамирабочими сервера, но поток сервера может обрабатывать данные от любого дескриптора, на
котором они доступны.
Следовательно, порт завершения ввода-вывода представляет собой набор
дескрипторов с перекрытием, а потоки ожидают сигнала порта. Когда чтение или запись на
одном из дескрипторов заканчиваются, один поток переходит в активное состояние и
получает данные и результаты операции ввода-вывода. После этого он может обработать
данные и снова ожидать сигнала порта.
Первая задача состоит в том, чтобы создать порт завершения ввода-вывода и внести
в него дескрипторы с перекрытием.
Управление портами завершения ввода-вывода
Функция CreateIoCompletionPort служит как для создания порта, так и для
добавления дескрипторов. Поскольку она должна выполнять две задачи, порядок
использования ее параметров довольно сложен.
HANDLE CreateIoCompletionPort ( HANDLE FileHandle,
57
HANDLE ExistingCompletionPort, DWORD CompletionKey,
DWORD NumberOfConcurrentThreads );
Параметры
Порт завершения ввода-вывода — это совокупность дескрипторов файлов, открытых
в режиме OVERLAPPED. Параметр FileHandle — это асинхронный дескриптор, который
добавляется в порт. Если в этом параметре указать значение INVALID_HANDLE_VALUE,
функция создает новый порт завершения ввода-вывода и возвращает его как возвращаемое
значение. Следующий параметр, ExistingCompletionPort , в этом случае должен иметь
значение NULL.
ExistingCompletionPort — порт, созданный при первом вызове, и порт, в который
добавляется дескриптор из первого параметра. Если добавление прошло успешно, функция
также возвращает дескриптор порта, при неудаче возвращается NULL.
CompletionKey задает ключ, который будет входить в пакет завершения для
FileHandle. Этот ключ обычно является индексом массива структур данных, содержащих
тип операции, дескриптор и указатель на буфер данных.
NumberOfConcurrentThreads указывает максимальное число потоков, которые
могут работать одновременно. Все потоки вне этого предела, ожидающие сигнала порта,
остаются заблокированными. Если этот параметр равен нулю, пределом будет число
процессоров в системе.
С портом завершения ввода-вывода можно связать. неограниченное количество
асинхронных дескрипторов. При первом вызове CreateIoCompletionPort создается порт и
определяется максимальное число потоков. Для каждого дескриптора, который должен быть
связан с портом, эту функцию надо вызывать снова. К сожалению, не существует способа
удаления дескриптора из порта завершения.
Дескрипторы, связанные с портом, не должны использоваться в функциях
ReadFileEx или WriteFileEx . В документации Microsoft не рекомендуется совместно
использовать файлы или другие объекты с помощью других открытых дескрипторов.
Основная литература: [1] – 46 - 52 c., 359 – 386 с.
Контрольные вопросы:
1. Каким устройствам обычно назначаются стандартные устройства?
2. Специальные функции консольного ввода-вывода
3. метода выполнения асинхронного ввода-вывода
Лекция 13. Тема: Безопасность объектов Win32
Цели системы безопасности
У системы безопасности две основные цели: проверка уровня доступа и контроль за
действиями клиента. Чтобы выполнить первую цель, необходимо убедиться в том, что
пользователь обладает правом доступа к защищаемому объекту. Если это не так, попытка
доступа должна окончиться неудачей, а пользователь должен получить сообщение «доступ
запрещен» (access denied). Вторая функция системы безопасности — аудит, то есть
слежение за действиями клиента — выполняется далеко не всегда. Аудит подразумевает
документирование в файле журнала действий клиента, связанных с доступом к тому или
иному защищаемому объекту. Администраторы могут включать или выключать систему
аудита, они также могут настроить ее таким образом, чтобы документировались не все
попытки доступа, а только те, которые были связаны с нарушением нрав доступа и в
результате оказались безуспешными.
Права и привилегии
Система безопасности следит за выполнением тех или иных действий клиента при
помощи двух основных механизмов. Эти два механизма — это права и привилегии.
Привилегия — это разрешение на выполнение некоторым пользователем некоторого
действия в отношении всей системы в целом. Например, пользователь может обладать
58
привилегиями подключаться к системе, осуществлять резервное копирование или
форматирование дисков, отлаживать программы.
В отличие от привилегий права применяются иначе. Право — это разрешение на
выполнение некоторым пользователем некоторого действия в отношении некоторого
отдельного объекта. Права назначаются в отношении конкретных объектов, доступ к
которым контролируется операционной системой. Правом могут обладать не только
отдельные пользователи, но и группы пользователей.
Атрибуты безопасности
Почти любой объект, созданный с помощью системного вызова Create, в первую
очередь имеет параметр атрибутов безопасности. Поэтому программы могут защищать
файлы, процессы, потоки, события, семафоры, именованные каналы и т.д.
Создание безопасности начинается с включения в вызов Create структуры
SECURITY_ATTRIBUTES. Важный элемент структуры SECURITY_ATTRIBUTES –
указатель на дескриптор безопасности, в котором представлены сведения о владельце
объекта и пользователях, которым предоставлены или запрещены различные права.
Отдельный процесс идентифицируется своим маркером доступа, который определяет
пользователя-владельца и принадлежность к группе. Когда процесс пытается обратиться к
объекту, ядро Windows NT может опознать процесс по этому маркеру и на основании
информации в дескрипторе безопасности решить, имеет ли процесс требуемые права на
обращение к объекту.
Атрибуты безопасности имеют следующее определение:
Typedef struct _ SECURITY_ATTR IBUTES {
DWORD nLength;
// Размер структ уры
LPVOID lpSecurit yDescriptor ;
// Дескриптор безопасности,
// контролирующий дост уп к объект у
BOOL bInheritHandle; // Разрешает наследование дескриптора
// (handle)
// дочерним процессом
} SECURITY_ATTRIBUTES;
Элемент nLength должен иметь значение sizeof (SECURITY_ATTRIBUTES).
Обзор элементов безопасности: дескриптор безопасности.
Дескриптор
безопасности
инициализируется
функцией
InitializeSecurit yDescriptor.
Структура SECURITY_DESCRIPTOR хранит в себе информацию, связанную с
защитой некоторого объекта от несанкционированного доступа. В этой структуре, которую
называют дескриптором безопасности, содержится информация о том, какие пользователи
обладают правом доступа к объекту и какие действия эти пользователи могут выполнить в
отношении этого объекта.
Дескриптор безопасности объекта хранит в себе следующую информацию;
- SID (securit y identifier ) владельца объекта;
- SID основной группы владельца объекта;
- список разграничительного контроля доступа (Discretionary Access Control List,
DACL-дискреционный список управления доступом);
- системный список управления доступом (S ystem Access Control List, SACL);
- управляющая информация (например, сведения о том, как списки ACL передают
информацию дочерним дескрипторам безопасности);
Список DACL определяет, кто обладает (и кто не обладает) правом доступах объекту.
Список SACL определяет, информация о каких действиях вносится в файл журнала.
Функция InitializeSecurit yDescriptor инициализирует указанный вами дескриптор
таким образом, что в нем отсутствует DASL, SACL, владелец и основная группа владельца, а
все управляющие флаги установлены в значение FALSE, При этом дескриптор имеет
абсолютный формат. Что это значит? Дескриптор в абсолютном (absolute) формате содержит
59
лишь указатели на информацию, связанную с защитой объекта. В отличие от этого
дескриптор в относительном (self-relative) формате включает в себя всю необходимую
информацию, которая располагается в памяти последовательно поле за полем. Таким
образом, абсолютный дескриптор нельзя записать на диск (так как при последующем чтении
его с диска все указатели потеряют смысл), а относительный дескриптор — можно.
Windows позволяет преобразовывать абсолютный дескриптор в относительную форму
и обратно. Обычно это требуется лишь в случае, если вы записываете дескриптор на диск и
считываете дескриптор с диска. Системные вызовы, требующие передачи указателя на
дескриптор безопасности, работают только с дескрипторами в абсолютном формате.
Преобразование осуществляется при помощи вызовов MakeSelfRelativeSD и
MakeAbsoluteSD. Преобразовать абсолютную форму в относительную несложно. Однако
обратное преобразование (из относительной в абсолютную) обычно выполняется в
несколько этапов.
Функции
SetSecurit yDescriptorOwner
и
SetSecurit yDescriptorGroup
связывают SID с дескрипторами безопасности. Списки ACL инициализируются с помощью
функции InitializeAcl и затем связываются с дескриптором безопасности функциями
SetSecurit yDescriptorDacl или SetSecurityDescriptorSacl .
Списки контроля доступа
Каждый ACL представляет собой набор (список) элементов управления доступом
(access control entry — ACE). Существует два типа элементов управления доступом: для
разрешения и для запрещения доступа.
Сначала ACL инициализируется функцией InitializeAcl, а затем в него добавляются
элементы управления доступом. Каждый АСЕ содержит идентификатор SID и маску
доступа, которая определяет предоставляемые или запрещаемые права. Типичные права
доступа — GENERIC_READ и DELETE.
Для добавления элементов управления доступом в разграничительные ACL служат
две функции: AddAccessAllowedAce и AddAccessDeniedAce. Первая из этих функций
предназначена для добавления к SACL, что вызывает аудит доступа к указанному SID.
И наконец, для удаления элементов управления доступом служит функция
DeleteAce, а для обращения к ним — GetAce.
Использование безопасности объектов Win32
Каждый процесс также имеет идентификаторы SID (в маркере доступа), на
основании которых ядро системы определяет, разрешен ли доступ и надо ли вести аудит.
Маркер доступа также может давать владельцу некоторые привилегии (неотъемлемую
способность выполнять операции, которая имеет более высокий приоритет, чем права в
ACL). Благодаря этому администратор может обладать привилегиями чтения и записи во все
файлы, даже не имея определенных прав в списках ACL файлов.
Легко увидеть, что происходит, когда процесс выдает запрос на обращение к объекту.
Во-первых, процесс имеет некоторые привилегии на основании своего удостоверения
пользователя и его принадлежности к группе. Эти привилегии записаны в идентификаторах
SID.
Если идентификаторы пользователя и группы не разрешают доступа, ядро системы
ищет права доступа в ACL. Первый элемент, который определенно предоставляет или
запрещает требуемую службу, имеет решающее значение. Поэтому важен порядок, в
котором элементы управления доступом вводятся в ACL. Часто АСЕ, запрещающие доступ,
помещаются в начало списка, чтобы пользователь, определенно лишенный доступа, не
получил его на основании членства в группе, имеющей такой доступ. Но можно смешивать
элементы разрешения и запрещения, чтобы получить желательную семантику. АСЕ,
запрещающий все права, может быть последним в списке; это гарантирует, что всем
пользователям, явно не упомянутым в АСЕ, доступ будет запрещен.
60
Права объекта и доступ к объекту
Объект, например файл, получает свои права при создании, хотя они могут быть
изменены позже. Процесс запрашивает доступ к объекту в тех случаях, когда он хочет
использовать дескриптор, например при обращении к CreateFile. Запрос дескриптора
содержит в одном из своих параметров необходимый доступ, например GENERIC_READ.
Если процесс имеет права на получение требуемого доступа, запрос проходит успешно.
Разные дескрипторы одного и того же объекта могут иметь различный доступ. Для флагов
доступа применяются те же значения, которые используются для предоставления или
запрещения прав при создании ACL.
Инициализация описателя безопасности
Работа с дескриптором начинается с его инициализации. При этом в параметре psd
необходимо указать адрес правильной структуры SECURITY_DESCRIPTOR. Эти структуры
не прозрачны для программиста, и для работы с ними применяются определенные функции.
Параметру dwRevision присваивается значение константы SECURITY_DESCRIPTOR_REVISION
BOOL InitializeSecurit yDescriptor (
PSECUR ITY_DESCRIPTOR psd, DWORD dwRevision)
Идентификаторы безопасности
Идентификаторы SID применяются в Win32 для опознания пользователей и групп.
Программа может искать SID по имени учетной записи, которая соответствует пользователю, группе, домену и т.д. Учетная запись может находиться в удаленной системе.
BOOL LookupAccountName ( LPCTSTR lpszS ystem,
LPCTSTR lpszAccount, PSID psid, LPDWORD lpcbSid,
LPTSTR lpszReferencedDomain, LPDWORD lpcchReferencedDomain,
PSID_NAME_USE psnu)
Параметры
lpszSystem и lpszAccount указывают на имена системы и учетной записи. Часто
lpszS ystem присваивается значение NULL, чтобы указать на локальную систему.
psid — возвращаемая информация, которая имеет размер *lpcbSid. Если буфер
недостаточно велик, функция завершается с ошибкой, возвращая требуемый размер.
lpszReferencedDomain — строка длиной *lpcchReferencedDomain символов.
Параметр длины должен быть инициализирован в размер буфера (сбои обрабатываются
обычным протоколом). Возвращаемое значение определяет домен, в котором находится
искомое имя. Имя учетной записи "Administrators" возвращает "BUILTIN", тогда как имя
учетной записи пользователя возвращает то же самое имя пользователя.
Параметр psnu указывает на переменную SID_NAME_USE (перечислимого типа) и
может проверяться на такие значения, как SidTypeWellKnownGroup , SidTypeUser,
SidTypeGroup и т.д.
BOOL LookupAccou ntSid ( LPCTSTR lpszSystem, PS ID psid,
LPTSTR lpszAccount, LPDWORD lpcchName,
LPTSTR lpszReferencedDomain,
LPDWORD lpcchReferencedDomain, PS ID_NAME__USE psnu)
Функция LookupAccountSid позволяет проделать обратный процесс, указав SID и
получив имя учетной записи. Именем учетной записи может быть любое доступное для
процесса имя. Некоторые имена, такие как Everyone (Все), хорошо известны. Имя учетной
записи пользователя данного процесса (данного сеанса) можно получить с помощью
функции
BOOL GetUserName ( LPTSTR lpBuffer, LPDWORD lpcchBuffer)
Имя пользователя и его длина возвращаются обычным способом. Создавать SID и
управлять
ими
можно
с
помощью
таких
функций,
как InitializeSid и
AllocateAndInitializeSid .
Но примеры ограничены лишь идентификаторами,
полученными по именам учетных записей.
61
Как только SID станут известны, они могут быть введены в инициализированный
дескриптор безопасности.
BOOL SetSecurit yDescriptbrOwner ( PSECUR ITY__DESCR IPTOR psd,
PSD psidOwner, BOOL fOwnerDefault ed)
BOOL SetSecurityDescriptorGroup ( PSECURITY__DESCRIPTOR psd,
PSID psidGroup, BOOL fGroupDefaulted)
Здесь psd указывает на соответствующий описатель безопасности, а psidOwner или
psidGroup ) — это адрес SID владельца (группы). Параметр fOwnerDefaulted или
(fGroupDefaulted) указывает, что нужно использовать заданную по умолчанию
информацию.
Функции
GetSecurit yDescriptbrOwner
и
GetSecurit yDescriptorGroup
возвращают SID (владельца или группы) указанного описателя безопасности.
Управление списками ACL
Рассмотрим, как управлять списками ACL, как связать ACL с дескриптором
безопасности и как добавлять в него элементы контроля доступа.
Работа начинается с инициализации структуры ACL. К ACL нельзя обратиться
непосредственно. Программа должна только предоставить буфер, который будет играть роль
ACL; его содержимым управляет функция.
BOOL InitializeAcl ( PAC L pAcl, DWORD cbAcl,
DWORD dwAclRevision )
Здесь pAcl — адрес предоставленного программистом буфера размером в cbAcl байтов.
Размер ACL в 1 Кбайт более чем достаточен для большинства задач. Параметр
dwAclRevision должен иметь значение ACL_REVISION
После инициализации в список добавляются элементы контроля доступа в
желательном порядке. Для этого предназначены две следующие функции:
BOOL AddAccess AllowedAce ( PAC L pAcl, DWORD dwAclRevision,
DWORD dwAccessMask, PS ID pSid)
BOOL AddAccessDeniedAce ( PACL pAcl, DWORD dwAclRevision,
DWORD dwAccessMask, PS ID pSid)
Здесь pAcl указывает на ту же структуру ACL, которая была инициализирована функцией
InitializeAcl, a dwAclRevision снова имеет значение ACL_REVISION. Параметр pSid
указывает на SID, например полученный из функции LookupAccountName .
Маска доступа (dwAccessMask) определяет права, которые будут предоставлены или
запрещены пользователю или группе, указанной идентификатором SID. Предварительно
определенные значения маски зависят от типа объекта.
В заключение следует связать ACL с дескриптором безопасности. Для
разграничительного ACL применяется следующая функция:
BOOL SetSecurit yDescriptbrDacl ( PSECURITY_DESCRIPTOR psd,
BOOL fDaclPresent, PACL pAcl, BOOL fDaclDefaulted )
Если значение fDaclPresent равно TRUE, это указывает, что в структуре pAcl
имеется ACL; если FALSE — функция игнорирует все заданное содержимое рАс1.
Если последний флаг fDaclDefaulted равен FALSE, это означает, что ACL генерируется программистом. В противном случае ACL формируется принятым по умолчанию
механизмом, таким как наследование, и значение fDaclDefaulted должно быть равно
FALSE.
Чтение и изменение дескрипторов безопасности
После того как дескриптор безопасности будет связан с файлом, нужно определить
параметры безопасности существующего файла и при необходимости изменить их. Для
чтения и задания параметров безопасности файла в дескрипторе безопасности применяются
следующие функции:
BOOL GetFileSecurity ( LPCTSTR lpszFileName,
SECUR ITY_INFORMATION secInfo,
62
PSECUR ITY__DESCRIPTOR psd, DWORD cbSd,
LPDWORD lpcbLengthNeeded)
BOOL SetFileSecurity ( LPCTSTR lpszFileName,
SECUR ITY__INFORMATION secInfo,
PSECUR ITY_DESCRIPTOR psd)
Параметры
secInfo — константа перечислимого типа, принимающая такие значения, как
OWNER_SECURITY_INFORMATION,
GROUP_SECURITY_INFORMATION,
DACL_SECURITY_INFORMATION и SACL_SECURITY_INFORMATION
Этот параметр указывает, какую часть описателя безопасности следует получить или
установить. Значения могут объединяться при помощи поразрядного "или".
Лучший способ выяснить размер буфера возврата для GetFileSecurity— вызвать эту
функцию дважды. При первом вызове в параметре cbSd просто укажите 0. После выделения
буфера вызовите функцию второй раз.
Само собой разумеется, что для выполнения этих операций нужны соответствующие
разрешения на файл. Например, для успешного выполнения SetFileSecurit y необходимо
иметь разрешение WRITE_DAC или быть владельцем объекта.
Функции GetSecurit yDescriptorOwner и GetSecurit yDescriptorGroup могут
извлекать идентификаторы SID из дескрипторов безопасности, полученных функцией
GetFileSecurit y. Для получения АСL служит следующая функция:
BOOL GetSecurit yDescriptorDacl ( PSECURITY_DESCRIPTOR psd,
LPBOOL fDaclPresent, PACL *pAcl, LPBOOL lpfDaclDefaulted)
Ее параметры почти идентичны параметрам SetSecurit yDescriptorDacl , с той лишь
разницей, что флаги не устанавливаются, а возвращаются, указывая, имеется ли данный АСL
и был ли он установлен по умолчанию или пользователем.
Чтобы расшифровать АСL, надо узнать, сколько элементов АСЕ он содержит.
BOOL GetAclInformation ( PACL pAc l,
LPVOID pAclInformation, DWORD cbAclInfo,
ACL_INFORMATION_CLASS dwAcl InfoClass)
В большинстве случаев класс информации АСL dwAcl InfoClass принимает
значение AclSizeInformation, а параметр Acl Information — это структура типа
ACL_SIZE_INFORMATION. Еще одним значением класса может быть AclRevisionInformation .
Структура ACL_SIZE_INFORMATION содержит три элемента: наиболее важный из
них, AceCount, показывает, сколько элементов содержится в списке. Чтобы определить,
достаточно ли велик АСL, посмотрите значения элементов AclBytesInUse и
AclBytesFree этой структуры.
BOOL GetAce ( PACL pAcl, DWORD dwAceIndex, LPVOID *pAce)
Эта функция позволяет получить элементы АСЕ (их общее количество уже известно)
по индексу. Параметр рАсе указывает на структуру АСЕ, в которой есть элемент Header,
содержащий, в свою очередь, элемент АсеТуре. Тип элемента можно проверить на значения
ACCESS_ALLOWED_ACE_TYPE и ACCESS_DENIED_ACE_TYPE.
Основная литература: [1] – 112 - 131 c.
Контрольные вопросы:
1. Какую информацию включают атрибуты безопасности?
2. Какие элементы включает дескриптор безопасности?
3. Назначение списков контроля доступа.
63
Лекция 14. Тема: Структурная обработка исключений
Структурная обработка исключительных ситуаций (Structured Exception
Handling — SEH) в Win32 представляет собой надежный механизм, позволяющий
приложениям отвечать на неожиданные события, такие как исключительные ситуации при
адресации, сбои при выполнении арифметических операций и системные ошибки. Кроме
того, SEH делает возможным завершение программы из любой точки в блоке кода, а также
автоматически выполняет указанные программистом действия и восстановление при
ошибках.
SEH гарантирует, что программа сможет освободить ресурсы и выполнить другие
действия по очистке до того, как блок, поток или процесс завершится либо в соответствии с
программой, либо из-за неожиданного исключения. Кроме того, SEH можно легко добавить
к имеющемуся коду, причем это часто упрощает логику программы.
Исключения и их обработчики
Если в программе не будет какой-либо обработки исключений, непреднамеренная
исключительная ситуация, например разыменование нулевого указателя или деление на
нуль, приведет к немедленной остановке программы. Это может вызвать проблему, если,
например, программа создала временный файл, который должен быть удален перед
завершением работы. SEH позволяет задать блок кода — обработчик исключения, который
сможет удалить временный файл, когда произойдет исключение.
Блоки Try и Except
Сначала определим, какие блоки кода нужно контролировать, и снабдим их
обработчиками исключений. Можно контролировать функцию целиком или иметь
отдельные обработчики исключений для разных блоков кода и функций.
Блок кода может оказаться удачным местом для обработчика исключений в
ситуациях, перечисленных ниже.
• Возникают обнаруживаемые ошибки, включая ошибки системных вызовов, и
нужно исправить ошибку, а не завершать программу.
• Широко применяются указатели, так что существует вероятность разыменования
указателей, не инициализированных должным образом.
• Выполняется много действий с массивами, и индексы массивов могут выйти за
доступные пределы.
• Выполняются арифметические операции с плавающей запятой, и существуют
проблемы деления на нуль, неточных результатов и переполнения.
• В коде вызывается функция, которая может породить исключительную ситуацию либо
преднамеренно, либо из-за недостаточной проверки.
В примерах как этой главы, так и всей книги для установления контроля за блоком
кода создаются следующие блоки try и except:
_ try { /* Блок контролируемого кода */ }
_ except {выражение_фильтра) { /* Блок обработки исключения */ }
Заметьте, что try и except — ключевые слова, распознаваемые компилятором.
Блок try является частью обычного кода приложения. Если в блоке происходит
исключение, операционная система передает управление обработчику исключения —
блоку кода, связанному с ключевым словом except. Последующие действия определяются
значением выражение_фильтра.
Следует заметить, что исключение может произойти и в блоке, который вложен в
блок __ try; в этом случае поддержка времени выполнения "разворачивает" стек, на
ходит обработчик исключения и передает ему управление. То же самое происходит,
когда исключение возникает внутри функции, вызываемой в блоке try.
Выражения фильтра и их значения
Выражение_фильтра в операторе except вычисляется немедленно после того, как
происходит исключение. Обычно это литеральная константа, вызов функции фильтра или
условное выражение. Во всех случаях выражение должно возвратить одно из трех значений.
64
1. EXCEPTION_EXECUTE_HANDLER — система выполняет блок except. Это обычная
ситуация.
2. EXCEPTION_CONTINUE_SEARCH — система игнорирует обработчик исключения и
последовательно ищет его во вложенных блоках, пока не находит.
3. EXCEPTION_CONTINUE_EXECUTION — система немедленно возвращает
управление в точку, в которой произошло исключение. После некоторых исключений
продолжение невозможно, и, если программа пытается продолжить работу, не
медленно генерируется другое исключение.
Коды исключений
Блок except или выражение фильтра может определить конкретное исключение с
помощью функции
DWORD GetExceptionCode (VOID)
Код исключения должен быть получен немедленно после исключения. Поэтому сама
функция фильтра не может вызывать GetExceptionCode (это ограничение установлено в
компиляторе). Обычно эта функция вызывается в выражении фильтра, где кодом
исключения служит параметр заданной пользователем функции фильтра.
except (MyFilter (GetExceptionCode ())) { }
В этой ситуации функция фильтра определяет и возвращает значение выражения
фильтра, которое должно быть одним из трех значений, указанных выше. Функция может
определить на основе кода исключения свое возвращаемое значение; например, фильтр
может передавать исключения при операциях с плавающей запятой внешнему обработчику
(возвращая EXCEPTION_CONTINUE_SEARCH), а нарушение доступа к памяти
обрабатывать
в
текущем
обработчике
(возвращая
EXCEPTION_EXECUTE_HANDLER).
GetExceptionCode может возвращать множество различных значений кодов
исключений. Все эти коды подразделяются на несколько категорий.
• Программные нарушения, например:
- EXCEPTION_ACCESS_VIOLATION — попытка читать или записывать по
виртуальному адресу, к которому процесс не имеет доступа;
- EXCEPTION_DATATYPE_MISALIGNMENT — многие типы процессоров требуют,
например, чтобы данные типа DWORD были выровнены по 4-байтовым границам;
- EXCEPTION_NONCONTINUABLE — значение выражения фильтра было
EXCEPTION_CONTINUE_EXECUTION, но после данного исключения продолжение
невозможно.
• Исключения, вызываемые функциями распределения памяти, HeapAlloc и
HeapCreate, если в них используется флаг HEAP_GENERATE_EXCEPTIONS.
Значение
кода
будет
STATUS_NO_MEMORY
либо
EXCEPTION_ACCESS_VIOLATION.
• Определяемый
пользователем
код
исключения,
генерируемый
функцией
RaiseException, которая рассматривается ниже.
• Множество разнообразных кодов по арифметическим операциям (особенно с
плавающей запятой), например:
EXCEPTION_INT_DIVIDE_BY_ZERO и EXCEPTION_FLT_OVERFLOW.
• Исключения, используемые отладчиками, например EXCEPTION_BREAKPOINT и
EXCEPTION_SINGLE_STEP.
Существует также альтернативная функция, вызываемая только из выражения
фильтра, которая возвращает дополнительную (в том числе зависящую от типа процессора)
информацию:
LPEXCEPTION_POINTERS GetExceptionInformation (VOID)
Структура EXCEPTION_POINTERS содержит как зависимую от процессора, так и
независимую информацию, сгруппированную в двух других структурах:
65
typedef struct _EXCEPTION_POINTERS {
PEXCEPTION_RECORD ExceptionRecord;
PCONTEXT ContextRecord;
} EXCEPTION_POINTERS;
EXCEPTION_RECORD содержит элемент для ExceptionCode, с тем же набором
значений, которые возвращаются функцией GetExceptionCode. Еще один элемент
EXCEPTION_RECORD —
ExceptionFlags —
имеет
значение 0 или
EXCEPTION_NONCONTINUABLE, что позволяет функции фильтра определить, следует ли
пытаться продолжать выполнение. Другие элементы включают адрес виртуальной памяти
ExceptionAddress
и массив параметров ExceptionInformation . В случае
EXCEPTION_ACCESS_VIOLATION первый элемент указывает, было ли это нарушение при
записи (1) или при чтении (0), а второй элемент — адрес виртуальной памяти.
Второй элемент EXCEPTION_POINTERS — ContextRecord — содержит зависимую
от процессора информацию. Есть разные структуры для каждого типа процессора;
информацию об этом можно найти в <winnt.h>.
Ошибки и исключения
Ошибку можно считать ситуацией, которая иногда может возникнуть в известном
месте. Ошибки при системных вызовах, например, должны немедленно обнаруживаться
логикой кода, и о них должно выдаваться сообщение. Для этого программисты обычно
вставляют явную проверку, чтобы узнать, не потерпела ли неудачу, например, операция
чтения из файла.
С другой стороны, исключение может возникнуть почти в любом месте, и проверку на
исключение сделать невозможно или неудобно. Примеры — деление на нуль и нарушения
доступа к памяти.
Тем не менее различие между ошибками и исключениями иногда стирается. Win32
может генерировать исключения в ходе резервирования памяти функциями НеарАllос и
HeapCreate, если объема памяти недостаточно. Программы также могут вызывать
собственные исключения с определенными программистом кодами с помощью функции
RaiseException.
Обработчики исключений позволяют выходить из внутренних блоков или функций по
логике программы, не прибегая для передачи управления к goto или longjmp. Эта
возможность особенно ценна, если блок кода обращается к таким ресурсам, как открытые
файлы, память или объекты синхронизации, так как обработчик может освободить их; кроме
того, можно продолжить выполнение программы после обработчика исключения, не
завершая ее. Также программа может восстанавливать при выходе из блока состояние
системы.
Генерируемые пользователем исключения
С помощью функции RaiseException можно вызвать исключение в любой точке в
ходе выполнения программы. Таким образом программа сможет обнаружить ошибку и
обработать ее как исключение.
VOID RaiseException ( DWORD dwExceptionCode,
DWORD dwExceptionFlags, DWORD cArguments, LPDWORD lAr guments)
Параметры
dwExceptionCode — код, определяемый пользователем. Не используйте бит 28, который
зарезервирован для системы. Код ошибки устанавливается в битах 27-0 (т.е. во всех, кроме
самой старшей шестнадцатеричной цифры). Бит 29 устанавливается для обозначения
"заказного" исключения (не Microsoft). Биты 31—30 указывают строгость исключения. Ниже
показано, как это делается, причем начальная шестнадцатеричная цифра кода показана для
случая, когда бит 29 установлен.
• 0 — успех (начальная цифра 2).
• 1 — информационный (начальная цифра 6).
• 2 — предупреждение (начальная цифра А).
66
•
3 — ошибка (начальная цифра Е).
Параметр dwExceptionFIags обычно устанавливается в 0, но установка значения в
EXCEPTION_NONCONTINUABLE указывает, что выражение фильтра не должно
генерировать EXCEPTION_CONTINUE_EXECUTION; если это будет так, произойдет
немедленное исключение EXCEPTION_NONCONTINUABLE.
Параметр lpArguments, если не имеет значения NULL, указывает на массив размера
cArguments (третий параметр), содержащий 32-разрядные значения, которые будут
переданы в выражение фильтра. Максимально допустимое количество параметров
EXCEPTION_MAXIMUM_PARAMETERS в настоящее время равно 15. Обращаться к этой
структуре следует через GetExceptionInformation .
Обработчики завершения
Обработчик завершения служит почти для той же цели, что и обработчик исключения,
но он выполняется, когда поток выходит из блока как при нормальном ходе программы, так
и при наступлении исключения. Кроме того, обработчик завершения не может выявить
исключение.
Обработчик завершения создается с помощью ключевого слова __finall y в операторе
try-finall y. Его структура такая же, как и у оператора try-except, однако
здесь нет выражения фильтра. Обработчики завершения, подобно обработчикам
исключений, дают удобный способ закрытия дескрипторов, освобождения ресурсов,
восстановления масок и других действий по возвращению процесса в определенное
состояние перед выходом из блока. Например, программа может выполнять операторы return
в середине блока, а обработчик завершения будет выполнять работу по
очистке. Таким образом, не требуется ни вставлять код очистки непосредственно в
блок, ни прибегать к оператору goto, чтобы перейти к этому коду.
__try { /* Блок кода. */ }
__finall y { /* Обработчик завершения (блок finally). */ }
Выход из блока Try
Обработчик завершения выполняется всякий раз, когда управление выходит из блока
try по любой из следующих причин:
• достигается конец блока try и управление "проваливается" в обработчик завершения;
• выполняется один из следующих операторов, которые вызывают выход из блока:
return, break, goto, longjmp, continue, __leave
• исключение.
Аварийное завершение
Завершение по любой другой причине, отличной от достижения конца блока и
перехода к обработчику завершения или выполнения оператора __leave, считается
аварийным. Оператор __leave выполняет переход к концу блока __try и вызов обработчика
завершения, что более эффективно, чем goto, так как в этом случае не требуется
разворачивать стек. В обработчике завершения можно применять следующую
функцию, чтобы определить, как завершился блок try:
BOOL AbnormalTermination (VOID)
Функция возвращает TRUE при аварийном завершении и FALSE при нормальном
завершении.
Выполнение обработчика завершения и выход из него
Обработчик завершения, или блок finally, выполняется в контексте блока или
функции, которую он контролирует. Управление может передаваться с конца обработчика
завершения на следующий оператор. Также в обработчике завершения может
выполняться оператор перехода (return, break, continue, goto, longjmp или
__leave). Еще один вариант — выход из обработчика по исключению.
Сочетание блоков Finally и Except
Отдельный блок try должен иметь один блок finall y или except; оба блока сразу не
допускаются. Но можно вложить один блок в другой; часто этот метод оказывается
67
полезным. Следующий код правилен и гарантирует, что временный файл будет удален при
выходе из цикла как по логике программы, так и по исключению. Эта методика также
полезна для того, чтобы гарантировать разблокирование файла. Можно также применить
внутренний блок try-except для операций с плавающей запятой.
try { /* Внешний блок try-except. */
while (...)
try ( /* Вн утренний блок try-finall y. */
hFile = CreateFile (TempFile, ...);
if (...) try { /* Вн утренний блок try-except. */
/* Разрешение исключений ПЗ и выполнение вычислений. */
}
_ except (EXCEPTION EXECUTE HANDLER) {
... /* Обработка исключения ПЗ. */ _ clearfp (); }
... /* Обработка, не связанная с ПЗ. */ }
finall y { /* Конец цикла while loop. */
/* Выполняется на КАЖДОЙ итерации цикла */
CloseHandle (hFile); DeleteFile (TempFile); } }
except (выражение_фильтра) {
/* Обработчик исключения. */ }
Основная литература: [1] – 90 - 111 c.
Контрольные вопросы:
1. Что представляет собой структурная обработка исключений?
2. Структура блоков Try и Except
3. Назначение обработчиков завершения
Лекция 15. Тема: Межпроцессное взаимодействие
Двумя основными механизмами Win32 для обеспечения межпроцессного
взаимодействия (interprocess communication, IPC) являются анонимный канал и
именованный канал.
Также имеются почтовые ячейки, которые позволяют проводить широковещательное
распространение сообщений типа "один ко многим".
Анонимные каналы
Анонимные каналы Win32 позволяют проводить одностороннее (полудуплексное),
символьно-ориентированное межпроцессное взаимодействие. Каждый канал имеет два
дескриптора: дескриптор чтения и дескриптор записи. Для создания канала служит
следующая функция CreatePipe:
BOOL CreatePipe ( PHANDLE
phRead,
PHANDLE
phWri te,
LPSECUR ITY_ATTRIBUTES lpsa, DWORD cbPipe)
Чтобы использовать канал для IPC, нужен еще один процесс, с которым будет связан
один из дескрипторов канала.
Чтение из дескриптора чтения канала блокируется, если канал пуст. В противном
случае считывается столько байтов, сколько имеется в канале, но не превышая количества,
указанного при вызове ReadFile. Также блокируется операция записи в заполненный канал,
который реализуется в буфере памяти.
Кроме всего прочего, анонимные каналы пропускают данные только в одну сторону.
Для двунаправленной связи нужны два канала.
Именованные каналы
Именованные каналы обладают рядом особенностей, которые делают их удобным
средством реализации приложений с использованием IPC. Особенности именованных
каналов (некоторые из них присутствуют не всегда) перечислены ниже.
• Именованные каналы ориентированы на сообщения, так что читающий процесс
может читать сообщения переменной длины точно так, как они передаются
записывающим процессом.
68
• Именованные каналы двунаправлены, так что два процесса могут обмениваться
сообщениями по одному и тому же каналу.
• Может быть несколько независимых экземпляров именованного канала.
На
пример, несколько клиентов могут связываться с одним сервером, используя
один и тот же канал, и сервер может ответить клиенту по тому же экземпляру
канала.
• К каналу по имени могут обращаться системы в сети. Связь через именованный
канал не зависит от того, работают ли оба процесса на одной машине или на
разных.
• Существует несколько функций-полуфабрикатов, упрощающих взаимодействие
по именованному каналу и соединение клиента с сервером.
Использование именованных каналов
Функция CreateNamedPipe создает первый экземпляр именованного канала и
возвращает его дескриптор. Также эта функция определяет максимальное количество
экземпляров канала и, следовательно, количество клиентов, которых можно обслуживать
одновременно.
Обычно процесс создания называется сервером. Процессы-клиенты, возможно,
работающие на других системах, открывают этот канал с помощью CreateFile.
Создание именованных каналов
Ниже приведена функция CreateNamedPipe.
HANDLE CreateNamedPipe ( LPCTSTR lpszPipeName,
DWQRD fdwOpenMode, DWORD fdwPipeMode,
DWORD nMaxInstances, DWORD cbOutBuf,
DWORD cblnBuf,
DWORD dwTimeOut, LPSECUR ITY_ATTRIBUTES lpsa)
Параметры
lpszPipeName обозначает имя канала в форме: \\.\pipe\[п уть]имя-канала
Точка обозначает локальную машину; иначе говоря, создать канал на удаленной машине
нельзя.
fdwOpenMode имеет одно из следующих значений:
- PIPE_ACCESS_DUPLEX — эквивалентно комбинации GENERIC_READ и
GENERIC_WRITE;
- PIPE_ACCESS_INBOUND — направление данных только от клиента к серверу,
эквивалентно GENERIC_READ;
- PIPE_ACCESS_OUTBOUND — эквивалентно GENERIC_WRITE.
Режим также может быть FILE_FLAG_WRITE_THROUGH (не используется для
каналов сообщений) и FILE_FLAG_OVERLAPPED.
fdwPipeMode может принимать три взаимоисключающих пары флагов. Они указывают,
является ли запись ориентированной на сообщения или байтовой, осуществляется ли чтение
по сообщениям или по блокам и блокируется ли операция чтения.
- PIPE_TYPE_BYTE и PIPE_TYPE_MESSAGE, которые взаимно исключают друг
друга, указывают, записываются ли данные в канал как поток байтов или как
поток сообщений. Для всех экземпляров канала применяется один и тот же тип.
- PIPE_READMODE_BYTE и PIPE_READMODE_MESSAGE указывают, читаются ли
данные как поток байтов или как поток сообщений. Для PIPE_READMODE_MESSAGE
необходимо PIPE_TYPE_MESSAGE.
- PIPE_WAIT и PIPE_NOWAIT определяют, будет ли операция ReadFile блокироваться.
Указывайте значение PIPE_WAIT, поскольку для асинхронного ввода-вывода есть
способы получше.
nMaxInstances определяет количество экземпляров канала и, следовательно, количество
одновременно обслуживаемых клиентов. Если указать значение PIPE_UNLIMITED_
INSTANCES, количество каналов будет определять ОС в зависимости от доступных
системных ресурсов.
69
cbOutBuf и cbInBuf задают размеры в байтах буферов ввода и вывода, используемых
для именованных каналов. Если указать нуль, будут использоваться значения по умолчанию.
dwTimeOu t — принятый по умолчанию тайм-аут (в миллисекундах) для функции
WaitNamedPipe. Эта ситуация, в которой функция создания определяет тайм-аут для
другой функции, является уникальной.
Возвращаемое значение в случае ошибки — INVALID_HANDLE_VALUE, так как
дескрипторы каналов подобны дескрипторам файлов.
lpsa играет ту же роль, что и в других функциях создания.
При первом вызове CreateNamedPipe фактически создается именованный канал, а
не просто экземпляр. При закрытии последнего дескриптора экземпляра сам этот экземпляр
удаляется. Удаление последнего экземпляра именованного канала вызывает удаление самого
канала.
Подключение клиента к именованному каналу
Клиент может подключиться к именованному каналу с помощью вызова CreateFile с
указанием имени канала. Во многих случаях клиент и сервер находятся на одной машине;
тогда имя имеет вид:
\\.\pipe\[путь]имя_канала
Если бы сервер находился на другой машине, имя выглядело бы так:
\\имя_сервера\pipe\[путь]имя_канала
Если сервер локальный, то применение для имени символа "." вместо указания имени
самой машины значительно повышает быстродействие.
Функции состояния именованного канала
GetNamedPipeHandleState возвращает для данного открытого дескриптора
сведения о том, находится ли канал в блокируемом или неблокируемом режиме,
ориентирован ли он на сообщения или на байты, о количестве экземпляров канала и т.д.
SetNamedPipeHandleState позволяет программе задавать эти атрибуты состояния.
GetNamedPipelnfo определяет, связан ли дескриптор с клиентом или сервером,
каков размер буфера и т.д.
Функции подключения именованного канала
После создания экземпляра именованного канала сервер может отслеживать подключение клиента (с помощью функций CreateFile или CallNamedPipe), используя
функцию ConnectNamedPipe.
BOOL ConnectNamedPipe ( HANDLE hNamedPipe, LPOVERLAPPED lpo)
Если задать в lpo значение NULL, выполнение ConnectNamedPipe завершится
сразу же, как только появится подключение клиента. Обычно функция возвращает TRUE.
Значение FALSE может быть, если клиент подключается между вызовами сервером функций
CreateNamedPipe и ConnectNamedPipe . В этом случае GetLastError возвращает
значение ERROR_PIPE_CONNECTED.
После возвращения из ConnectNamedPipe сервер может читать запросы с помощью
ReadFile и записывать ответы, используя WriteFile. В заключение сервер должен вызвать
функцию DisconnectNamedPipe, чтобы освободить дескриптор (экземпляр канала) для
соединения с другим клиентом.
Последняя функция, WaitNamedPipe, предназначена для синхронизации
подключений клиента. Эта функция завершается, если на сервере имеется незавершенное
обращение к ConnectNamedPipe. Используя WaitNamedPipe, клиент может убедиться,
что сервер готов к подключению, после чего вызвать CreateFile. Кроме того, вызов
сервером функции ConnectNamedPipe в этом случае завершится успешно.
Соединение клиента и сервера через именованный канал
Сначала в последовательности сервера осуществляется подключение клиента, с
которым сервер связывается до тех пор, пока тот не разорвет соединение, затем сервер
закрывает подключение на стороне сервера и соединяется с другим клиентом.
Далее приводится последовательность подключения клиента, в которой клиент
закрывается, когда завершает работу, позволяя другому клиенту соединиться через тот же
70
экземпляр именованного канала. Клиент может соединяться с сервером через сеть, если ему
известно имя сервера.
Обратите внимание, что между клиентом и сервером происходит состязание. Вопервых, вызов клиентом WaitNamedPipe завершится неудачно, если сервер еще не создал
именованный канал. Во-вторых, возможны обстоятельства, в которых клиент может
завершить свой вызов CreateFile прежде, чем сервер вызовет ConnectNamedPipe. В этом
случае ConnectNamedPipe возвратит FALSE, но связь через именованный канал будет
работать, как положено.
Транзакционные функции именованного канала
Клиент выполняет следующее:
• открывает экземпляр канала, создавая долговременное подключение к серверу и
потребляя этот экземпляр канала;
• попеременно посылает запросы и ожидает ответов;
• закрывает подключение.
Обычную последовательность функций WriteFile, ReadFile можно считать
отдельной транзакцией клиента, и в Win32 имеется такая функция для каналов сообщений.
BOOL TransactNamedPipe ( HANDLE hNamedPipe, LPVOID lpvWriteBuf,
DWORD cbWriteBuf, LPVOID lpvReadBuf, DWORD cbReadBu f,
LPDWORD lpcbRead, LPOVER LAPPED lpa)
Назначение параметров не нуждается в объяснении, так как эта функция представляет
собой сочетание WriteFile и ReadFile для дескриптора именованного канала.
Указываются как выходной, так и входной буферы, а * lpcbRead задает длину сообщения.
Функция TransactNamedPipe удобна, но требует постоянного подключения, что
ограничивает число клиентов.
CallNamedPipe, вторая функция-полуфабрикат, лишена этого недостатка, так как
объединяет в себе всю последовательность: CreateFile, WriteFile, ReadFile, CloseHandle.
Преимущество заключается в более эффективном использовании канала, несмотря на
издержки подключения при каждом запросе.
BOOL CallNamedPipe ( LPCPSTR lpszPipeName, LPVOID lpvWriteBuf,
DWORD сbWriteBuf, LPVOID lpvReadBuf, DWORD сbReadBuf,
LPDWORD lpcbRead, DWORD dwTimeOut)
Использование параметров подобно TransactactNamedPipe с тем исключением, что для
канала указывается имя, а не дескриптор. Функция CallNamedPipe является синхронной.
Здесь также указывается тайм-аут в миллисекундах — для подключения, но не для
транзакции. Для dwTimeOut существует также три специальных значения:
• NMPWAIT_NOWAIT;
• NMPWAIT_WAIT_FOREVER;
• NMPWAIT_USE_DEFAULT_WAIT — в этом случае используется тайм-аут по
умолчанию, заданный при вызове CreateNamedPipe.
Наблюдение за сообщениями в именованном канале
Кроме чтения именованного канала с помощью ReadFile, можно также определять,
присутствует ли в нем сообщение, которое можно считать. Для этого служит функция
PeekNamedPipe.
BOOL PeekNamedPipe ( HANDLE hPipe, LPVOID lpvBuffer,
DWORD cbBuffer, LPDWQRD lpcbRead, LPDWORD lpcbAvail,
LPDWORD lpcbMessage)
Функция PeekNamedPipe читает все байты или сообщения в канале, не разрушая
их; она не блокируется и завершается немедленно.
Чтобы узнать, есть ли данные в канале, проверьте значение *lpcbAvail; если есть,
*lpcbAvail будет больше нуля. В этом случае lpvBuffer и lpcbRead могут иметь
значение NULL. ЕСЛИ буфер задан параметрами lpvBuffer и cbBuffer, то *lpcbMessage
сообщает, есть ли оставшиеся байты сообщения, которые не вписываются в буфер, что
71
позволяет выделить большой буфер перед чтением из именованного канала. В байтовом
режиме канала это значение нулевое.
Еще раз подчеркнем, что PeekNamedPipe не уничтожает сообщения, так что для
удаления сообщений или байтов из канала нужен дополнительный вызов ReadFile.
Безопасность именованного канала
Ниже приведены важные права безопасности для именованных каналов.
• GENERIC_READ.
• GENERIC_WRITE.
• SYNCHRONIZE (разрешает ожидание потока).
Соответствующие права устанавливаются в зависимости от режима доступа
(дуплексный, входящий или исходящий). Для всех трех режимов требуется право
SYNCHRONIZE.
Почтовые ячейки
Почтовая ячейка Win32 имеет имя. Почтовые ячейки представляют собой
широковещательный механизм и ведут себя иначе, чем именованные каналы, что делает их
полезными в некоторых весьма важных, но довольно редких ситуациях. Ниже приведены
основные характеристики почтовых ячеек.
• Почтовая ячейка является однонаправленной.
• Почтовая ячейка может иметь несколько отправителей и несколько получателей,
но чаще встречается форма с одним отправителем и несколькими получателями
или наоборот.
• Отправитель, или клиент, не знает наверняка, получили ли на самом деле сообщение
все, некоторые либо хотя бы один получатель, или сервер.
• Почтовые ячейки можно находить в пределах домена сети.
• Длина сообщения ограниченна.
Для использования почтовой ячейки нужно выполнить ряд операций.
• Каждый сервер (получатель) создает дескриптор почтовой ячейки, используя
CreateMailslot.
• Сервер ожидает приема сообщения из почтовой ячейки, вызвав ReadFile.
• Клиент, не обладающий возможностью чтения, должен открывать почтовую
ячейку функцией CreateFile и посылать сообщения с помощью WriteFile.
Операция открытия завершится неудачно (имя не будет найдено), если ожидающих
получателей нет.
Сообщение клиента может читаться всеми серверами; все они получают одно и то же
сообщение.
Существует еще одна возможность. Клиент, вызывая CreateFile, может указать имя
почтовой ячейки в форме:
\\*\maiIslot\имя_почтовой_ячейки
Таким образом, символ * играет роль подстановочного знака, и клиент может найти
каждый сервер в домене имен — объединенной в сеть группе систем, которой сетевой
администратор присвоил общее имя.
Создание и открытие почтовой ячейки
Серверы почтовой ячейки (получатели) с помощью функции CreateMailslot
создают почтовую ячейку и получают дескриптор для применения в ReadFile. На
отдельной машине может быть только одна почтовая ячейка с данным именем, однако
несколько систем в сети могут использовать одно и то же имя, что позволяет
воспользоваться преимуществами почтовых ячеек в ситуации с несколькими получателями.
HANDLE CreateMailslot ( LPCTSTR lpszName, DWORD cbMaxMsg,
DWORD dwReadTimeout, LPSECURITY__ATTR IB UTES lpsa)
Параметры
lpszName указывает на имя почтовой ячейки в форме:
\\.\mailslot\[путь]имя
Имя должно быть уникальным. Символ "." показывает, что почтовая ячейка создана на
текущей машине.
72
cbMaxMsg — максимальный размер (в байтах) сообщений, которые может отправлять
клиент. 0 означает, что ограничения нет.
dwReadTimeout — длительность (в миллисекундах) ожидания операции чтения.
Значение 0 означает немедленный возврат, a MAILSLOT_WAIT_FOREVER — бесконечное
ожидание (без тайм-аута).
В заключение клиент должен указать флаг FILE_SHARE_READ.
Функции GetMailslotInfo и SetMailslotInfo подобны своим аналогам для именованных
каналов.
Основная литература: [1] – 286-306 c.
Контрольные вопросы:
1. Как выполняется связь между процессами с использованием анонимного канала?
2. Как клиенты и серверы используют именованные каналы?
3. Назначение почтовых ячеек?
2.3 Планы лабораторных занятий
Лабораторная работа №1
Тема: Разработка консольного приложения.
Цель работы: Изучение основ применения API функции для консольных приложений.
Задание.
1. Изменить размер окна консоли.
2. Поменять заголовок окна консоли.
3. Изменить позиции курсора.
4. Поменять цветовые атрибуты текста.
5. Разобрать алгоритм работы процедур NUMPAR и GETPAR.
Основная литература: [4] – 164- 192 c.
Контрольные вопросы:
1. Какие аргументы использует АРІ функция GetStdHandle?
2. Дайте названия цветов букв и символов используемых функцией SetConsoleTextAttribute?
3. Назовите параметры функции CharToOem?
4. Какие типы событий зарезервированы операционной системой?
Лабораторная работа №2
Тема: Разработка пользовательского интерфейса.
Цель работы: Изучение основ программирования в среде Win32 и вопросов проектирования
интерфейса пользователя.
Задания:
1. Изучить процедуру главного окна.
2. Изучить класс и функции создания окон.
3. Изучить используемые в программе API-функции
4. Изменить параметры функции GetMessageA
5. Изменить параметры функции CreateWindowExA
6. Изменить главную процедуру окна WNDPROC
7. Изучить общие элементы управления – кнопки, флажки, переключатели, полосы
прокрутки, редакторы текста, списки строк, комбинированные списки.
Основная литература: [4] – 31- 92 c., [8] – 5-86 c.
Контрольные вопросы:
1.
2.
3.
4.
Какие API-функции используются в программе?
Какие структуры используются в программе?
Структура графического приложения.
Какие API-функции включает цикл обработки сообщений?
73
Лабораторная работа №3
Тема: Использование ресурсов.
Цель работы: Изучить использование наиболее употребляемых ресурсов
Задания:
1. Изучить наиболее употребляемые ресурсы
2. Написать программу с использованием ресурса - Битовая картинка.
3. Написать программу с использованием ресурса - Строка.
4. Написать программу с использованием ресурса - Диалоговое окно.
5. Написать программу с использованием ресурса - Меню.
6. Написать программу с использованием ресурса - Акселераторы.
Основная литература: [4] –193 - 260 c., [8] –145 - 190 c.
Контрольные вопросы:
1. Какие API-функции используются в программе?
2. Какие структуры используются в программе?
3. Выгоды использования ресурсов.
4. Отличия в поведении диалогового окна от обычного.
5. Отличия в описании диалогового окна и меню от других ресурсов.
Лабораторная работа №4
Тема: Управление файлами.
Цель работы: Изучение основ работы с файлами в Win32.
Задания:
1. Изучить API-функции для работы с файлами и каталогами.
2. Написать программу получения информации о дисках, установленных в компьютере.
3. Написать программу работы с каталогами.
4. Написать программу записи информации в файл и чтения информации из файла.
5. Написать программу поиска файлов.
Основная литература: [4] –261 - 351 c.
Контрольные вопросы:
1. Какие API-функции используются для определения и изменения текущей директории?
2. Какие API-функции используются для создания, копирования, перемещения, удаления
файлов?
3. Алгоритм чтения и записи в файл
4. Какие API-функции используются для работы с характеристиками файла?
Лабораторная работа №5
Тема: Создание динамических библиотек.
Цель работы: Освоить создание динамических библиотек.
Задания:
1. Создать DLL.
2. Написать программу вызова динамической библиотеки, используя явное связывание.
3. Написать программу вызова динамической библиотеки, используя неявное
связывание.
4. Написать программу и DLL. Организовать передачу параметров между ними.
5. Написать программу загрузки ресурса из динамической библиотеки.
Основная литература: [4] – 430 - 457 c.
Контрольные вопросы:
1. Виды связывания.
2. Параметры процедуры входа
3. Какие API-функции используются при работе с DLL?
74
Лабораторная работа №6
Тема: Управление процессами, потоками.
Цель работы: Изучение основных принципов управления процессами, потоками и объектов
синхронизации.
Задания:
1. Написать программу с использованием процессов
2. Написать программу с использованием потоков
3. Написать программу с использованием приоритетов потоков
4. Написать программу с использованием событий
5. Написать программу с использованием критических секций
6. Написать программу с использованием семафоров
7. Написать программу с использованием мьютексов
Основная литература: [1] – 167-260 c.
Контрольные вопросы:
1. На чем основана вытесняющая многозадачность?
2. Назначение объектов синхронизации
3. Виды объектов синхронизации
Лабораторная работа №7
Тема: Исследование структуры PE-формата
Цель работы: Исследование формата исполняемых файлов Windows с использованием
отладчиков, дизассемблеров
Задания:
1. Запуск программы под отладчиком и анализ ее работы
2. Исследование программы с помощью дизассемблера
3. Изучение общей структуры файлов
4. Изучение заголовков исполняемого файла
5. Изучение таблицы объектов
6. Изучение разделов в исполняемом файле
7. Изучить экспорт функций и механизм экспорта
8. Изучить импорт функций и механизм импорта
Основная литература: [7] – 95 - 184 c.
Контрольные вопросы:
1. Cигнатуры исполняемых файлов.
2. Способы исследования программ.
3. Назначение заголовков исполняетого файла
4. Назначение разделов в исполняемом файле
75
2.4
Планы занятий в рамках самостоятельной работы студентов под
руководством преподавателя (СРСП) (30 часов)
Методические рекомендации к выполнению СРСП
Форма проведения СРСП предполагается в виде разработки индивидуальных
упражнений, а также подготовки кратких рефератов по заданиям и темам курса и в
соответствии с тематикой лекционных и лабораторных занятий, с целью отработки навыков
в декларативном стиле, выбора структур данных и проектирования программ.
№ Задания
1
Ознакомление с
Win32 и Win64
2
Cредства
разработки
программного
обеспечения
Кодирование
текстовой
информации в ОС
Windows
Основы рисования и
копирования
изображений
3
4
5
Интерфейс
графических
устройств - GDI
6
Интерфейс
графических
устройств - GDI
7
Взаимодействие
программы с
пользователем
Ресурсы.
8
9
Общие элементы
управления.
8
Общие элементы
управления.
9
Rich Edit Control
Форма
проведе
ния
Дискуссия
Методические рекомендации
Дискуссия
О кодировании текстовой информации.
ОЕМ и АNSI. Кодировка Unicode.
Рекомендуе
мая
Литература
[1]-19- 36 с.
Основы операционных систем. Win32,
стандарты и открытые системы. Принципы
Win32.
Тренинг Провести анализ инструментальных
[4]-11-31,
средств разработки программ
103-115 c.
[5]-54- 75 с
Дискуссия
[4]- 116-123
с.
[6]-6- 33 с.
Контекст устройства. Коды растровых
операций. Полосы прокрутки. Контекст
устройства и WM_PAINT. Рисование
графических примитивов.
Тренинг Изучить обработку сообщений. Выполнить
вывод текстовых строк и простых
геометрических фигур согласно варианту.
[6]– 33-69
c., [8]-89112 с.
Тренинг Использование логических шрифтов.
Написать программу, использующую
несколько логических шрифтов и вывод
текста с помощью функций TextOut() и
DrawText() согласно варианту.
Тренинг Ресурсы. Меню и акселераторы. Написать
программу
[5]-173- 180
с., [8]-112129 c.
Тренинг Диалоговые окна и их элементы. Работа с
диалоговыми окнами. Кнопки, списки.
Окно редактирования. Написать
программу согласно варианту.
Тренинг Работа со строкой состояния, спином,
трекбаром. Написать программу согласно
варианту.
Тренинг Работа с индикатором, окнами подсказок,
списком изображений, окном просмотра
деревьев. Написать программу согласно
варианту.
Тренинг Окно редактирования, поддерживающее
форматирование текста.
76
[5]–157173 с., [8]112-144 c.
[6] - 70- 98
с., [8]-145190 c.
[6]-98-135
с., [8]-4188, 234-312
с.
[6] – 135156с.
[6] -156-310
с., [8]-210220 c.
[6] –202222 с.
10 Таймеры Windows и
служба времени.
11 Окна Windows
12 Реестр
13 Файловые системы
14 Консольные
приложения
15 Безопасность
Windows
Установка таймера. Программа календаряТренинг часов. Определение и вывод текущего
времени. Измерение однократных
интервалов.
Дискус- Организация дочерних окон. Окна
сия
предопределенных классов в главном окне
приложения
Дискус- Структура реестра. Работа с реестром
сия
Дискус- Провести анализ файловых систем
сия
Тренинг Написать программу консольного
приложения согласно варианту.
Дискус- Изучить стандартные механизмы защиты
сия
[5]-229-238
c.
[5] - 238265 с.
[6] - 222236 с.
[4] - 261316 с.
[1] - 172186 с.
[3] - 380405 с.
2.5 Планы занятий в рамках самостоятельной работы студентов (СРС) (30 час)
Методические рекомендации к выполнению СРС.
СРС включает в себя выполнение домашних заданий и упражнений в соответствии с
тематикой лекционных занятий и лабораторных работ. Студент должен самостоятельно,
выбрать оптимальную структуру данных, использовать управляющие структуры и принципы
в соответствии с требованиями стиля и технологии декларативного программирования.
Выполнение упражнений и заданий СРС будут контролироваться в аудиторные занятия под
руководством преподавателя
№
Название темы СРС
Методические рекомендации
1
Объекты ядра
2
Задания
3,4
Синхронизация
потоков
5
Волокна
Иметь представление о объектах ядра и
совместном использовании объектов ядра
несколькими процессами.
Изучить вопросы включения, завершения
процессов в задании и определения
ограничений, налагаемых на процессы в
задании.
Изучить вопросы синхронизация потоков в
пользовательском режиме и с
использованием объектов ядра
Иметь представление о работе с волокнами
6
Исследование
виртуальной памяти
7,8
Использование памяти
9
Проецируемые в
память файлы
Внедрение DLL и
перехват API-вызовов
Отладчики и
дизассемблеры
10,
11
12
Получение системной информации, статуса
виртуальной памяти. Определение
состояния адресного пространства.
Уметь использовать виртуальную и
динамически распределяемую память в
приложениях.
Изучить принципы проецирования в память
файлов
Изучить способы внедрения DLL и перехват
API-вызовов
Освоить методы исследования программ.
77
Рекомендуемая
литература
[2] - 28- 46 с.
.
[2] - 98- 129 с.
[2] - 187- 288 с.
[2]-303-312 с.
[2] - 342- 367 с.
[2]-368-398 с.,
461-474 с.
[2] - 409- 460 с.
[2] -533-567 с.
[4] -641 - 708 с.
13,
14
15
Драйверы
Изучить стуктуру и написание драйверов.
[4] -711 -754 с.
Сетевое
программирование
Освоить методы разработки программ с
использованием Windows Sockets..
[1] -313- 340 с.
2.6 Тестовые задания для самоконтроля с указанием ключей
1. Основным модулем выполнения в Win32 является
A) поток
B) процесс
C) программа
D) приложение
E) библиотека
2. Приложения консольного типа
A) работают в текстовом режиме
B) формируют окна
C) обрабатывают сообщения
D) требуют GUI
E) работают в графическом режиме
3. GUI-приложениями являются
A) Notepad, Calculator, Wordpad
B) Cmd.exe, Notepad
C) Far manager, Calculator
D) Cmd.exe, Calculator
E) Far manager, Wordpad
4. Консольными приложениями являются
A) Cmd.exe
B) Notepad
C) Calculator
D) Wordpad
E) Internet Explorer
5. Любому EXE- или DLL-модулю, загружаемому в адресное пространство процесса,
присваивается
A) уникальный описатель экземпляра
B) имя
C) номер
D) индекс
E) маска
6. Базовый адрес, но которому загружается приложение, определяется
A) компоновщиком
B) программистом
C) системным администратором
D) пользователем
E) приложением
7. Буква W в конце имени API-функции обозначает, что функция
A) использует строки в Unicode
B) использует строки в ASCII-коде
C) использует строки в scan-коде
D) записыает строки
E) читает строки
8. Процесс создается при вызове функции
A) CreateProcess
B) ExitProcess
78
C) TerminateProcess
D) OpenProcess
E) GetCurrentProcess
9. Классы приоритета влияют на распределение
A) процессорного времени между процессами и их потоками
B) памяти между процессами и их потоками
C) ресурсов между процессами и их потоками
D) адресного пространства между процессами и их потоками
E) сообщений
10. Процесс завершается, если один из потоков процесса вызывает функцию
A) ExitProcess
B) OpenProcess
C) GetCurrentProcess
D) OpenThread
E) CreateProcess
11. Какой функцией любой поток может завершить любой процесс
A) TerminateProcess
B) ExitProcess
C) GetCurrentProcess
D) CreateProcess
E) OpenProcess
12. Рекомендуемый способ завершения процесса
A) входная функция первичного потока возвращает управление
B) ExitProcess
C) TerminateProcess
D) GetCurrentProcess
E) CreateProcess
13. Область памяти, выделенная системой для объекта ядра "процесс", не освобождается,
пока счетчик числа его пользователей не достигнет
A) 0
B) 1
C) 2
D) 3
E) 4
14. Размер файла можно определить с помощью функции
A) FindFirstFile
B) SetEndOfFile
C) ReadFile
D) CreateFile
E) WriteFile
15. Размер файла можно установить с помощью функции
A) SetEndOfFile
B) FindFirstFile
C) ReadFile
D) ReadFile
E) WriteFile
16. Для закрытия дескриптора поиска предназначена функция
A) FindClose
B) CloseHandle
C) SetEndOfFile
D) FindFirstFile
79
E) OpemFile
17. Функция FindFirstFile возвращает
A) Дескриптор поиска
B) Дескриптор безопасности
C) Дескриптор контекста
D) Дескриптор процесса
E) Дескриптор потока
18. Блок __try { } содержит
A) блок контролируемого кода
B) блок обработки сообщений
C) обработчик завершения
D) обработчик управления консоли
E) коды ошибок
19. Блок __except { } содержит
A) блок обработки сообщений
B) блок контролируемого кода
C) коды ошибок
D) обработчик управления консоли
E) обработчик завершения
20. Блок __finally { } содержит
A) обработчик завершения
B) блок контролируемого кода
C) коды ошибок
D) обработчик управления консоли
E) блок обработки сообщений
21. ACL представляет собой
A) набор элементов управления доступом
B) набор идентификаторов объектов
C) набор идентификаторов субъектов
D) набор идентификаторов безопасности владельца
E) набор идентификаторов безопасности группы
22. Каждый процесс Win32 имеет собственное виртуальное адресное пространство размером
A) 4 Гбайт
B) 4 Мбайт
C) 1 Гбайт
D) 1 Мбайт
E) 64 Кбайт
23. Heaps это
A) кучи
B) волокна
C) нити
D) задания
E) страницы
24. Неявное связывание это
A) связывание во время загрузки
B) связывание во время выполнения
C) связывание во время выгрузки
D) связывание во время открытия приложения
E) связывание во время закрытия приложения
25. Явное связывание это
A) связывание во время выполнения
B) связывание во время загрузки
80
C) связывание во время выгрузки
D) связывание во время открытия приложения
E) связывание во время закрытия приложения
26. Какой объект Win32 позволяет проводить одностороннее символьно-ориентированное
межпроцессное взаимодействие?
A) анонимный канал
B) именованный канал
C) сервер
D) клиент
E) протокол
27. Процессы-клиенты открывают именованный канал с помощью функции
A) CreateFile
B) CreateNamedPipe
C) CreatePipe
D) ReadFile
E) WriteFile
28. Какой объект Win32 представляют собой широковещательный механизм?
A) почтовые ячейки
B) анонимный канал
C) именованный канал
D) сервер
E) клиент
29. Объекты Win32, объединяющие возможности ввода-вывода с перекрытием и
возможности независимых потоков
A) порты завершения ввода-вывода
B) почтовые ячейки
C) анонимные каналы
D) именованные каналы
E) расширенный ввод-выод с процедурами завершения
30. Алтернативой способу межпроцесной связи является
A) RPC
B) SEH
C) EXE
D) DLL
E) ACL
№ вопроса
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Правильный ответ
А
А
А
А
А
А
А
А
А
А
А
А
А
А
А
№вопроса
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
81
Правильный ответ
А
А
А
А
А
А
А
А
А
А
А
А
А
А
А
2.7 Экзаменационные вопросы по курсу
1. Основы программирования в операционной системе Windows.
2. Возможные структуры программ для Windows.
3. Классическая структура программы для Windows.
4. Вызов функций API
5. Структура программы. Регистрация класса окон.
6. Структура программы. Создание главного окна.
7. Структура программы. Цикл обработки очереди сообщений.
8. Структура программы. Процедура главного окна.
9. Вопросы системного программирования в Windows.
10. Страничная и сегментная адресация.
11. Схема преобразования логического адреса в линейный адрес в защищенном режиме
адресации
12. Преобразование линейного адреса в физический с учетом страничной адресации
13. Плоская модель памяти FLAT. Адресное пространство процесса.
14. Понятие ресурса. Редакторы и трансляторы ресурсов.
15. Ресурсы. Иконки.
16. Ресурсы. Курсоры.
17. Ресурсы. Битовая картинка.
18. Ресурсы. Строка.
19. Ресурсы. Диалоговое окно.
20. Ресурсы. Акселераторы.
21. Ресурсы. Меню.
22. Объекты ядра.
23. Учет пользователей объектов ядра. Защита.
24. Таблица описателей объектов ядра.
25. Создание, закрытие объекта ядра.
26. Совместное использование объектов ядра несколькими процессами.
27. Наследование описателя объекта.
28. Совместное использование объектов ядра несколькими процессами.
29. Именованные объекты.
30. Совместное использование объектов ядра несколькими процессами.
31. Дублирование описателей объектов.
32. Управление процессами. Процессы и потоки в Windows.
33. Создание процессов. Параметры функции CreateProcess().
34. Идентификация процессов. Параметры.
35. Копирование дескрипторов.
36. Выход из процесса и его завершение.
37. Ожидание завершения процесса.
38. Блоки и строки окружения процесса.
39. Создание событий управления консоли.
40. Обзор потоков. Основы потока.
41. Задания.
42. Ограничения, налагаемые на процессы в задании.
43. Включение процесса в задание
44. Завершение всех процессов в задании.
45. Получение статистической информации о задании.
46. Управление потоками. Внутреннее устройство потока.
47. Функция CreateThread(). Параметры.
48. Идентификация потока.
49. Приостановка и продолжение выполнения потоков.
50. Ожидание завершения потоков.
82
51. Удаленные потоки.
52. Локальная память потоков.
53. Приоритеты потоков и процессов.
54. Состояния потока.
55. Синхронизация потоков.
56. Необходимость синхронизации потоков.
57. Объекты синхронизации потоков.
58. Критические секции и защита данных.
59. Объект CRITICAL_SECTION.
60. Использование объектов CRITICAL_SECTION для защиты разделяемых переменных.
61. Мьютексы. Покинутые мьютексы.
62. Мьютексы в сравнении с объектами CRITICAL_SECTION.
63. Синхронизация кучи.
64. Объекты синхронизации потоков. Семафоры.
65. Объекты синхронизации потоков. События.
66. Дополнительные правила использования мьютексов и объектов CRITICAL_SECTION.
67. Дополнительные сблокированные функции.
68. Обсуждение быстродействия при управлении памятью.
69. Мьютексы, события и модель переменных состояния.
70. Объект очереди.
71. Управление памятью.
72. Архитектура управления памятью в Win32.
73. Кучи. Управление памятью кучи.
74. Последовательность действий при работе с кучей.
75. Файлы, отображаемые в память. Объекты отображения файлов.
76. Отображения адресного пространства процесса в объекты отображения.
77. Динамические библиотеки.
78. Динамические библиотеки. Неявное связывание. Явное связывание.
79. Точки входа динамической библиотеки.
80. Реестр. Управление разделами.
81. Реестр. Управление параметрами.
82. Файловые системы Win32.
83. Именование файлов. Открытие, чтение, запись и закрытие файлов.
84. Стандартные устройства и консольный ввод-вывод.
85. Управление файлами и каталогами.
86. Указатели файлов. Определение размера файла.
87. Атрибуты файлов и работа с каталогами.
88. Стратегии обработки файлов.
89. Блокировка файлов.
90. Исключения и их обработчики.
91. Ошибки и исключения.
92. Обработчики завершения.
93. Обработчики управления консоли.
94. Атрибуты безопасности.
95. Обзор элементов безопасности: дескриптор безопасности.
96. Идентификаторы безопасности.
97. Управление списками АСL.
98. Чтение и изменение дескрипторов безопасности.
99. Анонимные каналы. Именованные каналы.
100. Почтовые ячейки. Создание, подключение и обозначение каналов и почтовых ячеек.
83
Глоссарий
1. Процесс содержит собственное независимое виртуальное адресное пространство с
кодом и данными, защищенными от других процессов.
2. Поток в Win32 является основным элементом выполнения, и процесс может
содержать несколько независимых потоков, разделяющих его адресное пространство
и другие ресурсы.
3. Планирование потоков проводится на основе обычных факторов: доступности
ресурсов, таких как процессоры и физическая память, приоритетов, справедливости
распределения ресурсов и т.д.
4. Локальная память потока (Thread Local Storage - TLS) – массивы указателей,
которые дают процессу возможность выделять память для создания собственного
уникального окружения данных.
5. Критическая секция - часть кода, которая одновременно может выполняться только
одним потоком
6. Мьютекс (взаимное исключение) – это объект, схожий по смыслу с критической
секцией. Мьютекс переходит в свободное состояние, когда он не занят (не захвачен)
ни одним потоком
7. Событие устанавлиает объект в свободное (сигнальное состояние)
8. Семафор – синхронизирующий объект, используемый для учета ресурсов. Он
содержит счетчик, определяющий число доступных ресурсов. Семафор находится в
свободном состоянии, если значение его счетчика больше нуля, и в занятом, если
значение счетчика равно нулю (все ресурсы выбраны)
9. Блокировка файлов – это ограниченная форма синхронизации параллельных
процессов и потоков. Заблокированный файл может быть открыт только для чтения
(совместный доступ) или для чтения и записи (монопольный доступ)
10. Реестр – это централизованная иерархическая база данных конфигурации
приложений и системы.
11. Структурная обработка исключительных ситуаций (Structured Exception Handling
- SEH) в Win32 представляет собой надежный механизм, позволяющий приложениям
отвечать на неожиданные события. SHE делает возможным завершение программы из
любой точки в блоке кода, а также автоматически выполняет указанные
программистом действия и восстановление при ошибках.
84
СОДЕРЖАНИЕ
УЧЕБНАЯ ПРОГРАММА ДИСЦИПЛИНЫ – SYLLABUS
1.1 Данные о преподавателя
1.2 Данные о дисциплине
1.3 Пререквизиты
1.4 Постреквизиты
1.5 Цели и задачи дисциплины
1.6 Перечень и виды заданий и график их выполнения
1.7 Список литературы
1.8 Контроль и оценка знаний
1.9 Политика и процедура курса
2. СОДЕРЖАНИЕ АКТИВНОГО РАЗДАТОЧНОГО МАТЕРИАЛА
2.1 Тематический план курса
2.2 Тезисы лекционных занятий
2.3 Планы лабораторных занятий
2.4 Планы занятий в рамках самостоятельной работы студентов
под руководством преподавателя (СРСП)
2.5 Планы занятий в рамках самостоятельной работы студентов (СРС)
2.6 Тестовые задания для самоконтроля
2.7 Экзаменационные вопросы по курсу
Глоссарий
85
3
3
3
3
3
4
5
5
7
7
7
8
73
76
77
78
82
84
УМК ДС обсужден на заседании кафедры
“Программное обеспечение систем и сетей”
Протокол № 7 « 26 » февраля 2008 г.
УМК ДС одобрен на заседании научно-методического
Совета института Информационных технологий
Протокол № ___ «__ » _______ 2008 г.
УЧЕБНО-МЕТОДИЧЕСКИЙ КОМПЛЕКС
ДИСЦИПЛИНЫ ДЛЯ СТУДЕНТОВ
по дисциплине Системное программирование
для специальности 050602 – Информатика
Мустафина Бахытжан Мухамеджановна
_________________________________________________________
Подписано в печать___.___.200___г. Формат 60х84 1/16. Бумага книжножурнальная. Объем ___.___уч.-изд.л. Тираж___экз. Заказ №___.
______________________________________________________________
Отпечатано в типографии издательства КазНТУ имени К.И. Сатпаева
г.Алматы, ул.Ладыгина, 32
86
Download