Systemy Operacyjne – semestr drugi Wyk ad pierwszy ł

Komentarze

Transkrypt

Systemy Operacyjne – semestr drugi Wyk ad pierwszy ł
Systemy Operacyjne – semestr drugi
Wykład pierwszy
„Rodowód” Linuksa
Inspiracją dla autora systemu Linux był system Minix napisany przez Andrew Tanenbauma, który bazował z kolei na
systemie Unix. Termin Unix w obecnych czasach oznacza całą rodzinę systemów operacyjnych opartych na wspólnej
filozofii, której źródłem jest projekt systemu operacyjnego, jaki powstał w roku 1969 w Bell Labs, będących częścią
amerykańskiego koncernu AT&T. System ten jest pochodną innego systemu operacyjnego Multics, nad którego
powstaniem pracowano w trzech ośrodkach: w MIT, w AT&T i w General Electric. Firma AT&T po pewnym czasie
wycofała się z prac, ale osoby zatrudnione przy tym projekcie pozostały jej pracownikami. Do ich grona należeli między
innymi Ken Thompson, Dennis Ritchie i Douglas McIlroy. To ta trójka odegrała największą rolę w powstaniu pierwszej
wersji systemu Unix. Pracę nad nią rozpoczęli Ken Thompson i Dennis Ritchie, pisząc cały kod w assemblerze
komputera PDP-7, który poznali tworząc wcześniej grę „Space Travel” dla tej platformy sprzętowej. Znaczący wkład do
projektu systemu wniósł Douglas McIlroy, opracowywując np.: łącza nienazwane, które są jednym z środków
zapewniających komunikację między procesami, a także wyznaczając reguły tworzenia kodu, które dziś są częścią
inżynierii oprogramowania. W roku 1973 kod źródłowy Uniksa został w większości przepisany w języku C, który
opracował Dennis Ritchie1, dzięki czemu system ten stał się łatwy w przenoszeniu na inne platformy sprzętowe2. Firma
AT&T podjęła decyzję o rozpoczęciu sprzedaży systemu Unix. Każda jego wersja była rozprowadzana razem z kodem
źródłowym, a więc wiele firm i innych instytucji, które nabyło ten system, mogło go modyfikować wprowadzając nowe
funkcje. Pod względem wprowadzonych do tego systemu innowacji wyróżniał się Uniwersytet Berkeley. Wszystkie
wersje systemu Unix, które powstały w tej placówce były oznaczane nazwą BSD (Berkeley System Distribution).
Największy wkład do prac nad Uniksem BSD wniósł Bill Joy. To w Berkeley powstał słynny edytor vi, powłoka csh,
a także tu dodano do jądra podsystemy odpowiedzialne za obsługę protokołu TCP/IP i pamięć wirtualną. Z czasem te
funkcje zostały włączone do dystrybucji rozpowszechnianej przez AT&T (wersja System III). W chwili obecnej rozwój
gałęzi BSD jest kontynuowany przez takie projekty, jak FreeBSD, OpenBSD, NetBSD i DragonFly BSD. Aby
uporządkować rozwój Uniksa opracowano kilka standardów, z których najważniejszymi są POSIX oraz SUS. Większość
odmian Uniksa spełnia wymagania tych standardów.
W chwili obecnej niemalże wszystkie odmiany Uniksa to systemy wielodostępne i wielozadaniowe, obsługujące
wszystkie rozwiązania w nowoczesnych platformach sprzętowych. Jest jednakże kilka cech, które odróżniają go od
innych systemów operacyjnych. Przede wszystkim jest on systemem o stosunkowo prostej budowie (jego twórcy
stosowali zasadę Keep It Small and Simple – KISS3). Jądro Uniksa implementuje niewielką liczbę wywołań
systemowych, podczas gdy w innych systemach ta liczba dochodzi do dziesiątek tysięcy. Wywołania systemowe
w Uniksie zostały opracowane w myśl zasady sformułowanej przez Douglasa McIlroy'a: „Rób jedną rzecz, ale rób ją
dobrze”, co oznacza, że wykonują jedną czynność, ale w sposób uniwersalny. Większość elementów w systemie Unix
jest traktowana jako plik, co znacznie ułatwia zarządzanie urządzeniami i danymi. W końcu Unix ma mechanizmy,
które pozwalają na tworzenie w krótkim czasie nowych procesów, oraz dostarcza środków prostej komunikacji między
nimi4. Unix był także jednym z pierwszych systemów operacyjnych, które zostały napisane w języku wysokiego
poziomu, co uczyniło go systemem przenośnym.
Charakterystyka Linuksa
Linux5 jest systemem uniksopodobnym (ang. Unix-like), ale nie jest Uniksem. Pracę nad jądrem tego systemu
rozpoczął Linus Benedict Torvalds w roku 1991, wzorując się na wspomnianym systemie Minix. Nie oznacza to jednak,
że kod przez niego napisany zawiera fragmenty kodu Miniksa lub oryginalnego Uniksa. Linux powstał „od zera”, ale
jest zgodny ze standardami POSIX i SUS. Pierwsza wersja kodu źródłowego Linuksa miała ponad 10 tysięcy wierszy
kodu. Bieżące wersje liczą około 14 milionów linii kodu i są tworzone przez obszerną grupę programistów,
współpracujących ze sobą przez Internet. Prace tej grupy koordynuje oczywiście Linus Torvalds. Kod źródłowy Linuksa
dostępny jest na zasadach licencji GNU GPL v2.0, która gwarantuje, że jest on wolnodostępny. Należy zauważyć, że
słowo Linux ma dwa znaczenia. W powyższym tekście oznaczało ono po prostu jądro systemu operacyjnego. W drugim
znaczeniu, oznacza podstawową wersję systemu operacyjnego, która oprócz jądra zawiera również zestaw narzędzi do
kompilacji programów w językach C i assembler, bibliotekę języka C i powłokę systemową. Wszystkie wymienione tu
elementy, oprócz jądra, zostały stworzone przez fundację GNU, założoną przez Richarda Stallmana. Istnieje wiele
odmian systemu Linux, nazywanych dystrybucjami, które oprócz wymienionych tu podstawowych narzędzi zawierają
również inne oprogramowanie np. system X Window, będący implementacją środowiska graficznego użytkownika, oraz
1
2
3
4
5
Pozostałą część pozostawiono w assemblerze.
Dokładniej – łatwiej było go przenieść na inne platformy, niż system napisany w całości w assemblerze.
Właściwie ten skrót rozwijano jako "Keep It Simple, Stupid!". Osobiście wolę wersję łagodniejszą :-)
Ostatnio pojawiają się głosy, że te mechanizmy komunikacji stają się powoli przestarzałe.
W kwestii językowej: piszemy Linux, ale odmieniamy: Linuksa, Linuksowi, itd.
1
Systemy Operacyjne – semestr drugi
szereg innych aplikacji, niezbędnych użytkownikom. W dalszej części wykładu słowo „Linux” będzie oznaczało
wyłącznie jądro systemu operacyjnego. Pierwotnie Linux przeznaczony był tylko na platformę sprzętową i386 (powstał
na komputerze wyposażonym w procesor Intel 80386). Obecnie obsługuje obsługuje całą gamę mniej lub bardziej
popularnych platform sprzętowych, takich jak : AMD64, PowerPC, Ultra Sparc, MIPS. Wspiera również architektury
równoległe (SMP, NUMA).
Zasada działania jądra
Jądro (ang. kernel) systemu (nazywane czasami rdzeniem (ang. core)) jest częścią systemu operacyjnego, która stale
rezyduje w pamięci komputera, nadzoruje pracę sprzętu i aplikacji użytkownika, oraz dostarcza tym ostatnim
określonych usługi. Najczęściej jądro (monolityczne) zawiera takie elementy, jak: procedury obsługi przerwań,
mechanizm szeregowania procesów, mechanizm zarządzania pamięcią i obsługi plików. Jądro pracuje w trybie
uprzywilejowanym, co oznacza, że ma nieograniczony dostęp do wszystkich zasobów systemu komputerowego, w tym
do pamięci operacyjnej. Obszar pamięci (w sensie zakresu adresów) zajmowany przez jądro nazywany jest przestrzenią
adresową jądra. Dosyć często o czynnościach wykonywanych przez jądro mówimy, że zachodzą w przestrzeni jądra.
Procesy użytkownika pracują w trybie procesora, w którym dostęp do zasobów systemu jest ograniczony. Obszary
pamięci (również w sensie zakresu adresów) przyporządkowane aplikacjom użytkownika nazywamy przestrzenią
adresową użytkownika. Analogicznie jak ma to miejsce w przypadku jądra, o czynnościach, które są wykonywane
przez aplikacje mówimy, że są wykonywane w przestrzeni użytkownika. Do komunikacji między procesami
użytkownika, a jądrem służą wywołania systemowe. Zazwyczaj aplikacje użytkownika nie wywołują ich bezpośrednio,
lecz korzystają z funkcji dostępnych w standardowej bibliotece języka C (libc). Niektóre z tych funkcji zawierają sam
kod wywołania funkcji systemowej, czyli są swego rodzaju „opakowaniami” na wywołania systemowe, inne funkcje
wykonują przed zainicjalizowaniem wywołania systemowego dodatkowe czynności, jeszcze inne mogą korzystać
z większej liczby wywołań systemowych. Są również funkcje, które nie korzystają w ogóle z wywołań systemowych.
Jeśli jądro wykonuje za pośrednictwem wywołania systemowego jakąś usługę dla aplikacji użytkownika, to działa
w kontekście procesu, co oznacza, że kod jądra realizujący to wywołanie ma dostęp do informacji o procesie, który
korzysta z tego wywołania. W takiej sytuacji można również powiedzieć, że aplikacja wykonuje wywołanie systemowe
w przestrzeni jądra, choć nie jest to określenie poprawne. Jądro systemu jest oprogramowaniem sterowanym
zdarzeniami. Zdarzenia pochodzące od sprzętu są sygnalizowane za pomocą przerwań. Te przerwania pojawiają się
zazwyczaj asynchronicznie6. Z każdym z nich jest związany pewien numer, na podstawie którego jądro odnajduje
i wykonuje odpowiednią procedurę obsługi tego przerwania. Te procedury są wykonywane w kontekście przerwania, co
oznacza, że nie są związane z żadnym konkretnym procesem. Pozwala to przyspieszyć ich działanie. Jądro ma również
możliwość blokowania wszystkich przerwań lub tylko wybranych przerwań, na czas obsługi jednego z nich.
Reasumując, procesor w systemie komputerowym, który kontroluje Linux, albo wykonuje kod aplikacji, albo kod jądra
w kontekście procesu, albo kod jądra w kontekście przerwania.
Porównanie z innymi systemami uniksowymi
Jądro sytemu Linux jest podobne do rdzeni innych systemów wzorowanych na Uniksie, ale istnieje również kilka
znaczących różnic. Podobnie jak w większości Uniksów, jądro Linuksa jest monolityczne, ale udostępnia możliwość
korzystania z ładowanych dynamicznie modułów, które zazwyczaj są sterownikami urządzeń. Linux obsługuje również
symetryczne przetwarzanie wieloprocesorowe (SMP) oraz architekturę NUMA, co nie jest cechą wszystkich Uniksów.
Podobnie jak Solaris i IRIX, Linux może obsługiwać od wersji 2.6 wywłaszczanie zadań jądra. Jądro Linuksa nie
rozróżnia wątków i procesów, traktuje je niemalże w ten sam sposób. Niektóre mechanizmy, które przez twórców
Uniksa zostały uznane za mało wydajne lub wręcz za zbędne (jak np. obsługa strumieni) nie zostały w ogóle
zaimplementowane w jądrze Linuksa. Ze względów wydajnościowych strony pamięci zajmowanej przez jądra Linuksa
nie podlegają wymianie. Ta ostatnia cecha jest konsekwencją faktu, że Linux jest tworzony przez ogromną społeczność
programistów – w wyniku dyskusji i rozważań członkowie tej społeczność doszli do wniosku, że taki mechanizm
spowodowałby pogorszenie wydajności systemu. Kolejne wersje jądra są oznaczane symbolami składającymi się
z trzech7 liczb rozdzielonych kropkami. Pierwsza liczba to główny numer wersji, druga to numer podwersji, trzecia
oznacza kolejne wydanie, w którym dokonano znaczących zmian lub rozbudowano funkcjonalność jądra. Przed
wydaniem jądra 2.6.0 nieparzysty numer podwersji oznaczał wersję rozwojową, która mogła być niestabilna, a numer
parzysty podwersji, wersję stabilną. Od wersji 3.0 jądra numer podwersji oznacza kolejne wydanie, a trzecia liczba
używana jest do oznaczania wersji, w których usunięto wykryte usterki. Wersje rozwojowe oznaczane są za pomocą
liter rc, za którymi występuje numer wersji rozwojowej. Niektóre dystrybucje Linuksa mogą dodawać do tych oznaczeń
dodatkowe liczby.
6
7
Istnieją również przerwania synchroniczne.
Pojawiła się również czwarta liczba (jądro 2.6.8.1). Jej wprowadzenie związane było z naprawieniem poważnego błędu,
który wykryto tuż po wypuszczeniu wersji 2.6.8. Tę numerację stosowano czasem w późniejszych wersjach.
2
Systemy Operacyjne – semestr drugi
Krótka charakterystyka metodologii programowania jądra
Ponieważ jądro systemu z definicji ma być optymalne pod względem wykorzystania pamięci i szybkości działania, to
przeglądając kod źródłowy Linuksa możemy spotkać wiele fragmentów, które łamią niektóre przyjęte kanony dobrze
napisanego oprogramowania (jak choćby nieużywanie instrukcji goto). Tak, jak w przypadku innych systemów
uniksopodobnych, większość kodu Linuksa jest napisana w języku C, a tylko niewielka część, zależna od konkretnej
architektury sprzętowej w assemblerze8. Pisząc własny fragment jądra, czy to w postaci modułu, czy ingerując
w istniejący kod należy mieć świadomość pewnych ograniczeń. Przede wszystkim, pisząc kod jądra nie mamy dostępu
do funkcji standardowej biblioteki C (libc, a konkretniej glibc). Jest to naturalną konsekwencją tego, że to jądro
stanowi wsparcie dla tej biblioteki, a nie odwrotnie. Na szczęście zostały zaimplementowane w jądrze funkcje, będące
substytutami tych z biblioteki glibc. Zamiast funkcji printf jest funkcja printk, zamiast funkcji do manipulowania
łańcuchami znaków, które są dostępne po włączeniu pliku string.h są podobne funkcje dostępne po włączeniu pliku
linux/string.h. Uogólniając – jądro może korzystać wyłącznie ze swoich plików nagłówkowych, niedozwolone jest
korzystanie z zewnętrznych plików nagłówkowych. Kod jądra jest pisany z wykorzystaniem standardu ISO C99 języka
C, oraz z wykorzystaniem rozszerzeń GNU C. Oznacza to, że może być kompilowany prawie wyłącznie przez kompilator
C pochodzący z GCC (Gnu Compilers Collection). Do wspomnianych rozszerzeń należy wykorzystywanie funkcji
rozwijanych w miejscu wywołania (ang. inline functions), obecnie wprowadzone już do standardu języka C. Takie
funkcje są bardziej bezpieczne i eleganckie niż makra, a przy tym równie skuteczne. Funkcje rozwijane w miejscu
wywołania są definiowane z użyciem słów kluczowych static i inline. Należy jednak pamiętać, aby nie nadużywać
takich funkcji, bo prowadzi to do niepotrzebnego zwiększenia objętości kodu wynikowego. Innym z wykorzystywanych
rozszerzeń jest możliwość wprowadzania specjalnych wstawek assemblerowych do kodu napisanego w języku C. Te
wstawki są stosowane wszędzie tam, gdzie wymagana jest optymalizacja szybkości działania jądra. Kompilator gcc
udostępnia dwie dyrektywy wspomagające optymalizację kodu. Są to likely() i unlikely(). Służą one do oznaczania
prawdopodobieństwa wykonania kodu w instrukcjach warunkowych, np.: jako unlikely() możemy zaznaczyć warunki
związane z obsługą większości wyjątków. Pisząc kod działający w przestrzeni jądra musimy pamiętać, że nie mamy
„siatki zabezpieczającej” w postaci ochrony pamięci. Jeśli będziemy źle zarządzać pamięcią jądra, to możemy naruszyć
stabilność systemu operacyjnego. Jeśli chcemy użyć liczb i arytmetyki zmiennoprzecinkowej, to musimy sami
oprogramować FPU, jednakże nigdy dotąd potrzeba używania takiej arytmetyki w przestrzeni jądra nie zaistniała.
Rozmiar stosu jądra9 jest ograniczony i wynosi w przypadku architektur 32 bitowych 8 KiB10, a w przypadku 64
bitowych procesorów Alpha 16 KiB. Procesy użytkownika dysponują stosem znajdującym się w przestrzeni
użytkownika, który nie jest ograniczony11. Programując w jądrze musimy pamiętać o synchronizacji dostępu do
zasobów współdzielonych. Mamy do dyspozycji np. takie środki synchronizacji jak semafory i rygle pętlowe (ang. spinlock), czyli semafory z aktywnym oczekiwaniem. Ostatnią ważną rzeczą, o której należy pamiętać pisząc lub zmieniając
kod jądra, to przenośność. Nawet pisząc kod w języku C możemy doświadczyć różnych problemów, jak choćby
porządek bitów i bajtów w słowie, czy rozmiary poszczególnych typów danych.
Bardzo krótkie wprowadzenie do tworzenia modułów jądra 2.6 i nowszych
12
Moduły jądra są formą bibliotek współdzielonych, jednakże nie są one dynamicznie łączone z procesami użytkownika
lecz z samym jądrem. Dzięki modułom możemy łatwo dodawać lub usuwać nowe funkcje do jądra, bez konieczności
restartowania systemu. Pisząc własny moduł należy być świadomym tego, że błąd w module może spowodować
poważne konsekwencje dla pracy systemu, a w niektórych przypadkach może również doprowadzić do uszkodzenia
sprzętu.
Sposób pisania modułów dla jądra Linuksa 2.6 i nowszych różni się od tego, który obowiązywał we wcześniejszych
wersjach. Przede wszystkim, aby skompilować moduł należy mieć zainstalowany, w zależności od wersji jądra, cały
jego kod źródłowy lub specjalną wersję do kompilacji modułów. Kompilację wykonujemy za pomocą programu
narzędziowego „make”. W ramce poniżej przedstawiona została zawartość przykładowego pliku konfiguracyjnego
(Makefile) dla tego programu.
Treść tego pliku została zapożyczona z dokumentu pt. "The Linux Kernel Module Programming Guide", którego
autorami są Peter Jay Salzman, Michael Burian i Ori Pomerantz, a który jest dostępny pod adresem
http://www.tldp.org/LDP/lkmpg/2.6/lkmpg.pdf. Plik ten zawiera regułę „clean”, która usuwa pliki powstałe w wyniku
8
9
Dodajmy: w notacji AT&T.
Określenia co najmniej mylące. Chodzi o stos jądra, który jest przydzielany każdemu procesowi użytkownika
i używany jeśli ten proces zainicjalizuje wywołanie systemowe.
10 Istnieje możliwość zmniejszenia tego rozmiaru do 4 KiB.
11 Dokładniej: większość dystrybucji Linuksa ograniczają rozmiar stosu do 8MiB, ale użytkownik uprzywilejowany może
znieść te ograniczenia.
12 Całość rozdziału (wraz z przykładami) bazuje na książce” Jonathan Corbet, Alessandro Rubini, Greg Kroah-Hartman,
"Linux Device Drivers".
3
Systemy Operacyjne – semestr drugi
kompilacji pliku z kodem źródłowym modułu.
Aby program „make” wykonał regułę „clean”
należy go wywołać następująco: „make clean”.
Wywołania programu bez parametru spowoduje
skompilowanie modułu o nazwie „first”. Który
moduł ma zostać skompilowany określa wiersz
„obj-m := first.o” pliku konfiguracyjnego.
obj-m := first.o
KERNELDIR = /lib/modules/$(shell uname -r)/build
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(PWD) clean
Moduł o nazwie „first” jest modułem typu „Hello World!”, który powinni stworzyć wszyscy początkujący programiści,
chcący pisać własne moduły (kod należy umieścić w pliku o nazwie „first.c”):
#include<linux/module.h>
static int __init first_init(void)
{
printk(KERN_ALERT"Welcome\n");
return 0;
}
static void __exit first_exit(void)
{
printk(KERN_ALERT"Good bye\n");
}
module_init(first_init);
module_exit(first_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Arkadiusz Chrobot <[email protected]>");
MODULE_DESCRIPTION("Another \"Hello World!\" kernel module :-)");
MODULE_VERSION("1.0");
Plik nagłówkowy „linux/module.h” jest dołączny do kodu każdego modułu jądra. Zawiera on między innymi makra
preprocesora, które zostaną opisane niżej. Funkcje o nazwach „first_init” i „first_exit” są odpowiedzialne odpowiednio
za wykonanie inicjalizacji tuż po załadowaniu modułu i deinicjalizację tuż przed usunięciem modułu z pamięci
operacyjnej. Obie funkcje zostały zdefiniowane z użyciem słowa kluczowego static, co oznacza, że są dostępne
wewnątrz przestrzeni nazw modułu „first”, co zapobiega ewentualnemu konfliktowi nazw. Żadna z tych funkcji nie
przyjmuje parametrów wywołania. Funkcja „first_exit” nic również nie zwraca, natomiast „first_init” zwraca wartość
typu int. Jeśli użylibyśmy terminologii znanej z języków obiektowych, to o tych funkcjach możemy myśleć jako
o konstruktorze i destruktorze. W nagłówkach takich funkcji mogą, ale nie muszą występować znaczniki __init i __exit.
Pierwszy sygnalizuje, że funkcja jest używana wyłącznie podczas inicjalizacji modułu i po jej wykonaniu można zwolnić
pamięć na nią przydzieloną. Drugi znacznik sygnalizuje, że funkcja jest używana wyłącznie podczas sprzątania. Ten
znacznik ma znaczenie jeśli moduł jest włączany do kodu jądra na etapie kompilacji, lub gdy jądro jest tak
skonfigurowane, że nie pozwala na usunięcie załadowanych modułów. Makra module_init oraz module_exit przyjmują
jako parametry wywołania adresy funkcji odpowiedzialnych za inicjalizację i sprzątanie po module. Te makra
pozwalają programiście powiadomić kompilator, które funkcje będą odpowiedzialne za inicjalizację i sprzątanie po
module. Cztery pozostałe makra nie są obowiązkowe i pozwalają określić licencję na jakiej moduł jest
rozpowszechniany, autora modułu (zalecane jest podanie jego adresu e-mail), opis modułu oraz jego wersję. Wszystkie
te informacje są zapisane w postaci łańcuchów znaków, według konwencji obowiązującej w języku C. Mając plik
wynikowy możemy je odczytać posługując się programem „modinfo” np.: „modinfo first.ko”. Nieumieszczenie w kodzie
modułu makra MODULE_LICENSE lub podanie nazwy innej niż ta, która określa jedną z wolnych licencji spowoduje
wystąpienie komunikatu ostrzegawczego, mówiącego o tym, że jądro zostało „skażone” modułem o nieznanej lub
niedopuszczalnej licencji. Nazwy licencji, które nie generują takiego ostrzeżenia, to: „GPL”, „GPL v2”, „GPL additional
rights”, „Dual BSD/GPL”, „Dual MPL/GPL”. Moduły na licencji niewolnej mogą jako parametr tego makra przekazać
„Proprietary”. W kodzie modułu występują wywołania funkcji „printk”. Warto zwrócić uwagę na znacznik będący
argumentem wywołania tej funkcji i poprzedzający właściwy łańcuch znaków. Określa on poziom ważności
4
Systemy Operacyjne – semestr drugi
emitowanego komunikatu lub inaczej poziom logowania. Istnieje kilka takich znaczników: KERN_EMERG – sytuacja
awaryjna, KERN_ALERT – problem wymagający natychmiastowej reakcji, KERN_CRIT – sytuacja krytyczna,
KERN_ERR – błąd, KERN_WARNING – ostrzeżenie, KERN_NOTICE – sytuacja normalna, ale zaszło zdarzenie godne
odnotowania, KERN_INFO – informacja, KERN_DEBUG – komunikat diagnostyczny. Komunikaty od modułów jądra
zapisywane są w buforze komunikatów jądra, a stąd mogą być skopiowane do pliku dziennika systemowego (zazwyczaj
/var/log/messages), wypisywane na konsolę i można je wyświetlić na ekran za pomocą polecenia „dmesg”. Moduły
jądra możemy ładować lub usuwać będąc zalogowanymi jako użytkownik „root” lub jako użytkownik posiadający takie
uprawnienia (patrz polecenia „sudo” i „su”). Korzystając z polecenia „insmod” możemy załadować moduł, a poleceniem
„rmmod” usunąć, np.: „insmod first.ko”, „rmmod first”. Informacje o załadowanych modułach możemy uzyskać
używając polecenia „lsmod”, nawet będąc zalogowanymi jako zwykły użytkownik. Istnieją również inne narzędzia do
obsługi modułów, które nie będą tutaj opisywane (np. „modprobe”). Ze względu na dosyć częste zmiany w API jądra
warto zapoznać się z działaniem makrodefinicji preprocesora LINUX_VERSION_CODE i KERNEL_VERSION. Pierwsza
zwraca wersję źródeł jądra zainstalowanych w systemie w postaci wartości typu int, a druga zamienia numer wersji
jądra przekazany jej przez argumenty w postaci trzech liczb (np. KERNEL_VERSION(2,6,19)) na postać numeryczną.
Dzięki nim możemy napisać odpowiednie instrukcje warunkowe dla preprocesora, aby włączał fragmenty kodu
przeznaczone dla danej wersji jądra podczas kompilacji.
5