2004 05 Tworzenie modułów jądra – ten pierwszy raz [Programowanie]


dla programistów
Tworzenie modułów
jÄ…dra  ten pierwszy raz
Marek Sawerwain
rogramowanie systemowe raczej trudne i wymagające dużych umie-
stało się mniej popularne. jętności. Najprostsze moduły są jednak
Zdecydowana większość pro- bardzo łatwe do napisania i wbrew
Pgramistów tworzy oprogra- pozorom, nie trzeba doskonale oriento-
mowanie przy pomocy wygodnych śro- wać się w zawiłościach jądra.
dowisk pracy, które ukrywają większość Listing 1 zawiera przykład takiego
szczegółów systemu. Na szczęście auto- modułu, który wyświetla nieśmiertelne
rzy sterowników czy ogólnie programiści  Hello World! . W odniesieniu do typo-
systemowi zawsze będą mieli zadania do wych programów w języku C, mamy tu
realizacji  ktoś musi napisać niezbędny kilka różnic.
kod niskiego poziomu do obsługi jakie- Pierwsza to naturalnie brak funk-
goÅ› urzÄ…dzenia. cji main. Zamiast niej sÄ… dwie specjalne
Programowanie systemowe, czyli funkcje, których deklaracja jest koniecz-
m.in. tworzenie sterowników, wymaga na. Pierwsza (int init_module();) jest
oczywiście doskonałej znajomości sprzę- wywoływana w momencie załadowa-
tu, ale podstawy tworzenia modułów dla nia modułu przez jądro. Jak łatwo zgad-
jądra Linuksa każdy może opanować bez nąć, powinny być w niej zawarte wszyst-
większych problemów. Jest to dość cenna kie wstępne czynności. Odwrotna w dzia-
umiejętność, gdyż procedury działają- łaniu jest funkcja void cleanup_module();.
ce na poziomie jądra w niektórych przy- Jej zadaniem jest usunięcie np. przydzie-
padkach mogą osiągać lepszą wydajność, lonej pamięci, czyli mówiąc nieco kolo-
a nie każdy moduł musi być przecież od kwialnie, posprzątanie po pracy modułu.
razu sterownikiem do jakiegoÅ› urzÄ…dze- Istotnym elementem jest licencja. Jak
nia podłączonego do komputera. wiadomo, twórcy jądra Linuksa są bardzo
W tym artykule chciałbym pokazać wrażliwi na tym punkcie, więc podczas
prosty moduł, który w katalogu /proc pisania modułów wymagane jest określe-
umieści plik z informacjami o proce- nie licencji, na jakiej udostępniamy nasz
sorze. Oczywiście, jądro Linux oferuje moduł. W naszym przypadku będzie to
nam takie informacje (w pliku cpuinfo), oczywiście licencja GPL, dlatego dodaje-
ale zrobienie tego samodzielnie da nam my do kodu dodatkowe makro w nastÄ™-
kilka bezcennych doświadczeń. pującej postaci: MODULE_LICENSE( GPL );.
Na samym poczÄ…tku naszej pracy od
razu należy ustalić, dla jakiego typu jądra
Listing 1. Hello World! jako moduł jądra
będziemy pisać moduły. Poszczególne
Na płycie CD/DVD Linuksa.
rodziny jÄ…der, takie jak 2.0.x, 2.2.x, 2.4.x
Na płycie CD/DVD znajdują
oraz najnowsza 2.6.x (starsze typy jÄ…dra #define MODULE
się pliki zródłowe napisanego
#define __KERNEL__
odeszły już do lamusa), dość znacz-
sterownika, jak również
#include
wszystkie listingi. nie się między sobą różnią. Ponieważ
#include
w momencie pisania tego artykułu naj-
MODULE_LICENSE( GPL );
O autorze
większą popularność ma jądro 2.4.x, to
int init_module(){
Autor zajmuje siÄ™ tworzeniem
S
moduły omawiane w tym artykule są printk ( Witaj
oprogramowania dla WIN32
Åšwiecie!!!\n );
przeznaczone właśnie dla tej rodziny.
i Linuksa. Zainteresowania: teoria
return 0;
języków programowania oraz
}
PoczÄ…tki sÄ… Å‚atwe
dobra literatura. Kontakt
void cleanup_module()
z autorem: autorzy@linux.com.pl. Tworzenie większego oprogramowania,
{ }
działającego na poziomie jądra, to zadanie
60
maj 2004
tworzenie modułów jądra Linux dla programistów
plik wykonywalny (bądz biblioteka jest, aby podać cel all, a w jego nagłów-
dynamiczna), czyli efekt pracy programu ku wymienić wszystkie pliki obiektowe.
konsolidującego, tzw. linkera, który łączy Kompilacji dokona sam Make  użyje do
poszczególne pliki obiektów (pliki o roz- tego tylko opcji  -c oraz dodatkowych
szerzeniu *.o) w ostateczny plik binarny. opcji podanych w zmiennej CFLAGS.
W przypadku modułu jądra sytuacja jest Plik makefile znajdujący się na płycie
odmienna. Wystarczają nam tylko same CD/DVD jest nieco bogatszy, gdyż zawie-
pliki obiektowe  nie dokonujemy konso- ra dwa dodatkowe cele Å‚adujÄ…ce wszyst-
lidacji pliku wykonywalnego. kie moduły do jądra (polecenie: make
Rysunek 1. Wyniki polecenia lsmod
Kompilacja przykładu z Listingu 1 load) oraz usuwający moduły z pamięci
Brak tego makra spowoduje, że podczas przedstawia się następująco: (polecenie: make unload).
ładowania modułu (poleceniem insmod)
S
otrzymamy komunikat o niezgodności gcc -c module1.c -D__KERNEL Deklaracja parametrów
licencji, chociaż będzie on pracował -I/usr/src/linux/include jądra
poprawnie. Bardzo często do modułu w momencie
Jądro Linuksa w przypadku modu- Istotne jest wskazanie położenia plików ładowania przekazywane są dodatkowe
łów oferuje jeszcze dwa inne makra: nagłówkowych jądra. Kilka informacji na parametry. np.:
MODULE_AUTHOR, gdzie podajemy autora ten temat zawiera ramka yródła jądra w
modułu, oraz MODULE_DESCRIPTION, które- systemie. Po wykonaniu tego polecenia insmod ne io=0x260
go przeznaczeniem jest podanie krótkie- otrzymamy plik module1.o, gotowy do
go opisu modułu. załadowania poleceniem insmod. Robimy Opis dodatkowych parametrów jest dość
Kolejną różnicą jest brak typowych to w następujący sposób: prosty. Korzystamy z makra MODULE_PAR
funkcji bibliotecznych. Jak widać, nie włą- o dwóch argumentach. W pierwszym
czamy do naszego modułu pliku nagłów- insmod ./module1.o parametrze podajmy zmienną, gdzie
kowego stdio.h. Nie jest to problemem, będzie przechowywana wartość, nato-
gdyż jądro oferuje kilka funkcji będą- Wskazanie, że chodzi o plik znajdujący się miast w drugim określamy typ parametru.
cych odpowiednikami typowych funkcji w katalogu bieżącym - ./ - jest konieczne, Nazwa tego parametru jest odczytywana
bibliotecznych. Podstawowymi przykła- ponieważ nasz moduł nie został jeszcze z nazwy zmiennej, więc trzeba w tym
dami są printk oraz sprintf. Jądro posia- zainstalowany w domyślnym katalogu, w miejscu podać nazwę znaczącą, która
da również własne odpowiedniki takich którym system przechowuje moduły. będzie charakteryzować przeznaczenie
funkcji, jak memset czy strcpy. Nagłówki Załadowany moduł usuwamy pole- parametru. Jeśli to nie wystarczy, to za
tych funkcji zawiera plik string.h. ceniem:
Bardzo ważne są także dwie definicje
yródła jądra w systemie
preprocesora: #define MODULE oraz #define rmmod module1
Do poprawnej i bezproblemowej kompila-
__KERNEL__. Ich definicja musi nastÄ…-
cji naszych modułów potrzebne są nam
pić przed włączeniem pozostałych plików W przypadku kilkunastu modułów najle-
zródła jądra, a dokładniej pliki nagłów-
nagłówkowych. Definicje można także piej przygotować odpowiedni skrypt dla
kowe. Stało się tradycją, że kompletne
zdefiniować opcją  -D podczas wywoły- programu Make. Przykład takiego pliku,
zródła jądra są umieszczane w katalogu
wania polecenia kompilatora  gcc. kompilującego wszystkie moduły z tego /usr/src/ (w skrypcie makefile z Listin-
gu 2 powołujemy się na ten katalog).
Zadanie naszego modułu jest try- artykułu (ich kod zródłowy jak zawsze
W wielu dystrybucjach pliki nagłówkowe
wialne  instrukcją printk (odpowied- znajduje się na płycie CD/DVD), przed-
sÄ… jednak umieszczane w standardowym
nikiem printf) wyświetlamy komunikat stawia Listing 2.
katalogu /usr/include. Dość często są
tekstowy. Struktura skryptu makefile nie jest
to pliki pochodzÄ…ce z innej wersji jÄ…dra
Po załadowaniu modułu możemy nie skomplikowana. Definiujemy dokładnie
niż jest zainstalowana w systemie. Po
zobaczyć na ekranie naszego komunikatu, trzy zmienne: CFLAGS zawiera dodatko-
kompilacji modułów z innymi plikami
gdyż zostanie on przesłany do logu jądra, we opcje przeznaczone dla kompilatora,
nagłówkowymi, podczas próby ładowa-
a ten możemy przejrzeć wydając polece- CC to zmienna zawierająca polecenie kom-
nia otrzymamy komunikat o niezgodności
nie dmesg (lepiej dmesg | less  będziemy pilatora, a zmienna OBJS określa wszystkie
wersji. RozwiÄ…zanie jest bardzo proste.
mogli przeglądać log strona po stronie kla- nazwy plików obiektowych, które maja
Wystarczy skasować katalogi linux, asm
wiszami [PageUp] i [PageDown]). Niektóre zostać utworzenie przez skrypt. Oczywi- oraz scsi z /usr/include. Jeśli zależy nam
dystrybucje są jednak tak skonfigurowane, ście muszą istnieć odpowiednie pliki zró- na obecności tych katalogów, to zamiast
kopiowania plików najlepiej utworzyć
iż komunikaty przekazane przez printk są dłowe.
odniesienia symboliczne. ZnajdujÄ…c siÄ™
kierowane na konsolÄ™. Skrypty dla programu Make zazwy-
w katalogu /usr/include, gdy tworzymy
czaj posiadają wiele reguł, np. jak otrzy-
dowiÄ…zanie linux, wydajemy polecenie ln
Kompilacja oraz ładowanie mać z pliku zródłowego plik obiektu.
w następującej postaci:
modułu Wiele podstawowych reguł jest wbu-
Gdy tworzymy standardowe oprogramo- dowanych do Make a, więc nie musimy
S
ln -s /usr/src/linux/include/linux
wanie, to przystępując do kompilacji naj- definiować elementarnych reguł kompila-
linux
częściej oczekujemy, że wynikiem będzie cji kodu zródłowego do modułu. Ważne
61
www.linux.com.pl
dla programistów
S
__pde=create_proc_read_entry(file_
Listing 2. Skrypt makefile
proc_name, 0, NULL, procfile_read, NULL);
odpowiedzialny za kompilacjÄ™
przykładów
Jej wynikiem jest wskaznik umieszczony
KERNEL_DIR=/usr/src/linux
w naszej zmiennej. Pierwszy parametr
tej funkcji to nazwa pliku. Następnie
S
CFLAGS=-D__KERNEL -I$(KERNEL_DIR)
określamy tryb dostępu do pliku  zero
-Wall
zapewni nam możliwość odczytywa-
CC=gcc
S
nia zawartości pliku. Kolejny argument
OBJS=module1.o module2.o
Rysunek 2. Testowanie modułu z Listingu 4
module3.o mod_cpu.o
to punkt umocowania naszego pliku
all: $(OBJS)
 wartość NULL oznacza katalog główny i zarejestrować własny plik w systemie
