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?).
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 /procmoż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 0różni się ona od innych linijek głównie tym, że na pierwszym miejscu, gdzie powinno być urządzenie jakie należy zamontować jest none.
Inicjalizacją procfs'a zajmuje się funkcja proc_root_init wywoływana z funkcji start_kernel. Inicjalizacja, to głównie:
int err = register_filesystem(&proc_fs_type);gdzie proc_fs_type jest strukturą typu file_system_type zawierającą podstawowe informacje o systemach plików.
//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; };
Schemat postępowania zawsze jest taki sam:
static struct proc_dir_entry *proc_create( struct proc_dir_entry **parent, const char *name, mode_t mode, nlink_t nlink);
static int proc_register(struct proc_dir_entry * dir, struct proc_dir_entry * dp);
struct proc_dir_entry *proc_mkdir(const char *name, struct proc_dir_entry *parent);
struct proc_dir_entry *proc_mknod(const char *name, mode_t mode, struct proc_dir_entry *parent, kdev_t rdev);
struct proc_dir_entry *proc_symlink(const char *name, struct proc_dir_entry *parent, const char *dest);
struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode, struct proc_dir_entry *parent);Przy czym w tym przypadku należy jeszcze samodzielnie ustawić pole proc_fops gdyż i tak dla każdego pliku będzie to co innego.
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.
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.
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).