Tajemnice ATARI

PROGRAMOWANIE PROCESORA 6502
W KOMPUTERACH ATARI XL/XE cz. V

Budowa dyskowego pliku binarnego

    Plik binarny w formacie DOS-u może się składać z dowolnej liczby niezależnych segmentów (bloków). Każdy segment ma na przedzie tzw. nagłówek, który określa obszar pamięci, gdzie DOS powinien umieścić dane tego bloku. Nagłówek może mieć długość 4 lub 6 bajtów, w tym drugim przypadku pierwsze dwa bajty zawierają liczby 255. Pierwszy segment pliku MUSI mieć nagłówek 6-bajtowy, bo stanowi to informację o rodzaju pliku dla DOS-u, pozostałe - wedle uznania.

   Końcowe cztery bajty nagłówka niosą adresy początku i końca obszaru pamięci, gdzie zostanie umieszczony ten segment. Bezpośrednio po nagłówku następują dane segmentu w ilości wynikającej z nagłówka (adres2 - adres1 + 1 bajtów). Inaczej mówiąc, segment stanowi zapisany na dysku pewien obszar pamięci komputera uzupełniony o adresy określające jego położenie. Po ostatnim bajcie danych może następować nagłówek następnego segmentu, itd.

   W praktyce rzadko odczuwamy potrzebę rozmyślania o nagłówkach bloków. Robi to bowiem za nas asembler, który zapisuje każdy ciąg bajtów, poprzedzony rozkazem ORG, w postaci osobnego segmentu. Warto przy tym pamiętać, że QA przed pierwszym ORG programu wykonuje "w myśli" rozkaz ORG 0.

   Ten mechanizm pozwala pisać "programy" bez rozkazów maszynowych, np. taki:
       OPT  %100101

       ORG  $8000
EKRA   EQU  *
    DTA D' >>> Programowanie '
    DTA D'procesora 6502 <<< '
       ORG  $600
DLIS   DTA  B($70),B($70),B($70)
       DTA  B($42),A(EKRA)
       DTA  B($41),A(DLIS)
       ORG  $58
       DTA  A(EKRA)
       ORG  $2C5
       DTA  B($02),B($6A)
       ORG  $22F
       DTA  B($22)
       DTA  A(DLIS)
   Taki "program" spowoduje wyświetlenie napisu na ekranie, chociaż nie zawiera ani jednego rozkazu maszynowego. Umieszcza on stosowne wartości w komórkach sterujących wyświetlaniem obrazu, wypełnia program dla ANTIC-u (DL) i ekran.

   Rozkaz DTA pozwala umieścić w programie dane dowolnego rodzaju, np. D'tekst' buduje ciąg bajtów, zawierających kody ekranowe (internal) znaków składających się na 'tekst'. B(wyrażenie) tworzy bajt z podaną wartością, zaś A(wyrażenie) - słowo. Poszczególne składowe rozkazu DTA oddziela się przecinkami.

   Parametry asemblacji OPT %100101 pozwolą wyemitować kod tego programu na dysk lub kasetę. Takie ustawienie zmusza asembler do poprzedzania każdego segmentu bajtami nagłówkowymi.

   Dwie uwagi: Po pierwsze, nie każdy DOS podejdzie do tego kodu jednakowo wyrozumiale. Np. SpartaDos będzie go próbował mimo wszystko uruchomić, skacząc do początkowych bajtów danych jak do programu. W tym DOS-ie trzeba się posłużyć rozkazem LOAD nazwa_programu. Po drugie, zmieniony wygląd ekranu pozostaje trwale, trzeba użyć klawisza RESET, żeby przywrócić ekran do normalnego stanu.

   Dodanie do tego żywych efektów dźwiękowych będzie już wymagało wykonania prawdziwego programu:
POKEY  EQU  $D200
OKRES  EQU  POKEY+0
BARWA  EQU  POKEY+1
       ORG  $480

PROG   LDA  #$AA
       STA  BARWA
L0     LDX  #255
L1     STX  OKRES
       LDY  DELAY
