Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых

MS-DOS для программиста

© Александр Фролов, Григорий Фролов
Том 18, М.: Диалог-МИФИ, 1995, 254 стр.

[Назад] [Содеожание] [Дальше]

5.2. Инициализация резидентной программы

Отложим на время описание способа, с помощью которого резидентная программа может выгрузить себя из памяти, и займемся более детально процессом инициализации.

На первом этапе инициализации наша резидентная программа резервирует память для области кучи (heap) и стека. Это необходимо, если функция, получающая управление при активизации программы, работает со стандартными функциями из библиотеки транслятора (например, открывает файлы или потоки ввода/вывода, заказывает области памяти функцией malloc и т. п.).

Для того чтобы вам была понятна процедура резервирования памяти, а также способ, при помощи которого мы вычисляем размер области памяти, передаваемой в качестве параметра функции _dos_keep , рассмотрим расположение различных сегментов, составляющих программу в модели памяти small . Эта модель используется в нашей программе TSRDEMO. Описание расположения сегментов для других моделей памяти вы сможете найти в руководстве программиста, которое поставляется вместе с Borland C++ (или другим аналогичным средством разработки).

В модели памяти small программе выделяются две области памяти размером не более 64 Кбайт. Одна область содержит сегмент кода с названием _TEXT и содержит исполнимый код программы, в другой расположены сегмент инициализированных данных с названием _DATA , сегмент неинициализированных данных _BSS , область кучи и сегмент стека. Перед сегментом кода расположен сегмент PSP (рис. 5.1).

Рис. 5.1. Расположение сегментов в модели памяти small

Из этого рисунка видно, что вслед за сегментом PSP в памяти располагается сегмент кода, а затем - сегмент данных.

Какие из этих сегментов потребуются программе, после того как она останется резидентной в памяти?

Блок памяти, выделенный в сегменте PSP для хранения переменных среды, очевидно, не нужен, так как его содержимое может быть прочитано на этапе инициализации и в последующем останется неизменным. С сегментом кода тоже все ясно - его нужно оставить в памяти.

Если резидентная программа составлена на языке программирования С, то наибольшие трудности возникают при определении необходимого размера сегмента данных.

Программа может иметь статические глобальные переменные, имеющие или не имеющие начальные значения. Они располагаются, соответственно, в сегментах _DATA и _BSS , доступ к которым возможен через сегментный регистр DS. Размер указанных сегментов можно определить только приблизительно, зная общий объем памяти, выделенной программой для статических и глобальных переменных.

Вслед за сегментом _BSS в памяти расположена область HEAP, которая иногда называется просто кучей. Из этой области (доступ к которой возможен с помощью регистра DS) происходит динамическое выделение памяти по явному или неявному запросу программы. Заметим, что вызов многих функций стандартной библиотеки С приводит к неявному выделению памяти из области HEAP.

Механизм выделения памяти из области HEAP имеет одну особенность: если программа заказала для себя блок памяти из этой области (например, функцией malloc), а затем освободила его, этот блок памяти не будет возвращен MS-DOS до завершения работы программы. Когда программе опять потребуется блок памяти, по возможности он будет выделен из области HEAP без обращения к соответствующей функции прерывания MS-DOS.

Что же касается стека, то, как известно, он адресуется с помощью регистров SS:SP. Содержимое регистра SS после запуска программы в модели памяти small равно содержимому регистра DS, так как для этой модели памяти стек расположен в сегменте данных.

В отличие от области данных HEAP, которая растет в направлении к концу памяти (в сторону больших адресов), стек растет в направлении к началу памяти. На рис. 5.1 это показано при помощи стрелок.

Вслед за областью стека в памяти расположена область FAR HEAP. Блоки памяти из этой области выделяются в тех случаях, когда программа заказывает их такой функцией, как farmalloc.

Для того чтобы при активизации резидентная программа могла вызывать стандартные функции библиотеки транслятора С, необходимо зарезервировать достаточное количество памяти для областей HEAP и стека. Кроме того, при активизации необходимо правильно загрузить регистры SS:SP, чтобы они указывали на нижнюю границу блока памяти, выделенного резидентной программой для стека.

Как мы уже говорили, программа, составленная на языке программирования С, остается резидентной в памяти с помощью функции _dos_keep . Через первый параметр этой функции следует передать код завершения (для анализа в пакетных файлах), а через второй - объем памяти, который должен быть зарезервирован для программы после ее завершения. Этот объем нужно указать в параграфах.

Взгляните на последние две строки нашей программы TSRDEMO:

tsrsize =  (_DS - _CS) + (_SP / 16);
_dos_keep (0, tsrsize + 1);

