System plików proc - prezentacja



Co to? Po co?

System plików proc (dalej będę zwykle pisać krótko procfs) jest wirtualnym systemem plików i interfejsem do pewnych struktur danych działającego systemu (jądra). Celem powstania procfs'a było ułatwienie życia programistom i użytkownikom systemu, dlatego też idea ta przyjęła kształt właśnie wirtualnego systemu plików - dane z pliku czyta się łatwo, nietrudno także się je w plikach zapisuje.
Zauważmy jak łatwo, mając procfs'a, napisać taki program jak top albo ps (te programy rzeczywiście korzystają z katalogu /proc - można sprawdzić zabierając prawa dostępu, wyrzucą wtedy jakiś błąd).

Interfejs ten pozwala przeważnie na odczyt pewnych informacji, ale w niektórych przypadkach można także do plików w strukturze procfs'a zapisywać dane i w ten sposób zmieniać zachowanie niektórych części jądra systemu.

Warto zwrócić uwagę na wspomnianą ideę łatwego, ogólnie znanego interfejsu, nie jest to jedyne miejsce gdzie się stosuje podejście plikowe do rzeczy o wiele bardziej abstrakcyjnych (pytanie do słuchaczy: gdzie na przykład jeszcze?).

Niektóre pliki w /proc

Montowanie procfs'a

Nie ma nic magicznego w montowaniu systemu plików proc, choć w większości dystrybucji montuje się go nieco wcześniej niż pozostałe systemy plików, a robi się tak dlatego, że wiele programów uruchamianych podczas startu korzysta właśnie z informacji zawartych w katalogu /proc. Gdyby ktoś chciał ręcznie zamontować sobie procfs, to należy zrobić coś takiego:

mount -t proc none /proc
można na przykład zamienić parametr /proc na jakiś inny katalog i stworzyć sobie "drugiego" procfs'a. Linijka w /etc/fstab wygląda tak:
none	/proc	proc	defaults	0 0
różni się ona od innych linijek głównie tym, że na pierwszym miejscu, gdzie powinno być urządzenie jakie należy zamontować jest none.

Implementacja

Inicjalizacja

Inicjalizacją procfs'a zajmuje się funkcja proc_root_init wywoływana z funkcji start_kernel. Inicjalizacja, to głównie:

Typy i struktury danych

//typ funkcji generującej zawartość pliku
typedef int (read_proc_t)(char *page, char **start, off_t off,
	int count, int *eof, void *data);

//typ funkcji obsługującej zapis do pliku z procfs'a
typedef int (write_proc_t)(struct file *file, const char *buffer,
	unsigned long count, void *data);

//typ funkcji generującej zawartość funkcji (dawniejsze implementacje)
typedef int (get_info_t)(char *, char **, off_t, int);

//struktura zawierająca pojedynczy wpis w procfs
struct proc_dir_entry {
	//numer i-węzła
	unsigned short low_ino;
	//długość nazwy
	unsigned short namelen;
	//nazwa
	const char *name;
	//atrybuty pliku
	mode_t mode;
	//liczba zawieranych plików (dla katalogów)
	nlink_t nlink;
	//właściciel pliku
	uid_t uid;
	//grupa do jakiej należy plik
	gid_t gid;
	//rozmiar pliku
	unsigned long size;
	//operacje i-węzłowe
	struct inode_operations *proc_iops;
	//operacje na pliku
	struct file_operations *proc_fops;
	//funkcja do generowania zaw. pliku
	//(zaszłość trzymana ze względu na niektóre, starsze kawałki kodu,
	//zamiast tego powinno używać się read_proc)
	get_info_t *get_info;
	//jeżeli dany wpis jest z jakiegoś modułu, to powinno się to pole ustawić
	//na THIS_MODULE (używane do zwiększania/zmniejszania liczników użycia modułu)
	struct module *owner;
	//następny w katalogu, katalog-ojciec oraz lista podkatalogów
	struct proc_dir_entry *next, *parent, *subdir;
	//dane specyficzne dla pliku (głównie zawartość linków)
	void *data;
	//funkcja wywoływana przy czytaniu z pliku
	read_proc_t *read_proc;
	//funkcja wywoływana przy zapisie do pliku
	write_proc_t *write_proc;
	//licznik użyć
	atomic_t count;
	//flaga skasowania (ustawia się jak ma być skasowany, a jest w użyciu)
	int deleted;
	//dla tworzonych urządzeń w procfs'ie
	kdev_t  rdev;
};


