ГЛАВА 5

- Объекты и процессы ядра Windows
  - Многозадачность
  - Объекты ядра Windows
  - Процессы и потоки
    - Объекты ядра
    - Объекты GDI и User
- Управление памятью в Win32
  - Организация виртуальной памяти в Windows
  - Кучи и менеджеры куч
  - Динамические хранилища
- Обработка ошибок в Win32
  - Обработка ошибок с помощью функции GetLastError
  - Обработка ошибок с помощью функции SetErrorMode
  - Экстренное завершение приложения

Любой программист, создающий приложения Windows, должен иметь представление о процессах, происходящих в операционной системе, для создания качественных и эффективных приложений. Такие знания помогут решить многие проблемы, возникающие при разработке приложений, тесно связанных с операционной системой. В рассмотренных ранее главах мы уже изучили понятие потока и многопоточности. Эти понятия имеют непосредственное отношение к процессам операционной системы.
В данной главе мы рассмотрим, что такое объект ядра и что такое процесс ядра операционной системы. Изучим объекты GDI и User. Рассмотрим, как Windows управляет памятью и как эта операционная система обрабатывает ошибки.
Объекты и процессы ядра Windows
Сразу оговоримся, что все сказанное в этой главе относится к следующим версиям Windows: Windows 95, 98, 2000 и Windows NT, т. к. только в данных версиях была введена поддержка 32-разрядных приложений.
Примечание.
Для среды Windows 3.1 Microsoft специально разработала пакет Win32s, позволяющий с некоторыми ограничениями использовать поддержку приложений Win32.
Ядро Windows (Windows kernel) - это часть операционной системы, которая обеспечивает поддержку низкоуровневых функций, необходимых для выполнения приложений. Например, всякий раз, когда приложению нужна дополнительная память, оно обращается к ядру Windows.
Между всеми вышеперечисленными системами существуют различия в поддержке 32-разрядных приложений. В табл. 1.8 перечислены некоторые отличия, существующие между тремя операционными системами.
Таблица 1.8. Различия операционных систем при поддержке Win32 API

| Характеристика | Windows NT | Windows 95 | Windows 3.1 с поддержкой Win32 |
| 32-битная система координат | Есть | Нет | Нет |
| Асинхронный файловый ввод/вывод | Есть | Нет | Нет |
| Асинхронная модель ввода информации | Есть | Есть | Нет |
| Мультимедиа API | Есть | Есть | На уровне Windows 3.1 |
| Поддержка многопроцессорных материнских плат | Есть | Нет | Нет |
| Динамический обмен данными (DDE) по сети | Есть | Есть | Нет |
| Поддержка процессоров других фирм-производителей (не Intel) | Есть | Нет | Нет |
| Вытесняющая многозадачность | Есть | Есть | Нет |
| Безопасность (сертификат С2) | Есть | Нет | Нет |
| Разделяемое адресное пространство | Есть | Есть | Нет |
| Поддержка TAPI (Telephone API) | Есть | Есть | Нет |
| Потоки | Есть | Есть | Нет |
| Системные ресурсы для объектов User и GDI | Практически не ограничены | Расширенные | Ограниченные |

