Blog literacki, portal erotyczny - seks i humor nie z tej ziemi


Do spisu tresci tematu 1.
1.6 Zegar i przerwania





Spis tresci

Wprowadzenie
Ladowanie systemu a przerwania
Mechanizm wolania funkcji systemowych

Algorytm system_call
Algorytm ret_from_sys_call

Obsluga wyjatkow
Obsluga przerwan sprzetowych

Funkcja do_IRQ()

Rejestrowanie funkcji wolanych przez do_[fast_]IRQ()

Sterowniki urzadzen - "bottom halves"

Przerwanie zegarowe

Funkcja do_timer()

Bibliografia






Wprowadzenie

Obsluga przerwan, w tym przerwania zegarowego, w sposob naturalny
mocno uzalezniona jest od architektury komputera goszczacego system
operacyjny. Poniewaz Linuxa zaimplementowano poczatkowo na zwyklym
klonie IBM PC z jednym procesorem 80386 Intela, skupie sie na tej wlasnie
platformie (jest to zreszta jedyny sprzet, jaki w miare dobrze znam).
Choc materialy, ktore wymieniam w
bibliografii dotyczyc moga roznych wersji
Linuxa, podstawowym zrodlem informacji byl dla mnie kod jadra w wersji
2.0.20 dostepny na serwerze zodiac1.

Uzywac bede nastepujacej terminologii dotyczacej przerwan (vide [GoTu]):

przerwania sprzetowe (hardware interrupts) zglaszane sa przez
sterownik przerwan na zadanie jakiegos urzadzenia (np. zegara lub
klawiatury)
wyjatki (exceptions) generowane sa przez sam procesor; dziela
sie na:

niepowodzenia (faults), zglaszane przed pelnym
wykonaniem instrukcji i umozliwiajace jej powtorzenie; dobrym
przykladem jest tu wyjatek #PF (Page Fault), powstajacy przy
odwolaniu do strony nieobecnej w pamieci
potrzaski (traps), sygnalizowane po wykonaniu instrukcji,
gdy spelnione sa wyzwalajace je warunki; przykladem jest instrukcja
int, ktorej wykonanie zawsze podnosi wyjatek
zalamania (aborts), powstajace w wyniku powaznych i
nienaprawialnych bledow; na przyklad wyjatek #DF (Double Fault)
podnoszony jest wtedy, gdy przed zakonczeniem obslugi niepowodzenia
zgloszone zostaje nastepne.




W dalszej czesci tekstu poruszone zostana nastepujace zagadnienia:

Podprogramy obslugujace przerwania zglaszane sa procesorowi przez
jadro podczas ladowania systemu.
Jeden z potrzaskow (wyzwalany przez int 0x80) zwiazany jest z
mechanizmem wolania funkcji systemowych.
Obsluga wyjatkow zaszyta jest gleboko w jadrze
Linuxa i nie ma do niej bezposredniego dostepu. Informacje o wystapieniu
niektorych wyjatkow przekazywane sa do procesow uzytkownika w formie
sygnalow.
Przerwania sprzetowe moga byc obslugiwane
przez funkcje zglaszane w czasie pracy systemu przez sterowniki
urzadzen.
W specjalny sposob obslugiwane jest przerwanie sprzetowe
zegara systemowego.


Osoby pragnace dowiedziec sie wiecej o technicznych szczegolach obslugi
przerwan, priorytetach przerwan sprzetowych, czynnosciach wykonywanych
przez procesor przy przelaczaniu zadan i innych fascynujacych detalach
odsylam do ksiazki [GoTu].






Ladowanie systemu a przerwania

W trakcie
inicjacji jadra systemu kilkakrotnie zmieniane sa deskryptory
przerwan. Zabawe rozpoczyna kod znajdujacy sie w

arch/i386/kernel/head.s, ktory tworzy tablice deskryptorow
przerwan (IDT - Interrupt Descriptor Table) wypelniona deskryptorami
wskazujacymi funkcje ignore_int() (zadanie tego podprogramu
sprowadza sie do wyswietlenia tekstu "Unknown interrupt").

Znacznie ciekawsza jest funkcja

trap_init(), ustanawiajaca podprogramy
obslugi wyjatkow oraz wypelniajaca globalna i lokalne tablice
deskryptorow przy pomocy makr set_call_gate(),
set_system_gate() i set_trap_gate() zdefiniowanych
w pliku
include/asm-i386/system.h. Jako funkcja obslugi przerwania 0x80
rejestrowany jest takze interfejs do funkcji
systemowych - fragment kodu asemblerowego oznaczony etykieta