clean:
systemu proc. Pózniej podajemy proce- proc. Czynności, które wykonuje nasz
rm -f $(OBJS)
durę odpowiedzialną za przygotowanie kod, warto pokazać za pomocą prostego
danych. Ostatni argument to wskaznik schematu blokowego. Rysunek 4 przed-
pomocą makra MODULE_PARM_DESC istnieje void*  możemy poprzez ten argument stawia taki schemat. Różni się on tym od
możliwość dodania opisu określonego przekazać dowolne dane, jeśli zachodzi typowego schematu, że zamiast wbloków
parametru. taka potrzeba. START i STOP mamy tu takie czynności,
Listing 3 zawiera kod zródłowy Gdy użytkownik spróbuje odczy- jak załadowanie modułu oraz usunięcie
modułu definiującego jeden argument tać wartość naszego pliku, to oczywi- modułu.
par1 typu string (wszystkie typy parame- ście zostanie wywołana funkcji procfi- Dotychczas nie wspominałem, w jaki
trów zostały zebrane w Tabeli 1). le_read. Dysponuje ona szeregiem para- sposób możemy uzyskać informacje
Argumenty mogą posiadać wartości metrów. Najważniejszy dla nas parametr o procesorze. Wykorzystamy do tego celu
domyślne. Są one przypisane w momen- to page. Jak widać z listingu, fun- instrukcję cpuid. Z jej pomocą możemy
cie deklaracji zmiennej. W przykła- kcją sprintf przepisujemy przyszłą zdobyć wiele informacji, jednak dla swo-
dzie wartością domyślną jest tekst  de- zawartość pliku do tej właśnie zmien- istej  politycznej poprawności , na począ-
fault value Zostanie on oczywiście nej. tek sprawdzimy, czy nasz system obsłu-
.
zastąpiony w momencie określenia war- Podczas usuwania modułu konieczną guje taką instrukcję. Jest ona obecna we
tości argumentu podczas ładowania operacją jest usunięcie wpisu z syste- wszystkich wydanych procesorach zgod-
modułu: mu proc. Używamy w tym celu funkcji nych z Intelem na przestrzeni ostatnich
remove_proc_entry. Pierwszy argument kilkunastu lat.
insmod ./module2 par1= nowa wartość to nazwa naszego pliku, a drugim jest Osoby, które chcą dokładniej przyj-
wskazanie na katalog rodzicielski. Pod- rzeć się sposobom wykrywania proces-
Nowy plik w katalogu /proc czas wywoływania podaliśmy wartość ora, odsyłam do kodu zródłowego
Zadaniem naszego modułu jest dostar- NULL, wskazując, że mamy na myśli kata- jądra  plik arch/i386/kernel/setup.c.
czenie informacji o procesorze. Natu- log główny. W artykule zostały jednak zastoso-
ralnym rozwiązaniem jest umieszczenie Gdy nie usuniemy naszego wpisu, wane inne funkcje, których kod
tych informacji w katalogu /proc. Listing a dowolny program spróbuje odczytać pochodzi z programu MPlayer 1.0.3,
4 zawiera fragmenty modułu tworzące- nasz plik, zakończy się to jego przerwa- a dokładniej z plików cpudetect.c i cpu-
go przykładowy plik w systemie proc niem i zrzutem pamięci, czyli utworze- detect.h.
 brakuje tylko pełnej definicji funkcji niem ulubionego pliku wszystkich pro- Przykład typowej funkcji has_cpuid,