Итак, Win32 API (Application Programming Interface) - это интерфейс разработки 32-разрядных приложений Windows.
Многозадачность
Многозадачность (multitasking) - свойство операционной системы выполнять одновременно несколько приложений.
Примечание
По сути, истинной многозадачности нет в большинстве из вышеперечисленных операционных систем. Происходит лишь переключение между задачами. Но, за счет того, что эти переключения происходят довольно часто, у пользователя создается впечатление, что приложения выполняются одновременно.
В ранних, 16-разрядных операционных системах поддерживалась так называемая кооперативная многозадачность (cooperative multitasking). Это такой вид многозадачности, когда приложение в процессе своего выполнения само "решает" передавать управление операционной системе или продолжать занимать процессорное время. При этом другие приложения, запущенные вместе с первым, просто не выполняют никаких действий. Данный вид многозадачности приводил к "зависаниям" операционной системы вместе со всеми запущенными приложениями, если "висло" приложение, захватившее ресурсы процессора.
На смену кооперативной многозадачности пришла вытесняющая многозадачность (preemptive multitasking). Вытесняющая многозадачность появилась лишь в 32-разрядных операционных системах. Данный вид многозадачности подразумевает, что управление всеми приложениями ведет операционная система. Она выделяет каждому приложению определенный квант времени процессора в зависимости от приоритета приложения (см. главу 3).
Объекты ядра Windows
Рассмотрим объекты, с которыми работает операционная система Windows.
Сразу обратим ваше внимание на то, что объекты Win32 и объекты Delphi - абсолютно разные объекты. В Win32 объекты делятся на объекты ядра (kernel) и объекты GDI и User.
Процессы и потоки
Процесс - это выполняющееся приложение Windows. Так как Windows - многозадачная операционная система, то в ней может работать сразу несколько процессов. Каждый процесс получает свое адресное пространство (размером до 4-х гигабайт). В этом пространстве хранится код приложения, его данные, а также все подключаемые библиотеки (DLL).
Сами процессы ничего не выполняют. Каждый процесс состоит из потоков (threads), которые выполняют код процесса (подробнее о потоках и об их создании см. главу 3). Любой процесс состоит как минимум из одного потока, который называется первичным или главным потоком (primary thread). Процесс может состоять из нескольких потоков, только один из которых будет главным.
Поток - это объект операционной системы, который представляет собой часть кода, находящегося внутри некоторого процесса. .
При создании процесса операционная система создает его главный поток, который может генерировать дополнительные потоки. При этом каждому потоку процесса Windows выделяет свои кванты времени процессора, в зависимости от приоритета потока.
Для работы с процессами Win32 API имеет встроенные функции, перечисленные в табл. 1.9.
Таблица 1.9. Функции Win32 API для работы с процессами

| Функция Win32 АРI | Предназначение |
| CreateProcess | Создает новый процесс и его главный поток. Используется вместо функции Windows 3.1 winExec. При помощи данной функции можно запускать приложения |
| ExitProcess | Завершение выполнения процесса и всех его потоков |
| GetCurrentProcess | Возвращает псевдодескриптор текущего процесса. Настоящий дескриптор текущего процесса можно получить С ПОМОЩЬЮ функции DuplicateHandle |
| DuplicateHandle | Функция, предназначенная для создания копии объекта ядра |
| GetCurrentProcess ID | Возвращает идентификатор текущего процесса. Данный идентификатор уникален для каждого процесса операционной системы |
| GetExitCodeProeess | Получение статуса окончания процесса |
| GetPriorityClass | Возвращает класс приоритета для конкретного процесса |
| GetStartupInfo | Возвращает содержимое структуры TStartupinfo, которая создается во время создания процесса |
| OpenProcess | Возвращает дескриптор процесса по его идентификатору |
| SetPriorityClass | Устанавливает класс приоритета для конкретного процесса |
| TerminateProcess | Прекращение выполнения процесса и всех его потоков |
| WaitForInput Idle | Перевод процесса в режим ожидания ввода |

