Одна из важнейших систем MS-DOS - система управления программами . Она отвечает за выполнение процедуры запуска и завершения программ, в том числе имеющих оверлейную структуру.
Теперь, когда мы знаем структуру памяти на момент завершения загрузки операционной системы, можно посмотреть, а что же происходит дальше, когда пользователь запускает какую-нибудь программу.
В среде MS-DOS пользователь может запустить два типа программ (если не считать пакетных файлов, которые, вообще говоря, не являются программами, состоящими из машинных кодов). Файлы, содержащие программы этих двух типов, имеют расширение имени .com и .exe (мы будем называть их, соответственно, com-программы и exe-программы).
Указанные программные файлы имеют различный формат и загружаются по-разному, однако, когда загрузка завершена, структура распределенной для них памяти выглядит совершенно одинаково.
Программы, которые хранятся в файлах с расширением имени .com - это двоичный образ программы, состоящий из кода и данных. Образно говоря, com-файл содержит программу в "чистом" виде. Такая программа (как и exe-программа) может загружаться в любое место памяти. MS-DOS выполняет ее привязку к физическим адресам при загрузке с помощью установки сегментных регистров. Существенным ограничением com-программы является то, что она не может занимать больше одного сегмента (соответственно, стандартный com-файл не может иметь размер, превосходящий 64 Кбайта).
Программа второго типа (exe-программа) может иметь любой размер. В самом начале файла программы содержится заголовок (у файла com-программы заголовка нет). Этот заголовок используется операционной системой в процессе загрузки программы в память для правильной установки сегментных регистров. Заголовок exe-файла нужен только при загрузке; когда программа загружена и готова к работе, самого заголовка уже нет в памяти.
Заголовок exe-файла состоит из форматированной зоны и таблицы расположения сегментов (Relocation Table ). Форматированная зона выглядит следующим образом:
Смещение, байт |
Размер, байт |
Имя поля |
Описание |
0 |
2 |
signature |
Два байта 'MZ' (4Dh, 5Ah), идентифицирующие
exe-файл |
2 |
2 |
part_pag |
Размер последней страницы программы в
байтах (страница содержит 512 байт) |
4 |
2 |
file_size |
Размер программы в страницах по 512 байт |
6 |
2 |
rel_item |
Количество элементов в таблице
расположения сегментов |
8 |
2 |
hdr_size |
Размер заголовка файла в параграфах
(длина параграфа - 16 байт) |
10 |
2 |
min_mem |
Минимальное количество памяти в
параграфах, которое нужно зарезервировать после
области загруженной программы |
12 |
2 |
max_mem |
Максимальное количество памяти в
параграфах, которое нужно зарезервировать после
области загруженной программы |
14 |
2 |
ss_reg |
Начальное значение для загрузки
сегментного регистра SS |
16 |
2 |
sp_reg |
Начальное значение для загрузки
регистра SP |
18 |
2 |
chk_summ |
Контрольная сумма всех слов в файле |
20 |
2 |
ip_reg |
Значение, которое будет загружено в
регистр IP при запуске программы |
22 |
2 |
cs_reg |
Смещение от начала программы для
установки сегментного регистра кода CS |
24 |
2 |
relt_off |
Смещение таблицы расположения
сегментов программы от начала exe-файла |
26 |
2 |
overlay |
Номер оверлея, равен 0 для основного
модуля программы |
Таблица расположения сегментов программы начинается сразу после форматированной области и состоит из четырехбайтовых значений в формате <смещение:сегмент>.
Область файла после таблицы расположения сегментов выравнивается на границу параграфа с помощью байта-заполнителя. Дальше начинается сама программа.
Приведем описание заголовка файла и таблицы расположения сегментов, которые вы можете использовать для доступа к отдельным полям указанных структур:
typedef struct { unsigned signature; unsigned part_pag; unsigned file_size; unsigned rel_item; unsigned hdr_size; unsigned min_mem; unsigned max_mem; unsigned ss_reg; unsigned sp_reg; unsigned chk_summ; unsigned ip_reg; unsigned cs_reg; unsigned relt_off; unsigned overlay; } EXE_HDR; typedef struct { unsigned offset; unsigned segment; } RELOC_TAB;
Для демонстрации приемов работы с заголовком exe-файла приведем исходный текст программы EXELIST (листинг 3.1).
Эта программа считывает форматированную часть заголовка exe-файла, проверяет наличие в его первых двух байтах признака exe-формата ('MZ'). Если признак имеется, программа выводит на экран расшифрованное содержимое заголовка и таблицу перемещений, если такая таблица присутствует. В качестве параметра при запуске надо передать программе путь к exe-файлу.
Листинг 3.1. Файл exelist\ exelist.cpp
#include <stdio.h> #include <stdlib.h> typedef struct { unsigned signature; unsigned part_pag; unsigned file_size; unsigned rel_item; unsigned hdr_size; unsigned min_mem; unsigned max_mem; unsigned ss_reg; unsigned sp_reg; unsigned chk_summ; unsigned ip_reg; unsigned cs_reg; unsigned relt_off; unsigned overlay; } EXE_HDR; typedef struct { unsigned offset; unsigned segment; } RELOC_TAB; void main(int, char *[]); int get_exeh(EXE_HDR *exeh, RELOC_TAB **rtb, FILE *exe_file); int listhdr(char *path); void main(int argc, char *argv[]) { printf("Просмотр заголовка exe-файла\n" "(C) Фролов А.В., 1995\n\n"); if(argc != 2) { printf(" Укажите путь к exe-файлу в качестве" "параметра\n" ); return; } if(listhdr(argv[1]) != 0) { printf("Ошибка в формате файла или нет такого" "файла\n"); return; } } // ------------------------------------------- // listhdr // Отображение заголовка exe-файла // ------------------------------------------- int listhdr(char *path) { EXE_HDR header; RELOC_TAB *reloc; FILE *inpfile; int i; if((inpfile = fopen(path,"rb")) == 0) return(-1); if(get_exeh(&header,&reloc,inpfile) != 0) { fclose(inpfile); return(-1); } printf("Магическое число: %04X\n" "Длина последней страницы файла: %d\n" "Количество страниц в файле: %d\n" "Кол. элементов табл. перемещений: %d\n" "Размер заголовка в параграфах: %d\n" "Минимальная память для программы: %04X\n" "Максимальная память для программы: %04X\n" "Значение адреса стека SS:SP: %04X:%04X\n" "Контрольная сумма: %04X\n" "Значения для регистров CS:IP: %04X:%04X\n" "Смещение табл. перемещений: %02X\n" "Номер оверлея: %d\n", header.signature, header.part_pag, header.file_size, header.rel_item, header.hdr_size, header.min_mem, header.max_mem, header.ss_reg, header.sp_reg, header.chk_summ, header.cs_reg, header.ip_reg, header.relt_off, header.overlay); if(reloc != 0) { printf("\nСодержимое таблицы перемещений:\n\n"); for(i=0; i < header.rel_item; i++) { printf("%04X:%04X\n", (reloc+i)->segment, (reloc+i)->offset); } free(reloc); } fclose(inpfile); return(0); } // ------------------------------------------- // get_exeh // Прочитать заголовок exe-файла // // Функция читает заголовок exe-файла в структуру // типа exe_HDR, заказывает память для таблицы // размещений сегментов и считывает таблицу // в эту область. Адрес заказанной области // помещается по адресу, указанному через // параметр rtb. // Если таблица размещений отсутствует, память для // нее не заказывается. // // Параметры: // exeh - указатель на структуру, которая // должна быть заполнена информацией // из заголовка exe-файла // // rtb - указатель на слово памяти, в котором // хранится указатель на таблицу // размещений сегментов программы // // exe_file - указатель на открытый exe-файл // // Возвращаемое значение: // 0 при успешном считывании заголовка; // -1 в случае неправильного формата заголовка // ------------------------------------------- int get_exeh(EXE_HDR *exeh, RELOC_TAB **rtb, FILE *exe_file) { int i, j, k; // Считываем форматированную часть заголовка for(i=0; i < sizeof(EXE_HDR); i++) { *(((char*)exeh) + i) = fgetc(exe_file); if(feof(exe_file)) break; } // Убеждаемся, что это EXE-файл if(exeh->signature != 0x5a4d) return(-1); // Eсли есть таблица перемещений, // заказываем для нее память if((i = exeh->rel_item) != 0) { *rtb = (RELOC_TAB*)malloc(i * sizeof(RELOC_TAB) + 16); // Считываем таблицу перемещений for(k=0; k<i; k++) { for(j=0; j < sizeof(RELOC_TAB); j++) { *((char*)(*rtb)+j+k*sizeof(RELOC_TAB)) = fgetc(exe_file); if(feof(exe_file)) break; } } } else *rtb = (RELOC_TAB *)NULL; return(0); }
Загрузка com- и exe-программ происходит по-разному, однако есть некоторые действия, которые операционная система выполняет в обоих случаях одинаково:
А дальше действия системы по загрузке com- и exe-программ будут различаться.
Для com-программ, которые представляют собой двоичный образ односегментной программы, выполняется чтение файла программы с диска и запись его в память по адресу PSP :0100h.
Размер обычных com-программ, как мы уже говорили, не превышает 64 Кбайт, так как они состоят только из одного сегмента. Но, строго говоря, com-программы могут состоять и из нескольких сегментов. В этом случае они должны сами управлять содержимым сегментных регистров, используя в качестве базового адрес PSP .
В процессе загрузки com-программы операционная система выполняет следующие действия:
Загрузка exe-программы происходит значительно сложнее, так как связана с настройкой сегментных адресов:
size = ((file_size * 512) - (hdr_size * 16) - part_pag
START_OFF = hdr_size * 16;
START_SEG = <сегментный адрес PSP > + 10h;
- считывается содержимое элемента таблицы как два двухбайтных слова (OFF, SEG);
- вычисляется сегментный адрес ссылки перемещения по формуле:
REL_SEG = (START_SEG + SEG)
- выбирается слово по адресу REL_SEG:OFF, затем к этому слову прибавляется значение START_SEG, после чего сумма записывается обратно по тому же адресу
При инициализации регистры ES и DS устанавливаются на начало PSP , регистр AX устанавливается так же, как и для com-программ, в сегментный регистр стека SS записывается значение START_SEG + ss_reg, а в регистр SP записывается значение sp_reg.
Для передачи управления программе в сегментный регистр CS записывается значение START_SEG + cs_reg, а в регистр IP - значение ip_reg. Такая запись невозможна напрямую, поэтому операционная система сначала записывает в свой стек значение для CS, затем значение для IP и после этого выполняет команду дальнего возврата RETF (команда возврата из дальней процедуры).
Префикс программного сегмента всегда создается при загрузке программы в память и имеет следующий формат:
Программы могут получить из PSP такую информацию, как параметры командной строки при запуске, размер доступной памяти. Зная адрес PSP, легко найти сегмент области переменных среды и получить другую полезную информацию.
Формат блока PSP и описание назначения всех его полей приведены ниже:
Смещение, байт |
Размер, байт |
Имя поля |
Описание |
0 |
2 |
int20h |
Двоичный код команды INT 20h (программы
могут использовать эту команду для завершения
своей работы) |
2 |
2 |
mem_top |
Нижняя граница доступной памяти в
системе в параграфах |
4 |
1 |
reserv1 |
Зарезервировано |
5 |
5 |
call_dsp |
Команда CALL межсегментного вызова
диспетчера MS-DOS |
10 |
4 |
term_adr |
Адрес завершения (Terminate Address) |
14 |
4 |
cbrk_adr |
Адрес обработчика прерывания, который
получает управление, если пользователь нажал
комбинацию клавиш <Ctrl+Break> |
18 |
4 |
crit_err |
Адрес обработчика критической ошибки |
22 |
2 |
parn_psp |
Сегмент PSP программы, запустившей данную
программу (программы-родителя) |
24 |
20 |
file_tab |
Таблица открытых файлов; если в этом
поле находятся байты 0FFH, то таблица не
используется |
44 |
2 |
env_seg |
Сегмент блока памяти, содержащего
переменные среды |
46 |
4 |
ss_sp |
Адрес стека программы SS:SP |
50 |
2 |
max_open |
Максимальное количество открытых
файлов |
52 |
4 |
file_tba |
Адрес таблицы открытых файлов |
56 |
24 |
reserv2 |
Зарезервировано |
80 |
3 |
disp |
Диспетчер функций MS-DOS |
83 |
9 |
reserv3 |
Зарезервировано |
92 |
16 |
fcb1 |
Если первый аргумент командной строки
содержит правильное имя файла, поле
форматируется как стандартный блок FCB |
108 |
20 |
fcb2 |
Заполняется для второго аргумента
командной строки аналогично fcb1 |
128 |
1 |
p_size |
Количество значащих символов в
неформатированной области параметров, либо
буфер обмена с диском DTA , назначенный по
умолчанию |
129 |
127 |
parm |
Неформатированная область параметров,
заполняется при запуске программы из командной
строки |
Для обращения к полям PSP мы определим тип данных PSP:
typedef struct { unsigned char int20h[2]; unsigned mem_top; unsigned char reserv1; unsigned char call_dsp[5]; void far *term_adr; void far *cbrk_adr; void far *crit_err; unsigned parn_psp; unsigned char file_tab[20]; unsigned env_seg; void far *ss_sp; unsigned max_open; void far *file_tba; unsigned char reserv2[24]; unsigned char disp[3]; unsigned char reserv3[9]; unsigned char fcb1[16]; unsigned char fcb2[20]; unsigned char p_size; unsigned char parm[127]; } PSP ;
Используя поле parn_psp, можно определить адрес PSP родительской программы, то есть программы, запустившей вашу программу.
Поле term_adr содержит значение, полученное из таблицы векторов прерываний для прерывания INT 22h. Это адрес программы, которая получает управление, когда текущая программа завершает свою работу. Такой программой может быть, например, программа command.com .
Ваша программа может определить функцию, которая будет получать управление при завершении ее работы. Для этого она должна записать свой собственный адрес в ячейку таблицы векторов прерываний, соответствующую прерыванию INT 22h, а затем запустить другую программу. Поле term_adr блока PSP запущенной программы будет содержать адрес родительской программы. Когда основная программа завершит свою работу, MS-DOS восстановит адрес программы завершения в векторе прерывания INT 22h из поля term_adr блока PSP.
Поле cbrk_adr содержит адрес программы обработки прерывания, которое возникает, когда пользователь нажимает комбинацию клавиш <Ctrl+Break>. При запуске программы этот адрес переписывается из ячейки таблицы векторов прерываний, соответствующей прерыванию INT 23h .
Программа может устанавливать свою собственную функцию обработки прерывания по комбинации клавиш <Ctrl+Break>. Поэтому при завершении работы программы MS-DOS восстанавливает оригинальное значение из поля cbrk_adr.
Поле crit_err предназначено для восстановления содержимого вектора прерывания INT 24h (адреса обработчика критических ошибок).
Способы переназначения векторов будут приведены в главе, посвященной прерываниям .
Конечно, программы, составленные на языке С, не обязательно должны использовать PSP для доступа к параметрам командной строки и переменным среды. Для этого есть параметры функции main и набор функций типа getenv, putenv и т. п., предназначенных для работы со средой. Но блок PSP содержит и другую информацию, доступ к которой с помощью стандартных функций невозможен.
Как программе определить адрес своего PSP ?
Очень просто сделать это в программах, составленных на языке ассемблера: при запуске программы адрес PSP передается ей через регистры DS и ES. То есть этот адрес равен DS:0000 или ES:0000 (для com-программ на PSP указывают также регистры CS и SS).
Программам, составленным на языке С, доступна глобальная переменная _psp типа unsigned int. Эта переменная содержит сегментный адрес PSP .
В качестве примера приведем текст программы PARM (листинг 3.2), составленной на языке ассемблера, которая выводит на экран передаваемые ей через PSP параметры запуска.
Листинг 3.2. Файл parm\ parm.asm
.MODEL tiny DOSSEG .DATA parm_msg DB "Укажите параметры", 13, 10, "$" .CODE .STARTUP mov cl,ds:80h ; количество символов ; в командной строке cmp cl,0 je ask_parm ; нет параметров - просим ; указать параметры mov si,81h ; со смещением 81h ; начинается область параметров cld get_parm: ; Загружаем в al очередной символ строки параметров lods BYTE PTR es:[si] mov ah,2 ; выводим его на экран mov dl,al int 21h loop get_parm jmp end_progr ask_parm: mov ah, 9h mov dx, OFFSET parm_msg int 21h end_progr: .EXIT 0 END
Для трансляции и редактирования программы PARM вы можете использовать пакетный файл, приведенный в листинге 3.3.
Листинг 3.3. Файл parm\mk.bat
tasm parm tlink parm /t
Программа PSPLIST (листинг 3.4), составленная на языке С, определяет адрес своего блока PSP , затем показывает содержимое некоторых полей PSP.
Листинг 3.4. Файл psplist\psplist.cpp
#include <stdio.h> #include <stdlib.h> #include <dos.h> typedef struct { unsigned char int20h[2]; unsigned mem_top; unsigned char reserv1; unsigned char call_dsp[5]; void far *term_adr; void far *cbrk_adr; void far *crit_err; unsigned parn_psp; unsigned char file_tab[20]; unsigned env_seg; void far *ss_sp; unsigned max_open; void far *file_tba; unsigned char reserv2[24]; unsigned char disp[3]; unsigned char reserv3[9]; unsigned char fcb1[16]; unsigned char fcb2[20]; unsigned char p_size; unsigned char parm[127]; } PSP ; void main(void) { PSP far *psp_ptr; // Конструируем указатель на PSP psp_ptr = (PSP far *)MK_FP (_psp, 0); printf("PSP расположен по адресу: %Fp\n" "Доступно памяти, байт: %ld\n" "PSP родительской программы: %Fp\n" "\n", psp_ptr, (long)(psp_ptr->mem_top)*16L, MK_FP (psp_ptr->parn_psp, 0)); }
Ваша программа может при необходимости запустить другую exe- или com-программу.
Программа, составленная на языке ассемблера, запускает другую программу с помощью функции 4Bh прерывания INT 21h . Для выполнения той же задачи из программ, составленных на языке С, следует использовать разнообразные функции, входящие в состав стандартной библиотеки системы разработки.
Сначала рассмотрим процедуру запуска программы при помощи функции 4Bh прерывания INT 21h .
Перед вызовом прерывания вы должны загрузить регистры процессора следующим образом:
Регистр |
Содержимое |
AH |
4Bh |
AL |
Код подфункции (0, 1, 2, 3, 5) |
DS:DX |
Указатель на текстовую строку в формате
ASCIIZ , содержащую путь к запускаемой программе |
ES:BX |
Указатель на блок параметров EPB |
После возврата из прерывания флаг переноса CF устанавливается в 0, если ошибок не было, и в 1 - при обнаружении ошибок. Если произошла ошибка, ее код записывается в регистр AX:
Код ошибки |
Описание |
1 |
Неверный код подфункции |
2 |
Не найден файл запускаемой программы |
3 |
Указанный путь не найден |
4 |
Слишком много открытых файлов |
5 |
Нет доступа |
8 |
Нет памяти для загрузки программы |
10 |
Длина блока среды больше 32 Кбайт |
11 |
Неправильный формат запускаемого
exe-файла |
Функция 4Bh прерывания INT 21h имеет несколько
подфункций:
Код |
Описание |
0 |
Загрузить и выполнить программу |
1 |
Загрузить, но не выполнять программу |
2 |
Загрузить, но не выполнять программу
(недокументированная подфункция) |
3 |
Загрузить программу как оверлей (не
создавая при этом блок PSP ) |
5 |
Подготовить программу для выполнения |
Опишем эти подфункции более подробно.
Для функции 0 регистры DS:DX должны указывать на
полный путь запускаемой программы в формате ASCIIZ
(т. е. на текстовую строку, закрытую двоичным
нулем). Блок параметров EPB (Exec Parameter Block ) в этом
случае имеет следующий формат:
Смещение, байт |
Размер, байт |
Имя поля |
Описание |
0 |
2 |
seg_env |
Сегментный адрес среды, которая
создается родительской программой для
запускаемой программы. Если в этом поле
находится 0, то для запускаемой программы
копируется среда родительской программы |
2 |
4 |
cmd |
Дальний адрес строки параметров для
запускаемой программы. Эта строка должна иметь
такой же формат, как и в PSP , т. е. в начале строки
находится байт со значением, равным количеству
символов в строке параметров, а затем - сама
строка параметров |
6 |
4 |
fcb1 |
Адрес блока FCB . Этот адрес будет записан
в блок PSP со смещением 5Ch |
10 |
4 |
fcb2 |
Адрес блока FCB . Этот адрес будет записан
в блок PSP со смещением 6Ch |
Запущенной программе доступны все файлы, открытые родительской программой.
Если родительская программа сама формирует среду для дочерней программы, она должна подготовить новую среду на границе параграфа и поместить значение сегментного адреса в поле seg_env блока EPB .
Для примера приведем исходный текст простой программы SPARM (листинг 3.5), которая запускает программу с именем parm.com из текущего каталога. Программу parm.com мы уже рассматривали (листинг 3.2). Эта программа выводит на экран параметры, полученные ей при запуске через командную строку.
Листинг 3.5. Файл sparm\sparm.asm
.MODEL tiny DOSSEG .DATA path db "PARM.COM",0 command_line db 11,"Parm1 Parm2" epb dw 0 cmd_off dw ? cmd_seg dw ? fcb1 dd ? fcb2 dd ? .CODE .STARTUP ; ; Освобождаем лишнюю память за концом программы ; mov bx, OFFSET last ; смещение конца программы mov cl,4 ; вычисляем длину программы в параграфах shr bx,cl add bx,17 ; добавляем 1 параграф для ; выравнивания и 256 байт для стека mov ah, 4Ah ; изменяем размер выделенного int 21h ; блока памяти mov ax,bx ; устанавливаем новое значение shl ax,cl ; для указателя стека dec ax mov sp,ax mov bx,OFFSET command_line ; адрес командной mov cmd_off,bx ; строки для блока EPB mov cmd_seg,ds mov ax,ds mov es,ax mov bx, OFFSET epb ; ES:BX указывают на EPB mov dx, OFFSET path ; DS:DX указывают на путь ; к файлу запускаемой программы mov ax, 4B00h ; AH = 4Bh ; AL = 0 загрузить и выполнить int 21h .EXIT 0 last: db ? END
Программа SPARM освобождает всю неиспользуемую ей память, после чего на освободившееся место загружает программу parm.com. Такая процедура необходима потому, что MS-DOS выделяет всю имеющуюся память в распоряжение запускаемой com-программы. Поэтому при попытке запустить программу без предварительного освобождения части памяти функция 4Bh вернет код ошибки 8 (нет памяти для загрузки программы).
Для изменения размера блока памяти, выделенного программе, мы использовали функцию 4Ah прерывания INT 21h .
Подфункции 1 и 2 прерывания INT 4Bh используются операционной системой MS-DOS для собственных нужд (это внутренние подфункции MS-DOS). Они также необходимы для создания программ-отладчиков, таких как, например, debug.com или td.exe. Мы приведем недокументированный формат блока EBP для этих функций.
Для подфункции 1 блок EBP имеет следующий формат:
Смещение, байт |
Размер, байт |
Имя поля |
Описание |
0 |
2 |
seg_env |
Сегментный адрес среды, которая
создается родительской программой для
запускаемой программы. Если в этом поле
находится 0, то для запускаемой программы
копируется среда родительской программы |
2 |
4 |
cmd |
Дальний адрес строки параметров для
запускаемой программы |
6 |
4 |
fcb1 |
Адрес блока FCB . Этот адрес будет записан
в блок PSP со смещением 5Ch |
10 |
4 |
fcb2 |
Адрес блока FCB . Этот адрес будет записан
в блок PSP со смещением 6Ch |
14 |
4 |
ss_sp |
В это поле после завершения работы
запускаемой программы будет записано содержимое
регистров SS:SP |
18 |
4 |
entry_p |
Адрес точки входа в загруженную
программу, который нужно записать в регистры CS:IP
при запуске программы |
Формат блока EPB для подфункции 2:
Смещение, байт |
Размер, байт |
Имя поля |
Описание |
0 |
2 |
seg_env |
Сегментный адрес среды, которая
создается родительской программой для
запускаемой программы. Если в этом поле
находится 0, то для запускаемой программы
копируется среда родительской программы |
2 |
4 |
cmd |
Дальний адрес строки параметров для
запускаемой программы |
6 |
4 |
fcb1 |
Адрес блока FCB . Этот адрес будет записан
в блок PSP со смещением 5Ch |
10 |
4 |
fcb2 |
Адрес блока FCB . Этот адрес будет записан
в блок PSP со смещением 6Ch |
Подфункция 3 используется для загрузки программных оверлеев . Оверлей загружается в адресное пространство родительской программы, поэтому MS-DOS не заказывает дополнительной памяти и не строит PSP . Формат EPB для этой подфункции:
Смещение, байт |
Размер, байт |
Имя поля |
Описание |
0 |
2 |
seg_env |
Сегментный адрес, по которому
загружается программа |
2 |
4 |
reloc |
Фактор перемещения. Для exe-программ
обычно содержит то же значение, что и поле seg_env,
для com-программ в этом поле находится значение 0 |
Следующий фрагмент программы загружает программу parm.com как оверлей без передачи ей управления (программа загружается в буфер buff):
.DATA path db "PARM.COM",0 epb dw 0 reloc dd 0 .CODE .STARTUP mov ax,ds mov es,ax mov bx,SEG buff mov epb,bx mov bx,OFFSET epb ; ES:BX указывают на EPB mov dx,OFFSET path ; DS:DX указывают на путь ; загружаемой программы mov ax, 4B03h ; AH = 4Bh ; AL = 0 загрузить оверлей int 21h ; ; Работа с загруженной программой ........ ; .EXIT 0 buff: dd 100 dup(?) END
Подфункция 5 используется для загрузки и предварительной подготовки программы к выполнению . Она впервые появилась в MS-DOS версии 5.0. Вы можете использовать ее вместо недокументированных подфункций 1 и 2.
Если программа, запущенная с помощью подфункции 5, попытается определить версию MS-DOS, ей будет предоставлен не истинный номер версии, а определенный с учетом действия драйвера setver.
Напомним, что с помощью драйвера setver MS-DOS может "обмануть" программу, сообщив ей, что работает MS-DOS, например, версии 3.31 или любой другой версии, указанной пользователем. Такая возможность требуется в тех случаях, когда программа была рассчитана на конкретную версию MS-DOS, но, тем не менее, способна работать и в новой версии.
Для подфункции 5 указатель, расположенный в регистрах DS:DX, должен указывать на структуру EXECSTATE, описанную ниже:
Смещение, байт |
Размер, байт |
Имя поля |
Описание |
0 |
2 |
Reserved |
Зарезервировано |
2 |
2 |
Flags |
Тип программы: 0 - com-программа, 1 -
exe-программа, 2 - оверлей |
4 |
4 |
ProgName |
Указатель на текстовую строку ASCIIZ ,
содержащую имя программы |
8 |
2 |
PSP |
Сегмент блока PSP новой программы |
10 |
4 |
StartAddr |
Стартовый адрес CS:IP новой программы |
14 |
4 |
ProgSize |
Общий размер программы с учетом размера
блока PSP |
Пользователи языка С имеют в своем распоряжении несколько возможностей запустить программу.
Самый простой способ - использовать функцию system . Эта функция может выполнить любую команду MS-DOS или любую программу, а также пакетный файл. Например:
system("FORMAT A:");
При использовании этой функции должен быть доступен файл command.com .
К сожалению, хотя функция system и возвращает код завершения, по нему нельзя сделать вывод о том, как была выполнена запускаемая программа. Если в качестве аргумента функции будет передано имя несуществующей программы, на экране появится сообщение:
Bad command or file name
Код возврата в этом случае будет 0 - как будто все хорошо!
Другая возможности запустить программу - использовать функции spawn и exec.
Функция spawn и ее разновидности запускают программу как дочерний процесс. Функция exec загружает новую программу как оверлей на место старой и передает ей управление без возврата. После завершения дочерней программы управление будет передано программе command.com или программе, которая запустила родительскую программу.
Семейство функций spawn обеспечивает запуск дочерней программы, передавая ей родительскую или с специально сформированную среду. Кроме того, в файле process.h описаны параметры, которые можно передать функции spawn:
Параметр |
Описание |
P_WAIT |
Выполнение родительской программы
задерживается до завершения дочерней программы |
P_NOWAIT |
Родительская программа продолжает
выполнение сразу после запуска дочерней
программы. Этот параметр имеет смысл только для
мультизадачных операционных систем |
P_OVERLAY |
Загружает программу как оверлей и
передает ей управление. Этот режим соответствует
функции exec в том смысле, что родительская
программа не получит управления после
завершения дочерней |
Ниже мы привели исходный текст программы SPARM1 (листинг 3.6), которая запускает программу parm.com с помощью функции spawnlpe. Эта функция входит в стандартную библиотеку Borland C++ и позволяет не только запустить программу, но и сформировать для нее среду, а также передать параметры.
Листинг 3.6. Файл sparm1\sparm1.cpp
#include <stdio.h> #include <conio.h> #include <process.h> int main(void) { char *env[] = { "PARMVAR=d:\\VARS", NULL }; int rc; rc = spawnlpe(P_WAIT ,"parm","parm", "Parm1", "Parm2", NULL, env); if(rc == -1) printf("Невозможно запустить процесс"); else printf("\nПроцесс завершен"); return rc; }
Старые версии MS-DOS (до 2.0) требовали выполнения достаточно сложной процедуры для завершения программы. В начале работы программы было нужно сохранить адрес PSP , затем перед завершением работы поместить этот адрес в стек, записать туда же слово 0000h и выполнить команду дальнего возврата. Управление при этом передается в начало PSP, где находится команда INT 20h .
Для версий MS-DOS, начиная с 2.0, существуют более удобные способы.
С помощью прерывания INT 20h или функции 0 прерывания INT 21h обычно завершают свою работу com-программы. Учтите, что перед завершением работы программы регистр CS должен указывать на PSP .
Более удобна функция 4Ch прерывания INT 21h которую можно использовать с любым содержимым регистров.
Последний способ рекомендуется для повсеместного использования. Он позволяет передать родительской программе (например, программе command.com ) код завершения. Этот код доступен для анализа в пакетных файлах командой if errorlevel.
Примеры программ на языке ассемблера, приведенные в нашей книге, содержат директиву .EXIT . Эта директива завершает выполнение программы с помощью функции 4Ch и позволяет передать код завершения.
Если ваша программа запустила дочернюю программу и та завершилась, передав код возврата, то родительская программа может определить этот код с помощью функции 4Dh прерывания INT 21h . Эта функция возвращает код в регистре AX.
Программа, написанная на языке С, может завершаться с помощью оператора return в функции main или с помощью функции exit в любом месте программы. При этом также возможна передача кода возврата.
Существуют еще способы завершения работы программы, при которых программа (или ее часть) остается резидентной в памяти. Это вызов прерывания INT 27h или функции 31h прерывания INT 21h . О таком способе будет подробно рассказано в главе, посвященной резидентным программам.