3. Управление программами

Error message

Notice: Undefined index: add term path in hansel_get_breadcrumbs() (line 524 of /data/sites/msdosworld.ru/www/sites/all/modules/hansel/hansel.module).

3.1. Форматы программных файлов

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

Оператор может запустить два типа программ (если не считать командных файлов, которые, вообще говоря, не являются программами, состоящими из машинных кодов) - программы, имеющие расширение имени .COM и .EXE. Эти файлы имеют различный формат и загружаются по-разному, однако, когда загрузка завершена, в памяти компьютера эти два типа программ выглядят совершенно одинаково.

COM-файл - это двоичный образ Вашей программы, состоящий из кода и данных. То есть это файл, содержащий программу в "чистом" виде. Такая программа (как и EXE-программа) может загружаться в любое место памяти. DOS выполняет ее привязку к физическим адресам при загрузке с помощью установки сегментных регистров. Существенным ограничением COM-программы является то, что она не может занимать больше одного сегмента (соответственно, файл .COM не может быть по длине больше 64К).

Программа в формате EXE может иметь любой размер. В самом начале файла программы содержится заголовок (у COM-файла заголовка нет). Этот заголовок используется операционной системой в процессе загрузки программы в память для правильной установки сегментных регистров. Заголовок EXE-файла нужен только при загрузке; когда программа загружена и готова к работе, самого заголовка уже нет в памяти.

Заголовок EXE-файла состоит из форматированной зоны и таблицы расположения сегментов (Relocation Table). Форматированная зона выглядит следующим образом:

 

(0) 2 signatureдва байта 'MZ' (4Dh, 5Ah), индентифицирующие файл в формате EXE
(+2) 2part_pagдлина последней страницы программы в байтах (страница содержит 512 байт)
(+4) 2file_sizeразмер программы в страницах по 512 байт
(+6) 2rel_itemчисло элементов в таблице расположения сегментов
(+8) 2hdr_sizeразмер заголовка файла в параграфах (длина параграфа - 16 байт)
(+10) 2min_memминимальное количество памяти в параграфах, которое нужно зарезервировать в памяти за концом загруженной программы
(+12) 2max_memмаксимальное количество памяти в параграфах, которое нужно зарезервировать в памяти за концом загруженной программы
(+14) 2ss_regвеличина смещения от начала программы, которая используется для загрузки сегментного регистра стека SS
(+16) 2sp_regвеличина смещения от начала программы, которая используется для загрузки регистра SP
(+18) 2chk_summконтрольная сумма всех слов в файле
(+20) 2ip_regзначение для регистра IP, которое будет использовано при начальном запуске программы
(+22) 2cs_regсмещение от начала программы для установки сегментного регистра кода CS
(+24) 2relt_offсмещение от начала файла таблицы расположения сегментов программы
(+26) 2overlayномер оверлея, равен 0 для основного модуля

Таблица расположения сегментов программы начинается сразу после форматированной области и состоит из четырехбайтовых значений в формате "смещение:сегмент".

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

В файле sysp.h есть описание заголовка файла и таблицы расположения сегментов, которые вы можете использовать при обработке заголовка EXE-файла:

typedef struct _EXE_HDR_ {
        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 _RELOC_TAB_ {
        unsigned offset;
        unsigned segment;
} RELOC_TAB;

В качестве примера приведем программу, которая считывает форматированную часть заголовка EXE-файла, проверяет наличие в его первых двух байтах признака EXE-формата ('MZ'). Если признак имеется, программа выводит на экран расшифрованное содержимое заголовка и таблицу перемещений, если такая таблица присутствует. В качестве параметра программе надо при запуске передать имя исследуемого EXE-файла.

#include <stdio.h>
#include <stdlib.h>
#include "sysp.h"

void main(int, char *[]);

void main(int argc, char *argv[]) {

        printf("Распечатка заголовка EXE-файла\n"
                  "Copyright (C)Frolov A., 1990\n\n");

        if( argc != 2 ) {
           printf( "  Задайте путь EXE-файла в качестве"
                      "параметра\n" );
           exit(0);
        }

        if( gethdr( argv[1]) != 0) {
                printf( "Ошибка в формате файла или нет такого"
                           "файла\n" );
                exit(0);
        }
        exit(0);
}

int gethdr( 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);
}

