wtorek, 13 maja 2014

[C/C++] Pre-inkrementacja vs post-inkrementacja w pętli for

Którą wersję operatora inkrementacji powinniśmy stosować w pętli for i dlaczego?

Spójrzmy na dwa przykładowe kody:

for( int i = 0; i < 3; i++)
{
     std::cout << i << "\n";
}

for( int i = 0; i < 3; ++i)
{
     std::cout << i << "\n";
}

W obu przypadkach wynikiem będzie:

0
1
2

Krótkie przypomnienie: 
  • ++i - operator pre-inkrementacji, zwiększa wartość zmiennej i o jeden, następnie pobiera jej wartość
  • i++ - operator post-inkrementacji, pobiera wartość zmiennej i, następnie zwiększa jej wartość o jeden

Korzystniejsze jest jednak korzystanie z operatora pre-inkrementacji. Może być to nieco zaskakujące, ale jest to prawda. Pomyślmy o tym jak każda z wersji operatora inkrementacji działa, gdy to przeanalizujemy łatwo zauważyć że w przypadku post-inkrementacji istnieje potrzeba aby przechować gdzieś wartość która po zwróceniu zostanie zwiększona o 1, taka sytuacja nie występuje przy pre-inkrementacji gdzie od razu zwiększamy wartość a następnie ją zwracamy. Oznacza to zatem że kod generowany przy użyciu ++i jest bardziej wydajny niż przy i++, to samo dotyczy oczywiście operatora deinkrementacji.

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.