Жанр: Учеба

Техника программирования на турбо-С

сторінка №16

еской природы "кучи" и стека вы можете
только делать некоторые предположения о размерах программы.

-------------------------------------------------------¬
¦ ¦
¦ СТЕК ¦
+------------------------------------------------------+
¦ ¦
¦ " КУЧА " ¦
+------------------------------------------------------+
¦ ¦
¦ НЕИНИЦИАЛИЗИРОВАННЫЕ STATIC И EXTERNAL ДАННЫЕ ¦ BSS
+------------------------------------------------------+"------
¦ ¦
¦ ИНИЦИАЛИЗИРОВАННЫЕ STATIC И EXTERNAL ДАННЫЕ ¦ DATA
+------------------------------------------------------+"------
¦ ¦
¦ ¦
¦ КОД ПРОГРАММЫ ¦
¦ ¦ TEXT
+------------------------------------------------------+"------
¦ ¦
¦ P S P ¦
L-------------------------------------------------------

рис.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
L-------------------------------------------------------

рис.11.3. Структура PSP.

Вызов прерывания для завершения процесса.(PSP:0)
------------------------------------------------

Это поле содержит команду INT 0x20; это сделано для
поддержки программ, перенесенных из CP/M в ДОС. В CP/M программа
завершает свое выполнение переходом на свой нулевой адрес.

Сегментный адрес верхней границы памяти.(PSP:2)
-----------------------------------------------

При выполнении программы ДОС выделяет ей участок памяти, в
который программа загружается. Это поле содержит сегментный адрес
конца этого участка памяти.

Адрес обработчика завершения.(PSP:0xa)
--------------------------------------

При выполнении программы ДОС запоминает текущее содержание
вектора прерывания 0х22 в этом поле. После завершения программы
ДОС восстанавливает значение, используя это поле. Вектор
прерывания 0х22 указывает на системный обработчик завершения
программ.

Адрес обработчика Ctrl-Break.(PSP:0xe)
--------------------------------------

При выполнении программы ДОС запоминает текущее содержание
вектора прерывания 0х23 в этом поле. После завершения программы
ДОС восстанавливает значение, используя это поле. Вектор
прерывания 0х23 указывает на системный обработчик Ctrl-Break.

Адрес обработчика критических ошибок.(PSP:0x12)
-----------------------------------------------

При выполнении программы ДОС запоминает текущее содержание
вектора прерывания 0х24 в этом поле. После завершения программы
ДОС восстанавливает значение, используя это поле. Вектор
прерывания 0х24 указывает на системный обработчик критических
ошибок.

Отметим, что ДОС восстанавливает значения этих трех векторов
при завершении программы и объявлении себя резидентной. Если TSR
надо перехватывать эти прерывания, то придется присоединять себя
к ним при каждом вызове.

Сегментный адрес PSP родителя.(PSP:0x16)
----------------------------------------

Любая программа выполняется в результате обращения другой
программы к ДОС. Обычно программой-отцом является командный
процессор ДОС (COMMAND.COM), хотя любая программа может быть
родителем любой другой. Это поле в PSP содержит сегментный адрес
PSP программы-отца.

У командного процессора нет "папаши", поэтому это поле в PSP
командного процессора содержит сегментный адрес собственного PSP,
что является указателем на самого себя.

Таблица указателей файлов.(PSP:0x18)
-----------------------------------------------------------------

Это поле представляет собой массив в 20 байт,каждый из
которых представляет указатель файла(file handler). При открытии
файла в программе ДОС возвращает в программу номер файла для
использования при обращении к ДОС для записи в файл и чтения из
него. (Программы на Си, использующие потоковый ввод-вывод, прямо
не обращаются к этим номерам - они используют указатель FILE,
определенный в stdio; однако стандартные библиотечные функции Си,
поддерживающие такой ввод-вывод, используют эти номера скрытым от
вызывающей программы способом.) Эти номера файлов - номера байт в
таблице указателей файлов, а элементы этой таблицы хранят
значения, указывающие на соответствующую файлу структуру в
системных таблицах управления файлами.

Сегментный адрес области системных параметров.(PSP:0x2c)
--------------------------------------------------------

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

Адрес стека на время вызова функции ДОС.(PSP:0x2e)
--------------------------------------------------

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