Объекты ядра
Объекты ядра (kernel objects) - это процессы, потоки, события, семафоры, мьютексы и т. д., т. е. все то, с чем работает ядро Windows.
При создании объекта он существует в адресном пространстве процесса. При этом дескриптор объекта доступен породившему его процессу. Данный дескриптор не может быть использован другими приложениями для доступа к объекту разных процессов. Но эта проблема разрешима: с помощью функций Win32 API процесс имеет возможность получения собственного дескриптора (отличного от дескриптора процесса, породившего объект) для уже существующего объекта.
Например, первый процесс создает именованный или неименованный мьютекс и возвращает его дескриптор с помощью команды createMutex. Для того чтобы данный мьютекс мог использоваться другим процессом, необходимо воспользоваться функцией openMutex, которая возвращает дескриптор уже существующего мьютекса.
При обращении к объектам ядра Windows использует счетчик обращений к объекту. При создании или использовании объекта ядра приложениями счетчик увеличивается. При прекращении использования объекта ядра приложением счетчик уменьшается. Наконец, при обнулении счетчика объект ядра уничтожается.
Объекты GDI и User
В Windows 3.1 не было объектов ядра. Доступ ко всем объектам операционной системы осуществлялся с помощью дескрипторов. Объекты операционной системы делились на две группы: объекты, находящиеся в локальной памяти модулей GDI и User, и объекты, находящиеся в глобальной памяти.
Интерфейс графического устройства (Graphical Device Interface, GDI) - это часть Windows, которая управляет шрифтами, средствами печати и другими графическими системами Windows.
Когда приложение выводит что-либо на экран, оно использует службы, представляемые GDI.
Объекты GDI- это палитры, изображения, шрифты, и т. д., т.е. то, чем управляет GDI.
Средства пользовательского интерфейса (User) - это часть Windows, отвечающая за все окна, которые создаются приложениями.
Объекты User - это, в первую очередь, окна, меню.
В 16-разрядной версии Windows имелась непосредственная связь между объектом и его дескриптором. Таким образом, в данной версии Windows существовала таблица, содержащая указатели на все объекты операционной системы. Эта таблица была доступна любому приложению или динамически компонуемой библиотеке Windows. Она называлась таблицей локальных дескрипторов (Local Descriptor Table). В результате, любое приложение (или DLL) могло обращаться к объекту, используемому другим приложением.
В 32-разрядной операционной системе объекты хранятся в собственных адресных пространствах процессов, и для каждого процесса существует своя собственная таблица дескрипторов объектов. Таким образом, каждый процесс теперь работает с собственными дескрипторами объектов.
Любые дескрипторы объектов GDI или User управляются специальными подсистемами Win32 API. Для GDI - это GDI.EXE, для User - USER.EXE. Данные подсистемы выполняют создание, освобождение и проверку корректности работы дескрипторов объектов.
Управление памятью в Win32
В данном разделе мы рассмотрим все, чего еще не касались ранее о распределении памяти в Win32.
Организация виртуальной памяти в Windows
Как нам уже известно, Win32 - это 32-разрядная операционная система. Таким образом, любое приложение, запущенное в Win32, может захватывать адресное пространство в размере 4 Гбайта. Каждое приложение (процесс операционной системы) обладает своим индивидуальным адресным пространством, которое не пересекается с адресными пространствами других приложений.
Процесс выделения памяти в Windows состоит из двух последовательных этапов:
- резервирования участка памяти (виртуального адресного пространства), необходимого размера для размещения приложения и его данных. На этом этапе, физически, операционная система не выделяет оперативную память. То есть, фактически, вы можете дать команду операционной системе зарезервировать 300 Мбайт адресного пространства, и при этом не займете практически никаких системных ресурсов. Вы можете непосредственно указать операционной системе адреса, которые резервируете, или предоставить это дело операционной системе;
Примечание
Адресное пространство в Win32 резервируется блоками по 64 Кбайта. Поэтому, несмотря на ваши пожелания, при резервировании адресного пространства операционная система реально зарезервирует первый (базовый) участок адресного пространства объемом 64 Кбайта. Данное разбиение адресного пространства на блоки служит для ускорения работы ядра операционной системы.
- выделения реальной, физической памяти в зарезервированный участок виртуального адресного пространства. На данном этапе операционная система выделяет реальный блок памяти, который также состоит из небольших блоков.
Примечание
Минимальный блок памяти, с которым работает операционная система, называется страницей памяти. Размер страницы памяти различен в разных операционных системах. Например, в Windows NT - он равен 8 Кбайт, а в Windows 95/98 - 4 Кбайта.
Так как объем оперативной памяти компьютера обычно достаточно небольшой (для современных компьютеров 32-128 Мбайт), то операционная система вынуждена при нехватке основной оперативной памяти использовать так называемый файл подкачки (swap file) или виртуальную память. Таким образом, если в оперативной памяти компьютера имеется несколько приложений, запущенных одновременно, и им не хватает оперативной памяти, Windows просто выгружает те страницы памяти, к которым длительное время не было обращений. В случае, когда приложению потребуется выгруженная страница памяти, Windows освободит страницу, выгрузив страницу, к которой давно не было обращений, и загрузит на ее место требуемую, после чего вернет управление приложению.
Все вышеперечисленные действия абсолютно незаметны для приложения. Приложению не нужно заботиться о выгрузке и загрузке страниц, все эти действия выполняет операционная система.
Кучи и менеджеры куч
Серьезные приложения интенсивно используют механизмы выделения и освобождения памяти. В коммерческих приложениях широко применяются такие элементы, как: динамические массивы, строки, объекты и многое другое. При этом, создание и удаление подобных элементов происходит довольно часто, а сами элементы имеют относительно небольшой размер.
Такая работа по выделению памяти и ее освобождению, по отношению к элементам небольшого размера, является неэффективной. Во-первых, снижается производительность, т. к. резервирование адресного пространства и выделение страниц памяти происходит на уровне ядра операционной системы. Во-вторых, теряются большие объемы памяти. Например, если приложение требует выделить 256 байт под строку, операционная система реально выделяет одну страницу памяти, объемом 4 или 8 килобайт (в зависимости от операционной системы).
Для решения проблемы выделения памяти небольшим элементам приложения была введена организация по принципу кучи (heap). Куча - это достаточно большой непрерывный участок памяти, из которого выделяются небольшие блоки. Для того чтобы куча могла функционировать, в операционную систему был включен так называемый менеджер кучи. Менеджер кучи - это специальный механизм, который следит за выделением и освобождением блоков памяти. Для каждого вновь созданного процесса Windows по умолчанию создает кучу. Все кучи, которые создаются операционной системой, являются потокобезопасными. Следовательно, у программиста есть возможность обращаться к одной куче из разных потоков одновременно. Для работы с менеджером кучи Win32 API можно пользоваться следующими функциями (табл. 1.10).
Таблица 1.10. Функции Win32 API для работы с кучей

