Opis funkcji open, close, read i write
Mateusz Stachlewski
1 Wprowadzenie
W niniejszym opracowaniu opisuję implementację w jądrze Linux 2.4.7
czterech podstawowych funkcji systemowych obsługi plików: open, close,
read i write. Skupiam się na warstwie VFS (ang. Virtual File System). Przedstawiam
opatrzone komentarzem i uwagami kody źrodłowe z pominięciem obsługi błedów.
W moim przekonaniu przestudiowanie podanych kodów stanowi modelowe wprowadzenie
do VFS.
2 Opis funkcji sys_open czyli podprogramu obsługi wywołania open(2)
// Ważne: parametr filename pokazuje na przestrzeń adresową użytkownika.
long sys_open(const char * filename, int flags, int mode) {
char * tmp;
int fd;
struct file *f; // struct file opisuje plik otwarty przez dany proces
// getname kopiuje napis filename (scieżka dostępu do pliku) z
// przestrzeni adresowej procesu do przestrzeni jądra.
// Czyli getname = kmem_cache_alloc + strncpy_from_user.
tmp = getname(filename);
// get_unused_fd przydziela jedną pozycję w tablicy current->files->fd
// W razie potrzeby, tablica ta jest dynamicznie powiększana.
fd = get_unused_fd();
// filp_open przydziela i inicjuje obiekt typu struct file.
// To tutaj wykonywana jest główna część funkcji sys_open().
f = filp_open(tmp, flags, mode);
// fd_install instaluje struct file * f w tablicy current->files->fd
fd_install(fd, f);
// putname zwalnia pamięć jądra przydzieloną na zapamiętanie napisu
// filename (jest to proste makro wołające kmem_cache_free).
putname(tmp);
return fd; // zwraca indeks do tablicy current->files->fd lub błąd
}
// Poniższa funkcja przydziela, inicjuje i zwraca obiekt typu struct file
// odpowiadający scieżce filename.
struct file * filp_open(const char * filename, int flags, int mode) {
struct nameidata nd;
// open_namei() interpretuje ścieżkę dostępu do pliku oraz pobiera i
// zwraca obiekt pozycji katalogu (struct dentry) związany z filename
// (poprzez struct nameidata * nd czyli ostatni parametr wywołania).
// To tutaj sprawdzane są prawa dostępu do pliku.
open_namei(filename, flags, mode, &nd);
// Funkcja dentry_open tworzy (i zwraca) strukturę file dla pliku
// wyznaczonego przez nd.dentry i nd.mnt
return dentry_open(nd.dentry, nd.mnt, flags);
}
struct nameidata {
// Struktura dentry opisuje pozycję (wpis, dowiązanie) w katalogu.
struct dentry * dentry;
// Struktura vfsmount opisuje zamontowany system plików.
struct vfsmount * mnt;
...
};
// fd_install instaluje struct file * f w tablicy current->files->fd
void fd_install(unsigned int fd, struct file * file) {
struct files_struct * files = current->files;
write_lock(&files->file_lock);
files->fd[fd] = file;
write_unlock(&files->file_lock);
}
UWAGI:
(1) Dla każdego nowo utworzonego procesu tablica current->files->fd (czyli
tablica wskaźników struct file *) ma początkowo 32 elementy.
Mapy bitowe current->files->open_fds (ity bit ustawiony wiw, gdy
current->files->fd[i] wskazuje na otwarty plik) i current->files->
->close_on_exec mają początkowy rozmiar 1024 bitów. W razie potrzeby
obiekty te są dynamicznie powiększane.
(2) W funkcji get_unused_fd (wołanej bezpośrednio z sys_open) wykonywane
jest makro FD_CLR(fd, current->files->close_on_exec). Oznacza to, że
domyślnie deskryptory nie są zamykane przez funkcje z rodziny exec().
(3) Pole f_op obiektu pliku (struct file) jest ustawiane (w funkcji
dentry_open) na pole i_fop obiektu i-wezla. W ten sposób ustawiane są
funkcje obsługi operacji plikowych związanych z danym systemem plików.
(4) W funkcji dentry_open wołana jest funkcja open właściwa dla danego obiektu
pliku, jesli została ona zdefiniowana (tzn. jeśli wskaźnik
current->files->fd[fd]->f_op->read jest różny od NULL).
ZMIANY w wersji 2.4:
Struktura inode została powiększona o pole struct file_operations * i_fop,
natomiast usunięto wskaźnik default_file_ops ze struktury inode_operations.
3 Opis funkcji sys_close czyli podprogramu obsługi wywołania close(2)
long sys_close(unsigned int fd) {
struct file * filp;
struct files_struct *files = current->files;
// Funkcja write_lock zakłada blokadę pętlową (spinlock) na tablicę
// current->files reprezentującą pliki otwarte przez proces.
// Jest to konieczne, gdyż tablica ta może być dzielona przez
// kilka procesów.
write_lock(&files->file_lock);
filp = files->fd[fd];
files->fd[fd] = NULL;
FD_CLR(fd, files->close_on_exec);
FD_CLR(fd, files->open_fds);
write_unlock(&files->file_lock);
return filp_close(filp, files);
}
// Drugi parametr to current->files.
// Typ fl_owner_t jest narzucony przez POSIX.
int filp_close(struct file *filp, fl_owner_t id) {
int retval = 0;
// Tutaj wołana jest metoda flush (o ile została zdefiniowana).
if (filp->f_op && filp->f_op->flush) {
lock_kernel();
retval = filp->f_op->flush(filp);
unlock_kernel();
}
// Teraz zwalniane sa blokady typu posix związane z tym plikiem.
locks_remove_posix(filp, id);
// fput zwalnia obiekt pliku.
fput(filp);
return retval;// zwraca kod powrotu metody flush (lub 0)
}
4 Opis funkcji sys_read czyli podprogramu obsługi wywołania read(2)
ssize_t sys_read(unsigned int fd, char * buf, size_t count) {
ssize_t ret = -EBADF;
struct file * file;
// fget pobiera z tablicy current->files->fd adres obiektu file
// odpowiadającego deskryptorowi fd i zwiększa licznik file->f_count.
file = fget(fd);
// Tutaj sprawdzane jest czy file->f_mode zezwala na odczyt z pliku.
if (file->f_mode & FMODE_READ) {
// Teraz sprawdza się czy na fragment pliku, którego dotyczy operacja
// read nie sa założone blokady uniemożliwiające odczyt.
ret = locks_verify_area(FLOCK_VERIFY_READ, file->f_dentry->d_inode,
file, file->f_pos, count);
if (!ret && file->f_op && (read = file->f_op->read) != NULL)
// Wołana jest funkcja read właściwa dla danego systemu plików.
ret = read(file, buf, count, &file->f_pos);
}
if (ret > 0)
inode_dir_notify(file->f_dentry->d_parent->d_inode, DN_ACCESS);
// fput zwalnia obiekt pliku.
fput(file);
return ret; // Zwracana jest liczba odczytanych bajtów (lub bład).
}
ZMIANY w wersji 2.4:
(1) usunięto nawiasy lock_kernel i unlock_kernel (globalna blokada jądra)
(2) dodano wywołanie inode_dir_notify
5 Opis funkcji sys_write czyli podprogramu obsługi wywołania write(2)
ssize_t sys_write(unsigned int fd, const char * buf, size_t count) {
ssize_t ret = -EBADF;
struct file * file;
// fget pobiera z tablicy current->files->fd adres obiektu file
// odpowiadającego deskryptorowi fd i zwiększa licznik file->f_count.
file = fget(fd);
// Tutaj nastepuje sprawdzenie czy file->f_mode zezwala na zapis do pliku.
if (file->f_mode & FMODE_WRITE) {
struct inode *inode = file->f_dentry->d_inode;
// Teraz sprawdza się czy na fragment pliku, którego dotyczy operacja
// write nie sa założone blokady uniemożliwiające zapis.
ret = locks_verify_area(FLOCK_VERIFY_WRITE, inode, file,
file->f_pos, count);
if (!ret && file->f_op && (write = file->f_op->write) != NULL)
// Wołana jest funkcja write zdefiniowana dla danego obiektu file.
ret = write(file, buf, count, &file->f_pos);
}
if (ret > 0)
inode_dir_notify(file->f_dentry->d_parent->d_inode, DN_MODIFY);
// fput zwalnia obiekt pliku.
fput(file);
return ret; // sys_write zwraca liczbę zapisanych bajtów (lub bład).
}
ZMIANY w wersji 2.4:
(1) Usunięto nawiasy lock_kernel i unlock_kernel (pozwalające pobrać i
zwolnić globalną blokadę jądra).
(2) Dodano wywołanie inode_dir_notify.
(3) Usunięte zostały nawiasy down(&inode->i_sem) i up(&inode->i_sem) chroniące
wywołanie funkcji write zdefiniowanej dla danego obiektu pliku.