Autor: Bolesław Szewczyk

Obsługa urządzenia MEM

Mem jest jednym z prostych urządzeń wchodzących w skład grupy urządzeń znakowych o wspólnym numerze major równym 1. Nazwijmy tą grupę tymczasowo "grupą urządzeń MEM".

Zastosowanie urządzeń z grupy MEM jest niezbyt skomplikowane, ich implementacja również jest prosta (Implementacja urządzeń MEM składa się z dwóch plików: drivers/char/mem.c oraz drivers/char/random.c. Użyto tutaj tylko podstawowych mechanizmów jądra). Obecnie do grupy MEM należy dziewięć urządzeń:
 
Numer minor urządzenia Plik urządzenia w /dev/ Plik źródłowy
Opis i zastosowanie urządzenia
1. /dev/mem drivers/char/mem.c
Pamięć fizyczna stacji roboczej.
2. /dev/kmem drivers/char/mem.c
Pamięc wirtualna, widoczna z poziomu jądra (kernel memory).
3. /dev/null drivers/char/mem.c
Specjalne urządzenie, do którego zapis powoduje bezpowrotną utratę danych, które zapisujemy.
4. /dev/port drivers/char/mem.c
Umożliwia dostęp do portów sprzętowych.
5. /dev/zero drivers/char/mem.c
Plik o nieskończonym rozmiarze wypełniony zerami.
6. /dev/core drivers/char/mem.c
Urządzenie zostało usunięte z aktualnej wersji Linuxa, a plik /dev/core jest dowiązaniem symbolicznym do pliku /proc/kcore (Kernel core).
7. /dev/full drivers/char/mem.c
Próba zapisu do tego urządzenia kończy się błędem "No space left on device" (ENOSPC).
8. /dev/random drivers/char/random.c
Generator liczb losowych na podstawie "entropii" - źródłem entropii jest między innymi pomiar czasu między naciśnięciami klawiszy przez użytkownika. Blokuje odczyt czekając na wystarczającą ilość zdarzeń losowych.
9. /dev/urandom drivers/char/random.c
Generator liczb losowych aż do wyczerpania "entropii", a następnie pseudolosowych. Nieblokujące.

Najbardziej istotne funkcje, zdefiniowane w pliku mem.c to:

  • do_write_mem() - przy użyciu funkcji copy_from_user() zapisuje dane do pamięci (wirtualnej lub fizycznej). W przypadku błędu zwraca błąd "Bad address" (EFAULT).
  • read_mem() - czyta z fizycznej pamięci jądra za pomocą funkcji copy_to_user(). W przypadku błędu zwraca błąd "Bad address" (EFAULT).
  • mmap_mem() - mapuje pamięć w inne miejsce, przy użyciu remap_page_range(). Jeśli jest to niemożliwe, zwraca błąd "Resource temporarily unavailable" (EAGAIN)
  • read_kmem() - działanie podobne do read_mem(), z tym że czytanie odbywa się z pamięci wirtualnej widzianej przez kernel. Operacja ta może wymagać alokacji nowej strony za pomocą __get_free_page(). Jeśli alokacja sie nie powiedzie, zwracany jest błąd "Out of memory" (ENOMEM).
  • write_mem(), write_kmem() - tłumaczą adres na zrozumiały dla copy_from_user() i wywołują do_write_mem(). Dokładniej, write_kmem() sprawdza czy adres mieści się w przedziale pamięci wirtualnej, a write_mem() sprawdza czy adres jest w przedziale pamięci fizycznej i przekazuje go do do_write_mem() po zmianie przez makro __va().
  • read_port(), write_port() - sprawdzają za pomocą verify_area() czy operacja na wybranym porcie / portach jest dozwolona. Jeżeli nie, zwracają "Bad address" (EFAULT). Następnie próbują wykonać odczyt / zapis do portu. W przypadku błędu zwracany jest "Bad address" (EFAULT).
  • read_null() - zwraca zero.
  • write_null() - zwraca ilość bajtów, które polecono zapisać. Oznacza to, że zapis się powiódł.
  • read_zero() - wypełnia bufor zerami, o ile jest to dozwolone dla obecnego użytkownika. W przeciwnym przypadku zwraca błąd "Bad address" (EFAULT).
  • mmap_zero() - mapuje blok zer przy użyciu shmem_zero_setup() lub zeromap_page_range(), zależnie od tego, czy ma to być blok pamięci dzielonej.
  • write_full() - zwraca błąd "No space left on device" (ENOSPC).
  • null_lseek() - ta funkcja odpowiada za ustawianie pozycji pliku w urządzeniach null, zero i full. Należy o niej wspomnieć, ponieważ jest napisana tylko po to, aby można było otworzyć te urządzenia w trybie zapisu zaczynającego od końca pliku (append).
  • memory_lseek() - odpowiada za zmianę pozycji w plikach urządzeń mem, kmem i port. Nie robi nic poza zmianą pozycji w strukturze file, która jest parametrem. Nie pozwala na ustawianie pozycji w pliku względem końca pliku, ponieważ trudno mówić o końcu pliku w przypadku pamięci oraz portów. Zwraca błąd "Invalid argument" (EINVAL), jeśli zarządamy zmiany pozycji względem końca pliku.
  • open_port() - wywoływana, gdy otwieramy jedno z urządzeń mem, kmem i port. Sprawdza, czy korzystanie z urządzenia jest dozwolone za pomocą makra capable().
  • memory_open() - wywoływana podczas otwierania dowolnego urządzenia z grupy MEM. Podmienia strukturę file_operations w argumencie, w zależności od numeru minor urządzenia. Dla nieznanych numerów minor urządzeń zwraca błąd "No such device or address" (ENXIO). Następnie wywołuje open z podmienionego file_operations (jeśli jest zdefiniowany).
  • memory_devfs_register(), chr_dev_init() - rejestrują urządzenia MEM.
