Linux a czas rzeczywisty
Wprowadzenie do systemów RT:

RTLinux:

KURT Linux:

Slajdy:

UTIME

UTIME wprowadza kilka zasadniczych zmian do Linuksa, aby umożliwić wykonywanie aplikacji real-time. Przede wszystkim musi zapewnić możliwość generowania przerwań zegarowych częściej niż standardowo w Linuksie, gdyż wiele aplikacji rt wymaga dokładniejszego śledzenia czasu. W Linuksie czas pomiędzy kolejnymi przerwaniami zegarowymi wynosi 10 milisekund. UTIME zapewnia możliwość generowania przerwań nawet co 1 mikrosekundę.

W Linuksie proces, który potrzebuje, aby dana funkcja została wykonana w danym momencie tworzy obiekt struktury timer_list (plik include/linux/timer.h) i dodaje go do listy timerów.

struct timer_list {
	struct list_head list;
	unsigned long expires;
	unsigned long data;
	void (*function)(unsigned long);
};

Pole expires określa czas, po którym funkcja function ma zostać wywołana. Pole data to argumenty przekazywane funkcji.

Zegar systemowy jest niczym innym jak licznikiem kolejnych tyknięć zegara. Po każdym tyknięciu system uaktualnia licznik jiffies, następnie sprawdza listę timerów czy należy wywołać funkcję jakiegoś procesu. Jeśli tak, czyli jeśli expires jest mniejsze od wartości licznika jiffies, system wywołuje funkcję function i usuwa timer z listy timerów. Widać zatem, że niemożliwe jest obsługiwanie takich procesów, które wymagają częstszych niż czas pomiędzy dwoma tyknięciami zegara interwencji lub po prostu dokładniejszego liczenia czasu.

Jak zmienia to UTIME?

Narzucającym się początkowo rozwiązaniem byłoby takie zaprogramowanie zegara systemowego, aby częściej zgłaszał przerwania do procesora, wtedy czas pomiędzy kolejnymi tyknięciami zmniejszyłby się. Oznaczałoby to jednak nadmierne wykorzystywanie i zegara i procesora, przerwania zgłaszane byłyby bardzo często niezależnie od tego, czy jakieś procesy potrzebują aktualnie aż tak dokładnego mierzenia czasu.

Zauważmy jednak, że rzadko proces wymaga przerwań następujących w każdej mikrosekundzie. Nie potrzeba więc sprawdzać wszystkiego w każdej mikrosekundzie, wystarczy możliwość generowania przerwań w każdej mikrosekundzie.

Jak więc dokładnie wygląda rozwiązanie? Do struktury timer dodano nowe pole usec. Oznacza ono dokładną mikrosekundę w danym "jiffy" kiedy ma zostać wykonana funkcja (a więc czas od początku liczenia czasu do wywołania funkcji w mikrosekundach wynosi 1000 * expires + usec). Dodano również dwie zmienne globalne - jiffies_u (czas w mikrosekundach w aktualnym jiffie) oraz jiffies_intr (czy podczas aktualnie obsługiwanego przerwania nastąpiła aktualizacja licznika jiffies - na tej podstawie wykonywane lub nie wykonywane są te operacje, które Linux standardowo wykonuje podczas obsługi przerwania zegarowego). Dodano również funkcję update_time, wywoływaną przez funkcję obsługi przerwania zegara systemowego. Funkcja ta na podstawie czasu poprzedniego wywołania i time stamp counter-a (TSC) uaktualnia zmienne jiffies, jiffies_u i jiffies_intr. Następnie funkcja obsługi przerwania liczy czas do następnego przerwania, programuje zegar systemowy, aby zgłosił przerwanie właśnie w tym czasie i wywołuje funkcje tych procesów, których czas właśnie się skończył.

Jest jednak pewien problem - niektóre podsystemy jądra zakładają, że licznik jiffies będzie regularnie uaktualniany, a tutaj może zdarzyć się tak, że w momencie, gdy licznik powinien być zwiększony, nie kończy się czas żadnego procesu. Aby wymusić regularne zwiększanie licznika, system podczas programowania czasu następnego przerwania sprawdza czy ma to nastąpić po spodziewanej kolejnej aktualizacji licznika. Jeśli tak, czas następnego przerwania ustawiany jest na moment potrzebnej aktualizacji, mimo że nie nastąpi wtedy żadne budzenie procesu.

Pseudo-kod funkcji obsługi przerwania:

void timer_interrupt()
{
	update_time();
	t = time_to_next_event();
	program_timer_chip(t);
	run_timer_list();
	if (jiffies_intr)
	{
		do_other_stuff();
		jiffies_intr = 0;
	}
}

Pseudo-kod funkcji update_time():

void update_time()
{
	static last_update = 0;
	new_update = get_tsc();

	jiffies_u += (last_update - new_update) /
			tsc_cycles_per_jiffy;;
	last_update = new_update;

	if (jiffies_u > USEC_PER_JIFFIES)
	{
		jiffies_intr++;
		jiffies_u -= USEC_PER_JIFFIES;
		jiffies++;
	}
}