ся частью большой темы, называемой переключением контекста
(context switching).
Если отобразить сегменты, в которых выполняется программа,
как ее контекст, то можно увидеть, что при многих обстоятельствах
бывает необходимо изменять полный контекст программы. Примерами
этого могут служить вызовы резидентных подпрограмм, вызывающих
библиотеки RTL и использующие некоторые типы оверлеев или сопрог-
рамм. (Сопрограмма является структурной единицей программы, кото-
рая используется для описания логически параллельных действий и
вызывается подобно подпрограмме. В отличие от подпрограммы, каж-
дый вызов сопрограммы возобновляет ее выполнение с точки послед-
него возврата. Сопрограмма представляет собой вид специального
оверлея, который не имеет связей порождающая-порожденная подпрог-
раммы). В этом случае, когда одна подпрограмма получает управле-
ние, она желает установить для выполнения свои собственные сег-
мент данных, внешний сегмент и сегмент стека. Во время получения
управления из другой программы наверняка известно только то, что
ее программный сегмент и указатель инструкции установлены в над-
лежащие значения. Обратимся к листингу 3-9. После вызова функции
загрузки и выполнения программы контекст вызывающей программы был
сброшен и этот листинг показывает как устанавливать контекст
программы. Пример, приведенный в листинге 3-9, несколько неудачен
тем, что не сохраняет контекст предыдущей программы, а просто пе-
резаписывает его.
При получении управления, если необходимо сохранить целый
набор регистров, наиболее легким способом выполнения этого явля-
ется способ, заключающийся в том, чтобы сначала установить новый
стек программы и затем записать в него эти регистры. Поскольку
значения стекового сегмента и указателя стека не могут быть сох-
ранены в стеке вызывающей программы (в связи с отсутствием спо-
соба получения их обратно) и поскольку они не могут быть сохра-
нены в новом стеке (который еще не установлен), параметры стека
должны сохраняться в памяти. Если в виде исключения поместить в
один и тот же сегмент программные коды и данные, то для сохране-
ния старых стекового сегмента и указателя стека и установке
новых стекового сегмента и указателя стека может быть использо-
вана последовательность программных кодов, показанная в листин-
ге 3-11.
- 3-58 -
Листинг 3-11. Переключение стека для программы типа .EXE
----------------------------------------------------------------
enter: mov cs:old_stk_seg,ss ; Сохранение значений старого
mov cs:old_stk_ptr,sp ; стека
mov ss,cs:new_stk_seg ; загрузка значений нового
mov sp,cs:new_stk_ptr ; стека
push ds ; регистры стекового сегмента
push es
push ax ; начало записи в стек общих регистров
...
push bp
push si
push di
...
body:<тело программы> ; здесь начинается тело программы
...
pop di ; начало восстановления общих регистров
pop si
pop bp
...
pop ax
pop es ; восстановление регистров сегмента
mov ss,cs:old_stk_seg ; восстановление старых зна-
mov sp,cs:old_stk_ptr ; чений стека
jmp exit ; обход памяти данных
old_stk_seg dw ? ; стековый сегмент вызывающей программы
old_str_ptr dw ? ; указатель стека вызывающей программы
; стековый сегмент подпрограммы
new_stk_seg dw segment stack
; указатель стека подпрограммы
new_stk_ptr dw top_of_stack
exit: ; позиция выхода
ret ; возврат в вызывающую программу
----------------------------------------------------------------
Программные коды в листинге 3-11 зависят от имеющихся значе-
ний для стекового сегмента и указателя стека, уже размещенных в
памяти. Для резидентных подпрограмм и подпрограмм RTL это должно
быть выполнено с помощью процесса инициализации. Надлежащие зна-
чения в память для программ типа .EXE операционная система
MS-DOS помещает в процессе настройки.
В связи с тем, что подпрограммы типа .COM не могут содержать
значения сегмента, эти подпрограммы требуют другого способа пе-
реключения стеков. Запоминание значений для вершины стека в па-
мяти не вызывает проблем, за исключением адреса начала сегмента.
Т.к. подпрограммы типа .COM для своих целей совместно используют
один и тот же сегмент, то значение стекового сегмента может быть
получено из регистра программного сегмента. К несчастью, семей-
ство микропроцессоров 8086 не поддерживает пересылку из регистра
сегмента в регистр сегмента, поэтому значение может быть пере-
дано косвенным путем. В связи с отсутствием регистра, в котором
можно было бы сохранить значение, значение передается через па-
мять, используя кодовый сегмент. Для реализации этого способа
начинайте подпрограмму со следующей инструкции:
mov cs:new_stk_seg,cs ; получение нового стекового сегмента
Если необходимо, то для переключения стеков в программе мож-
- 3-59 -
но разработать два макроса, содержащих требуемые программные ко-
ды. Первый макрос включает программный код из входа в тело
программы, а второй макрос программный код из выхода из тела
программы. Оба макроса должны соответствовать именам переменных
стека в области данных, а второй макрос, кроме того, должен при-
нимать метку вершины стека top_of_stack как параметр для включе-
ния в предложение dw для указателя нового стека new_stk_ptr. В
эти макросы не должна входить инструкция RET. Это позволит ис-
пользовать эти макросы для выхода как с помощью инструкций JMP и
IRET, так и с помощью инструкции RET.
Для файлов типа .EXE второй макрос должен также принимать
как параметр имя стекового сегмента. Пример описанных выше мак-
росов для файлов типа .COM содержится в листинге 3-12 (INIT28),
приводимом позднее в этой главе.
Дополнительные соображения по переключению стеков
При переключении стеков, или, наоборот, при манипуляции сте-
ковым сегментом программа уязвима для прерываний. При изменении
стекового сегмента, но не указателя стека, или, когда возникает
авария, должно бы произойти прерывание. В семействе микропроцес-
соров 8086 это предотвращается путем изменения указателя стека
сразу же после инструкции, которая загружает стековый сегмент.
Когда один из процессоров семейства микропроцессоров 8086 загру-
жает регистр сегмента (с помощью инструкции MOV или инструкции
POP), прерывание задерживается до тех пор, пока не будет выпол-
нена следующая инструкция. Эта особенность позволяет благополуч-
но обновлять регистр стекового сегмента и регистр указателя сте-
ка. Это также объясняет то, почему отладчик DEBUG пропускает
одну инструкцию при отслеживании инструкции MOV для регистра
сегмента. Отладчик DEBUG выполняет программу в пошаговом режиме
благодаря установке флажка прерывания, который генерирует преры-
вание #1, следующее после большинства инструкций. Т.к. при вы-
полнении инструкции, следующей за инструкцией переслать (MOV) в
регистр сегмента, прерывания запрещаются, то отладчик DEBUG не
получает управление до тех пор, пока не выполнит две инструкции,
следующие за инструкцией MOV.
В некоторых случаях не всегда нужно обращаться к длинному
переходу при переключении стеков, демонстрируемому в листинге
3-11. Некоторые регистры могут помещаться в стек вызывающей
программы много раз, позволяя регистрам использоваться в прог-
рамме или, по крайней мере, передавать новые значения в регистр
стека. Конкретный программист должен сам решать вопрос о том,
как много текущего контекста сохранять в отдельной программе.
Если переключение контекста используется с сопрограммами, то
каждая подпрограмма заканчивается сохранением контекста другой
подпрограммы. Хотя это и излишне, потому что только одной под-
программе необходимо сохранять контекст другой подпрограммы, но
в действительности не вредно. Сопрограмма, использующая эту
структуру, должна осуществлять выход только через функцию с ко-
дом 4Ch "Завершить программу" так, чтобы MS-DOS смогла правильно
завершить программу, независимо от состояния стека.
Если параметры передаются из одной программы в другую и каж-
дая программа поддерживает свой собственный стек, то для доступа
к параметрам в стеке нельзя использовать регистр BP. Вместо это-
го программисту необходимо извлечь значение стекового сегмента
вызывающей программы и переслать его в любой из регистров сег-
- 3-60 -
мента DS или ES и выполнять доступ к памяти относительно этого
регистра. Параметры затем можно прочитать из стека вызывающей
программы, даже если вызываемая программа использует свой собс-
твенный стек.
Введение в резидентную часть оперативной памяти
В некоторых случаях MS-DOS сама реализуется как резидентная
программа. Взгляните снова на рис.3-15, и Вы увидите схему памя-
ти для типичной MS-DOS версии 2.0 или выше. (Заметим, что это
исполнение необязательно применять для версий MS-DOS, выше чем
версия 3.1). Все части MS-DOS, за исключением нерезидентной час-
ти файла COMMAND.COM, располагаются в оперативной памяти все
время. Программы пользователя осуществляют доступ к MS-DOS пос-
редством прерываний или переходов к прерываниям, точно также,
как выполнялись резидентные подпрограммы пользователя.
Отдельные части операционной системы являются общими для
всех систем MS-DOS и совместимы даже между системами различных
номеров версий. Другие части систем являются уникальными для от-
дельных номеров версий или отдельных аппаратных средств, работа-
ющих под управлением MS-DOS. Различные компоненты. входящие в
MS-DOS, и атрибуты, связанные с каждым компонентом, показаны в
таблице 3-6. Названия компонентов могут изменяться от версии к
версии, но функции компонентов эквивалентны. Файлы, входящие в
состав того или иного компонента, приведены в Руководстве пользо-
вателя для той или иной версии MS-DOS. Заметим, что некоторые
файлы могут быть "скрытыми" файлами, которые не высвечиваются в