system_call.

Pojawiajaca sie w trap_init() funkcja

lcall7() uzywana jest prawdopodobnie tylko przy wykonywaniu
binariow w formacie iBCS2 i zapewne moze byc wolana z procesu
uzytkownika instrukcja call.

W funkcji
init_IRQ() ustawiana jest na okolo 100 Hz czestotliwosc pracy
zegara systemowego, a deskryptory przerwan sprzetowych ustawiane sa
przy pomocy makra set_intr_gate() na procedure-atrape,
konczaca tylko przerwanie (polega to na wyslaniu polecenia EOI, End
Of Interrupt, do odpowiedniego sterownika przerwan).

W funkcji
time_init() rejestrowany jest wreszcie podprogram obslugi
przerwania zegarowego.

Po przeanalizowaniu konfiguracji systemu, inicjalizacji konsoli i
managera pamieci wlaczane sa wreszcie makrem sti() przerwania
maskowalne. Gdzies po drodze do ostatecznego zaladowania systemu
pod przerwania sprzetowe podlaczaja sie jeszcze sterowniki urzadzen
(np. klawiatury czy myszy).






Mechanizm wolania funkcji systemowych

Tablica funkcji systemowych

sys_call_table Linuxa 2.0.20 zawiera 163 pozycje. W kazdej
z nich znajduje sie adres podprogramu z przestrzeni jadra uruchamianego
przez funkcje
system_call() (no, prawie: jest tez jedno zero zastepujace
adres funkcji afs_syscall). Nazwy podprogramow sa z dokladnoscia
do prefiksu sys_ albo old_ takie, jak implementowanych
przez nie funkcji systemowych.

Interakcja miedzy funkcja z przestrzeni adresowej uzytkownika uzywajaca
funkcji systemowej (np. setuid()) a jadrem Linuxa wyglada
nastepujaco:

Funkcja setuid() jest zdefiniowana w bibliotece libc
przy pomocy makra syscall1() (1 to liczba argumentow funkcji
systemowej). W przypadku funkcji o zmiennej liczbie argumentow (np.
ioctl()), implementacja jest bardziej skomplikowana, lecz
zachowane sa opisane ponizej zasady.
Makro syscall1() tworzy fragment kodu, ktory odklada na stos
przekazany funkcji argument, dorzuca do niego numer funkcji (23 dla
setuid()) i wykonuje instrukcje int 0x80.
Sterowanie przekazywane jest automagicznie do algorytmu (dosc ciezko
nazwac go funkcja)
system_call. Przed jego
wykonaniem procesor zmienia tryb pracy z 3 (kod uzytkownika) na 0
(kod nadzorcy), po powrocie z niego wykonywany jest algorytm
ret_from_sys_call
i - znow automagicznie - procesor wraca do trybu uzytkownika.
Po powrocie z jadra do przestrzeni uzytkownika syscall1()
sprawdza kod powrotu funkcji systemowej. Jesli jest dodatni, od razu
go zwraca. W przeciwnym razie wstawia do errno wartosc
-kod_powrotu i zwraca -1.


Dostep do danych przechowywanych w przestrzeni adresowej uzytkownika
(selektor jego segmentu danych jest w rejestrze FS) zapewniaja m.in.
makra get_user() i put_user() zdefiniowane w pliku

arch/i386/kernel/segment.h. Ich wykonanie moze spowodowac
koniecznosc sciagniecia do pamieci strony, do ktorej sie odwoluja, co
opisane jest
gdzie indziej.






Algorytm
system_call()

Argument: numer wolanej funkcji systemowej
Wynik: ujemny numer bledu albo nieujemny kod powrotu



{
if(numer_funkcji > NR_syscalls) // NR_syscalls = 256
return(-ENOSYS);
if(sys_call_table[numer_funkcji]==NULL)
return(-ENOSYS);
return((sys_call_table[numer_funkcji])());
}


Uwaga: w powyzszym kodzie moga zostac wykonane rozkazy call
syscall_trace,
ale to juz nie moj rewir.







Algorytm
ret_from_sys_call


{
if(intr_count!=0) // obslugujemy jakies przerwanie
return; // wroc do przerwanego procesu
while(czekaja funkcje "bottom half")
{
++intr_count;
do_bottom_half();
--intr_count;
}
if(przerwanym procesem bylo jadro)
return;
if(ustawiona jest flaga need_resched)
{
schedule();
goto ret_from_sys_call;
}
if(biezacy proces to task[0]) // do task[0] nie sa wysylane
return; // zadne sygnaly
if(czekaja jakies sygnaly)
do_signal();
}