Pliku random.c nie będe szczegółowo omawiał. Generator liczb losowych pobiera "losowość" z zewnątrz - wykorzystuje odstępy czasowe między naciśnięciami klawiszy przez użytkownika, odstępy czasowe między wystąpieniami niektórych przerwań, ruchy myszy oraz zachowanie urządzeń blokowych. "Losowość" dodawana jest to tablicy "entropii". Liczby w tej tablicy podlegają następnie pewnym przekształceniom arytmetycznym, które mają na celu równomierne (losowe, nieprzewidywalne) rozprowadzenie generowanych liczb (Otrzymujemy losowy ciąg zer i jedynek, w którym ani jedynka, ani zero nie są wyróżnione pod względem prawdopodobieństwa).

Działanie typowych operacji plikowych (ze struktury file_operations) na urządzeniach z grupy mem:


 
mem kmem port null zero full random urandom
llseek(file,offset,whence) Ustawienie wskaźnika pozycji w file,
o ile whence != SEEK_END 
(Wtedy błąd: "Invalid argument" - EINVAL).
Brak działania
(i brak błędu).
NI
read(file,buffer,count,pos) Odczyt z pamięci. Czytanie danych z portów. Zwraca 0. Zeruje buffer. Blokujący odczyt losowych liczb. Nieblokujący odczyt liczb losowych i pseudolosowych.
write(file,buffer,count,pos) Zapis do pamięci. Zapis do portów. Zwraca count. Błąd "No space left on device" (ENOSPC). "Żywienie" generatora liczb losowych, czyszczenie entropii i inne operacje (tylko root).
ioctl(inode,file,desc,req) NI
poll(file,poll_table) Sprawdzenie, czy są liczby losowe do odczytania. NI
mmap(file,vm_area) Mapuje pamięć. NI Mapuje wyzerowany blok pamięci. NI
open(inode,file) Błąd "Permission denied" (EPERM), jeśli nie wywoływane przez roota. Ok.

Pozostałe operacje, czyli: readdir, flush, release, fsync, fasync, check_media_change, revalidate, lock, readv, writev i readpage nie są używane. Próba ich wykonania zakończy się błędem. Dokładniej, wykonywanie operacji niezaimplementowanych (nie wymienionych w tabeli lub oznaczonych NI) powoduje błąd "Invalid argument" (EINVAL).