LEKCJA 20 - JE�LI PROGRAM POWINIEN URUCHOMI� INNY PROGRAM... ________________________________________________________________ W trakcie tej lekcji dowiesz si�, jak w C++ mo�na programowa� * procesy potomne * pisa� programy rezyduj�ce w pami�ci (TSR) ________________________________________________________________ O programach rezydentnych (TSR) i procesach potomnych. Warunek zewn�trznej zgodno�ci z poprzednimi wersjami DOS wyra�nie hamuje ewolucj� systemu MS DOS w kierunku "powa�nych" system�w operacyjnych umo�liwjaj�cych prac� wieloprogramow� w trybie "multiuser", "multitasking" i "time sharing". Pewn� namiastk� pracy wieloprocesowej daj� nam ju� DOS 5/6 i Windows 3.1. Mo�na ju� otwiera� wiele okien program�w jednocze�nie, mo�na np. drukowa� "w tle", mo�na wreszcie pisa� rezyduj�ce stale w pami�ci programy klasy TSR (ang. Terminated and Stay Resident) uaktywniaj�ce si� "od czasu do czasu". O bloku PSP. System DOS przydziela programom blok - "nag��wek" wst�pny nazywany PSP (ang. Program Segment Prefix). Blok ten zawiera informacje o stanie systemu DOS w momencie uruchamiania programu (nazywanego tu inaczej procesem). Znajduj� si� tam informacje o bie��cym stanie zmiennych otoczenia systemowego (ang. environment variables) i parametrach uruchomieniowych. Blok PSP zajmuje 256 bajt�w na pocz�tku kodu programu w zakresie adres�w: CS:0000 ... CS:0100 (hex) W�a�ciwy kod programu zaczyna si� zatem od adresu CS:0100. Interpreter rozkaz�w systemu DOS �aduje programy do pami�ci pos�uguj�c si� funkcj� systemow� nr 75 (4B hex). Wszystko jest proste dop�ki mamy do czynienia z programem "kr�tkim" typu *.COM. Je�li jednak�e program uruchamiany jest w wersji "d�ugiej" - *.EXE, dowolna mo�e by� nie tylko d�ugo�� pliku, ale tak�e pocz�tkowa zawarto�� rejestr�w CS, SS, SP i IP. W plikach typu *.EXE pocz�tek bloku PSP wskazuj� rejestry DS (DS:0000) i ES. W Borland C++ masz do dyspozycji specjaln� funkcj� getpsp() przy pomocy kt�rej mo�esz uzyska� dost�p do bloku PSP programu. Kr�tki przyk�ad zastosowania tej funkcji poni�ej: /* Przyk�ad zastosowania funkcji getpsp(): */ # include <stdio.h> # include <dos.h> main() { static char TAB[128]; char far *ptr; int dlugosc, i; printf("Blok PSP: %u \n", getpsp()); ptr = MK_FP(_psp, 0x80); dlugosc = *ptr; for (i = 0; i < dlugosc; i++) TAB[i] = ptr[i+1]; printf("Parametry uruchomieniowe: %s\n", TAB); } W normalnych warunkach po wykonaniu "swojej roboty" program zostaje usuni�ty z pami�ci operacyjnej (czym zajmuje si� funkcja systemowa nr 76 - 4C (hex)). Aby tak si� nie sta�o, program mo�e: * uruchomi� sw�j proces (program) potomny; * wyj�� "na chwil�" do systemu DOS - tj. uruchomi� jako sw�j proces potomny interpreter COMMAND.COM; * przekaza� sterowanie programowi COMMAND.COM pozostaj�c w pami�ci w postaci "u�pionej" oczekuj�c na uaktywninie. Poni�ej kilka prostych przyk�ad�w uruchamiania jednych proces�w przez inne w Borland C++: /* Funkcja execv(): uruchomienie programu "potomnego"*/ # include <process.h> # include <stdio.h> # include <errno.h> void main(int argc, char *argv[]) { int i; printf("Parametry uruchomieniowe:"); for (i=0; i<argc; i++) printf("\n%d) %s", i, argv[i]); printf("Przekazuje parametry do procesu 2 par_1, par_2...\n"); execv("CHILD.EXE", argv); .... exit (2); } [P074.CPP] /* Funkcja system() - na chwil� do DOS */ # include <stdlib.h> # include <stdio.h> void main() { printf("Wyjscie do DOS i wykonanie jednego rozkazu:\n"); system("dir > c:\plik.dir"); } /* Funkcje grupy spawn...() : spawnl() */ # include <process.h> # include <stdio.h> # include <conio.h> void main() { int rezultat; rezultat = spawnl(P_WAIT, "program.exe", NULL); if (rezultat == -1) { perror(" Fiasko !"); exit(1); } } /* Funkcja spawnle() */ # include <process.h> # include <stdio.h> # include <conio.h> void main() { int rezultat; rezultat = spawnle(P_WAIT, "program.exe", NULL, NULL); if (rezultat == -1) { perror("Fiasko !"); exit(1); } } Zagadnienie uruchamiania program�w potomnych (ang. child process) przez programy macie�yste (ang. parent process) jest rozpracowane w C++ do�� dok�adnie i zarazem obszernie. Istnieje wiele gotowych funkcji bibliotecznych, z us�ug kt�rych mo�esz tu skorzysta�. Wszystko to nie jest jednak "prawdziwym" programem TSR. Przyjrzyjmy si� zatem dok�adniej dopuszcalnym przez system DOS sposobom zako�czenia programu nie powoduj�cym usuni�cia programu z pami�ci. Je�li program rezydentny jest niewielki (kod < 64 K), mo�emy zako�czy� program pos�uguj�c si� przerywaniem INT 39 (27 hex). Je�li natomiast zamierzamy pos�ugiwa� si� d�u�szymi programami, mamy do dyspozycji funkcj� systemow� nr 49 (31 hex). Nale�y tu zwr�ci� uwag�, �e zako�czenie programu w taki spos�b (z pozostawieniem w pami�ci) nie spowoduje automatycznego zamkni�cia plik�w, a jedynie opr�nienie bufor�w. Programy rezydentne dzieli si� umownie na trzy kategorie: [BP] - background process - procesy dzia�aj�ce "w tle"; [SV] - services - programy us�ugowe - np. PRINT; [PP] - pop up programs - uaktywniane przez okre�lon� kombinacj� klawiszy; System DOS dysponuje tzw. przerywaniem multipleksowym (naprzemiennym) wykorzystywanym cz�sto przez programy rezydentne. Jest to przerywanie nr INT 47 (2F hex). MS DOS za�atwia takie problemy funkcjami nr 37 (25 hex) - zapisanie wektora przerywania i 53 (35 hex) - odczytanie wektora przerywania. Z jakich funkcji C++ mo�na skorzysta�? W C++ masz do dyspozycji par� funkcji getvect() i setvect() (ang. GET/SET VECTor - pobierz/ustaw wektor przerywania). Poni�ej kr�tkie przyk�ady zastosowa� tych funkcji. /* Opcja: Options | Compiler | Code generation | Test Stack Overflow powinna zosta� wy��czona [ ] (off) */ # include "stdio.h" # include "dos.h" # include "conio.h" /* INT 28 (1C hex) - Przerywanie zegarowe */ void interrupt ( *oldhandler)(void); int licznik = 0; void interrupt handler(void) { /* Inkrementacja globalnej zmiennej licznik */ licznik++; /* Wywolujemy stary "handler" zegara */ oldhandler(); } void main() { /* Zapami�taj poprzedni wektor przerywania 28 */ oldhandler = getvect(28); /* Zainstaluj now� funkcje obslugi przerywania */ setvect(28, handler); /* Inkrementuj licznik */ for (; licznik < 10; ) printf("licznik: %d\n",licznik); //odtworz stara funkcje obslugi przerywania: interrupt handler setvect(28, oldhandler); } # include <stdio.h> # include <dos.h> void interrupt nowa_funkcja(); // prototyp funkcji - handlera void interrupt (*oldfunc)(); /* interrupt function pointer */ int warunek = 1; main() { printf("\n [Shift]+[Print Screen] = Quit \n"); printf("Zapamietaj, i nacisnij cosik...."); while(!kbhit()); /* zapamietaj stary wektor */ oldfunc = getvect(5); /* INT 5 to przerywanie Sys Rq, albo Print Screen */ /* zainstaluj nowa funkcje obslugi: interrupt handler */ setvect(5, nowa_funkcja); while (warunek) printf("."); /* Odtworz stary wektor przerywania */ setvect(5, oldfunc); printf("\n Udalo sie... nacisnij cosik..."); while(!kbhit()); } /* Definicja nowego handlera */ void interrupt nowa_funkcja() { warunek = 0; /* jesli warunek == 0, petla zostanie przerwana*/ } Je�li nasz program zamierza korzysta� z przerywania multipleksowego INT 47 (2F hex), nale�y pami�ta�, �e przerywanie to wykorzystuj� tak�e inne programy systemowe. Rozr�nia� te programy mo�na przy pomocy identyfikator�w (podaj� dziesi�tnie): 01 - PRINT.EXE 06 - ASSIGN.COM 16 - SHARE.EXE (10 hex) 26 - ANSI.SYS 67 - HIMEM.SYS 72 - DOSKEY.COM 75 - TASK SWITCHER 173 - KEYB.COM 174 - APPEND.EXE 176 - GRAFTABL.COM 183 - APPEND.EXE Identyfikator programu TSR jest przekazywany za po�rednictwem rejestru AH. System DOS jest na razie systemem w zasadzie jednozadaniowym i jednou�ytkownikowym, w kt�rym zasoby s� przydzielane procesom kolejno (ang. serially reusable resources). Aby uchroni� si� przed potencjalnym konfliktem, powinni�my upewni� si�, czy DOS "nic nie robi". Cz�sto stosowan� "sztuczk� techniczn�" jest zastosowanie flag ErrorMode i InDos systemu oraz wykorzystanie mechanizm�w przerywa� nr 36 i 40 (24 i 28 hex). Przydatn� informacj� jest tak�e identyfikator programu - PID. Na tak� ewntualno�� Borland C++ dysponuje makrem getpid zdefiniowanym w pliku nag��wkowym <PROCESS.H>: # define getpid() (_psp) Inn� przydatn� funkcj� mo�e okaza� si� keep() (ang. keep resident - pozosta� rezydentny). Oto kr�tki przyk�ad zastosowania tej funkcji - zn�w z wykorzystaniem przerywa� zegarowych. # include <dos.h> # define INTR 0x1C /* przerywanie INT 28 */ # define ATTR 0x7900 /* ograniczenie wielko�ci sterty (heap length) i stosu (stack length): */ extern unsigned _heaplen = 1024; extern unsigned _stklen = 512; void interrupt ( *oldhandler)(void); void interrupt handler(void) { unsigned int (far *ekran)[80]; ...
PabloPCK