ioctl


W informatyce ioctl (skrót od input/output control ) jest wywołaniem systemowym dla operacji wejścia/wyjścia specyficznych dla urządzenia i innych operacji, których nie można wyrazić zwykłymi wywołaniami systemowymi. Pobiera parametr określający kod żądania; efekt wywołania zależy całkowicie od kodu żądania. Kody żądań są często specyficzne dla urządzenia. sterownik urządzenia CD-ROM, który może nakazać fizycznemu urządzeniu wysunięcie dysku, dostarczyłby ioctl poproś o kod, aby to zrobić. Niezależne od urządzenia kody żądań są czasami używane w celu zapewnienia przestrzeni użytkownika dostępu do funkcji jądra, które są używane tylko przez podstawowe oprogramowanie systemowe lub wciąż są w fazie rozwoju.

Wywołanie systemowe ioctl po raz pierwszy pojawiło się pod tą nazwą w wersji 7 systemu Unix . Jest obsługiwany przez większość systemów Unix i systemów uniksopodobnych , w tym Linux i macOS , chociaż dostępne kody żądań różnią się w zależności od systemu. System Microsoft Windows zapewnia podobną funkcję o nazwie „ DeviceIoControl ” w interfejsie API Win32 .

Tło

Konwencjonalne systemy operacyjne można podzielić na dwie warstwy, przestrzeń użytkownika i jądro . Kod aplikacji, taki jak edytor tekstu , znajduje się w przestrzeni użytkownika , podczas gdy podstawowe funkcje systemu operacyjnego, takie jak stos sieciowy , znajdują się w jądrze. Kod jądra obsługuje wrażliwe zasoby i implementuje bariery bezpieczeństwa i niezawodności między aplikacjami; z tego powodu system operacyjny uniemożliwia aplikacjom działającym w trybie użytkownika bezpośredni dostęp do zasobów jądra.

przestrzeni użytkownika zwykle wysyłają żądania do jądra za pomocą wywołań systemowych , których kod znajduje się w warstwie jądra. Wywołanie systemowe ma zwykle postać „wektora wywołania systemowego”, w którym żądane wywołanie systemowe jest oznaczone numerem indeksu. Na przykład exit() może być wywołaniem systemowym o numerze 1, a write() o numerze 4. Wektor wywołania systemowego jest następnie używany do znalezienia żądanej funkcji jądra dla żądania. W ten sposób konwencjonalne systemy operacyjne zazwyczaj dostarczają kilkaset wywołań systemowych do przestrzeni użytkownika.

Chociaż jest to celowy projekt do uzyskiwania dostępu do standardowych funkcji jądra, wywołania systemowe są czasami nieodpowiednie do uzyskiwania dostępu do niestandardowych urządzeń peryferyjnych. Z konieczności większość sprzętowych urządzeń peryferyjnych (znanych również jako urządzenia) jest bezpośrednio adresowalna tylko w jądrze. Jednak kod użytkownika może wymagać bezpośredniej komunikacji z urządzeniami; na przykład administrator może skonfigurować typ nośnika w sieci Ethernet interfejs. Nowoczesne systemy operacyjne obsługują różnorodne urządzenia, z których wiele oferuje duży zbiór udogodnień. Niektóre z tych udogodnień mogą nie być przewidziane przez projektanta jądra, w wyniku czego jądro ma trudności z dostarczaniem wywołań systemowych do korzystania z urządzeń.

Aby rozwiązać ten problem, jądro zostało zaprojektowane tak, aby było rozszerzalne i może akceptować dodatkowy moduł zwany sterownikiem urządzenia , który działa w przestrzeni jądra i może bezpośrednio adresować urządzenie. Interfejs ioctl to pojedyncze wywołanie systemowe, za pomocą którego przestrzeń użytkownika może komunikować się ze sterownikami urządzeń. Żądania dotyczące sterownika urządzenia są wektoryzowane w odniesieniu do tego ioctl wywołanie systemowe, zwykle za pomocą uchwytu do urządzenia i numeru żądania. Podstawowe jądro może zatem pozwolić przestrzeni użytkownika na dostęp do sterownika urządzenia, nie wiedząc nic o funkcjach obsługiwanych przez urządzenie i bez potrzeby niemożliwie zarządzanej kolekcji wywołań systemowych.

Używa

Konfiguracja urządzeń sprzętowych

Powszechnym zastosowaniem ioctl jest sterowanie urządzeniami sprzętowymi.