L2     DEY
       BNE  L2
       DEX
       BNE  L1
       INC  DELAY
       BPL  L0
       STX  BARWA
       STX  OKRES
       JMP  (10)
   Ten fragment można dopisać bezpośrednio do poprzedniego. Spotykamy tu nowy tryb adresacji, który stosuje się tylko z rozkazem JMP. Jest to tzw. skok pośredni: procesor przejdzie do wykonywania rozkazu, którego adres znajduje się w słowie pod adresem 10. Aby taki program wykonał się od etykiety PROG, należy uzupełnić go o
       ORG $2EO
       DTA A(PROG)
   DOS lub COS po załadowaniu wszystkich segmentów programu poszukuje w słowie pod adresem $2E0 adresu początku programu. Wcześniej ustawił te komórki w taki sposób, by móc rozpoznać, czy program umieścił tam swoje dane. Jeżeli wczytywany program wstawi tam (jako jeden z bloków) jakiś adres, to system po załadowaniu WSZYSTKICH bloków skacze do podanego adresu rozkazem JMP lub JSR. Z uwagi na to właśnie, że niektóre systemy wywołują programy przez JMP, a nie JSR, błędem jest kończenie programu użytkowego rozkazem RTS, który nie ma żadnego sensu w parze z JMP. Godny polecenia jest skok JMP (10), pod tym adresem bowiem zapisany jest adres początku głównej procedury DOS. Do testów pod QA jednak, zwłaszcza gdy używa się klawisza RESET, lepiej czasowo wpisać RTS.

   Jak widać na powyższym przykładzie, nie jest wymagana jakaś konkretna kolejność segmentów. W szczególności segment definiujący adres uruchomienia może się znaleźć gdziekolwiek, nawet na początku programu. W praktyce jednak okazuje się, że zachowanie porządku przynosi pewne korzyści. Na przykład w SpartaDOS-ie program nie musi podawać adresu uruchomienia, o ile początek logiczny programu pokrywa się z fizycznym (innymi słowy: jeżeli adres uruchomienia wskazuje na pierwszy bajt pierwszego segmentu).

   Można jednak zauważyć, że niektóre programy zaczynają się wykonywać jeszcze przed zakończeniem procesu wczytywania. Na przykład QA wyświetla swą wizytówkę i sprawdza przydział pamięci zanim rozpocznie się odczyt właściwego programu. Takie działanie jest możliwe dzięki tzw. inicjalizacji międzysegmentowej. Wczytujący się program może wpisać w słowo $2E2 (INITAD) adres procedury (uprzednio wczytanej do pamięci), a system niezwłocznie wykona tę procedurę (przed odczytaniem następnego segmentu).

   Dzięki temu mechanizmowi każdy segment może być wykonywany bądź inicjalizowany osobno, możliwe też jest (jak w przywołanym wyżej przykładzie QA) powtórne użycie obszaru pamięci po wykorzystanym już podprogramie do umieszczenia następnego segmentu. Nie ma żadnego ograniczenia na liczbę takich międzyblokowych inicjalizacji. Jest to bardzo pożyteczny mechanizm, choć trochę kłopotliwy, gdy się pisze programy do uruchamiania takich plików. Zwłaszcza odczyt z kasety, gdy procedura międzyblokowa wykonuje się zbyt długo, może ulec zaburzeniu. Z tego względu niektóre kopiery, np. NCopy, wydłużają przerwy pomiędzy rekordami zapisywanymi na kasecie w miejscach, gdzie wykryty zostanie blok umieszczany w INITAD.

