poniedziałek, 2 września 2013

Programowanie sterowników pod Linux cz.5 - odczyt i zapis

Przyjrzyjmy się deklaracjom funkcji:

  • ssize_t read(struct file *filp, char __user *buff, size_t count, loff_t *f_pos)
  • ssize_t write(struct file *filp, char __user *buff, size_t count, loff_t *f_pos)

Argumenty:

  • struct file *filp - wskaźnik na plik, nie mylić z FILE który występuje w standardowej bibliotece C a nigdy w kodzie kernela
  • char __user *buff - wskaźnik na bufor przechowujący dane, należy pamiętać, że jest to wskaźnik z poziomu user-space, z tego powodu nie możemy go zwyczajnie wyłuskać w naszym module, który wykonywany jest w kernel-space
  • size_t count - rozmiar danych do transferu
  • loff_t *f_pos - aktualna pozycja w pliku

Jak zostało wspomniane wyżej, wskaźnika buff nie możemy wyłuskać. Tutaj z pomocą przychodzą nam funkcje zdefinowane w nagłówku <linux/uaccess.h>

  • unsigned long copy_to_user(void __user *to, const void *from, unsigned long count) - kopiuje dane z kernel-space do user-space
  • unsigned long copy_from_user(void *to, const void __user *from, unsigned long count) - kopiuje dane z user-space do kernel-space

Argumenty:

  • to - wskaźnik na adres docelowy
  • from - wskaźnik na adres źródłowy
  • count - rozmiar  danych do transferu w bajtach

Obie funkcje zwracają ilość bajtów, których nie udało się skopiować. W przypadku pełnego powodzenia zwracana jest wartość 0. Zaimplementujmy więc mechanizm zapisu i odczytu.

static ssize_t char_read(struct file *filp, char __user *buff, size_t count, loff_t *f_pos)
{
   static const ssize_t size = sizeof(device.array);
   printk("char_read()\n");

   if ((*f_pos) >= size)
      return 0;

   if (copy_to_user(buff,&device.array,size) != 0)
      return -EFAULT;

   (*f_pos) = size;
   return size;
}

Zmienna ssize_t size będzie przechowywać rozmiar tablicy z naszego wirtualnego urządzenia. Na początku sprawdzamy czy pozycja w pliku nie wykroczyła poza zakres. Jeśli tak, to kończymy odczytywanie. Następnie kopiujemy zawartość naszej tablicy do bufora znajdującego się w user-space. Ustawiamy odpowiednią pozycję pliku i zwracamy ilość skopiowanych bajtów.

static ssize_t char_write(struct file *filp, const char __user *buff, size_t count, loff_t *f_pos)
{
   printk("char_write()\n");

   if (copy_from_user(&device.array,buff,count) != 0)
       return -EFAULT;

   printk("Zapisano %s\n",device.array);
   return count;
}

Podczas zapisu korzystamy z funkcji copy_from_user, ponieważ kopiujemy dane z przestrzeni użytkownika do kernela. Zwracamy ilość zapisanych bajtów.

Z tak zaimplementowanymi funkcjami polecam sprawdzać działanie za pomocą prostego skryptu, np.




Po uruchomieniu skryptu:



sobota, 31 sierpnia 2013

Programowanie sterowników pod Linux cz.4 - device nodes

Teraz musimy nieco porozmawiać na temat /dev oraz /proc/devices.

 Katalog /dev zawiera pliki urządzeń (device nodes). Przykładowo:

  • /dev/hda1 - plik odpowiedzialny za pierwszą partycję dysku twardego
  • /dev/console - plik odpowiedzialny za klawiaturę
  • /dev/cdrom - plik odpowiedzialny za napęd CDROM

Za pomocą device nodes aplikacja może porozumiewać się ze sterownikiem urządzenia.

Z kolei w pliku /proc/devices znajduje się lista sterowników urządzeń, które są akutalnie skonfigurowane i działające.

Obecnie jesteśmy na etapie gdzie nasze urządzenie jest widoczne pod odpaleniu sterownika w /proc/devices, jednak nie jest widoczne w katalogu /dev/. Zatem póki co nie możemy wykorzystać sterownika w żadnej aplikacji pisanej z poziomu user-space, ponieważ brakuje elementu pośredniczącego między aplikacją a samym sterownikiem. 

Wpisy w /dev/ możemy dodawać ręcznie za pomocą polecenia:

