Lekcja 02- Pierwszy moduł

Wstęp

W tej lekcji w końcu zaimplementujemy pierwszy prosty moduł kernelowy. Nie będzie on robił nic szczególnego, jego jedyną funkcją będzie wypisanie informacji w logu systemowym, że został on załadowany lub usunięty z systemu.

Jak programować w kernelu?

Pierwszą różnicą, która wrzuci Ci się w oczy patrząc na kod kernelowych modułów to brak funkcji main. Do implementacji modułów należy podejść jak do implementacji programu z użyciem jakiegoś frameworka. W przypadku modułów linuksowych musimy implementować funkcje, które są wywoływane w odpowiednim momencie np. podczas załadowania lub usunięcia modułu, odczytu danych lub wykrycia urządzenia.

Drugą różnicą jest brak dostępu do biblioteki standardowej, kod kernela dostarcza odpowiednie funkcje np. do logowania czy alokacji pamięci, ale nie są to te same funkcje co w przypadku biblioteki standardowej języka C.

Po trzecie musisz zwrócić uwagę na dostępy do pamięci. Gdy piszesz standardowy program to system operacyjny czuwa nad tym abyś nie napsuł czegoś poprzez dostęp do pamięci do której nie powinieneś mieć dostępu. W przypadku implementacji takich operacji w kernelu nikt Cię nie powstrzyma ponieważ system operacyjny nad tobą wtedy nie czuwa ponieważ implementujesz właśnie część systemu operacyjnego.

Po czwarte, odradza się używanie typu float w kernelu, wynika to z faktu, że tryb FPU jest wyłączony domyślnie w kernelu. Oczywiście użycie floata nie spowoduje wybuchu twojego komputera, ale może to negatywnie wpłynąć na obliczenia przeprowadzane przez programy użytkownika.

Kernel space i user space

Oba powyższe terminy odnoszą się do przestrzeni adresowych. Jak łatwo się domyślić kernel space to przestrzeń adresowa kernela, a user space to przestrzeń adresowa użytkownika. Przez taki podział dane dostępne w kernelu nie są łatwo dostępne przez programy i odwrotnie. Wiedza ta zostanie użyta w praktyce w kolejnych lekcjach.

Implementacja

Teraz przyszła pora na to co tygryski lubią najbardziej czyli na implementacje. Pierwszy kod jest bardzo krótki zatem zostanie on zaprezentowany poniżej w całości, a następnie omówiony:

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

static int __init hello_init(void) {
	pr_info("Hello world!\n");
	return 0;
}
static void __exit hello_exit(void) {
	pr_info("End of the world\n");
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_AUTHOR("Adam Olek");
MODULE_LICENSE("GPL");

Jak widać kod jest bardzo krótki, składa się z kilku nagłówków, dwóch funkcji oraz kilku makr.

hello_init to funkcja, która zostanie wywołana podczas ładowania modułu, hello_exit natomiast zostanie wywołane gdy moduł zostanie usunięty z systemu.
Obie funkcje wywołują pr_info. W gruncie rzeczy pr_info nie jest funkcją tylko jest makrem opakowującym funkcję printk, która to jest kernelowym odpowiednikiem funkcji printf. Użytkowo główna różnica pomiędzy printk oraz printf polega na tym, że używając printk oprócz danych do wyświetlenia przekazujemy również priorytet loga. Dla prostoty użycia zostały zdefiniowane makra takie jak pr_info, pr_err czy pr_debug, które elegancko opakowują funkcję printk i nie musimy przekazywać priorytetu loga.

Dodatkowo w oczy mogły wrzucić się atrybutu funkcji __init oraz __exit. W gruncie rzeczy nie mają one wpływu na moduły, które są ładowane podczas pracy systemu, mają one wpływ na kod, który jest wkompilowany w kernel. __init w przypadku kodu wkompilowanego w kernel informuje, żeby dana funkcja została usunięta z pamięci po jej wykonaniu ponieważ nie będzie ona później wykorzystywana i nie ma sensu aby zajmowała miejsce. Atrybut __exit informuje kernel żeby w przypadku kodu wkompilowanego w kernel nie był on używany i nie był brany do wynikowego pliku binarnego ponieważ nie zostanie on nigdy wykonany.

Następnie mamy dwa makra, module_init i module_exit, służą one do określenia funkcji init oraz exit.

Następnie mamy makro, które określa autora kodu- MODULE_AUTHOR.

Makro MODULE_LICENSE określa licencję pod jaką dany moduł został napisany. Na pozór to może się wydawać, że to makro ma jedynie funkcje informacyjną nie ma żadnego wpływu na działanie kodu. Nic bardziej mylnego, jeśli użyjemy licencji, która nie jest kompatybilna z licencją GPL nie będziemy mogli używać funkcji kernela, które zostały stworzone pod tą właśnie licencją.

Kompilacja

Aby skompilować moduł musimy utworzyć plik Makefile o następującej zawartości:

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

Parametrem -C musimy wskazać kod Linuksa, dla którego kompilujemy moduł.

Teraz możemy przebudować moduł za pomocą komendy make, musimy jednak wskazać docelową architekturę i używany zestaw kompilatorów. Dla naszych płytek komendy te wyglądają następująco:

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

Zostanie wygenerowanych kilka plików, nas najbardziej interesuje plik o rozszerzeniu ko czyli nasz moduł.

Testowanie modułu

Teraz przetestujemy nasz moduł. Uruchom swoją płytkę i prześlij na nią zbudowany plik 02_hello.ko(chyba, że nazwałeś plik inaczej) np. za pomocą komendy scp:

scp user@hostip:~/ścieżka/do/02_hello.ko .

Teraz możesz załadować swój moduł:

sudo insmod 02_hello.ko

Jeśli na konsoli nie pojawiła żadna informacja o błędzie to wszystko zadziałało. Jeśli chcesz zobaczyć log z funkcji init użyj komendy dmesg. Możesz też dodatkowo sprawdzić listę załadowanych modułów za pomocą komendy lsmod.

Aby sprawdzić czy usunięci modułu działa wykonaj komendę:

sudo rmmod 02_hello

Ponownie użyj komendy dmesg aby sprawdzić log z funkcji exit.

Im system jest dłużej uruchomiony tym więcej logów znajduje się w logu systemowym, aby wypisać kilka ostatnich logów użyj komendy tail. Np. aby wypisać 10 ostatnich logów użyjemy komendy:

dmesg | tail -n 10
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 *