Komputer prowadzi obliczenia i kontaktuje się z użytkownikiem, tzn. przeprowadza operacje wejścia/wyjścia. W wykładzie 10 zajmiemy się tym drugim aspektem życia komputera tzn. wejściem/wyjściem. Bez niego komputer byłby bezużyteczny. Co z tego, że wszystko obliczy, skoro nie można zobaczyć wyników?
System operacyjny zarządza operacjami wejścia/wyjścia i sprawuje nadzór nad urządzeniami zewnętrznymi, które te operacje obsługują. Różnorodność urządzeń zewnętrznych jest ogromna. W tym wykładzie omówimy wspólne dla wszystkich urządzeń wejścia/wyjścia zagadnienia związane z systemem wejścia/wyjścia: podstawy sprzętowe, zasadnicze usługi świadczone przez system operacyjny, interfejs wejścia/wyjścia aplikacji oraz kwestie wydajnościowe.
Komputer składa się z: procesora, urządzeń zewnętrznych, sterowników urządzeń zewnętrznych, pamięci operacyjnej, pamięci podręcznej, sterownika pamięci i magistrali systemowej. Urządzenia zewnętrzne komunikują się z systemem komputerowym poprzez przesyłanie sygnałów. Łączem między urządzeniem i komputerem jest tzw. port. Porty są podłączane do szyn tzn. mediów, którymi przesyłane są sygnały (takim medium może być wiązka przewodów; przesyła się przez nią sygnały w postaci impulsów elektrycznych). Do jednej szyny może być podłączonych wiele portów urządzeń. W komputerze mogą również występować układy szeregowe, tzn. że urządzenie A jest podłączone do urządzenia B, które z kolei jest połączone z urządzeniem C, które jest już bezpośrednio połączone do portu komputera.
Sterownik jest układem elektronicznym, który nadzoruje pracę portu, szyny lub urządzenia albo większej ich grupy. Sterownik pośredniczy w przesyłaniu informacji z i do urządzeń. Sterownik ma własną pamięć, w której może buforować dane przesyłane do i z urządzeń. Pozwala to na szybkie przesłanie informacji szyną systemową, niezależnie od prędkości urządzenia. Do szyny systemowej mogą też być podłączone sterowniki, które łączą główną szynę z szynami niższego poziomu (np. PCI, ISA), do których też mogą być podłączone sterowniki, itd. Często też urządzenia zewnętrzne są podłączone do sterownika za pomocą odpowiedniej szyny (np. SCSI). Tak więc w systemie zwykle mamy wiele szyn:
Procesor przekazuje sterownikom polecenia i dane niezbędne do wykonania tych poleceń. Sterownik ma specjalny rejestr, który służy do przekazywania komunikatów z i do procesora. Procesor porozumiewa się ze sterownikiem pisząc i czytając bity w tym rejestrze. Przesłanie tych bitów do rejestru urządzenia może odbywać się poprzez specjalne instrukcje wejścia/wyjścia będące zleceniami przesłania bitów na adres portu wejścia/wyjścia. Rozkazy te powodują przekazanie bitów z i do rejestrów sterujących urządzenia poprzez szynę.
Rejestry sterujące urządzenia mogą być też odwzorowane w pamięci operacyjnej. Wówczas komunikacja z urządzeniem odbywa się poprzez zapis i odczyt odpowiednich bajtów w pamięci operacyjnej.
Protokół komunikacji między procesorem i sterownikiem urządzenia musi obejmować zagadnienie koordynacji ich współpracy. Procesor musi mieć wiedzę o tym, czy urządzenie jest zajęte, żeby odebrać od niego wyniki zleconego zadania i móc mu zlecić nowe.
Do przekazywania tej informacji mogą służyć dwa bity: bit gotowości procesora i bit zajętości urządzenia. Sterownik ustawia na 1 bit zajętości, gdy właśnie obsługuje zlecenie, i zeruje go, gdy skończy. Procesor ustawia na 1 bit gotowości polecenia, gdy polecenie dla sterownika jest już gotowe. Koordynacja ma postać następującego ciągu czynności:
Przerwania są sprzętowym mechanizmem służącym do zawiadamiania procesora o rozmaitych zdarzeniach. Procesor ma tzw. linię zgłaszania przerwań, którą bada po wykonaniu każdej instrukcji. Widać więc, że wsparcie sprzętowe dla mechanizmu przerwań jest nieodzowne. Gdy procesor wykryje, że zgłoszono jakieś przerwanie (mógł to zrobić na przykład sterownik urządzenia), to zachowuje stan swoich obliczeń (m.in. licznik instrukcji) i wykonuje skok do procedury obsługi przerwania. Ta procedura wykonuje odpowiednie czynności związane z obsługą przerwania i na końcu wydaje instrukcję powrotu z przerwania, która powoduje przywrócenie stanu obliczeń procesora sprzed przerwania.
Wśród przerwań niektóre są ważniejsze od innych. Obsługa tych ważniejszych nie powinna być przerywana przez te mniej ważne. Nie będziemy przecież obsługiwać przerwań z klawiatury w czasie obsługi przerwania spadku zasilania w sieci, gdy system operacyjny musi szybko zamrozić i zapisać stan wszystkich obliczeń dopóki UPS jeszcze utrzymuje przez pewien czas napięcie. Wyróżnia się więc zwykle dwie grupy przerwań: maskowalne i niemaskowalne. Odbieranie przerwań maskowalnych może być chwilowo wyłączone (procesor je wtedy ignoruje albo odkłada na później). Odbierania przerwań niemaskowalnych nie można wyłączyć. Sterowniki urządzeń korzystają z przerwań maskowalnych. Przerwania niemaskowalne wiążą się z poważniejszymi zdarzeniami, takimi jak nieusuwalne błędy pamięci.
Przerwanie niesie ze sobą swój identyfikator. W większości systemów jest to niewielka liczba będąca przesunięciem (offsetem) w specjalnej tablicy zwanej wektorem przerwań. W tej tablicy znajdują się adresy procedur obsługi poszczególnych przerwań. I tak wystąpienie przerwania nr 5 powoduje skok do procedury obsługi, której adres znajduje się na piątej pozycji tejże tablicy. W ten sposób obsługa przerwania nie wymaga wyszukiwania odpowiedniej procedury, a jedynie odczytania jednej pozycji wektora przerwań. W procesorze Intel wektor przerwań ma 255 pozycji. Pierwsze 32 pozycje odpowiadają przerwaniom niemaskowalnym (są to rozmaite błędy). Pozostałe pozycje są przerwaniami maskowalnymi.
Obsługa operacji wejścia/wyjścia za pomocą przerwań przebiega następująco:
Gdy urządzenie przekazuje dużą ilość danych, dokonywanie tego transferu bajt po bajcie poprzez procesor jest marnotrawstwem. Lepszym rozwiązaniem jest skorzystanie z pamięci operacyjnej. Idea DMA (direct memory access) jest bardzo prosta: sterownik wpisuje dane bezpośrednio do pewnego bufora w pamięci, skąd można potem te dane odczytać. Do realizacji DMA potrzebne jest dodatkowe urządzenie, tzw. sterownik bezpośredniego dostępu do pamięci. Sterownik ten pośredniczy między sterownikiem urządzenia i pamięcią operacyjną, zarządzając szyną pamięci w celu przekazania poszczególnych bajtów. Bezpośredni dostęp do pamięci odbywa się wg następującego scenariusza:
Jak wspomniano na wstępie, różnorodność urządzeń wejścia/wyjścia jest ogromna. Niemniej jednak, za pomocą podejścia warstwowego można tak obudować metody dostępu do urządzeń, że korzystanie z nich odbywa się w sposób uniwersalny za pomocą niewielkiej liczby podprogramów.
Różnice między poszczególnymi urządzeniami są obudowane za pomocą specjalnego oprogramowania tzw. modułów sterujących (ang. driver). Moduł sterujący bezpośrednio zarządza sterownikiem urządzenia (ang. controller) i oferuje podsystemowi wejścia/wyjścia jądra pewien standardowy interfejs. To pozwala na łatwe dołączanie nowych rodzajów urządzeń, upraszcza pisanie systemów operacyjnych i oprogramowania użytkowego.
Interfejs programowy urządzenia na poziomie jądra można więc uzależnić jedynie od jego kluczowych właściwości. Oto krótka klasyfikacja urządzeń:
Obsługa żądania wejścia/wyjścia zajmuje pewien czas. Proces zlecający może w tym czasie coś robić. Istnieją trzy opcje określające, jak może zachować się zlecający proces:
Jądra systemów operacyjnych oferują wiele usług związanych z wejściem/wyjściem. W tym punkcie omówimy te usługi oraz wykorzystanie przy ich implementacji pewnych szczególnych właściwości jądra.
Ogólne zagadnienie planowania omówiono w jednym z poprzednich wykładów. Skoncentrowano się tam na planowaniu dostępu do procesora. Dostęp do urządzeń również warto planować. Dotyczy to na przykład dysków. Gdyby żądania obsługiwano w takiej kolejności, jak przyszły, głowica dysku mogłaby przesuwać się bez potrzeby w tę i we w tę. Można za to tak zaplanować obsługę żądań dyskowych by zminimalizować zbędne ruchy głowicy. Co więcej, pewne zlecenia (np. dotyczące pamięci wirtualnej) mogą być pilniejsze niż inne (np. żądania aplikacji). Z każdym urządzeniem może być związana kolejka zleceń obsługiwana według odmiennej strategii. Systemy operacyjne starają się być też sprawiedliwe w planowaniu dostępu do urządzeń.
Bufor to obszar pamięci do przechowywania danych przesyłanych między dwoma urządzeniami. Buforowanie takiego transferu danych jest ważne, ponieważ:
Pamięć podręczna (ang. cache) jest obszarem szybkiej pamięci do przechowywania kopii danych. To udogodnienie pozwala na szybszy dostęp do oryginału danych i jest kluczem do szybszego działania systemu komputerowego. Jeśli aktualne dane są akurat w pamięci podręcznej, nie ma potrzeby wykonywania kosztownych czasowo operacji dyskowych.
Spooling (SPOOL = simultaneous peripheral operations on-line = jednoczesna bezpośrednia praca urządzeń) to użycie bufora do przechowywania danych zlecenia dla obecnie zajętego urządzenia o dostępie wyłącznym. Przykładem takiego urządzenia jest drukarka. Zadania drukowania oczekują w kolejce na możliwość realizacji.
System operacyjny udostępnia wywołania systemowe do przydziału i zwolnienia urządzenia o dostępie wyłącznym. Takie wywołania mogą prowadzić do zakleszczenia, z którymi system operacyjny musi sobie jakoś poradzić.
System operacyjny może radzić sobie z rozmaitymi rodzajami błędów takimi jak błędny odczyt z dysku, awaria urządzenia (chwilowa lub trwała), czy chwilowe problemy z zapisem. Nieudaną operację można na przykład powtórzyć. Jeśli nic się nie da zrobić, wywołanie systemowe zwraca kod błędu. Oprócz tego w dzienniku systemowym system operacyjny zapisuje informacje o wszelkich awariach.
Jądro przechowuje informacje o stanie urządzeń wejścia/wyjścia w strukturach danych takich jak tablice otwartych plików, połączenia sieciowe, stany urządzeń znakowych. Są tam też złożone struktury do śledzenia buforów, przydziału pamięci, flag modyfikacji bloków. W niektórych systemach operacyjnych do implementacji przekazywania komunikatów wejścia/wyjścia wykorzystano metody znane z języków programowania obiektowego.
Rozważmy operację odczytu pliku z dysku. Składa się ona z następujących czynności:
Droga od zlecenia operacji odczytu bloku dyskowego mogłaby wyglądać następująco:
Ta obserwacja pozwoliła na opracowanie mechanizmu strumieni, który umożliwia dynamiczne łączenie modułów obsługi w potoki. Cóż stoi na przeszkodzie, żeby w potoku sterownik-moduł sterujący-jądro albo z powrotem umieścić jeszcze jakieś dodatkowe moduły przetwarzające?
Strumień składa się z czoła, które służy do komunikacji z procesem użytkownika i z zakończenia sterującego, które zarządza sterownikiem. Między czołem i zakończeniem sterującym może występować wiele pośrednich modułów obsługi. Każdy taki moduł ma kolejkę do odczytu i kolejkę do zapisu. Moduły można dowolnie komponować i dynamicznie wymieniać.
Wejście/wyjście ma szczególnie duży wpływ na wydajność systemu operacyjnego, ponieważ:
Szczególnie kosztowna jest obsługa ruchu sieciowego a zwłaszcza w wypadku zdalnych sesji użytkownika. Popatrzmy na następujący scenariusz:
Poprawę wydajności wejścia/wyjścia osiągniemy poprzez:
W wykładzie 10 poznaliśmy, czym jest urządzenie zewnętrzne (wejścia/wyjścia) oraz w jaki sposób korzysta z niego system komputerowy. Kontakt procesora z urządzeniem może być zaimplementowany za pomocą odpytywania, przerwań i DMA.
Żądanie procesu użytkownika trafia do jądra systemu operacyjnego, w którym odpowiedni fragment oprogramowania tzw. podsystem wejścia/wyjścia zleca odpowiednie operacje modułowi sterującemu. Moduł sterujący to fragment oprogramowania komunikujący się bezpośrednio ze sprzętowym sterownikiem urządzenia. Moduł sterujący obudowuje charakterystykę urządzenia prezentując jądru systemu operacyjnego standardowy interfejs.
Wydajność wejścia/wyjścia ma wielki wpływ na wydajność całego systemu. Warto inwestować więc w usprawnienie realizacji operacji wejścia/wyjścia. Można tego dokonać m.in. poprzez zmniejszenie liczby koniecznych przełączeń kontekstu, zwiększenie pobieranych jednorazowo porcji danych, ograniczenie kopiowania danych, zwiększenie wydajności operacji poprzez implementacje elementarnych operacji w sprzęcie etc.