Na przykład w systemach Win32 wywołania ioctl mogą komunikować się z urządzeniami USB lub mogą wykrywać informacje o geometrii dysku podłączonych urządzeń pamięci masowej.

W OpenBSD i NetBSD ioctl jest używany przez sterownik pseudo-urządzenia bio(4) i narzędzie bioctl do implementacji zarządzania woluminami RAID w zunifikowanym, niezależnym od dostawcy interfejsie, podobnym do ifconfig .

W NetBSD ioctl jest również używany przez system sysmon .

Terminale

Jednym z zastosowań ioctl w kodzie udostępnianym aplikacjom użytkowników końcowych jest terminalowe wejście/wyjście.

Unix tradycyjnie intensywnie wykorzystywały interfejsy wiersza poleceń . Interfejs wiersza poleceń systemu Unix jest zbudowany na pseudo terminalach (ptys), które emulują sprzętowe terminale tekstowe, takie jak VT100s . Pty jest kontrolowane i konfigurowane tak, jakby było urządzeniem sprzętowym, za pomocą ioctl . Na przykład rozmiar okna pty jest ustawiany za pomocą TIOCSWINSZ . Funkcja ioctl TIOCSTI (kontrola we/wy terminala, symulacja wejścia terminala) może wepchnąć znak do strumienia urządzenia.

Rozszerzenia jądra

Gdy aplikacje muszą rozszerzyć jądro, na przykład w celu przyspieszenia przetwarzania sieciowego, wywołania ioctl zapewniają wygodny sposób łączenia kodu przestrzeni użytkownika z rozszerzeniami jądra. Rozszerzenia jądra mogą zapewniać lokalizację w systemie plików, którą można otworzyć według nazwy, przez którą można wysłać dowolną liczbę wywołań ioctl , umożliwiając zaprogramowanie rozszerzenia bez dodawania wywołań systemowych do systemu operacyjnego.

alternatywa sysctl

Według dewelopera OpenBSD , ioctl i sysctl to dwa wywołania systemowe rozszerzające jądro, przy czym sysctl jest prawdopodobnie prostszym z nich.

W NetBSD framework sysmon_envsys do monitorowania sprzętu używa ioctl do proplib ; podczas gdy OpenBSD i DragonFly BSD zamiast tego używają sysctl dla swoich odpowiednich ram hw.sensors . Oryginalna wersja envsys w NetBSD została zaimplementowana z ioctl zanim proplib był dostępny i zawierała komunikat sugerujący, że framework jest eksperymentalny i powinien zostać zastąpiony przez sysctl(8) , który powinien zostać opracowany, co potencjalnie wyjaśnia wybór sysctl w OpenBSD wraz z późniejszym wprowadzeniem hw.sensors w 2003 roku. Jednak kiedy framework envsys został przeprojektowany w 2007 roku wokół proplib , wywołanie systemowe pozostało jako ioctl , a wiadomość została usunięta.

Implementacje

Uniks

Wywołanie systemowe ioctl po raz pierwszy pojawiło się w wersji 7 Unix , jako stty o zmienionej nazwie . Wywołanie ioctl przyjmuje jako parametry :

  1. otwarty deskryptor pliku
  2. numer kodu żądania
  3. niewpisany wskaźnik do danych (albo idący do sterownika, wracający ze sterownika, albo jedno i drugie).

Jądro zazwyczaj wysyła wywołanie ioctl bezpośrednio do sterownika urządzenia, który może zinterpretować numer żądania i dane w dowolny wymagany sposób . Autorzy każdego dokumentu sterownika żądają numerów dla tego konkretnego sterownika i dostarczają je jako stałe w pliku nagłówkowym .

Niektóre systemy Unix, w tym Linux , mają konwencje, które kodują w numerze żądania rozmiar danych, które mają być przesłane do/ze sterownika urządzenia, kierunek przesyłania danych i tożsamość sterownika realizującego żądanie. Niezależnie od tego, czy taka konwencja jest przestrzegana, jądro i sterownik współpracują w celu dostarczenia jednolitego kodu błędu (oznaczonego stałą symboliczną ENOTTY ) do aplikacji, która wysyła żądanie do sterownika, który go nie rozpoznaje.

Mnemonik ENOTTY (tradycyjnie kojarzony z komunikatem tekstowym „ To nie jest maszyna do pisania ”) wywodzi się z najwcześniejszych systemów, które zawierały wywołanie ioctl , gdzie tylko urządzenie dalekopisowe ( tty ) zgłaszało ten błąd. Chociaż symboliczny mnemonik jest ustalany przez wymagania dotyczące zgodności, niektóre nowoczesne systemy bardziej pomocne wyświetlają bardziej ogólny komunikat, taki jak „ Niewłaściwa operacja sterowania urządzeniem ” (lub jego lokalizacja ).