mknod /dev/"nazwa_pliku_urzadzenia" c "numer major" "numer minor"

np. mknod /dev/char_device c 250 0


Plik urządzenia powinien być widoczny i gotowy do użycia. Pamiętajcie, aby użyć takie numery major i minor, które zostały przydzielone wcześniej do urządzenia. Używanie mknod za każdym razem byłoby dość upierdliwe, zajmiemy się więc tym jak wykonać automatyczne tworzenie device node'a dla naszego urządzenia.

Użyjemy do tego:
  • struct class - klasa urządzenia
  • class_create(THIS_MODULE, "device_class_name") - funkcja tworząca klasę urządzenia
  • device_create(struct class *cl, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...) - funkcja tworząca wpis w /dev/, gdzie:
  • parent - wskaźnik na rodzica urządzenia
  • devt - struktura zawięrająca major i minor
  • drvdata - dodatkowe dane
  • fmt - nazwa pliku urządzenia, która będzie widnieć jako wpis w /dev/

Dodajemy nagłówek <linux/device.h> oraz makro MODULE_LICENSE("GPL") i tworzymy device node:

struct class *cl;

if ((cl = class_create(THIS_MODULE, "char_driver")) == NULL)
{
   unregister_chrdev_region(devNum,1);
   return -1;
}

if ((device_create(cl, NULL, devNum, NULL, "my_device")) == NULL)
{
   class_destroy(cl);
   unregister_chrdev_region(devNum,1);
   return -1;
}

Do funkcji unload dodajemy:

device_destroy(cl, devNum);
class_destroy(cl);

Przyszedł czas, aby porozmawiać z naszym sterownikiem. Proponuję wysłać coś do sterownika i zobaczyć co się stanie. Wpisujemy w konsolę:



Następnie korzystamy z dmesg i naszym oczom powinno ukazać się:


W momencie gdy przekierowujemy wyjście z konsoli na nasze urządzenie wykonywana jest zdefiniowana przez nas operacja zapisu, czuli funkcja char_write(), na chwilę obecną wypisuje ona jedynie swoją nazwę. Przed i po wywołaniu funkcji char_write() zostały wywołane char_open() - otwarcie urządzenia, oraz char_release - zamknięcie urządzenia. W kolejnej części dopiszemy implementacje funkcji, aby działały zgodnie ze swoim rzeczywistym przeznaczeniem.

Programowanie sterowników pod Linux cz.3 - operacje

Naszym kolejnym krokiem będzie zdefiniowanie operacji które można wykonywać za pomocą sterownika. W tym celu posłużymy się strukturą file_operation zdefiniowaną w nagłówku <linux/fs.h>. Struktura ta składa się zestawu wskaźników na funkcje, które mamy zamiar zaimplementować. W tej części zajmiemy się implementacją 4 funkcji:

  • read()
  • write()
  • open()
  • release()

Dodajmy zatem definicje tych funkcji do kodu z poprzednich części:

static ssize_t char_read(struct file *filp, char __user *buff, size_t count, loff_t *f_pos)
{
   printk("char_read()\n");
   return 0;
}

static ssize_t char_write(struct file *filp, const char __user *buff, size_t count, loff_t *f_pos)
{
   printk("char_write()\n");
   return 0;
}

static int char_open(struct inode *inode, struct file *filp)
{
   printk("char_open()\n");
   return 0;
}

static int char_release(struct inode *inode, struct file *filp)
{
   printk("char_release()\n");
   return 0;
}

Następnie należy zdefiniować strukturę:

file_operation my_operations = 
{
   .owner = THIS_MODULE,
   .open = char_open,
   .release = char_release,
   .read = char_read,
   .write = char_write
};

W zasadzie już nieco wcześniej ponienem był wpomnieć o strukturze cdev. Jest to struktura która będzie instancją naszego urządzenia znakowego.

struct cdev my_cdev;
cdev_init(&my_cdev, &my_operations);

Powyższy kod alokuje instancję urządzenia oraz powiązuje ze zdefinowanymi operacjami. Teraz za możemy poinformować kernel o naszej strukturze za pomocą funkcji:

int cdev_add(struct cdev *cdev, dev_t num, unsigned int count);

  • cdev - struktura dla której chcemy ustawić numery num
  • num - struktra zawierająca numer major oraz minor
  • ilość urządzeń które chcemy powiązać z cdev

Dodajmy więc następujący kod:

if (cdev_add(&my_cdev, devNum, 1) < 0)
{
   printk("Problem z dodaniem cdev\n");
   return -1;
}

Pamiętajmy aby w funkcji unload() zwolnić zasoby poprzez:

cdev_del(&my_cdev);

W ten sposób stworzyliśmy szablon pod sterownik dla urządzenia znakowego.

poniedziałek, 26 sierpnia 2013

Programowanie sterowników pod Linux cz.2 - numery urządzenia

W systemie Linux urządzenia rozpoznawane są za pomocą przydzielanych im numerów zwanych major i minor.

Za pomocą polecenia ls -l /dev/ | grep "^c" wyświetlimy listę aktywtnych urządzeń znakowych wraz z ich numerami.




W nagłówku <linux/types.h> zdefiniowana jest struktura dev_t, która zawiera oba te numery. Z kolei nagłówek <linux/kdev_t.h> udostępnia nam makra służące do wykonywania operacji:
  • MAJOR(dev_t dev) - zwraca wartość major
  • MINOR(dev_t dev) - zwraca wartośc minor
  • MKDEV(int major, int minor) - wstawia wartości major i minor do struktury dev_t
Aby móc używać naszego urządzenia w systemie musimy je zarejestrować. Służy do tego funkcja

int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int cnt, char *name)

  • dev - struktura do której zostanie dobrana wartość major
  • firstminor - początkowa pozycja minor
  • cnt - ilość plików urządzeń do zarejestrowania
  • name - nazwa urządzenia
Funkcja dobiera wolną liczbę major i ustawia ją w strukturze dev, następnie rejestruje określoną przez cnt ilość urządzeń rozpoczynając od pary <major, firstminor>.

Należy także pamięć o deaktywacji sterownika i zwolnieniu zajmowanych numerów za pomocą funkcji:

unregister_chrdev_region(dev_t dev,unsigned int cnt);


Zmodyfikujmy więc kod z poprzedniej części:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>

struct virtual_device{
   char array[100]; 
};

static dev_t devNum; // struktura na liczby major i minor

static int __init load(void)

   printk("Aktywacja sterownika\n");
   if (alloc_chrdev_region(&devNum,0,1,"MyDevice") < 0)
   {
      return -1;
   }
   printk("Sterownik aktywowany. Major: %d Minor: %d",MAJOR(devNum),MINOR(devNum));
   return 0;


static void __exit unload(void) 

   printk("Deaktywacja sterownika\n"); 
   unregister_chrdev_region(devNum,1);


module_init(load);
module_exit(unload);

Po skompilowaniu i uruchomieniu modułu powinien pojawić się komunikat podający jakie liczby uzyskało nasze urządzenie. Dodatkowo aby sprawdzić czy wszystko przebiegło jak należy używamy komendy cat /proc/devices i szukamy numeru oraz nazwy naszego urządzenia.



niedziela, 25 sierpnia 2013

[C/C++] Operator sizeof() cz. 2

W poprzedniej części mówiliśmy o rozmiarach struktur. Istnieje możliwość, aby wyliczyć rozmiar struktury bez korzystania z operatora sizeof():

#include <stdio.h>

struct S{        
int a;
short b;
char c;
int d;
};

int main(int argc,char *argv[])
{
struct S arr[2];
 int size = (char*)&arr[1] - (char*)&arr[0];
printf("Sizeof: %d Bez sizeof: %d\n",sizeof(struct S),size);
return 0;
}

Tworzymy 2-elementową tablicę struktur S. Elementy tablicy zaalokowane są w ciągłym obszarze pamięci, zatem różnica pomiędzy początkowym adresem elementu o indeksie 1 i elementu o indeksie 0 zwróci faktyczny rozmiar struktury S;

niedziela, 18 sierpnia 2013

[C/C++] operator sizeof() cz.1

Operator sizeof() - do czego służy? Na pewno większość wie, że zwraca on rozmiar danego typu w bajtach. Co ciekawe często możemy otrzymać różne wyniki. Skompilujmy poniższy kod:

#include <stdio.h>

