{{indexmenu_n>9}}
======== HW 9 - Vícevláknová aplikace s meziprocesovou komunikací ========
^ **[[courses:bab36prga:hw:start#ulohy_hw8_a_hw9 | Časný termín]]** | **[[courses:bab36prga:hw:start#Úloha HW9 | 08.05.2021 23:59 PDT]]** |
^ Termín odevzdání | ** 15.5.2021 23:59 PDT** |
^ Povinné zadání | 8b kontrola [[courses:bab36prga:resources:cs:start|Coding stylu]]|
^ Volitelné zadání | není |
^ Bonusové zadání | není |
^ Počet uploadů | bez omezení |
^ Podpora | {{ :courses:bab36prga:hw:bab36prga-hw09.zip |}} OS X binárka --- //2021/04/30 12:10//, [[courses:bab36prga:hw:hints|Doporučený postup při vypracování HW9]]|
Cílem úlohy je rozvést zkušenosti s využitím pojmenované roury pro komunikaci aplikace v počítači s poskytnutým Modulem a s prací s více vlákny pro zpracování více zdrojů událostí.
HW9 vychází z HW8, kterou je možné přímo rozšířit o výpočet fraktálu a využití fronty pro komunikaci mezi vlákny.
====== Povinné zadání ======
Vytvořte ovládají program, který komunikuje s Modul aplikací simulujícím Nucleo řídící desku, a realizuje výpočet fraktálu, který mapuje dynamický systém v části komplexní roviny do 2D roviny.
\\
Odevzdávaný ovládací program bude obsahovat následující funkcionality:
* Program jde zkompilovat, nepadá a je **korektně ukončen**.
* Program po spuštění reportuje stav programu a výpočtu.
* Program komunikuje s Modul výpočetní aplikací.
* Výsledek výpočtu je přenášen po pixelech a průběžně zobrazován na standardní výstup.
* Program počítá fraktál na PC.
* **Program korektně ukončuje vlákna.**
* Aplikace **korektně uvolňuje alokovanou dynamickou paměť**.
* Program řádně ošetřuje možné výjimečné stavy a reaguje na případné chyby.
* Probíhající výpočet ve výpočetním modulu lze kdykoliv přerušit z řidící aplikace nebo uživatelským vstupem výpočetního modulu.
===== Výpočet fraktálu =====
Modul aplikace počítá konkrétní část fraktálu podle zadaných parametrů (od ovládací aplikace).
Konkrétně se jedná o výpočet Juliovy množiny, který je dán rekurzivní rovnicí:
$$z_{i+1}=z_{i}^{2}+c,$$
pro kterou vyšetřujeme, zdali pro nějaké zvolené komplexní číslo $c$ posloupnost čísel $z_i$ diverguje či nikoliv. Pro příslušný bod $z_0\in\mathbb{C}$ tak dostáváme posloupnosti hodnot $z_1, z_2, \ldots,z_n$ a říkáme, že bod $z_0$ patří do Juliovy množiny, pokud je posloupnost hodnot $z_1, z_2, \ldots,z_n$ omezená, tj. nediverguje. Test divergence posloupnosti můžeme imperativně otestovat pro zvolený počet prvků posloupnosti $n$ tak, že ověříme, že platí $|z_i|< 2$ pro $0 < i \le n$. Tedy v případě, že absolutní hodnota libovolného komplexního čísla $z_i$ z posloupnosti $z_i\in{z_1, z_n,\ldots, n}$ je větší nebo rovna 2 posloupnost diverguje a bod $z_0$ leží vně Juliovy množiny. Přesnost určení zdali $z_0\in\mathbb{C}$ patří nebo nepatří do Juliovy množiny záleží na počtu testovaných prvků posloupnosti ${z_1, z_2, \ldots, z_n}$. Pro větší hodnoty $n$ může být výpočet náročnější, hodnota ale bude přesnější. Dále můžeme využít hodnotu prvního konkrétního kroku $z_k$, $02$ pro konkrétní $z_0$. Barva je vypočtena po složkách RGB podle
* R = $9(1-t)t^3\cdot 255$;
* G = $15(1-t)^2t^2\cdot 255$;
* B = $8.5(1-t)^3t\cdot 255$;
kde $t$ je podíl $k/n$, tj. číslo od 0 do 1.
Nejzajímavější části Juliovy množiny jsou tak její okraje a zmenšením části komplexní roviny, ze které vybíráme komplexní čísla $z_0$ můžeme přibližit okraj např.
Například pro obdélníkový výřez komplexní roviny definovaný body $a,b\in\mathbb{C}$ mapovaný do obrázku s rozlišením $320\times 240$ získáme:
| {{ :courses:b3b36prg:semestral-project:julia_set--05_-05j-05_05j.png?200 |}} | {{ :courses:b3b36prg:semestral-project:julia_set-001_02j-005_025j.png?200 |}} |
| $a=-0.5-0.5j$, $b=0.5+0.5j$ | $a=0.01+0.2j$, $b=0.05+0.25j$ |
=== Vykreslení fraktálu ===
Vlastní zobrazení fraktálu se realizovuje s využitím knihovny [[https://www.libsdl.org/|SDL]] a s využitím ''xwin_sdl'' modulu, který je v poskytnutých zdrojových kódech (''xwin_sdl.c'',''xwin_sdl.h'').
Tento modul obsahuje **čtyři funkce**:
* ''int xwin_init(int w, int h);'' - inicializuje okno o velikosti $w\times h$ pixelů
* ''void xwin_close();'' - zavře incializované okno
* ''void xwin_redraw(int w, int h, unsigned char *img);'' - překreslí obrázek o velikosti $w\times h$ pixelů odkazovaný ukazatelem ''img'' ve formátu RGB, tj. každý pixel je dán trojicí 8bitových hodnot udávající barvu pixelu (v RGB) a prvních $3w$ hodnot tak definuje první řádek obrázku.
* ''void xwin_poll_events(void);'' - vyprázdní frontu zpráv aplikace od grafického rozhraní, které může například v systému Unity na Ubuntu způsobovat ztmavnutí okna aplikace. Funkci je vhodné volat například po stisku klávesy nebo zpracování událost v hlavní smyčce zpráv, či v periodicky volat v pravidelné aktualizaci obsahu grafického okna.
Příklad použití ''xwin_sdl'' modulu:
...
xwin_init(width, height);
...
// fractal is computed
...
redraw(width, height, grid, n, image);
xwin_redraw(computation.w, computation.h, computation.img);
...
xwin_close();
...
, kde ''redraw(int w, int h, uint8_t *grid, uint8_t threshold, unsigned char *out)'' je funkce realizující vypočet barvy po RGB složkách.
Knihovnu SDL je doporučeno použít ve verzi 2, tj. např. [[https://www.libsdl.org/download-2.0.php|sdl2-2.0.5]].
Potřebné adresáře s hlavičkovými soubory a knihovnami pro linkování jsou přidány prostřednictvím nástroje ''sdl2-config'' v přiloženém ''Makefile'' jako
CFLAGS+=$(shell sdl2-config --cflags)
LDFLAGS+=$(shell sdl2-config --libs)
===== Modul aplikace =====
Modul aplikace počítá části fraktálu podle zadaných parametrů, které jsou z **ovládací aplikace odesílány po rouře** (pipe) zprávami ''MSG_COMPUTE_DATA''.
Aplikace reaguje na stisk kláves:
* 'a' - přeruší probíhající výpočet v Modulu (''MSG_ABORT'')
* 'r' - resetuje aplikaci
* 'q' - korektně ukončí program terminací jednotlivých vláken a ukončením hlavního vlákna programu voláním
==== Komunikační protokol ====
Modul aplikace počítá konkrétní část, které jsou odesílány zprávami ''MSG_COMPUTE_DATA''.
Vlastní výpočet probíhá tak, že řídicí aplikace zašle zprávu ''MSG_COMPUTE'', která specifikuje část výpočtu tzv. chunk ID (cid).
Výchozím bodem obdélníkového výřezu pro výpočet je dán komplexním číslem $re + im j$ a počtem hodnot (sloupců) na reálné ose (z leva do prava) a počtem hodnot (řádku) na imaginární ose (zhora dolů). Krok sloupce a řádku, spolu s hodnotou komplexního čísla $c$ a počtem kroků je součástí zprávy ''MSG_SET_COMPUTE''.
Definice jednotlivých zpráv odpovídá definici složeného typu ''struct message'' v souboru ''message.h''
// Definition of the communication messages
typedef enum {
MSG_OK, // ack of the received message
MSG_ERROR, // report error on the previously received command
MSG_ABORT, // abort - from user button or from serial port
MSG_DONE, // report the requested work has been done
MSG_GET_VERSION, // request version of the firmware
MSG_VERSION, // send version of the firmware as major,minor, patch level, e.g., 1.0p1
MSG_STARTUP, // init message (id, up to 9 bytes long string, cksum)
MSG_SET_COMPUTE, // set computation parameters
MSG_COMPUTE, // request computation of a batch of tasks (chunk_id, nbr_tasks)
MSG_COMPUTE_DATA, // computed result (chunk_id, result)
MSG_NBR
} message_type;
#define STARTUP_MSG_LEN 9
typedef struct {
uint8_t major;
uint8_t minor;
uint8_t patch;
} msg_version;
typedef struct {
uint8_t message[STARTUP_MSG_LEN];
} msg_startup;
typedef struct {
double c_re; // re (x) part of the c constant in recursive equation
double c_im; // im (y) part of the c constant in recursive equation
double d_re; // increment in the x-coords
double d_im; // increment in the y-coords
uint8_t n; // number of iterations per each pixel
} msg_set_compute;
typedef struct {
uint8_t cid; // chunk id
double re; // start of the x-coords (real)
double im; // start of the y-coords (imaginary)
uint8_t n_re; // number of cells in x-coords
uint8_t n_im; // number of cells in y-coords
} msg_compute;
typedef struct {
uint8_t cid; // chunk id
uint8_t i_re; // x-coords
uint8_t i_im; // y-coords
uint8_t iter; // number of iterations
} msg_compute_data;
typedef struct {
uint8_t type; // message type
union {
msg_version version;
msg_startup startup;
msg_set_compute set_compute;
msg_compute compute;
msg_compute_data compute_data;
} data;
uint8_t cksum; // message command
} message;
Inicializační zpráva ''MSG_STARTUP'' může být například definována jako
message msg = { .data.startup.message = { 'P', 'R', 'G', 'A', '-', 'H', 'W', '0', '9' } };
vhodnější je však například zakódovat do zprávy identifikaci autora, např. ČVUT username.
=== Výměna zpráv a průběh komunikace ===
Komunikace probíhá pomocí zápisové a čtecí roury.
* Po startu Module posílá zprávu ''MSG_STARTUP''
* Na příjem zprávy ''MSG_GET_VERSION'' odpovídá Modul ''MSG_VERSION'' nebo ''MSG_ERROR''
* Na příjem zprávy ''MSG_SET_COMPUTE'' odpovídá Modul ''MSG_OK'' nebo ''MSG_ERROR''
* Na příjem zprávy ''MSG_COMPUTE'' odpovídá Modul ''MSG_OK'' a zahajuje výpočet nebo v případě chyby posílá ''MSG_ERROR''. Výpočet probíhá postupně a pro každou hodnotu bodu (komplexní roviny/pixelu obrázku) zasílá Modul zprávu ''MSG_COMPUTE_DATA''. Po dokončení výpočtu zasílá Modul zprávu ''MSG_DONE''.
* Výpočet je možné přerušit zasláním zprávy ''MSG_ABORT'', na kterou Modul odpovídá ''MSG_OK'' a přeruší a ukončí aktuáně probíhající výpočet (jeli v režimu výpočtu).
* Po přerušení výpočtu posílá Modul zprávu ''MSG_ABORT''
* Přerušení výpočtu je možné také stiskem klávesy 'a'. V takovém případě Modul přeruší výpočet a zasílá zprávu ''MSG_ABORT'', tj. zprávu ''MSG_ABORT'' posílá Modul buď na základě stisku klávesy nebo žádosti o přerušení výpočtu (poté co přeruší výpočet).
V případě zaslání zprávy ''MSG_SET_COMPUTE'' nebo i ''MSG_COMPUTE'' jsou nastaveny nové hodnoty a výpočet pokračuje podle posledně zadaných hodnot. Kdykoliv (i v případě běžícího výpočtu) je možné si vyžádat verzi aplikace Modul zasláním zprávy ''MSG_GET_VERSION'', na kterou Modul promptně odpovídá ''MSG_VERSION''.
Typický průběh komunikace mezi PC a Modulovou aplikací může vypadat například následovně:
* //Modul//: ''MSG_STARTUP''
* //PC//: ''MSG_GET_VERSION''
* //Modul//: ''MSG_VERSION''
* //PC//: ''MSG_SET_COMPUTE''
* //Modul//: ''MSG_OK''
* //PC//: ''MSG_COMPUTE''
* //Modul//: ''MSG_OK''
* //Modul//: ''MSG_COMPUTE_DATA''
* //Modul//: ''MSG_COMPUTE_DATA''
* //Modul//: ''MSG_COMPUTE_DATA''
* //Modul//: ''MSG_COMPUTE_DATA''
* ...
* //Modul//: ''MSG_COMPUTE_DATA''
* //Modul//: ''MSG_DONE''
Je doporučené uvažovat omezený rozsah ''n_re'' a ''n_im'' zprávy ''MSG_SET_COMPUTE'', který spolu s bajtovou reprezentací ''c_id'' udává, jak je možné organizovat výpočet a jak velké obrázky (rozlišení) je možné aplikací počítat.
Dále je doporučené omezení hodnoty $n$, aby nebyla vyšší než 200.
===== Ovládací aplikace =====
Implementaci ovládací aplikace lze také založit na řešení HW8, která vyčítá hodnoty stiknutých kláves a příslušně reaguje.
Výpis stavu aplikace lze jednoduše realizovat textovým výpisem.
/*Způsob ovládání není striktně předepsán a záleží na invenci řešení. */
Ovládací aplikace bude reagovat na stisk následujících kláves:
* 'g' - vyžádá si číslo verze Modulu (''MSG_GET_VERSION'')
* 's' - nastaví parametry výpočtu (''MSG_SET_COMPUTE'')
* '1' - spustí výpočet (''MSG_COMPUTE'')
* 'a' - přeruší probíhající výpočet (''MSG_ABORT'')
* 'r' - resetuje cid
* 'l' - smaže aktuální obsah výpočtu (bufferu)
* 'p' - překreslí obsah okna aktuálním stavem výpočtu (bufferem)
* 'c' - spočte fraktál na PC (pro testovací a kontrolní účely)
* 'q' - korektně ukončí program terminací jednotlivých vláken a ukončením hlavního vlákna programu voláním return EXIT_SUCCESS;
/*Důležitou částí je organizace kódu a dedikování jednotlivých vláken pro 1) čtení z klávesnice; 2) čtení z roury; 3) výpočet fraktálu, 4) výpis fraktálu; případně další hlavní vlákno v boss/worker modelu. */
V průběhu běhu aplikce může program vypisovat dílčí stavy a události na standardní (chybový) výstup s nějakým jednoduchým rozlišením kategorie typu výpisu např. ''INFO'', ''DEBUG'', ''WARN'', ''ERROR'':
"ERROR: Cannot open serial port %s\n", serial
"INFO: Create thread '%s' %s\n", threads_names[i], ( r == 0 ? "OK" : "FAIL")
"INFO: Get version requested\n"
"INFO: Set new computation resolution %dx%d no. of chunks: %d\n", ...
"WARN: New computation parameters requested but it is discarded due to on ongoing computation\n"
"INFO: Set new computation resolution %dx%d no. of chunks: %d\n", ..
"INFO: New computation chunk id: %d for part %d x %d\n", msg.data.compute.cid, msg.data.compute.n_re, msg.data.compute.n_im
"WARN: New computation requested but it is discarded due on ongoing computation\n"
"INFO: Chunk reset request\n"
"WARN: Chunk reset request discarded, it is currently computing\n"
"WARN: Abort requested but it is not computing\n"
"WARN: received grid results is out of range of the current grid"
"ERROR: send_message() does not send all bytes of the message!\n"
"INFO: Module restarted - '%s'\n", str
"INFO: Receive ok from Module\n"
"WARN: Receive error from Module\n"
"INFO: Abort from Module\n"
"DEBUG: received results %d\n", computation.grid[idx]
"WARN: received grid results is out of range of the current grid"
"WARN: received compute data has cid %d which is different from cid %d - cannot align data to the grid properly\x0a", msg->data.compute_data.cid, computation.cid
"WARN: Module sends new data without computing \n"
"INFO: Module reports the computation is done computing: %d\n", computation.computing
"INFO: Prepare new chunk of data cid: %d\n", msg_send.data.compute.cid
"ERROR: send_message() does not send all bytes of the message!\n"
"INFO: Call join to the thread %s\n", threads_names[i]
"INFO: Joining the thread %s has been %s\n", threads_names[i], (r == 0 ? "OK" : "FAIL")
"INFO: Exit input thead %p\n", pthread_self()
"ERROR: Unknown message type has been received 0x%x\n - '%c'", c, c
"ERROR: Cannot parse message type %d\n", msg_buf[0]
"WARN: the packet has not been received discard what has been read\n"
"ERROR: Cannot receive data from the serial port\n"
"INFO: Exit serial_rx_thread %p\n", pthread_self()
"DEBUG: Write message: "
==== Doporučený postup implementace ====
Doporučený postup implementace a tipy k implementaci naleznete zde [[courses:bab36prga:hw:hints|Doporučení a tipy pro HW9]]
====== Odevzdávané soubory ======
Odevzdávejte zip archiv, který bude obsahovat všechny podpůrné soubory, které jste dostali k dispozici, implementovanou logiku aplikace ''prga-hw09-main.c'', implementovanou frontu ''event_queue.c'', a všechny další soubory, které využíváte.
====== Odevzdání a hodnocení ======
Veřejné příklady + Makefile: {{ :courses:bab36prga:hw:bab36prga-hw09.zip |}}
Program pokud možno realizujte na cvičení, nahrajte do odevzdávacího systému. Funkčnost programu ověří učitel.
^ ^ Povinné zadání ^
^ Název v BRUTE | HW9 |
^ Odevzdávané soubory | ''prga-hw09-main.c'', ''event_queue.c'', podpůrné soubory |
^ Argumenty při spuštění | žádné |
^ Procvičované oblasti | vlákna, pojmenovaná roura |