¦ ИНИЦИАЛИЗИРОВАННЫЕ STATIC И EXTERNAL ДАННЫЕ ¦ DATA
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ<ДДДДДД
¦ ¦
¦ ¦
¦ КОД ПРОГРАММЫ ¦
¦ ¦ TEXT
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ<ДДДДДД
¦ ¦
¦ P S P ¦
АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
рис.11.3. Структура программ с крохотной моделью памяти
в Турбо Си.
Приблизительно подсчитать размер программы можно, используя
MAP-файл, генерируемый программой TLINK. В его начале находится
информация, подобная следующей:
Start Stop Length Name Class
00000H 010BAH 010BBH _TEXT CODE
010C0H 013D8H 00319H _DATA DATA
013DAH 013DDH 00004H _EMUSEG DATA
013DEH 013DFH 00002H _CVTSEG DATA
013E0H 013E5H 00006H _SCNSEG DATA
013E6H 014EDH 00108H _BSS BSS
014EEH 014EEH 00000H _BSSEND BSSEND
Самая правая колонка, с заголовком class, показывает тип
сегментов, представленных значениями в левых колонках. CODE
содержит код программы, DATA содержит инициализированные
переменные, а BSS - неинициализированные. Значение в колонке Stop
равно шестнадцатиричному адресу конца области
неинициализированных переменных и начала "кучи". Программа, не
использующая стека и "кучи", будет иметь размер, равный этому
значению + 256 байт на PSP.
Чтобы оценить размер "кучи", посмотрите, сколько и как вы
используете функции распределения памяти. При использовании
оконных функций из предыдущих глав требования к "куче" можно
оценить исходя из количества и размеров одновременно существующих
окон. Каждое окно требует буфера размером с удвоенное
произведение ширины на высоту окна. Этот буфер удаляется из
"кучи" при закрытии окна, поэтому максимальное использование
"кучи" будет в момент создания максимального количества окон на
экране. Надо учитывать также и использование вами функций
распределения памяти. Если ваша программа распределяет память в
зависимости от внешних условий, таких, как ввод пользователя или
зависимости между данными, необходимы надежные обнаружение и
обработка ошибок. Не взирая на ошибку, никогда не вызывайте
функцию exit из резидентной программы.
Вместе с определением размеров "кучи" определяется и ее
верхняя граница, а, следовательно, и нижняя граница стека.
Осталось теперь найти его верхнюю границу. При первом выполнении
программы начало стека устанавливается на отметку 64К. При
завершения и объявления себя резидентной программа сообщает ДОС
свои размеры, и ДОС использует всю остальную память для загрузки
других программ. При вызове TSR-программа должна установить
указатель стека внутри себя, а не на другую программу. Если
размер объявлен меньше 64К, то указатель стека должен быть также
установлен ниже 64К.
Лучший метод найти оптимальный размер стека - метод проб и
ошибок. Сначала запустите программу на 64К, а затем смещайте
вершину стека к меньшим адресам. При каждом таком передвижении
испытывайте программу в условиях максимального использования
стека и "кучи". Продолжайте эксперименты до тех пор, пока ваша
программа не будет "вешать" систему или неправильно
выполняться. Затем поставьте вершину стека на безопасное смещение
и интенсивно используйте программу, пока не поверите наконец, что
стек безопасен для "кучи".
Было бы хорошо, если бы имелся более научный и точный способ
определения размеров программы, но этот подход работает, и
кажется, что это единственно реальный метод.
Переключение контекстов.
-----------------------------------------------------------------
При первом исполнении TSR-программы она использует все
ресурсы, предоставляемые ДОС нормальной задаче. После завершения
и объявления себя резидентной эти ресурсы отдаются другим
программам или, при отсутствии выполняемых программ, командному
процессору ДОС. При выполнении TSR-программы в результате
"горячего ключа" она "паразитирует" на прерванной программе. ДОС
неизвестно, что начала выполняться другая задача, и все ресурсы
по-прежнему принадлежат выполнявшейся раннее задаче. Поэтому
системные указатели на эти ресурсы должны быть изменены так,
чтобы TSR-программа стала выполняемой задачей, "известной"
ДОС. Такая передача ресурсов между задачами называется
переключением контекстов, и мультизадачные ДОС делают это
автоматически. В однозадачной ДОС PC однако, переключения
контекстов не производится, и прерывающая задача должна делать
это сама.
Стек.
-----------------------------------------------------------------
Для всех программ нужен стек. У резидентной программы есть
свой стек, но после прерывания текущей задачи указатель стека и
сегмент стека в компьютере указывают на стек прерванной
задачи. Может показаться, что лучшее решение - это использовать
стек прерванной программы. На деле многие ассемблерные
TSR-программы так и делают, но для этого приходится ограничивать
использование стека. Но, во-первых, неизвестно, какой размер
стека был у прерванной программы. А во-вторых, ДОС гарантирует
достаточный размер стека только для сохранения регистров. Си -
язык с интенсивным использованием стека, и вам понадобится
больший его размер, чем обеспечивает ДОС, и это значит, что надо
переключаться на собственный стек.
Переключение на собственный стек означает, что надо
запомнить значение региста сегмента стека до переключения. И это
значение должно быть восстановлено до передачи управления в
прерванную программу. Регистры сегментов и указателей могут быть
прямо адресованы в Турбо Си использованием псевдопеременных _SS и
_SP. При объявлении резидентной TSR-программа запоминает
собственный сегмент стека. После вызова она запоминает контекст
стека прерванной программы и устанавливает свой стек. Это
производится с помощью установки регистра сегмента стека на
значение, запомненное при первом запуске, и указателя стека на
величину, вычисленную из размеров программы.
Если TSR-программа реентерабельна, то есть может прерывать
сама себя, переключение стеков может привести к ошибке. При
втором переключении стека вы перезапишете область сохранения
стековых регистров. Чтобы избежать этого, надо писать
нереентерабельные резидентные программы. Это небольшая потеря -
резидентные программы не нуждаются в том, чтобы быть
реентерабельными (вам не нужно прерывать свою
программу-калькулятор, чтобы запустить еще одну такую же).
Чтобы сделать TSR-программу нереентерабельной,
устанавливайте флаг при ее вызове. Он должен оставаться
установленным то окончания работы TSR-программы. При повторном
вызове (например, из-за случайного нажатия "горячего ключа")
проверяется установка флага и вторичного запуска не производится.
Program Segment Prefix (PSP).
-----------------------------------------------------------------
PSP - это управляющая область в 256 байт, которая строится в
памяти в начале каждой программы. Она содержит различные поля,
используемые ДОС для управления выполнением программы. На рис.
11.4 показана ее структура. Далее будут обсуждаться все поля PSP.
Заметим, что многие эти поля не были официально описаны фирмами
Microsoft или IBM. Они используются так, как описано ниже, но их
использование или модификация в прикладной задаче не
санкционировано при продаже. Знание этих полей - подарок от
хэккеров, расчленивших ДОС и напечатавших о своих находках. Эти
данные верны для популярных версий ДОС - 2.0, 2.1, 3.0, 3.1, 3.2,
3.3, за исключением специально оговоренных случаев. ДОС 4.0 не
публиковалась в США, и, как утверждается, будущие версии ДОС
будут поддерживать мультизадачность и будут предназначены только
для компьютеров с процессорами 80286/80386. Использование полей
PSP описанным способом совершенно безопасно. Многие популярные
коммерческие программы делают это точно так же.
ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¬
¦ ¦
¦ Вызов прерывания для завершения процесса ¦ 0000
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
¦ ¦
¦ Сегментный адрес верхней границы памяти ¦ 0002
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
¦ ¦
¦ 0 ¦ 0004
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
¦ ¦
¦ Команда вызова диспетчера функций ДОС ¦ 0005
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
¦ ¦
¦ Адрес обработчика завершения ¦ 000A
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
¦ ¦
¦ Адрес обработчика Ctrl-Break ¦ 000E
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
¦ ¦
¦ Адрес обработчика критических ошибок ¦ 0012
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
¦ ¦
¦ Сегментный адрес PSP родителя ¦ 0016
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
¦ ¦
¦ Таблица указателей файлов ¦ 0018
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
¦ ¦
¦ Сегментный адрес области системных параметров ¦ 002C
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
¦ ¦
¦ Адрес стека на время вызова функции ДОС ¦ 002E
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
¦ ¦
¦ Размеры таблицы указателей файлов ¦ 0032
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
¦ ¦
¦ Адрес таблицы указателей файлов ¦ 0034
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
¦ ¦
¦ Зарезервировано ДОС ¦ 0038
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
¦ ¦
¦ Блок управления файлом #1 ¦ 005C
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
¦ ¦
¦ Блок управления файлом #2 ¦ 006C
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
¦ ¦
¦ Остаток командной строки/Дисковый буфер ¦ 0080
АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
рис.11.3. Структура PSP.