Autor: Bolesław SzewczykObsł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:
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).
- 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.
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).