нет. Имейте ввиду, что установка переключателей может быть неверной, что ограничивает достоверность такого подхода. Для определения числа 16-байтных параграфов, доступных для DOS, используйте функцию 4AH прерывания 21H. ES должен иметь то же значение, что при старте задачи: ;---определение числа параграфов доступных для DOS MOV AH,4AH ;указываем нужную функцию MOV BX,0FFFFH ;требуем слишком большую память INT 21H ;BX содержит число доступных параграфов AT использует функцию 88H прерывания 15H для проверки наличия расширенной памяти, которая ищет память вне адресного пространст- ва процессора в обычном режиме адресации. Говорят, что она ищет память за отметкой 1 мегабайта. При этом на системной плате дол- жно быть от 512 до 640 килобайт памяти, чтобы эта функция рабо- тала. Число килобайтных блоков расширенной памяти возвращается в AX. Низкий уровень. Первый пример проверяет число банков памяти по 64K в первых десяти 64-килобайтных сегментах памяти. Если Вы будете проверять старшие 6 банков памяти, то имейте ввиду, что имеются видеобуфер, начиная с B000:0000 (и, возможно, A000:0000) и ПЗУ, начиная с F000:0000 (и, возможно, C000:0000). ;---проверка каждого банка памяти: CLI ;запрет аппаратных прерываний MOV AX,CS ;получаем значение кодового сегмента AND AX,0FFFH ;сбрасываем старшие 4 бита MOV ES,AX ;помещаем указатель в ES MOV DI,0 ;DI считает число банков памяти MOV CX,10 ;будем проверять 10 банков MOV BL,'X' ;для проверки используем 'X' NEXT: MOV DL,ES:[0] ;сохраняем значение тестируемой ячейки MOV ES:[0],BL ;помещаем 'X' в эту ячейку MOV DH,ES:[0] ;читаем тестируемую ячейку MOV ES:[0],DL ;восстанавливаем значение CMP DH,'X' ;совпадает с тем, что писали? JNE GO_AHEAD ;если нет, то банк отсутствует INC DI ;увеличиваем число банков GO_AHEAD: MOV AX,ES ;готовим увеличение указателя ADD AX,1000H ;указываем на следующие 64K MOV ES,AX ;возвращаем указатель в ES LOOP NEXT ;обрабатываем следующий банк STI ;разрешаем аппаратные прерывания Раздел 2. Управление прерываниями. Прерывания это готовые процедуры, которые компьютер вызывает для выполнения определенной задачи. Существуют аппаратные и прог- раммные прерывания. Аппаратные прерывания инициируются аппарату- рой, либо с системной платы, либо с карты расширения. Они могут быть вызваны сигналом микросхемы таймера, сигналом от принтера, нажатием клавиши на клавиатуре и множеством других причин. Аппа- ратные прерывания не координируются с работой программного обес- печения. Когда вызывается прерывание, то процессор оставляет свою работу, выполняет прерывание, а затем возвращается на прежнее место. Для того чтобы иметь возможность вернуться точно в нужное место программы, адрес этого места (CS:IP) запоминается на стеке, вместе с регистром флагов. Затем в CS:IP загружается адрес прог- раммы обработки прерывания и ей передается управление. Программы обработки прерываний иногда называют драйверами прерываний. Они всегда завершаются инструкцией IRET (возврат из прерывания), которая завершает процесс, начатый прерыванием, возвращая старые значения CS:IP и регистра флагов, тем самым давая программе воз- можность продолжить выполнение из того же состояния. С другой стороны, программные прерывания на самом деле ничего не прерывают. На самом деле это обычные процедуры, которые вызы- ваются Вашими программами для выполнения рутинной работы, такой как прием нажатия клавиши на клавиатуре или вывод на экран. Одна- ко эти подпрограммы содержатся не внутри Вашей программы, а в операционной системе и механизм прерываний дает Вам возможность обратиться к ним. Программные прерывания могут вызываться друг из друга. Например, все прерывания обработки ввода с клавиатуры DOS используют прерывания обработки ввода с клавиатуры BIOS для полу- чения символа из буфера клавиатуры. Отметим, что аппаратное пре- рываение может получить управление при выполнении программного прерывания. При этом не возникает конфликтов, так как каждая подпрограмма обработки прерывания сохраняет значения всех исполь- зуемых ею регистров и затем восстанавливает их при выходе, тем самым не оставляя следов того, что она занимала процессор. Адреса программ прерываний называют векторами. Каждый вектор имеет длину четыре байта. В первом слове хранится значение IP, а во втором - CS. Младшие 1024 байт памяти содержат вектора преры- ваний, таким образом имеется место для 256 векторов. Вместе взя- тые они называются таблицей векторов. Вектор для прерывания 0 начинается с ячейки 0000:0000, прерывания 1 - с 0000:0004, 2 - с 0000:0008 и т.д. Если посмотреть на четыре байта, начиная с адре- са 0000:0020, в которых содержится вектор прерывания 8H (прерыва- ние времени суток), то Вы обнаружите там A5FE00F0. Имея ввиду, что младший байт слова расположен сначала и что порядок IP:CS, это 4-байтное значение переводится в F000:FEA5. Это стартовый адрес программы ПЗУ, выполняющей прерывание 8H. На рис. 1-2 пока- зана схема выполнения программой прерывания 21H. 1.2.1 Программирование контроллера прерываний 8259. Для управления аппаратными прерываниями во всех типах IBM PC используется микросхема программируемого контроллера прерываний Intel 8259. Поскольку в ккаждый момент времени может поступить не один запрос, микросхема имеет схему приоритетов. Имеется 8 уров- ней приоритетов, кроме AT, у которого их 16, и обращения к соот- ветствующим уровням обозначаются сокращениями от IRQ0 до IRQ7 (от IRQ0 до IRQ15), что означает запрос на прерывание. Максимальный приоритет соответствует уровню 0. Добавочные 8 уровней для AT обрабатываются второй микросхемой 8259; этот второй набор уровней имеет приоритет между IRQ2 и IRQ3. Запросы на прерывание 0-7 соответствуют векторам прерываний от 8H до 0FH; для AT запросы на прерывания 8-15 обслуживаются векторами от 70H до 77H. Ниже при- ведены назначения этих прерываний: Аппаратные прерывания в порядке приоритета. IRQ 0 таймер 1 клавиатура 2 канал ввода/вывода 8 часы реального времени (только AT) 9 программно переводятся в IRQ2 (только AT) 10 резерв 11 резерв 12 резерв 13 мат. сопроцессор (только AT) 14 контроллер фиксированного диска (только AT) 15 резерв 3 COM1 (COM2 для AT) 4 COM2 (модем для PCjr, COM1 для AT) 5 фиксированный диск (LPT2 для AT) 6 контроллер дискет 7 LPT1 Прерыванию времени суток [2.1.0] дан максимальный приоритет, поскольку если оно будет постоянно теряться, то будут неверными показания системных часов. Прерывание от клавиатуры [3.1.0] вызы- вается при нажатии или отпускании клавиши; оно вызывает цепь событий, которая обычно заканчивается тем, что код клавиши поме- щается в буфер клавиатуры (откуда он затем может быть получен программными прерываниями). Микросхема 8259 имеет три однобайтных регистра, которые управ- ляют восемью линиями аппаратных прерываний. Регистр запроса на прерывание (IRR) устанавливает соответствующий бит, когда линия прерывания сигнализирует о запросе. Затем микросхема автоматичес- ки проверяет не обрабатывается ли другое прерывание. При этом она запрашивает информацию регистра обслуживания (ISR). Дополнитель- ная цепь отвечает за схему приоритетов. Наконец, перед вызовом прерывания, проверяется регистр маски прерываний (IMR), чтобы узнать разрешено ли в данный момент прерывание данного уровня. Как правило программисты обращаются только к регистру маски пре- рываний через порт 21H [1.2.2] и командному регистру прерываний через порт 20H [1.2.3]. 1.2.2 Запрет/разрешение отдельных аппаратных прерываний. Программы на аасемблере могут запретить аппаратные прерывания, перечисленные в [1.2.1]. Это маскируемые прерывания; другие аппа- ратные прерывания, возникающие при некоторых ошибках (таких как деление на ноль) не могут быть маскированы. Имеются две причины для запрета аппаратных прерываний. В первом случае все прерывания блокируются с тем чтобы критическая часть кода была выполнена целиком, прежде чем машина произведет какое-либо другое действие. Например, прерывания запрещают при изменении вектора аппаратного прерывания, избегая выполнения прерывания когда вектор изменен только наполовину. Во втором случае маскируются только определенные аппаратные прерывания. Это делается когда некоторые определенные прерывания могут взаимодействовать с операциями, критичными к временам. Например, точно рассчитанная по времени процедура ввода/вывода не может себе позволить быть прерванной длительным дисковым прерыва- нием. Низкий уровень. Выполнение прерываний зависит от значения флага прерывания (бит 9) в регистре флагов. Когда этот бит равен 0, то разрешены все прерывания, которые разрешает маска. Когда он равен 1, то все аппаратные прерывания запрещены. Чтобы запретить прерывания, установив этот флаг в 1, используется инструкция CLI. Для очистки этого флага и восстановления прерываний - инструкция STI. Избе- гайте отключения прерываний на длительный период. Прерывание времени суток происходит 18.2 раза в секунду и если к этому пре- рыванию был более чем один запрос в то время, когда аппаратные прерывания были запрещены, то лишние запросы будут отброшены и системное время будет определяться неправильно. Имейте ввиду, что машина автоматически запрещает аппаратные прерывания при вызове программных прерываний и автоматически разрешает их при возврате. Когда Вы пишете свои программные пре- рывания, то Вы можете начать программу с инструкции STI, если Вы можете допустить аппаратные прерывания. Отметим также, что если за инструкцией CLI не следует STI, то это приведет к остановке машины, так как ввод с клавиатуры будет заморожен. Для маскирования определенных аппаратных прерываний нужно просто послать требуемую цепочку битов в порт с адресом 21H, который соответствует регистру маски прерываний (IMR). Регистр маски на второй микросхеме 8259 для AT (IRQ8-15) имеет адрес порта A1H. Установите те биты регистра, которые соответствуют номерам прерываний, которые Вы хотите маскировать. Этот регистр можно только записывать. Нижеприведенный пример блокирует диско- вое прерывание. Не забудьте очистить регистр в конце программы, иначе обращение к дискам будет запрещено и после завершения прог- раммы. ;---маскирование 6-го бита регистра маски прерываний MOV AL,01000000B ;маскируем бит 6 OUT 21H,AL ;посылаем в регистр маски прерываний . MOV AL,0 ; OUT 21H,AL ;очищаем IMR в конце программы 1.2.3 Написание собственного прерывания. Имеется несколько причин для написания собственного прерыва- ния. Во-первых, большинство из готовых прерываний, обеспечиваемых операционной системой, ничто иное, как обычные процедуры, доступ- ные для всех программ, и Вы можете пожелать добавить свое в эту библиотеку. Например, многие Ваши программы могут использовать процедуру, выводящую строки на экран вертикально. Вместо того, чтобы включать ее в каждую программу в качестве процедуры Вы можете установить ее как прерывание, написав программу, которая останется резидентной в памяти после завершения [1.3.4]. Тогда Вы можете использовать INT 80H вместо WRITE_VERTICALLY (имейте вви- ду, что вызов прерывания несколько медленней, чем вызов процеду- ры). Второй причиной написания прерывания может быть использование какого-либо отдельного аппаратного прерывания. Это прерывание автоматически вызывается при возникновении определенных условий. В некоторых случаях BIOS инициализирует вектор этого прерывания так, что он указывает на процедуру, которая вообще ничего не делает (она содержит один оператор IRET). Вы можете написать свою процедуру и изменить вектор прерываний, чтобы он указывал на нее. Тогда при возникновении аппаратного прерывания будет выполняться Ваша процедура. Одна из таких процедур это прерывание времени суток [2.1.0], которое автоматически вызывается 18.2 раза в се- кунду. Обычно это прерывание только обновляет показание часов, но Вы можете добавить к нему любой код, который Вы пожелаете. Если Ваш код проверяет показания часов и вступает в игру в определен- ные моменты времени, то возможны операции в реальном времени. Другие возможности - это написание процедур обработки Ctrl-Break [3.2.8], PrtSC [3.2.9] и возникновения ошибочных ситуаций [7.2.5]. Прерывания принтера [6.3.1] и коммуникационные [7.1.8] позволяют компьютеру быстро переключаться между операциями вво- да/вывода и другой обработкой. Наконец, Вы можете захотеть написать прерывание, которое пол- ностью заменит одну из процедур операционной системы, приспособ- ленное к Вашим программным нуждам. В [1.2.4] показано как напи- сать прерывание внутри прерывания, которое позволяет Вам модифи- цировать существующие процедуры. Средний уровень. Функция 25H прерывания 21H устанавливает вектор прерывания на указанный адрес. Адреса имеют размер два слова. Старшее слово содержит значение сегмента (CS), младшее содержит смещение (IP). Чтобы установить вектор, указывающим на одну из Ваших процедур, нужно поместить сегмент процедуры в DS, а смещение в DX (следуя порядку нижеприведенного примера). Затем поместите номер прерыва- ния в AL и вызовите функцию. Любая процедура прерывания должна завершаться не обычной инструкцией RET, а IRET. (IRET выталкивает из стека три слова, включая регистр флагов, в то время как RET помещает на стек только два. Если Вы попытаетесь тестировать такую процедуру как обычную процедуру, но кончающуюся IRET, то Вы исчерпаете стек.) Отметим, что функция 25H автоматически запре- щает аппаратные прерывания в процессе изменения вектора, поэтому не существует опасности, что посреди дороги произойдет аппаратное прерывание, использующее данный вектор. ;---установка прерывания PUSH DS ;сохраняем DS MOV DX,OFFSET ROUT ;смещение для процедуры в DX MOV AX,SEG ROUT ;сегмент процедуры MOV DS,AX ;помещаем в DS MOV AH,25H ;функция установки вектора MOV AL,60H ;номер вектора INT 21H ;меняем прерывание POP DS ;восстанавливаем DS ;---процедура прерывания ROUT PROC FAR PUSH AX ;сохраняем все изменяемые регистры . . POP AX ;восстанавливаем регистры MOV AL,20H ;эти две строки надо использовать OUT 20H,AL ;только для аппаратных прерываний IRET ROUT ENDP В конце кода каждого из Ваших аппаратных прерываний Вы должны включить следующие 2 строчки кода: MOV AL,20H OUT 20H,AL Это просто совпадение, что числа (20H) одни и те же в обеих строках. Если аппаратное прерывание не заканчивается этими стро- ками, то микросхема 8259 не очистит информацию регистра обслужи- вания, с тем чтобы была разрешена обработка прерываний с более низкими уровнями, чем только что обработанное. Отсутствие этих строк легко может привести к краху программы, так как прерывания от клавиатуры скорее всего окажутся замороженными и даже Ctrl-Alt-Del окажется бесполезным. Отметим, что эта добавка не нужна для тех векторов прерываний, которые являются расширениями существующих прерываний, таким как прерывание 1CH, которое добав- ляет код к прерыванию времени суток [2.1.7]. Когда программа завершается, должны быть восстановлены ориги- нальные вектора прерываний. В противном случае последующая прог- рамма может вызвать данное прерывание и передать управление на то место в памяти, в котором Вашей процедуры уже нет. Функция 35 прерывания 21H возвращает текущее значение вектора прерывания, помещая значение сегмента в ES, а смещение в BX. Перед установкой своего прерывания получите текущее значение вектора, используя эту функцию, сохраните эти значения, и затем восстановите их с помощью функции 25H (как выше) перед завершением своей программы. Например: ;---в сегменте данных: KEEP_CS DW 0 ;хранит сегмент заменяемого прерывания KEEP_IP DW 0 ;хранит смещение прерывания ;---в начале программы MOV AH,25H ;функция получения вектора MOV AL,1CH ;номер вектора INT 21H ;теперь сегмент в ES, смещение в BX MOV KEEP_IP,BX ;запоминаем смещение MOV KEEP_CS,ES ;запоминаем сегмент ; ---в конце программы CLI PUSH DS ;DS будет разрушен MOV DX,KEEP_IP ;подготовка к восстановлению MOV AX,KEEP_CS ; MOV DS,AX ;подготовка к восстановлению MOV AH,25H ;функция установки вектора MOV AL,1CH ;номер вектора INT 21H ;восстанавливаем вектор POP DS ;восстанавливаем DS STI Имеется пара ловушек, которых следует избегать при написании прерывания. Если новая процедура прерывания должна иметь доступ к данным, то необходимо позаботиться, чтобы DS был правильно уста- новлен (обычно прерывание может использовать стек вызывающей программы). Другая неприятность может заключаться в том, что при завершении программы по Ctrl-Break вектор прерывания не будет восстановлен, если только Вы не предусмотрите, чтобы программа реакции на Ctrl-Break выполняла эту процедуру [3.2.8]. Низкий уровень. Описанные выше функции MS DOS просто получают или изменяют пару слов в младших ячейках памяти. Смещение вектора может быть вычислено простым умножением номера вектора на 4. Например, чтобы получить адрес прерывания 16H в ES:BX: ;---получение адреса прерывания 16H SUB AX,AX ;устанавливаем ES на начало памяти MOV ES,AX ; MOV DI,16H ;номер прерывания в DI SHL DI,1 ;умножаем на 2 SHL DI,1 ;умножаем на 2 MOV BX,ES:[DI] ;берем младший байт в BX MOV AX,ES:[DI]+2 ;берем старший байт в ES MOV ES,AX ; Не рекомендуется прямо устанавливать вектор прерываний, обходя функцию DOS. В частности в многозадачной среде операционная сис- тема может поддерживать несколько таблиц векторов прерываний и реальный физический адрес таблицы может быть известен только DOS. 1.2.4 Дополнение к существующему прерыванию. Хотя и не часто, но иногда бывает полезно добавить код к су- ществующему прерыванию. В качестве примера рассмотрим программы, которые преобразуют одно нажатие клавиши в длинные определяемые пользователем символьные строки (макроопределения клавиатуры). Эти программы используют факт, что весь ввод с клавиатуры посту- пает поступает через функцию 0 прерывания 16H BIOS [3.1.3]. Все прерывания ввода с клавиатуры DOS вызывают прерывание BIOS для получения символа из буфера клавиатуры. Поэтому необходимо моди- фицировать лишь прерывание 16H, таким образом, чтобы оно служило шлагбаумом для макроопределений, после чего любая программа будет получать макроопределения, независимо от того, какое прерывание ввода с клавиатуры она использует. Конечно, модифицировать прерывания BIOS и DOS непросто, пос- кольку BIOS расположена в ПЗУ, а DOS поступает без листинга и они ограничены размерами отведенной для них памяти. Но Вы можете написать процедуру, которая предшествует и/или следует за соот- ветствующим прерыванием, и эта процедура может вызываться при вызове прерывания DOS или BIOS. Например, в случае прерывания 16H, Вам нужно написать процедуру и указать на нее вектором пре- рывания для 16H. Оригинальное значение вектора 16H тем временем переносится в какой-либо неиспользуемый вектор, скажем, 60H. Новая процедура просто вызывает прерывание 60H, чтобы использо- вать оригинальное прерывание 16H; поэтому когда программа вызы- вает прерывание 16H, управление передается Вашей процедуре, кото- рая затем вызывает оригинальное прерывание 16H, которая по завер- шении опять возвращает управление Вашей процедуре, а из нее уже Вы возвращаетесь в то место программы, из которого был вызов прерывания 16H. После того как это сделано, в новой процедуре может содержаться любой код, как до, так и после вызова прерыва- ния 60H. На рис. 1-3 показана диаграмма этой процедуры. Вот крат- кая сводка необходимых действий: 1. Создать новую процедуру, вызывающую прерывание 60H. 2. Перенести вектор прерывания для 16H в 60H. 3. Изменить вектор 16H, чтобы он указывал на новую процедуру. 4. Завершить программу, оставляя ее резидентной [1.3.4]. Раздел 3. Управление программами. Большинство программ загружаются в память, запускаются, а затем удаляются операционной системой при завершении. Языки высо- кого уровня обычно не имеют альтернативы. Но для программистов на ассемблере имеется другая возможность и данный раздел демонстри- рует ее. Некоторые программы действуют как драйверы устройств или драйверы прерываний и они должны быть сохранены в памяти ("резидентными") даже после их завершения (вектора прерываний обеспечивают механизм, посредством которого последующие программы могут обращаться к резидентным процедурам). Иногда программе необходимо запустить из себя другую программу. На самом деле DOS позволяет программе загрузить в память вторую копию COMMAND.COM, которая может использована как средство интерфейса с пользовате- лем или выполнения команд типа COPY или DIR. Программы могут быть в двух форматах: .EXE или .COM. Программы первого типа могут быть больше 64K, но они требуют некоторой обработки перед тем, как DOS загрузит их в память. С другой сто- роны COM программы существуют прямо в том формате, который нужен для загрузки в память. COM программы особенно полезны для корот- ких утилит. В обоих случаях код, составляющий программу, предва- ряется в памяти префиксом программного сегмента (PSP). Это об- ласть размером 100H байт, которая содержит информацию необходимую DOS для работы программы; PSP также обеспечивает место для файло- вых операций ввода/вывода [5.3.5]. При загрузке EXE файла и DS и ES указывают на PSP. Для COM файлов CS также сначала указывает на PSP. Отметим, что MS DOS 3.0 имеет функцию, которая возвращает номер сегмента PSP. Это функция 62H прерывания 21H; ей ничего не надо подавать на входе, а в BX возвращается номер параграфа. Одна из причин, по которой интересно положение PSP, состоит в том, что его первое слово содержит номер прерывания DOS, которое будет приводить к завершению программы. Когда выполняется послед- ний оператор RET программы, то значения на вершине стека указы- вают счетчику команд (регистр IP) на начало PSP, таким образом код завершения выполняется как следующая инструкция программы. Дальнейшее обсуждение этого смотрите в пунктах [1.3.4] и [1.3.6]. Для справки приводим значение полей PSP: Смещение Размер поля Значение 0H DW номер функции DOS завершения программы 2H DW размер памяти в параграфах 4H DW резерв 6H DD длинный вызов функции диспатчера DOS AH DD адрес завершения (IP,CS) EH DD адрес выхода по Ctrl-Break (IP,CS) 12H DD адрес выхода по критической ошибке 16H 22 байта резерв 2CH DW номер параграфа строки среды 2EH 46 байтов резерв 5CH 16 байтов область параметров 1 (формат FCB) 6CH 20 байтов область параметров 2 (формат FCB) 80H 128 байтов область DTA по умолчанию/получает командную строку программы 1.3.1 Манипуляции с памятью. Когда MS DOS загружает программу, то она помещается в младшую область памяти, сразу же за COMMAND.COM и установленными драйве- рами устройств или другими утилитами, которые резидентны в памя- ти. В этот момент времени вся память за программой отведена этой программе. Если программе нужна память для создания области дан- ных, то она может приближенно вычислить где в памяти кончается ее код и затем поместить требуемую область данных в любое место за концом кода. Для определения адреса конца программы поместите в конце программы псевдосегмент типа: ZSEG SEGMENT ; ZSEG ENDS В ассемблере IBM PC ZSEG будет последним сегментом, так как сегменты располагаются в алфавитном порядке. С другими ассембле- рами нужно действительно поместить эти строки в конце программы. В самой программе достаточно поставить оператор MOV AX,ZSEG и AX будет указывать на первый свободный сегмент памяти за программой. Такой подход будет работать до тех пор, пока программа не будет предполагать о наличии памяти, которой на самом деле нет. Он не будет также работать в многопользовательской среде, когда несколько программ могут делить между собой одну и ту же область адресов. Для решения этой проблемы MS DOS имеет возможность отс- леживать 640K системной памяти и отводить по требованию программы блоки памяти любого размера. Блок памяти - это просто непрерывная область памяти, его максимальный размер определяется размером доступной памяти, в частности, он может быть больше одного сег- мента (64K). Если затребован слишком большой блок, то DOS выдает сообщение об ошибке. Любая возможность перекрытия блоков исключе- на. Кроме того MS DOS может освобождать, урезать или расширять существующие блоки. Хотя программа не обязана использовать эти средства, но удобно и предусмотрительно делать это. Некоторые функции DOS требуют, чтобы были использованы средства управления памятью DOS, например, завершение резидентной программы [1.3.4] или вызов другой программы из данной [1.3.2]. Прежде чем отвести память, существующий блок (вся память от начала программы до конца) должен быть обрезан до размера прог- раммы. Затем, при создании блока, DOS создает 16-байтный управ- ляющий блок памяти, который расположен непосредственно перед блоком памяти. Первые 5 байтов этого блока имеют следующее значе- ние: байт 0 ASCII 90 - если последний блок в цепочке, иначе ASCII 77. байты 1-2 0 если блок освобожден байты 3-4 размер блока в 16-байтных параграфах DOS обращается к блокам по цепочке. Адрес первого блока хра- нится во внутренней переменной. Значение этой переменной позво- ляет DOS определить положение первого отведенного блока, а из информации, содержащейся в нем, может быть найден следующий блок и т.д., как показано на рис. 1-4. Как только Вы начали использо- вать систему распределения памяти DOS, то Вы обязаны придержи- ваться ее. Если программа изменит содержимое управляющего блока, то цепочка будет разорвана и DOS начнет выдавать сообщения об ошибке. MS DOS обеспечивает три функции распределения памяти, номера от 48H до 4AH прерывания 21H. Функция 48H отводит блок памяти, а 49H - освобождает блок памяти. Третья функция ("SETBLOCK") ме- няет размер памяти, отведенной для программы; эта функция должна быть использована перед двумя остальными. После ее выполнения можно спокойно отводить и освобождать блоки памяти. Программа должна освободить все отведенные ею блоки перед завершением. Иначе эта память будет недоступной для последующего использова- ния. Средний уровень. Все три функции распределения памяти прерывания 21H используют 16-битный адрес начала блока памяти, с которым они оперируют. Этот адрес соответствует сегменту, с которого начинается блок (блок всегда начинается со смещения 0 данного сегмента). Таким образом реальный адрес ячейки начала блока равен этому адресу, умноженному на 16. Также, для всех трех функций, BX содержит число 16-байтных разделов памяти (параграфов), которые будут отводиться или освобождаться. Если функция не может быть выполне- на, то устанавливается флаг переноса, а в AX возвращается код ошибки, объясняющий причину. Возможны три кода ошибки: 7 разрушен управляющий блок памяти 8 недостаточно памяти для выполнения функции 9 неверный адрес блока памяти Функция отведения блока использует коды 7 и 8, а освобождения - 7 и 9, в то время как функция изменения блока использует все три кода. В следующем примере сначала отводится блок, размером 1024 байта. При этом BX содержит требуемое число 16-байтных парагра- фов, а при завершении стартовый адрес блока равен AX:0 (т.е. смещение 0 в сегменте со значением, содержащимся в AX). Вторая часть примера освобождает этот же блок, как и требуется при за- вершении программы. В данном случае значение полученное в AX помещается в ES. DOS следит за размером блока и знает какое коли- чество параграфов надо освободить. ;---отведение блока размером 1024 байта MOV AH,48H ;номер функции MOV BX,64 ;требуем 64 параграфа INT 21H ;пытаемся отвести блок JC ERROR ;обрабатываем ошибку в случае неудачи MOV BLOCK_SEG,AX;иначе сохраняем адрес блока . ;---освобождаем тот же блок MOV AX,BLOCK_SEG ;получаем стартовый адрес блока MOV ES,AX ;помещаем его в ES MOV AH,49H ;номер требуемой функции INT 21H ;освобождаем блок памяти Наконец, приведем пример использования функции 4AH. ES содер- жит значение сегмента PSP, т.е. самого первого байта памяти, с которого загружена программа. Это значение присваивается ES при старте задачи. Для использования SETBLOCK надо либо вызывать эту функцию в самом начале программы (прежде чем ES будет изменен), либо сохранить его начальное значение для последующего использо- вания. BX содержит требуемый размер блока в 16-байтных параграфах. Для определения этого размера поместите добавочный "искуственный" сегмент в конец программы. В макроасссемблере IBM PC сегменты располагаются в алфавитном порядке, поэтому Вы можете поместить его в любое место программы, при условии, что его имя это что-то вроде "ZSEG". В других ассемблерах действительно помещайте фик- тивный сегмент в конец программы. Программа может прочитать пози- цию этого сегмента и, сравнивая ее со стартовым сегментом, полу- чить количество памяти, требуемое самой программе. В момент заг- рузки программы и ES и DS содержат номер параграфа самого начала программы в префиксе программного сегмента; для COM файлов CS также указывает на эту позицию, но для EXE файлов это не так. ;---освобождение памяти (ES имеет значение при старте) MOV BX,ZSEG ;получаем # параграфа конца программы + 1 MOV AX,ES ;получаем # параграфа начала программы SUB BX,AX ;вычисляем размер программы в параграфах MOV AH,4AH ;номер функции INT 21H ;освобождаем память JC MEMORY_ERROR ;проверяем на ошибку ;--- ZSEG SEGMENT ZSEG ENDS 1.3.2 Запуск одной программы из другой. MS DOS обеспечивает функцию EXEC (номер 4BH прерывания 21H), реализующую вызов одной программы из другой. Первая программа называется "родителем", а загружаемая и запускаемая - "потомком". Высокий уровень. В Бейсик версии 3.0 введена команда SHELL. Со значительными ограничениями она позволяет бейсиковской программе загрузить и выполнить другую программу. Формат этой команды SHELL ком_строка. Командная строка может быть просто именем программы или она может содержать кроме имени параметры, которые обычно следуют за именем программы в командной строке. Если ком_строка не указана, то загружается копия COMMAND.COM и появляется запрос операционной системы. В этот момент можно выполнить любую команду MS DOS, а по завершению вернуть управление бейсиковской программе, введя ко- манду EXIT. Имеется ряд ограничений при использовании SHELL. Если загру- жаемая программа меняет режим работы дисплея, то он не будет автоматически восстановлен при возврате. Перед загрузкой програм- мы все файлы должны быть закрыты, и это не может быть программа, которая остается резидентной после завершения. Обсуждение ряда других проблем содержится в руководстве по Бейсику. Средний уровень. Функция 4BH более сложна, чем остальные, требуя четырех подго- товительных шагов: 1. Подготовить в памяти место, доступное программе. 2. Создать блок параметров. 3. Построить строку, содержащую накопитель, путь и имя прог- раммы. 4. Сохранить значения регистров SS и SP в переменных. Поскольку при загрузке программы MS DOS выделяет ей всю дос- тупную память, то необходимо освободить место в памяти. Если не освободить часть памяти, то не будет места для загрузки второй программы. В [1.3.1] объяснено как это сделать с помощью функции SETBLOCK. После того как память освобождена, Вы должны просто поместить в BX требуемое число 16-байтных параграфов, заслать 4AH в AH и выполнить прерывание 21H, делая доступным программе именно то число параграфов, которое ей требуется. Блок параметров, на который должны указывать ES:BX это 14-байтный блок блок памяти, в который Вы должны поместить сле- дующую информацию: DW сегментный адрес строки среды DD сегмент и смещение командной строки DD сегмент и смещение первого FCB DD сегмент и смещение второго FCB Строка среды - это строка, состоящая из одной или более специ- фикаций, которым следует MS DOS при выполнении программы. Элемен- ты строки среды такие же, как и те что можно обнаружить в диско- вом файле CONFIG.SYS. Например, в строку может быть помещено VERIFY = ON. Просто начните строку с первого элемента, завершив его символом ASCII 0, потом запишите следующий и т.д. За послед- ним элементом должны следовать два символа ASCII 0. Строка должна начинаться на границе параграфа (т.е. ее адрес по модулю 16 дол- жен быть равен нулю). Это вызвано тем, что соответствующий вход в блоке параметров, указывающий на строку, содержит только 2-байт- ное сегментное значение. Все это не нужно, если новая программа может работать с той же строкой среды, что и программа "роди- тель". В этом случае надо просто поместить два символа ASCII 0 в первые 2 байта блока параметров. Следующие 4 байта блока параметров указывают на командную строку для загружаемой программы. "Командная строка" - это сим- вольная строка, определяющая способ работы программы. При загруз- ке программы из DOS она может иметь вид вроде EDITOR A:CHAPTER1\ NOTES.MS. При этом вызывается редактор и ему передается имя файла в подкаталоге накопителя A для немедленного открытия. Когда Вы подготавливаете командную строку для EXEC, то надо включать толь- ко последнюю часть информации, но не имя загружаемой программы. Перед командной строкой должен стоять байт, содержащий длину этой строки, и она должна завершаться символом <ВК> (ASCII 13). Последние 8 байтов блока параметров указывают на управляющие блоки файлов (FCB). FCB содержит информацию об одном или двух файлах, указанных в командной строке. Если открываемых файлов нет, то надо заполнить все 8 байт символом ASCII 0. В [5.3.5] объяснено, как работает FCB. Начиная с версии MS DOS 2.0, исполь- зование FCB необязательно и Вы можете не включать информацию FCB, вместо этого используя новую конвенцию дескриптора файлов (file handler), в которой доступ к файлу предоставляется по кодовому номеру, а не через FCB (также обсуждается в [5.3.5]). Наконец, Вы должны построить строку с указанием накопителя, пути и имени файла. Эта строка именует загружаемую программу. DS:DX указывает на эту строку при выполнении EXEC. Эта строка - стандартная строка ASCIIZ, т.е. ничего более, чем стандартная спецификация файла, завершаемая кодом ASCII 0. Например, это может быть B:\NEWDATA\FILER.EXE<NUL>, где символом <NUL> обозна- чен код ASCII 0. После того как вся указанная информация подготовлена, остается последняя задача. Поскольку все регистры будут изменены вызывае- мой задачей, то надо сохранить сегмент стека и указатель стека, с тем чтобы они могли быть восстановлены, когда управление будет возвращено вызвавшей задаче. Для их сохранения создайте перемен- ные. Поскольку значение регистра DS также будет изменено, то эти переменные не могут быть найдены, до тех пор пока не будут повто- рены операторы MOV AX,DSEG и MOV DS,AX. После того как SS и SP сохранены, поместите 0 в AL, для выбора операции "загрузка и запуск" (EXEC используется также для оверлеев [1.3.5]). Затем поместите 4AH в AH и вызовите прерывание 21H. В этот момент запу- щены две программы, причем программа "родитель" находится в оста- новленном состоянии. MS DOS предоставляет возможность программе потомку передать родителю код возврата, таким образом могут быть переданы ошибки и статус. В [7.2.5] объяснено как это сделать. Что касается самой функции запуска, то при возникновении ошибки устанавливается флаг переноса, а регистр AX в этом случае будет возвращать 1 - для неправильного номера функции, 2 - если файл не найден, 5 - при дисковой ошибке, 8 - при нехватке памяти, 10 - если неправильна строка среды и 11 - если неверен формат. Приводимый пример - простейший из возможных, но часто больше ничего и не надо. Здесь оставлен нулевым блок параметров и не создана строка среды. Это означает, что загружаемой программе не будет передаваться командная строка и что среда будет такой же, как и для вызывающей программы. Вы должны только изменить распре- деление памяти, создать имя и (пустой) блок параметров и сохра- нить значения SS и SP. ;---в сегменте данных FILENAME DB 'A:TRIAL.EXE',0 ;загружаем TRIAL.EXE PARAMETERS DW 7DUP(0) ;нулевой блок параметров KEEP_SS DW 0 ;переменная для SS KEEP_SP DW 0 ;переменная для SP ;---перераспределение памяти MOV BX,ZSEG ;получить # параграфа конца MOV AX,ES ;получить # параграфа начала SUB BX,AX ;вычислить размер программы MOV AH,4AH ;номер функции INT 21H ;перераспределение ;---указываем на блок параметров MOV AX,SEG PARAMETERS ;в ES - сегмент MOV ES,AX ; MOV BX,OFFSET PARAMETERS ;в BX - смещение ;---сохранить копии SS и SP MOV KEEP_SS,SS ;сохраняем SS MOV KEEP_SP,SP ;сохраняем SP ;---указываем на строку имени файла MOV DX,OFFSET FILENAME ;смещение - в DX MOV AX,SEG FILENAME ;сегмент - в DS MOV DS,AX ; ;---загрузка программы MOV AH,4BH ;функция EXEC MOV AL,0 ;выбираем "загрузку и запуск" INT 21H ;запускаем задачу ;---впоследствии, восстанавливаем регистры MOV AX,DSEG ;восстанавливаем DS MOV DS,AX ; MOV SS,KEEP_SS ;восстанавливаем SS MOV SP,KEEP_SP ;восстанавливаем SP ;---в конце программы создаем фиктивный сегмент ZSEG SEGMENT ;см. [1.3.1] ZSEG ENDS 1.3.3 Использование команд интерфейса с пользователем из программы. Программа может иметь в своем распоряжении полный набор команд интерфейса с пользователем DOS, таких как DIR или CHKDSK. Когда эти команды используются из программы, загружается и запускается вторая копию COMMAND.COM. Хотя такой подход может сэкономить много усилий при программировании, для его успешной реализации требуется достаточное количество памяти для этой второй копии и Ваша программа может попасть в ловушку если памяти недостаточно. Высокий уровень. Бейсик 3.0 может загрузить вторую копию COMMAND.COM с помощью оператора SHELL. SHELL обсуждается в [1.3.2]. COMMAND.COM загру- жается когда не указано имя файла, поэтому вводя просто SHELL, Вы получаете запрос MS DOS. В этот момент можно использовать любую из утилит DOS, включая командные файлы. Для возврата в вызвавшую программу надо ввести EXIT. Средний уровень. В этом случае к примеру, приведенному в [1.3.2] нужно добавить командную строку. Обычно она начинается с байта длины строки, затем следует сама командная строка и, наконец, код ASCII 13. При передаче команды COMMAND.COM Вы должны указать /C перед строкой (см. пункт "Вызов вторичного командного процессора" руководства по MS DOS). Вы должны также указать накопитель, на котором нахо- дится COMMAND.COM, поместив имя накопителя в начале командной строки. Чтобы вывести каталог накопителя A:, а COMMAND.COM при этом находится на накопит