Uwagi:

intr_count blokuje rekurencyjne wejscia do podprogramow
obslugi przerwan, ktore nie musza byc wielowejsciowe
funkcja
do_bottom_half() opisana jest wraz z
obsluga przerwan sprzetowych
funkcja schedule() powinna byc opisana w
temacie 2
funkcja do_signal() rowniez zajal sie
ktos inny






Obsluga wyjatkow

Podprogramy obslugi wyjatkow skladaja sie z dwoch czesci: definiowany w
pliku
entry.s fragmentu kodu przechodzi do trybu jadra, wola funkcje
o nazwie do_nazwa_wyjatku() zdefiniowana
zazwyczaj w pliku
traps.c i wraca do przerwanego procesu, wykonujac algorytm
ret_from_sys_call. (Niektore
podprogramy, np. obslugi wyjatku 0, sa z powodow technicznych bardziej
skomplikowane)


Wiekszosc podprogramow obslugi generowana jest przez makro

DO_ERROR(), ktore wola funkcje
force_sig() z odpowiednim numerem sygnalu (patrz tabelka pod
spodem), po czym sprawdza, czy wyjatku nie spowodowal kod jadra (jesli
tak, zatrzymuje system procedura

die_if_kernel()).



Wyjatek
Sygnal
Nazwa podprogramu obslugi


0 SIGFPE do_divide_error
3 SIGTRAP do_int3
4 SIGSEGV do_overflow
5 SIGSEGV do_bounds
7 SIGSEGV do_device_not_available
8 SIGSEGV do_double_fault
9 SIGFPE do_coprocessor_segment_overrun
10 SIGSEGV do_invalid_TSS
11 SIGBUS do_segment_not_present
12 SIGBUS do_stack_segment
17 SIGSEGV do_alignment_check


Uwaga: wyjatek 9 obslugiwany jest w kontekscie procesu, ktory ostatnio
uzywal koprocesora (wszystkie pozostale - w kontekscie procesu biezacego).


Poza tym:

wyjatek 1 (praca krokowa) obsluguje funkcja
do_debug(), ktora wola force_sig(SIGTRAP, current),
po czym - jesli przerwanym procesem byl kod jadra - zeruje rejestr DR7
wyjatek 2, bedacy w rzeczywistosci przerwaniem
niemaskowalnym (NMI), obsluguje funkcja do_nmi(),
wyswietlajaca komunikat o potencjalnych problemach z pamiecia RAM
wyjatek 13 obsluguje funkcja
do_general_protection(), w ktorej po magii zwiazanej z trybem
wirtualnym 8086 (byc moze chodzi tu o emulacje MS DOS-u ?) wolana jest
funkcja force_sig(SIGSEGV, current)
wyjatek 14 obsluguje funkcja

do_page_fault(), ktorej opis powinien znajdowac sie w
temacie 4
wyjatek 16 obsluguje funkcja
do_coprocessor_error(), ktora po zebraniu argumentow wola
(posrednio) force_sig(SIGFPE) w kontekscie procesu, ktory
ostatni uzywal koprocesora
wyjatki 15 i 18..47
sa zarezerwowane. Wykonywana po ich zgloszeniu funkcja
do_reserved() wyswietla stosowny komunikat.







Obsluga przerwan sprzetowych

Podstawowe informacje dotyczace przerwan sprzetowych:

urzadzenie (mowa tu tylko o urzadzeniach z wlasnym IRQ, czyli
podlaczonych do sterownika przerwan) dochodzi do wniosku, ze zaszla
sytuacja, o ktorej powinien dowiedziec sie procesor (np. nacisnieta
zostala litera 'A' na klawiaturze) i zglasza te potrzebe sterownikowi
przerwan
sterownik sprawdza, czy nie jest przypadkiem obslugiwane przerwanie
o wyzszym lub rownym zgloszonemu priorytecie; jesli jest, czeka na
moment, w ktorym wszystkie takie przerwania zostana obsluzone
(najwyzszy priorytet ma zegar systemowy); jesli obslugiwane jest
przerwanie o priorytecie nizszym, mozna jego obsluge przerwac
jesli w czasie oczekiwania przerwania o numerze n
na obsluzenie nadejdzie drugie przerwanie o tym samym numerze,
zostanie ono zignorowane (przerwania nie sa zliczane)
gdy sterownik moze juz zglosic procesorowi przerwanie, musi poczekac
na ustawienie flagi IF procesora, ktora odblokowuje przerwania (jest
ona automatycznie kasowana przez procesor przed rozpoczeciem
wykonywania procedury obslugi przerwania; nalezy ja ustawic recznie
lub po zakonczeniu procedury odtwarzany jest jej poprzedni stan)
dopoki procedura obslugi przerwania nie wysle do sterownika sygnalu
konca przerwania (EOI, End Of Interrupt), sterownik uwaza przerwanie
za obslugiwane.

