Linux内核文件系统-页高速缓存

简介:

Linux内核的VFS是非常经典的抽象,不仅抽象出了flesystem,super_block,inode,dentry,file等结构,而且还提供了像页高速缓存层的通用接口,当然,你可以自己选择是否使用或者自己定制使用方式。本文主要根据自己阅读Linux Kernel 3.19.3系统调用read相关的源码来追踪页高速缓存在整个流程中的痕迹,以常规文件的页高速缓存为例,了解页高速缓存的实现过程,不过于追究具体bio请求的底层细节。另外,在写操作的过程中,页高速缓存的处理流程有所不同(回写),涉及的东西更多,本文主要关注读操作。Linux VFS相关的重要数据结构及概念可以参考Document目录下的vfs.txt


1.与页高速缓存相关的重要数据结构

除了前述基本数据结构以外,struct address_space 和 struct address_space_operations也在页高速缓存中起着极其重要的作用。

  • address_space结构通常被struct page的一个字段指向,主要存放已缓存页面的相关信息,便于快速查找对应文件的缓存页面,具体查找过程是通过radix tree结构的相关操作实现的。
  • address_space_operations结构定义了具体读写页面等操作的钩子,比如生成并发送bio请求,我们可以定制相应的函数实现自己的读写逻辑。
//include/linux/fs.h
struct address_space {
    //指向文件的inode,可能为NULL
    struct inode        *host;  
    //存放装有缓存数据的页面
    struct radix_tree_root  page_tree;  
    spinlock_t      tree_lock;  
    atomic_t        i_mmap_writable;
    struct rb_root      i_mmap; 
    struct list_head    i_mmap_nonlinear;
    struct rw_semaphore i_mmap_rwsem;
    //已缓存页的数量
    unsigned long       nrpages;    
    unsigned long       nrshadows;  
    pgoff_t         writeback_index;
    //address_space相关操作,定义了具体读写页面的钩子
    const struct address_space_operations *a_ops;   
    unsigned long       flags;  
    struct backing_dev_info *backing_dev_info; 
    spinlock_t      private_lock;   
    struct list_head    private_list;   
    void            *private_data;
} __attribute__((aligned(sizeof(long))));
//include/linux/fs.h 
struct address_space_operations {
    //具体写页面的操作
    int (*writepage)(struct page *page, struct writeback_control *wbc);
    //具体读页面的操作
    int (*readpage)(struct file *, struct page *);
    int (*writepages)(struct address_space *, struct writeback_control *);
    //标记页面脏
    int (*set_page_dirty)(struct page *page);
    int (*readpages)(struct file *filp, struct address_space *mapping, struct list_head *pages, unsigned nr_pages);
    int (*write_begin)(struct file *, struct address_space  *mapping, loff_t pos, unsigned len, unsigned flags, struct page **pagep, void **fsdata);
    int (*write_end)(struct file *, struct address_space *mapping, loff_t pos, unsigned len, unsigned copied, struct page *page, void *fsdata);
    sector_t (*bmap)(struct address_space *, sector_t);
    void (*invalidatepage) (struct page *, unsigned int, unsigned int);
    int (*releasepage) (struct page *, gfp_t);
    void (*freepage)(struct page *);
    ssize_t (*direct_IO)(int, struct kiocb *, struct iov_iter *iter, loff_t offset);
    int (*get_xip_mem)(struct address_space *, pgoff_t, int, void **, unsigned long *);

    int (*migratepage) (struct address_space *, struct page *, struct page *, enum migrate_mode);
    int (*launder_page) (struct page *);
    int (*is_partially_uptodate) (struct page *, unsigned long, unsigned long);
    void (*is_dirty_writeback) (struct page *, bool *, bool *);
    int (*error_remove_page)(struct address_space *, struct page *);
    /* swapfile support */
    int (*swap_activate)(struct swap_info_struct *sis, struct file *file, sector_t *span);
    void (*swap_deactivate)(struct file *file);
};

2.系统调用read流程与页高速缓存相关代码分析

关于挂载和打开文件的操作,不赘述(涉及的细节也很多…),(极其)简陋地理解,挂载返回挂载点的root dentry,并且读取磁盘数据生成了super_block链接到全局超级块链表中,这样,当前进程就可以通过root dentry找到其inode,从而找到并生成其子树的dentry和inode信息,从而实现查找路径的逻辑。打开文件简单理解就是分配fd,通过dentry将file结构与对应inode挂接,最后安装到进程的打开文件数组中,这里假设已经成功打开文件,返回了fd,我们从系统调用read开始。

  • 定义系统调用read
//定义系统调用read
//fs/read_write.c
SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
    //根据fd number获得struct fd
    struct fd f = fdget_pos(fd);
    ssize_t ret = -EBADF;
    if (f.file) {
        //偏移位置
        loff_t pos = file_pos_read(f.file);
        //进入vfs_read
        //参数:file指针,用户空间buffer指针,长度,偏移位置
        //主要做一些验证工作,最后进入__vfs_read
        ret = vfs_read(f.file, buf, count, &pos);
        if (ret >= 0)
            file_pos_write(f.file, pos);
        fdput_pos(f);
    }
    return ret;
}
  • 进入__vfs_read
//fs/read_write.c
ssize_t __vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{
    ssize_t ret;
    //注意这,我们可以在file_operations中定义自己的read操作,使不使用页高速缓存可以自己控制
    if (file->f_op->read)
        ret = file->f_op->read(file, buf, count, pos);
    else if (file->f_op->aio_read)
        //会调用f_ops->read_iter
        ret = do_sync_read(file, buf, count, pos);
    else if (file->f_op->read_iter)
        //会调用f_ops->read_iter
        //这里ext2中又将read_iter直接与generic_file_read_iter挂接,使用内核自带的read操作,稍后会以ext2为例分析
        ret = new_sync_read(file, buf, count, pos);
    else
        ret = -EINVAL;
    return ret;
}
  • 以ext2为例,进入ext2的file_operations->read
//fs/ext2/file.c
const struct file_operations ext2_file_operations = {
    .llseek     = generic_file_llseek,
    .read       = new_sync_read,  //重定向到read_iter此处即generic_file_read_iter
    .write      = new_sync_write,
    .read_iter  = generic_file_read_iter, //使用内核自带的通用读操作,这里会进入页高速缓冲的部分
    .write_iter = generic_file_write_iter,
    .unlocked_ioctl = ext2_ioctl,
#ifdef CONFIG_COMPAT
    .compat_ioctl   = ext2_compat_ioctl,
#endif
    .mmap       = generic_file_mmap,
    .open       = dquot_file_open,
    .release    = ext2_release_file,
    .fsync      = ext2_fsync,
    .splice_read    = generic_file_splice_read,
    .splice_write   = iter_file_splice_write,
};
  • 进入generic_file_read_iter
ssize_t
generic_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
{
    struct file *file = iocb->ki_filp;
    ssize_t retval = 0;
    loff_t *ppos = &iocb->ki_pos;
    loff_t pos = *ppos;
    /* coalesce the iovecs and go direct-to-BIO for O_DIRECT */
    if (file->f_flags & O_DIRECT) {
        struct address_space *mapping = file->f_mapping;
        struct inode *inode = mapping->host;
        size_t count = iov_iter_count(iter);
        loff_t size;
        if (!count)
            goto out; /* skip atime */
        size = i_size_read(inode);
        //先写?
        retval = filemap_write_and_wait_range(mapping, pos,
                    pos + count - 1);
        if (!retval) {
            struct iov_iter data = *iter;
            retval = mapping->a_ops->direct_IO(READ, iocb, &data, pos);
        }
        if (retval > 0) {
            *ppos = pos + retval;
            iov_iter_advance(iter, retval);
        }

        /*
         * Btrfs can have a short DIO read if we encounter
         * compressed extents, so if there was an error, or if
         * we've already read everything we wanted to, or if
         * there was a short read because we hit EOF, go ahead
         * and return.  Otherwise fallthrough to buffered io for
         * the rest of the read.
         */
        if (retval < 0 || !iov_iter_count(iter) || *ppos >= size) {
            file_accessed(file);
            goto out;
        }
    }
    //进入真正read,在address_space的radix tree中查找
    //偏移的page,如果找到,直接copy到用户空间如果未找到,
    //则调用a_ops->readpage读取发起bio,分配cache page,
    //读入数据,加入radix,然后拷贝到用户空间,完成读取数据的过程.
    retval = do_generic_file_read(file, ppos, iter, retval);
out:
    return retval;
}
EXPORT_SYMBOL(generic_file_read_iter);
  • 进入do_generic_file_read
    这个函数基本是整个页高速缓存的核心了,在具体的bio操作请求操作之前判断是否存在缓存页面,如果存在拷贝数据到用户空间,否则分配新页面,调用具体文件系统address_space_operations->readpage读取块数据到页面中,并且加入到radix tree中。
