Глава 17. Программирование в защищенном режиме DOS
Микропроцессор 80286 дает новый способ адресации к памяти:
защищенный режим виртуальной адресации или просто защищенный ре-
жим. Этот новый режим адресации дает три основных преимущества:
* Адресация к памяти объемом до 16 мегабайт.
* Логическое адресное пространство, превышающее пространство
физических адресов.
* Способ изоляции программ друг от друга, так что одна прог-
рамма не может нарушать другой выполняющейся одновременно
с ней программы.
С помощью Borland Pascal вы легко можете писать работающие в
защищенном режиме прикладные программы DOS без необходимости при-
менения дополнительного "расширителя" DOS. Вы обнаружите, что
многие программы реального режима прекрасно работают в защищенном
режиме. Данная глава поможет вам модифицировать те программы, ко-
торые этого не делают, и прояснит некоторые основные моменты за-
щищенного режима и его отличия от реального режима.
Что такое защищенный режим?
Процессор 80286 и более поздние процессоры поддерживают два
режима операций: защищенный режим и реальный режим. Реальный ре-
жим совместим с работой процессора 8086 и позволяет прикладной
программе адресоваться к памяти объемом до одного мегабайта. За-
щищенный режим расширяет диапазон адресации до 16 мегабайт. Ос-
новное отличие между реальным и защищенным режимом заключается в
способе преобразования процессором логических адресов в физичес-
кие. Логические адреса - это адреса, используемые в прикладной
программе. Как в реальном, также и в защищенном режиме логический
адрес - это 32-разрядное значение, состоящее из 16-битового се-
лектора (адреса сегмента) и 16-битового смещения. Физические ад-
реса - это адреса, которые процессор использует для обмена данны-
ми с компонентами системной памяти. В реальном режиме физический
адрес представляет собой 20-битовое значение, а в защищенном ре-
жиме - 24-битовое.
Когда процессор обращается к памяти (для выборки инструкции
или записи переменной), он генерирует из логического адреса физи-
ческий адрес. В реальном режиме генерация физического адреса сос-
тоит из сдвига селектора (адреса сегмента) на 4 бита влево (это
означает умножение на 16) и прибавления смещения. Полученный в
результате 20-разрядный адрес используется затем для доступа к
памяти.
16Мб-----------------
¦ ¦
--------- ¦ ¦
¦Смещение+- ¦ ¦
L--------- ¦ ¦ ¦
¦ +----------------+
L--+----->----------¦+ сегмент 64К
-->+----------------+-
¦ ¦ ¦
¦ ¦ Пространство ¦
--------- ------- ¦ ¦ адресов ¦
¦Селектор+-+ x 16 +------ ¦ ¦
L--------- L------- ¦ ¦
0L-----------------
Рис. 17.1 Генерация физического адреса в реальном режиме.
Чтобы получить физический адрес в защищенном режиме, селек-
торная часть логического адреса используется в качестве индекса
таблицы дескрипторов. Запись в таблице дескрипторов содержит
24-битовый базовый адрес, к которому затем для образования физи-
ческого адреса прибавляется смещение логического адреса.
16Мб-----------------
¦ ¦
--------- ¦ ¦
¦Смещение+- ¦ ¦
L--------- ¦ ¦ ¦
¦ +----------------+
Таблица дескрипторов L--+----->----------¦+ сегмент 64К
------- -->+----------------+-
+------+ ¦ ¦ ¦
+------+ ¦ ¦ Пространство ¦
+------+ ¦ ¦ адресов ¦
-->+------+---- ¦ ¦
¦ +------+ ¦ ¦
¦ +------+ 0L-----------------
¦ +------+
¦ +------+
¦ +------+
¦ +------+
¦ +------+
¦ +------+
¦ +------+
¦ +------+
--------- ¦ +------+
¦Селектор+-- L-------
L---------
Рис. 17.2 Генерация физического адреса в защищенном режиме.
Каждая запись в таблице дескрипторов называется дескриптором
и определяет сегмент в памяти. Запись таблицы дескрипторов зани-
мает 8 байт, а записанная в дескрипторе информация включает в се-
бя базовый адрес, предельное значение и флаги полномочий доступа
к сегменту.
Записи предельного значения сегмента и полномочий доступа в
дескрипторе определяют размер и тип сегмента. Сегменты могут
иметь размер от 1 до 65536 байт и могут быть сегментами кода или
сегментами данных. Сегменты кода могут содержать выполняемые ма-
шинные инструкции и доступные только по чтению данные. Сегменты
данных могут содержать данные, доступные по чтению и записи. За-
писывать данные в сегменты кода или выполнять инструкции в сег-
ментах данных невозможно. Любая попытка сделать это или попытка
доступа к данным вне границ сегмента вызывает общий сбой по на-
рушению защиты (сокращенно сбой GP). Поэтому режим и называется
защищенным.
По данному адресу в реальном режиме прикладная программа мо-
жет определить физический адрес. В защищенном режиме это обычно
не так, поскольку селекторная часть логического адреса является
индексом в таблице дескрипторов, и сам селектор не имеет прямого
отношения к вычислению физического адреса. Это дает то преиму-
щество, что управление виртуальной памятью можно реализовать, не
влияя на прикладную программу. Например, путем простого обновле-
ния поля базового адреса дескриптора сегмента, операционная сис-
тема может перемещать сегмент в физической памяти без влияния на
использующую сегмент прикладную программу. Прикладная программа
ссылается только на селектор сегмента, и на селектор не влияют
изменения в дескрипторе.
Прикладная программа редко имеет дело с дескрипторами. При
необходимости дескрипторы создаются и уничтожаются операционной
системой и администратором памяти, а прикладная программа знает о
соответствующих селекторах. Селекторы аналогичны описателям фай-
лов - с точки зрения прикладной программы это то, что обслужива-
ется операционной системой, но в операционной системе они работа-
ют как индексы содержащих дополнительную информацию таблиц.
Расширения Borland защищенного режима DOS
Расширения защищенного режима Borland Pascal реализованы че-
рез два компонента: DPMI-сервер (файл DPMI16BI.OVL) и администра-
тор этапа выполнения (файл RTM.EXE).
DPMI-сервер
Интерфейс защищенного режима DOS (DPMI) - это отраслевой
стандарт, позволяющий программам DOS аппаратно-независимым путем
получить доступ к развитым средствам персональных компьютеров,
реализованных на процессорах 80286, 80386 и 80486. Определены
функции DPMI для обслуживания таблиц дескрипторов, переключения
режима, распределения расширенной памяти, выделения памяти DOS,
управления подсистемой прерываний и взаимодействия с программами
реального режима.
Расширения защищенного режима Borland Pascal основаны на
спецификации DPMI 0.9. Хотя спецификация DPMI не поддерживает вы-
зовы DOS из прикладных программ защищенного режима, DPMI-сервер
Borland и серверы многих других фирм, включая улучшенный режим
Windows 3.x, поддерживают прерывание INT 21H и другие стандартные
прерывания DOS и BIOS, используемые обычно в приложениях DOS за-
щищенного режима.
Администратор этапа выполнения
Администратор этапа выполнения (RTM.EXE) является надстрой-
кой DPMI-сервера и обеспечивать для прикладных программ защищен-
ного режима несколько служебных функций. Администратор этапа вы-
полнения содержит загрузчик защищенного режима и администратор
памяти защищенного режима и позволяет под DPMI сосуществовать
нескольким клиентам защищенного режима.
Приложения защищенного режима Borland используют те же фор-
маты выполняемых файлов, что и Windows 3.x и OS/2 1.x. Программ-
ный загрузчик администратора этапа выполнения может загружать как
выполняемые файлы (.EXE), так и динамически компонуемые библиоте-
ки (.DLL).
Администратор памяти защищенного режима позволяет прикладным
программам защищенного режима распределять блоки динамической па-
мяти. Администратор памяти поддерживает фиксированные, перемещае-
мые и выгружаемые блоки, а также обслуживает код и сегменты дан-
ных прикладной программы. Используя уникальные для защищенного
режима средства, администратор памяти функционирует также в ка-
честве администратора оверлеев, автоматически загружая и выгружая
сегменты кода (по этой причине прикладной программе защищенного
режима не требуется модуль Overlay).
Прикладные программы могут получить доступ к программам за-
щищенного режима через модуль WinAPI. Модуль WinAPI, описанный в
следующем разделе, реализует подмножество функций API (прикладно-
го программного интерфейса) Windows, обеспечивая управление па-
мятью, обслуживание программных модулей, управление ресурсами,
загрузку динамически компонуемых библиотек и доступ к селекторам
на нижнем уровне. Поскольку администратор этапа выполнения API
является подмножеством API Windows, вы можете написать совмести-
мые на уровне двоичного кода динамически компонуемые библиотеки,
которые можно использовать и в защищенном режиме DOS, и в
Windows.
Разработка прикладных программ DOS защищенного режима
Написание прикладной программы защищенного режима не предс-
тавляет собой сложной задачи. Вам не нужно беспокоиться о селек-
торах и адресах памяти. Операционная система с расширениями
Borland все делает за вас. Фактически, большинство ваших программ
реального режима может прекрасно работать в защищенном режиме. В
следующих разделах описывается некоторая разница между реальным и
защищенным режимом, о которых вы должны знать при разработке
прикладной программы защищенного режима.
Надежное программирование в защищенном режиме
Существует несколько приемов, используемых обычно в програм-
мах реального режима, которые в программах защищенного режима бу-
дут приводить к общему нарушению защиты (сбой GP). Borland Pascal
при сбое GP выводит ошибку этапа выполнения 216. Сбой GP происхо-
дит, когда вы пытаетесь получить доступ к памяти, к которой ваша
прикладная программа обращаться не может. Операционная система
останавливает прикладную программу, но сбоя системы не происхо-
дит. Хотя сбои GP и прекращают работу вашей программы, система
"защищена" от сбоя. К сбою GP приводит следующее:
* загрузка в сегментные регистры недопустимых значений;
* обращение к памяти вне границы сегмента;
* запись в сегмент кода;
* разыменование указателей nil.
Примечание: Сбои по нарушению защиты предохраняют вашу
систему от плохой практики программирования.
Загрузка в сегментные регистры недопустимых значений
Когда процессор работает в защищенном режиме, сегментные ре-
гистры (CS, DS, ES и SS) могут содержать только селекторы. Пос-
кольку селекторы являются индексами в таблице дескрипторов, они
не имеют физического отношения к памяти, на которую ссылается.
Если вы пытаетесь загрузить в сегментный регистр произвольное
значение, то возможно получите сбой GP, поскольку это значение
может не представлять допустимого дескриптора.
Функция Ptr и массивы Mem
При разыменовании указателей компилятор генерирует код для
загрузки сегментного регистра. Если вы строите указатели с по-
мощью стандартной функции Ptr, то нужно обеспечить, чтобы сег-
ментная часть указателя была допустимым селектором. Аналогично,
при работе с массивами Mem, MemW и MemL вы вместо физических ад-
ресов должны использоваться селекторы. Например, при доступе к
рабочей области ROM BIOS (сегмент $0040) или к областям видеопа-
мяти (сегменты $A000, $B000 и $B800) следует использовать вместо
абсолютных значений переменные SegXXXX. (Переменные SegXXXX опи-
сываются ниже.)
Абсолютные переменные
В защищенном режиме вы не можете задавать абсолютный адрес
переменной. Любой исходных код, где сегмент и смещение задаются в
операторе absolute, нужно переписать. Например, вам может потре-
боваться построить указатель, используя переменные SegXXXX.
Операции с сегментами
Добавление или вычитание значений из селекторной части ука-
зателя обычно не допускается. Например, добавление к селекторной
части указателя $1000 в реальном режиме увеличивает указатель на
64К, но в защищенном режиме результирующий указатель будет недо-
пустимым. Вместо этого для выделения и управления блоками памяти
следует использовать функцию GlobalXXXX модуля WinAPI.
В Borland Pascal существует способ выполнения арифметических
операций с селекторами с помощью переменной SelectorInc (см. ни-
же).
Использование сегментных регистров в качестве временных переменных
В реальном режиме некоторые старые программы на ассемблере
используют сегментные регистры для хранения временных переменных.
В защищенном режиме это работать не будет, так как обычно сохра-
няемые в сегментных регистрах временные значения не являются до-
пустимыми селекторами.
Доступ к памяти вне границ сегмента
В реальном режиме каждый сегмент имеет размер 64К. В защи-
щенном режиме дескриптор сегмента содержит поле, специфицирующее
предельный размер сегмента, и если вы пытаетесь обратиться к дан-
ным вне границ сегмента, по получите сбой GP. При загрузке прик-
ладной программы администратор этапа выполнения устанавливает со-
ответствующие предельные значения для сегментов кода, данных и
стека. Кроме того, блок памяти, распределяемый с помощью функции
GlobalAlloc модуля WinAPI, имеет предельное значение сегмента,
соответствующее размеру блока памяти.
Запись в сегмент кода
В реальном режиме можно записывать переменные в сегмент ко-
да, поскольку реальные режим не определяет, что может и что не
может существовать в сегменте. В защищенном режиме это не так.
Селектор защищенного режима имеет флаг чтения/записи или доступа
только по чтению, а селекторы кода всегда отмечены как доступные
только по чтению. Если вы пытаетесь записывать в селектор сегмен-
та кода, происходит сбой GP. Однако вы можете использовать псев-
доним и написать самомодифицирующийся код (см. ниже).
Разыменование указателей nil
При преобразовании прикладной программы реального режима в
защищенный режим, в программе, которая уже годы работала без оши-
бок, возможно внезапное появление определенных ошибок. Например,
вы можете случайно разыменовывать указатель nil, или обнаружите,
что ваша программа содержит "потерянные" указатели, которые разы-
меновываются после их освобождения. В реальном режиме такие ошиб-
ки не обязательно проявляются, но в защищенном режиме они обычно
приводят к сбою GP. Согласно своему названию, защищенный режим
значительно лучше предохраняет вас от ошибок, связанных с указа-
телями.
Сегменты кода и данных
Аналогично программе Borland Pascal реального режима, прог-
рамма защищенного режима содержит несколько сегментов кода, сег-
мент данных и сегмент стека. При загрузке программы защищенного
режима администратор этапа выполнения автоматически выделяет се-
лекторы для сегментов кода, данных и стека. Для сегментов кода
с помощью директивы компилятора $C можно управлять отдельными ат-
рибутами. В частности, сегменты кода можно сделать перемещаемыми
или фиксированными в физической памяти, они могут загружаться
предварительно или по запросу, а также могут быть выгружаемыми
или постоянными.
Примечание: Подробнее о директиве компилятора $C расс-
казывается в Главе 21 данного руководства и в Главе 2 ("Ди-
рективы компилятора") "Справочного руководства программис-
та".
Атрибуты сегмента кода позволяют вам обозначать сегмент как
статический (перемещаемый, предварительно загружаемый, постоян-
ный) или динамический (перемещаемый, загружаемый по запросу, выг-
ружаемый). Таким образом, в защищенном режиме вам не нужно ис-
пользовать модуль Overlay и директиву компилятора $O, и в версии
модуля System для защищенного режима переменные OvrXXXXXX отсутс-
твуют.
Управление динамически распределяемой памятью
Администратор динамически распределяемой области памяти
Borland Pascal защищенного режима довольно существенно отличается
от администратора динамически распределяемой памяти Borland
Pascal реального режима. В частности, переменные HeapOrg,
HeapEnd, HeapPtr и FreeList в версии модуля System для защищенно-
го режима не определены. Администратор этапа выполнения динами-
чески распределяемой области памяти Borland Pascal защищенного
режима (который идентичен администратору этапа выполнения динами-
чески распределяемой области памяти Borland Pascal для Windows)
для выполнения основных операций по выделению и освобождению па-
мяти использует администратор этапа выполнения, а для оптимизации
распределения небольших блоков памяти включает в себя подсистему
вторичного распределения сегмента. Подробнее об администраторе
динамически распределяемой области памяти этапа выполнения расс-
казывается в Главе 21.
Предопределенные селекторы
В модуле System для обычно используемых адресов реального
режима предусмотрено несколько предопределенных селекторов. Они
именуются по физическому сегменту, которому данные селекторы
присвоены, и используются для совместимости между реальным и за-
щищенным режимом DOS.
Предопределенные селекторы Таблица 17.1
-------------------T--------------------------------------------
¦ Селектор ¦ Описание ¦
+------------------+--------------------------------------------+
¦ Seg0040 ¦ Используется для доступа к области данных¦
¦ ¦ BIOS $40 в младших адресах. ¦
+------------------+--------------------------------------------+
¦ SegA000 ¦ Используется для доступа к графической па-¦
¦ ¦ мяти EGA и VGA по адресу сегмента $A000. ¦
+------------------+--------------------------------------------+
¦ SegB000 ¦ Используется для доступа к видеопамяти мо-¦
¦ ¦ нохромного адаптера по адресу сегмента¦
¦ ¦ $A000. ¦
+------------------+--------------------------------------------+
¦ SegB800 ¦ Используется для доступа к видеопамяти¦
¦ ¦ цветного графического адаптера по адресу¦
¦ ¦ сегмента $A000. ¦
L------------------+---------------------------------------------
В реальном режиме переменные SegXXXX всегда содержат значе-
ния $0040, $A000, $B000 и $B800 соответственно. В защищенном ре-
жиме код запуска библиотеки исполняющей системы создает четыре
селектора, ссылающихся на конкретные области памяти реального ре-
жима. При ссылке на эти области памяти вам следует использовать
переменные SegXXXX. Например, если у вас был код следующего вида:
CtrMode := Mem[$40: $49];
то вместо него следует записать:
CtrMode := Mem[Seg0040: $49];
Используя переменные SegXXXX, вы можете гарантировать, что
ваша программа без изменений будет работать в реальном и защищен-
ном режимах.
Переменная SelectorInc
Переменная SelectorInc модуля System содержит значение, ко-
торое должно прибавляться к селектору или вычитаться из него для
получения следующего или предыдущего селектора в таблице дескрип-
торов. SelectorInc полезно использовать при работе с большими
блоками памяти (превышающими 64К) и при доступе к псевдонимам
сегментов.
Для выделения блоков, превышающих 64К (такие блоки называют
также большими блоками памяти), можно использовать функции
GlobalAlloc и GlobalAllocPrt в модуле WinAPI. Большие блоки неп-
рерывны в физической памяти, но из-за 16-разрядной архитектуры
процессора прикладная программа не может получить к ним доступ
целиком. Для большого блока памяти администратор памяти выделяет
несколько непрерывных (следующих подряд) селекторов, каждый из
которых (кроме последнего) ссылается на часть большого блока па-
мяти размером 64К. Например, чтобы выделить блока памяти размером
в 220К, администратор памяти создает четыре селектора, при этом
первые три селектора ссылаются на блоки по 64К, а последний се-
лектор - на блок размером 28К. Прибавляя SelectorInc к селектору,
принадлежащему большому блоку, вы можете получить селектор для
следующего сегмента, а вычитая SelectorInc - для предыдущего.
При распределении большого блока функция GlobalAlloc всегда
возвращает описатель первого сегмента, а GlobalAllocPtr - указа-
тель на первый сегмент.
Приведенная ниже функция GetPtr воспринимает указатель боль-
шого блока (возвращаемый функцией GlobalAllocPtr) и 32-разрядное
смещение и возвращает указатель на заданное внутри блока смеще-
ние.
function GetPtr(P: Pointer; Offset: Longint): Pointer;
type
Long = record
Lo, Hi: Word;
end;
begin
GetPtr := Ptr(
Long(P).Hi + Long(Offset).Hi * SelectorInc,
Long(P).Lo + Long(Offset).Lo);
end;
Заметим, что старшее слово параметра Offset используется для
определения того, сколько раз нужно увеличить селекторную часть P
для получения корректного сегмента. Например, если Offset равно
$24000, то селекторная часть P будет увеличена на 2 *
SelectorInc, а смещение P - на $4000.
Следующая функция LoadFile загружает в блок памяти весь файл
и возвращает указатель на блок. Если файл превышает 64К, то выде-
ляется большой блок памяти.
function LoadFile(const FileName: string): Pointer;
var
Buffer: Pointer;
Size, Offset, Count: Longint;
F: file;
begin
Buffer := nil;
Assign(F, FileName);
Reset(F, 1);
Size := FileSize(F);
Buffer := GlobalAllocPtr(gmem_Moveable, Size);
if Buffer <> nil then
begin
Offset := 0;
while Offset < Size do
begin
Count := Size - Offset;
if Count > $8000 then Count := $8000;
BlockRead(F, GetPtr(Buffer, Offset)^, Count);
Inc(Offset, Count);
end;
end;
LoadFile := Buffer;
end;
Переменная SelectorInc определена также в версии модуля
System для реального режима. В реальном режиме она всегда содер-
жит значение $1000, которое при сложении его с сегментной частью
указателя реального режима увеличивает указатель на 64К.
Другим образом вы можете использовать переменную SelectorInс
только в программах DOS защищенного режима. Используйте перемен-
ную SelectorInc для доступа к псевдонимам сегментов, выделяемых
администратором этапа выполнения при загрузке прикладной програм-
мы. Для каждого сегмента кода прикладной программы администратор
этапа выполнения создает селектор-псевдоним, ссылающийся на тот
же сегмент, но имеющий полномочия селектора данных. Для сегментов
стека и данных селекторы-псевдонимы не создаются.
Чтобы получить доступ к селектору-псевдониму для конкретного
сегмента, добавьте к селектору сегмента SelectorInc. Предположим,
например, что P - это переменная типа Pointer, а Foo - процедура
или функция. Тогда присваивание вида:
P := Addr(Foo)
приводит к тому, что P будет указывать на выполняемую доступную
только по чтению точку входа Foo, а после оператора:
P := Ptr(Seg(Foo) + SelectorInc, Ofs(Foo));
P будет ссылаться на тот же адрес, но с полномочиями на чте-
ние/запись.
Модуль WinAPI
Модуль WinAPI дает вам непосредственный доступ к расширениям
Borland защищенного режима DOS. Чтобы облегчить написание перено-
симых прикладных программ и совместимых на уровне двоичного кода
DLL, разработан интерфейс WinAPI, являющийся подмножеством интер-
фейса API Windows.
Модуль WinAPI позволяет вам использовать функции управления
памятью, управления ресурсами, модулями, селекторами и многие
другие функции API. Ниже приведено их краткое описание. Полное
описание констант, типов, процедур и функций модуля WinAPI вы мо-
жете найти в "Справочном руководстве программиста".
При работе под Windows подпрограммы API, поддерживаемые с
помощью модуля WinAPI, находятся в динамически компонуемых библи-
отеках KERNEL.DLL и USER.DLL. В защищенном режиме DOS эти DLL не
требуются, так как администратор этапа выполнения защищенного ре-
жима содержит реализацию подпрограмм KERNEL и USER, автоматичес-
ки перенаправляя их вызовы администратору.
Управление памятью
При разработке программ, работающих с динамической памятью,
обычно используются стандартные процедуры New, Dispose, GetMem и
FreeMem. Однако получить доступ к администратору памяти защищен-
ного режима Borland вы можете с помощью функций GlobalXXXX в мо-
дуле WinAPI.
Заметим, что функции GlobalXXXXPtr комбинируют в одной подп-
рограмме общие последовательности вызовов функций, такие как
GlobalAlloc, за которыми следуют вызовы GlobalLock, GlobalUnlock
или GlobalFree.
Подпрограммы управления памятью API
Таблица 17.2
----------------------T-----------------------------------------
¦ Функция ¦ Описание ¦
+---------------------+-----------------------------------------+
¦ GetFreeSpace ¦ Определяет объем свободной памяти в ди-¦
¦ ¦ намически распределяемой области. ¦
+---------------------+-----------------------------------------+
¦ GlobalAlloc ¦ Выделяет блок памяти в динамически расп-¦
¦ ¦ ределяемой области. ¦
+---------------------+-----------------------------------------+
¦ GlobalAllocPtr ¦ Выделяет и блокирует блок памяти (с по-¦
¦ ¦ мощью вызовов GlobalAlloc и GlobalLock).¦
¦ ¦ ¦
+---------------------+-----------------------------------------+
¦ GlobalCompact ¦ Переупорядочивает память, распределен-¦
¦ ¦ ную в динамической области, так что ос-¦
¦ ¦ вобождается заданный объем памяти. ¦
+---------------------+-----------------------------------------+
¦ GlobalDiscard ¦ Выгружает заданный объект памяти. ¦
+---------------------+-----------------------------------------+
¦ GlobalDosAlloc ¦ Распределяет память, к которой можно по-¦
¦ ¦ лучить доступ в реальном режиме DOS. Эта¦
¦ ¦ память будет существовать в первом мега-¦
¦ ¦ байте линейного адресного пространства. ¦
+---------------------+-----------------------------------------+
¦ GlobalDosFree ¦ Освобождает память, выделенную ранее с¦
¦ ¦ помощью GlobalDosAlloc. ¦
+---------------------+-----------------------------------------+
¦ GlobalFlags ¦ Получает информацию о блоке памяти. ¦
+---------------------+-----------------------------------------+
¦ GlobalFree ¦ Освобождает разблокированный блок памяти¦
¦ ¦ и делает его описатель недействительным.¦
+---------------------+-----------------------------------------+
¦ GlobalFreePtr ¦ Разблокирует и освобождает блок памяти¦
¦ ¦ с помощью GlobalUnlock и GlobalFree. ¦
+---------------------+-----------------------------------------+
¦ GlobalHandle ¦ Получает описатель объекта в памяти по¦
¦ ¦ заданному адресу сегмента. ¦
+---------------------+-----------------------------------------+
¦ GlobalLock ¦ Увеличивает счетчик ссылки блока памяти¦
¦ ¦ и возвращает указатель на него. ¦
+---------------------+-----------------------------------------+
¦ GlobalLockPtr ¦ То же, что и GlobalLock, но вместо опи-¦
¦ ¦ сателя воспринимает указатель. ¦
+---------------------+-----------------------------------------+
¦ GlobalLRUNewest ¦ Перемещает объект в памяти на новую не-¦
¦ ¦ давно используемую позицию, минимизируя,¦
¦ ¦ таким образом, вероятность выгрузки¦
¦ ¦ объекта. ¦
+---------------------+-----------------------------------------+
¦ GlobalLRUOldest ¦ Перемещает объект в памяти на самую¦
¦ ¦ "старую" недавно используемую позицию,¦
¦ ¦ максимизирую вероятность выгрузки объ-¦
¦ ¦ екта. ¦
+---------------------+-----------------------------------------+
¦ GlobalNorify ¦ Вызывает адрес экземпляра процедуры уве-¦
¦ ¦ домления, передавая описатель блока, ко-¦
¦ ¦ торый нужно выгрузить. ¦
+---------------------+-----------------------------------------+
¦ GlobalPageLock ¦ Увеличивает значение счетчика блокиров-¦
¦ ¦ ки для памяти, связанной с данным селек-¦
¦ ¦ тором. ¦
+---------------------+-----------------------------------------+
¦ GlobalPageUnlock ¦ Уменьшает значение счетчика блокировки¦
¦ ¦ для памяти, связанной с данным селекто-¦
¦ ¦ ром. ¦
+---------------------+-----------------------------------------+
¦ GlobalPtrHandle ¦ По заданному указателю на блок памяти¦
¦ ¦ возвращает описатель этого блока. ¦
+---------------------+-----------------------------------------+
¦ GlobalReAlloc ¦ Перераспределяет блок памяти. ¦
+---------------------+-----------------------------------------+
¦ GlobalReAllocPtr ¦ Разблокирует, перераспределяет и блоки-¦
¦ ¦ рует блок памяти (используя функции¦
¦ ¦ GlobalUnlock, GlobalReAlloc и¦
¦ ¦ GlobalLock). ¦
+---------------------+-----------------------------------------+
¦ GlobalSize ¦ Определяет текущий размер блока памяти. ¦
+---------------------+-----------------------------------------+
¦ GlobalUnfix ¦ Разблокирует блок памяти, блокированный¦
¦ ¦ ранее с помощью GlobalLock. ¦
+---------------------+-----------------------------------------+
¦ GlobalUnockPtr ¦ То же, что и GlobalUnlock, но вместо¦
¦ ¦ описателя воспринимает указатель. ¦
+---------------------+-----------------------------------------+
¦ LockSegment ¦ Блокирует заданный выгружаемый сегмент. ¦
+---------------------+-----------------------------------------+
¦ UnlockSegment ¦ Разблокирует сегмент. ¦
L---------------------+------------------------------------------
Функция GlobalAlloc используется для распределения блоков
памяти. Для их освобождения применяется функция GlobalFree. Адми-
нистратор памяти поддерживает три типа блоков памяти: фиксирован-
ный, перемещаемый и выгружаемый. Фиксированный блок остается в
одних и тех же адресах физической памяти. Перемещаемый блок может
перемещаться в физической памяти и освобождать место для других
запросов на выделение памяти, а выгружаемые блоки могут выгру-
жаться из памяти, освобождая место для других блоков. С помощью
передаваемых GlobalAlloc флагов вы можете выбрать один из этих
трех типов:
* gmem_Fixed (фиксированный)
* gmem_Moveable (перемещаемый)
* gmem_Moveable + gmem_Discardable (выгружаемый)
Прикладная программа обычно выделяет только перемещаемые
блоки памяти, которые представляются типом THandle в модуле
WinAPI. Описатель памяти - это значение размером в слово, которое
идентифицирует блок памяти аналогично тому, как описатель файла -
это значение размером в слово, идентифицирующее открытый файл.
Перед тем как вы сможете получить доступ к памяти, его нужно
заблокировать с помощью функции GlobalAlloc, а когда вы закончите
к нему обращаться, его нужно разблокировать с помощью функции
GlobalUnlock. GlobalLock возвращает полный 32-разрядный указатель
на первый байт блока. Смещение указателя всегда равно 0. В защи-
щенном режиме DOS селектор указателя - это тоже самое, что описа-
тель блока, но в Windows это не всегда так.
Правильная последовательность вызовов для выделения, блоки-
ровки, разблокировки или освобождения блока показана в приведен-
ном ниже примере. В данном примере H - это переменная типа
THandle, а P - указатель:
H := GlobalAlloc(gmem_Moveable, 8192); { выделение блока }
if H <> then { если память выделена }
begin
P := GlobalLock(H); { блокировка блока }
.
. { доступ к блоку через P }
.
GlobalUnlock(H); { разблокировать блок }
GlobalFree(H); { освободить блок }
end;
Блокировка и разблокировка блока при каждом обращении к нему
достаточно утомительна и ведет к ошибкам, и реально она необходи-
ма только для выгружаемых блоков и в прикладных программах
Windows, работающих в реальном режиме. Во всех других ситуациях
лучшим решением является блокировка блока сразу после его выделе-
ния и сохранение этого состояния до освобождения блока. С этой
целью модуль WinAPI включает в себя семейство подпрограмм-"оболо-
чек" GlobalXXXXPtr. Особый интерес представляет функция
GlobalAllocPtr, которая выделяет и блокирует блок памяти, и функ-
ция GlobalFreePtr, разблокирующая и освобождающая блок памяти. С
помощью этих подпрограмм приведенный выше пример можно упростить:
H := GlobalAlloc(gmem_Moveable, 8192); { выделение блока }
if H <> then { если память выделена }
begin
.
. { доступ к блоку }
.
GlobalFreePtr(P); { освободить блок }
end;
Вызвав функцию GlobalReAlloc, вы можете изменить размер или
атрибуты блока памяти, сохранив его содержимое. Функция
GlobalReAlloc возвращает новый описатель блока, который может от-
личаться от передаваемого функции описателя, если старый размер
или новый размер блок превышает 64К. Заметим, что в тех случаях,
когда старый размер блока и новый его размер меньше 64К,
GlobalReAlloc всегда может изменить размер блока, не изменяя его
описателя.
Функция GlobalReAlloc можно также использоваться для измене-
ния атрибутов блока. Это можно сделать, задав наряду с
gmem_Moveable или gmem_Discardable флаг gmem_Modify.
Функция GlobalReAlloc выполняет те же действия, что и
GlobalReAlloc, но обе они вместо описателей использует указатели.
Имеется также ряд других, менее часто используемых
GlobalXXXX. Все они подробно описаны в Главе 1 ("Справочник по
библиотеке") "Справочного руководства программиста".
Управление модулем
Администратор этапа выполнения поддерживает следующие подп-
рограммы обслуживания модулей:
Подпрограммы API обслуживания модулей Таблица 17.3
----------------------------T-----------------------------------
¦ Подпрограмма ¦ Описание ¦
+---------------------------+-----------------------------------+
¦ FreeLibrary ¦ Делает недействительным загружен-¦
¦ ¦ ный модуль библиотеки, и освобож-¦
¦ ¦ дает соответствующую память, если¦
¦ ¦ ссылок на модуль больше нет. ¦
+---------------------------+-----------------------------------+
¦ GetModuleFileName ¦ Дает полный маршрут и имя выполня-¦
¦ ¦ емого файла, задающий, откуда заг-¦
¦ ¦ ружен модуль. ¦
+---------------------------+-----------------------------------+
¦ GetModuleHandle ¦ Определяет описатель заданного мо-¦
¦ ¦ дуля. ¦
+---------------------------+-----------------------------------+
¦ GetModuleUsage ¦ Определяет счетчик ссылок на мо-¦
¦ ¦ дуль. ¦
+---------------------------+-----------------------------------+
¦ GetProcAddress ¦ Определяет адрес экспортируемой¦
¦ ¦ библиотечной функции. ¦
+---------------------------+-----------------------------------+
¦ LoadLibrary ¦ Загружает указанный библиотечный¦
¦ ¦ модуль. ¦
L---------------------------+------------------------------------
Некоторые из этих подпрограмм воспринимают в качестве пара-
метра описатель модуля. Описатель модуля самой прикладной прог-
раммы хранится в переменной HInstance, описанной в модуле System.
Управление ресурсами
Администратор этапа выполнения поддерживает следующие подп-
рограммы управления ресурсами:
Функции API управления ресурсами Таблица 17.4
-----------------------T----------------------------------------
¦ Функция ¦ Описание ¦
+----------------------+----------------------------------------+
¦ AccessResource ¦ Открывает заданный выполняемый файл и¦
¦ ¦ перемещает указатель файла на начало¦
¦ ¦ заданного ресурса. ¦
+----------------------+----------------------------------------+
¦ FindResource ¦ Определяет адрес ресурса в заданном¦
¦ ¦ файле ресурса. ¦
+----------------------+----------------------------------------+
¦ FreeResource ¦ Уменьшает счетчик ссылок для загружен-¦
¦ ¦ ного ресурса. Когда значение этого¦
¦ ¦ счетчика становится равным нулю, то ис-¦
¦ ¦ пользуемая ресурсом память освобождает-¦
¦ ¦ ся. ¦
+----------------------+----------------------------------------+
¦ LoadResource ¦ Загружает заданный ресурс в память. ¦
+----------------------+----------------------------------------+
¦ LoadString ¦ Загружает заданную строку ресурса. ¦
+----------------------+----------------------------------------+
¦ LockResource ¦ Блокирует заданный ресурс в памяти и¦
¦ ¦ увеличивает его счетчик ссылок. ¦
+----------------------+----------------------------------------+
¦ SizeOfResource ¦ Возвращает размер (в байтах) заданного¦
¦ ¦ ресурса. ¦
+----------------------+----------------------------------------+
¦ UnlockResource ¦ Разблокирует заданный ресурс и уменьша-¦
¦ ¦ ет на 1 счетчик ссылок на ресурс. ¦
L----------------------+-----------------------------------------
Ресурсы могут компоноваться с прикладной программой с по-
мощью директив компилятора {$R имя_файла}. Указанные файлы должны
быть файлами ресурсов Windows (.RES). Обычно с прикладными прог-
раммами защищенного режима DOS компонуются только строковые ре-
сурсы и ресурсы, определенные пользователем. Другие типы ресурсов
Windows к прикладной программе DOS обычно неприменимы.
Примечание: Ресурсы Turbo Vision не следуют тем же
соглашениям, что ресурсы Windows, и к ним нельзя обращаться
с помощью подпрограмм API.
Некоторые подпрограммы API управления ресурсами требуют ука-
зания описателя экземпляра, которым обычно является указатель эк-
земпляра прикладной программы (который содержится в переменной
HInstance модуля System).
Управление селектором
Прикладной программе обычно не требуется манипулировать се-
лекторами, но в отдельных ситуациях полезно использовать следую-
щие подпрограммы обслуживания селектора:
Подпрограммы API управления селектором Таблица 17.5
------------------------T---------------------------------------
¦ Функция ¦ Описание ¦
+-----------------------+---------------------------------------+
¦ AllocDStoCSAlias ¦ Отображает селектор сегмента данных на¦
¦ ¦ селектор сегмента кода. ¦
+-----------------------+---------------------------------------+
¦ AllocSelector ¦ Выделяет новый селектор. ¦
+-----------------------+---------------------------------------+
¦ ChangeSelector ¦ Генерирует селектор кода, соответству-¦
¦ ¦ щий заданному селектору данных, или¦
¦ ¦ генерирует заданный селектор, соот-¦
¦ ¦ ветствующий селектору кода. ¦
+-----------------------+---------------------------------------+
¦ FreeSelector ¦ Освобождает селектор, первоначально¦
¦ ¦ выделенный функциями AllocDStoCSAlias¦
¦ ¦ или AllocSelector. ¦
+-----------------------+---------------------------------------+
¦ GetSelectorBase ¦ Дает базовый адрес селектора. ¦
+-----------------------+---------------------------------------+
¦ GetSelectorLimit ¦ Возвращает предельное значение для за-¦
¦ ¦ данного селектора. ¦
+-----------------------+---------------------------------------+
¦ PrestoChangoSelector¦ Генерирует селектор кода, соответству-¦
¦ ¦ ющий заданному селектору данных, либо¦
¦ ¦ генерирует селектор данных, соответс-¦
¦ ¦ твующий селектору кода. ¦
+-----------------------+---------------------------------------+
¦ SetSelectorBase ¦ Устанавливает базовый адрес селектора.¦
+-----------------------+---------------------------------------+
¦ SetSelectorLomit ¦ Устанавливает предельное значение се-¦
¦ ¦ лектора. ¦
L-----------------------+----------------------------------------
Другие подпрограммы API
Администратор этапа выполнения поддерживает следующие допол-
нительные подпрограммы API:
Прочие подпрограммы API Таблица 17.6
--------------------T-------------------------------------------
¦ Функция ¦ Описание ¦
+-------------------+-------------------------------------------+
¦ DOS3Call ¦ Вызывает функцию прерывания DOS 21h; вызы-¦
¦ ¦ вается только из подпрограмм ассемблера. ¦
+-------------------+-------------------------------------------+
¦ FatalExit ¦ Передает отладчику текущее состояние опе-¦
¦ ¦ рационной среды защищенного режима и вы-¦
¦ ¦ выводит подсказку для ввода инструкций о¦
¦ ¦ продолжении работы. ¦
+-------------------+-------------------------------------------+
¦ GetDOSEnviroment¦ Определяет текущую строку операционной¦
¦ ¦ среды задачи. ¦
+-------------------+-------------------------------------------+
¦ GetVersion ¦ Дает текущую версию операционной среды¦
¦ ¦ Windows или операционной системы DOS. ¦
+-------------------+-------------------------------------------+
¦ GetWinFlags ¦ Дает используемые Windows флаги конфигура-¦
¦ ¦ ции памяти. ¦
+-------------------+-------------------------------------------+
¦ MessageBox ¦ Создает, выводит на экран и обслуживает¦
¦ ¦ окно сообщений. ¦
L-------------------+--------------------------------------------
Совместно используемая DLL, чтобы определить, выполняется ли
она в защищенном режиме DOS или под Windows, может использовать
функцию GetWinFlags, например:
if GetWinFlags and wf_DPMI <> 0 then
Message('Работа в защищенном режиме DOS')
else
Message('Работа в среде Windows');
Прямой доступ к DPMI-серверу
Прямой доступ к DPMI-серверу вы можете получить через преры-
вание $31, которое непосредственно вызывает DPMI-сервер в обход
администратора этапа выполнения. Однако это опасный прием. DPMI
не поддерживает очистку ресурсов, таких как векторы прерываний
памяти; корректно с этими проблемами работает администратор этапа
выполнения. Вы должны глубоко понимать концепции защищенного ре-
жима и знать о существенном риске, с которым связано использова-
ние данного метода доступа защищенного режима.
Компиляция прикладной программы защищенного режима
В большинстве случаев для получения прикладной программы за-
щищенного режима вам не нужно делать ничего особенного. Просто
скомпилируйте свою программу, задав в качестве целевой работы за-
щищенный режим одним из следующих способов:
* В IDE выберите команду Compile¦Target и в диалоговом окне
Target Platform (Целевая платформа) выберите
Protected-mode Application.
* При использовании компилятора, работающего в режиме ко-
мандной строки, укажите для выбора в качестве целевой
платформы защищенного режима параметр /CP.
Выполнение программы защищенного режима DOS
Когда вы выполняете программу DOS защищенного режима, нужно
обеспечить наличие в текущем каталоге или по маршруту DOS файлов
DPMI16BI.OVL (сервер DPMI), RTM.EXE (администратор этапа выполне-
ния) и всех DLL, с которыми работает ваша программа.
Примечание: Лицензионное соглашение позволяет вам
распространять файлы DPMI16BI.OVL и RTM.EXE вместе с вашей
программой.
В выполняемом файле .EXE защищенного режима DOS используется
тот же формат файла, что и в Windows 3.x и OS/2 1.x. Этот формат
файла является надмножеством обычного формата .EXE DOS и состоит
из обычного образа файла .EXE, называемого фиктивным модулем, за
которым следует расширенный заголовок и код, данные и ресурсы за-
щищенного режима. Ниже показана последовательность событий при
выполнении программы защищенного режима DOS.
1. DOS загружает фиктивный модуль реального режима и переда-
ет ему управление.
2. Если средства DPMI отсутствуют, то фиктивный модуль заг-
ружает DPMI-сервер из файла DPMI16BI.OVL. Некоторые новые
администраторы памяти поддерживают средства DPMI (как,
например, это делается в окне DOS улучшенного режима
Windows 3.х). В таких конфигурациях фиктивный модуль не
загружает DPMI-сервер, но использует уже имеющийся.
3. Далее, если администратор этапа выполнения еще не загру-
жен в память, фиктивный модуль загружает его из файла
RTM.EXE. Если прикладная программа защищенного режима вы-
полняет другую программу защищенного режима, обе исполь-
зуют одну копию администратора этапа выполнения.
4. Если средства DPMI и администратор этапа выполнения при-
сутствуют, фиктивный модуль переключается из реального в
защищенный режим и передает управление расширенному заг-
рузчику .EXE в администратора этапа выполнения.
5. Загрузчик сначала загружает используемую прикладной прог-
раммой DLL (если она имеется), затем загружает сегменты
кода и данных прикладной программы. Наконец, загрузчик
передает управление на точку входа прикладной программы.
При выполнении вашей прикладной программы защищенного режима
DOS всегда возможно ситуация, когда уже присутствует DMPI-сервер,
отличный от сервера Borland. Поскольку между серверами могут быть
небольшие различия, особенно в плане обработки прерываний DOS, вы
должны проверить программу и убедиться, что она работает со всеми
возможными серверами, которые могут ей встретиться.
Когда прикладная программа защищенного режима DOS выполняет-
ся в окне DOS улучшенного режима Windows, вы можете управлять
объемом расширенной памяти, которую выделяет администратор этапа
выполнения, задав в файле .PIF прикладной программы предельное
значение памяти XMS.
Управление объемом используемой RTM памяти
По умолчанию администратор этапа выполнения использует при
загрузке всю доступную память. Затем по запросам он выделяет па-
мять своим клиентам (через подпрограммы API администратора памя-
ти).
В защищенном режиме нет разницы между обычной памятью (ниже
1 мегабайта) и расширенной памятью (с адресами выше 1 мегабайта);
для программ защищенного режима доступны оба типа памяти. Однако
администратор этапа выполнение отдает предпочтение расширенной
памяти. Только после того как вся расширенная память будет выде-
лена, или когда прикладная программа специально запрашивает
обычную память (например, с помощью функции GlobalDosAlloc), ад-
министратор этапа выполнения выделяет обычную память.
Причина, по которой администратор этапа выполнения предпочи-
тает расширенную память, заключается в том, что прикладная прог-
рамма может с помощью вызова подпрограммы Exec в модуле Dos по-
рождать другие прикладные программы. Порожденные прикладные прог-
раммы не обязательно являются программами защищенного режима; та-
ким образом, им может потребоваться обычная память. Фактически,
порожденные программы защищенного режима запускаются как програм-
мы реального режима и переключаются в защищенный режим только
после успешной загрузки фиктивным модулем средств DPMI и адми-
нистратора этапа выполнения.
Администратор этапа выполнения перед порождением прикладной
программы пытается освободить максимальный объем обычной памяти
(например, перенеся перемещаемые блоки в расширенную память). Од-
нако попытки освобождения расширенной памяти не предпринимаются.
Таким образом, если должны порождаться прикладные программы защи-
щенного режима, не использующие администратор этапа выполнения,
то необходим споcоб управления распределением памяти администра-
тором этапа выполнения.
Чтобы управлять тем, сколько памяти может использовать адми-
нистратор этапа выполнения, в командной строке DOS добавьте к
строке операционной среды DOS переменную среды RTM:
SET RTM={параметр nnnn}
Возможные параметры перечислены в следующей таблице. Значе-
ние nnnn может быть десятичным или шестнадцатиричным числом в ви-
де xAB54 или xab54.
Параметры переменной операционной
среды RTM, используемые для управления памятью Таблица 17.7
---------------------T------------------------------------------
¦ Параметр ¦ Описание ¦
+--------------------+------------------------------------------+
¦ EXTLEAVE nnnn ¦ Всегда оставляет не менее nnnn килобайт¦
¦ ¦ доступной расширенной памяти. По умолча-¦
¦ ¦ нию это значение равно 640К. ¦
+--------------------+------------------------------------------+
¦ EXTMAX nnnn ¦ Не выделяет более nnnn килобайт расширен-¦
¦ ¦ ной памяти. По умолчанию используется¦
¦ ¦ значение 4 гигабайта. В Windows использу-¦
¦ ¦ емое по умолчанию значение равно половине¦
¦ ¦ доступной памяти. ¦
+--------------------+------------------------------------------+
¦ EXTMIN nnnn ¦ Если после применения EXTMAX или EXTLEAVE¦
¦ ¦ доступно менее nnnn килобайт, то програм-¦
¦ ¦ ма завершается с сообщением о нехватке¦
¦ ¦ памяти (Out of memory). По умолчанию это¦
¦ ¦ значение равно 0. ¦
+--------------------+------------------------------------------+
¦ REALLEAVE nnnn ¦ Всегда оставляет не менее nnnn параграфов¦
¦ ¦ доступной реальной памяти. По умолчанию¦
¦ ¦ это значение равно 64К или 4096 парагра-¦
¦ ¦ фов. ¦
+--------------------+------------------------------------------+
¦ REALMAX nnnn ¦ Не выделяет более nnnn параграфов реаль-¦
¦ ¦ ной памяти. По умолчанию это значение¦
¦ ¦ равно 1 мегабайту или 65535 параграфов. ¦
+--------------------+------------------------------------------+
¦ REALMIN nnnn ¦ Если после применения REALMAX и REALLEAVE¦
¦ ¦ доступно менее nnnn параграфов, то прог-¦
¦ ¦ рамма завершается с сообщением о нехватке¦
¦ ¦ памяти (Out of memory). По умолчанию это¦
¦ ¦ значение равно 0. ¦
L--------------------+-------------------------------------------
Следующая команда DOS ограничивает RTM 2 мегабайтами расши-
ренной памяти и обеспечивает, что нераспределенными останутся
128К реальной памяти.
SET RTM=EXTMAX 2048 REALLEAVE 8192
Назад | Содержание | Вперед