TCSETS jest przykładem wywołania ioctl na porcie szeregowym . Normalne wywołania odczytu i zapisu na porcie szeregowym odbierają i wysyłają bajty danych. Wywołanie ioctl(fd,TCSETS,data) , niezależne od takich normalnych we/wy, kontroluje różne opcje sterownika, takie jak obsługa znaków specjalnych lub sygnały wyjściowe na porcie (takie jak sygnał DTR ).

Win32

Win32 DeviceIoControl przyjmuje jako parametry:

  1. uchwyt otwartego obiektu (odpowiednik deskryptora pliku w Win32)
  2. numer kodu żądania („kod kontrolny”)
  3. bufor dla parametrów wejściowych
  4. długość bufora wejściowego
  5. bufor dla wyników wyjściowych
  6. długość bufora wyjściowego
  7. OVERLAPPED , jeśli używane są nakładające się wejścia/wyjścia .

Kod kontrolny urządzenia Win32 uwzględnia tryb wykonywanej operacji.

Istnieją 4 zdefiniowane tryby pracy, mające wpływ na bezpieczeństwo sterownika urządzenia -

  1. METHOD_IN_DIRECT : Adres bufora jest weryfikowany pod kątem możliwości odczytu przez osobę wywołującą w trybie użytkownika.
  2. METHOD_OUT_DIRECT : Adres bufora jest weryfikowany jako możliwy do zapisu przez osobę wywołującą w trybie użytkownika.
  3. METHOD_NEITHER : Adresy wirtualne trybu użytkownika są przekazywane do sterownika bez mapowania lub sprawdzania poprawności.
  4. METHOD_BUFFERED : Współdzielone bufory kontrolowane przez IO Manager są używane do przenoszenia danych do iz trybu użytkownika.

Alternatywy

Inne wektorowe interfejsy wywołań

Urządzenia i rozszerzenia jądra mogą być łączone z przestrzenią użytkownika za pomocą dodatkowych nowych wywołań systemowych, chociaż takie podejście jest rzadko stosowane, ponieważ twórcy systemów operacyjnych starają się, aby interfejs wywołań systemowych był skoncentrowany i wydajny.

W systemach operacyjnych Unix popularne są dwa inne interfejsy wywołań wektorowych: wywołanie systemowe fcntl („kontrola plików”) konfiguruje otwarte pliki i jest używane w sytuacjach takich jak włączanie nieblokujących operacji we/wy ; a setsockopt („ustaw opcję gniazda”) konfiguruje otwarte gniazda sieciowe , narzędzie używane do konfigurowania zapory pakietowej ipfw w systemach BSD Unix .

Mapowanie pamięci

Unix
i możliwości wejścia/wyjścia są czasami dostarczane za pomocą plików mapowanych w pamięci . Aplikacje współpracujące z urządzeniami otwierają lokalizację w systemie plików odpowiadającą urządzeniu, tak jak w przypadku wywołania ioctl , ale następnie używają wywołań systemowych mapowania pamięci, aby powiązać część swojej przestrzeni adresowej z jądrem. Ten interfejs jest znacznie wydajniejszym sposobem zapewniania masowego przesyłania danych między urządzeniem a w przestrzeni użytkownika ; indywidualny ioctl lub wywołania systemowe odczytu/zapisu powodują narzut z powodu powtarzających się przejść z przestrzeni użytkownika do jądra, gdzie dostęp do zakresu adresów mapowanych w pamięci nie wiąże się z takim narzutem.
Można użyć metod
Win32 Buffered IO lub nazwanych obiektów mapowania plików;
jednak w przypadku prostych sterowników urządzeń standardowe dostępy DeviceIoControl METHOD_ są wystarczające.

łącze sieciowe

Netlink to podobny do gniazda mechanizm komunikacji między procesami (IPC), zaprojektowany jako bardziej elastyczny następca ioctl .

Implikacje

Złożoność

ioctl minimalizują złożoność interfejsu wywołań systemowych jądra. Jednakże, zapewniając programistom miejsce do „przechowywania” bitów i fragmentów interfejsów programistycznych jądra, ioctl komplikują ogólny interfejs API użytkownika do jądra. Jądro, które zapewnia kilkaset wywołań systemowych, może dostarczyć kilka tysięcy wywołań ioctl.

