2004.05 Tworzenie modułów ...
|
2004.05 Tworzenie modułów jądra – ten pierwszy raz [Programowanie], Informatyka, ►Artykuły, Linux+PL, ...
[ Pobierz całość w formacie PDF ] 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 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 są 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 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 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 ; } 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
[ Pobierz całość w formacie PDF ] zanotowane.pldoc.pisz.plpdf.pisz.pllily-lou.xlx.pl
|
|
Linki |
: Strona pocz±tkowa | : 2015 05 FLUGZEUG CLASSIC, FLUGZEUG CLASSIC - 2015 | : 2001.05 Szkoła konstruktorów, Elektronika, Szkoła konstruktorów, Szkola konstruktorow | : 2.05 Ancient Aliens - Aliens and the Third Reich, Ancient Aliens | : 2015 05 CLAUSEWITZ, 2015 CLAUSEWITZ | : 2012 AON - Bibliografia publikacji pracowników w 2011r, 002-05 WOJSKO POLSKIE OD 01.01.1990, AON - Bibliografia publikacji pracowników | : 2006 AON - Bibliografia publikacji pracowników w 2004r, 002-05 WOJSKO POLSKIE OD 01.01.1990, AON - Bibliografia publikacji pracowników | : 2009 AON - Bibliografia publikacji pracowników w 2007r, 002-05 WOJSKO POLSKIE OD 01.01.1990, AON - Bibliografia publikacji pracowników | : 2010 AON - Bibliografia publikacji pracowników w 2008r, 002-05 WOJSKO POLSKIE OD 01.01.1990, AON - Bibliografia publikacji pracowników | : 2011 AON - Bibliografia publikacji pracowników w 2010r, 002-05 WOJSKO POLSKIE OD 01.01.1990, AON - Bibliografia publikacji pracowników | : 2004 2 IPN - Pamięć i Sprawiedliwosc, IPN - PAMIĘĆ I SPRAWIEDLIWOŚĆ |
zanotowane.pldoc.pisz.plpdf.pisz.plrafalsal.opx.pl
. : : . |
|