Jądro Linuksa (tak jak jądro każdego innego systemu operacyjnego) od czasu do czasu blokuje przerwania. Jeśli w tym czasie pojawi się przerwanie zegarowe, to jest ono zablokowane i nie zostaje obsłużone. Następuje utrata precyzji czasowej. RTLinux implementuje bardzo eleganckie rozwiązanie tego problemu.
W kodzie Linuksa przerwania są blokowane za pomocą makr sti, cli i iret: Makra te wyglądają dla architektur jednoprocesorowych następująco (plik include/asm-i386/processor.h):
#define cli() __cli() #define __cli() __asm__ __volatile__("cli": : :"memory") #define sti() __sti() #define __sti() __asm__ __volatile__("sti": : :"memory")Makra te wywołują instrukcje asemblera cli, sti i iret:
RTLinux zastępuje makra sti, cli i iret przez makra S_STI, S_CLI, S_IRET, które emulują makra sti, cli i iret. Emulowane przerwania nazywane są soft (bo ich blokowanie następuje tylko w pamięci operacyjnej), w odróżnieniu od prawdziwych przerwań hard, które blokowane są naprawdę - przez sprzęt.
Makra działają jak następuje:
S_CLI: movl $0, SFIF
Zmienna SFIF (software interrupt flag) to flaga, która oznacza, czy przerwania soft są zablokowane. Makro S_CLI po prostu zeruje tą flagę. Kiedy przychodzi przerwanie (via jądro RTLinuksa) emulator sprawdza tę zmienną Jeśli zmienna jest ustawiona (przerwania są odblokowane), natychmiast jest wywoływana funkcja obsługi przerwania. Jeżeli SFIF == 0 nie jest ustawiona, funkcja obsługi nie jest wywoływana. Zamiast tego ustawiany jest w zmiennej, która przechowuje informacje o nieobsłużonych przerwaniach bit odpowiadający temu przerwaniu. Kiedy Linux odblokuje przerwania, wykonywane są funkcje obsługi dla wszystkich nieobsłużonych przerwań. Linux nie może naprawdę zablokować przerwań, zatem nie ma wpływu na czas ich obsługi.
Makro S_STI:
S_STI: sti pushfl pushl $KERNEL_CS pushl $done_STI S_IRET done_STI:Makro S_STI modyfikuje stos, tak by imitował stan w czasie obsługi przerwania: odkłada na stos flagi (instrukcja pushfl), segment kodu jądra i adres następnej instrukcji do wykonania (done_STI). Następnie używa makra S_IRET do emulacji powrotu z przerwania. To działa, bo makro S_IRET odblokowuje przerwania soft.
Najciekawsze jest makro S_IRET.
S_IRET: push %ds pushl %eax pushl %edx movl $KERNEL_DS ,%edx mov %dx,%ds cli movl SFREQ, %edx andl SFMASK, %edx 1: bsfl %edx, %eax 2: jz not_found S_CLI sti jmp SFIDT(,%eax,4) not_found: movl $1, SFIF sti popl %edx popl %eax pop %ds iretNajpierw zachowuje niektóre rejestry na stosie, następnie modyfikuje rejestr ds (data segment) tak, by wskazywał na segment jądra (chcemy mieć dostęp do zmiennych globalnych). Następnie maska bitowa reprezentująca nieobsłużone przerwania jest skanowana w poszukiwaniu ustawionych bitów:
Dlaczego tutaj pojawiają się instrukcje cli i sti?
Sprawdzanie, czy są nieobsłużone przerwania i podjęcie decyzji, co zrobić
muszą być wykonane atomowo (kod między cli i sti. W przeciwnym
wypadku,
gdyby między instrukcją 1 i 2 pojawiło się przerwanie, przerwania
soft są zablokowane i w instrukcji 1 nie znaleziono nieobsłużonych
przerwań, wywołanie funkcji obsługi
nowego przerwania musiałoby poczekać aż do następnego wykonania S_STI
lub S_IRET. Emulator zmieniłby stan zmiennych (przyszło nowe
przerwanie), ale makro S_IRET już by tego nie sprawdziło.
Warto jeszcze uważnie prześledzić, w jaki sposób współpracują ze sobą makra S_STI i S_IRET. Po wywołaniu S_STI kontrola zostanie przekazana do makra S_IRET, które obsłuży wszystkie przerwania. Następnie wykonanie wróci do makra S_STI, do instrukcji done_STI. Dlaczego? S_STI wcześniej odłożyło tę wartość na stosie, iret ją zdejmie i posłuży się nim jako adresem kolejnej instrukcji (instruction pointer).
Poprzednia - Przerwania - Następna