Budowa pliku "BOOT"

   Programy samowczytujące, zwane żargonowo "butami", zbudowane są znacznie prościej. Sześciobajtowy nagłówek pliku określa jego długość, adres ładowania i uruchomienia. Odmiennie niż w przypadku pliku dyskowego, gdzie nagłówek był elementem zewnętrznym w stosunku do segmentu, tutaj wchodzi on w skład wczytywanego bloku i jest razem z nim umieszczany w pamięci. Pierwszy bajt nagłówka jest przez system ignorowany, zwykle więc zawiera 0. W drugim bajcie umieszcza się rozmiar pliku liczony w 128-bajtowych rekordach, co odpowiada fizycznej strukturze standardowego zapisu na taśmie lub na dysku (liczba 0 oznacza 256 rekordów). Zawsze więc wczytuje się wielokrotność 128 bajtów. "BOOT" kasetowy nie różni się niczym od dyskowego.

   Ponieważ QA nie ma specjalnego trybu do tworzenia plików samowczytujących, trzeba wykorzystać tryb "bez nagłówków", co uzyskuje się rozkazem
       OPT %1100101
   Tym, że nasz program nie wypełni całkowicie wielokrotności 128 bajtów, nie warto się przejmować. Trzeba tylko zadbać, by podany rozmiar nie był krótszy od programu. Nasz przykład zmieści się z powodzeniem w jednym rekordzie (sektorze).
       ORG $700
       DTA B(0),B(l)
   Drugi i trzeci bajt zawiera adres ładowania, czyli wskazuje miejsce w pamięci, gdzie zostanie umieszczony nasz "but". System nie nakłada żadnych ograniczeń na ten adres, ale przyjęło się umieszczać programy samowczytujące od siódmej strony pamięci.
       DTA A($700)
   Pozostałe dwa bajty nagłówka zawierają adres procedury inicjalizacji. O ile niczego nie popsujemy, to ta procedura będzie się później wykonywać zawsze w wyniku naciśnięcia klawisza RESET. Trzeba bowiem pamiętać, że samowczytujące programy z reguły są odporne na RESET. Typowym przykładem takiego programu jest DOS.
       DTA A(INIT)
   Nasz krótki "but" może być wykorzystany w programie formatującym dyski, by omyłkowa próba wystartowania systemu z pustej dyskietki nie powodowała zawieszenia komputera lub przykrych efektów dźwiękowych.

   Po wczytaniu do pamięci stosownej liczby sektorów (w naszym przypadku jednego), wynikającej z nagłówka, system uruchamia procedurę "kontynuacji", która powinna znajdować się bezpośrednio po nagłówku. Tej procedurze można powierzyć różne zadania. Zwykle przejmuje ona wczytywanie dalszej części programu (bo rasowy "but" ma co najwyżej 3 sektory). Jeżeli, tak jak w naszym przykładzie, nie ma dalszej części, to wystarczy ustawić adres DOSVEC, by system wiedział, gdzie skoczyć po wykonaniu wszystkich czynności wstępnych.
DOSVEC EQU  10


       LDA START
       STA DOSVEC+1
   Procedura kontynuacji przekazuje do systemu za pomocą flagi C informację o tym, czy proces wczytywania zakończył się powodzeniem. Ustawienie tej flagi oznaczałoby porażkę. A zatem
       CLC
       RTS
i proces BOOT został pomyślnie zakończony. Teraz system wykona swoje tajemnicze operacje, podczas których wywoła też naszą procedurę inicjalizacji. Jej adres zostanie skopiowany z dwu końcowych bajtów nagłówka do słowa CASINI (2) w przypadku taśmy lub DOSINI (12) w przypadku dysku. Ta procedura przeznaczona jest do wykonywania podczas sekwencji RESET i powinna regenerować te elementy, które RESET unicestwia. Np. DOS uzupełnia HATABS o wpis urządzenia "D:". Błędem jest natomiast ustawianie komórek, które przy RESET nie ulegają zmianie, jak DOSVEC lub BOOT (9). Niestety, wiele programów, tworzonych przez dyletentów, tak właśnie postępuje. Wymusza to konieczność ich "przechytrzania" przez programy rezydentne, takie takie jak XLFriend czy Bug Hunter, co zwiększa stopień skomplikowania tych ostatnich. Dla naszych potrzeb w zupełności wystarczy RTS, ale dla lepszego wyobrażenia, czy i kiedy się to wykonuje, zmieńmy kolor obrzeża ekranu.
RAMKA  EQU  712
ZNAKI  EQU  709

INIT   LDA  ZNAKI
       STA  RAMKA
       RTS
   Teraz system spokojnie przekaże sterowanie do głównego programu, którego adres znajdzie w DOSVEC. A ten program wyświetli komunikat
TEKST  DTA  C'Tu nic nie ma! '
       DTA  C'Stuknij ESC...'
       DTA  B($9B)


CIOV   EQU  $E456
IOCB   EQU  $340
CHNO   EQU  0
PISZ   EQU  9

