Linux Kernel II Hello kernel - jak napisać pierwszy moduł
Przypomnienie (I) Moduły uruchamiane są i działają w przestrzeni Kernela (Kernel space), Moduły piszemy w języku C, Moduły działają inaczej niż aplikacje: służą rozszerzaniu podstawowych funkcjonalności jądra systemu Linux, Popełnianie błędów programistycznych może owocować poważniejszymi problemami. page 2
Przypomnienie (II) Przykładowy i uproszczony schemat architektury aplikacji: Hardware Driver Kernel Syscall Library App page 3
Wywołania systemowe (syscalls) Podstawowe instrukcje systemu Linux dostępne z poziomu użytkownika, Zwykle obudowane bibliotekami systemowymi, nie są wywoływane wprost, Każda funkcja standardowych bibliotek języka C może być zastąpiona wywołaniem, bądź serią wywołań systemowych, Przykładowo: int printf(const char *format,...); [std C lib function] ssize_t write(int fd, const void *buf, size_t count); [syscall] page 4
Czy moduły możemy pisać w C++? (pytanie z poprzedniego wykładu) Najprostsza odpowiedź: NIE! Dlaczego? Bo... Kernel nie zapewnia wzparcia dla C++, Bo... nie ma innych sterowników w C++, Bo C jest wystarczające, aby napisać każdy sterownik. Trudniejsza odpowiedź: TAK! Zawsze możemy przepisać Kernel w języku C++ ;) Więcej informacji: http://www.tux.org/lkml/#s15-3 page 5
Cykl życia modułu Wczytaj moduł (Insert) Wywołaj funkcję inicjującą (Init function) komenda insmod Komenda rmmod Czekaj na polecenie (request) Kolejka poleceń (request queue) Remove module Wywołaj funkcję czyszczącą (Cleanup) page 6
Pierwszy moduł: przygotowanie (I) Czego potrzebujemy? Kompilatora GCC, który musi być odowiedni dla posiadanej wersji Linuxa, zwykle jest on dostarczany wraz z toolchainem, Skompilowanego, bądź odpowiednio przygotowanego kodu źródłowego systemu Linux, Konfiguracji Kernela (plik.config), Listy symboli używanych w Kernelu (plik Module.symvers). page 7
Pierwszy moduł: przygotowanie (II) Jeśli posiadamy skompilowane źródła systemu Linux w odpowiedniej wersji, to właśnie zaoszczędziliśmy sobie nieco pracy, Jeśli posiadamy prekompilowany system Linux (np. Arch Linux, Ubuntu czy Raspbian), to: Używamy przeglądarki Google, aby wyszukać instrukcję odtworzenia źródeł systemu Linux, Wszystkie instrukcje są do siebie podobne i sprowadzają się do pozyskania plików.config oraz Module.symvers, Większość popularnych systemów posiada łatwo dostępną instrukcję. page 8
Przykład: przygotowanie systemu Raspbian (III) Przykładowa instrukcja: https://github.com/notro/rpi-source/wiki Powyższa instrukcja w skrócie: Aktualizujemy kompilator GCC, jeśli jest taka potrzeba, Pobieramy skrypt i uruchamiamy go, a wtedy: Skrypt aktualizuje firmware RPI (także Kernel), pobiera źródła systemu Linux w najnowszej wersji do katalogu domowego, Odzyskuje konfigurację systemu Linux z /proc/config.gz, bądź generuje domyślną: make bcmrpi_defconfig, Pobiera plik Module.symvers (!), Wykonuje make modules_prepare. page 9
Plik.config W pliku.config przechowywane są informacje o obecnej konfiguracji systemu Linux, Aby skonfigurować Kernel użyj: make <configuration> [ls -l arch/arm/configs] make mrproper make menuconfig make oldconfig page 10
Plik Module.symvers przechowuje dane o eksportowanych przez Kernel symboli, Symbole to funkcje widoczne dla innych modułów, Budowa pliku: 0x5d9a4bf2 CRC, ipv6_chk_custom_prefix nazwa funkcji (symbol) net/ipv6/ipv6 moduł w którym symbol jest umiejscowiony. page 11
#include <linux/init.h> #include <linux/module.h> #include <linux/sched.h> MODULE_LICENSE("Dual BSD/GPL"); static int init hello_init(void) { printk(kern_alert "Hello kernel with process=%s (PID=%i, parent= %s)\n", current->comm, current->pid, current->parent->comm); return 0; } static void exit hello_exit(void) { printk(kern_alert "Goodbye kernel world.\n"); } module_init(hello_init); module_exit(hello_exit); Moduł Hello Kernel page 12
Pliki nagłówkowe Linux/module.h Plik potrzebny wszystkim dynamicznie ładowanym modułom, Zawiera wiele definicji między innymi makra MODULE_LICENSE, Linux/init.h Plik nagłówkowy zawierający definicje potrzebne to inicjalizacji modułów, m.in. makra module_init, module_exit, init, exit, Linux/sched.h Plik nagłówkowy zawierający m.in. definicje dostarczające dane o obecnym procesie, Current comm, current parent, current pid. page 13
Makro MODULE_LICENSE Możliwe licencje znaleźć można w pliku nagłówkowym linux/module.h, Dostępne licencje: GPL, GPL v2, Dual BSD/GPL, Dual MPL/GPL, Proprietary i inne..., Niektórych funkcji można używać jedynie w modułach oznaczonych konkretnymi licencjami, Moduły nie zawierające definicji licencji traktowane są jako proprietary, Kiedy moduł oznaczony jest jako proprietary, Kernel staje się skażony (ang. tainted ), Skażone Kernele nie są wspierane przez brać linuxową... page 14
Funkcja init static int init hello_init(void) { printk(kern_alert "Hello kernel with process=%s (PID=%i, parent= %s)\n", current->comm, current->pid, current->parent->comm); return 0; } Hello_init: funkcja statyczna, zwracająca wartość decymalną (integer), nie przyjmująca argumentów, init: znacznik mówiący Kernelowi, że funkcja jest używana tylko podczas inicjalizacji modułu (potem jest usuwana z pamięci), Printk: funkcja pisząca, podobna do funkcji printf w przestrzeni użytkownika (userspace), KERN_ALERT: poziom logowania, jest to zwykły string, current : struktura zawierająca informacje o obecnym procesie. page 15
Funkcja exit static void exit hello_exit(void) { printk(kern_alert "Goodbye kernel world.\n"); } Hello_exit: funkcja statyczna, nie zwracająca oraz nie przyjmująca wartości, exit: znacznik mówiący Kernelowi, że funkcja jest używana jedynie podczas kończenia pracy modułu, Jeśli funkcja oznaczona jako exit zostanie użyta w innym kontakście, zwrócony zostanie błąd. page 16
module_init(hello_init); module_exit(hello_exit); Makra module_init i module_exit Nazwy funkcji użyte jako argumenty makr module_init i module_exit są uruchamiane odpowiednio: W czasie inicjalizacji pracy modułu użyta zostanie funkcja hello_init, W czasie kończenia pracy modułu użyta zostanie funkcja hello_exit, Przykładowy wynik końcowy: page 17
KERN_VER := $(shell uname -r) PWD := $(shell pwd) KERNELDIR?= /lib/modules/$(kern_ver)/build obj-m := hello.o all: clean compile Kompilacja modułu: plik makefile compile: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules clean: $(MAKE) -C $(KERNELDIR) M=$(PWD) clean page 18
Dlaczego pliki makefile modułów Kernela wyglądają inaczej niż tradycyjne? Ponieważ plik makefile kompilowanego modułu staje się częścią procesu kompilowania całego Kernela, Ta linijka jest odpowiedzialna za dołączenie świeżo napisanego modułu do procesu kompilacji Kernela: obj-m := hello.o Kompilacja modułu: wyjaśnienie Za wywołanie procesu kompilacji odpowiada komenda $(MAKE): $(MAKE) -C $(KERNELDIR) M=$(PWD) modules page 19
Kompilacja modułu: informacje dodatkowe (I) Moduły możemy budować na dwa sposoby: Jako wbudowane (build-in), ładowane automatycznie podczas startu systeu Linux, Jako zewnętrzne (loadable), ładowane przed użytkownika za pomocą komendy insmod bądź modprobe, Aby zbudować moduł jako wbudowany należy w pliku.config : Oznaczyć moduł jako y, np. CONFIG_HELLO_WORLD=y Aby zbudować moduł jako zewnętrzny należy w pliku.config : Oznaczyć moduł jako m, np. CONFIG_HELLO_WORLD=m Dlatego też, jeśli w pliku makefile napiszemy: Obj-y := hello.o, nasz moduł będzie wbudowany, Obj-m := hello.o, nasz moduł będzie zewnętrzyny. page 20
Kompilacja modułu: informacje dodatkowe (II) Budowanie modułu złożonego z plików hello1.c and hello2.c: obj-m := hello.o hello-objs := hello1.o hello2.o Nietypowa komenda $(MAKE) : $(MAKE) -C $(KERNELDIR) M=$(PWD) modules Odpowiada ona za wywołanie dodatkowego procesu budowania dziecka w obrębie nadrzędnego polecenia make. page 21
Dynamiczne uruchamianie i kończenie pracy modułów insmod aby wczytać I uruchomić moduł, rmmod aby zakończyć pracę modułu, lsmod aby wyświetlić załadowane moduły, Polecenie: dmesg tail wyświetli wszystkie polecenia printk użyte w module. page 22
Kernel symbol table Kernel Symbol Table to po prostu Tablica Symboli Kernela, Tablica symboli odnosi się bezpośrednio to wspomnianego wczesniej pliku Module.symvers, Co należy zrobić, aby napisaną przez nas funkcję dodać do tablicy symboli Kernela? Należy wyeksportować ją za pomocą makra: EXPORT_SYMBOL(), Po użyciu makra EXPORT_SYMBOL Kernel umieści naszą funkcję w globalnej tablicy symboli i stanie się ona widoczna dla wszystkich modułów w Kernelu. page 23
Potencjalne problemy i pozbywanie się ich (I) Kiedy zobaczymy taki błąd: Error inserting './hello.ko': -1 Invalid module format Znaczy to, że coś poszło nie tak... Aby przyjrzeć się bliżej problemowi wpisujemy: cat /var/log/messages, bądź dmesg i sprawdzamy ostatnie wpisy, Najczęstsze błędy: 1. hello: version magic '<this>' should be '<that>' Zła wersja kernela: należy poszukać DOKŁADNIE takiego samego Kernela, bądź pogrzebać w /linux/module.h (czasem trudne). page 24
Potencjalne problemy i pozbywanie się ich (II) 2. hello: no symbol version for module_layout Źródła systemu Linux nie są kompletne. Brakuje w nich pliku Module.symvers : należy pobrać plik od twórcy, bądź skompilować źródła systemu Linux, które posiadamy. 3. W przypadku każdego problemu najprostszym, rozwiązaniem jest przeszukanie internetu. Kernel posiada znakomitą dokumentację! :) page 25
Aby skompilować swój moduł potrzebujesz odpowiednio przygotowane źródła systemu Linux: konfiguracja oraz stworzenie tablicy symboli są niezbędne! Moduły możemy budować jako integralną część Linuxa (build-in) bądź jako zewnętrzne pliki ładowane dynamicznie (loadable), Proces budowania modułu jest częścią systemu budowania CAŁEGO systemu Linux, Pliki nagłówkowe systemu Linux dostarczają wielu ciekawych informacji, Częste sięganie do dokumentacji systemu Linux może zaoszczędzić wielu nerwów, Programowanie w systemie Linux wymaga wprawy i szczególnie na początku, może sprawiać problem. Podsumowanie page 26
Linux Kernel Wprowadzenie Koniec