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).
Mimo że procesor może porozumiewać z urządzeniami przez wydzielone obszary
RAM-u,
to głównym sposobem komunikacji (gdy nie chcemy lub nie możemy używać sterowników)
ciągle pozostają porty. Jeśli chcecie, możecie wykonać komendę
cat /proc/ioports
, która powie, które urządzenie zajmuje które porty.
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 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) dostęp do portów jest zabroniony dla zwykłych aplikacji (o prawa
dostępu do portów trzeba prosić system operacyjny - zaraz zobaczymy, jak to zrobić).
Jak więc działają na przykład stare DOS-owe gry? Odpowiedź jest prosta: nie działają w trybie
chronionym. Windows 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 eax, 101 ; funkcja systemowa "sys_ioperm": mov ebx, 70h ; poczynając od portu 70h mov ecx, 20 ; tyle bajtów będziemy mogli wysłać/odebrać mov edx, 71h ; końcowy numer portu int 80h ; niestety, musimy być rootem cmp eax, 0 ; sprawdzamy, czy błąd. Nie wiem, ; co ta funkcja ma ; zwracać, ale ten sposób zdaje ; się działać jl koniec ; jeśli wystąpił błąd, to zapis do ; portów, do których nie mamy uprawnień, ; zakończy się "Segmentation fault" ; ( "Naruszenie ochrony pamięci" ) mov al, 0 out 70h, al ; ustaw przerwę na milion nanosekund, czyli ; jedną milisekundę mov dword [ts1+timespec.tv_sec], 0 mov dword [ts1+timespec.tv_nsec], 1000000 ; w FASMie: ; mov dword [ts1.tv_sec], 0 ; mov dword [ts1.tv_nsec], 1000000 mov eax, 162 ; sys_nanosleep mov ebx, ts1 ; adres struktury timespec mov ecx, 0 ; adres wynikowej struktury timespec int 80h ; wykonaj przerwę w programie in al, 71h koniec: ; ... ; w FASMie: ; segment readable writeable section .data ; w FASMie: ;struc timespec ;{ ; .tv_sec: rd 1 ; .tv_nsec: rd 1 ;} ; ;ts1: timespec struc timespec .tv_sec: resd 1 .tv_nsec: resd 1 endstruc ts1 istruc timespec
Wszystko jasne, oprócz bloku z wywołaniem sys_nanosleep.
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ć.
Komunikacja z urządzeniami nie zawsze jednak musi wymagać uprawnień administratora i korzystania z funkcji sys_ioperm. Sporo rzeczy (na przykład z klawiaturą) można zrobić, korzystając z funkcji sys_ioctl.
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.