Nie zastanawialiście się kiedyś, jak procesor komunikuje się z tymi wszystkimi urządzeniami,
które znajdują się w komputerze?
Teraz zajmiemy się właśnie sposobem, w jaki procesor uzyskuje dostęp do urządzeń zewnętrznych
(zewnętrznych dla procesora, niekoniecznie tych znajdujących się poza obudową komputera).
Procesor może porozumiewać z urządzeniami przez wydzielone obszary
RAM-u. Te informacje można
zobaczyć w Windows we właściwościach (prawie) każdego urządzenia, na karcie Zasoby
, pod
hasłem Zakres pamięci
.
Mimo iż niekiedy sporo urządzeń zajmuje po jakimś skrawku pamięci, to jednak nie wszystkie.
Głównym sposobem komunikacji ciągle pozostają porty (Zasoby
- Zakres wejścia/wyjścia
).
Porty są to specjalne adresy, pod które procesor może wysyłać dane. Stanowią oddzielną
strefę
adresową (16-bitową, jak dalej zobaczymy, więc najwyższy teoretyczny numer portu
wynosi 65535), choć czasami do niektórych portów można dostać się przez pamięć RAM. Są to
porty mapowane do pamięci (memory-mapped), którymi nie będziemy się zajmować.
Lista przerwań Ralfa Brown'a (RBIL) zawiera plik ports.lst (który czasami trzeba osobno utworzyć - szczegóły w dokumentacji). W pliku tym (wygodnie jest go przeglądać na przykład programem ii.exe, dołączanym do RBIL) znajdują się szczegóły dotyczące całkiem sporej liczby portów odpowiadającym różnym urządzeniom. I tak, mamy na przykład
No dobrze, wiemy co ma który port i tak dalej, ale jak z tego skorzystać?
Procesor posiada dwie instrukcje przeznaczone specjalnie do tego celu.
Są to IN
i OUT
.
Ich podstawowa składnia wygląda tak:
in al/ax/eax, numer_portu out numer_portu, al/ax/eax
Uwagi:
num
jest 8-bitowy, toIN AL, num
wczyta 1 bajt z portu o numerze num
IN AX, num
wczyta 1 bajt z portu num
(do AL) i
1 bajt z portu num+1
(do AH)IN EAX, num
wczyta po 1 bajcie z portów num
, num+1
,
num+2
i num+3
i umieści w odpowiednich częściach rejestru EAX (od najmłodszej)
num
jest 16-bitowy, toIN AX, num
wczyta 2 bajty z portu o numerze num
IN EAX, num
wczyta 2 bajty z portu o numerze num
i 2 bajty z portu
o numerze num+1
num
jest 32-bitowy, toIN EAX, num
wczyta 4 bajty z portu o numerze num
OUT
Teraz byłaby dobra pora na jakiś przykład (mając na uwadze dobro swojego komputera, NIE URUCHAMIAJ PONIŻSZYCH KOMEND):
in al, 0 ; pobierz bajt z portu 0 out 60h, eax; wyślij 4 bajty na port 60h mov dx, 300 ; 300 > 255, więc musimy użyć DX in al, dx ; wczytaj 1 bajt z portu 300 out dx, ax ; wyślij 2 bajty na port 300
Nie rozpisywałem się tutaj za bardzo, bo ciekawsze i bardziej użyteczne przykłady znajdują się w moich mini-kursach (programowanie diód na klawiaturze, programowanie głośniczka).
Jak już wspomniałem wcześniej, porty umożliwiają dostęp do wielu urządzeń. Jeśli więc chcesz
poeksperymentować, nie wybieraj portów zajętych na przykład przez kontrolery dysków twardych, gdyż
zabawa portami może prowadzić do utraty danych lub uszkodzenia sprzętu.
Dlatego właśnie w nowszych systemach operacyjnych (tych pracujących w trybie chronionym, jak na przykład
Linux czy Windows) dostęp do portów jest zabroniony dla zwykłych aplikacji (o prawa
dostępu do portów trzeba prosić system operacyjny).
Jak więc działają na przykład stare DOS-owe gry? Odpowiedź jest prosta: nie działają w trybie
chronionym. System uruchamia je w trybie udającym tryb rzeczywisty (taki, w jakim pracuje
DOS), co umożliwia im pełną kontrolę nad sprzętem.
Wszystkie programy, które dotąd pisaliśmy też uruchamiają się w tym samym trybie, więc mają
swobodę w dostępie na przykład do głośniczka czy karty dźwiękowej. Co innego programy pisane w nowszych
kompilatorach na przykład języka C - tutaj może już być problem. Ale na szczęście my nie musimy się
tym martwić...
Jeszcze jeden ciekawy przykład - używanie CMOSu. CMOS ma 2 podstawowe porty: 70h, zwany portem adresu i 71h, zwany portem danych. Operacje są proste i składają się z 2 kroków:
Oto przykład. Odczytamy tutaj czas w komputerze, a konkretnie - sekundy:
mov al, 0 out 70h, al out 0edh, al in al, 71h
Wszystko jasne, oprócz tej dziwnej komendy: OUT 0edh, al
. Jak spojrzycie w ports.lst,
ten port jest (jako jeden z dwóch) opisany jako dummy port for delay
. Czyli nic nie robi,
poza opóźnianiem.
Po co to komu, pytacie?
Przy współczesnych częstotliwościach procesorów, CMOS (jak z resztą i inne układy) może
po prostu nie zdążyć z odpowiedzią na naszą prośbę, gdyż od chwili wysłania numeru komórki do
chwili odczytania danych mija za mało czasu. Dlatego robimy sobie przerwę na kilkanaście taktów
zegara procesora.
Kiedyś między operacjami na CMOSie zwykło się pisać JMP short $+2
, co też
oczywiście nie robiło nic, poza zajmowaniem czasu (to jest po prostu skok o 2 bajty do przodu
od miejsca, gdzie zaczyna się ta dwubajtowa instrukcja, czyli skok do następnej instrukcji),
ale ta operacja już nie trwa wystarczająco długo, aby ją dalej stosować.
W dzisiejszych czasach porty już nie są tak często używane, jak były kiedyś. Jest to spowodowane przede wszystkim wspomnianym trybem chronionym oraz tym, że wszystkie urządzenia mają już własne sterowniki (mające większe uprawnienia do manipulowania sprzętem), które zajmują się wszystkimi operacjami I/O. Programista musi jedynie uruchomić odpowiednią funkcję i niczym się nie przejmować.
Dawniej, portów używało się do sterowania grafiką czy wysyłania dźwięków przez głośniczek lub karty dźwiękowe. Teraz tym wszystkim zajmuje się za nas system operacyjny. Dzięki temu możemy się uchronić przed zniszczeniem sprzętu.
Mimo iż rola portów już nie jest taka duża, zdecydowałem się je omówić, gdyż po prostu czasami mogą się przydać. I nie będziecie zdziwieni, gdy ktoś pokaże wam kod z jakimiś dziwnymi instrukcjami IN i OUT...
Szczegóły dotyczące instrukcji dostępu do portów także znajdziecie, jak zwykle, u AMD i Intela.
Miłej zabawy.