Размер области памяти, выделенной для сегмента кода, можно вычислить как _DS - _SS, так как сегмент данных расположен сразу вслед за сегментом кода. Нижняя граница сегмента данных определяется содержимым регистра SP. Дополнительно мы резервируем еще один параграф памяти, прибавляя к переменной tsrsize число 1.

Функция tsrinit

Функция tsrinit выполняет все действия, необходимые для инициализации резидентной программы.

Прежде всего, эта функция резервирует область памяти для стека, который будет использован в момент активизации резидентной программы.

Вот соответствующий фрагмент кода:

wk_ptr = malloc(HEAP_RESERVED);
if(wk_ptr == NULL) return 0;

tsr_stack = malloc(STACK_SIZE);
if(tsr_stack == NULL) return 0;

tsr_stack += STACK_SIZE;
free(wk_ptr);

На первый взгляд может показаться, что здесь мы выполняем странные действия - вначале заказываем блок памяти, имеющий размер HEAP_RESERVED, а потом сразу же отдаем его, так и не использовав ни разу. Тем не менее, такие действия имеют определенный смысл.

Вернувшись к рис. 5.1, заметим, что перед стеком должна располагаться область памяти HEAP. Она нужна для работы функций динамического выделения памяти (и не только для этих функций). Поэтому область стека резидентной программы должна быть расположена ниже, как это показано на рис. 5.1.

На самом деле вызывая функцию malloc в первый раз, мы резервируем память для области HEAP. После второго вызова функции malloc будет выделена область памяти размером STACK_SIZE, расположенная сразу после области HEAP. Такая область подходит для стека резидентной программы.

Так как стек растет в направлении к началу памяти, мы запоминаем в переменной tsr_stack адрес нижней границы полученной области (точнее говоря, компоненту смещения адреса стека, пригодную для загрузки в регистр SP).

Когда резидентная программа активизируется, регистры SS:SP будут загружены так, чтобы указывать на выделенную нами область стека. При этом перед стеком будет расположена область памяти, имеющая размер HEAP_RESERVED. Она будет выполнять роль области HEAP, обеспечивая возможность использования стандартных функций библиотеки С.

После всех этих манипуляций функция tsrinit получает и сохраняет указатель на так называемый флаг InDos , вызывая для этого функцию getInDosFlag. Этот флаг устанавливается перед началом работы любых функций прерываний MS-DOS и сбрасывается после завершения выполнения указанных функций. Наша резидентная программа использует флаг InDos для определения возможности активизации.

Далее функция tsrinit сохраняет старые значения ряда векторов прерываний и устанавливает новые обработчики для этих прерываний.

В завершение функция освобождает блок памяти, выделенный для переменных среды в сегменте PSP :

FP_SEG (fp) = _psp;
FP_OFF (fp) = 0x2c;
_dos_freemem(*fp);

Для этого полный адрес освобождаемого блока памяти формируется в указателе fp. Напомним, что сегментная компонента адреса блока переменных среды находится в PSP . Смещение соответствующего слова равно 0x2c. Сегментный адрес блока PSP находится в глобальной переменной с именем _psp.

Флаг InDos

Функция tsrinit вызывает функцию getInDosFlag, получающую и сохраняющую указатель на флаг InDos , а также на флаг обработчика критических ошибок. Последний устанавливается в том случае, когда MS-DOS обрабатывает критическую ошибку ввода/вывода.

Резидентная программа не должна активизироваться, если установлены указанные выше флаги. Однако есть одно исключение - если вызвано прерывание INT 28h (о котором мы еще будем говорить), можно выполнять активизацию резидентной программы.

Получить указатель на флаг InDos несложно - для этого достаточно воспользоваться недокументированной функцией 34h прерывания INT 21h :

regs.h.ah = 0x34;
intdosx (&regs, &regs, &sregs);
FP_SEG (indos_ptr) = sregs.es;
FP_OFF (indos_ptr) = regs.x.bx;

Искомый адрес возвращается в регистрах ES:BX.

В MS-DOS ранних версий (до версии 3.0) флаг обработчика критических ошибок был расположен непосредственно перед флагом InDos. В версии 3.0 его перенесли на новое место - сразу после флага InDos. В обоих случаях расположение этих флагов не было указано в документации.

Для выяснения расположения флага обработчика критических ошибок в MS-DOS новых версий вы должны использовать недокументированную функцию 5Dh прерывания INT 21h:

regs.x.ax = 0x5D06;
intdosx (&regs,&regs,&sregs);
FP_SEG (crit_err_ptr) = sregs.ds;
FP_OFF (crit_err_ptr) = regs.x.si;

Эта функция вернет указатель на флаг обработчика критических ошибок в регистрах DS:SI.

[Назад] [Содеожание] [Дальше]