LEKCJA 37: KAŹDY DYSK JEST ZA MAŁY, A KAŹDY PROCESOR ZBYT WOLNY... ________________________________________________________________ W trakcie tej lekcji dowiesz się, jak komputer dysponuje swoimi zasobami w środowisku tekstowym (DOS). ________________________________________________________________ Truizmy użyte w tytule mają znaczyć, że "zasoby najlepszego nawet komputera są ograniczone" i zwykle okazują się wystarczające tylko do pewnego momentu. Najbardziej newralgiczne zasoby to: * czas mikroprocesora i * miejsce w pamięci operacyjnej. Tworzone przez nas programy powinny wystrzegać się zatem najcięższych grzechów: * nie pozwalać mikroprocesorowi na słodkie nieróbstwo; Rzadko uzmysławiamy sobie, że oczekiwanie na naciśnięcie klawisza przez użytkownika (czasem po przeczytaniu napisu na ekranie) trwa sekundy (1, 2, .... czasem 20), a każda sekunda lenistwa PC to stracone miliony cykli mikroprocesora. * oszczędnie korzystać z pamięci dyskowej, a szczególnie oszczędnie z pamięci operacyjnej RAM. MODELE PAMIĘCI IBM PC. Jak zapewne wiesz, Twój PC może mieć: * pamięć ROM (tylko do odczytu), * konwencjonalną pamięć RAM (640 KB), * pamięć rozszerzoną EMS i XMS, * pamięć karty sterownika graficznego ekranu (np. SVGA-RAM), * pamięć Cache dla buforowania operacji dyskowych. Najczęściej stosowane modele pamięci to: * Small - mały, * Medium - średni, * Compact - niewielki (tu mam wątpliwość, może "taki sobie" ?), * Large - duży, * Huge - jeszcze większy, odległy. Dodatkowo może wystąpić * Tiny - najmniejszy. Taki podział został spowodowany segmentacją pamięci komputera przez procesory Intel 8086 i podziałem pamięci na bloki o wielkości 64 KB. Model Small (Tiny, jeśli jest) jest najszybszy, ale najmniej pojemny. Model Huge - odwrotnie - najpojemniejszy, za to najwolniejszy. Model Tiny powoduje ustawienia wszystkich rejestrów segmentowych mikroprocesora na tę samą wartość (początek tej samej stronicy pamięci) i umieszczenie wszystkich zasobów programu wewnątrz wspólnego obszaru pamięci o wielkości nie przekraczającej 64 KB. Wszystkie skoki są wtedy "krótkie", a wszystkie pointery (adresy) 16-bitowe. Kompilacja z zastosowaniem modelu Tiny pozwala uzyskać program wykonywalny w wersji *.COM (a nie *.EXE). Ale niestety nie wszystkie programy mieszczą się w 64 KB. W modelu Small segment kodu jest jeden (kod max. 64 K) i segment danych też tylko jeden (dane max. 64 K), ale są to już dwa różne segmenty. Zestawienia najważniejszych parametrów poszczególnych modeli pamięci przedstawia tabelka poniżej: Modele pamięci komputera IBM PC. ________________________________________________________________ Model Segment kodu Segment danych *dp *cp ________________________________________________________________ Tiny 1 1 (CS = DS) 16 bit 16 bit Small 1 1 16 bit 16 bit Medium wiele 1 16 bit 32 bit Compact 1 wiele 32 bit 16 bit Large wiele wiele 32 bit 32 bit Huge wiele wiele 32 bit 32 bit ________________________________________________________________ *dp - data pointer - wskaźnik do danych (near/far) *cp - code pointer - wskaźnik do kodu. Large - kod + dane = max. 1 MB. Huge - kod = max. 1 MB, wiele segmentów danych po 64 K każdy. Wynikające z takich modeli pamięci kwalifikatory near, far, huge dotyczące pointerów w C++ nie są akceptowane przez standard ANSI C (ponieważ odnoszą się tylko do IBM PC i nie mają charakteru uniwersalnego). Trzeba tu zaznaczyć, że typ wskaźnika jest przez kompilator przyjmowany domyślnie (automatycznie) zgodnie z wybranym do kompilacji modelem pamięci. Jeśli poruszamy się wewnątrz niewielkiego obszaru pamięci, możesz "forsować" bliższy typ pointera, przyspieszając tym samym działanie programów: huge *p; ... near *ptr; //Bliski pointer ... near int Funkcja(...) //Bliska funkcja { ... } #define ILE (1024*640) near unsigned int Funkcja(void) { huge char *ptr; // tu długi pointer jest niezbędny long suma = 0; for (p = 0; p < ILE; p++) suma += *p; return (suma); } Zarówno zadeklarowanie funkcji jako bliskiej (near), jak i jako statycznej (static) powoduje wygenerowanie uproszczonej sekwencji wywołania funkcji przez kompilator. Daje to w efekcie mniejszy i szybszy kod wynikowy. IDENTYFIKACJA KLAWISZY. Znane Ci z pliku i "klasyczne" funkcje obsługi konsoli mają pewne zalety. Korzystanie z klasycznych, nieobiektowych mechanizmów powoduje z reguły wygenerowanie znacznie krótszego kodu wynikowego. Funkcje scanf() i gets() wymagają wciśnięcia klawisza [Enter]. Dla szybkiego dialogu z komputerem znacznie bardziej nadają się szybsze getch() i kbhit(). Ponieważ klawiatura zawiera także klawisze specjalne (F1 ... F10, [Shift], [Del], itp.), pełną informację o stanie klawiatury można uzyskać za pośrednictwem funkcji bioskey(), korzystającej z przerywania BIOS Nr 16. Oto krótki przykład zastosowania funkcji bioskey(): #include "bios.h" #include "ctype.h" #include "stdio.h" #include "conio.h" # define CTRL 0x04 # define ALT 0x08 # define RIGHT 0x01 # define LEFT 0x02 int klawisz, modyfikatory; void main() { clrscr(); printf("Funkcja zwraca : %d", bioskey(1)); printf("\n Nacisnij klawisz ! \n"); while (!bioskey(1)); printf("Funkcja zwrocila: %c", bioskey(1)); printf("\nKod: %d", (char)bioskey(1)); ... A to jeszcze inny sposób korzystania z tej bardzo przydatnej funkcji, tym razem z innymi parametrami: /* Funkcja z parametrem (0) zwraca kod klawisza: ------ */ klawisz = bioskey(0); /* Funkcja sprawdza stan klawiszy specjalnych --------- */ modyfikatory = bioskey(2); if (modyfikatory) { printf("\n"); if (modyfikatory & RIGHT) printf("RIGHT"); if (modyfikatory & LEFT) printf("LEFT"); if (modyfikatory & CTRL) printf("CTRL"); if (modyfikatory & ALT) printf("ALT"); printf("\n"); } /* drukujemy pobrany klawisz */ if (isalnum(klawisz & 0xFF)) printf("'%c'\n", klawisz); else printf("%#02x\n", klawisz); } Należy tu zwrócić uwagę, że funkcje kbhit() i bioskey() nie dokonują czyszczenia bufora klawiatury. Identyfikują znak (znaki) w buforze, ale pozostawiają bufor w stanie niezmienionym do dalszej obróbki. Zwróć uwagę, że funkcja getch() może oczekiwać na klawisz w nieskończoność. Sprawdzić szybciej, czy użytkownik nacisnął już cokolwiek możesz np. tak: if (kbhit()) ...; if (!kbhit()) ...; while (!bioskey(1)) ... if (bioskey(1)) ...; Inną wielce przydatną "szybką" funkcją jest getch(). Oto praktyczny przykład pobierania i testowania naciśniętych klawiszy klawiatury. [P131.CPP] # include "stdio.h" # include "conio.h" char z1, z2; void Odczyt(void) { z2 = '\0'; z1 = getch(); if (z1 == '\0') z2 = getch(); } main() { clrscr(); printf("\nKropka [.] = Quit"); printf("\nRozpoznaje klawisze [F1] ... [F3] \n\n"); for (;;) { while(!kbhit()); Odczyt(); if (z1 == '.') break; if (z1 != '\0') printf("\nZnak: %c", z1); else switch (z2) { case ';' : printf("\n F1"); break; case '<' : printf("\n F2"); break; case '=' : printf("\n F3"); break; default : printf("\n Inny klawisz specjalny!"); } } return 0; } Klawisze specjalne powodują wygenerowanie dwubajtowego kodu (widzianego w powyższym przykładowym programie jako dwa jednobajtowe znaki z1 i z2). Funkcja getch() pobiera te bajty z bufora klawiatury kolejno jednocześnie czyszcząc bufor. W przypadku klawiszy specjalnych pierwszy bajt jest zerowy (NULL, '\0', 00h), co jest sprawdzane w programie. A oto tabela kodów poszczególnych klawiszy: Kody klawiszy klawiatury IBM PC. ________________________________________________________________ Klawisze Kody ASCII (dec) ________________________________________________________________ Home G 71 (00:47h) '\0', 'G' End O 79 (00:4Fh) '\0', 'O' PgUp I 73 PgDn Q 81 Ins R 82 Del S 83 F1 ; 59 F2 ... F10 <, ... D 60, ... 68 Shift + F1 T 84 ... Shift + F10 ] 93 Ctrl + F1 ^ 94 ... Ctrl + F10 f 103 Alt + F1...F10 h, ... q 104, ... 113 Alt + 1...9 x, ... Ą (?) 120, ... 128 Alt + 0 Ć (?) 129 Strzałki kursora: LeftArrow K 75 RightArrow M 77 UpArrow H 72 DownArrow P 80 Ctrl + PgDn v 118 Ctrl + PgUp Ń (?) 132 Ctrl + Home w 119 Ctrl + End u 117 ________________________________________________________________ Wyprowadzanie znaków na ekran można przeprowadzić szybciej posługując się przerywaniem DOS INT 29H. Drukowanie na ekranie w trybie tekstowym przebiega wtedy szybciej niż robią to standardowe funkcje , , czy . Poniżej prosty przykład praktyczny wykorzystania przerywania 29H: [P132.CPP] # include # include # pragma inline void SpeedBox(int, int, int, int, char); main() { clrscr(); for (; !kbhit(); ) { int x = rand() % 40; int y = rand() % 12; SpeedBox(x, y, (80 - x), (24 - y), ('€' + x % 50)); } return 0; } void SpeedBox(int x1, int y1, int x2, int y2, char znak) { int k; for (; y1 < y2; y1++) { gotoxy(x1, y1); for (k = x1; k < x2; k++) { asm MOV AL, znak asm INT 29H } } } [Z] ________________________________________________________________ 1. Opracuj program pozwalający porównać szybkość wyprowadzania danych na ekran monitora różnymi technikami (cout, puts(), printf(), asm). 2. Porównaj wielkość plików wynikowych .EXE powstających w różnych wariantach z poprzedniego zadania. ________________________________________________________________