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