IT技术博客大学习 共学习 共进步

文件操作函数在VFS层的实现

edsionte's Linuxworld|新手区 2013-07-15 13:27:12 浏览 2,423 次

虚拟文件系统(Virtual Filesystem Switch,VFS)为各种文件系统提供了一个通用的接口,它使得上层进程在进行与文件系统相关的操作时可以使用同一组系统调用,但是系统调用在内核中可以根据不同的文件系统执行不同的操作。

与文件相关的基本操作函数有:open、read、write和close,本文将结合内核源代码分析这些函数在虚拟文件系统中的实现。

1.open()的实现

open系统调用的作用是打开或创建一个文件,并且返回该文件的文件描述符。在内核中,open系统调用主要完成的工作是为此次打开的文件创建file对象,该对象在fd_array数组中的索引值为返回用户空间的文件描述符。

open系统调用对应的系统调用服务例程为sys_open,不过目前内核已经统一使用SYSCALL_DEFINEn这种方式对系统调用服务例程进行定义。在open系统调用服务例程中又直接调用了do_sys_open函数,它是打开动作的主体函数。

long do_sys_open(int dfd, const char __user *filename, int flags, int mode)
{
        char *tmp = getname(filename);
        int fd = PTR_ERR(tmp);
  
        if (!IS_ERR(tmp)) {
                fd = get_unused_fd_flags(flags);
                if (fd >= 0) {
                        struct file *f = do_filp_open(dfd, tmp, flags, mode, 0);
                        if (IS_ERR(f)) {
                                put_unused_fd(fd);
                                fd = PTR_ERR(f);
                        } else {
                                fsnotify_open(f->f_path.dentry);
                                fd_install(fd, f);
                        }
                }
                putname(tmp);
        }
        return fd;
}

用户进程使用open打开文件时将传递文件路径filename,因此该函数第一步先通过getname函数从用户空间读取文件路径到内核空间,暂存到tmp。通过get_unused_fd_flags函数在当前进程的fd_array数据中找到一个何时的位置,并返回其索引。

接下来通过do_filp_open函数执行打开文件的核心操作:根据系统调用中的标志参数flags和访问模式mode设置相应的局部变量以便后续使用;根据要打开文件的路径tmp寻找其inode节点,如果该inode节点不存在并且设置了O_CREATE标志则在磁盘上创建一个新的磁盘索引节点;分配一个新的文件对象,并根据系统调用传递的标志和访问模式设置文件对象的f_flags和f_mode字段;使用索引节点的i_fop字段初始化文件对象的f_op字段;将该文件对象插入到超级块指向的打开文件链表中;如果文件对象操作函数集中的open函数被定义则调用它;最后返回这个文件对象;

如果这个文件对象创建成功,则通过fd_install函数将该文件对象赋值到fd_array数组的第fd个元素中。

2.read()的实现

读文件系统调用read()的作用是根据文件描述符fd读取指定长度size的数据到缓冲区buf中。该系统调用的实现涉及了内核中对I/O进行处理的各个层次,但是对于VFS层来说实现方法比较清晰。

SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
        struct file *file;
        ssize_t ret = -EBADF;
        int fput_needed;
          
        file = fget_light(fd, &fput_needed);
        if (file) {
                loff_t pos = file_pos_read(file);
                ret = vfs_read(file, buf, count, &pos);
                file_pos_write(file, pos);
                fput_light(file, fput_needed);
        }
  
        return ret;
}

在read系统调用对应的服务例程中,首先使用fget_light函数通过fd获取对应的文件对象;再通过file_pos_read函数获取读文件的起始偏移量,即文件对象的f_pos字段的值;接着通过vfs_read函数进行读操作;通过file_pos_write函数更新文件当前的偏移量;通过fput_light函数释放文件对象;最终返回vfs_read函数的返回值ret,该值则为实际读取数据的长度。

read系统服务例程中最核心的函数即为vfs_read,它的主要工作是选择一个具体的读操作函数。如果当前文件对象操作函数集中的read钩子函数(file->f_op->read)被实现(通常在驱动程序中实现),则调用它。否则使用内核默认的读函数do_sys_read。

ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{
        ssize_t ret;
  
        if (!(file->f_mode & FMODE_READ))
                return -EBADF;
        if (!file->f_op || (!file->f_op->read && !file->f_op->aio_read))
                return -EINVAL;
        if (unlikely(!access_ok(VERIFY_WRITE, buf, count)))
                return -EFAULT;
  
        ret = rw_verify_area(READ, file, pos, count);
        if (ret >= 0) {
                count = ret;
                if (file->f_op->read)
                        ret = file->f_op->read(file, buf, count, pos);
                else
                        ret = do_sync_read(file, buf, count, pos);
                if (ret > 0) {
                        fsnotify_access(file->f_path.dentry);
                        add_rchar(current, ret);
                }
                inc_syscr(current);
        }
  
        return ret;
}

事实上,do_sys_read函数在内部调用钩子函数aio_read(file->f_op->aio_read),该钩子函数一般指向内核实现的通用读函数generic_file_aio_read。这个通用函数已经不属于我们本文所述的VFS层的实现范畴。

3.write函数的实现

write系统调用在VFS层的实现流程与read类似,只不过在出现read的地方将其相应的置换为write。

SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
                size_t, count)
{
        struct file *file;
        ssize_t ret = -EBADF;
        int fput_needed;
  
        file = fget_light(fd, &fput_needed);
        if (file) {
                loff_t pos = file_pos_read(file);
                ret = vfs_write(file, buf, count, &pos);
                file_pos_write(file, pos);
                fput_light(file, fput_needed);
        }
  
        return ret;
}

当然最终实现写文件操作的函数也是file->f_op->write或者内核中通用的写操作generic_file_aio_write。

4.close()的实现

close系统调用对应的服务例程中,它首先通过fd在文件对象数组中获取文件对象,接着则将fd处的文件对象清空。接下来的大部分工作都通过filp_close函数完成,它主要的工作是调用flush钩子函数将页高速缓存中的数据全部写回磁盘,释放该文件上的所有锁,通过fput函数释放该文件对象。最后返回0或者一个错误码。

建议继续学习

  1. GFS, HDFS, Blob File System架构对比 (阅读 10,342)
  2. MooseFS知多少 (阅读 6,043)
  3. MogileFS 的介绍(MogileFS 系列1) (阅读 4,982)
  4. 一线DBA总结:MySQL搭配XFS文件系统优势最大 (阅读 4,823)
  5. 文件系统的树形结构改善构思 (阅读 4,064)
  6. linux环境下使用GFS文件系统 (阅读 3,663)
  7. MogileFS 的客户端和API(MogileFS 系列4) (阅读 3,443)
  8. Linux下如何迁移VG及文件系统 (阅读 3,383)
  9. MogileFS 的安装(MogileFS 系列2) (阅读 3,303)
  10. MogileFS 的设置和管理(MogileFS 系列3) (阅读 2,903)