Assembler a systémJan Hlavatý
Multitasking je jednou z největších výhod operačního systému Amigy. Všechny programy pod systémem běží v tomto prostředí, proto je nutné o tomto
prostředí něco vědět. To něco najdete tady! Multitasking je v podstatě
vymoženost, která umožňuje (zdánlivě) současný běh několika programů (procesů).
Toho je dosaženo přepínáním mezi těmito programy.
Z pohledu samotného běžícího programu to vůbec není poznat - běží jako by byl
jediným spuštěným programem. V systému Amigy existují v podstatě dva základní
druhy takovýchto nezávisle běžících programů:
1. TASK - ryzí jádro multitaskingu - je udržován exec.library a používá se pro
softwarová zařízení. Pro uživatelské programy není příliš vhodný, protože task
nemůže používat dos.library (viz dále)
2. PROCES - je v podstatě nadstavbou tasku. Jeho součástí je task, ale navíc má
v sobě zabudovány dodatečné informace které umožňují procesu používat funkce
dos.library. Procesy spravuje dos.library. Naprostá většina uživatelských
programů je spuštěna jako proces.
Existují dva způsoby jak spustit uživatelský program CLI a Workbench. Oba tyto
způsoby spustí váš program jako proces, ale poněkud odlišnym způsobem: CLI samo
o sobě je program, který se spustí jako proces - např. příkazem NEWSHELL. Ten si
pak otevře své okno a čeká na příkaz od uživatele - který program má spustit. Už
v tomto okamžiku proces CLI běží. Když uživatel napíše do okna CLI jméno
příkazu, CLI ho nahraje do paměti a spustí ho jako PODPROGRAM. Po návratu z
tohoto podprogramu se CLI vrátí zpět k čekání na příkaz uživatele - proces
existuje pořád, až do jeho ukončení příkazem ENDSHELL (nebo endcli). Teprve
potom přestává proces CLI existovat. Proces CLI poskytuje spuštěnému programu
některé informace, jako např. standardní vstup a výstup směřující obvykle do
okna CLI (pokud nebyl přesměrován jinam) a příkazová řádka, na které jsou
uvedeny argumenty. Workbench přistupuje ke spouštění aplikací jinak - běží sice
sám také jako proces, ale když má něco spustit (např. nakliknete 2x ikonu
programu) nahraje kód programu do paměti a ten spustí jako jiný, NEZÁVISLÝ
proces. Argumenty jsou v tomto případě předány pomocí mechanismů pro komunikaci
mezi procesy - zpráv a portů (viz dále). Exec.library má seznam všech tasků
(tedy i procesů), ve kterém má každý task svoji prioritu. Běží vždy task s
nejvyšší prioritou. Pokud je víc tasků se stejnou prioritou, přepínají se mezi
sebou v určitých intervalech. Priorita může nabývat hodnot z rozsahu -128..+127.
Normální uživatelské procesy mají prioritu 0. Je jasné, že tasky s nižší
prioritou by se těžko dostávaly ke slovu - proto existuje finta, jak předat
nevyužitý čas procesů s vyšší prioritou těm s nižší prioritou: Jakmile proces na
něco čeká, nedělá to v klasické čekací smyčce, ale "spí" pomocí funkce
exec.library Wait(). Tato funkce umožňuje čekat na tzv. SIGNÁL tak, že daný task
je dočasně pozastaven. Pokud přijde tasku signál na který čekal, opět se
"probudí". Signál je v postatě bit ve struktuře tasku. Každý task má několik
systémových signálů (např. čtyři signály pro BREAK - když stisknete v okně CLI
klávesy ctrl-C, ctrl-D, ctrl-E nebo ctrl-F, je procesu CLI vyslán odpovídající
signál. Totéž lze dosáhnout příkazem break z DOSu.) Navíc má každý tank zhruba
16 signálů které jsou k dispozici uživateli. Tyto signály je třeba alokovat než
budou použitelné. Signál pro task může vyslat buďto jiný task, nebo i obslužná
rutina přerušení.
Tento případ je běžný u tasků obsluhujících nějaké hardwarové zařízení, které o
obsluhu požádá přerušením. Task pak nezabírá žádný strojový čas, pokud to není
nutné. Signály se hojně používají v komunikaci mezi procesy. V praxi to vypadá
tak, že task si alokuje funkcí AllocSignal() exec.library patřičný signál na
který bude čekat, a pak čeká pomocí funkce Wait(), které předá masku všech
signálů, při jejichž přijetí se má čekání ukončit. Nějaký jiný task nebo
přerušení pak mohou (pokud znají který signál mají použít) vyslat signál pomocí
funkce Signal. Tato Funkce vyžaduje jak číslo signálu, tak i pointer na task
kterému má být signál vyslán. Při "úklidu" před ukončením programu by se měly
všechny alokované signály opět uvolnit funkcí FreeSignal(). Jinak by se mohlo
lehce stát, že po několikerém spuštění z CLI vám signály dojdou (jak jsem uvedl
výše, proces CLI existuje stále tedy i jeho alokované signály zůstanou
alokované). Tolik zatím k signálům. Seznamy (LISTS)
Další částečkou skládanky kterou budeme potřebovat jsou seznamy. Co to je?
Je to jeden ze základních softwarových objektů. Slouží k udržování jakýchsi
skupin datových struktur (uzlů - nodes), navzájem propojených pomoci pointerů
(čili adres) ukazujících na následující či předchozí uzel. Takový seznam může
sloužit k různým účelům. Jeho hlavním znakem je snadnost připojování nových
prvků a jejich odebírání, bez nutnosti posouvat nějaké pole. V seznamu by mohly
například být uloženy řádky textu v textovém editoru. Přidávání nových řádek
někam doprostřed by pak bylo snadnější než třeba posouvání kusu textu v paměti
tak aby vzniklo místo na nový řádek. Když chcete něco zapojit doprostřed
seznamu, prostě ho na některém místě rozpojíte, vložíte nový uzel a pak oba
konce připojíte k tomuto uzlu. V klasických školách programování bývá většinou
ukazatel na další uzel u posledního uzlu nulový, tak se detekuje konec seznamu.
Začátek seznamu bývá uložen v nějakém pointeru. Tento postup poněkud
znepříjemňuje operace se seznamem, protože je třeba dávat si neustále pozor kam
nový uzel zapojujeme -jestli to náhodou není začátek nebo konec seznamu, protože
tam se to musí udělat jinak kvůli té nule. Programátoři exec.library na to šli
"od lesa". Všechny uzly v seznamu jsou propojeny oběma směry. Každý seznam v
systému má svoji "hlavičku" - strukturu MinListHeader (MLH). Ta se používá
kdykoliv chceme něco dělat se seznamem jako s celkem. Tato struktura je
definována v include souboru "exec/lists.i":
APTR MLH_HEAD ;pointer na první node v seznamu
APTR MLH_TAIL ;ukončovací 0 (NULL) APTR
MLH_TAILPRED ;pointer na poslední node v seznamu
Všechny uzly v seznamu pak začínají strukturou MinimalListNode (MLN),
definovanou v include souboru "exec/nodes.i":
APTR MLN_SUCC ;pointer na následující uzel
APTR MLN_PRED ;pointer na předchozí uzel
Hlavička seznamu je ve skutečnosti kombinací dvou překrývajících se uzlů -
prvního a posledního. Z hlediska klasického programování tedy seznam obsahuje
stále minimálně dva uzly - první a poslední. První uzel sestává z pointerů
MLH_HEAD jako ukazatel na další node a MLH_TAIL jako ukazatel na předchozí,
který je vždy NULL. Poslední prvek seznamu sestává z MLH_TAIL jako ukazatele na
další prvek (vždy NULL) a z MLH_TAILPRED jako ukazatel na předchozí uzel. Touto
fintou zmizí ony speciální případy s prvním a posledním prvkem seznamu, protože
veškeré prvky zapojené do seznamu se nacházejí uvnitř, mezi prvním a posledním
"falešným" uzlem. To s sebou samozřejmě nese trochu rozdílnou práci se seznamem
- zejména pokud jde o detekci konce seznamu při prohledávání. Netestuje se totiž
jestli TENTO uzel nemá jako ukazatel na další uzel NULL, ale jestli NÁSLEDUJÍCÍ
uzel nemá v MLN_SUCC NULL. Pokud si chcete vytvořit svůj vlastní seznam v tomto
stylu, musí být hlavička před použitím inicializována takto:
MLH_HEAD ukazuje na MLH_TAIL položku headeru MLH_TAIL musí být 0 MLH_TAILPRED
ukazuje na položku MLH_HEAD headeru.
;předpokládejme že v A0 je adresa hlavičky:
MOVE.L A0,LH_TAILPRED(A0)
ADDQ. L #4,A0
CLR.L (A0)
MOVE.L A0,-(A0)
Tento úsek kódu je obsažen v systémovém makru NEWLIST, které jako argument chce
adresový registr s adresou hlavičky. Struktury které jsem právě popsal jsou
minimálními verzemi hlavičky a uzlu, v systému se většinou používají struktury
rozšířené o typ, prioritu a jméno:
STRUCTURE LH,O
APTR LH_HEAD ;min. verze
APTR LH_TAIL ;hlavičky
APTR LH_TAILPRED ;^^
UBYTE LH_TYPE ;plus typ (NT_...)
UBYTE LH_pad
LABEL LH_SIZE ;word aligned
STRUCTURE LN,0 ;List Node
APTR LN_SUCC ; Pointer na další (successor)
APTR LN_PRED ; Pointer na předchozí (predecessor)
UBYTE LN_TYPE ; typ (NT_...)
BYTE LN_PRI ; Priorita, pro zatřídění
APTR LN_NAME ; Jméno, string ukončený 0
LABEL LN_SIZE ; word aligned
Tyto rozšířené struktury umožňují kontrolu typu uzlu v seznamu, zatřiďování do
seznamu podle priority a vyhledávání v seznamu podle jména uzlu. Pro práci s
těmito seznamy poskytuje exec.library několik funkcí:
AddHead(A0:list,A1:node) přidá node na začátek seznamu
AddTail(A0:list,A1:node) přidá node na konec seznamu
Insert(A0:list,A1:node,A2:zanode) přidá node do seznamu ZA zanode
RemHead(A0:list)->DO:node/NULL odpojí první node ze seznamu a vrátí pointer na
něj nebo NULL když je seznam prázdný
RemTail(A0:list)->DO:node/NULL odpojí poslední node ze seznamu a vrátí pointer
na něj nebo NULL když je seznam prázdný
Remove(A1:node) odpojí node ze seznamu
Enqueue(A0:list,A1:node) - POUZE PRO ROZŠÍŘENĚ VERZE STRUKTUR (LH_...) - zatřídí
node do seznamu podle priority - čím vyšší priorita, tím bliž k začátku. Uzly se
stejnou prioritou jsou řazeny FIFO
FindName(A0:list,A1:name)->DO:node/NULL POUZE PRO ROZŠÍŘENÉ VERZE STRUKTUR -
najde v seznamu první uzel se stejným jménem a vrátí ptr na něj nebo NULL když
není žádný s takovým jménem. Pokud může existovat více uzlů se stejným jménem,
je možné pro hledání dalšího dát místo ukazatele na hlavičku seznamu adresu
posledně nalezeného node - pak se bude hledat až od něj dál.
Seznamy se používají zejména jako FIFO FRONTY při předávání zpráv mezi procesy,
ale i všechny systémové objekty (např. knihovny nebo devices) jsou zapojeny do
seznamů - když třeba zavoláte funkci OpenLibrary() se jménem knihovny, systém
vyhledává otevíranou knihovnu ve svém seznamu pomocí FindName(). Komunikace mezi procesy
Procesy mezi sebou komunikují pomocí ZPRÁV (messages) a PORTŮ (ports). Port
je analogií poštovní schránky kterou daný task občas kontroluje, nebo může čekat
(spát) než se na portu objeví nějaká zpráva. Zprávy (messages) jsou jako dopisy
- jeden task pošle jinému dopis a počká si na odpověď... Message je v podstatě
kus paměti s vyplněnými informacemi, který odesílající tank PŮJČI adresátovi
jakmile si adresát přečte informace ze zprávy, musí strukturu VRÁTIT - čímž
potvrdí její přijetí. Odesílatel pak nesmí data v poslané zprávě měnit dokud mu
nepřijde zpátky, protože neví jestli si adresát data už přečetl. Adresát by měl
po přečtení dat message co nejrychleji vrátit zpět, aby nezdržoval odesílatele,
který je nucen na vrácení zprávy čekat. Ve skutečnosti je port datová struktura,
která obsahuje hlavičku seznamu došlých zpráv plus údaje co se má stát když
přijde nová zpráva. Tzv. veřejné (PUBLIC) porty mají ještě svoje jméno a sou
zařazeny do systémového seznamu portů, a ty mohly být kýmkoliv nalezeny podle
jména. To je jako by dal task svoje telefonní číslo do veřejného telefonního
seznamu. V závislosti na nastavení portu může příchod nové zprávy způsobit tři
věci: - vyslat signál tasku vlastnícímu port - vyvolat softwarové přerušení -
nic. Nejčastěji se používá signál tasku, protože adresát obvykle čeká na příchod
zprávy pomocí Wait(), aby zbytečně nezabíral čas jiným taskům. Pokud potřebujete
adresu tasku (tedy jeho datové struktury), použijete funkci exec.library
FindTask(A1:jméno). Je-li jméno NULL, vrátí funkce adresu vašeho vlastního
tasku, jinak se pokusí vyhledat v seznamu tasků task se stejným jménem. Takto je
struktura portu definována v include "exec/ports.i":
STRUCTURE MP,LN_SIZE ;rozš. node včetně jména
UBYTE MP_FLAGS ;akce když dorazí message
UBYTE MP_SIGBIT ;číslo signálu pro task
APTR MP_SIGTASK ;ptr na task/ptr na SoftInt str.
STRUCT MP_MSGLIST,LH SIZE ;seznam došlých messages
LABEL MP_SIZE
MP FLAGS může nabývat těchto hodnot:
PA_SIGNAL - vyšle signál č. MP_SIGBIT tanku MP_SIGTASK
PA_SOFTINT - vyvolá softwarové přerušení MP_SOFTINT
PA_IGNORE - ignoruje příchod message Struktura message vypadá takto:
STRUCTURE MN,LN SIZE ;node pro zapojení do seznamu
APTR MN_REPLYPORT ;port kam poslat message zpátky
UWORD MN_LENGTH ;délka message v bajtech včetně MN_SIZE
LABEL MN_SIZE ;dále následují posílaná data
Následující funkce pracují s porty a zprávami:
AddPort(A1:port) - přidá port s vyplněným jménem do systémového seznamu public
portů
RemPort(A1:port) - odstraní public port ze systémového seznamu
FindPort(A1:jméno)->DO:port/NULL - pokusí se najít v syst. seznamu portů port se
stejným jménem a vrátí jeho adresu. Pokud nenajde, vrátí NULL.
GetMsg(A0:port)->DO:message/NULL - vezme nejstarší došlý message z portu a vrátí
jeho adresu. Pokud na portu žádny message není, vrátí NULL.
PutMsg(A0:port,A1:message) - pošle message na port - způsobí patřičnou akci pro
došlé message
ReplyMsg(A1:message) - vrátí došlý message zpátky odesílateli poté, co byl
zpracován
WaitPort(A0:port)->DO:message - počká až na daný port přijde message. Poté co
funkce skončí, na portu může být jedno nebo i víc messages. Pro jejich
vyzvednutí je třeba volat opakovaně GetMsg() (dokud nebude NULL).
CreateMsgPort()->DO:port/NULL (V36) - Alokuje z volné paměti port, inicializuje
ho, alokuje signál a nastaví port na posílání signálu při příchodu message.
Pokud to má být public port je třeba v něm vyplnit LN_NAME na jméno portu a
LN_PRI na prioritu a potom použít AddPort().
DeleteMsgPort(A0:port) (V36) - Uvolní alokovaný port zpět do volné paměti. Pokud
byl předtím přidán do systémového seznamu portů, je třeba ho odtamtud odstranit
pomocí RemPort(). Než bude voláno DeleteMsgPort(), nesmí už na portu být žádné
message (všechny message musí být zpracovány a vráceny přes ReplyMsg()).
Pokračování příště Vytlačiť článok
Pozn.: články boli naskenované ako text a preto obsahujú aj zopár chýb. Taktiež neručíme za zdrojové kódy (Asm, C, Arexx, AmigaGuide, Html) a odkazy na web. Dúfame, že napriek tomu vám táto databáza dobre poslúži.
Žiadna časť nesmie byť reprodukovaná alebo inak šírená bez písomného povolenia vydavatela © ATLANTIDA Publishing
none
|