Размеры таблицы указателей файлов.(PSP:0x32)
--------------------------------------------

Это поле содержит счетчик количества вхождений в таблицу
указателей файлов. Обычно его значение равно 20. Заметим, что это
поле не используется в версиях ДОС до 3.0.

Адрес таблицы указателей файлов.(PSP:0x32)
------------------------------------------

Это поле содержит полный (long) адрес таблицы указателей
файлов; сегмент этого адреса равен сегменту PSP; а смещение -
0х18. Заметим, что это поле не используется в версиях ДОС до 3.0.

Вероятно, предыдущие два поля добавлены, чтобы позволить
программе использовать более, чем 20 одновременно открытых
файлов. После выделения большей таблицы надо поместить ее адрес и
количество файлов в эти поля, скопировать в нее 20 значений из
PSP, и таким образом программа может увеличить количество
одновременно открытых файлов по сравнению со значением из
оператора FILES=файла CONFIG.SYS.

Блок управления файлом #1.(PSP:0x5c)
------------------------------------

Это поле содержит блок управления файлом, который строится
ДОС в случае, если в командной строке в качестве первого
параметра было указано имя файла.

Блок управления файлом #2.(PSP:0x6c)
------------------------------------

Это поле содержит блок управления файлом, который строится
ДОС в случае, если в командной строке в качестве второго
параметра было указано имя файла.

Предыдущие два поля созданы для поддержки программ,
конвертированных из CP/M.

Остаток командной строки/Дисковый буфер.(PSP:80)
------------------------------------------------

Последнее поле также создано под влиянием CP/M. После
запуска программы все, что находилось в командной строке, начиная
со второго символа после имени программы, строится в виде строки
слов и записывается в это поле. Лишние пробелы удаляются. В
первый байт поля записывается количество символов в этой строке.

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

Контекстное переключение PSP.
-----------------------------

У каждой программы есть свой PSP. Но ДОС известен только
один PSP - находящийся перед программой, запущенной последней.
Программа в ДОС может порождать выполнение другой программы, и
программы-дети могут наследовать значения из PSP "папаши", но,
так как ДОС знает только об одной активной задаче, то и только об
одном PSP.


PSP содержит несколько интересных полей, но самое интересное
для дальнейшего обсуждения - это массив из 20 указателей
файлов. Программа может открыть до 20 файлов одновременно. С
каждым файлом ассоциируется указатель, являющийся элементом
массива в PSP. В этом массиве есть место для 20 указателей, и
первые пять из них отданы для логических устройств stdin, stdout,
stderr, stdaux, stdprn. При открытии файлов новые указатели
записываются в массив, неиспользуемые позиции массива имеют
значение -1. К файлам программа обращается по номерам указателя в
массиве, а в элементе массива с таким номером хранится ссылка на
соответствующую структуру в системных таблицах управления
файлами.

При открытии файла в TSR-программе его указатель
записывается в PSP этой программы. При вызове TSR-программы ДОС
считает, что выполняется по-прежнему прерванная программа. При
обращении к ДОС для работы с файлом, открытым при первоначальном
запуске TSR-программы, ДОС будет брать указатель файла с данным
номером из PSP прерванной программы. Поэтому возможно или
обращение к чужому файлу, если файл с таким номером открыт и в
прерванной программе, или ошибка по обращению к неоткрытому
файлу.

Разрешить эту проблему можно следующим образом:

- не открывать файлов во время инициализации TSR-программы;
открывать их после вызова и прерывания другой программы. Тогда
для файлов TSR-программы будут использоваться указатели в PSP
прерванной программы.

- не использовать работу с указателями файлов ДОС 2.0 и
выше. Пользоваться FCB(File Control Block) из ДОС 1.1. Эти
функции не используют PSP. Таблицы FCB строятся в области данных
программы, использующих файлы.

- переключить системный указатель PSP при вызове
TSR-программы на ее PSP, и после отработки вернуть все на свои
места.

Каждое из этих решений имеет свои недостатки:

- При использовании таблицы указателей файлов из PSP
прерванной программы имеются два серьезных недостатка. Во-первых,
может оказаться неприемлемым открывать и закрывать файлы при
каждом вызове TSR-программы. Во-вторых, нельзя быть уверенным,
что в таблице прерванной задачи достаточно места для указателей
файлов TSR-программы.