int main(void)
{
  printf("%d\n", sizeof(char));
  printf("%d\n", sizeof(short));
  printf("%d\n", sizeof(int));
  printf("%d\n", sizeof(long));
  printf("%d\n", sizeof(long long)); // c99
  printf("%d\n", sizeof(void*));
  return 0;


U mnie wyniki są następujące: 1,2,4,8,8. Możliwe że uzyskaliście inne. Od czego zależy więc rozmiar danych? Od architektur komputerów na których program został uruchomiony, bądź systemu operacyjnego. Teoretycznie tak, jednak nie zapominajmy o tym że rozmiar danych zależy przede wszystkim od kompilatora. Przykładowo, jeśli mamy 64-bitowy procesor, możemy używać 16-bitowego kompilatora, w którym to int czy void* będzie miał rozmiar właśnie 16 bitów.

Ogólnie powinna zachodzić następująca zależność:


  • sizeof(char) ≤ sizeof(short) ≤ sizeof(int) ≤ sizeof(long) ≤ sizeof(long long)

Istnieją jednak minimalne progi rozmiarowe ustalane na podstawie minimalnego zakresu:

  • sizeof(char) - minimum 1 bajt
  • sizeof(short) - minimum 2 bajty
  • sizeof(int) - minimum 2 bajty
  • sizeof(long) - miniumum 4 bajty
  • sizeof(long long) - minimum 8 bajtów (dostępne od standardu c99)

Jeśli chodzi o sizeof(void*) rozmiar zależny jest od szerokości słowa. Czyli dla kompilatora:

  • 16-bitowego - 2 bajty
  • 32-bitowego - 4 bajty
  • 64-bitowego - 8 bajtów

A jak sprawa wygląda ze strukturami? Interesująco. Zaczniemy powoli. Wszystko omawiać będę biorąc pod uwagę rozmiary danych, które występują u mnie (char: 1, short: 2, int: 4 - GCC 64bit x86).

struct A{
  char znak;
};

Tutaj sizeof(struct A) zwróci 1 - nic zaskakującego.

struct B{};

Nie wszystkie kompilatory zezwolą nam na zdefiniowanie takiej struktury (GCC pozwoli), jednak jeśli pozwolą to sizeof(struct B) zwróci 0 jeśli skompilujemy plik z rozszerzeniem *.c, natomiast gdy plik będzie miał rozszerzenie *.cpp zwróci 1.

struct C{
  char znak;    // 1 bajt
                // 1 bajt wyrównawczy - wyrównanie do 2
  short liczba; // 2 bajty
};

Wydawałoby się, że skoro sizeof(char) == 1, a sizeof(short) == 2, sizof(struct C) == 3. Sprawdźcie więc wyniki. Okazuje się, że zostaje nam zwrócony rozmiar większy niż suma rozmiarów danych użytych w strukturze. Wszystkiemu winny jest kompilator, który dodaje odpowiednią ilość bajtów w celu wyrównania danych w pamięci (data structure alignment), zapewnia to szybszy odczyt danych przez procesor. Ile zostaje dodanych bitów wyrównawczych? Dokładnie tyle by uzupełnić pamięć tak, aby wyrównać ją do wielokrotności liczby 2, w zależności od rozmiaru kolejnego pola.

Spójrzmy na to z tej perspektywy:

struct D{
  char znak;  // 1 bajt
              // 3 bajty wyrównawcze - wyrównanie do 4
  int liczba; // 4 bajty
  char znak2; // 1 bajt
              // 3 bajty wyrównawcze - wyrównanie do 4
};

Łącznie 12 bajtów, sporo jak na 2 chary i inta. Pamiętajmy także, że ostatnie pole struktury jest zawsze wyrównywane tak, aby całkowity rozmiar struktury był wielokrotnością najszerszego pola w strukturze.

struct E{
  char znak;  // 1 bajt
  char znak2; // 1 bajt
              // 2 bajty wyrównawcze - wyrównanie do 4
  int liczba; // 4 bajty
};

Tutaj mamy łącznie 8 bajtów. Przy utworzeniu 1000 obiektów uzyskalibyśmy dodatkowo kilka kB. Widać więc, że warto zwracać uwagę na kolejność zmiennych w strukturze np. przy programowaniu mikroprocesorów, gdzie pamięć jest często bardzo ograniczona. 

poniedziałek, 12 sierpnia 2013

Start w świat projektów Open Source

Udział w rozwijaniu projektów Open Source to to wspaniała sprawa. Możemy zająć się projektem, który nas interesuje, zdobywać nowe umiejętności i doświadczanie pod okiem profesjonalistów, poznać ciekawych ludzi, a nawet zarobić niemałe pieniądze. Dodatkowo wpis w CV o przyczynianiu się do ruchu Open Source  na pewno zadziała na naszą korzyść. Wiele osób nie wie jednak od czego zacząć i dość szybko rezygnuje.

Wybór projektu


Jak wybrać odpowiedni projekt dla siebie? Przede wszystkim powinniśmy wybrać zgodnie ze swoimi zainteresowaniami oraz umiejętnościami. Niezależnie w jakim języku programujecie, na pewno znajdziecie dla siebie coś odpowiedniego. Projekty Open Source znajdziecie tutaj:


Pamiętajcie, aby nie wybrać projektu który okaże się zbyt trudny, może to was zniechęcić. Projekty określane jako beginner-friendly to między innymi:

  • Drupal
  • Python
  • LibreOffice
  • PostgreSQL
  • wxWidgets
  • Jenkins

Komunikacja


Aby działać w środowisku open source musimy nauczyć się komunikować. Wybraliśmy nasz projekt - jeśli ma on swoją stronę to na nią wchodzimy, szukamy informacji o listach mailingowych i zapisujemy się na listę mailingową dla developerów. Na stronach projektów często są także linki do źródeł, tutoriale dla początkujących oraz rozbudowane FAQ'i. Jeżeli nie znajdziemy strony projektu to najlepiej skontaktować się z główną osobą odpowiedzialną za projekt, ładnie się przywitać i dać znać że chcemy wziąć udział w rozwijaniu danego oprogramowania.

System operacyjny i narzędzia


Projekty Open Source można rozwijać zarówno pod Windowsem i Linuxem, chociaż wiele z nich jest typowo Linuxowych. Tak czy inaczej uważam, że Linux jest tutaj najlepszeym wyborem (jeśli chodzi o rozwój Open Source) i gorąco go polecam. Warto także znać lub zapoznać się z narzędziami, które będziemy używać, w zależności od projektu:

  • GCC - kompilator C/C++/Objective-C/Fortran/Ada
  • GDB - debugger, DDD - graficzna wersja GDB, Valgrind - bada wycieki pamięci
  • Makefile, Scons
  • Git, Svn, Mercurial - systemy kontroli wersji

Jeśli chodzi o IDE, to w zasadzie jest często zbędne, można używać zwykłych edytorów tekstu z pluginami do np. autouzupełniania składni, najpopularniejsze to: Gedit, Kate, Vim. Ja osobiście IDE lubię i czasami używam. Warto zwrócić uwagę na:

  • Eclipse
  • Code::Blocks
  • Netbeans
  • Kdevelop
  • QtCreator
  • Anjuta
  • Geany

Źródła i pierwsza kompilacja


Nadszedł czas aby sięgnąć po źródła, link znajdziemy najczęściej na stronie projektu, oczywiście szukamy źródeł wersji unstable (developerskiej). Teraz wypadało by nasze źródła skompilować, jeśli sprawia to komuś trudność, to należy szukać informacji na stronie projektu. Podstawową zasadą jest jednak, aby na początek doinstalować wszystkie biblioteki potrzebne do kompilacji naszych źródeł.

Czas na kodowanie


Skomilowaliśmy źródła - aplikacja działa. Nareszcie możemy zabrać się za programowanie. Tylko co konkretnie robić? Na początek - szukamy bugtrackera i zabieramy się za poprawianie bugów, pozwoli nam to oswoić się z projektem i nabrać podstawowego doświadczenia. Przy pierwszych próbach pisania kodu naszym największym sprzymierzeńcem będzie dokumentacja, która jest dostępna w źródłach lub na stronie projektu - należy ją czytać i z niej korzystać. W przypadku gdy projekt nie ma bugtrackera, ale mamy jakąś wizję bądź pomysł na pewne zmiany/rozbudowę aplikacji, obowiązkowo przed przystąpieniem do kodowania trzeba omówić nasze pomysły z innymi developerami na liście mailingowej, szkoda by było gdyby nasz wysiłek poszedł na marne. Jeśli jednak nie wiemy czym się zająć, również odsyłam na listę mailingową - ktoś tam na pewno nad czymś aktualnie pracuje i chętnie skorzysta z pomocy.


sobota, 3 sierpnia 2013

Programowanie sterowników pod Linux cz.1 - wprowadzenie

Zaczynamy. Na początek seria wpisów dotycząca programowania sterowników pod Linux. Poza wpisami wszystkim zainteresowanym proponuję literaturę którą sam przerabiam:


Aby programować sterowniki pod Linuxa trzeba znać język C przynajmniej na podstawowym poziomie (w innym przypadku polecam http://cm.bell-labs.com/cm/cs/cbook/). Zawsze trzeba od czegoś zacząć, my zaczniemy od ściągnięcia aktualnych źródeł linuxowego kernela dostępnych tutaj: https://www.kernel.org/. Ściągamy najnowszą stabilną wersję.


Po co nam źródła kernela?

  • Aby programować sterowniki musimy nieco zagłębić się w działanie systemu Linux.
  • W katalogu /drivers/ mamy doskonałe przykłady sterowników

Przystąpmy więc do omówienia samego programowania sterownika. Sterownik to warstwa pośrednicząca pomiędzy urządzeniem a systemem operacyjnym. Aby zapewnić uniwersalność kursu, będziemy pisać kod dla wirtualnego urządzenia, za które posłuży nam 100-elementowa tablica znakowa.

W systemie Linux mamy do czynienia z trzema typami sterowników:

  • znakowe (mysz, klawiatura)
  • blokowe (dysk twardy, pendrive)
  • sieciowe (moduł wifi)

Nasz sterownik będzie sterownikiem znakowym, ponieważ zajmiemy się jedynie kwestiami związanymi z transmisją danych z/do urządzenia. Sterownik ma za zadanie porozumiewać się z urządzeniem, konkretnie podejmować operacje związane z:

  • podłączeniem urządzenia (czynności inicjalizacyne, alokacja potrzebnych zasobów)
  • odłączeniem urządzenia(dealokacja zasobów) 
  • zapisywaniem danych do urządzenia
  • odczytywaniem danych z urządzenia

Są to cztery podstawowe operacje za które odpowiadją funkcje open(), release(), write() oraz read(), a do ich realizacji w zupełności wystarczy tablica charów. Nasze urządzenie definiujemy więc w następujący sposób:

struct device{
   char array[100];
};

Warto także wspomnieć o pewnych różnicach w programowaniu kernela. Najważniejsza zasada - nie korzystamy ze standardowych bibliotek języka C, ani żadnych innych bibliotek, które nie są udostępniane bezpośrednio przez kernel (z małymi wyjątkami). Pliki nagłówkowe, które będą nam potrzebne na początek to:

  • linux/kernel.h 
  • linux/module.h 
  • linux/fs.h // operacje na urządzeniu
  • linux/uaccess.h // copy_to_user(), copy_from_user()
  • linux/cdev.h // biblioteka dla sterownika znakowego

Moduły kernela muszą zawierać dwa podstawowe makra:

  • module_init()
  • module_exit()

Jako argument module_init()  podajemy nazwę funkcji która ma zostać odpalona przy inicjalizacji modułu, natomiast dla module_exit() funkcję wykonywaną w momencie jego usunięcia.

Wykorzystamy także dwa makra:

  • __init - dla funkcji wykorzystywanej jedynie przy inicjalizacji
  • __exit - dla funkcji wykorzystywanej jedynie przy deaktywacji

Zbierzmy to w całość:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccsess.h>
#include <linux/cdev.h>
struct virtual_device{
   char array[100]; 
};

static int __init load(void)

   printk("Aktywacja sterownika\n");


static void __exit unload(void) 

   printk("Deaktywacja sterownika\n"); 


module_init(load);
module_exit(unload);

Funkcja printk() jest odpowiednikiem printf() w kernelu, w działaniu jest bardzo podobna. Funkcje load() i unload() przekazujemy do module_init() oraz module_exit().

Nadszedł czas aby wypróbować nasz moduł. W katalogu z kodem modułu stwórzmy plik Makefile zakładając, że kod modułu znajduje się w pliku driver.c (w przypadku innej nazwy należy zmodyfikować poniższy kod).

obj-m := driver.o

all:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

Następnie za pomocą polecenia make kompilujemy nasz moduł. Jeśli wszystko przebiegło pomyślnie w katalogu zostało dodane kilka plików, między innymi plik driver.ko, który jest naszym modułem. Mając gotowy moduł korzystamy z poleceń:

insmod driver.ko - ładujemy nasz moduł
lsmod - wyświetlamy wszystkie aktywne moduły (powinien być widoczny moduł driver)
dmesg - wyświetlamy bufor warstwy kernela
rmmod driver.ko - usuwamy moduł
dmesg - ponownie wyświetlamy bufor warstwy kernelaj

Jeśli wszystko przebiegło pomyślnie w terminalu powinny być widoczne komunikaty: