Cvičení č. 5 Programování ve Windows program stopky 3 Body
Datum: 31.3.2008 1
Obsah 1. Úvod.............................................................................................................................................2 2. Pokyny pro odevzdání..................................................................................................................2 3. Příprava ........................................................................................................................................2 4. Úlohy............................................................................................................................................3 4.1. Požadavky na program SleepTest .........................................................................................3 4.2. Požadavky na implementaci..................................................................................................4 4.3. Komunikace s Grafickým uživatelským rozhraním..............................................................4 4.4. Doporučení pro implementaci...............................................................................................4 4.5. Popis ukázkového programu Stopky.....................................................................................6 4.6. Popis použitých funkcí Windows (WinAPI).........................................................................7
1. Úvod V tomto cvičení si ukážeme, jak je možno vytvářet více-procesní aplikace v prostředí Windows. Vytvoříme program s využitím aplikačního rozhraní Windows (Win32 API) simulující stopky. Cílem cvičení je získat představu o tvorbě programů, které provádějí souběžně několik úloh v prostředí operačních systémů Windows a na řešení modelové úlohy získat praktické dovednosti v této oblasti. V rámci cvičení bude nejprve vyzkoušen jednoduchý program simulující stopky s využitím dvou prováděcích toků (threads). Jeden prováděcí tok bude zajišťovat běh hodin, druhý tok bude komunikovat s grafickým uživatelským rozhraním (zobrazovat čas a zpracovávat přikazy uživatele). Dále bude vytvořen program pro otestování přesnosti funkce Sleep(). Tato funkce Windows API slouží k pozastavení prováděcího toku na zadaný počet milisekund. Vyzkoušíme volat tuto funkci a měřit skutečnou dobu pozastavení.
2. Pokyny pro odevzdání Výstupem cvičení bude protokol – krátká zpráva o řešení úloh na cvičení. Šablonu protokolu si stáhněte z Moodle. Protokol by měl obsahovat odpovědi na otázky, zdrojový kód Vašich řešení podle zadání, případně stručný popis úprav v ukázkovém kódu podle bodu 4 v tomto dokumentu. Dále bude výstupem protokolu soubor se zdrojovým kódem programu SleepTest. Oba soubory (protokol i zdrojový kód) pojmenujte svým jménem ve formátu Prijmeni_Jmeno, a zabalte to archivu ZIP. Soubor ZIP také pojmenujte svým jménem podle výše uvedeného vzoru a odevzdejte na Moodle.
3. Příprava Prostudujte si prezentaci na Moodle Windows a real-time. Dále postupujte podle návodu ke cvičení.
2
4. Úlohy o Stáhněte si z Moodle ukázkový program v archivu win_stopky.zip. Archiv obsahuje kompletní projekt s ukázkovým programem. Nakopírujte všechny soubory z archivu do složky na lokálním disku a otevřete projekt poklepáním na soubor .vcproj, případně z prostředí Visual Studio 2005 přes nabídku File -> Open -> Project/Solution. o Vyzkoušejte ukázkový program. Před spuštěním programu spusťte grafické rozhraní (stopky.exe), případně nakopírujte stopky.exe do složky vašeho projektu tak, aby bylo grafické rozhraní spuštěno automaticky při startu vašeho programu. o Prostudujte ukázkový program a upravte jej tak, aby stopky běžely s dvojnásobnou rychlostí. Jakou úpravu bylo třeba provést? o Vytvořte program SleepTest podle zadání uvedeného níže. Funkci programu můžete vidět na ukázkovém programu dostupném na Moodle. o Zhodnoťte v protokolu přesnost funkce Sleep podle výsledků získaných vaším programem. Zaměřte se na následující otázky: o Jaký vliv na přesnost funkce Sleep má délka pozastavení? o Má nějaký vliv na přesnost počet threads, které jsou současně v testovacím programu spuštěny? o Jaká je přesnost měření dosahovaná pomocí funkce GetTickCount()? Podívejte se do dokumentace k funkci GetTickCount() a případně pomocí vlastního experimentu s funkcí GetSystemTimeAdjustment() zjistěte rozlišení systémového časovače na vašem počítači. o Pokuste se v dokumentaci najít jakým způsobem by bylo možno měřit dobu pozastavení přesněji. Shrnutí úloh 1. 2. 3. 4. 5.
Vyzkoušejte a prostudujte ukázkový program win_stopky. Upravte program tak, aby stopky běžely s dvojnásobnou rychlostí. Vytvořte program SleepTest V protokolu odpovězte na otázky uvedené výše. K protokolu přiložte zdrojový kód vašeho programu SleepTest.
4.1. Požadavky na program SleepTest o Program bude sloužit k otestování přesnosti časování pomocí funkceWin API Sleep. o Výstup programu bude na obrazovku konzoly (např. pomocí funkce printf). o Po spuštění se program dotáže na požadovaný počet vytvořených prováděcích toků (threads). Povolené hodnoty budou 0 až 10. o Hlavní tok programu (funkce main) provede opakované měření prodlevy vytvořené voláním funkce Sleep. Po každém měření vypíše na obrazovku zjištěnou hodnotu. o Měření bude provedeno pro hodnoty prodlevy 50, 100 a 500 ms. Pro každou prodlevu program provede 10 měření. o Po provedení všech měření program zobrazí výsledky – průměrné hodnoty pro jednotlivé prodlevy a dále hodnotu maximální odchylky od požadované prodlevy. o Program bude možno kdykoliv ukončit stiskem klávesy (např. Q nebo X). K tomuto účelu vytvořte prováděcí tok, který bude sledovat stav klávesnice a v případě stisku příslušné klávesy ukončí celý program voláním funkce Win API ExitPpocess(). 3
4.2. Požadavky na implementaci o Program bude vytvářet nejméně jeden prováděcí tok (kromě hlavního toku) – a to tok pro obsluhu klávesnice. Dále pak až 10 toků pro vytěžování procesoru. o Měření přesnosti časování pomocí Sleep bude provádět hlavní prováděcí tok programu (tj. funkce main). o Program před provedením měření spustí 0 až 10 prováděcích toků, podle požadavku uživatele. Tyto toky budou pouze provádět v nekonečné smyčce libovolný výpočet a vytěžovat tak procesor. Kód toků implementujte pomocí jedné (společné) funkce – kód vykonávaný jednotlivými toky bude tedy společný pro všechny toky. Způsob jak toho dosáhnout je uveden dále.
4.3. Komunikace s Grafickým uživatelským rozhraním Program nevyužívá GUI.
4.4. Doporučení pro implementaci Vytvářejte program postupně, krok za krokem. Nejprve můžete např. spustit ve funkci main() jen prováděcí tok pro obsluhu klávesnice. Za spuštění toku umístěte nekonečnou smyčku (aby funkce main neskončila, protože tím by skončil i celý program). Vyzkoušejte, zda je možno program ukončit stiskem zvolené klávesy. Poté přidejte kód pro vytváření „vytěžujících“ prováděcích toků (viz níže) a vyzkoušejte jeho funkčnost. Počet prováděcích toků, které ve vašem programu běží, si můžete ověřit ve správci úloh systému Windows, ve sloupci „podprocesy“. Poté přidejte samotný kód pro měření přesnosti funkce Sleep(). Využijte k tomu funkci pro zjištění času GetTickCount(), příklad použití je uveden dále. Pro vytvoření několika vytěžujících prováděcích toků nemá smysl definovat pro každý tok vlastní funkci. Všechny toky mohou provádět stejný kód. Při vytváření toků pak funkci CreateThread prostě předáme jméno stejné funkce. Kód by mohl vypadat následovně: // Zde je načten požadovaný počet toků od uživatele do proměnné nToku. // … HANDLE hThread; DWORD id; for ( int i=0; i
4
// „telo“ vytezujiciho provadeciho toku DWORD WINAPI tok_funkce(void* pParam) { double d = 0; while(1) { d = d*1.125; if ( d > 100000) d = 0.0; } }
Dobu pozastavení prováděcího toku zjistíme voláním funkce GetTickCount(). Tato funkce vrací počet milisekund, které uběhly od startu systému. Pro nás není důležitá absolutní hodnota vrácená funkcí, ale rozdíl hodnot získaný před a po voláním funkce Sleep(). Dobu pozastavení v milisekundách tedy zjistíme takto: DWORD start, stop; start = GetTickCount(); Sleep(50); stop = GetTickCount(); DWORD doba = stop – start;
Obecná struktura programu SleepTest může být následující: 1. 2. 3. 4. 5.
Include soubory Definice proměnných a prototypů funkcí Funkce main = thread 1 Funkce pro obsluhu klávesnice = thread 2 Funkce pro vytěžování procesoru = thread 3 až N, společné tělo pro N threads. Kód viz např. ukázka tok_funkce(void* pParam) výše.
Funkce main potom může mít následují strukturu: 1. 2. 3. 4. 5.
Definice proměnných Vytvoření toku pro obsluhu klávesnice Načtení požadovaného počtu „vytěžujících“ prováděcích toků z klávesnice Vytvoření požadovaného počtu toků (viz ukázka kódu výše) Provedení testu funkce Sleep pro hodnotu 50 ms v cyklu, který se provede 10-krát. Pro závěrečné vyhodnocení stačí vypsat průměrnou dobu pozastavení, tj. zde stačí uložit součet změřených dob pozastavení a maximální odchylku od požadované doby. Ukázkový kód měření doby pozastavení pomocí funkce GetTickCount najdete výše. 6. Provedení stejného testu funkce Sleep pro hodnotu 100 ms. 7. Provedení stejného testu s hodnotou 500 ms. 8. Výpočet a výpis výsledků (průměrných hodnot pozastavení).
5
4.5. Popis ukázkového programu Stopky Program jehož zdrojový kód najdete v souboru stopky_win.cpp simuluje stopky s využitím dvou prováděcích toků (threads) systému Windows. Tok 1 = obsluha příkazů uživatele Tok 2 = běh času Funkce main představuje hlavní prováděcí tok programu. V této funkci je nejprve vytvořen prováděcí tok zajišťující běh času (thread_cas). K tomu se použije funkce CreateThread (vytvoř prováděcí tok). Nejdůležitějším parametrem této funkce je název funkce, která představuje „výkonné tělo“ prováděcího toku. V našem příkladě je to funkce thread_cas. hTokCas = CreateThread(NULL, 0, thread_cas, NULL, 0, &id); if ( hTokCas == NULL ) { printf("Nepodarilo se vytvorit thread hodin.\n"); Stopky_Exit(); return 1; }
Funkce CreateThread vrací handle vytvořeného prováděcího toku. Pokud by se tok nepodařilo vytvořit, vrátí NULL. Potom jsou v nekonečné smyčce přijímány zprávy z GUI. while ( pokracuj ) { key = Stopky_GetKeyPress(true); switch ( key ) { case STOPKY_START_STOP: …
Při příchodu zprávy STOPKY_START_STOP se pozastaví nebo znovu-spustí prováděcí tok zajišťující běh času pomocí funkce SuspendThread (pozastav prováděcí tok) a ResumeThread (znovu-spusť prováděcí tok). Za funkcí main je funkce thread_cas. Tato funkce představuje tělo prováděcího toku, který zajišťuje běh času stopek. DWORD WINAPI thread_cas(LPVOID lpParameter) { while (1 ) { sec++; // zvýšit sekundy o 1 Stopky_DisplayTime(0, sec); // omezení času na 99 sekund.... if ( sec == 99 ) sec = 0;
}
Sleep(1000); } return 0;
// počkat 1 sekundu
6
Prototyp funkce je DWORD WINAPI thread_cas(LPVOID lpParameter); Funkce tedy vrací hodnotu DWORD a dostává jeden vstupní parametr, ukazatel void. Uvnitř funkce se pouze v nekonečné smyčce aktualizuje čas v grafickém rozhraní stopek (volání funkce GUI Stopky_DisplayTime). Všimněte si volání funkce Sleep(1000), která po každé aktualizaci času pozastaví prováděcí tok na 1000 milisekund. Windows neposkytují periodicky spouštěné prováděcí toky a tak tímto způsobem zajišťujeme správnou rychlost běhu času. Poznámky: DWORD je datový typ definovaný v rozhraní WinAPI. Představuje celé číslo bez znaménka o velikosti 4 bajty. LPVOID je datový typ představují ukazatel na neurčený datový typ (void*). WINAPI je určení tzv. volací konvence používané pro funkce rozhraní WinAPI. Volací konvence určuje jakým způsobem jsou na úrovni asembleru předávány funkcím parametry atd. Pro nás nemá toto slovo žádný význam - kromě toho, že jej musíme uvést u každé funkce, která bude sloužit jako „tělo“ pro prováděcí tok.
4.6. Popis použitých funkcí Windows (WinAPI) DWORD WINAPI GetTickCount(void); Funkce vrací počet milisekund, které uběhly od startu systému. Po 49,7 dne (pokud systém není tak dlouho vypnut) hodnota přeteče a počítá se znovu od nuly.
VOID WINAPI ExitProcess(__in UINT uExitCode); Ukončí volající proces a všechny jeho prováděcí toky (threads). Parametry: uExitCode – Kód vrácený procesem operačnímu systému. Např. může být 0 pokud proces skončil bez problémů, ale je možno použít libovolnou hodnotu.
VOID WINAPI Sleep(__in DWORD dwMilliseconds); Pozastaví vykonávání aktuálního prováděcího toku na zadaný počet milisekund. Parametry: dwMilliseconds - Doba na kterou má být prováděcí tok pozastaven, v milisekundách. Hodnota 0 způsobí, že se volající tok vzdá zbytku svého časového kvanta (time slice). Podrobnosti k jednotlivým funkcím můžete najít v nápovědě Visual Studio nebo online na MSDN.
5. Odkazy Moodle – podpora předmětu na http://vyuka.fai.utb.cz. Magisterské Studium, Programování realtime aplikací. Klíč k zápisu je PR2008.
7