- Использование функций с FCB имеет два
недостатка. Во-первых, файлы, открываемые с помощью FCB, должны
быть в текущем подкаталоге. Пути для спецификации файла указывать
нельзя. Во-вторых, все стандартные библиотечные функции Турбо Си
для работы с файлами предполагают использование более гибких
указателей. Для использования FCB вы должны создать функции,
эквивалентные стандартным open, close, read, write, fclose, fget,
fput, fprintf и т.д.

- Не имеется документированной функции ДОС для изменения
адреса PSP. Есть документированная функция для чтения текущего
PSP (INT 0x21, функция 0x62), но она доступна только в ДОС версии
3.0 и выше. Две функции ДОС (0х50 и 0х51), не описанные в
документации, устанавливают и записывают адрес PSP, но они
недоступны для использования резидентными программами в ДОС 2.0 и
2.1. Они разделяют стек с некоторыми функциями ДОС, которые могут
быть прерваны резидентной программой. Если TSR-программа
использует функции 0х50 и 0х51 в этом случае, система
зависнет. Эти функции можно использовать только с ДОС версий 3.0
и выше. Адрес PSP называется идентификатором процесса (Process ID
- PID). Две скрытые функции ДОС носят название GetPID (получить
идентификатор процесса) и SetPID (установить идентификатор
процесса).

Ни одно из этих решений не является удовлетворительным.
Необходимы работающие заменители функций GetPID и SetPID. Одно из
решений - экспериментально определить адрес, куда ДОС записывает
PID. Этот адрес, конечно, зависит от версии ДОС и может быть
уникальным даже при использовании одной версии ДОС. Использование
таких значений является опасным решением.


Лучшим является определение адреса PID оперативно во время
выполнения TSR-программы. Найти адреса, куда ДОС записывает PID,
можно, используя следующую процедуру. Сначала, текущий PID
восстанавливается с использованием функции GetPID. Затем по
памяти, занимаемой ДОС, осуществляется поиск копии значения
PID. Как только такое значение находится, его адрес запоминается
и PID изменяется на какое-либо значение с помощью функции SetPID.
Значение по найденному адресу анализируется на нахождение там
нового значения. Если это так, то обнаружен адрес, куда в ДОС
записывается PID, и он запоминается. Первоначальное значение PID
восстанавливается, и поиск продолжается. Версии ДОС до 3.0
сохраняют PID в двух местах. Версии от 3.0 и выше поддерживают
одно значение PID. Адреса PID и PID самой TSR-программы
записываются до завершения и обьявления себя резидентной. При
вызове TSR-программы один из сохраненных адресов PID используется
для чтения PID прерванной программы. Это значение запоминается,
устанавливается значение PID резидентной программы. Перед
завершением резидентной программы она восстанавливает в системе
PID прерванной программы.

Этот способ используется в функциях резидентного драйвера в
главе 12.

Дисковый буфер.
-----------------------------------------------------------------

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

В библиотеке Турбо Си есть функции, читающие и
устанавливающие адрес дискового буфера. Getdta возвращает адрес
текущего дискового буфера, и setdta изменяет адрес. Эти функции
имеют следующий формат:

#include "dos.h"
char far *dta;

dta = getdta();

setdta(dta);

Если TSR-программа будет использовать функции ДОС, которые
пишут в дисковый буфер, она должна сохранить адрес дискового
буфера прерванной программы, установить свой, и перед завершением
работы восстановить предыдущий адрес.

Если TSR-программа будет писаться на Си, нельзя быть
уверенным, что она не изменяет или не использует дисковый
буфер. Действия функций из библиотеки Си достаточно не изучены, и
лучшим решением будет самое осторожное. Резидентный драйвер,
описанный в главе 12, сохраняет дисковый буфер прерванной
программы.

Прерывание от клавиатуры (9).
-----------------------------------------------------------------

При нажатии

Список сторінок

Поділитися у соцмережах

☏ Замовлення реклами: +3805044-68-872

© Асоціація електронних бібліотек України

☝ Усі матеріали сайту (зокрема статті, зображення, рекламні оголошення тощо) призначені лише для попереднього ознайомлення. Усі права на публікації, представлені на сайті, належать їхнім законним власникам. Просимо Вас не зберігати копії інформації.