Dostęp do sprzętu peryferyjnego – podstawy

Zarys

W życiu każdego projektanta systemów operacyjnych nadchodzi moment, w którym chciałby się on skontaktować z urządzeniami zewnętrznymi, gdyż samotny procesor przestaje spełniać jego wygórowane oczekiwania :) Jak to bywa prawie ze wszystkim, istnieje kilka sposobów dostępu do sprzętu peryferyjnego, co wynika oczywiście z naturalnej ewolucji hardware’u – w tym poście zajmę się jednak tylko dwoma, najprostszymi metodami – port I/O oraz memory-mapped I/O, co dałoby się przetłumaczyć jako odpowiednio – wejście/wyjście za pomocą portów oraz wejście wyjście odwzorowane w pamięci. Wśród bardziej zaawansowanych metod, na które jeszcze przyjdzie czas, warto wspomnieć o DMA oraz dostępie przez przerwania.

Memory-mapped I/O

Memory-mapped I/O to bardzo prosty sposób na dostęp do urządzeń zewnętrznych polegających na tym, iż w dostępnej pamięci operacyjnej – a dokładniej w dostępnej przestrzeni adresowej – posiadamy zarezerwowany zakres adresów pozwalających na odwoływanie się do urządzeń. Rezerwacja może być zarówno stała, jak i tymczasowa. Technicznie, w uproszczeniu rzecz ujmując, realizacja wygląda tak, iż każde z urządzeń monitoruje szynę adresową procesora i reaguje na każdy jego dostęp do odpowiedniego zakresu. Zaletami takiego podejścia są oczywiście – prostota i jasność w korzystaniu (łatwo programuje się to w C), gdyż dostęp do pamięci jest prosty; procesor oparty o memory-mapped I/O jest tańszy, mniejszy, prostszy i mniej energochłonny; dostęp do pamięci jest szybki, a więc dostęp do urządzeń poprzez szynę adresową też – również dzięki możliwości wykorzystania różnych trybów adresowania. Wady? Takie podejście „przerywa” ciągłość pamięci w konwencjonalnym ujęciu, a więc zmniejsza też ilość dostępnej pamięci. Dla formalności, przykładowy kod:

unsigned char *videoram = (unsigned char *) 0xB8000;
videoram[0] = 65; /* character 'A' */
videoram[1] = 0x07; /* forground, background color */

W ten sposób wypiszemy literkę ‚A’ na ekranie, odwołując się do karty graficznej. Wygląda prosto, prawda? I o to właśnie chodzi :) Pozostaje jeszcze pytanie, na które nie ma odpowiedzi – skąd wiedzieć która cześć pamięci za co odpowiada? Rozwiązania należy szukać w mapie pamięci, która dla każdej architektury może być inna. Warto też pamiętać, że istnieje możliwość dynamicznego przydzielania adresów dla urządzeń.

Port I/O

Port I/O to kolejna, starsza i generalnie gorsza metoda dostępu do urządzeń, która ciągle jest obecna w architekturze x86 z powodu.. tak tak, zgodności wstecznej. Sposób ten polega na istnieniu dodatkowej, obok RAM-u, przestrzeni adresowej, w której każde urządzenie ma swój adres. Dostęp do portów odbywa się za pomocą specjalnych instrukcji, charakterystycznych dla architektury, które nie mają swojego odpowiednika w języku C. W assemblerze x86 są to: IN, INS/INSB/INSW/INSD, OUT, OUTS/OUTSB/OUTSW/OUTSD pozwalające wczytać/pisać odpowiednią porcję danych z/do wskazanego portu. Technicznie, transfer danych odbywa się za pomocą specjalnego I/O pina w CPU lub za pomocą przeznaczonej do tego szyny adresowej, która nieco przyspiesza ten proces. Zaletami tego rozwiązania jest oszczędność przestrzeni adresowej,  a więc i samej pamięci oraz fakt, iż dostęp przez odpowiednie instrukcje, w jasny sposób wskazuje czytającemu kod programiście, kiedy odbywa się komunikacja ze sprzętem. Wadą, jak już wspomniałem, jest powolność oraz brak wsparcia dla operacji 64 bitowych. Przykładowy kod:

a20wait:
        in      al, 0x64
        test    al, 2
        jnz     a20wait
        ret

Kod ten, jak widać, wczytuje do rejestru al (ax) bajt z urządzenia o adresie 0x64, o którym będzie jeszcze mowa ;) Warto zwrócić uwagę, że używany jest tu rejestr (e)ax, gdyż… jest to jedyny rejestr dostępny do użycia z portami w architekturze x86 – niestety. Podobnie – adres może być stałą natychmiastową lub wartością wyłącznie z rejstru DX. Pozostaje nam jeszcze odpowiedź na pytanie zadane dla dostępu przez memory-mapped I/O – czyli skąd wiedzieć, który port połączony jest z którym urządzeniem. Odpowiedź i tym razem jest niełatwa. Niektóre urządzenia takie jak timery, kontrolery przerwań, porty PS/2, serial, parallel, dyskietka, dysk twardy, karta graficzna mają przypisane konkretne adresy z powodów kompatybilności. Pozostałe urządzenia typu plug-and-play mają adresy przypisane przez BIOS i aby je otrzymać, należy poprosić o to szynę PCI :) Inne urządzenia (np. karty ISA) posiadają zworki, którymi da się ustawić konkretny port. Pamiętacie ustawianie portów Soundblastera w grach za czasów DOS-a? Tak – to właśnie dlatego :)

Podsumowanie

Nowoczesne architektury – 32 i 64 bitowe odchodzą od modelu z portami na rzecz zunifikowanej przestrzeni adresowej – ze względu na wysoką wydajność i elegancję takiego rozwiązania. W obecnych czasach, mała ilość pamięci nie jest już przeszkodą. Są jednak chwile, w których dostęp za pomocą portów jest słuszną (lub jedyną ;>) metodą dostępu do urządzeń – należy więc o niej pamiętać. Konkretne przykłady użycia – już wkrótce.

Jedna myśl do “Dostęp do sprzętu peryferyjnego – podstawy”

Możliwość komentowania jest wyłączona.