static ssize_t do_generic_file_read(struct file *filp, loff_t *ppos,struct iov_iter *iter, ssize_t written)
{
    /* 省略部分 */
    for (;;) {
        struct page *page;
        pgoff_t end_index;
        loff_t isize;
        unsigned long nr, ret;
        //读页面的过程中可能重新调度
        cond_resched();
find_page:
        //redix tree中查找 
        page = find_get_page(mapping, index);
        //没找到
        if (!page) {
            //先读到页缓存
            //分配list page_pool
            //调用a_ops->readpages or a_ops->readpage读取数据
            //a_ops->readpage负责提交bio
            page_cache_sync_readahead(mapping,
                    ra, filp,
                    index, last_index - index);
            //再找
            page = find_get_page(mapping, index);
            //还是没找到...
            if (unlikely(page == NULL))
                //去分配页面再读
                goto no_cached_page;
        }
        //readahead related 
        if (PageReadahead(page)) {
            page_cache_async_readahead(mapping,
                    ra, filp, page,
                    index, last_index - index);
        }
        //不是最新
        if (!PageUptodate(page)) {
            if (inode->i_blkbits == PAGE_CACHE_SHIFT ||
                    !mapping->a_ops->is_partially_uptodate)
                goto page_not_up_to_date;
            if (!trylock_page(page))
                goto page_not_up_to_date;

            if (!page->mapping)
                goto page_not_up_to_date_locked;
            if (!mapping->a_ops->is_partially_uptodate(page,
                            offset, iter->count))
                goto page_not_up_to_date_locked;
            unlock_page(page);
        }
page_ok: //好,拿到的cached page正常了

         /* 省略其他检查部分 */

        //到这,从磁盘中读取块到page cache或者本身page cache存在,一切正常,拷贝到用户空间
        ret = copy_page_to_iter(page, offset, nr, iter);
        offset += ret;
        index += offset >> PAGE_CACHE_SHIFT;
        offset &= ~PAGE_CACHE_MASK;
        prev_offset = offset;

        //释放页面
        page_cache_release(page);
        written += ret;
        if (!iov_iter_count(iter))
            goto out;
        if (ret < nr) {
            error = -EFAULT;
            goto out;
        }
        //继续
        continue;

page_not_up_to_date:
        /* Get exclusive access to the page ... */
        error = lock_page_killable(page);
        if (unlikely(error))
            goto readpage_error;

page_not_up_to_date_locked:
        /* Did it get truncated before we got the lock? */
        if (!page->mapping) {
            unlock_page(page);
            page_cache_release(page);
            continue;
        }
        /* Did somebody else fill it already? */
        if (PageUptodate(page)) {
            unlock_page(page);
            goto page_ok;
        }

readpage: //为了no_cached_page
        /*
         * A previous I/O error may have been due to temporary
         * failures, eg. multipath errors.
         * PG_error will be set again if readpage fails.
         */
        ClearPageError(page);
        /* Start the actual read. The read will unlock the page. */
        //还是调用a_ops->readpage 
        error = mapping->a_ops->readpage(filp, page);
        if (unlikely(error)) {
            if (error == AOP_TRUNCATED_PAGE) {
                page_cache_release(page);
                error = 0;
                goto find_page;
            }
            goto readpage_error;
        }
        if (!PageUptodate(page)) {
            error = lock_page_killable(page);
            if (unlikely(error))
                goto readpage_error;
            if (!PageUptodate(page)) {
                if (page->mapping == NULL) {
                    /*
                     * invalidate_mapping_pages got it
                     */
                    unlock_page(page);
                    page_cache_release(page);
                    goto find_page;
                }
                unlock_page(page);
                shrink_readahead_size_eio(filp, ra);
                error = -EIO;
                goto readpage_error;
            }
            unlock_page(page);
        }
        //page ok
        goto page_ok;

readpage_error:
        /* UHHUH! A synchronous read error occurred. Report it */
        page_cache_release(page);
        goto out;

no_cached_page:
        /*
         * Ok, it wasn't cached, so we need to create a new
         * page..
         */
        //从冷页面链表中拿一个page
        page = page_cache_alloc_cold(mapping);
        if (!page) {
            error = -ENOMEM;
            goto out;
        }
        //加入cache
        error = add_to_page_cache_lru(page, mapping,
                        index, GFP_KERNEL);
        if (error) {
            page_cache_release(page);
            if (error == -EEXIST) {
                error = 0;
                goto find_page;
            }
            goto out;
        }
        goto readpage;
    }
/* 省略部分 */

ref: Linux Kernel 3.19.3 source code

相关文章
|
7天前
|
算法 Linux 调度
深入理解Linux内核调度器:从基础到优化####
本文旨在通过剖析Linux操作系统的心脏——内核调度器,为读者揭开其高效管理CPU资源的神秘面纱。不同于传统的摘要概述,本文将直接以一段精简代码片段作为引子,展示一个简化版的任务调度逻辑,随后逐步深入,详细探讨Linux内核调度器的工作原理、关键数据结构、调度算法演变以及性能调优策略,旨在为开发者与系统管理员提供一份实用的技术指南。 ####
33 4
|
2天前
|
算法 Linux 开发者
Linux内核中的锁机制:保障并发控制的艺术####
本文深入探讨了Linux操作系统内核中实现的多种锁机制,包括自旋锁、互斥锁、读写锁等,旨在揭示这些同步原语如何高效地解决资源竞争问题,保证系统的稳定性和性能。通过分析不同锁机制的工作原理及应用场景,本文为开发者提供了在高并发环境下进行有效并发控制的实用指南。 ####
|
8天前
|
存储 运维 监控
深入Linux基础:文件系统与进程管理详解
深入Linux基础:文件系统与进程管理详解
48 8
|
10天前
|
缓存 资源调度 安全
深入探索Linux操作系统的心脏——内核配置与优化####
本文作为一篇技术性深度解析文章,旨在引领读者踏上一场揭秘Linux内核配置与优化的奇妙之旅。不同于传统的摘要概述,本文将以实战为导向,直接跳入核心内容,探讨如何通过精细调整内核参数来提升系统性能、增强安全性及实现资源高效利用。从基础概念到高级技巧,逐步揭示那些隐藏在命令行背后的强大功能,为系统管理员和高级用户打开一扇通往极致性能与定制化体验的大门。 --- ###
36 9
|
9天前
|
缓存 负载均衡 Linux
深入理解Linux内核调度器
本文探讨了Linux操作系统核心组件之一——内核调度器的工作原理和设计哲学。不同于常规的技术文章,本摘要旨在提供一种全新的视角来审视Linux内核的调度机制,通过分析其对系统性能的影响以及在多核处理器环境下的表现,揭示调度器如何平衡公平性和效率。文章进一步讨论了完全公平调度器(CFS)的设计细节,包括它如何处理不同优先级的任务、如何进行负载均衡以及它是如何适应现代多核架构的挑战。此外,本文还简要概述了Linux调度器的未来发展方向,包括对实时任务支持的改进和对异构计算环境的适应性。
30 6
|
10天前
|
缓存 Linux 开发者
Linux内核中的并发控制机制:深入理解与应用####
【10月更文挑战第21天】 本文旨在为读者提供一个全面的指南,探讨Linux操作系统中用于实现多线程和进程间同步的关键技术——并发控制机制。通过剖析互斥锁、自旋锁、读写锁等核心概念及其在实际场景中的应用,本文将帮助开发者更好地理解和运用这些工具来构建高效且稳定的应用程序。 ####
29 5
|
10天前
|
算法 Unix Linux
深入理解Linux内核调度器:原理与优化
本文探讨了Linux操作系统的心脏——内核调度器(Scheduler)的工作原理,以及如何通过参数调整和代码优化来提高系统性能。不同于常规摘要仅概述内容,本摘要旨在激发读者对Linux内核调度机制深层次运作的兴趣,并简要介绍文章将覆盖的关键话题,如调度算法、实时性增强及节能策略等。
|
11天前
|
存储 监控 安全
Linux内核调优的艺术:从基础到高级###
本文深入探讨了Linux操作系统的心脏——内核的调优方法。文章首先概述了Linux内核的基本结构与工作原理,随后详细阐述了内核调优的重要性及基本原则。通过具体的参数调整示例(如sysctl、/proc/sys目录中的设置),文章展示了如何根据实际应用场景优化系统性能,包括提升CPU利用率、内存管理效率以及I/O性能等关键方面。最后,介绍了一些高级工具和技术,如perf、eBPF和SystemTap,用于更深层次的性能分析和问题定位。本文旨在为系统管理员和高级用户提供实用的内核调优策略,以最大化Linux系统的效率和稳定性。 ###
|
10天前
|
Java Linux Android开发
深入探索Android系统架构:从Linux内核到应用层
本文将带领读者深入了解Android操作系统的复杂架构,从其基于Linux的内核到丰富多彩的应用层。我们将探讨Android的各个关键组件,包括硬件抽象层(HAL)、运行时环境、以及核心库等,揭示它们如何协同工作以支持广泛的设备和应用。通过本文,您将对Android系统的工作原理有一个全面的认识,理解其如何平衡开放性与安全性,以及如何在多样化的设备上提供一致的用户体验。
|
9天前
|
缓存 运维 网络协议
深入Linux内核架构:操作系统的核心奥秘
深入Linux内核架构:操作系统的核心奥秘
27 2
下一篇
无影云桌面