Wszystkie instancje struktury proc_dir_entry odpowiadają jakimś plikom, które w systemie tworzą strukturę drzewiastą więc i same podobną strukturę, oto jak może wyglądać jej kawałek:



Dodawanie nowych plików do /proc

Schemat postępowania zawsze jest taki sam:

Ponieważ różnych typów plików jest tylko kilka i przeważnie większość ustawianych pól w strukturze proc_dir_entry dla jednego typu jest zawsze taka sama, przygotowano funkcje, które w łatwy i wygodny sposób zrobią wymienione kroki za nas, są to (należy zaincludować plik nagłówkowy linux/proc_fs.h, a ich definicje są w pliku fs/proc/generic.c): Parametr parent we wszystkich tych funkcjach to wskaźnik na strukturę proc_dir_entry zarejestrowanego wcześniej w procfs'ie katalogu, w którym nowo tworzony plik ma się znaleźć. Jeżeli ma to być w głównym katalogu, to można podać NULL lub wskaźnik na zmienną proc_root (zadeklarowaną w fs/proc/root.c), natomiast jeżeli chodzi nam o inny katalog, to dostępnych jest kilka innych zmiennych: Parametr name, to nazwa tworzonego pliku.
Parametr mode określa typ tworzonego pliku.

Obsługa wypisywania zawartości pliku

Aby stworzyć plik, dla którego przewidujemy możliwość czytania, potrzebna nam będzie funkcja obsługująca to czytanie, musi ona wyglądać jakoś tak:

int read_function(char* page, char** start, off_t off, int count, int* eof, void* data);
Generowane informacje powinny zostać zapisane pod adres page + off i nie powinno być zapisane więcej niż count bajtów. Pod adres eof powinna zostać wpisana jedynka jeżeli nastąpił koniec informacji, a 0 wpp. Taka funkcja musi zwrócić ilość zapisanych do page znaków.
W powyższym akapicie wiele razy pojawiło się słowo "powinno", oznacza to tutaj, że niekoniecznie tak się to rozwiązuje w obecnym kodzie. Ponieważ w większości przypadków wypisywane informacje są krótkie i po prostu ignoruje się parametr off i zapisuje po prostu pod adres page.

Po utworzeniu takiej funkcji wystarczy wpisać jej adres w pole read_proc struktury proc_dir_entry danego pliku.

Obsługa zapisywania do pliku

Podobnie jak w przypadku wypisywania zawartości, tak i teraz należy zdefiniować własną funkcję:

int write_function(struct file* file, const char* buffer, unsigned long count, void* data);
Funkcja ta ma czytać z adresu buffer maksymalnie count bajtów (należy użyć funkcji copy_from_user bo jest to pamięć z user space). Zwracać należy ilość wczytanych bajtów.

Jeszcze trochę o katalogach /proc/<pid>/

Katalogi odpowiadające procesom oraz link self w /proc są dość specyficzne dlatego implementowane są nieco inaczej. Nie ma dla nich oddzielnych struktur proc_dir_entry, wszystkie funkcje obsługujące katalog /proc różnią się przez to nieco od funkcji obsługujących pozostałe katalogi procfs'a (zwykle tym, że oprócz zrobienia tego co tamte wykonują na koniec dodatkowe rzeczy związane z katalogami procesów).