5.4   Запуск програми-нащадка з поверненням у предок

Дуже корисними при побудові програм із послідовною динамічною структурою є бібліотечні функції Turbo С spawnxxx(). У цілому вони схожі на функції ехесххх(). Різниця між ними полягає в тому, що spawnxxx() дозволяють вибрати метод завантаження образу нащадка: при використанні spawnxxx() можна завантажувати нащадок як із перекриттям образу предка, так і зі збереженням його, що уможливлює повернення в програму-батько.

#include <process.h>
int spawnl( int mode, char *path, char *arg0, …, NULL );
int spawnle( int mode, char *path, char *arg0, …, NULL, char **env );
int spawnlp( int mode, char *path, char *arg0, …, NULL );
int spawnlpe( int mode, char *path, char *arg0, …, NULL, char **env );
int spawnv( int mode, char *path, char *argv[ ] );
inl spawnve( int mode, char *path, char *argv[ ], char **env );
int spawnvp( int made, char *path, char *argv[ ] );
int spawnvpe( int mode, char *path, char *argv[], char **env );

За винятком цілочисельного параметру mode, зміст параметрів функцій ідентичний розглянутим раніше при описі функції ехесххх(). Параметр mode має три визначених значення, описаних у файлі <process.h>:

P_WAIT - предок зупиняється і запускається нащадок, після завершення якого керування повертається в предок (єдино можливий спосіб у MS-DOS);

P_NOWAIT - нащадок і предок виконуються паралельно як незалежні процеси (цей засіб не реалізується в програмах під керуванням MS-DOS, і Turbo С його не підтримує);

P_OVERLAY - нащадок замішає предка, тобто предок припиняє своє існування і тому неможливе повернення керування в предок, функції spawnxxx() із таким прапором цілком ідентичні функціям ехесххх(),

Якщо функції spawnxx() за якимись причинами не може бути виконана, у точку виклику повертається -1 і в зовнішню змінну еrrno записується код помилки. Можливі значення кодів помилок і причини, їх що викликають, приведені вище (Табл. 13). Додатково функції spawnxxx() контролюють коректність значення параметру mode. Якщо задається неприпустиме значення параметру, зовнішня змінна errno одержує значення EINVAL.

У випадку успішного виконання нащадка зі значенням mode рівним P_WAIT, функції spawnxxx() повертають код завершення нащадка.

Розходження між окремими функціями spawnxxx() такі ж, як і для функцій ехесхх(). Буква ‘р’ у назві функції говорить про те, що для пошуку файлу нащадка будуть проглядатися не тільки маршрут, заданий параметром path, але і маршрути, зазначені в рядку середовище PATH, якщо пошук по маршруті path завершуйся невдачею. Буква ‘е’ свідчить про те, що при запуску програми-нащадка буде перевизначатися середовище. Масив покажчиків env містить покажчики на ASCIIZ-рядки нового середовища. Якщо буква 'е' у назві функції відсутня, нащадок успадковує середовище предка в незмінному виді. Буква ‘l' у назві функції свідчить про те, що в точку входу main() нащадка передається відоме заздалегідь число слів, покажчики на які задаються при виклику функції. Якщо число параметрів у функції заздалегідь невідомо, те єдиний вихід - використовувати масив покажчиків і передавати адресу початку цього масиву. Всі функції spawnxxx(), що мають у назві букву ‘v’, передають у якості другого аргументу покажчик на масив покажчиків argv.

Наведемо приклад програми-предка, що дозволяє визначити виграш у швидкості за рахунок використання сопроцесору математики з плаваючою точкою. Предок двічі викликає нащадка: спочатку з рядком середовища “87=NO”, потім із рядком середовища “87=YES”. Обидва рази нащадок передає в предок час виконання тестового набору операцій. Предок порівнює отримані значення і виводить на екран коефіцієнт прискорення, забезпечуваного використанням сопроцесору математики з плаваючою точкою. Предок на самому початку аналізує наявність у системі сопроцесору і завершується, якщо він відсутній. Алгоритм визначення присутності сопроцесору заснований на аналізі бита 1 слова області даних BIOS за адресою 40:10h. Якщо битий 1 дорівнює одиниці, у комп'ютері встановлений сопроцесор.

Нащадок виконує тестовий набір із МАХ операцій додавання, МАХ операції множення, МАХ операцій ділення чисел із плаваючою точкою типу float і компілюється з включеною опцією емуляції сопроцесору. Це призводить до генерації самоналагоджувального на апаратуру програмного коду. Нехай у системі встановлений сопроцесор математики з плаваючою точкою. Арифметичні операції над дійсними числами виконуються по спеціальних підпрограмах (емуляція сопроцесору) у випадку, коли в середовищі програми є рядок "87=NO", або використовується сопроцесор, якщо є рядок середовища "87=YES". У випадку, коли в системі відсутній сопроцесор, незалежно від установки середовища виконується програмна емуляції арифметичних операцій із плаваючою точкою. Далі наводиться текст програми-предка:

/* L5_5.C */
#include <stdio.h>
#include <dos.h>
#include <stdlib.h>
#include <process.h>
#define MAX 0xFFFF
int main(void) {
  struct OP_TIME {      /* структура для часу виконання операцій */
    float summing,          /* час  МАХ додавань */
            multiplaying,     /* час МАХ множень */
            dividing;           * час МАХ ділень */
  } with_87, without_87;
  char *envp[] = { “87=YES”, NULL };
  char text_seg[7], text_offs[7];
  unsigned seg, offs;
  /* Тест наявності сопроцесору /
  if( !(*(unsigned far *)МК_FP( 0x40, 0x10 ) & 0x0002 ) ) {
    puts( “\a В комп'ютері відсутній сопроцесор” );
    return(2);
  }
  seg = _DS;
  offs = FP_OFF( &with_87 );
  ultoa( seg, text_seg, 16 );
  ultoa( offs, text_offs, 16 );
  /* Запуск нащадка для роботи з використанням сопроцесору */
  if( spownpe( P_WAIT, “test.exe”, “test.exe”, text_seg,
                      text_offs, NULL, envp ) == -1 ) {
     perror( “\a Невдача запуску нащадка” );
     return(1);
  }
  offs = FP_OFF( &without_87 );
  itoa( offs, text_offs, 16 );
  envp[0] = “87=NO”;

  /* Запуск нащадка без використання сопроцесору (емуляція). */
  if( spawnlpe( P_WAIT,”test.exe”, ”test.exe”, text_seg,
                        text_offs, NULL, envp ) == -1 ) {
    perror( “\а Невдача запуску нащадка” );
    return(2);
  }
  /* Опрацювання статистики і виведення результатів */
  printf( “час %u додавань: з сопроцесором -%5.2f\n”\
             “ без нього (емуляція) - %5.2f\n”\
             коефіціент прискорення - %5.2f\n",
              MAX, with_87.summing, without_87.summing,
              without_87.summing/with_87.summing );
  printf( “час %u множень: з сопроцесором -%5.2f\n”\
             “ без нього (емуляція) - %5.2f\n”\
             коефіціент прискорення - %5.2f\n",
              MAX, with_87.multiplaying, without_87.multiplaying,
              without_87.multiplaying/with_87.multiplaying );
  printf( “час %u ділень: з сопроцесором -%5.2f\n”\
             “ без нього (емуляція) - %5.2f\n”\
             коефіціент прискорення - %5.2f\n",
              MAX, with_87.dividing, without_87.dividing,
              without_87.dividing/with_87.dividing );
  return(0);
}

Предок двічі викликає програму-нащадок, передаючи їй сегмент і зсув області пам'яті, у якому повинні записуватися результати виконання предка. Передані значення перетворюються в рядок символів. Нащадок повинний бути розташований у файлі TEST.EXE. Далі наводиться Сі-текст програми нащадка. Нащадок виконує по МАХ разів додавання, множення і ділення чисел із плаваючою точкою. Визначає у форматі float витрачений час і записує його в область пам'яті, адреса якої передана через командний рядок при виклику нащадка.

/*TEST.С */
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <dos.h>
#include <bios.h>
#define MAX 0xFFFF
int main( int argc, char **argv ) {
  unsigned seg, offs;
  struct OP_TIME {      /* структура для часу виконання операцій */
    float summing,          /* час  МАХ додавань */
            multiplaying,     /* час МАХ множень */
            dividing;           * час МАХ ділень */
  } far *parent_mem;
  unsigned repeat;
  float first_op = 123456.789, second_op = 9876543.21, result;
  long bios_time_before, bios_time_after;
  if( argc < 3 ) { /* Чи всі аргументи задано? */
    puts( “\a”);
    return( 1 );
  }
  sscanf( argv[1], “%x”, &seg);
  sscanf( argv[2], “%x”, &offs);
  parent_mem = ( struct OP_TIME far* )MK_FP( seg, offs );

  bios_time_before = biostime( 0, 0L);        /* час до початку додавань */
  for( repeat = 0; repeat < MAX; repeat++)
    result = first_op + second_op;
  bios_time_after = biostime( 0, 0L );          /* час після додавань */
  parent_mem -> summing =
  (float)(bios_time_afterbios_time_before) / (float)CLK_TCK;

  bios_time_before = biostime( 0, 0L);        /* час до початку множень */
  for( repeat = 0; repeat < MAX; repeat++)
    result = first_op * second_op;
  bios_time_after = biostime( 0, 0L );          /* час після множень */
  parent_mem -> multiplaying =
  (float)(bios_time_afterbios_time_before) / (float)CLK_TCK;

  bios_time_before = biostime( 0, 0L);        /* час до початку ділення */
  for( repeat = 0; repeat < MAX; repeat++)
    result = first_op / second_op;
  bios_time_after = biostime( 0, 0L );          /* час після ділення */
  parent_mem -> dividing =
  (float)(bios_time_afterbios_time_before) / (float)CLK_TCK;

  return( 0 );
}

Наведемо результат виконання програми-предка для комп'ютера з тактовою частотою 25 МГц:

Час 65535 додавання: з сопроцесором - 0.49
 без нього (емуляція) – 637
 коефіцієнт прискорення - 12.89

Час 63535 множень: із сопроцесором - 0.49
      без нього (емуляція) - 7.42
      коефіцієнт прискоренн я- 15.00

Час 65535 ділення: із сопроцесором - 0.60
      без нього (емуляція) - 8.24
      коефіцієнт прискорення -13.64

Отримані результати носять достатньо приблизний характер, по-перше, через низьку точність виміру часу, функція biostime() повертає число “тіків” BIOS, a у секунді усього 18.2 “тіка”. По-друге, вимірюється не чистий час виконання операцій із плаваючою точкою, а загальний час виконання всієї програми, у якій ряд операцій пов‘язаний з організацією циклу й інших дій.