Приведенная выше программа для чтения заголовка EXE-файла пользуется функцией get-exeh:

/**
*.Name      get_exeh
*
*.Title     Прочитать заголовок EXE-файла
*
*.Descr     Функция читает заголовок EXE-файла в
*           структуру типа EXE_HDR, заказывает память
*           для таблицы размещений сегментов и считывает
*           таблицу в эту область. Адрес заказанной области
*           помещается по адресу, передаваемому в rtb.
*           Если таблица размещений отсутствует, память для
*           нее не заказывается.
*
*.Params    int get_exeh(EXE_HDR *exeh,RELOC_TAB **rtb,
*                                       FILE *exe_file)
*
*              exeh -  указатель на структуру, которая
*                      должна быть заполнена информацией
*                      из заголовка EXE-файла
*
*              rtb  -  указатель на указатель на таблицу
*                      размещений сегментов программы
*
*              exe_file - указатель на открытый EXE-файл
*                      (до вызова функции нельзя обращаться
*                      к этому файлу, т.к. считается, что
*                      указатель текущего смещения
*                                  установлен на начало файла)
*
*.Return    0 при успешном считывании заголовка;
*           -1 в случае неправильного формата заголовка
**/

#include <stdlib.h>
#include <stdio.h>
#include "sysp.h"

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);

        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 *)0;

        return(0);}

3.2. Процесс загрузки программ в память

Загрузка COM- и EXE-программ происходит по-разному, однако есть некоторые действия, которые операционная система выполняет в обоих случаях одинаково.

  • Определяется наименьший сегментный адрес свободного участка памяти для загрузки программы (обычно DOS загружает программу в младшие адреса памяти, если при редактировании не указана загрузка в старшие адреса).
  • Создаются два блока памяти (и, следовательно, два блока MCB, описанные ранее) - блок памяти для переменных среды и блок памяти для PSP и программы.
  • Для DOS версии 3.х и старше в блок памяти переменных среды помещается путь файла программы.
  • Заполняются поля префикса сегмента программы PSP в соответствии с характеристиками программы (количество памяти, доступное программе, адрес сегмента блока памяти, содержащего переменные среды и т.д.)
  • Устанавливается адрес области Disk Transfer Area (DTA) на вторую половину PSP (PSP:0080).
  • Анализируются параметры запуска программы на предмет наличия в первых двух параметрах идентификаторов дисковых устройств. По результатам анализа устанавливается содержимое регистра AX при входе в программу. Если первый или второй параметры не содержат правильного идентификатора дискового устройства, то соответственно в регистры AL и AH записывается значение FF.

А дальше действия системы по загрузке программ форматов COM и EXE будут различаться.

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

После загрузки файла операционная система для COM-программ выполняет следующие действия:

  • сегментные регистры CS, DS, ES, SS устанавливаются на начало PSP;
  • регистр SP устанавливается на конец сегмента PSP;
  • вся область памяти после PSP распределяется программе;
  • в стек записывается слово 0000;

указатель команд IP устанавливается на 100h (начало программы) с помощью команды JMP по адресу PSP:100.

Загрузка EXE-программ происходит значительно сложнее, так как связана с настройкой сегментных адресов:

  • Считывается во внутренний буфер DOS форматированная часть заголовка файла.
  • Определяется размер загрузочного модуля по формуле:
