RTOS Mutex

A fejezet tartalma:
Az előző fejezet végén bemutatott példaprogram három programszálat futtatott, ám ezek egymástól függetlenül végezték feladatukat, ugyanúgy, mintha három külön mikrovezérlő kártyán futottak volna. Az RTOS alkalmazások többsége azonban nem ilyen, a benne futó programszálak egy nagy feladat részfeladatait végzik, így alkalmanként szükség lehet rá, hogy tevékenységeiket egymáshoz szinkronizálják, a közös használatú erőforrásokon megosztozzanak, vagy adatot cseréljenek egymással. Az mbed-RTOS fejlett eszköztárat biztosít ezekhez a feladatokhoz (jelzőbitek, mutex, szemafor, levelesláda és üzenetek formájában), melyek közül most a megosztott erőforrások kezelésére használt mutex használatát tárgyaljuk.

Mutex (kölcsönös kizárás)

A mutex név a kölcsönös kizárás angol elnevezésének (mutual exclusion) rövidítése. Többfeladatos programozásnál előfordul, hogy két vagy több programszál egyidejűleg ugyanazt az erőforrást akarja használni, ami nem engedhető meg, mert pl. összekeverednének a soros porton vagy az USB porton kiküldött adatok. Ilyenkor versengés alakul ki, s az erőforráshoz hamarabb forduló programszál egy mutex változó segítségével lefoglalja az eszközt, majd használat után megszünteti a lefoglalást. A kritikus szakaszhoz később érkező programszál pedig várakozásra kényszerül, amíg az erőforrás fel nem szabadul. A programszálak tehát kölcsönösen kizárják egymást a kritikus szakaszon, nem fordulhat elő, hogy egyidejűleg vezérlik a közös használatú perifériát.

1. ábra: Erőforrás megosztása mutex használatával

A mutex tulajdonképpen az általánosabb szemaforok speciális esete, amikor a szemafor csak két állapotot vehet fel: 0 és 1. Ezért bináris szemafornak is nevezik (szemben az ún. számláló szemaforral, amely többféle értéket is felvehet).

A Mutex objektumosztály tagfüggvényei

A kölcsönös kizárást biztosító mutexek létrehozása és kezelése a Mutex objektumosztály segítségével végezhető, tagfüggvényeit az alábbi táblázatban foglaltuk össze.
Függvénynév
Funkció
Mutex név
Létrehoz és inicializál egy "név" nevű mutex  objektumot
lock(time)
Várakozik, amíg a mutex elérhetővé válik. Ha a mutex foglalt, a várakozás alapértelmezetten korlátlan ideig tarthat, vagy a time paraméterben ezredmásodpercekben megadott idő leteltekor időtúllépéssel kilép a várakozásból.
trylock()
Megpróbálja lefoglalni a mutex példányt, de nem várakozik, ha nem sikerül.
unlock()
Felszabadítja a korábban lefoglalt mutex példányt.

Megjegyzések és korlátozások:

Mintapélda a mutex használatára

Az alábbi mintaprogram bemutatja a mutex használatát az stdio hozzáférés védelmére (printf() függvény használata). A program lefordításához importálni kell az mbed-RTOS programkönyvtárat.

Hardver követelmények:
1. lista: A 10_rtos_mutex/main.cpp program listája
#include "mbed.h"
#include "rtos.h"

Mutex stdio_mutex;

void notify(const char* name, int state) {
stdio_mutex.lock();
printf("%s: %d\n\r", name, state);
stdio_mutex.unlock();
}

void test_thread(void const *args) {
while (true) {
notify((const char*)args, 0); Thread::wait(1000);
notify((const char*)args, 1); Thread::wait(1000);
}
}

int main() {
Thread t2(test_thread, (void *)"Th 2");
Thread t3(test_thread, (void *)"Th 3");
test_thread((void *)"Th 1");
}

A 10_rtos_mutex projekt Makefile állománya:


PROJECT         := 10_rtos_mutex
DEVICES         := KL25Z
GCC4MBED_DIR    := ../..
GCC4MBED_TYPE   := Release
MBED_OS_ENABLE  := 1
NEWLIB_NANO     := 1
NO_FLOAT_SCANF  := 1
NO_FLOAT_PRINTF := 1

include $(GCC4MBED_DIR)/build/gcc4mbed.mk
A program több újdonságot tartalmaz, amelyek magyarázatra szorulnak:

2. ábra: A 10_rtos_mutex program futási eredménye

Mintapélda: Az étkező filozófusok problémája

Az étkező filozófusok esete (az angol szakirodalomban The Dining Philosopher Problem néven ismert) egy olyan probléma, ami könnyen és gyorsan megérthető, de felmerülő problémái valós informatikai problémákká növik ki magukat. A történet így szól: Egy tibeti kolostorban öt filozófus él. Minden idejüket egy asztal körül töltik. Mindegyikük előtt egy tányér, amelyből sohasem fogy ki a rizs. A tányér mellett jobb és bal oldalon is egy-egy pálcika található a 3. ábra szerinti elrendezésben. A filozófusok életüket az asztal melletti gondolkodással töltik. Amikor megéheznek, étkeznek, majd ismét gondolkodóba esnek a következő megéhezésig. És ez így megy az idők végezetéig. Az étkezéshez egy filozófusnak meg kell szereznie a tányérja melletti mindkét pálcikát. Ennek következtében amíg eszik, szomszédai nem ehetnek. Amikor befejezte az étkezést, leteszi a pálcikákat, amelyeket így szomszédai használhatnak.

