Základní úlohou operačního systému je plánování a obsluha zdrojů počítače pro jednotlivé úlohy, které na počítači běží. Mezi takto obsluhované zdroje patří procesor, operační paměť, sekundární paměti (disky, FLASH paměti pro data apod.) a různá komunikační rozhraní, jako jsou sériové a paralelní porty všech typů.
Cílem návrhu bylo vytvořit a implementovat reálný a provozuschopný operační systém, určený pro velmi malé počítačové systémy, disponující pouze jednotkami kilobyte paměti pro program i data. Zároveň by takový systém měl podporovat preemptivní multitasking, nějakou univerzální formu meziprocesové komunikace a podporu práce s přerušením.
Jako inspirace sloužil operační systém iRMX firmy Intel Corporation. Proto jsem jako pracovní název pro tento můj operační systém zvolil jRMX. Z hlediska jednoduchosti a i prostředí, kde daný operační systém poběží, pak vlastní jádro systému obsahuje pouze obsluhu (plánovač) procesoru a implementaci jedné formy meziprocesové komunikace. Obsluhy ostatních zdrojů počítačového systému mohou být implementovány ve formě uživatelských úloh, podobně, jako je tomu u iRMX firmy Intel.
Jako prostředí pro svůj chod potřebuje operační systém kromě procesoru a paměti také jeden systémový časovač. Od něj jsou odvozeny aktivity systému jako je přeplánování úloh, reálný čas a fronta časovače.
Plánování procesoru je hlavní částí celého systému. Operační systém vytváří takzvané virtuální procesory pro každou běžící úlohu. Implementuje preemptivní multitasking s technikou „Round-robin“ pro úlohy se stejnou prioritou.
Úloha je definována kódem a daty úlohy, zásobníkem a dvěma řídicími bloky.
Tabulka 1 Stavy úlohy operačního systému jRMX
Řídicí blok úlohy (TCB) je dynamickou strukturou, kterou operační systém používá pro simulaci virtuálního procesoru přiděleného úloze. Mimo jiné obsahuje stav a prioritu úlohy, registry procesoru a hodnotu časovače. Stavy úlohy jsou uvedeny v tabulce 1 a jejich přechodový graf je na obrázku 1. Priorita úlohy může nabývat hodnoty 0 až 255, přičemž nejnižší priorita je 0, nejvyšší je 255.
Obr. 1 Přechodový graf stavů úlohy
Informační blok úlohy (TIB) je statickou strukturou, která slouží k popisu úlohy a ve fázi startu operačního systému k aktivaci řídicího bloku úlohy.
Množina služeb jádra operačního systému je poměrně malá, avšak plně odpovídající potřebám daného systému. Navíc dodatečné vlastnosti lze implementovat pomocí existujících, jak bude například naznačeno v kapitole „Použití meziprocesové komunikace“.
Tabulka 2 Funkce jádra operačního systému jRMX
Celkový seznam služeb jRMX udává tabulka 2.
Operační systém jRMX implementuje pouze jednu formu meziprocesové komunikace, nazývanou schránka (mailbox). Schránka je datovou strukturou dvou front. Jedna fronta je frontou zpráv (message), zaslaných do dané schránky. Druhou frontou je pak fronta čekajícíchúloh u dané schránky. Tím je realizován klasický mechanismus, známý z teorie operačních systémů jako „producentkonzument“.
Schránky se rozlišují na globální a lokální. Globální schránky jsou definovány při startu operačního systému a předpokládá se jejich existence po celou dobu běhu systému. Lokální schránky jsou vytvářeny za běhu programu a jsou určeny pro jednu výměnu zpráv mezi dvěma úlohami. Po této výměně zpráv mohou zaniknout.
Zpráva, zasílaná do schránky, má povinnou část (hlavičku) tak, aby operační systém mohl se zprávou pracovat a nepovinnou část, což je jakákoliv struktura, odpovídající výměně dat mezi jednotlivými úlohami.
Tento mechanismus vede k přirozenému návrhu programu jako množiny spolupracujících procesů, které si mezi sebou zasílají zprávy. Typicky jsou takovými zprávami události nebo datové pakety. Speciální zprávy jsou zasílány funkcemi pro obsluhu přerušení.
Obr. 2 Možné stavy schránky
Možné stavy schránky jsou zobrazeny na obrázku 2.
Typickým použitím výše uvedeného mechanismu je zasílání zpráv mezi úlohami. V typické aplikaci má každá úloha jednu schránku, do které ostatní úlohy a obslužné funkce přerušení zasílají různé zprávy. Úloha je řešena jako nekonečná smyčka, kdy první akcí je volání jxWait() na svoji schránku. Po vybrání zprávy úlohou je tato zpracována a situace se opakuje. Je to vlastně klasická aplikace událostmi řízeného programování.
Jako další příklad použití meziprocesové komunikace uvedu implementaci binárního a čítacího semaforu. Semafory se používají ve víceúlohových operačních systémech pro synchronizaci přístupu více úloh k nějakému sdílenému prostředku, například ke sdíleným datům v operační paměti. Část kódu, která manipuluje se sdíleným prostředkem, se nazývá „kritická sekce“. Kritická sekce bývá ohraničena operacemi uzamknutí a odemknutí semaforu. Tyto operace lze v jRMX implementovat pomocí schránky a zprávy. Jak ekvivalent binárního semaforu je vytvořena schránka a do ní je zaslána právě jedna zpráva. Tato zpráva slouží jako klíč. Uzamknutí semaforu je pak provedeno úlohou pomocí funkce jxWait() jako pokus o získání klíče. Odemknutí semaforu je pak provedeno funkcí jxSend() s předchozí získanou zprávou (klíčem). Pokud byl klíč při pokusu o odemknutí již získán jinou úlohou, je tato nová úloha pozastavena do doby, než předchozí úloha vrátí klíč (zprávu) do schránky. Při implementaci čítacího semaforu je jediná změna v tom, že do schránky je na začátku zasláno tolik zpráv, kolik chceme, aby měl čítací semafor počáteční hodnotu. Příklad kritické sekce je uveden ve výpisu č. 2.
Časová fronta slouží operačnímu systému pro obsluhu úloh, čekajících na zprávu u schránky, nebo těch, které volaly funkci jxSleep(). Je realizována jako obousměrný zřetězený seznam řídicích bloků úloh (TCB). Pro obsluhu časové fronty je definovaná funkce jxTimer-Tick(). Tato funkce musí být volána během obslužné funkce přerušení od systémového časovače.
Operační systém jRMX umožňuje ve funkci obsluhy přerušení zaslat zprávu do jakékoliv schránky. Zpráva pak typicky obsahuje doplňkovou informaci o typu přerušení, například zda je vysílací registr sériového řadiče prázdný nebo zda uplynul nějaký timeout od hardwarového časovače a podobně. Pro zaslání zprávy z obslužné funkce přerušení existuje speciální varianta jxISend().
Pro start systému je nutno definovat seznam úloh a seznam globálních schránek. Ukazatele na tyto seznamy jsou umístěny do jedné struktury takzvaného Inicializačního řídicího bloku (ICCB), který je předán jako jediný parametr funkci jxInit(). Tato funkce inicializuje všechny globální schránky, všechny úlohy připraví k běhu a pak nastartuje plánovač procesoru, který spustí úlohu s nejvyšší prioritou.
Operační systém musí mít v době startu definovánu alespoň jednu úlohu. Úloha Idle bývá touto povinnou výbavou každého systému na bázi jRMX. Tato úloha má nejnižší prioritu 0 a bývá v ní často implementován mechanismus snižování spotřeby. Vzor takové úlohy pro procesor MSP430 je uveden ve zdrojovém kódu č. 1.
Toto jsou velmi výkonné 32bitové procesory, s mnoha periferiemi a relativně velkou operační pamětí vzhledem k nárokům operačního systému jRMX.
Kód jádra zabírá asi 1280 byte. Proměnné jádra pak 160 byte. Registry procesoru jsou uschovávány v zásobníku úlohy. Proto je nutné počítat s 60 byte zásobníku navíc. Procesor obsahuje mechanismy pro podporu operačních systémů, které jRMX využívá. Prvním zmíněným jsou 2 ukazatele zásobníku. MSP (Master Stack Pointer) je systémový zásobník, použitý při startu systému a jako zásobník pro obsluhu přerušení. PSP (Process Stack Pointer) je pak využíván pro jednotlivé úlohy.
Obslužné funkce přerušení se pro tento procesor mohou psát přímo v jazyce „C“ bez nutnosti jakýchkoliv speciálních direktiv nebo #pragma definic.
Dalším mechanismem, který jRMX používá pro přepnutí kontextu úlohy je výjimka PendSV. Tato výjimka je přímo doporučována k tomuto úkolu firmou ARM. Mimo jiné výhodou je, že během přepínání kontextu úlohy není vůbec zakázáno přerušení, což u ostatních architektur není možné.
Z hlediska výkonu plánovače úloh pak tyto procesory dosahují při taktovací frekvenci 48 MHz více než 120000 zaslání zpráv mezi úlohami za sekundu. K této variantě existuje také implementace UDP/IP protokolu, ovladače řadiče Ethernet pro procesory firmy Texas Instruments řady LM3S, a také například implementace SNMP protokolu, vše na bázi několika úloh operačního systému jRMX.
Procesory MSP430 mají velmi nízký příkon a mimo jiné proto většina modelů disponuje poměrně malou kapacitou paměti RAM, řádově od 128 byte až po 4 kilobyte. Několik větších modelů pak disponuje 16 kilobyte. Pro rozumné použití operačního systému je vhodná dolní hranice 1 kilobyte paměti RAM.
Pro úschovu kontextu procesoru a lokálních proměnných při přerušení je uměle (programově) vytvořen systémový zásobník, který je použit rovněž při startu systému. Ten pak uspoří kapacitu zásobníků jednotlivých úloh. Ty nemusí mít rezervován prostor v zásobníku na případnou obsluhu přerušení. Kód jádra zabírá asi 1200 byte. Proměnné jádra pak 48 byte. Registry procesoru jsou uschovávány v zásobníku úlohy. Proto je nutné počítat s 28 byte zásobníku navíc.
Přerušení nemohou být zanořena, nesmí se tedy povolit přerušení během obslužné funkce přerušení. Obslužnou funkci přerušení je nutno psát v jazyce assembler. Výpis č. 3 ukazuje možnou realizaci takové obslužné funkce.
Operační systém jRMX je implementován jako sada reentrantních funkcí v jazyce „C“, s výjimkou funkce jxTaskSw(), která způsobuje přepnutí kontextu úlohy. Tato funkce je napsána v assembleru pro daný typ procesoru. Kromě výše uvedených procesorů je tento systém rovněž implementován na procesory ARM7/ARM9, V850 a 78K firmy NEC (Renesas).
Komerčně se tento systém používá u výrobků firmy Softlink s. r. o (řada bezdrátových zařízení WACO). Samotný operační systém je distribuován ve formě knihovny funkcí a hlavičkového souboru pro překladač jazyka „C“.
Další podrobnosti a uživatelský manuál ve formě PDF souboru je možné získat na internetových stránkách www.jsoft.cz.