Systemy Operacyjne – semestr drugi
Wyk a
ł d pi t
ą y
Obs u
ł ga przerwań
Jednym z zadań systemu operacyjnego jest komunikacja z urządzeniami wej c ś ia – wyj c
ś ia. Ponieważ są one zazwyczaj du o
ż wolniejsze od procesora, to zwykle nie
oczekuje on na zakończenie przez nie działania, mimo to powinien wiedzieć kiedy taki moment nastąpi. Jednym z rozwi z ą ań tej kwestii jest okresowe sprawdzanie
stanu takich urządzeń, czyli tzw. polling. Niestety, to rozwi z
ą anie mo e
ż prowadzić do sytuacji, w której system operacyjny mo e
ż „przegapić” informacj ,
ę którą przesłało
urządzenie. Lepszym rozwiązaniem jest zastosowanie systemu przerwa .
ń Przerwanie jest sygnałem dla procesora, e
ż oto jakieś urządzenie zewn t
ę rzne wykonało swoją
pracę i nale y
ż je odpowiednio obsłu y
ż ć. Przerwania są najcz
c
ęś iej generowane asynchronicznie – mogą pojawić się w dowolnym momencie pracy procesora. Ka d ż e
urządzenie dysponuje poł c
ą zeniem z kontrolerem przerwa .
ń Kiedy chce zg os
ł
ić przerwanie sygnalizuje to za pomocą tego w a
ł
n
ś ie połączenia. Kontroler powiadamia
o wystąpieniu przerwania procesor. Ka d
ż emu urządzeniu jest również przyporządkowany numer przerwania, który pozwala okre l ś i ,
ć które urządzenie zgłosi o
ł
przerwanie i jak to przerwanie nale y
ż obsłu y
ż
.
ć Numer przerwania skojarzony jest z linią zgłoszenia przerwania IRQ (ang. Interrupt Request). W komputerach kompatybilnych z IBM PC część przerwań jest na stałe przypisana pewnym urządzeniom, a część – w szczególno c ś i dla tych urządzeń, które są podłączone z systemem
przez magistralę PCI – jest przydzielana w sposób dynamiczny. Oprócz obs u ł gi urz d
ą zeń zewnętrznych system przerwań pozwala na obsługę sytuacji wyj t
ą kowych.
Z każdym przerwaniem, które jest skojarzone z urządzeniem lub wyjątkiem związana jest procedura obsługi takiego przerwania (ang. interrupt handler lub interrupt service routine – ISR). W przypadku Linuksa procedury te są po prostu funkcjami napisanymi w języku C i umieszczonymi w sterowniku danego urządzenia lub w cz
c
ęś i jądra odpowiedzialnej za obsługę wyjątku. Ka d
ż a z takich funkcji jest napisana zgodnie z okre l
ś onym prototypem. Od zwykłych funkcji ró n
ż i je jedynie to, e
ż
wykonywane są w kontek c
ś ie przerwania i wyłącznie w reakcji na pojawienie się sytuacji krytycznej lub sygnału od urządzenia. Procedura obs u ł gi przerwania
wywo y
ł wana jest w sposób asynchroniczny i dlatego wa n
ż ym jest, aby jej wykonanie zostało zakończone w jak najkrótszym czasie. Z tego powodu kod obsługi przerwań jest podzielony na dwie cz
c
ęś i zwane górną po ów
ł
ką i dolną po ów
ł
ką. Górna połówka wykonuje wszystkie czynno c
ś i, których wykonanie jest konieczne zaraz po
odebraniu sygnału przerwania, przede wszystkim powiadamia urządzenie o przyjęciu przerwania. Połówka dolna realizuje wszystkie te czynno c ś i, których wykonanie
mo n
ż a odroczy
ć na pewien czas. Zazwyczaj jednak po ów
ł
ki dolne są wykonywane zaraz po wykonaniu połówek górnych.
Ka d
ż a procedura obs u
ł gi przerwania musi zosta
ć zarejestrowana za po r
ś ednictwem funkcji request_irq, która kojarzy funkcję z przerwaniem i uaktywnia daną lini : ę
int request_irq(unsigned int irq, irqreturn_t (*handler)(int, void *, struct pt_reqs *), unsigned long irqflags, const char *devname, void *dev_id) Pierwszy parametr okre l
ś a numer przerwania, drugi jest wska n
ź ikiem na procedurę obsługi przerwania, trzeci parametr albo jest zerem, albo maską bitową mogącą się składać z trzech znaczników: SA_INTERRUPT – określa czy dana procedura jest szybką procedurą obs u ł gi przerwania, je l
ś i tak, to jest ona wykonywana przy
wyłączonych wszystkich przerwaniach w systemie. Obecnie wymaga tego jedynie procedura obsługi przerwania zegarowego, SA_SAMPLE_RANDOM – okre l ś a, czy
przerwanie zasili pulę entropii jądra, SA_SHIRQ – okre l
ś a mo l
ż iwość współdzielenia linii przerwania z innymi procedurami obsługi przerwa .
ń Ka d
ż a z nich musi być
rejestrowana z tym znacznikiem ustawionym. Czwarty parametr, to nazwa urz d ą zenia, która jest używana w katalogu /proc/irq i w pliku /proc/interrupts. Ostatni parametr jest u y
ż wany w obsłudze linii współdzielonych. Pozwala on na zwolnienie linii z tylko jednej procedury obsługi. Je l ś i linia nie jest wspó d
ł zielona
przekazywana jest wartość NULL, ale w przypadku linii współdzielonych przekazywany jest wskaźnik na strukturę sterownika, która mo e ż być wykorzystywana przez
procedury obsługi przerwań. Funkcja request_irq() zwraca zero, je l
ś i jej wykonanie zakończy się sukcesem. Dosyć powszechnym bł d
ę em jest błąd -EBUSY. Funkcja ta
mo e
ż ulegać blokowaniu, wi c
ę nie mo e
ż by
ć u y
ż ta w kontek c
ś ie przerwania.
Komplementarną do request_irq() jest funkcja free_irq():
void free_irq(unsigned int irq, void *dev_id)
Funkcja ta zwalnia linię IRQ okre l
ś oną warto c
ś ią parametru „irq”. W przypadku linii współdzielonych konieczne jest określenie urz d ą zenia, gdyż z linii usuwana jest
tylko procedura związana z tym urządzeniem. W przypadku linii niewspółdzielonych mo n ż a przekazać NULL.
Funkcje obs u
ł gi przerwań są definiowane według nast p
ę ującego prototypu:
static irqreturn_t intr_handler(int irq, void *dev_id, struct pt_regs *regs) Pierwszy parametr okre l
ś a numer linii przerwania. Drugi jest unikalną wartością identyfikującą urządzenie, które współdzieli z innymi urządzeniami linię przerwania.
Tą warto c
ś ią mo e
ż być adres struktury sterownika, który jest także przydatny podczas działania funkcji. Ostatni parametr jest wska n ź ikiem na strukturę
przechowującą stan rejestrów procesora sprzed wywołania procedury obs u ł gi przerwania. Nie wymaga się, aby funkcje te by y
ł wielobie n
ż e, gdyż wywo a
ł nie funkcji
powoduje zablokowanie linii z nią związanej. Funkcje te mogą zwracać dwie warto c ś i: IRQ_HANDLED i IRQ_NONE. W ich miejsce można wykorzysta
ć makrodefinicję
IRQ_RETVAL(x), która zwraca IRQ_HANDLED jeśli x jest ró n
ż y od zera i IRQ_NONE w przeciwnym przypadku. Typ wartości zwracanej przez funkcję został
wprowadzony celem zachowania kompatybilno c
ś i z poprzednimi wersjami jądra.
Procedury obs u
ł gi przerwań są wywoływane w kontek c
ś ie przerwania, co oznacza, e
ż nie są dozwolone w nich wywołania funkcji blokujących oraz nie jest wa n ż a
wartość (ang. invalid) zwracana przez makrodefinicję current. Procedury obsługi przerwania muszą wykonywać się szybko, aby nie blokować kolejnych zgłoszeń przerwania na tej samej linii. Korzystają one ze stosu jądra, który jest ograniczony do 8KB w 32 – bitowych platformach sprzętowych PC i do 16KB w 64 – bitowych platformach sprzętowych Alpha.
W chwili otrzymania zgłoszenia przerwania jądro zapami t
ę uje bieżące warto c
ś i rejestrów na stosie i wywołuje funkcję do_IRQ(). Ta funkcja odczytuje numer przerwania z warto c
ś i rejestrów odło on
ż
ych na stosie, a nast p
ę nie potwierdza przyjęcie przerwania i blokuje linię z nim związaną wywołując mask_and_ack_8259A()1.
Dalej do_IRQ() sprawdza, czy linia posiada jakąś zarejestrowaną procedurę obsługi i czy nie jest ona w danej chwili wykonywana. Kolejną czynno c ś ią jest wywołanie
handle_IRQ_event(). Ta funkcja przegląda listę wszystkich procedur związanych z daną linią i po kolei je uruchamia. Je l ś i jest ustawiony znacznik
SA_SAMPLE_RANDOM, to wywołuje również add_interrupt_randomness(), po czym ko c ń zy swe dzia a
ł nie i sterowanie wraca do do_IRQ(), która porządkuje stos
i wywo u
ł je ret_from_intr(). Ta ostatnia wykonuje czynno c
ś i zwi z
ą ane z przywróceniem pracy procesu u y
ż tkownika b d
ą ź kodu j d
ą ra.
Informacje statystyczne na temat przerwań obs u
ł giwanych przez poszczególne procesory w systemie mo n
ż a znaleźć w pliku /proc/interrupts. Są tam informacje o liczbie
obsłu on
ż
ych przerwa ,
ń na danej linii, o urządzeniu lub urządzeniach, które są skojarzone z tą linią, oraz o kontrolerze przerwa , ń który obs u
ł guje zg os
ł
zenie danego
przerwania.
Do obsługi systemu przerwań w jądrze zdefiniowano nast p
ę ujące funkcje:
1
To odnosi się tylko do komputerów klasy PC.
1
Systemy Operacyjne – semestr drugi
1.
local_irq_disable() - wyłącza lokalny (dla jednego procesora) system przerwań, 2.
local_irq_enable() - komplementarna względem poprzedniej funkcji,
3.
local_irq_save(unsigned long flags) - zachowuje bieżący stan przerwań,
4.
local_irq_restore(unsigned long flags) - przywraca zapami t
ę any stan przerwa ,
ń
5.
disable_irq_nosync(unsigned int irq) – natychmiastowe wył c
ą zenie przerwania o zadanym numerze,
6.
disable_irq(unsigned int irq) – jak wy ej
ż , ale z odczekaniem, a
ż zako c
ń zone zostaną wywołania procedur obs u
ł gi przerwań skojarzonych z tą lini ,
ą
7.
enable_irq(unsigned int irq) – odblokowanie przerwania (komplementarna do disable_irq_nosync()), 8.
synchronize_irq(unsigned int irq) – jak wy ej
ż (komplementarna do disable_irq()),
9.
irqs_disabled() - sprawdzenie stanu lokalnego systemu przerwań,
10.
in_interrupt() - okre l
ś enie kontekstu wykonywanego kodu,
11.
in_irq() - okre l
ś enie, czy aktualnie wykonywany kod jest realizowany w ramach procedury obs u ł gi przerwania.
Funkcje local_irq_disable() i local_irq_enable() zast p
ą iły funkcje cli() i sti(), które wyłączały system przerwań dla wszystkich procesorów dost p ę nych w systemie i nie
by y
ł w zwi z
ą ku z tym optymalne. Działanie tych funkcji nie jest bezpieczne, więc nale y ż zapamiętać i potem przywrócić stan przerwań za pomocą pary nast p
ę nych
funkcji. Funkcje disable_irq_nosync() i enable_irq() oraz disable_irq() i synchronize_irq() muszą być wykonywane naprzemiennie, tzn. jeśli została wykonana okre l ś ona
liczba np.: funkcji disable_irq_nosync(), to tyle samo musi nastąpić wykonań funkcji enable_irq().
W kolejnych wersjach jądra Linuksa serii 2.6 wprowadzono szereg zmian do górnych połówek obsługi przerwań. Począwszy od jądra 2.6.19 z prototypu procedury obsługi przerwania zniknął parametr przez który przekazywany był wska n ź iki na strukturę zawieraj c
ą ą warto c
ś i rejestrów sprzed chwili otrzymania przerwania.
Niewiele procedur korzystało z tych informacji, a ich przekazanie było kosztowne pod względem czasu i miejsca na stosie. Dla tych, które potrzebują warto c ś i rejestrów
zdefiniowano funkcję o nazwie get_irq_regs(), która zwraca wska n
ź ik na strukturę struct pt_regs. Wraz z wydaniem jądra 2.6.22 rozpocz t ę o proces zastępowania flag
SA_* flagami IRQF_. Działanie to zakończono w wydaniu 2.6.24. Flaga SA_INTERRUPT zosta a ł najpierw zastąpiona flagą IRQF_DISABLE, a następnie
IRQF_TIMER, flaga SA_SAMPLE_RANDOM flagą IRQF_SAMPLE_RANDOM, a flaga SA_SHARED flagą IRQF_SHARED. Poza tym dodano inne flagi, których definicje można znaleźć w pliku include/linux/interrupt.h. W jądrze o numerze 2.6.31 wprowadzono mechanizm wątków przerwań. Intencją tego mechanizmu jest umieszczenie obsługi przerwania w osobnym wątku jądra, eliminuj c
ą tym samym niedogodno c
ś i związane z brakiem blokowania w kontek c
ś ie przerwania oraz niektóre
z mało wygodnych w obsłudze dolnych połówek. Aby uniknąć wymuszania zmiany w kodzie wszystkich sterowników urządzeń ten mechanizm jest opcjonalny.
Procedury obs u
ł gi przerwania, które mają być wykonane w ramach w t
ą ku rejestrowane są za pomocą funkcji o następującym prototypie:
int request_threaded_irq(unsigned int irq, irq_handler_t handler, irq_handler_t quick_check_handler, unsigned long flags, const char *name, void *dev) W porównaniu z funkcją rejestrującą zwykłe procedury obsługi przerwania przyjmuje ona dodatkowy parametr, będący wska n ź ikiem na tzw. szybką procedurę obsługi
przerwania. Ta procedura mo e
ż oprócz tych samych wartości co zwykła procedura obs u
ł gi przerwania zwrócić IRQ_WAKE_THREAD, która nakazuje aktywację wątku odpowiedzialnego za obsługę przerwania. Dodatkowo, aby ułatwić autorom sterowników przej c ś ie na model przerwań obsługiwanych przez wątki, procedura ta może
zwrócić wartość IRQ_NEEDS_HANDLING, aby zasygnalizować, e
ż przerwanie musi być obsłu on
ż
e przez zwykłą procedurę obs u
ł gi przerwania. W tej wersji jądra
wprowadzono tak e
ż funkcję o prototypie:
int pci_enable_msi_block(struct pci_dev *dev, int count)
która pozwala włączyć sterownikowi zgłaszanie bloków przerwań MSI (ang. Message Signaled Interrupts). Tego typu przerwania są wykorzystywane przez sprz t ę
korzystaj c
ą y z magistrali PCI-Express lub PCI 2.2. W przeciwieństwie do tradycyjnych przerwań tego typu przerwania nie są zgłaszane poprzez zmianę stanu na odpowiedniej linii przerwania lecz przez zapis przez urządzenie informacji o niewielkim rozmiarze pod okre l ś ony adres w pami c
ę i. Ta informacja jest odczytywana przez
PIC, który sygnalizuje przerwanie procesorowi.
2