START  LDX  #CHN0
       LDA  #PISZ
       STA  IOCB+2,X
       LDA  TEKST
       LDA  IOCB+6,X
       LDA  #255
       STA  IOCB+8,X
       STA  IOCB+9,X
       JSR  CIOV
i zaczeka na wymianę dyskietki na właściwą oraz naciśnięcie klawisza ESC
ESC    EQU  28
CH     EQU  764

       LDA  #ESC

WKEY   CMP  CH
       BNE  WKEY
po czym wykona zimny start.
COLD   EQU  $E477

JMP    COLD
       END
   Warto zauważyć, że nasz program nie ulega klawiszowi RESET, ma natomiast opcję "wyjścia". Wiadomo przecież, że 99% uszkodzeń komputerów zdarza się podczas włączania lub wyłączania sprzętu. Dlatego troskliwy użytkownik robi to jak najrzadziej, zaś dobrze wychowany program pomaga mu w tym, a nie przeszkadza.

   Kod rozkazu 9 przekazany do CIOV oznacza polecenie wysłania napisu o długości nie większej niż podana w IOCB +8 i +9. Przesyłanie tekstu zostaje samoczynnie przerwane przy napotkaniu pierwszego znaku końca wiersza ($9B = 155). Jest to wygodne przy wypisywaniu jednego wiersza tekstu o niesprecyzowanej długości.

   Dla testów zapiszmy nasz "but" na kasecie, wpisując w QA nazwę C: (w menu File/Obj) i wykonując Assembly. Nagrany plik należy uruchamiać poprzez wykonanie zimnego startu przy wciśniętych klawiszach SELECT i START.

   Do wywołania procedury zimnego startu nie trzeba wcale wyłączać komputera. Istnieje mnóstwo innych mniej lub bardziej pomysłowych sposobów. W DOS-ie lub COS-ie wystarczy napisać RUN E477. W Basicu praktykuje się BYE i RESET. W QA można ustawić adres Setup/Run na E477 i wykonać Run z głównego menu. Mając w pamięci XLF-a można przywołać wglądownicę, wpisać coś do komórki 580, schować wglądownicę (by w przyszłości XLF się cały nie wczytywał) i nacisnąć RESET.

   Aby umieścić nasz "but" na dyskietce, najprościej jest użyć programu Turbo-Watson lub jego młodszego kuzyna, Wacia, który prezentowany był w TA 4/91. Należy wczytać sektor pliku zawierającego wersję OBJ naszego programu, włożyć do napędu sformatowaną, pustą dyskietkę przeznaczoną do testów, zmienić numer sektora na 001 i zapisać go. Dyskietka, uruchamiana przy wciśniętym klawiszu OPTION, zgłosi się natychmiast, zaś bez OPTION - dopiero po rozkazie DOS.

   Podczas zimnego startu komputer podejmuje próbę wczytania "buta," z dyskietki, a o ile trzymamy naciśnięty klawisz START, także z kasety. Brak napędu dysków kwitowany jest charakterystycznym "pyrczeniem". Powodzenie odczytu programu z dysku powoduje ustawienie zerowego bitu (wartość 1) w komórce 9 (BOOT), zaś powodzenie odczytu z kasety ustawia bit 1 (wartość 2). Od tej wartości zależy późniejsze zachowanie się systemu w procedurze RESET, zwanej też ciepłym startem. 2 powoduje wykonywanie procedury inicjalizującej o adresie zapisanym w CASINI, a 1 - w DOSINI. Jak nietrudno się domyślić, wartość 3 wymusza wykonanie obu tych procedur.

   Komórka BOOT, bardzo ważna, określa zatem typ inicjalizacji. Jej poprawne ustawienie warunkuje prawidłową pracę systemu. Tymczasem do redakcji wciąż napływają cudowne pomysły, np. na nieśmiertelny mrugający kursor, którego podtrzymanie zasadza się na wpisaniu swojego adresu do DOSINI i zmianie wartości komórki BOOT na 1. Takie programy wędrują prosto do kosza. Ich autorzy, posiadacze magnetofonów, nie zdają sobie sprawy z efektu, jaki ich program wywoła w DOS-ie.

Janusz B. Wiśniewski



Powrót na start | Powrót do spisu treści | Powrót na stronę główną

Pixel 2001