Filip Murlak
Kontekst Dokument niniejszy powstał jako fragment prezentacji
przygotowywanej w ramach projektu Scenariusze ćwiczeń z budowy systemu
operacyjnego Linux realizowanego na zajęciach z Systemów Operacyjnych na
Wydziale Matematyki, Informatyki i Mechaniki Uniwersytetu Warszawskiego
w roku akademickim 2001/2002.
Uwagi techniczne Wszystkie informacje dotyczą jądra 2.4.7 i mogą nie być prawdziwe dla późniejszych wersji - dla wcześniejszych na pewno nie są! Opis dotyczy architektury i386. Wydruki kodu są poddane obróbce właściwej dla tej właśnie architektury (dyrektywy preprocesora, warunkowa kompilacja). Definicje struktur i funkcji pochodzą z - odpowiednio - include/linux/mm.h i drivers/char/mem.c.
Linux udostępnia pamięć jako urządzenia znakowe o numerze głównym 1. Obejmuje ona następujące urządzenia podrzędne:
Numer podrzędny | Plik | Opis |
1 | /dev/mem | pamięć fizyczna |
2 | /dev/kmem | pamięć wirtualna jądra |
3 | /dev/null | ujście dla niepotrzebnych danych |
4 | /dev/port | porty |
5 | /dev/zero | generator zer |
7 | /dev/full | urządzenie pełne, przy zapisie zwraca ENOSPACE |
8 | /dev/random | generator liczb pseudolosowych |
9 | /dev/urandom | nieblokujący generator liczb pseudolsowych |
Szczególnie sympatycznym urządzeniem jest /dev/null. Kompletna implementacja tego urządzenia wygląda następująco:
static ssize_t read_null (struct file * file, char * buf, size_t count, loff_t *ppos) { return 0; } static ssize_t write_null(struct file * file, const char * buf, size_t count, loff_t *ppos) { return count; } static loff_t null_lseek(struct file * file, loff_t offset, int orig) { return file->f_pos = 0; } static struct file_operations null_fops = { llseek: null_lseek, read: read_null, write: write_null, };Wystarczy je teraz zarejestrować i mamy gotowe urządzenie!
Przykład urządzenia, którego implementacja jest niebanalna stanowią rand i urand (drivers/char/random.c). Urządzenia te stanowią nową jakość w stosunku do funkcji bibliotecznych (rand() itp.), gdyż tamte zapewniają wprawdzie odpowiedni rozkład danych, ale charakteryzują się niską entropią (tzn. można przewidywać pewne wartości na podstawie innych). Implementacja ''losowych'' urządzeń oparta jest na przechwytywaniu informacji o czasie wystąpienia zdarzeń zewnętrznych (np. kliknięcie myszą, naciśnięcie klawisza na klawiaturze), co zdecydowanie podwyższa entropię. Na podstawie tych danych, przechowywanych w zbiorze entropii, urządzenie rand generuje tyle bajtów, ile (w pewnym sensie) znajduje się w tym zbiorze. Natomiast urand generuje dowolną ilość danych, ale nie gwarantuje przyzwoitego poziomu entropii.
Struktura fops musi zatem wyglądać następująco:
static struct file_operations (k)mem_fops = { llseek: memory_lseek, read: read_(k)mem, write: write_(k)mem, mmap: mmap_(k)mem, open: open_(k)mem, };Uwaga: Urządzenie (k)mem (także zero) jest wyjątkiem pośród urządzeń znakowych. Udostępnia odwzorowywanie w pamięci (mmap) i swobodny dostęp (lseek) - dlaczego?
static loff_t memory_lseek(struct file * file, loff_t offset, int orig)Ustawiamy (gdy orig==1 - przesuwamy) bieżącą pozycję w pliku file na offset.
static int open_port(struct inode * inode, struct file * filp)Realizuje operację open, sprawdzając czy można skorzystać z we/wy wywołując makro capable(CAP_SYS_RAWIO). Ta funkcja obsługuje też otwieranie urządzenia port - stąd nazwa.
static int mmap_mem(struct file * file, struct vm_area_struct * vma)
Odwzorowuje urządzenie (k)mem w pamięci.
Uwaga: Przy mapowaniu urządzenia zero (mmap_zero()),
gdy żądanie dotyczy pamięci dzielonej, dokonujemy zwykłego wyzerowania
odpowiednich obszarów. Operacja ta może być długotrwała, więc po każdej
stronie sprawdzamy flagę need_resched i ewentualnie oddajemy procesor
wywołując funkcje schedule().
static ssize_t write_mem(struct file * file, const char * buf, size_t count, loff_t *ppos) static ssize_t read_mem(struct file * file, char * buf, size_t count, loff_t *ppos)Pisanie i czytanie do pamięci.
Do tłumaczenia adresów z fizycznego na wirtualny jądra i odwrotnie
wykorzystujemy zdefiniowane w include/asm/page.h makra
__va() i __pa() (zwykłe przesunięcie
adresu o stałą PAGE_OFFSET w odpowiednią stronę).
Jeśli adres jest poprawny, to kopiujemy dane z pamięci do przestrzeni
adresowej użytkownika (lub odwrotnie) za pomocą:
copy_to_user() copy_from_user()zdefiniowanych w include/asm/uaccess.h.
static ssize_t write_mem (struct file * file, const char * buf, size_t count, loff_t *ppos) { unsigned long p = *ppos; unsigned long end_mem; end_mem = __pa(high_memory); if (p >= end_mem) return 0; if (count > end_mem - p) count = end_mem - p; return do_write_mem(file, __va(p), p, buf, count, ppos); } static ssize_t write_kmem (struct file * file, const char * buf, size_t count, loff_t *ppos) { unsigned long p = *ppos; if (p >= (unsigned long) high_memory) return 0; if (count > (unsigned long) high_memory - p) count = (unsigned long) high_memory - p; return do_write_mem(file, (void*)p, p, buf, count, ppos); }
Deklarujemy zainicjowane struktury xxx_fops (xxx - nazwa) dla każdego urządzenia podrzędnego (patrz wyżej) i pomocniczą strukturę:
struct file_operations memory_fops = { open: memory_open, };gdzie int memory_open (struct inode *, struct filp *) jest uniwersalną funkcją otwierającą urządzenie główne mem, odpowiednio modyfikującą pole fops w zależności od numeru podrzędnego. Struktura ta jest potrzebna w przypadku starej obsługi plików specjalnych - wywołanie devfs_register_chrdev() realizujące register_chrdev() (jeśli podczas ładowania systemu nie ustawiono opcji devfs=only) - gdy korzystamy wyłącznie z devfs, jest to zbędne, bo dla każdego urządzenia podrzędnego pamiętana jest osobna struktura file_operations. Rejestracji dokona funkcja wyglądającą mniej więcej tak:
int inicjuj () { /* to oczywiście NIE jest prawdziwy kod jądra */ static const struct { unsigned short minor; char *name; umode_t mode; struct file_operations *fops; } list[] = { {1, "mem", S_IFCHR, &mem_fops }, {2, "kmem", S_IFCHR, &kmem_fops}, /* ... {N, , , } */ }; int i; devfs_register_chrdev (MEM_MAJOR,"mem",&memory_fops); for (i = 0; i <= N; i++) devfs_register (NULL, list[i].name, DEVFS_FL_NONE, MEM_MAJOR, list[i].minor, list[i].mode, list[i].fops, NULL); rand_initialize (); return 0; }