Lekcja 05- Timery

Wstęp

W tej lekcji poznamy metody pracy z linuksowymi timerami czyli funkcjonalnością, która umożliwia nam odwleczenie wykonania danej funkcji w czasie. Aby zilustrować to zagadnienie zmodyfikujemy kod naszego sterownika z poprzedniej lekcji. Tym razem dioda będzie migać, a nie tylko świecić ciągłym światłem.

Jiffies

Jiffies to nie nazwa żadnego zespołu z ubiegłego wieku, jest to zmienna przechowująca czas, który upłynął od uruchomienia systemu. Zmienna ta jest aktualizowana z częstotliwością, która jest określona poprzez opcję HZ w kernelu, opcja ta jest ustawiania podczas kompilacji kernela. Typowo wynosi ona 1000 dla architektur x86/x86_64 lub 250 dla ARM/ARM64. Liczba oznacza jak często zmienna jiffies jest aktualizowana w ciągu sekundy czyli jest to dosłownie częstotliwość, zapewne stąd nazwa tej opcji- HZ. Możesz sprawdzić ile wynosi u Ciebie ta opcja w ustawieniach kernela, wystarczy, że wejdziesz do swoich źródeł kernela, uruchomisz menuconfig i wyszukasz HZ. Zarówno dla BBB jaki i RPi4 domyślnie powinna ona wynosić 250.

Traktuj jiffiesy jako podstawową jednostkę czasu w kernelu, jednostki takie jak np. milisekundy uzyskujemy poprzez przeliczenie ilości jiffiesów na daną jednostkę. Na szczęście istnieją do tego gotowe funkcje i nie musimy przeliczać wszystkiego ręcznie.

Implementacja

Jak zostało wspomniane we wstępie, aby zobrazować działanie timerów zmodyfikujemy sterownik z poprzedniej lekcji, dodamy do niego taki właśnie timer, który będzie sobie migał diodą. Taki sterownik wygląda następująco:

#include <linux/gpio.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/timer.h>

#define LED // 48 dla BBB lub 4 dla RPi4, wybierz odpowiedni pin
#define TIMEOUT 1000

static int led_state = 0;
static struct timer_list led_timer;

void led_timer_callback(struct timer_list *data)
{
	led_state ^= 1;
	gpio_set_value(LED, led_state);
	mod_timer(&led_timer, jiffies + msecs_to_jiffies(TIMEOUT));
}

static int __init led_init(void)
{
	if(!gpio_is_valid(LED)) {
		pr_err("Invalid GPIO pin!!1\n");
		return -ENODEV;
	}
	gpio_request(LED, "sysfs");
	gpio_direction_output(LED, led_state);
	gpio_export(LED, false);
	timer_setup(&led_timer, led_timer_callback, 0);
	mod_timer(&led_timer, jiffies + msecs_to_jiffies(TIMEOUT));
	return 0;
}
static void __exit led_exit(void)
{
	del_timer(&led_timer);
	gpio_set_value(LED, 0);
	gpio_unexport(LED);
	gpio_free(LED);
}

module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");

Jak widać niewiele kodu doszło względem kodu z poprzedniej lekcji. Pominiemy omówienie kodu związanego z GPIO, skupmy się tylko na ustawieniu timera. W funkcji init inicjalizujemy nasz timer za pomocą funkcji timer_setup. Jej parametrami są nasz timer, w naszym przypadku struktura timer_list jest zmienną globalną dla tego modułu. Drugim parametrem jest funkcja, która ma być wywołana przez timer, w naszym przypadku jest to funkcja led_timer_callback. Zwróć uwagę, że nie możesz tak przekazać jakiejkolwiek funkcji, musi ona zwracać typ void(czyli w zasadzie nic nie zwracać) oraz musi przyjmować jeden parametr w postaci wskaźnika na strukturę timer_list. Trzeci parametr timer_setup to flagi, które mogą wpływać na specyficzne działanie timerów, ich opis znajdziesz w źródłach kernela w pliku include/linux/timer.h.

Gdy już zainicjalizujemy timer to go ustawiamy za pomocą funkcji mod_timer, która przyjmuje dwa parametry. Pierwszy to oczywiście nasz timer, a drugi to czas w jakim ma się wykonać. Zwróć uwagę, że ustawiamy czas od „teraz” dlatego dodajemy jakąś wartość do obecnej wartości zmiennej jiffies. My chcemy odwlec wykonanie się naszej funkcji o 1 sekundę. Nie możemy jednak przekazać jak człowiek wartości 1 sekunda, musimy tę jedną sekundę przeliczyć na jiffiesy dlatego też używamy funkcji msecs_to_jiffies, która przelicza ilość milisekund na ilość jiffiesów. My chcemy odwlec wykonanie o jedną sekundę więc musimy przekazać wartość 1000 milisekund. I to tyle co musimy zrobić w funkcji init.

Teraz może zajmijmy się funkcją led_timer_callback czyli funkcją, którą chcemy wywoływać okresowo. Jej kod jest bardzo prosty- najpierw zmieniamy aktualny stan diody, a potem ten stan zapisujemy na pin GPIO. Ważna jest ostatnia linijka funkcji. Jeśli chcemy aby funkcja wywoływała się okresowo musimy timer ustawiać na końcu tej funkcji w innym przypadku funkcja zostanie wywołana tylko raz.

W funkcji exit dochodzi nam jedna linijka- wywołanie funkcji del_timer, która to usuwa nasz timer z systemu.

Makefile wygląda standardowo:

obj-m += 05_timer.o
all:
	make -C /ścieżka/do/zbudowanego/kernela M=$(PWD) modules
clean:
	make -C /ścieżka/do/zbudowanego/kernela M=$(PWD) clean

Przebuduj moduł komendą odpowiednią dla swojej płytki:

# BBB
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
# RPi4
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- KERNEL=kernel8

Testowanie sterownika

Przede wszystkim dioda musi być podłączona do płytki tak samo jak w poprzedniej lekcji. Gdy już wszystko jest podłączone prześlij nasz nowy sterownik na swoją płytkę np. za pomocą scp i załaduj moduł:

sudo insmod 05_timer.ko

Dioda powinna zacząć migać z częstotliwością 1hz czyli co jedną sekundę. Teraz odładuj moduł:

sudo rmmod 05_timer

Dioda powinna przestać migać.

Inne funkcje związane z czasem

W kernelu możesz się jeszcze spotkać z funkcjami, które służą do wprowadzenia opóźnienia pomiędzy kolejnymi operacjami, są to tzw. sleepy. Funkcje tego typu to nsleep, usleep i msleep. Funkcje te służą odpowiednio do wprowadzenia opóźnia wyrażonego w nanosekundach, mikrosekundach i milisekundach. Oczywiście dokładność tych funkcji może być różna na różnych systemach, będzie to zależało jak dokładny pomiar czasu oferuje dane urządzenie.

Dodatkowo jeśli dokładność czasowa standardowego kernelowego timera to za mało to można się przyjrzeć zegarom o wysokiej rozdzielczości lepiej znanych jako high resolution timers lub w skrócie hrtimers. Ich użycie jest zbliżone do użycia zwykłego kernelowego timer, w zasadzie to sposób użycia jest analogiczny, główną różnicą z punktu widzenia użytkownika API są inne nazwy funkcji.

Ten wpis został opublikowany w kategorii Kurs pisania sterowników. Dodaj zakładkę do bezpośredniego odnośnika.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *