Programszálak (Threads)
A fejezet tartalma:A C programok általános építőelemei a függvények, amelyeket azért hívunk meg, hogy valamilyen eljárást, előírt utasítás-sorozatot elvégezzenek. Az RTOS környezet általános építőköve a végrehajtási szál vagy programszál (thread), ami önálló végrehajtási egységként működő objektum, szekvenciálisan végrehajtható utasítás-sorozat. A programszál nagyon hasonlít a C függvényekhez, de van néhány fontos különbség: A meghívott C függvény mindig visszatér az őt meghívó programrészhez. A programszál viszont egy olyan függvény, amelyből nincs visszatérés: kötelezően tartalmaznia kell egy végtelen ciklust. A programszál tehát úgy vehető, mintha egy független program lenne, ami az RTOS környzeteben fut. Az RTOS alkalmazások több programszálból állnak, amelyek látszólag párhuzamosan futnak, s minden programszál külön feladatot lát el. A programszálak futásának összehangolását az ütemező végzi el, ami az RTOS rendszer központi eleme.
Az mbed-RTOS környezetben a programszálakat a Thread objektumosztály példányaiként hozhatjuk létre, s ezek saját veremtár területtel rendelkeznek. A programszálankénti veremtár alapértelmezett mérete (DEFAULT_STACK_SIZE) = 2 kB.
Az első programszál mindig a main() függvény lesz, amely automatikusan létrejön, ezt nekünk már nem kell Thread objektumként példányosítani. A main() függvényt is beleértve összesen legfeljebb 6 programszálat hozhatunk létre.
Programszálak létrehozása
Programszálakat a Thread objektumosztály példányosításával hozhatunk létre. Csak az első paraméter megadása kötelező, a többi paraméternek van alapértelmezett értéke.Thread mythread(void(*task)(void const *argument), // Függvénymutató
void *argument=NULL, // Átadandó adatra mutató pointer
osPriority priority=osPriorityNormal, // Programszál prioritása
uint32_t stack_size=DEFAULT_STACK_SIZE, // Veremtár mérete
unsigned char *stack_pointer=NULL // Veremtár mutató
A konstruktor meghívásakor első paraméterként egy speciális függvénymutatót kell megadnunk. Az a függvény, amire rámutatunk, adja meg a programszál végrehajtandó utasítás-sorozatát, s formailag az alábbi feltételeket kell, hogy kielégítse:
- A függvény végtelen ciklust tartalmaz, vagyis sohasem "térhet vissza".
- A függvény void típusú, azaz nincs visszatérési értéke.
- A függvény egyetlen paramétert vár, ami void const *arg típusú, azaz típus nélküli mutató.
A konstruktor harmadik paramétere a programszál futási prioritását adja meg (alapértelmezetten osPriorityNormal). A programszálakhoz rendelhető prioritás értékeket az alábbi táblázatban foglaltuk össze:
1. táblázat: Egy Thread objektum futási prioritásának lehetséges értékei
Szimbólum |
Érték |
Prioritás |
---|---|---|
osPriorityIdle |
-3 |
Tétlen (legalacsonyabb) |
osPriorityLow | -2 |
Alacsony |
osPriorityBelowNormal | -1 |
Normál alatti |
osPriorityNormal | 0 |
Normál |
osPriorityAboveNormal | 1 |
Normál fölötti |
osPriorityHigh | 2 |
Magas |
osPriorityRealtime | 3 |
Valósidejű (legmagasabb) |
osPriorityError |
0x84 |
Nem meghatározott, vagy
érvénytelen prioritás |
Programszálak futási állapotai
Egy Thread objektum az alábbi állapotok valamelyikében lehet:- RUNNING: A programszál pillanatnyilag futó állapotban van. Egyszerre csak egyetlen programszál lehet ebbe az állapotban.
- READY: A programszál futásra kész állapotban van (egyidejűleg több programszál is lehet ebben az állapotban). Amint az éppen futó programszál abbahagyja a tevékenységét, az ütemező a READY állapotú programszálak közül a legmagasabb prioritásúnak adja át a vezérlést (az lesz az új RUNNING állapotú programszál).
- WAITING: A programszál valamilyen eseményre várakozik.
- INACTIVE: Nem létrehozott, vagy leállított programszál(ak).
1. ábra: A programszálak lehetséges állapotai
A Thread objektumosztály tagfüggvényei
A Thread osztály tagfüggvényeit és statikus tagfüggvényeit az alábbi két táblázatban foglaltuk össze. A két típus között az a fő különbség, hogy a publikus tagfüggvények az objektumpéldányhoz rendeltek, így az objektumpéldány metódusaként hívhatók meg, mint pl. a set_priority() tagfüggvény az alábbi példában. Thread thread2(music); //Define a new task
thread2.set_priority(osPriorityHigh); //Give it high priority
A statikus publikus tagfüggvények
ezzel szemben nem tartoznak egyetlen objektumpéldányhoz sem, hanem az
osztályhoz tartoznak. Meghívásuk az osztály neve és a scope operátor
segítségével történhet, mint az alábbi példában: Thread::wait(1000); //Wait for 1000 msec
2. táblázat: A Thread osztály publikus tagfüggvényei
Függvény neve |
Funkciója |
---|---|
terminate() |
Bezárja a programszál futását és
kiveszi az aktív feladatok közül |
set_priority(pri) |
Beállítja/módosítja a
programszál futási prioritását |
get_priority()
|
Lekérdezi az aktuális
programszál prioritását |
signal_set(signals) |
Beállítja az aktuális programszál megadott jelzőbitjeit |
signal_clr(signals) |
Törli az aktuális programszál
megadott jelzőbitjeit |
get_state() |
Lekérdezi az aktuális
programszál állapotát |
stack_size() |
Lekérdezi a programszál
rendelkezésére álló veremtár méretét |
free_stack() |
Lekérdezi a programszál rendelkezésére álló, még szabad veremtárterület méretét |
used_stack() |
Lekérdezi a programszál által felhasznált veremtárterület méretét |
max_stack() |
Lekérdezi a programszál által a futása során felhasznált veremtárterület maximális méretét |
3. táblázat: A Thread osztály statikus publikus tagfüggvényei
Függvény neve |
Funkciója |
---|---|
signal_wait(signals,time) |
Várakozás indítása egy vagy több
eseményjelzőre vonatkozóan. Az opcionális time paraméter a maximális
várakozási időt adja meg ezredmásodpercekben. Alapértelmezett értéke osWaitForever, azaz végtelen
várakozás. |
wait(time) |
Várakozás a time paraméterben
ezredmásodpercekben megadott ideig |
yield() |
Átadja a vezérlést a következő
READY állapotú programszálnak. |
gettid() |
Lekérdezi az éppen futó
programszál azonosító számát |
Mintapélda: Egy többfeladatos RTOS alkalmazás
Az alábbi mintaprogramban három feladat fut párhuzamosan, három programszál felhasználásával.- A főprogram az első szál, ami a FRDM-KL25Z kártya MMA8451Q gyorsulásmérőjét kérdezi le 100 ms-onként, s a gyorsulásvektor X,Y,Z komponensei szerint vezérli a FRDM-KL25Z kártya RGB LED-jének színkomponenseit.
- A második programszál a PWM fejezetben bemutatott zenelejátszó mintapéldát futtatja végtelen ciklusban. A zene megszólaltatásához egy piezo csipogót kell kötni a D3 (PTA12) kivezetésre.
- A harmadik programszál az A0 analóg bemenetre (PTB0 kivezetés) kötött MCP9700A analóg hőmérő jelét méri meg, átlagolja, s az eredményt kiírja a standard kimenetre (az UART0 soros porton keresztül, 9600 bit/s, 8N1 formátum).
Hardver követelmények:
- FRDM-KL25Z kártya
- Piezo csipogó a D3 (PTA12) kivezetés és GND közé kötve
- MCP9700A analóg hőmérő az A0 (PTB0) bemenetre kötve
#include "mbed.h"
#include "rtos.h"
#include "MMA8451Q.h"
MMA8451Q acc(PTE25,PTE24,0x3A); //SDA, SCL, I2C address lef shifted
PwmOut rled(LED_RED); //configure RGB LED pins as PWM outputs
PwmOut gled(LED_GREEN);
PwmOut bled(LED_BLUE);
PwmOut buzzer(D3); //used to play music
float frequency[]= {659,554,659,554,550,494,554,587,494,659,554,440}; //frequency array
uint8_t beat[]= {2,2,2,2,2,1,1,2,2,2,2,4}; //beat array
void music(void const *args)
{
while (1) {
for (int i=0; i<12; i++) {
buzzer.period(1/frequency[i]); // set PWM period
buzzer=0.5; // set duty cycle
Thread::wait(250*beat[i]); // hold for beat period
}
}
}
void thermometer(void const *args)
{
AnalogIn ain(A0); // Analog input at PTB0
uint32_t mysum; // Used for summation
printf("\r\nTask3: analog thermometer - with averaging\r\n");
while(1) {
mysum = 0;
for(int i=0; i<3300; i++) {
mysum += ain.read_u16(); // sum up raw 16-bit data
}
float voltage = mysum>>16; // voltage in millivolts
float tempC = (voltage -500)/10; // tempereature in Celsius
printf("voltage: %5.0f mV temp: %5.1f C\r\n",voltage,tempC);
Thread::wait(2000);
}
}
int main(void)
{
Thread thread2(music); //Define a new task
thread2.set_priority(osPriorityHigh); //Give it high priority
Thread thread3(thermometer); //Define another new task
thread3.set_priority(osPriorityLow); //Give it high priority
while (true) { //Run the default task
float x, y, z;
x = abs(acc.getAccX()); //Read X component of acceleration
y = abs(acc.getAccY()); //Read Y component of acceleration
z = abs(acc.getAccZ()); //Read Z component of acceleration
rled = 1.0f - x; //Negative logic is used as the LEDs
gled = 1.0f - y; //are of common anode type...
bled = 1.0f - z;
Thread::wait(100); //Time period is ~ 100 ms
}
}
Megjegyzések:
- Ha új projektet hozunk létre, importálnunk kell az mbed-RTOS programkönyvtárat, a program elején be kell csatolnunk az "rtos.h" fejléc állományt és a main() függvény mellett létre kell hoznunk legalább egy további Thread objektumpéldányt.
- A FRDM-KL25Z kártya
gyorsulásmérőjének használatához a fenti programba az mbed Components
"Szenzorok" gyűjteményéből Emilio
Monti MMA8541Q programkönyvtárát
importáltuk. Hasonló néven másik programkönyvtár is elérhető, de annak
alkalmazásprogramozói felülete más, emiatt nem kompatibilis a
programunkkal!