size=((file_size*512)-(hdr_size*16)-part_pag
  • Определяется смещение начала загрузочного модуля в EXE-файле как hdr_size*16.
  • Вычисляется сегментный адрес для загрузки START_SEG, обычно используется значение PSP+10h.
  • Загрузочный модуль считывается в память по адресу START_SEG:0000.
  • Сканируются элементы таблицы перемещений, располагающейся в EXE-файле со смещением relt_off.
  • Для каждого элемента таблицы:

1. Считывается содержимое элемента таблицы как два двухбайтных слова (OFF,SEG).

2. Вычисляется сегментный адрес ссылки перемещения

REL_SEG = (START_SEG + SEG)

3. Выбирается слово по адресу REL_SEG:OFF, к этому слову прибавляется значение START_SEG, затем сумма записывается обратно по тому же адресу.

  • Заказывается память для программы, исходя из значений min_mem и max_mem.
  • Инициализируются регистры, и программа запускается на выполнение.

При инициализации регистры ES и DS устанавливаются на PSP, регистр AX устанавливается так же, как и для COM-программ, в сегментный регистр стека SS записывается значение START_SEG + ss_reg, а в SP записывается sp_reg.

Для запуска программы в CS записывается START_SEG+cs_reg, а в IP - ip_reg. Такая запись невозможна напрямую, поэтому операционная система сначала записывает в свой стек значение для CS, затем значение для IP и после этого выполняет команду дальнего возврата RETF (команда возврата из дальней процедуры).

3.3. Префикс программного сегмента

Теперь займемся вплотную префиксом программного сегмента PSP. Формат PSP уже был описан ранее, для удобства приведем его еще раз вместе со структурой из файла sysp.h:

 

(0) 2 int 20hдвоичный код команды int 20h (программы могут использовать эту команду для завершения своей работы)
(+2) 2mem_topнижняя граница доступной памяти в системе в параграфах
(+4) 1reserv1зарезервировано
(+5) 5call_dspкоманда вызова FAR CALL диспетчера MS-DOS
(+10) 4term_adrадрес завершения (Terminate Address)
(+14) 4cbrk_adrадрес обработчика Ctrl-Break
(+18) 4crit_errадрес обработчика критической ошибки
(+22) 2parn_pspсегмент PSP программы, запустившей данную программу (программы-родителя)
(+24) 20file_tabтаблица открытых файлов, если здесь находятся байты 0FFH, то таблица не используется
(+44) 2env_segсегмент блока памяти, содержащего переменные среды
(+46) 4ss_spадрес стека SS:SP программы
(+50) 2max_openмаксимальное число открытых файлов
(+52) 4file_tbaадрес таблицы открытых файлов
(+56) 24reserv2зарезервировано
(+80) 3dispдиспетчер функций DOS
(+83) 9reserv3зарезервировано
(+92) 16fcb1форматируется как стандартный FCB, если первый аргумент командной строки содержит правильное имя файла
(+108) 20fcb2заполняется для второго аргумента командной строки аналогично fcb1
(+128) 1p_sizeчисло значащих символов в неформатированной области параметров, либо буфер обмена с диском DTA, назначенный по умолчанию
(+129) 127parmнеформатированная область параметров, заполняется при запуске программы из командной строки
#pragma pack(1)

typedef struct _PSP_ {
        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;

#pragma pack()

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

Как программе узнать адрес своего PSP? Очень просто сделать это для программ, написанных на языке ассемблера: при запуске программы этот адрес передается ей через регистры DS и ES. То есть этот адрес равен DS:0000 или ES:0000 (для COM-программ на PSP указывают также регистры CS и SS).

Для программ, составленных на языке Си, доступна глобальная переменная _psp типа unsigned. Эта переменная содержит сегментный адрес PSP.

В качестве примера приведем текст программы на языке ассемблера, которая выводит на экран передаваемые ей через PSP параметры запуска:

                  .MODEL  tiny
                  DOSSEG

                  .STACK  100h

                  .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:

                  lods   BYTE PTR es:[si]  ; загружаем в al
                                           ;   очередной
                                           ;   символ строки
                                           ;   параметров

                  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

Приведенная ниже программа, составленная на языке Си, определяет адрес своего PSP, затем показывает содержимое некоторых полей из PSP:

#include <stdio.h>
#include <stdlib.h>
#include <dos.h>
#include "sysp.h"

void main(void);

void main(void) {

  PSP far *psp_ptr;


  psp_ptr = FP_MAKE(_psp,0);  // Конструируем указатель
                                                //  на PSP
  printf("PSP расположено по адресу:    %Fp\n"
                        "Доступно памяти, байт:       %ld\n"
                        "PSP родительской программы:  %Fp\n"
                        "\n",
                        psp_ptr,
                        (long)(psp_ptr->mem_top)*16L,
                        FP_MAKE(psp_ptr->parn_psp,0));
  exit(0);
}

Используя поле parn_psp, можно определить адрес PSP родительской программы, то есть программы, запустившей Вашу программу.

Немного о назначении полей term_adr, cbrk_adr, crit_err.

Поле term_adr содержит значение, полученное из таблицы векторов прерываний для вектора 22h. Это адрес программы, которая получает управление, когда текущая программа завершает свою работу. Это может быть, например, COMMAND.COM. Программа может создать свою собственную подпрограмму, которая будет получать управление при завершении работы основной программы. Она может записать свой собственный адрес в вектор 22h, затем запустить другую программу. В таком случае в запущенной программе это поле в ее PSP будет содержать адрес родительской программы. Когда основная программа завершает свою работу, DOS восстанавливает адрес программы завершения в векторе 22h из поля term_adr PSP.

Поле cbrk_adr содержит адрес программы обработки прерывания по нажатию Ctrl-Break из вектора 23h таблицы векторов прерываний. Так как программа может устанавливать свою собственную программу обработки прерывания по Ctrl-Break, DOS при завершении работы программы восстанавливает оригинальное значение из поля cbrk_adr.

Аналогично поле crit_err предназначено для восстановления содержимого вектора 24h - адреса обработчика критических ошибок.

Способы переназначения векторов будут приведены в разделе, посвященном прерываниям.

Конечно, программы, составленные на языке Си, не обязательно должны использовать PSP для доступа к параметрам командной строки и переменным среды. Для этого есть параметры функции main и набор функций типа getenv, putenv и т.п., предназначенных для работы со средой. Но ведь PSP содержит и другую информацию!

3.4. Запуск программ из программ

Ваша программа может при необходимости запустить другую программу формата EXE или COM. Для ассемблерных программ существует функция 4Bh прерывания INT 21h, для программ, составленных на языке Си - разнообразные функции, входящие в состав стандартной библиотеки. Сначала рассмотрим запуск программ при помощи функции 4Bh прерывания INT 21h.

Содержимое регистров перед вызовом прерывания:

AH = 4BH
AL - код подфункции (0, 1, 2, 3)
DS:DX - указатель на путь к запускаемой программе
ES:BX - указатель на блок параметров EPB

После возврата из прерывания флаг CF устанавливается в 0, если ошибок не было, и в 1 при обнаружении ошибок. Регистр AX в случае наличия ошибок содержит код ошибки:

 

1неверный код подфункции;
2файл запускаемой программы не найден;
3путь не найден;
4слишком много открытых файлов;
5нет доступа;
8нет памяти для загрузки программы;
10длина блока среды больше 32 килобайт;
11плохой формат запускаемого EXE-файла.

Функция 4Bh прерывания 21h имеет четыре подфункции с номерами от 0 до 3:

 

0загрузить и выполнить программу;
1загрузить, но не выполнять программу (внутренняя подфункция для DOS 3.х);
2загрузить, но не выполнять программу (внутренняя подфункция для DOS 2.х);
3загрузить программу как оверлей (не создавать PSP).

Для функции 0 регистры DS:DX должны указывать на полный путь запускаемой программы в формате ASCIIZ ( т.е. текстовая строка, закрытая двоичным нулем). Блок параметров EPB (Exec Parameter Block) в этом случае имеет следующий формат:

 

(0) 2seg_envсегментный адрес среды, которая создается родительской программой для запускаемой программы. Если в этом поле находится 0, то для запускаемой программы копируется среда родительской программы
(+2) 4cmdFAR-адрес строки параметров для запускаемой программы. Эта строка должна иметь такой же формат, как и в PSP, т.е. вначале идет байт со значением, равным количеству символов в строке параметров, а затем - сама строка параметров
(+6) 4fcb1адрес блока FCB, который будет помещен в PSP со смещением 5Ch (в PSP помещается блок, а не адрес!)
(+10) 4fcb2адрес блока FCB, который будет помещен в PSP со смещением 6Ch.

Запущенной программе доступны все файлы, открытые родительской программой.

Если родительская программа сама формирует среду для дочерней программы, она должна подготовить новую среду на границе параграфа и поместить значение сегментного адреса в поле seg_env блока EPB.

Приведем простую программу, которая запускает программу с именем PARM.COM из текущего каталога. Программу PARM.COM мы только что рассматривали, эта программа выводит на экран полученные ей в командной строке параметры.

                  .MODEL  small
                  DOSSEG

                  .STACK  100h

                  .DATA

path         db "PARM.COM",0
command_line db 8,"Parm Str"

epb      dw 0
cmd_off  dw ?
cmd_seg  dw ?
fcb1     dd ?
fcb2     dd ?

                  .CODE
                  .STARTUP

                  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

                  END

Эта программа использует модель памяти SMALL, и ее загрузочный модуль имеет формат EXE. При редактировании был указан стандартный для Quick C 2.01 размер памяти, требуемый для программы. Если попытаться использовать формат COM в модели TINY, то окажется, что вся память распределена COM-программе и для дочерней программы не осталось места.

Следующая программа освобождает всю неиспользуемую ей память, после чего на освободившееся место загружает программу PARM.COM:

                  .MODEL  tiny
                  DOSSEG

                  .STACK  100h

                  .DATA

path         db "PARM.COM",0
command_line db 8,"Parm Str"

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

Для изменения размера выделенного программе блока памяти мы использовали функцию 4Ah прерывания 21h.

Подфункции 1 и 2 прерывания 4Bh используются DOS (это внутренние подфункции DOS). Мы приведем недокументированный формат блока EBP для этих функций.

Для подфункнкции 1:

 

(0) 2seg_envсегментный адрес среды, которая создается родительской программой для запускаемой программы. Если в этом поле находится 0, то для запускаемой программы копируется среда родительской программы
(+2) 4cmdFAR-адрес строки параметров для запускаемой программы.
(+6) 4fcb1адрес блока FCB, который будет помещен в PSP со смещением 5Ch
(+10) 4fcb2адрес блока FCB, который будет помещен в PSP со смещением 6Ch.
(+14) 4ss_spэто поле будет содержать значение SS:SP после возврата
(+18) 4entry_pадрес точки входа в загруженную программу (CS:IP)

Для подфункции 2:

 

(0) 2seg_envсегментный адрес среды, которая создается родительской программой для запускаемой программы. Если в этом поле находится 0, то для запускаемой программы копируется среда родительской программы
(+2) 4cmdFAR-адрес строки параметров для запускаемой программы.
(+6) 4fcb1адрес блока FCB, который будет помещен в PSP со смещением 5Ch
(+10) 4fcb2адрес блока FCB, который будет помещен в PSP со смещением 6Ch.

Подфункция 3 используется для загрузки программных оверлеев. Оверлей загружается в адресное пространство родительской программы, поэтому DOS не заказывает дополнительной памяти и не строит PSP. Формат EPB для этой подфункции:

 

(0) 2seg_envсегментный адрес, по которому загружается программа
(+2) 4relocфактор перемещения, аналогичен элементу таблицы перемещений в заголовке EXE-файла

Следующая демонстрационная программа загружает программу PARM.COM_как оверлей без передачи ей управления:

                  .MODEL  small
                  DOSSEG

                  .STACK  100h

          .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

Программа загружается в буфер buff.

Пользователи языка Си имеют в своем распоряжении три возможности запустить программу.

Самый простой способ - использовать функцию system(). Эта функция может выполнить любую команду 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родительская программа продолжает выполнение сразу после запуска дочерней. Этот параметр имеет смысл только для операционных систем OS/2, UNIX, в которых поддерживается мультизадачность.
P_OVERLAYзагружает программу как оверлей и передает ей управление. Этот режим соответствует функции exec в том смысле, что родительская программа не получит управления после завершения дочерней.

В качестве примера использования функций запуска программы рассмотрим возможное решение проблемы создания HELP-системы для прикладной программы.

С помощью текстового редактора можно создать справочную базу данных в формате утилиты Microsoft HELPMAKE, затем, запуская в нужный момент диалоговую утилиту работы с базой данных Microsoft Quick Help QH.EXE, можно получить нужную справку.

Утилита QH использует базы данных, описанные в переменной среды HELPFILES. Мы будем использовать либо родительскую среду, где находится значение переменной HELPFILES по умолчанию, либо указывать новое значение для этой переменной.

Приведенная ниже программа используется для получения справки о функции стандартной библиотеки printf, поиск производится в HELP-базе QuickC:

#include <stdio.h>
#include <conio.h>
#include <process.h>

main()
{
         int r;

         // Получаем справку о функции printf,
         //  справочная база данных расположена
         //  в каталоге d:\qc2\bin

         r = help("HELPFILES=d:\\qc2\\bin;","printf");

         if( r == -1 )
                  printf( "Невозможно запустить процесс" );
         else
                  printf( "\nПроцесс завершен" );
         exit(r);
}


/**
*.Name      help
*
*.Title     Получить справку по заданному контексту
*
*.Descr     Функция получает в качестве параметров
*           переменную среды, указывающую на путь
*           к справочной базе данных и указатель
*           на строку контекста для поиска в базе.
*           Затем запускается как дочерний процесс
*           утилита Microsoft Quick Help QH.EXE, для
*           которой формируются среда и параметры.
*
*.Params    int help(char *help_file, char *help_topic);
*
*              help_file  - переменная среды, указывающая
*                           на путь к справочной базе
*
*              help_topic - контекст для поиска в базе
*
*
*.Return    0 при успешном запуске процесса
*           -1 не удалось запустить процесс
**/


int help(char *help_file, char *help_topic) {

         char *env[] = { "", NULL }; // Среда, которую
                                                    // получит QH при запуске

         if(*help_file != 0) {
                env[0] = help_file; // Формируем среду для QH

                // Запускаем утилиту

                return(spawnlpe(P_WAIT,"QH","QH",
                         "-u",help_topic,NULL,env));
         }
         else {

                // Если переменная среды не задана,
                //   используем родительскую среду

                return(spawnlp(P_WAIT,"QH","QH",
                         "-u",help_topic,NULL));
         
        }
}

Подробная информация об использовании утилит HELPMAKE и QH приводится в документации на Microsoft C 6.0.

3.5. Завершение работы программы

Старые версии DOS (до 2.0) требовали выполнения достаточно сложной процедуры для завершения программы. В начале работы программы необходимо было сохранить адрес PSP, затем, перед завершением работы поместить этот адрес в стек, поместить туда же слово 0000 и выполнить команду дальнего возврата. Управление при этом передается в начало PSP, где находится команда INT 20h.

Для версий DOS, начиная с 2.0, существуют более удобные способы - использование напрямую команды INT 20h или функции 0 прерывания 21h (CS при этом должен указывать на PSP, поэтому этот способ хорош для COM-программ), или функции 4Ch прерывания 21h в любое время и с любым содержимым регистров.

Последний способ рекомендуется для использования и имеет еще то преимущество, что позволяет передать родительской программе (например, COMMAND.COM) код завершения. Этот код доступен для анализа в пакетных файлах командой IF ERRORLEVEL.

Приведенные в книге примеры программ на языке ассемблера содержат директиву .EXIT. Эта директива завершает выполнение программы с помощью функции 4Ch и позволяет передать код завершения.

Если Ваша программа запустила дочернюю программу и та завершилась с передачей кода возврата, то родительская программа может определить этот код с помощью функции 4Dh прерывания 21h. Эта функция возвращает код в регистре AX.

Программа, написанная на языке Си, может завершаться с помощью return в функции main или с помощью exit в любом месте программы. При этом также возможна передача кода возврата.

Существуют еще способы завершения работы программы, при которых программа (или ее часть) остается резидентной в памяти. Это вызов прерывания INT 27H или функции 31h прерывания INT 21h. Об этом будет подробно рассказано в разделе, посвященном резидентным программам.

Вы находитесь в разделе: 

Добавить коментарий