Z powyzszego zgrubnego opisu wspolpracy CPU i sterownika przerwan
wynikaja dla tworcow procedur obslugi nastepujace reguly, ktorych
wplyw widac w kodzie Linuxa:

ustawiaj flage IF dopiero wtedy, gdy nie zaszkodzi ci przerwanie
pracy - ale rob to jak najszybciej, by nie blokowac przerwan o
wyzszym priorytecie
jesli za szybko wyslesz sygnal EOI do sterownika, mozesz spodziewac
sie nastepnego przerwania o tym samym numerze w najmniej odpowiedniej
chwili; jesli zrobisz to za pozno, mozesz je utracic
twoja procedura powinna byc jak najszybsza; jesli zwiazane sa z nia
jakies dlugie obliczenia, powinny byc przeprowadzane poza przerwaniem
(koncepcja "bottom halves" - patrz nizej).




W pliku
irq.c makro
BUILD_IRQ() tworzy po trzy podprogramy obslugujace kazde
przerwanie (nazwijmy je FOO):

FOO_interrupt(): potwierdza otrzymanie przerwania,
inkrementuje zmienna intr_count, ustawia flage IF, wola
do_IRQ() z argumentem rownym
numerowi przerwania, kasuje flage IF, wysyla do sterownika EOI,
dekrementuje zmienna intr_count i wykonuje algorytm
ret_from_sys_call
(podstawowy sposob obslugi przerwan)
fast_FOO_interrupt(): podobny do FOO_interrupt(),
ale nie ustawia IF (nie musi zatem ruszac intr_count), wola
do_fast_IRQ() i wraca
bezposrednio do przerwanego procesu
bad_FOO_interrupt(): tylko potwierdza otrzymanie przerwania
i wraca bezposrednio do przerwanego procesu.


Uwaga: wyjatkiem jest przerwanie zegarowe, ktore
obslugiwane jest w sposob specjalny.






Funkcja

do_IRQ()

Procedura obslugi IRQ, ktore zostalo zainstalowane bez flagi
SA_INTERRUPT (patrz nizej). Uruchamiana z ustawiona flaga IF,
po jej zakonczeniu sterowanie przechodzi do
ret_from_sys_call. Powinna
sie ja stosowac w przypadku przerwan, ktorych obsluga trwa dosc dlugo
(np. przerwanie zegara lub klawiatury).
Argument: irq - numer przerwania



{
inkrementuj licznik obsluzonych przerwan irq;
forEach(funkcja obslugi zarejestrowana dla irq)
wykonaj_funkcje;
if(ktoras z funkcji miala atrybut SA_SAMPLE_RANDOM)
add_interrupt_randomness(irq);
}


Uwaga: funkcja
add_interrupt_randomness() zwieksza entropie wbudowanego w
Linux generatora liczb pseudolosowych (jest to rozwiazanie, ktorym bylem
oczarowany - zazwyczaj o generowanie liczb pseudolosowych musi sie
troszczyc tworca uzywajacego ich oprogramowania).

Funkcja do_fast_IRQ() rozni sie od do_IRQ() jedynie
tym, ze wolanym funkcjom obslugi nie jest przekazywany adres odlozonych
na stosie argumentow.








Rejestrowanie funkcji wolanych przez do_[fast_]IRQ()

Aby do_IRQ() i do_fast_IRQ() mialy co robic, trzeba
najpierw zarejestrowac wolane przez nie funkcje. Wymienie teraz (gdyz
szczegoly, choc ciekawe, nie wydaja mi sie szczegolnie wazne) zwiazane
z tym zadaniem podprogramy:


request_irq() probuje zarejestrowac funkcje (nie musi sie
to udac - moze zabraknac pamieci na struct irqaction lub
wybranym przerwaniem nie mozna sie dzielic); jesli przerwanie to
nie jest uzywane, odblokowuje je

free_irq() wyrzuca funkcje z listy; jesli lista zostanie
oprozniona, blokuje przerwanie.



Struktura struct irqaction, w ktorej przechowywane sa dane
rejestrowanych funkcji, zdefiniowana jest w pliku