| Функция | Назначение |
| HeapCreate | Резервирует непрерывный блок памяти в виртуальном адресном пространстве процесса, т. е. создает кучу |
| HeapAlloc | Выделяет блок неперемещаемой памяти в куче |
| HeapReAlloc | Служит для изменения размера блока памяти, выделенного функцией HeapAlloc |
| HeapFree | Освобождает блок памяти, выделенной функцией HeapAlloc |
| HeapDestroy | Уничтожает кучу, созданную с помощью функции HeapCreate |
| VirtualAlloc | Резервирует или размещает страницы в виртуальной памяти процесса |
| VirtualFree | Освобождает страницы в виртуальной памяти процесса |
| VirtualLock | Защищает диапазон адресов от переноса в страницу памяти |
| VirtualUnlock | Снимает защиту, установленную функцией VirtualLock |
| VirtualQuery | Выдает информацию о диапазоне страниц в виртуальной памяти процесса, вызвавшего данную функцию |
| VirtualQusryEx | То же, что И VirtualQuery, только для определенного процесса |
| VirtualProtect | Изменяет права доступа к определенному диапазону памяти в адресном пространстве процесса, вызвавшего данную функцию |
| VirtualProtectEx | То же, что и VirtualProtect, только для определенного процесса |

Кроме перечисленных в табл. 1.10, существуют еще функции LocalAlloc (GlobalAlloc) и LocalFree (GlobalFree). Вы можете использовать любое на-писание функций, т. к. в Win32 нет разделения на глобальные и локальные кучи. Данные функции применяются, в основном, при работе с буфером обмена Windows. Эти функции являются устаревшими и не рекомендуются к использованию.
Кроме стандартного менеджера кучи, который предлагает Microsoft, создателями Delphi был предложен свой менеджер кучи, который поставляется вместе с Delphi. Менеджер кучи Delphi резервирует блоки виртуальной памяти размером 1 Мбайт. Размер выделяемых Delphi блоков реальной памяти - 16 Кбайт. При работе с менеджером куч Delphi можно использовать следующие функции:
- New, Dispose - применяются для выделения и освобождения памяти. Используются для динамической работы с элементами приложения;
- GetMem, FreeMem - также применяются для выделения и освобождения памяти. Используются для динамической работы с небольшими двоичными блоками памяти (блоки, буферы и т. п.).
Кроме вышеперечисленных менеджеров куч, программисту предоставлена возможность создания собственного менеджера. .
Динамические хранилища
Часто при создании приложений возникает задача, когда необходимо накапливать поступающие данные. В качестве примера можно привести задачу записи звука, прием данных по сети и т. п. В случае, когда объем передаваемых данных заранее известен - проблемы не возникает. В этом случае можно выделить блок памяти требуемого объема и заполнять его поступающими данными. Проблема может возникнуть, когда объем поступающих данных заранее неизвестен, и мы не знаем, какой объем памяти нам может понадобиться. Тогда используются так называемые динамические (или потоковые) хранилища. В Delphi в качестве таких хранилищ могут выступать динамические массивы, объект TMemoryStream и динамическое перераспределение памяти. Принцип работы всех перечисленных хранилищ достаточно прост: под запись данных выделяется блок памяти, затем, когда этот блок полностью заполняется данными, он изменяет свой размер на больший.
Нужно помнить, что такое изменение объема блока памяти внутри кучи достаточно неэффективно, особенно когда размер блока памяти достаточно большой. Сам процесс изменения объема блока памяти занимает большое количество системных ресурсов.
Обработка ошибок в Win32
Многие из функций Win32 API возвращают в качестве результата своего выполнения значение true или false. По данному значению мы можем узнать, успешно ли выполнилась функция.
Обработка ошибок с помощью функции GetLastError
При неудачном завершении своего выполнения функция возвращает значение false. Для того чтобы получить код происшедшей ошибки, можно воспользоваться функцией Win32 API GetLastError. Данная функция не содержит параметров.
Примечание
Не во всех случаях при возвращении функцией значения false, GetLastError возвращает код происшедшей ошибки. Поэтому, в некоторых случаях, вызов функции GetLastError не приведет к нужному результату.
Обратите внимание на то, что код ошибки связан с потоком, в котором данная ошибка произошла. Ошибки, произошедшие в разных потоках, будут иметь собственные коды ошибок, определяемые функцией GetLastError. Поэтому вызывайте функцию GetLastError в том же потоке, в котором произошла ошибка.
Примечание
Коды ошибок можно посмотреть в справочной системе Win32 Programmer's Reference, поставляемой вместе с Delphi.
Рассмотрим пример использования функции GetLastError:
If not CreateProcess (CommandLine, nil, nil, nil, False,
NORMAL_PRIORITY_CLASS, nil, nil, Startuplnfo, Processlnfo) then
Raise Exception.Create ('Ошибка создания процесса: ' +
IntToStr (GetLastError));
Данный код пытается создать процесс. При возникновении ошибки будет сгенерировано исключение, которое выведет код ошибки, полученный с помощью функции GetLastError.
Обработка ошибок с помощью функции SetErrorMode
Некоторые ошибки, такие как, например, "Устройство не готово" (при записи информации на дискету, которая защищена от записи), вызывают окна сообщений Windows, для информирования пользователя о случившейся ошибке. Приложение может обрабатывать такие ошибки, с помощью использования функции SetErrorMode:
SetErrorMode (uMode);
В данную функцию можно передавать любое из перечисленных в табл. 1.11 значение параметра uMode.
Таблица 1.11. Значения параметра uMode функции SetErrorMode

| Значение параметра | Выполняемое действие |
| SEM_FAILCRITICALERRORS | При установке данного значения операционная система не отображает окно сообщения об ошибке. Вместо этого, операционная система отправляет данную ошибку вызвавшему ее процессу |
| SEM_NOALIGNMENTFAULTEXCEPT | Данное значение не используется для процессоров семейства х86 |
| SEM_NOGPFAULTERRORBOX | Данное значение применяется при отладке приложений. При установке этого значения операционная система не отображает окно сообщения об ошибке |
| SEM_NOOPENFILEERRORBOX | При установке данного значения, операционная система не отображает окно сообщения об ошибке открытия файла. Ошибка возвращается в вызвавший ее процесс |

Экстренное завершение приложения
Для экстренного завершения работы приложения служит функция Win32 API FatalAppExit. Данная функция отображает окно сообщения о критической ошибке и, после закрытия этого окна пользователем, приложение завершает свою работу.
Примечание
Пользуйтесь этой функцией только в самых экстренных случаях, когда совсем ничего нельзя сделать для устранения ошибок. Данная функция опасна тем, что не освобождает системные ресурсы и память, занимаемую приложением.

Глава 4 Содержание Глава 6