Poprzednia - Następna

Przerwania

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 
		iret 
Najpierw 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: Jeśli nie znaleziono nieobsłużonych przerwań, ustawiana jest zmienna SFIF (odblokowujemy przerwania) i wykonywany jest ręcznie powrót z przerwania. Jeśli znaleziono nieobsłużone przerwania, wykonywany jest skok do funkcji obsługi przerwania. Funkcja ta zapisana w tablicy funkcji obsługi przerwań soft (soft interrupt descriptor table, zmienna SFIDF). Gdy funkcja się zakończy, wykona S_IRET, które sprawdzi, czy nie ma nieobsłużonych przerwań. W ten sposób wszystkie nieobsłużone przerwania zostaną obsłużone.

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