2004.05_Tworzenie modułów jądra – ten pierwszy raz_[Programowanie].pdf

(694 KB) Pobierz
439033947 UNPDF
dla programistów
Tworzenie modułów
jądra – ten pierwszy raz
Marek Sawerwain
stało się mniej popularne.
Zdecydowana większość pro-
gramistów tworzy oprogra-
mowanie przy pomocy wygodnych śro-
dowisk pracy, które ukrywają większość
szczegółów systemu. Na szczęście auto-
rzy sterowników czy ogólnie programiści
systemowi zawsze będą mieli zadania do
realizacji – ktoś musi napisać niezbędny
kod niskiego poziomu do obsługi jakie-
goś urządzenia.
Programowanie systemowe, czyli
m.in. tworzenie sterowników, wymaga
oczywiście doskonałej znajomości sprzę-
tu, ale podstawy tworzenia modułów dla
jądra Linuksa każdy może opanować bez
większych problemów. Jest to dość cenna
umiejętność, gdyż procedury działają-
ce na poziomie jądra w niektórych przy-
padkach mogą osiągać lepszą wydajność,
a nie każdy moduł musi być przecież od
razu sterownikiem do jakiegoś urządze-
nia podłączonego do komputera.
W tym artykule chciałbym pokazać
prosty moduł, który w katalogu /proc
umieści plik z informacjami o proce-
sorze. Oczywiście, jądro Linux oferuje
nam takie informacje (w pliku cpuinfo) ,
ale zrobienie tego samodzielnie da nam
kilka bezcennych doświadczeń.
Na samym początku naszej pracy od
razu należy ustalić, dla jakiego typu jądra
będziemy pisać moduły. Poszczególne
rodziny jąder, takie jak 2.0.x, 2.2.x, 2.4.x
oraz najnowsza 2.6.x (starsze typy jądra
odeszły już do lamusa), dość znacz-
nie się między sobą różnią. Ponieważ
w momencie pisania tego artykułu naj-
większą popularność ma jądro 2.4.x, to
moduły omawiane w tym artykule są
przeznaczone właśnie dla tej rodziny.
raczej trudne i wymagające dużych umie-
jętności. Najprostsze moduły są jednak
bardzo łatwe do napisania i wbrew
pozorom, nie trzeba doskonale oriento-
wać się w zawiłościach jądra.
Listing 1 zawiera przykład takiego
modułu, który wyświetla nieśmiertelne
„Hello World!”. W odniesieniu do typo-
wych programów w języku C, mamy tu
kilka różnic.
Pierwsza to naturalnie brak funk-
cji main . Zamiast niej są dwie specjalne
funkcje, których deklaracja jest koniecz-
na. Pierwsza ( int init_module(); ) jest
wywoływana w momencie załadowa-
nia modułu przez jądro. Jak łatwo zgad-
nąć, powinny być w niej zawarte wszyst-
kie wstępne czynności. Odwrotna w dzia-
łaniu jest funkcja void cleanup_module(); .
Jej zadaniem jest usunięcie np. przydzie-
lonej pamięci, czyli mówiąc nieco kolo-
kwialnie, posprzątanie po pracy modułu.
Istotnym elementem jest licencja. Jak
wiadomo, twórcy jądra Linuksa są bardzo
wrażliwi na tym punkcie, więc podczas
pisania modułów wymagane jest określe-
nie licencji, na jakiej udostępniamy nasz
moduł. W naszym przypadku będzie to
oczywiście licencja GPL , dlatego dodaje-
my do kodu dodatkowe makro w nastę-
pującej postaci: MODULE_LICENSE(„GPL”); .
Na płycie CD/DVD
Na płycie CD/DVD znajdują
się pliki źródłowe napisanego
sterownika, jak również
wszystkie listingi.
Listing 1. Hello World! jako moduł jądra
Linuksa.
#define MODULE
#define __KERNEL__
#include <linux/module.h>
#include <linux/kernel.h>
MODULE_LICENSE ( „GPL” );
int init_module () {
O autorze
Autor zajmuje się tworzeniem
oprogramowania dla WIN32
i Linuksa. Zainteresowania: teoria
języków programowania oraz
dobra literatura. Kontakt
z autorem: autorzy@linux.com.pl.
printk ( „<module1> Witaj S
Świecie!!! \n );
return 0 ;
void cleanup_module ()
{ }
Początki są łatwe
Tworzenie większego oprogramowania,
działającego na poziomie jądra, to zadanie
60
maj 2004
P rogramowanie systemowe
439033947.027.png 439033947.028.png 439033947.029.png 439033947.030.png 439033947.001.png 439033947.002.png 439033947.003.png
tworzenie modułów jądra Linux
dla programistów
Rysunek 1. Wyniki polecenia lsmod
plik wykonywalny (bądź biblioteka
dynamiczna), czyli efekt pracy programu
konsolidującego, tzw. linkera, który łączy
poszczególne pliki obiektów (pliki o roz-
szerzeniu *.o ) w ostateczny plik binarny.
W przypadku modułu jądra sytuacja jest
odmienna. Wystarczają nam tylko same
pliki obiektowe – nie dokonujemy konso-
lidacji pliku wykonywalnego.
Kompilacja przykładu z Listingu 1
przedstawia się następująco:
jest, aby podać cel all , a w jego nagłów-
ku wymienić wszystkie pliki obiektowe.
Kompilacji dokona sam Make – użyje do
tego tylko opcji „-c” oraz dodatkowych
opcji podanych w zmiennej CFLAGS .
Plik makefile znajdujący się na płycie
CD/DVD jest nieco bogatszy, gdyż zawie-
ra dwa dodatkowe cele ładujące wszyst-
kie moduły do jądra (polecenie: make
load ) oraz usuwający moduły z pamięci
(polecenie: make unload ).
Brak tego makra spowoduje, że podczas
ładowania modułu (poleceniem insmod )
otrzymamy komunikat o niezgodności
licencji, chociaż będzie on pracował
poprawnie.
Jądro Linuksa w przypadku modu-
łów oferuje jeszcze dwa inne makra:
MODULE_AUTHOR , gdzie podajemy autora
modułu, oraz MODULE_DESCRIPTION , które-
go przeznaczeniem jest podanie krótkie-
go opisu modułu.
Kolejną różnicą jest brak typowych
funkcji bibliotecznych. Jak widać, nie włą-
czamy do naszego modułu pliku nagłów-
kowego stdio.h . Nie jest to problemem,
gdyż jądro oferuje kilka funkcji będą-
cych odpowiednikami typowych funkcji
bibliotecznych. Podstawowymi przykła-
dami są printk oraz sprintf . Jądro posia-
da również własne odpowiedniki takich
funkcji, jak memset czy strcpy . Nagłówki
tych funkcji zawiera plik string.h .
Bardzo ważne są także dwie definicje
preprocesora: #define MODULE oraz #define
__KERNEL__ . Ich definicja musi nastą-
pić przed włączeniem pozostałych plików
nagłówkowych. Definicje można także
zdefiniować opcją „-D” podczas wywoły-
wania polecenia kompilatora – gcc .
Zadanie naszego modułu jest try-
wialne – instrukcją printk (odpowied-
nikiem printf) wyświetlamy komunikat
tekstowy.
Po załadowaniu modułu możemy nie
zobaczyć na ekranie naszego komunikatu,
gdyż zostanie on przesłany do logu jądra,
a ten możemy przejrzeć wydając polece-
nie dmesg (lepiej dmesg | less – będziemy
mogli przeglądać log strona po stronie kla-
wiszami [ PageUp ] i [P ageDown ]). Niektóre
dystrybucje są jednak tak skonfigurowane,
iż komunikaty przekazane przez printk
kierowane na konsolę.
gcc -c module1.c -D__KERNEL S
-I/usr/src/linux/include
Deklaracja parametrów
jądra
Bardzo często do modułu w momencie
ładowania przekazywane są dodatkowe
parametry. np.:
Istotne jest wskazanie położenia plików
nagłówkowych jądra. Kilka informacji na
ten temat zawiera ramka Źródła jądra w
systemie . Po wykonaniu tego polecenia
otrzymamy plik module1.o , gotowy do
załadowania poleceniem insmod . Robimy
to w następujący sposób:
insmod ne io=0x260
Opis dodatkowych parametrów jest dość
prosty. Korzystamy z makra MODULE_PAR
o dwóch argumentach. W pierwszym
parametrze podajmy zmienną, gdzie
będzie przechowywana wartość, nato-
miast w drugim określamy typ parametru.
Nazwa tego parametru jest odczytywana
z nazwy zmiennej, więc trzeba w tym
miejscu podać nazwę znaczącą, która
będzie charakteryzować przeznaczenie
parametru. Jeśli to nie wystarczy, to za
insmod ./module1.o
Wskazanie, że chodzi o plik znajdujący się
w katalogu bieżącym - ./ - jest konieczne,
ponieważ nasz moduł nie został jeszcze
zainstalowany w domyślnym katalogu, w
którym system przechowuje moduły.
Załadowany moduł usuwamy pole-
ceniem:
rmmod module1
Źródła jądra w systemie
Do poprawnej i bezproblemowej kompila-
cji naszych modułów potrzebne są nam
źródła jądra, a dokładniej pliki nagłów-
kowe. Stało się tradycją, że kompletne
źródła jądra są umieszczane w katalogu
/usr/src/ (w skrypcie makeile z Listin-
gu 2 powołujemy się na ten katalog).
W wielu dystrybucjach pliki nagłówkowe
są jednak umieszczane w standardowym
katalogu /usr/include. Dość często są
to pliki pochodzące z innej wersji jądra
niż jest zainstalowana w systemie. Po
kompilacji modułów z innymi plikami
nagłówkowymi, podczas próby ładowa-
nia otrzymamy komunikat o niezgodności
wersji. Rozwiązanie jest bardzo proste.
Wystarczy skasować katalogi linux , asm
oraz scsi z /usr/include . Jeśli zależy nam
na obecności tych katalogów, to zamiast
kopiowania plików najlepiej utworzyć
odniesienia symboliczne. Znajdując się
w katalogu /usr/include , gdy tworzymy
dowiązanie linux , wydajemy polecenie ln
w następującej postaci:
ln -s /usr/src/linux/include/linux S
linux
W przypadku kilkunastu modułów najle-
piej przygotować odpowiedni skrypt dla
programu Make . Przykład takiego pliku,
kompilującego wszystkie moduły z tego
artykułu (ich kod źródłowy jak zawsze
znajduje się na płycie CD/DVD), przed-
stawia Listing 2.
Struktura skryptu makefile nie jest
skomplikowana. Definiujemy dokładnie
trzy zmienne: CFLAGS zawiera dodatko-
we opcje przeznaczone dla kompilatora,
CC to zmienna zawierająca polecenie kom-
pilatora, a zmienna OBJS określa wszystkie
nazwy plików obiektowych, które maja
zostać utworzenie przez skrypt. Oczywi-
ście muszą istnieć odpowiednie pliki źró-
dłowe.
Skrypty dla programu Make zazwy-
czaj posiadają wiele reguł, np. jak otrzy-
mać z pliku źródłowego plik obiektu.
Wiele podstawowych reguł jest wbu-
dowanych do Make’a , więc nie musimy
definiować elementarnych reguł kompila-
cji kodu źródłowego do modułu. Ważne
Kompilacja oraz ładowanie
modułu
Gdy tworzymy standardowe oprogramo-
wanie, to przystępując do kompilacji naj-
częściej oczekujemy, że wynikiem będzie
www.linux.com.pl
61
439033947.004.png 439033947.005.png 439033947.006.png 439033947.007.png 439033947.008.png
 
dla programistów
Listing 2. Skrypt makeile
odpowiedzialny za kompilację
przykładów
__pde=create_proc_read_entry(file_ S
proc_name, 0, NULL, procfile_read, NULL);
Jej wynikiem jest wskaźnik umieszczony
w naszej zmiennej. Pierwszy parametr
tej funkcji to nazwa pliku. Następnie
określamy tryb dostępu do pliku – zero
zapewni nam możliwość odczytywa-
nia zawartości pliku. Kolejny argument
to punkt umocowania naszego pliku
– wartość NULL oznacza katalog główny
systemu proc . Później podajemy proce-
durę odpowiedzialną za przygotowanie
danych. Ostatni argument to wskaźnik
void* – możemy poprzez ten argument
przekazać dowolne dane, jeśli zachodzi
taka potrzeba.
Gdy użytkownik spróbuje odczy-
tać wartość naszego pliku, to oczywi-
ście zostanie wywołana funkcji procfi-
le_read . Dysponuje ona szeregiem para-
metrów. Najważniejszy dla nas parametr
to page . Jak widać z listingu, fun-
kcją sprintf przepisujemy przyszłą
zawartość pliku do tej właśnie zmien-
nej.
KERNEL_DIR=/usr/src/linux
CFLAGS=-D__KERNEL -I$(KERNEL_DIR) S
-Wall
CC=gcc
OBJS=module1.o module2.o S
module3.o mod_cpu.o
all: $(OBJS)
clean:
rm -f $(OBJS)
Rysunek 2. Testowanie modułu z Listingu 4
i zarejestrować własny plik w systemie
proc . Czynności, które wykonuje nasz
kod, warto pokazać za pomocą prostego
schematu blokowego. Rysunek 4 przed-
stawia taki schemat. Różni się on tym od
typowego schematu, że zamiast wbloków
START i STOP mamy tu takie czynności,
jak załadowanie modułu oraz usunięcie
modułu.
Dotychczas nie wspominałem, w jaki
sposób możemy uzyskać informacje
o procesorze. Wykorzystamy do tego celu
instrukcję cpuid . Z jej pomocą możemy
zdobyć wiele informacji, jednak dla swo-
istej „politycznej poprawności”, na począ-
tek sprawdzimy, czy nasz system obsłu-
guje taką instrukcję. Jest ona obecna we
wszystkich wydanych procesorach zgod-
nych z Intelem na przestrzeni ostatnich
kilkunastu lat.
Osoby, które chcą dokładniej przyj-
rzeć się sposobom wykrywania proces-
ora, odsyłam do kodu źródłowego
jądra – plik arch/i386/kernel/setup.c .
W artykule zostały jednak zastoso-
wane inne funkcje, których kod
pochodzi z programu MPlayer 1.0.3 ,
a dokładniej z plików cpudetect.c i cpu-
detect.h .
Przykład typowej funkcji has_cpuid ,
która sprawdza obecność cpuid , zawiera
Listing 5. Cały test, jak widać, został zapi-
sany przy pomocy assemblera. Ogólna
idea testu jest bardzo prosta – wystarczy
spróbować zmienić 21 bit rejestru flag
( EFLAGS ). Jeśli możemy zmienić ten bit, to
oznacza to, że mamy dostęp do instruk-
cji cpuid .
pomocą makra MODULE_PARM_DESC istnieje
możliwość dodania opisu określonego
parametru.
Listing 3 zawiera kod źródłowy
modułu definiującego jeden argument
par1 typu string (wszystkie typy parame-
trów zostały zebrane w Tabeli 1).
Argumenty mogą posiadać wartości
domyślne. Są one przypisane w momen-
cie deklaracji zmiennej. W przykła-
dzie wartością domyślną jest tekst ”de-
fault value” . Zostanie on oczywiście
zastąpiony w momencie określenia war-
tości argumentu podczas ładowania
modułu:
Podczas usuwania modułu konieczną
operacją jest usunięcie wpisu z syste-
mu proc . Używamy w tym celu funkcji
remove_proc_entry . Pierwszy argument
to nazwa naszego pliku, a drugim jest
wskazanie na katalog rodzicielski. Pod-
czas wywoływania podaliśmy wartość
NULL , wskazując, że mamy na myśli kata-
log główny.
Gdy nie usuniemy naszego wpisu,
a dowolny program spróbuje odczytać
nasz plik, zakończy się to jego przerwa-
niem i zrzutem pamięci, czyli utworze-
niem ulubionego pliku wszystkich pro-
gramistów – core .
insmod ./module2 par1=”nowa wartość”
Nowy plik w katalogu / proc
Zadaniem naszego modułu jest dostar-
czenie informacji o procesorze. Natu-
ralnym rozwiązaniem jest umieszczenie
tych informacji w katalogu /proc . Listing
4 zawiera fragmenty modułu tworzące-
go przykładowy plik w systemie proc
– brakuje tylko pełnej definicji funkcji
proc_calc_metrics , ale powrócimy do
niej w dalszej części.
Wszystkie funkcje zawiązane z zarzą-
dzaniem katalogiem /proc znajdują się w
pliku proc_fs.h . Obejmują one tworze-
nie katalogów oraz plików do odczy-
tu i zapisu. Nas interesuje utworzenie
pliku (zawierającego tekst) wyłącznie do
odczytu.
Rejestrację nowego pliku wykonuje-
my w funkcji init_module , ale zanim to
zrobimy, należy utworzyć zmienną glo-
balną reprezentującą ten plik. Definiuje-
my ją w taki sposób:
Zadanie główne –
informacje o procesorze
Po tych wstępnych informacjach, przy-
szedł czas na realizację głównego zada-
nia. Posiadamy już wystarczającą ilość
informacji o tym, jak utworzyć moduł
Tabela 1. Typy parametrów przekazywanych do modułów
Oznaczenie
Deinicja
b
pojedynczy bajt (unsigned char)
h
krótka liczba całkowita (short int)
i
liczba całkowita (int)
l
liczba całkowita długa (long)
struct proc_dir_entry* __pde;
s
ciąg znaków
Rejestracja pliku to wywołanie tylko
jednej funkcji:
n1-n2[bhils]
tablica o co najmniej n1 elementach, jednak nie dłuższa niż n2
elementy
62
maj 2004
439033947.009.png 439033947.010.png 439033947.011.png 439033947.012.png 439033947.013.png 439033947.014.png
 
tworzenie modułów jądra Linux
dla programistów
Listing 3. Deklaracja nowego parametru
modułu
#define MODULE
#define __KERNEL__
#include <linux/module.h>
#include <linux/kernel.h>
MODULE_LICENSE ( „GPL” );
char * par1 = ”default value” ;
MODULE_PARM ( par1, „s” );
int init_module () {
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ż-
ności od stanu rejestru eax , wykonuje
instrukcję cpuid , a otrzymane informa-
cje umieszcza w tablicy wskazanej przez
argument p naszej funkcji.
Rysunek 4. Schemat działania modułu
procesora. Należy do argumentu eax
załadować zero, a następnie wywo-
łać instrukcję do_cpuid . W podanej
tablicy zostaną umieszczone fragmen-
ty (po cztery litery) nazwy producenta.
W przypadku Intela będzie to napis
„GenuineIntel” , a dla AMD – „Authen-
ticAMD” . Procesory pozostałych firm,
takich jak choćby Transmeta, także
zawierają swoją sygnaturę, więc można
z pomocą instrukcji cpuid ustalić pro-
ducenta.
Kod wpisujący do zmiennej page
nazwę producenta oraz tzw. maksymal-
ną wartość poziomu, dla której genero-
wane są odpowiedzi instrukcji cpuid , jest
następujący:
Ściąganie informacji
Dysponując funkcjami opisanymi
w poprzednim punkcie, jesteśmy gotowi
do odczytania informacji o procesorze.
Na Listingu 6 znajdują się fragmenty
głównej funkcji procfile_read .
Wszystkie informacje o procesorze
są otrzymywane w wywołaniu funk-
cji procfile_read , gdy ktoś odczyta
nasz plik. Oprócz wykorzystywania
has_cpuid oraz do_cpuid , korzystamy
z dwóch dodatkowych funkcji. O jednej
z nich ( proc_calc_metrics ) już wspomi-
nałem, a druga ( proc_sprintf ) to odpo-
wiednik sprintf , działający poprawnie
w środowisku jądra. Funkcje te są nam
potrzebne, aby poprawnie pisać do
zmiennej page .
Jako pierwszą cenną informację
spróbujmy uzyskać nazwę producenta
Listing 4. Utworzenie nowego pliku w systemie plików /proc
#define MODULE
#define __KERNEL__
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
MODULE_LICENSE ( „GPL” );
char * file_proc_name = ”module3_info” ;
char * par1 = ”default value” ;
MODULE_PARM ( par1, „s” );
struct proc_dir_entry * __pde ;
int proc_calc_metrics ( char * page, char ** start, off_t off, int count, int * eof,
int len )
{...}
int procfile_read ( char * page, char ** start, off_t off, int count, int * eof, void
* data ) {
Rysunek 3. Moduł mod_cpu w akcji
– podstawowe informacje uzyskane przez
cpuid
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 () {
remove_proc_entry ( file_proc_name, NULL );
}
www.linux.com.pl
63
printk ( „<module2> Parametr S
par1=[%s] \n ”, par1 );
return 0 ;
}
439033947.015.png 439033947.016.png 439033947.017.png 439033947.018.png 439033947.019.png 439033947.020.png 439033947.021.png 439033947.022.png 439033947.023.png
 
dla programistów
Listing 5. Test, czy procesor udostępnia
instrukcję cpuid
Listing 6. Fragmenty funkcji procfile_read , która przygotowuje dane o procesorze
int procfile_read ( char * page, char ** start, off_t off, int count, int * eof, void
* data ) {
int has_cpuid(){
int a, c;
int len = 0 ;
unsigned int regs [ 4 ], regs2 [ 4 ];
proc_sprintf ( page, & off, & len, ”CPUID info v1.0 \n );
if ( has_cpuid ()) {
proc_sprintf ( page, & off, & len, ”instrukcja cpuid jest dostępna \n );
do_cpuid ( 0x00000000, regs );
...
if ( regs [ 0 ]>= 0x00000001 ) {
unsigned cl_size ;
do_cpuid ( 0x00000001, regs2 );
cpuType =( regs2 [ 0 ] >> 8 )& 0xf ;
if ( cpuType == 0xf ) cpuType = 8 +(( regs2 [ 0 ]>> 20 )& 255 );
cpuStepping = regs2 [ 0 ] & 0xf ;
hasTSC = ( regs2 [ 3 ] & ( 1 << 8 )) >> 8 ;
hasMMX = ( regs2 [ 3 ] & ( 1 << 23 )) >> 23 ;
...
cl_size = (( regs2 [ 1 ] >> 8 ) & 0xFF )* 8 ;
if ( cl_size ) {
cl_size = cl_size ;
proc_sprintf ( page, & off, & len, ”Wielkość linijki cache’u (w bajtach):
%u \n ”, cl_size );
__asm__ __volatile__ (
„pushf\n\t”
„popl %0\n\t”
„movl %0, %1\n\t”
„xorl $0x200000, %0\n\t”
„push %0\n\t”
„popf\n\t”
„pushf\n\t”
„popl %0\n\t”
: „=a” (a), „=c” (c)
:
: „cc”
);
return (a!=c);
}
void do_cpuid( unsigned int ax,
unsigned int *p){
__asm __volatile__ (
„movl %%ebx, %%esi\n\t”
„cpuid\n\t”
„xchgl %%ebx, %%esi”
: „=a” (p[0]), „=S” (p[1]),
„=c” (p[2]),
„=d” (p[3])
}
...
}
do_cpuid ( 0x80000000, regs );
if ( regs [ 0 ]>= 0x80000001 ) {
proc_sprintf ( page, & off, & len, ”rozszerzony poziom cpuid: %d \n ”, S
regs [ 0 ]& 0x7FFFFFFF );
do_cpuid ( 0x80000001, regs2 );
hasMMX |= ( regs2 [ 3 ] & ( 1 << 23 )) >> 23 ;
...
}
if ( regs [ 0 ]>= 0x80000006 ) {
do_cpuid ( 0x80000006, regs2 );
proc_sprintf ( page, & off, & len, ”rozszerzone informacje o cache’u %d \n ”, S
regs2 [ 2 ]& 0x7FFFFFFF );
cl_size = regs2 [ 2 ] & 0xFF ;
proc_sprintf ( page, & off, & len, ”Wielkosc linijki cache’u (w bajtach): S
%u \n ”, cl_size );
: „0” (ax));
}
unsigned char r[4];
do_cpuid(0x00000000, r);
proc_sprintf(page,&off,&len,
Gdy chcemy uzyskać dalsze informacje,
jest to uzależnione od maksymalnej war-
tości umieszczonej w tablicy r pod pierw-
szym (zerowym) elementem. W naszym
module wyświetlamy informacje o tym,
czy są dostępne dodatkowe instrukcje
typu MMX czy SSE. Informacje te uzy-
skamy wywołując do_cpuid w następu-
jący sposób:
}
proc_sprintf ( page, & off, & len, ”Zestawy instrukcji: \n MMX ... \n ”,
hasMMX, ... ,has3DNowExt );
}
else {
proc_sprintf ( page, & off, & len, ”instrukcja cpuid nie jest dostępna \n );
}
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-
cje, jaki typ instrukcji jest obsługiwany.
W naszym przypadku zawartość reje-
stru edx to ostatni element tablicy.
Sprawdzenie, jakie rodzaje instrukcji są
dostępne, to odpowiednie manipulowa-
nie bitami.
Załóżmy, że chcemy sprawdzić, czy
procesor oferuje nam dostęp do instruk-
cji SSE. Informacje o dostępności SSE
zawiera bit o numerze 25. Linijka kodu,
która wpisze do zmiennej hasSSE odpo-
wiednie wartości: jeden, jeśli instruk-
cje SSE są dostępne, a zero, jeśli nie, jest
następująca:
jest to zawartość rejestru edx ). Gdy bit
będzie ustawiony, to wynikiem ope-
racji bitowego „i” będzie oczywiście
jedynka na 25 pozycji. Ponieważ my
chcemy uzyskać normalną liczbę, zero
bądź jeden, wystarczy, jeśli z powro-
tem przesuniemy nasz bit o 25 pozycji
w lewo na początek. W ten sposób
uzyskujemy informację, czy mamy
dostęp do instrukcji SSE. W analogiczny
sposób sprawdzamy, czy mamy dostęp
do instrukcji SSE2 – sprawdzamy 26
hasSSE = (r[3] & (1 << 25 )) >> 25;
Aby nie kodować mozolnie liczby
o ustawionym 25 bicie, wykorzystujemy
przesuniecie bitowe w lewo. Przesu-
wamy jedynkę. Następnie wykonujemy
bitową operację „i” na trzecim elemen-
cie naszej tablicy (dla przypomnienia
64
maj 2004
439033947.024.png 439033947.025.png 439033947.026.png
 
Zgłoś jeśli naruszono regulamin