AMIGA REVIEW online
  Uvodná stránka     Software     Hry     Obaly     Download     Amiga na PC     Amiga Forever  

Assembler a systém

Jan 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


© ATLANTIDA Publishing Všechna práva vyhrazena.
Žádna část nesmí být reprodukována nebo jinak šířena bez písemného svolení vydavatele.



Amiga na Vašem PC rychle, snadno a zdarma!


none

AMIGA REVIEW

57 ( 11-12 / 2000 )
56 ( 9-10 / 2000 )
55 ( 7-8 / 2000 )
54 ( 5-6 / 2000 )
53 ( 3-4 / 2000 )
52 ( 1-2 / 2000 )
 
51 ( 12 / 1999 )
50 ( 11 / 1999 )
49 ( 10 / 1999 )
48 ( 9 / 1999 )
46-47 ( 7-8 / 1999 )
45 ( 6 / 1999 )
44 ( 5 / 1999 )
43 ( 4 / 1999 )
42 ( 3 / 1999 )
41 ( 2 / 1999 )
40 ( 1 / 1999 )
 
39 ( 12 / 1998 )
38 ( 11 / 1998 )
37 ( 10 / 1998 )
36 ( 9 / 1998 )
35 ( x / 1998 )
34 ( x / 1998 )
33 ( 1-2 / 1998 )
 
32 ( 11-12 / 1997 )
31 ( 9-10 / 1997 )
30 ( 7-8 / 1997 )
29 ( 6 / 1997 )
28 ( 5 / 1997 )
27 ( 4 / 1997 )
26 ( 3 / 1997 )
25 ( 2 / 1997 )
24 ( 1 / 1997 )
 
23 ( 12 / 1996 )
22 ( 11 / 1996 )
21 ( 10 / 1996 )
20 ( 9 / 1996 )
18-19 ( 7-8 / 1996 )
17 ( 6 / 1996 )
16 ( 5 / 1996 )
15 ( 4 / 1996 )
14 ( 3 / 1996 )
13 ( 2 / 1996 )
12 ( 1 / 1996 )
 
11 ( 12 / 1995 )
10 ( 11 / 1995 )
9 ( 10 / 1995 )
8 ( 9 / 1995 )
7 ( 7 / 1995 )
6 ( 5 / 1995 )

ATLANTIDA NEWS

5 ( 3 / 1995 )
4 ( 1 / 1995 )
 
3 ( 11 / 1994 )
2 ( 9 / 1994 )
1 ( 7 / 1994 )
0 ( 5 / 1994 )