16.6. Организация субпроцессов и резидентных программ
Операционная система MS-DOS обладает мощной способностью организовывать субпроцессы. Это означает, что одна программа может запустить другую, а та, в свою очередь, еще одну и т.д. Запускающая программа (процесс), включив работу субпроцесса, сама как бы замирает (но остается в памяти ПЭВМ). Субпроцесс выполняется в оставшейся памяти как любая другая программа. После его завершения вновь «оживает» процесс более высокого уровня.
Это свойство MS-DOS позволяет создавать программы-интеграторы. В них содержатся, как правило, меню запускаемых программ с комментариями и сами запуски этих программ, т.е. команды организации субпроцессов. Популярная программа Norton Commander, например, является нормальной нерезидентной программой, активно использующей субпроцессы (будь то запускаемые из нее файлы или подкачка программ типа dbview.exe).
Нормальные программы после завершения работы освобождают память ПЭВМ. Субпроцессы после их завершения передают управление вызвавшим их программам, а те, в конце концов, передают управление ОС.
Иначе работают так называемые резидентные программы. После запуска они могут выполнить сразу какие-либо действия, а могут
- 376 -
ограничиться только скрытыми «махинациями» с прерываниями ОС. Основная их особенность в том, что после завершения своей работы они остаются в памяти ПЭВМ, и оживают лишь при выполнении каких-либо условий. Как правило, резидентные программы настраивают на себя определенные векторы прерываний (сигнал с клавиатуры, обращение к диску, запуск программы и т.п.), анализируют, что произошло, и при необходимости выполняют заложенные в них действия. Примеров резидентных программ можно привести множество. Здесь и система SideKick, и драйверы кириллицы ALFA, BETA, антивирусные программы и сами вирусы, а также многое другое. Объединяет их одно: они все находятся в ОЗУ и «оживают» лишь при специальных условиях (нажатии определенной клавиши, например, ALFA, BETA; обращении к диску — антивирусные программы-защитники, наступлении заданного времени — программы расписаний и т.п.). В пассивном состоянии резидентные программы только занимают место и не мешают нормальным программам работать (в том числе вызывать субпроцессы и т.д.).
При написании программ с вызовом субпроцессов или резидентных программ на Турбо Паскале всегда необходимо предварительно рассчитывать требования к памяти конкретной программы. По умолчанию считается, что программа может работать со всей свободной на момент запуска памятью (рис. 16.15).
Рис. 16.15
Эта свободная память используется для локальных переменных процедур и функций и для динамических переменных (созданных
- 377 -
процедурами New и GetMem). В ней не могут быть организованы субпроцессы. А другой свободной памяти нет. Выход один — указать явно отводимый размер памяти (рис. 16.16).
Рис. 16.16
Теперь в свободной памяти можно организовывать субпроцессы. Похожая картина имеет место и с резидентными программами. Если не ограничить «аппетит» программы, то она останется резидентной, но отведет под себя всю оставшуюся память. При этом другие программы запускаться не будут — негде.
В Турбо Паскале для управления памятью служит ключ компилятора $М. Он должен стоять до первой строки текста на Паскале в основной программе (в модулях он не имеет смысла):
{$М Стек, МинимумКучи, МаксимумКучи }
Этот ключ детально обсуждался в разд. 11.4. На месте первого параметра должно стоять число в интервале от 1024 до 65520 (1K...64K) — это память для стека. Второй параметр — Минимум-Кучи — это число, показывающее минимальный размер требуемой свободной памяти (обычно это 0, максимальное значение — 655360 (640K). Если на момент запуска программы свободной памяти в ПЭВМ будет меньше чем МинимумКучи, то она не сможет работать и остановится с сообщением о невозможности выполнения. Последним идет максимальный размер кучи. Его диапазон тот же: от 0 до 655360 (0K...640K), но не меньше чем МинимумКучи.
- 378 -
Задавая размеры кучи, не следует заботиться об уже находящихся в памяти программах и вычислять, сколько места они занимают. Если для кучи выделено параметром МаксимумКучи места больше, чем реально осталось в памяти, то его значение будет автоматически сокращено. Основной задачей для разработчика программы является определение разумных ресурсов стека и кучи. Размер стека можно оценить, просуммировав размеры локальных переменных в процедурах и функциях самой «глубокой» по уровням вложенности цепочки их вызовов и найдя максимальное для программы значение этой суммы. Проще найти требуемые размеры кучи. Для этого надо просуммировать размеры динамических переменных (объектов), созданных при выполнении процедур New и GetMem. Заметим, что ограничившись этой суммой, надо далее идеально использовать память кучи, т.е. не оставлять в ней пустот между динамическими объектами, стирая некоторые из них. В противном случае, если куча будет «дырчатой» (разрывной), в нее могут не уместиться объекты, более крупные, чем максимальный свободный блок в ней, даже если сумма этих зазоров будет достаточно велика.
Тем не менее полученные числа для размеров стека и кучи вполне можно взять за начальные приближения. Если же они окажутся малы, то возникнут ошибки времени выполнения: 202 — переполнение стека и 203 — переполнение кучи (при отладке программ рекомендуем включать в них директиву проверки переполнения стека {$S+}).
Пусть, к примеру, вычисленные предварительно размеры стека и кучи будут равны 968 байт и 2000 байт соответственно. Тогда директива $М может иметь вид
{$М 1024, 0, 2048 -
Значения округлены в большую сторону до целого числа килобайтов. Вместо 0 можно, в принципе, поставить любое число, не превышающее максимальное значение кучи (здесь до 2048).
Программа, не имеющая вызовов New или GetMem внутри себя и во внешних библиотеках, подключаемых через директиву USES, кучи не использует, и может иметь директиву типа
{$М 4096, 0, 0 -
Установив директиву использования памяти, можно далее применять специальные процедуры (табл. 16.7) для организации субпроцессов и резидентных программ.
- 379 -
Процедуры
Назначение
SwapVectors
Восстанавливает системные либо временные векторы прерываний для запускаемых субпроцессов
Exec(ExeFile, ComLine : String)
Запускает выполнимый файл ExeFile (субпроцесс) со строкой параметров ComLine
Функция DosExitCode : Word
Возвращает код завершения субпроцесса
Keep(ExitCode : Word)
Завершение программы без стирания ее из памяти ПЭВМ (организация резидентного кода)