proc_calc_metrics, ale powrócimy do gramistów  core. która sprawdza obecność cpuid, zawiera
niej w dalszej części. Listing 5. Cały test, jak widać, został zapi-
Wszystkie funkcje zawiązane z zarzą- Zadanie główne  sany przy pomocy assemblera. Ogólna
dzaniem katalogiem /proc znajdujÄ… siÄ™ w informacje o procesorze idea testu jest bardzo prosta  wystarczy
pliku proc_fs.h. Obejmują one tworze- Po tych wstępnych informacjach, przy- spróbować zmienić 21 bit rejestru flag
nie katalogów oraz plików do odczy- szedł czas na realizację głównego zada- (EFLAGS). Jeśli możemy zmienić ten bit, to
tu i zapisu. Nas interesuje utworzenie nia. Posiadamy już wystarczającą ilość oznacza to, że mamy dostęp do instruk-
pliku (zawierającego tekst) wyłącznie do informacji o tym, jak utworzyć moduł cji cpuid.
odczytu.
Tabela 1. Typy parametrów przekazywanych do modułów
RejestracjÄ™ nowego pliku wykonuje-
Oznaczenie Definicja
my w funkcji init_module, ale zanim to
zrobimy, należy utworzyć zmienną glo- b pojedynczy bajt (unsigned char)
balną reprezentującą ten plik. Definiuje- h krótka liczba całkowita (short int)
my ją w taki sposób:
i liczba całkowita (int)
l liczba całkowita długa (long)
struct proc_dir_entry* __pde;
s ciąg znaków
n1-n2[bhils] tablica o co najmniej n1 elementach, jednak nie dłuższa niż n2
Rejestracja pliku to wywołanie tylko
elementy
jednej funkcji:
62
maj 2004
tworzenie modułów jądra Linux dla programistów
Listing 3. Deklaracja nowego parametru
modułu
#define MODULE
#define __KERNEL__
#include
#include
MODULE_LICENSE( GPL );
char *par1= default value ;
MODULE_PARM(par1,  s );
int init_module(){
S
printk ( Parametr
par1=[%s]\n , par1);
return 0;
}
void cleanup_module()
{ }
Na Listingu 5 znajduje się również
druga funkcja: do_cpuid. O ile zadaniem
pierwszej było sprawdzenie, czy mamy
dostęp do cpuid, to druga, w zależ-
Rysunek 4. Schemat działania modułu
ności od stanu rejestru eax, wykonuje
instrukcjÄ™ cpuid, a otrzymane informa-
cje umieszcza w tablicy wskazanej przez procesora. Należy do argumentu eax zawierają swoją sygnaturę, więc można
argument p naszej funkcji. załadować zero, a następnie wywo- z pomocą instrukcji cpuid ustalić pro-
łać instrukcję do_cpuid. W podanej ducenta.
ÅšciÄ…ganie informacji tablicy zostanÄ… umieszczone fragmen- Kod wpisujÄ…cy do zmiennej page
DysponujÄ…c funkcjami opisanymi ty (po cztery litery) nazwy producenta. nazwÄ™ producenta oraz tzw. maksymal-
w poprzednim punkcie, jesteśmy gotowi W przypadku Intela będzie to napis ną wartość poziomu, dla której genero-
do odczytania informacji o procesorze.  GenuineIntel , a dla AMD   Authen- wane sÄ… odpowiedzi instrukcji cpuid, jest
Na Listingu 6 znajdują się fragmenty ticAMD . Procesory pozostałych firm, następujący:
głównej funkcji procfile_read. takich jak choćby Transmeta, także
Wszystkie informacje o procesorze
są otrzymywane w wywołaniu funk-
Listing 4. Utworzenie nowego pliku w systemie plików /proc
cji procfile_read, gdy ktoÅ› odczyta
nasz plik. Oprócz wykorzystywania #define MODULE
#define __KERNEL__
has_cpuid oraz do_cpuid, korzystamy
#include
z dwóch dodatkowych funkcji. O jednej
#include
z nich (proc_calc_metrics) już wspomi-
#include
nałem, a druga (proc_sprintf) to odpo-
MODULE_LICENSE( GPL );
wiednik sprintf, działający poprawnie char *file_proc_name= module3_info ;
char *par1= default value ;
w środowisku jądra. Funkcje te są nam
MODULE_PARM(par1,  s );
potrzebne, aby poprawnie pisać do
struct proc_dir_entry* __pde;
zmiennej page.
int proc_calc_metrics(char *page, char **start, off_t off, int count, int *eof,
Jako pierwszÄ… cennÄ… informacjÄ™
int len)
spróbujmy uzyskać nazwę producenta {...}
int procfile_read(char *page, char **start, off_t off, int count, int *eof, void
*data){
int len;
len=sprintf(page, Hello, %s , par1);
return proc_calc_metrics(page, start, off, count, eof, len);
}
int init_module(){
__pde=create_proc_read_entry(file_proc_name, 0, NULL, procfile_read, NULL);
return 0;
}
void cleanup_module(){
Rysunek 3. Moduł mod_cpu w akcji
remove_proc_entry(file_proc_name, NULL);
 podstawowe informacje uzyskane przez
}
cpuid
63
www.linux.com.pl
dla programistów
Listing 5. Test, czy procesor udostępnia Listing 6. Fragmenty funkcji procfile_read, która przygotowuje dane o procesorze
instrukcjÄ™ cpuid
int procfile_read(char *page, char **start, off_t off, int count, int *eof, void
*data){
int has_cpuid(){
int len=0;
int a, c;
unsigned int regs[4], regs2[4];
proc_sprintf(page,&off,&len, CPUID info v1.0\n );
__asm__ __volatile__ (
if(has_cpuid()) {
 pushf\n\t
proc_sprintf(page,&off,&len, instrukcja cpuid jest dostępna\n );
 popl %0\n\t
do_cpuid(0x00000000, regs);
 movl %0, %1\n\t
...
 xorl $0x200000, %0\n\t
if (regs[0]>=0x00000001) {
 push %0\n\t
unsigned cl_size;
 popf\n\t
do_cpuid(0x00000001, regs2);
 pushf\n\t
cpuType=(regs2[0] >> 8)&0xf;
 popl %0\n\t
if(cpuType==0xf) cpuType=8+((regs2[0]>>20)&255);
:  =a (a),  =c (c)
cpuStepping=regs2[0] & 0xf;
:
hasTSC = (regs2[3] & (1 << 8 )) >> 8;
:  cc
hasMMX = (regs2[3] & (1 << 23 )) >> 23;
);
...
return (a!=c);
cl_size = ((regs2[1] >> 8) & 0xFF)*8;
}
if(cl_size){
cl_size = cl_size;
void do_cpuid(unsigned int ax,
proc_sprintf(page,&off,&len, Wielkość linijki cache u (w bajtach):
unsigned int *p){
%u\n ,cl_size);
__asm __volatile__ (
}
 movl %%ebx, %%esi\n\t
...
 cpuid\n\t
}
 xchgl %%ebx, %%esi
do_cpuid(0x80000000, regs);
:  =a (p[0]),  =S (p[1]),
if (regs[0]>=0x80000001){
 =c (p[2]),
S
proc_sprintf(page,&off,&len, rozszerzony poziom cpuid: %d\n ,
 =d (p[3])
regs[0]&0x7FFFFFFF);
:  0 (ax));
do_cpuid(0x80000001, regs2);
}
hasMMX |= (regs2[3] & (1 << 23 )) >> 23;
...
}
unsigned char r[4];
if(regs[0]>=0x80000006){
do_cpuid(0x00000000, r);
do_cpuid(0x80000006, regs2);
proc_sprintf(page,&off,&len, S
proc_sprintf(page,&off,&len, rozszerzone informacje o cache u %d\n ,
regs2[2]&0x7FFFFFFF);
cl_size = regs2[2] & 0xFF;
Gdy chcemy uzyskać dalsze informacje,
S
proc_sprintf(page,&off,&len, Wielkosc linijki cache u (w bajtach):
jest to uzależnione od maksymalnej war-
%u\n ,cl_size);
tości umieszczonej w tablicy r pod pierw-
}
szym (zerowym) elementem. W naszym
proc_sprintf(page,&off,&len, Zestawy instrukcji:\nMMX ... \n ,
module wyświetlamy informacje o tym, hasMMX, ... ,has3DNowExt );
}
czy są dostępne dodatkowe instrukcje
else {
typu MMX czy SSE. Informacje te uzy-
proc_sprintf(page,&off,&len, instrukcja cpuid nie jest dostępna\n );
skamy wywołując do_cpuid w następu-
}
jący sposób:
proc_sprintf(page,&off,&len, ---\n );
return proc_calc_metrics(page, start, off, count, eof, len);
}
do_cpuid(0x00000001, r);
Po wywołaniu tej instrukcji w reje-
strze edx znajdą się dodatkowe informa- wiednie wartości: jeden, jeśli instruk- jest to zawartość rejestru edx). Gdy bit
cje, jaki typ instrukcji jest obsługiwany. cje SSE są dostępne, a zero, jeśli nie, jest będzie ustawiony, to wynikiem ope-
W naszym przypadku zawartość reje- następująca: racji bitowego  i będzie oczywiście
stru edx to ostatni element tablicy. jedynka na 25 pozycji. Ponieważ my
Sprawdzenie, jakie rodzaje instrukcji są hasSSE = (r[3] & (1 << 25 )) >> 25; chcemy uzyskać normalną liczbę, zero
dostępne, to odpowiednie manipulowa- bądz jeden, wystarczy, jeśli z powro-
nie bitami. Aby nie kodować mozolnie liczby tem przesuniemy nasz bit o 25 pozycji
Załóżmy, że chcemy sprawdzić, czy o ustawionym 25 bicie, wykorzystujemy w lewo na początek. W ten sposób
procesor oferuje nam dostęp do instruk- przesuniecie bitowe w lewo. Przesu- uzyskujemy informację, czy mamy
cji SSE. Informacje o dostępności SSE wamy jedynkę. Następnie wykonujemy dostęp do instrukcji SSE. W analogiczny
zawiera bit o numerze 25. Linijka kodu, bitową operację  i na trzecim elemen- sposób sprawdzamy, czy mamy dostęp
która wpisze do zmiennej hasSSE odpo- cie naszej tablicy (dla przypomnienia do instrukcji SSE2  sprawdzamy 26
64
maj 2004
tworzenie modułów jądra Linux dla programistów
waż zastosowano słowo static, więc
Listing 7. Funkcje odpowiedzialne za bezpieczne umieszczanie tekstu podczas odczytu
powtarzamy jej definicjÄ™ w naszym
pliku w systemie proc
module. Zadanie funkcji proc_sprintf
to naturalnie wpisanie do zmiennej page
void proc_sprintf(char *page, off_t *off, int *lenp, const char *format, ...){
nowego tekstu, jednak z poszanowa-
int len = *lenp;
niem wielkości strony  PAGE_SIZE. Jest
va_list args;
to szczególnie ważne, gdy mamy zamiar
if (len > PAGE_SIZE-512)
wpisać dużo danych bądz chcemy
return;
zawartość pliku tworzyć fragmentami.
va_start(args, format);
len += vsnprintf(page + len, PAGE_SIZE-len, format, args); Tak też postępujemy w naszym module.
va_end(args);
Jak widać to w kodzie, każdy fragment
if (len <= *off) {
tekstu jest umieszczany z odpowiednim
*off -= len;
przesunięciem: page+len, zmniejszając
len = 0;
dodatkowo wartość przesunięcia off.
}
*lenp = len; Wyznaczona długość tekstu jest pózniej
}
poprzez argument int *lenp przekazy-
static int proc_calc_metrics(char *page, char **start, off_t off,
wana na zewnątrz. Gdyby zabrakło tego
int count, int *eof, int len){
elementu, to zawartość pliku byłaby
if (len <= off+count) *eof = 1;
błędnie odczytywana przez zewnętrz-
*start = page + off;
len -= off; ne aplikacje.
if (len>count) len = count;
Ostatnim elementem w funkcji
if (len<0) len = 0;
procfile_read jest wywołanie proc_
return len;
calc_metrics. Wyznaczy ona ostatecznie
}
poczÄ…tek danych zawartych w argumen-
cie start, a także ustawi flagę eof. Jak
bit. Obecność instrukcji MMX zawiera stawowych rodzajów plików w sys- widać, obsługa przekazania kilku linijek
23 bit. temie plików proc. Definicja funkcji tekstu, czyli ogólnie dość prostej czyn-
Ponieważ świat procesorów zgod- proc_calc_metrics jest ukryta, ponie- ności, wymaga sporego wysiłku, ale
nych z architekturą x86 nie składa się
wyłącznie z produktów firmy Intel, to
Tabela 2. Przykłady argumentów dla cpuid i rodzaj informacji, które można otrzymać tą drogą
nasz kod sprawdza także, czy mamy
Wartość rejestru Odpowiedz
do czynienia z procesorami AMD,
EAX
w których występują instrukcje 3DNow
0x00000000 W rejestrach ebx, edx i ecx znajdujÄ… siÄ™ fragmenty nazwy (po cztery
i 3DNowExt. Postępowanie jest bardzo
znaki) producenta procesora, a w eax maksymalny poziom wartości
podobne  podajemy tylko innÄ… war-
dla rejestru eax, dla której cpuid generuje poprawną odpowiedz.
tość do rejestru eax:
0x00000001 W eax znajdujÄ… siÄ™ zakodowane oznaczenia typu procesora, rodzi-
ny itd. W ebx znajdziemy typ procesu technologicznego, np. war-
do_cpuid(0x80000000, r);
tość 0x0E umieszczona w bitach 7..0 oznacza procesor Intel Pen-
tium 4 Xeon wykonany w technologii 0.18 µm. Rejestr ecx zawiera
Za pośrednictwem cpuid możemy
informacje o instrukcjach. Ustawiony bit zerowy oznacza obecność
pozyskać znacznie więcej informacji,
instrukcji SSE3. Również rejestr edx zawiera informacje o dostęp-
np. poznać wielkość cache u czy typ tech-
nych zestawach instrukcji: bit 23 odpowiada instrukcjom MMX, bit
nologii, w jakiej został wykonany proce-
25 instrukcjom SSE, a ustawiony bit 26 oznacza, że mamy dostęp
sor. Opis postaci argumentów dla kilku
do SSE2.
przypadków, które można odczytać za
0x00000002 Rejestr eax zawiera informacje o pamięci cache.
pomocÄ… instrukcji cpuid, zawiera Tabela 2.
Wszystkie informacje o parametrach 0x00000003 W rejestrach ebx, ecx i edx ukryty jest numer seryjny procesora.
i wartościach cpuid można odszukać
0x80000000 Uzyskujemy rozszerzone informacje o procesorze. Maksymalny
w portalu http://www.sandpile.org/.
argument znajduje się, podobnie jak dla wartości 0x00000000,
Zachęcam do modyfikacji kodu, który
w eax. A rejestry ebx, edx i ecx zawierajÄ… nazwÄ™ sprzedawcy
jest zawarty na płycie CD/DVD.
procesora.
0x80000001 Rozszerzone informacje o możliwościach procesora, np. ustawiony
Słówko o proc_calc_
bit 30 rejestru edx oznacza dostępność instrukcji 3DNow+, a bit 31
metrics i proc_sprintf
standardowych instrukcji 3DNow.
Obydwie funkcje pochodzÄ… z pliku
0x80000002, W rejestrach eax, ebx, ecx i edx zostanÄ… umieszczone fragmenty
proc_misc.c  znajduje się on w zró-
0x80000003, nazwy procesora.
dłach jądra w katalogu /fs/proc. Warto
0x80000004
do niego zajrzeć, aby na przykła-
0x80000006 Informacje o pamięci cache L2.
dzie poznać sposób tworzenia pod-
65
www.linux.com.pl
dla programistów
otowe funkcje: proc_sprintf i proc_calc_metrics, ułatwiają
ten proces  nie trzeba specjalnie wnikać w wewnętrzne
mechanizmy jądra, aby poprawnie napisać moduł.
Instalacja modułu
Ostatnim zadaniem jest poprawna instalacja naszego modułu
w systemie. Aadowanie modułu można przeprowadzić poda-
jąc dokładną ścieżkę określającą położenie pliku, np.:
insmod /usr/local/modules/mod_cpu.o
Lepszym rozwiązaniem jest umieszczenie modułu w odpo-
wiednim katalogu, np.: /lib/modules/x.x.x/kernel (x.x.x to
numer wersji naszego jÄ…dra). Po skopiowaniu pliku do tego
katalogu należy uaktualnić bazę modułów wydając polece-
nie depmod -a. Po tej czynności możemy już ładować nasz
moduł wydając polecenie: insmod mod_cpu. System samo-
dzielnie bez naszej pomocy znajdzie nasz moduł i wczyta
go do pamięci.
Podsumowanie
Mam nadzieję, że udało się mi pokazać, iż własnoręcznie
napisanie modułu nie jest trudne. Podstawowe API jądra
oferuje wiele ułatwień. Pewnym problemem jest skąpa
dokumentacja o programowaniu na poziomie jÄ…dra. Szcze-
gólnie teraz, gdy pojawiło się nowe jądro 2.6.0, a nie
ma obszernej dokumentacji dla wersji 2.4.0, trudno liczyć,
że pojawią się dokładne materiały dla jądra, które w tym
momencie staje się przestarzałe. Najlepszym rozwiązaniem
jest jednak analiza kodu zródłowego, w tym plików nagłów-
kowych  uzyskamy wiele niezbędnych informacji. Możemy
również podpatrywać rozwiązania, które zastosowali auto-
rzy jÄ…dra. Ostatecznie, problem dokumentacji nie jest
tak dotkliwy, choć trzeba wnieść duży wysiłek w analizę
kodu jądra. Z tego powodu zachęcam do modyfikacji
modułu i dołączenia dodatkowych informacji,
np. o wielkości cache u czy wsparcia dla instrukcji SSE3.
Najnowsze procesory Intela Prescott już oferują tego rodza-
ju instrukcje.
W Internecie:
" Strona domowa jÄ…dra Linuksa:
http://www.kernel.org/
" Kurs poruszajÄ…cy wszystkie podstawowe zagadnienia
istotne w tworzeniu programów dla jądra:
http://www.tldp.org/LDP/lkmpg/index.html
" Dokument porusza nieco inne aspekty, bowiem dotyczy
systemów wbudowanych, ale zawiera dokładny i przy-
stępny opis API systemu plików proc:
http://www.opentech.at/papers/embedded_proc/
embedded_proc.html
" Opis instrukcji cpuid i parametrów, które przyjmuje:
http://prioris.mini.pw.edu.pl/~michalj/cpuid3/
" Bardzo dokładny opis parametrów i zwracanych wartości
przez instrukcjÄ™ cpuid:
http://www.sandpile.org/ia32/cpuid.htm
66
maj 2004


Wyszukiwarka

Podobne podstrony:
Ten pierwszy raz
ten pierwszy raz
2004 05 Rozproszone fraktale [Bazy Danych]
32 2 Pierwszy raz z Przyjacielem od zawsze
TEN PIERWSZY DZIEN txt
35 lat temu Polacy pierwszy raz sprzeciwili siÄ™ PZPR
NASZ PIERWSZY RAZ, OSTATNI RAZ Ich Troje
pierwszy raz
2004 05 Sybase SQL Anywhere Studio 9 0 [Bazy Danych]
pierwszy raz
Matematyka dyskretna 2004 05 Funkcje boolowskie
2004 05 kolokwium 1
zdarzylo mi sie to pierwszy raz
POCALUJ MNIE TEN JEDEN RAZ txt
Nasz pierwszy raz
2004 05 etap1
Bayer Full Pierwszy raz
05 tworzenie schematow technologicznych
pierwszy raz

więcej podobnych podstron