include/linux/interrupt.h:


struct irqaction
{
void (*handler)(int, // numer obslugiwanego IRQ
void *, // tu bedzie irqaction.dev_id
struct pt_regs *); // NULL lub rejestry CPU
unsigned long flags; // flagi SA_*
unsigned long mask; // ???
const char *name; // nazwa funkcji obslugi
void *dev_id; // argument dla handler()
struct irqaction *next; // nastepna zarejestrowana funkcja
}

W polu flags ustawiane sa bity SA_* zdefiniowane w
pliku
asm-i386/signal.h.







Sterowniki urzadzen - "bottom halves"

Aby zminimalizowac czas spedzony w do_IRQ(),
projektanci sterownikow urzadzen wymagajacych czasochlonnej obslugi
powinni podzielic ja na dwie czesci. Szybka funkcja rejestrowana
przez request_irq() jest wolana w chwili nadejscia przerwania.
Wolniejsza funkcja realizujaca druga czesc obslugi wstawiana jest
do tablicy
bh_base; jej szybka siostra w razie potrzeby zaznacza
(funkcja
bh_mark()), ze
do_bottom_half() (uruchamiana w
ret_from_sys_call) ma dokonczyc (przy wlaczonych przerwaniach)
obsluge urzadzenia.






Przerwanie zegarowe

Opisane przy obsludze przerwan sprzetowych
trzy wersje procedur obslugi w przypadku przerwania zegarowego
zastepowane sa jedna (choc znana pod trzema nazwami). Jest ona bardzo
podobna do FOO_interrupt(); jedyna roznica polega na tym, ze
w trakcie jej wykonywania przerwania sa caly czas zablokowane.

Kazde przerwanie zegarowe powoduje wykonanie podlaczonej w

time_init() funkcji
timer_interrupt(). Wola ona najpierw opisana ponizej funkcje

do_timer(), a nastepnie przeprowadza jakies tajemnicze korekty
zegara czasu rzeczywistego.






Funkcja
do_timer()


{
jiffies++; // takty od startu systemu
lost_ticks++; // takty zuzyte przez proces
mark_bh(TIMER_BH); // wykonaj timer_bh()
if(tryb jadra)
{
lost_ticks_system++; // takty zuzyte przez system
if(profilowanie wlaczone
i PID procesu != 0)
gromadz informacje do profilu programu;
}
if(planista czeka na uplyw kwantu czasu)
mark_bh(TQUEUE_BH); // uruchom bottom half planisty
}

Uwagi:

zmienna jiffies opisana jest wraz z funkcjami systemowymi
dotyczacymi czasu
funkcja timer_bh() wola update_times() oraz funkcje
zwiazane ze stoperami, ktore juz zakonczyly odliczanie
sposob wykorzystania zegara przez planiste rozpracowywany byl
przez osoby zajmujace sie tematem 2.;
tam rowniez znajduje sie zapewne opis update_times()







Bibliografia


Pliki zrodlowe Linuxa


include/asm-i386/system.h

- definicje roznych makr "niskopoziomowych" (np. cli())

include/asm-i386/segment.h

- dostep do przestrzeni adresowej uzytkownika

arch/i386/kernel/head.s

- rozpoczyna prace w trybie wirtualnym (protected mode)

arch/i386/kernel/entry.s

- niskopoziomowe interfejsy do funkcji systemowych i podprogramow
obslugi wyjatkow

arch/i386/kernel/traps.c

- m.in. ustawia podprogramy obslugi wyjatkow

arch/i386/kernel/irq.c

- obsluga przerwan sprzetowych (kod)

include/asm-i386/irq.h

- obsluga przerwan sprzetowych (makra)

linux/kernel/softirq.c

- wykonywanie funkcji "bottom half"

include/linux/interrupt.h

- wspolne dla wszystkich platform makra

arch/i386/kernel/time.c

- duzo rzeczy zwiazanych z czasem

linux/kernel/sched.c

- powiazanie zegara z planista

The Linux Kernel Hacker's Guide


Panorama jadra Linuxa


Obsluga funkcji systemowych w Linuxie


Obsluga bledow braku strony przy odwolaniach do danych z przestrzeni
adresowej uzytkownika


[GoTu]
Goszczynski R., Tuszynski M.,
Mikroprocesory 80286, 80386 i i486,
Help 1991






Autor: Jan Koslacz
  • zanotowane.pl
  • doc.pisz.pl
  • pdf.pisz.pl
  • qualintaka.pev.pl
  •