Chociaż interfejs wywołań ioctl wygląda nieco inaczej niż konwencjonalne wywołania systemowe, w praktyce istnieje niewielka różnica między wywołaniem ioctl a wywołaniem systemowym; wywołanie ioctl jest po prostu wywołaniem systemowym z innym mechanizmem wysyłania. Wiele argumentów przemawiających przeciwko rozszerzeniu interfejsu wywołań systemowych jądra można zatem odnieść do interfejsów ioctl .

Dla twórców aplikacji wywołania systemowe nie różnią się niczym od podprogramów aplikacji; są to po prostu wywołania funkcji, które pobierają argumenty i zwracają wartości. Biblioteki uruchomieniowe systemu operacyjnego maskują złożoność związaną z wywoływaniem wywołań systemowych. Niestety, biblioteki uruchomieniowe nie powodują, że ioctl są przezroczyste. Proste operacje, takie jak wykrywanie adresów IP maszyny, często wymagają splątanych wywołań ioctl , z których każde wymaga magicznych liczb i struktur argumentów. [ potrzebne źródło ]

Libpcap i libdnet to dwa przykłady zewnętrznych bibliotek Unix zaprojektowanych w celu maskowania złożoności interfejsów ioctl , odpowiednio do przechwytywania pakietów i we/wy pakietów.

Bezpieczeństwo

Interfejsy użytkownika do jądra głównych systemów operacyjnych są często dokładnie sprawdzane pod kątem wad kodu i luk w zabezpieczeniach przed wydaniem. Audyty te zwykle koncentrują się na dobrze udokumentowanych interfejsach wywołań systemowych; na przykład audytorzy mogą zapewnić, że poufne wywołania zabezpieczeń, takie jak zmiana identyfikatorów użytkowników, będą dostępne tylko dla użytkowników administracyjnych.

Interfejsy ioctl są bardziej skomplikowane, bardziej zróżnicowane, a przez to trudniejsze do audytu niż wywołania systemowe. Ponadto, ponieważ wywołania ioctl mogą być dostarczane przez zewnętrznych programistów, często po wydaniu podstawowego systemu operacyjnego, implementacje wywołań ioctl mogą podlegać mniejszej kontroli, a tym samym zawierać więcej luk. Wreszcie, wiele wywołań ioctl , szczególnie w przypadku sterowników urządzeń innych firm, jest nieudokumentowanych.

Ponieważ procedura obsługi wywołania ioctl znajduje się bezpośrednio w trybie jądra, dane wejściowe z przestrzeni użytkownika powinny być dokładnie sprawdzane. Luki w zabezpieczeniach sterowników urządzeń mogą zostać wykorzystane przez lokalnych użytkowników poprzez przekazanie nieprawidłowych buforów do ioctl .

Win32 i Unix mogą chronić nazwę urządzenia w przestrzeni użytkownika przed dostępem aplikacji za pomocą określonych kontroli dostępu zastosowanych do urządzenia. Problemy z bezpieczeństwem mogą powstać, gdy twórcy sterowników urządzeń nie zastosują odpowiedniej kontroli dostępu do w przestrzeni użytkownika .

Niektóre nowoczesne systemy operacyjne chronią jądro przed wrogim kodem przestrzeni użytkownika (takim jak aplikacje, które zostały zainfekowane przez exploity związane z przepełnieniem bufora ) za pomocą opakowań wywołań systemowych. Opakowania wywołań systemowych implementują kontrolę dostępu opartą na rolach, określając, które wywołania systemowe mogą być wywoływane przez które aplikacje; opakowania mogą być na przykład użyte do „odwołania” prawa programu pocztowego do tworzenia innych programów. ioctl komplikują opakowania wywołań systemowych, ponieważ jest ich duża liczba, z których każdy przyjmuje inne argumenty, z których niektóre mogą być wymagane przez normalne programy.

Dalsza lektura

  •   W. Richard Stevens , Zaawansowane programowanie w środowisku UNIX (Addison-Wesley, 1992, ISBN 0-201-56317-7 ), sekcja 3.14.
  • Ogólne operacje sterowania we/wy w podręczniku online biblioteki GNU C
  • ioctl(2) Podręcznik programisty systemu Unix w wersji 7
  • ioctl(2) Podręcznik programisty Linuksa – Wywołania systemowe
  • ioctl(2) Podręcznik wywołań systemowych FreeBSD
  • ioctl(2) Podręcznik wywołań systemowych OpenBSD
  • ioctl(2) Podręcznik referencyjny wywołań systemowych Solarisa 10
  • „Dokumentacja DeviceIoControl w Microsoft Developer Network