3. ábra: Étkező filozófusok

Elemezzük, milyen problémákkal szembesülhetnek a filozófusaink:
Az alábbi programban a filozófusokat egy-egy programszál képviseli, melyek többé-kevésbé véletlenszerű időtartamig gondolkodnak, majd amikor megéheznek, megpróbálják megszerezni a tányéruk két oldalán található pálcikákat (a pálcikákat egy-egy mutex reprezentálja, s a mutex sikeres lezárása az általa képviselt pálcika megszerzését jelenti). Ha sikerült megszerezni mindkét pálcikát, akkor étkezés következik (ennek időtartama is véletlenszerű), majd letesszük a pálcikákat, s újra a gondolkodásnál folytatjuk.

Könnyen belátható, hogy a holtpont elkerülése a programozó felelőssége. Ha például egyszerre minden filozófus azzal kezdi, hogy megragadja a jobb kezénél levő pálcikát, akkor máris holtpontra jutottunk! Olyan stratégiát kell tehát kidolgozni, amelyben szerepet kap a kooperativitás - azaz ha valamelyik filozófus nem tudja megszerezni mindkét pálcikát, akkor leteszi a kezéből az elsőnek felvettet is, hogy a szomszédai étkezni tudjanak. Ez önmagában még kevés a holtpont elkerüléséhez, mert ha mindegyik filozófus felveszi a jobb kezénél levő pálcikát, majd - intelligens emberek lévén - belátják, hogy ez holtpont, ezért leteszik a pálcikát, azután kezdik elölről, akkor nem haladtunk sokra! Közelebb jutunk a megoldáshoz, ha azt a módszert alkalmazzuk, amit a hálózati kártyák ütközés detektálásakor használnak: véletlenszerűen választott ideig várakoznak, majd újra próbálkoznak.

A i. programszál futásakor mutex lefoglalásánál most nem a végtelen várakozást választjuk, hanem a trylock() metódussal ellenőrizzük, hogy sikerült-e a jobboldali pálcikához tartozó i-1. mutexet lefoglalni. Ha sikerült, akkor megpróbáljuk lefoglalni a másikat (az i. mutexet) is. Ha ez is sikerült, akkor kezdődhet a falatozás, majd letesszük a pálcikákat és kezdődhet a gondolkodás. Ha viszont a második mutexet nem sikerült lezárni, akkor a kooperativitás jegyében feloldjuk az első mutexet is (nehogy holtpont alakuljon ki), majd véletlen hosszúságú ideig várunk, mielőtt újra próbálkozunk.


Hardver követelmények:
2. lista: A 10_rtos_dpp_mutex/main.cpp program listája
#include "mbed.h"
#include "rtos.h"
Mutex stdio_mutex;
Mutex chopstick[5]; //Array of mutexes representing the 5 chopsticks

void notify(int num, int state) {
stdio_mutex.lock();
if(state) {
printf("Philospher %d is EATING \n\r", num);
}
else {
printf("Philospher %d is thinking \n\r", num);
}
stdio_mutex.unlock();
}

void philosopher(void const *args) {
while (true) {
if(chopstick[(int)args-1].trylock()) {
if(chopstick[(int)args%5].trylock()) {
notify((int)args,1); //Start EATING
Thread::wait(1000+rand()%1000);
chopstick[(int)args%5].unlock(); //Release chopsticks
chopstick[(int)args-1].unlock();
notify((int)args,0); //Start Thinking
Thread::wait(2000+rand()%2000); //Get's hungry after this time...
}
else {
chopstick[(int)args-1].unlock();
Thread::wait(100+rand()%100); //Wait for random time if failed
}
}
else {
Thread::wait(100+rand()%100); //Wait for random time if failed
}

}
}

int main()
{
Thread t2(philosopher, (void *)2U);
Thread t3(philosopher, (void *)3U);
Thread t4(philosopher, (void *)4U);
Thread t5(philosopher, (void *)5U);
philosopher((void *)1U);
}

A 10_rtos_dpp_mutex projekt Makefile állománya:


PROJECT         := 10_rtos_dpp_mutex
DEVICES         := KL25Z
GCC4MBED_DIR    := ../..
GCC4MBED_TYPE   := Release
MBED_OS_ENABLE  := 1
NEWLIB_NANO     := 1
NO_FLOAT_SCANF  := 1
NO_FLOAT_PRINTF := 1

include $(GCC4MBED_DIR)/build/gcc4mbed.mk

Megjegyzések:
A program egy futásának eredménye az alábbi ábrán látható:

4. ábra: A 10_rtos_dpp_mutex program futási eredménye