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.