Assembler a systém

Jan Hlavatý

... a ještě k multitaskingu

Vítejte v druhé části naší malé exkurze do vnitřku multitaskingu Amigy. Minule jsme si uvedli některé základní skutečnosti, se kterými se ohledně multitaskingu setkáváme. Jakákoli teorie je bez patřičných příkladů praktického využití poněkud nestravitelná, takže jsem pro vás připravil prográmek na hraní... Navíc jsem nakreslil nějaké ilustrační obrázky k seznamům, protože z textu není moc vidět, jak to funguje. Těch obrázků teď bude asi mnohem víc, aby ten text nevypadal tak suše... Pokud se divíte, proč tak málo vysvětluji assembler jako takový a radši se hrnu rovnou do systému, důvod je nasnadě: O assembleru jako takovém se dneska dá ledacos sehnat (myslím tím různé knížky atd.), i když to většinou postrádá spoustu důležitých informací. Tyto "díry" jsem se pokusil ucpat v prvních dílech seriálu... Narozdíl od assembleru však o operačním systému neseženete skoro nic - jediná knížka kterou jsem viděl se zabývá výlučně OS 1.3 a je plná příkladů jak se nemá programovat... Problém je, že kvantum informací o OS Amigy vydá na knihu tlustou 30 cm, pokud je podáno dostatečně vyčerpávajícím způsobem. Pro tento seriálek jsem tedy zvolil taktiku jednotlivých úzce zaměřených témat. V každé takové kapitolce se pokouším shrnout "pozadí" dané tématiky a pak popsat jednotlivé funkce s ní související... Neočekávejte tedy nějaký kompletní výpis všeho o všem... spíš "jak na to, když..."
Takže teď k výpisu programu "message.s". Účelem programu je ilustrovat komunikaci mezi procesy, kterou jsem popisoval minule. Konkrétně posílání messages přes veřejný port. Jak se to používá? Program je v podstatě schopen fungovat dvěma způsoby:
1. Jako přijímač message,
2. Jako vysílač message.
Abyste to vyzkoušeli, otevřete si 2 okna CLI (2 různé procesy) a v obou spusťte program "message" (patřičně přeložený) Program po spuštění automaticky hledá veřejný port se jménem "AHOJ_PORT". Pokud takový port v systému přítomen není, program ho vytvoří a bude fungovat jako přijímač - vypisovat došlá messages dokud nezmáčknete ctrl-c. Pokud "AHOJ_PORT" v systému existuje, program funguje jako vysílač - pošle na něj message s textem a skončí. Jednoduché, ne? Původně to měly být 2 různé programy lišící se jen v malém úseku kódu, tak jsem je spojil do jednoho abyste toho nemuseli opisovat 2x víc... Takže: spustili jste? Situace by měla vypadat jako na obrázku. Text, který vypíše přijímací strana pochází od vysílajícího, i když ve skutečnosti je obsažen také v přijímacím programu (anžto je to ten samý pro ram). Pokud nevěříte, zkuste si udělat dvě různé verze programu message lišící se od sebe vysílaným textem (stačí přepsat text za návěstím "posilanytext", uložit pod jiným jménem (např. "message2.s" a přeložit (mimochodem, už je PhxAss v4.20).
Ještě k veřejným portům: Pozor na ně! Veřejný port spravuje obvykle nějaký jiný program a může si s ním dělat co chce - tedy například v jakémkoliv okamžiku ho ze systému odstranit a zrušit! Pokud tedy chcete na veřejný port něco posílat, musíte učinit jistá bezpečnostní opatření . Musíte se ujistit že v okamžiku kdy něco posíláte port ještě existuje a že nezmizí dřív než tam stihnete něco poslat. Kdyby zmizel, nastala by nepříjemná situace - zhroucení systému, v lepším případě se vám vaše message už nikdy nevrátí a budete na něj čekat do vánoc (a to ještě bůh ví, kterého roku). Taktéž si musíte dát pozor když VY jste vlastníkem veřejného portu - než port úplně zrušíte, musíte ho odstranit ze seznamu veřejných portů (aby už nebyl k nalezení) a POTOM ještě zpracovat nebo alespoň VRÁTIT messages, které ještě na portu zbyly. Teprve potom je možno bezpečně port zničit... Pokud chcete vidět jak to všechno provést, jukněte do "message.s" - jsou tam dvě rutiny: SoukromyMod a VerejnyMod. VerejnyMod je úsek kódu pro přijímací mód, SoukromyMod je vysílací. U vysílání si všimněte že dvojice volání FindPort() a PutMsg() je uzavřena mezi Forbid() a Permit(). Funkce Forbid() pozastaví přepínání tasků a tedy běh všech ostatních tasků kromě vašeho, Permit() přepínání tasků zase povolí. To je dobré především na situace kde by mohly vzniknout konflikty mezi tasky/procesy. V našem případě by se bez Forbid()/Permit() mohlo stát, že těsně po tom, co náš task dokončí funkci FindPort() s tím že port našel, druhy task vlastnící dotyčný port by ho mohl právě v tom okamžiku zlomyslně zrušit - ještě než bychom na něj poslali message pomocí PutMsg(). My bychom pak poslali message na neexistující port, a máme zaděláno na havárii systému... Protože jsme však vychytralí, zakážeme na kritickou dobu mezi FindPort() a PvtMsg() přepínání tasků a port nám zmizet nemůže ani kdyby chtěl (protože ostatní tasky stojí). Jasný?
Teď se podíváme na přijímání a vlastnictví veřejného portu. Než začnete čekat na příchod message na váš port, je dobré vždycky zkusit, Jestli tam už nějaké nejsou, protože funkce Wait() nějakou tu chvilku zabere a proč čekat, když mi třeba přišlo víc messages najednou? Chceme přece být příslovečně rychlí... Takže obecně pro přijímání platí: Zkoušet GetMsg() a zpracovávat došlé messages dokud funkce GetMsg() nevrátí NULL (0). Pak teprve začít uvažovat o nějakém Wait(). Tenhle mechanismus funguje skoro všude - třeba při zpracování událostí v okně intuitionu. Další dobrý "grif" je vrátit message co nejdřív to bude možné, aby se nic zbytečně nezdržovalo... Pokud se podíváte do "message.s" na VerejnyMod, vidíte jak si dát pozor na zapomenutá message na odstraňovaném portu. Další ptákovinkou zde zabudovanou je primitivní kontrola jestli je to moje message nebo ne. Tohle jsem zabudoval kvůli ARexxu - zmatený uživatel by klidně mohl pomocí ARexxu poslat message někam kde vůbec nemá co dělat. Můžete to zkusit. Pusťte "message" a ARexx. Když zadáte v CLI:

rx "say show(p,,ax)"

vypíše se seznam veřejných portů v systému, z nichž však žádný nemusí být nutně port ARexx-ový. Bludné ARexxové message si na port "AHOJ_PORT" můžete poslat třeba takto:

rx "address AHOJ_PORT baf!"

Protože jsme tento případ částečně v "message" ošetřili, nezhroutíme se, což ale nemusí být nutně případ všech veřejných portů v systému takže: neposílat na neznámé porty! Pokud má aplikace ARexxový port, jistě je jeho jméno někde napsáno...
Jak jste si jistě všimli, vypisuje "message" hlášky do okna CLI pomocí šikovné rutinky, která funguje na OS 2.0... Ne že by něco podobného nešlo i na 1.3, ale tam by to člověku dalo mnohem víc práce (pomocí funkcí Output(), RawDoFmt(), Write() a ještě by si musel udělat buffer na text, jinak je výpis textu stráááášně pomalý). Takže - tahle rutinka se vám bude určitě hodit... Umí totiž víc než jen opsat přesně text - umí i vypisovat řetězce a čísla v různých formátech... V podstatě jde o variantu standardní funkce jazyka C - printf(). Jako základní parametr zadáte funkci tzv. formátovací řetězec - tj. text, ve kterém jsou označena místa kde se má vypsat nějaká hodnota a jakým způsobem. Řetězcem samozřejmě rozumíme řadu znaků ukončenou nulovým bajtem (tzv. C-string, protože tento způsob ukládání řetězců je známý z jazyka C). Všimněte si způsobu předávání parametrů: Hodnoty jsou ukládány jako LONGy na zásobník, odkud si je funkce přečte. Ukládají se od poslední k první, čímž pak na zásobníku vznikne za návratovou adresou podprogramu skupina LONGů s hodnotami od první do poslední. Tento způsob předávání parametrů je obvyklý v jazyku C umožňuje rekurzivní volání podprogramů. Samozřejmě po návratu z podprogramu musíte nahromaděné hodnoty ze zásobníku zase odstranit - jinak to špatně dopadne (zhroucení při prvním RTS...) - pozor na to může to být zdrojem nepříjemných chyb... když už mluvíme o funkcích v C - konvence je (jako v celém OS Amigy) že funkce nesmí změnit žádný registr kromě D0, D1, A0, A1. Návratové hodnoty se předávají obvykle v D0. (což Printf v message.s nesplňuje, ale já to vím a tak si dávám pozor). Lepší verzi vám ukáže prográmek "printf.s"...
Ve formátovacím řetězci se místa pro vložení nějakých hodnot označují pomocí znaku %", za kterým následuje popis typu hodnoty a způsobu výpisu. V OS 2.04+ jsou možné tyto kombinace:
%% - píše se jako jeden znak
"%", a y bylo vůbec možné tento znak vypsat
Za znakem % může následovat znak "-", který znamená že se má zarovnávat zleva pokud je požadovaná délka celého kousku textu větší než skutečně vyjde ze zadaných argumentů. Následuje desítkové číslo udávající maximální šířku položky. Pokud je první číslice "0", bude k doplnění vlevo použita nula místo mezery. Při výpisu stringu lze použít dvě čísla oddělená tečkou. Druhé číslo pak znamená kolik znaků se má maximálně zobrazit z vypisovaného stringu. Za čísly následuje modifikátor velikostí dat "I". Obvykle se argumenty předávají na zásobníku jako LONGy (32-bitové hodnoty) aby zásobník zůstal long-aligned. Znak "I" na to upozorňuje rutinu, protože ne z něj vy rutina načítala parametry jako WORDy. Pozor na to! Nakonec následuje písmeno udávající jaký typ hodnoty se má vypsat. Možné typy jsou:
"b" - BSTR (speciální formát stringu, který zůstal v DOSu jako dědictví po starých verzích dos.library, napsaných v BCPL. BSTR musí začínat na adrese dělitelné 4, protože ukazatel na začátek BSTR se předává jako adresa vydělená 4. Na místě kam tato adresa ukazuje se nachází BYTE, ve kterém je uložena délka stringu (0..255), za ním následují znaky. Někdy bývá BSTR ukončen nulou jako C-string, ale není to zaručeno! S BSTR se setkáte pokud se budete rejpat v dosu okolo handlerů a podobně...
"d" - desítkové číslo se znaménkem
"u" - desítkové číslo bez znaménka
"x" - hexadecimální číslo
"s" - string
"c" - jeden znak
Funkce VPrintf() z dos.library chce jako argumenty adresu formátovacího stringu v D1 a adresu pole argumentů v D2. V rutince Printf v prográmku "printf.s" jsem předávání argumentů a stringu vyřešil pomocí zásobníku jako v C. Před voláním Printf se na zásobník uloží adresa formátovacího stringu a případné argumenty. Rutina si vyzvedne adresu formátovacího stringu, dá ji do D1, a adresu na data za ním předá jako pointer na pole argumentů v D2. Když pak VPrintf() vypisuje formátovací string, pokaždé narazí-li na značku "%" vezme si z pole argumentů hodnotu o patřičné velikostí (v našem případě LONG) a posune se v poli dál. Funkce Printf tak jak je uvedena v "printf.s" nemění žádné registry a bude se vám jistě hodit...

; ===================================
; printf.s - zkráceno kvůli nedostatku místa
; ===================================
Printf movem.l d0-d2/a0-a1/a6,-(sp) ;schovat
lea (7*4)(sp),a0
move.l (a0)+d1
move.l a0,d2
move.l _DOSBase,a6
jsr _LVOVPrintf(a6)
movem.l (sp)+,d0-d2/a0-a1/a6 ;obnovit
rts

; ===================================
; demo na posilani messages
; vyzaduje OS 2.04+
; ===================================
incdir "INCL:"
include "exec/exec lib.i"
include "dos/dos lib.i"
include "exec/ports.i"
include "dos/dosextens.i"

;definice formatu meho message:

RSRESET

moje_message rs.b MN_SIZE
;standard. message
moje_id rs.11 ;ma byt "MOJE"
moje_text rs.11 ;pointer na text
moje_SIZE rs.10 ;velikost toho vseho

section prg,code

START move.l 4.w,ab
lea dosname(pc),a1
moveq #37,d0
jsr _LVOOpenLibrary(a6)
move.l d0,_DosBase
bne.s .mamdos
moveq #20,d0
rts
.mamdos pea vyrabim(pc)
bsr Printf
addq.l #4,sp
move.l 4.w,a6
jsr _LVOCreateMsgPort(a6)
move.l d0,port
beq fuj1
move.l d0,a1
moveq #0,d0
move.b MP_SIGBIT(a1),d1
bset d1 ,d0
bset #SIGBREAKB_CTRL_C,d0
move.l d0,waitmask
sf flag_public
sr _LVOForbid(a6)
lea portname(pc),a1
jsr _LVOFindPort(a6)
tst.l d0
bne .uztamje
;vytvorit verejny (public) port...
move.l port,al
move.l #portname LN_NAME(a1)
jsr _LVOAddPort(a6)
st flag_public
.uztamje jsr _LVOPermit(a6)
tst.b flag_public
beq never
pea portname(pc)
pea verejny(pc)
bsr Print
addq.l #8,sp
bsr VerejnyMod
bra .konec
.never bsr SoukromyMod
.konec pea rusim(pc)
bsr Printf
addq.l #4,sp
move.l 4.w,a6
move.l port(pc),a0
jsr _LVODeleteMsgPort(a6)
.fuj1 move.l DosBase(pc),a1
move.l 4.w,a6
jsr _LVOCIoseLibrary(a6)
moveq #0,d0
rts
VerejnyMod sf flag_exit
.main tst.b flag_exit
beq .neexit
rts
.neexit pea ahoj(pc)
bsr Prinft
addq.l #4,sp
move.l 4.w,a6
move.l waitmask,d0
jsr _LVOWait(a6)
btst #SIGBREAKB_CTRL_C,d0
beq .zkus
move.l 4.w,a6
move.l port,a1
jsr _LVORemPort(a6)
st flag_exit
.zkus move.l port,a0
jsr _LVOGetMsg(a6)
move.l a0,aktmessage
beq .main
move.l aktmessage(pc),a5
cmp.l #MOJE,moje_id(a5)
beq .jemoje
pea nenimoje(pc)
bsr Printf
addq.l #4,sp
bra .vratto
.jemoje pea zprava(pc)
bsr Pnntf
addq.l #4,sp
move.l moje_text(a5),d0
beq .vratto
move.l d0,-(sp)
pea jetam(pc)
bsr Printf
addq.l #8,sp
.vratto move.l aktmessage(pc),a1
move.l 4.w,a6
jsr _LVOReplyMsg(a6)
bra.s .zkus
SoukromyMod
pea posilam(pc)
bsr Printf
addq.l #4,sp
;vyplnit message
lea mojemessage(pc),a5
move.l port,MN_REPLYPORT(a5)
move.w #moje_ŠIZE,MN_LENGTH(a5)
move.l #MOJE,moje_id[a5]
move.l #posilanytext,moje_text(a5)
;pokusit se poslat na verejny port...
move.l 4.w a6
jsr _LVOForbid(a6)
lea portname(pc),a1
jsr _LVOFindPort(a6)
tst.l d0
bne.s .jeport
jsr _LVOPermit(a6)
pea portneni(pc)
bsr Printf
addq.l #4,sp
bra .konec
.jeport move.l d0,a0
lea mojemessage(pc),a1
jsr _LVOPutMsg(a6)
jsr _LVOPermit(a6)
pea cekam(pc)
bsr Printf
addq.l #4,sp
move.l 4.w,a6
move.l port,a0
jsr _LVOWaitPort(a6)
move.l port,a0
jsr _LVOGetMsg(a6)
.konec rts
Printf lea 4(sp),a0
move.l (a0)+d1
move.l a0,d2
move.l _DosBase(pc),a6
jmp _LVOVPrintf(a6)
cnop 0,4
_DosBase dc.l 0
port dc.l 0
waitmask dc.l 0
aktmessage dc.l 0
dosname dc.b "dos.library",0
portname dc.b "AHOJ_PORT",0
mojemessage dcb.b moje_SIZE,0
vyrabim dc.b "Vyrabim port",10,0
verejny dc.b "Pridavam ho do systemu"
dc.b " jako verejny port"
dc.b "%s",10,0
rusim dc.b "Rusim port...",10,0
ahoj dc.b "Cekam na message"
dc.b "nebo ctrl-C...",10,0
nenimoje dc.b "Prislo mi cizí message!?"
dc.b "POMOOOOC!",10,0
zprava dc.b "** Prislo mi message **"
dc.b 10,0
jetam dc.b "Je v nem text: °/s"
dc.b 10,0
flag_public dc.b 0
flag_exit dc.b 0
posilam dc.b "Posilam message...",10,0
cekam dc.b "Cekam na vraceni"
dc.b "message...",10,0
posilanytext dc.b "Halooo halooo, tady"
dc.b "je jiny proces!",0
portneni dc.b "Verejny port uz "
dc.b "neexistuje!",10,0



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