Linuxカーネルファイルシステム-ページキャッシュ

22648 ワード

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      readif (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;
    }
  • ext 2を例にext 2の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