文件的readpage方法

    技术2024-07-25  15

    1.3.3 普通文件的readpage方法

    回到do_mpage_readpage中,mpage_readpage传进来的buffer_head类型的map_bh参数的b_bdevb_blocknrb_size字段就被赋上值了,然后245~271行对这个map_bh进行一系列的检查,检查可能发生的异常条件。具体有这几种情况:当一些块在磁盘上不相邻时,或某块落如“文件洞”内时,或一个块缓冲区已经由get_block函数写入时。那么跳到confused标号处,用一次读一块的方式读该页。有关“文件的洞”的相关内容,请查阅ULK-3的相关章节。

     

    273-282行,很重要,为内部变量blocks[MAX_BUF_PER_PAGE]赋值。由于一页面包含4个块,所以blocks[0]~ blocks[3]就存放着磁盘上是相邻连续4个块的逻辑块号。page_block内部变量也从0变成3了。

     

    如果函数运行至286行,说明页中的所有块在磁盘上是相邻的。然而,它可能是文件中的最后一页,因此页中的一些块可能在磁盘上没有映像。如果这样的话,288行将页中相应的块缓冲区填上0;如果不是这样,298行将页描述符的标志PG_mappedtodisk置位。

     

    继续走,前面看到,我们传递进来的bioNULL的,所以来到308行的条件分支。309行调用调用mpage_alloc触发bio_alloc()函数分配包含单一段的一个新bio描述符,并且分别用块设备描述符地址和页中第一个块的逻辑块号blocks[0]来初始化bi_bdev字段和bi_sector字段。这两个信息已在上面的get_block函数中得到。

     

    通常,bio结构是由slab分配器分配的,但是,当内存不足时,内核也会使用一个备用的bio小内存池。内核也为bio_vec结构分配内存池——毕竟,分配一个bio结构而不能分配其中的段描述符也是没有什么意义的。bio_alloc就通过bio_alloc_bioset函数在bs->bvec_pool中给他的bio_vec分配。相应地,bio_put()函数减少bio中引用计数器(bi_cnt)的值,如果该值等于0,则释放bio结构以及相关的bio_vec结构。

     

    这个biobdev来自buffer_headb_bdev字段;bi_destructor被设置为bio_fs_destructor函数,作为当bio上的I/O操作完成时所执行的完成程序的地址;bi_io_vec通过bvec_alloc_bs函数初始化成数组,表示若干个bio待传输的段;同时bi_flagsbi_max_vecs字段也被设置了;bi_sectorblocks[0]左移(blkbits - 9)位,得到该块对应磁盘扇区号,因为BIO更关心的是扇区号而不是块号,后面谈通用块处理的时候会重点讨论它。另外min_t(int, nr_pages, bio_get_nr_vecs(bdev))表示这个bio中的iovec个数,后面讲到通用块层的时候再来详细说这些东西,很重要的。

     

     

    最后,bio初始化成功了,do_mpage_readpage成功返回这个biompage_readpage就调用mpage_bio_submit(READ, bio)处理请求。

     

    static struct bio *mpage_bio_submit(int rw, struct bio *bio)

    {

           bio->bi_end_io = mpage_end_io_read;

           if (rw == WRITE)

                  bio->bi_end_io = mpage_end_io_write;

           submit_bio(rw, bio);

           return NULL;

    }

     

    我们看到mpage_bio_submit 函数将biobi_end_io字段赋值成 mpage_end_io_read函数,随后调用 submit_bio 函数处理该请求:

     

    void submit_bio(int rw, struct bio *bio)

    {

           int count = bio_sectors(bio);

     

           BIO_BUG_ON(!bio->bi_size);

           BIO_BUG_ON(!bio->bi_io_vec);

           bio->bi_rw |= rw;

           if (rw & WRITE)

                  count_vm_events(PGPGOUT, count);

           else

                  count_vm_events(PGPGIN, count);

     

           if (unlikely(block_dump)) {

                  char b[BDEVNAME_SIZE];

                  printk(KERN_DEBUG "%s(%d): %s block %Lu on %s/n",

                         current->comm, current->pid,

                         (rw & WRITE) ? "WRITE" : "READ",

                         (unsigned long long)bio->bi_sector,

                         bdevname(bio->bi_bdev,b));

           }

     

           generic_make_request(bio);

    }

     

    函数最终将请求传递给函数 generic_make_request ,并由 generic_make_request 函数将请求

    提交给通用块层处理,到此为止,页高速缓存层的处理结束。

     

    1.3.4 块设备文件的readpage方法

    看到do_mpage_readpage函数的confused标号处,如果页中含有的块在磁盘上不连续,则函数跳到这里。如果页是最新的,调用block_read_full_page函数,这里牵涉到块设备文件的readpage方法。

     

    在前面ext2_read_inode函数中,我们看到init_special_inode函数用来建立设备的索引节点。该函数把索引节点对象的i_rdev字段初始化为设备文件的主设备号和次设备号,而把索引节点对象的i_fop字段设置为def_blk_fops或者def_chr_fops文件操作表的地址(根据设备文件的类型);把索引节点的i_data对应的a_ops字段设置为def_blk_aops(字符驱动不需要页高速缓存)操作表地址。因此,open()系统调用的服务例程也调用dentry_open()函数,后者分配一个新的文件对象并把其f_op字段设置为i_fop中存放的地址,即再一次指向def_blk_fopsdef_chr_fops的地址。正是这两个表的引入,才使得在设备文件上所发出的任何系统调用都将激活设备驱动程序的函数而不是基本文件系统的函数。

     

    我们这里是块设备文件,所以关注的是def_blk_fops全局变量,来自fs/block_dev.c

     

    const struct file_operations def_blk_fops = {

           .open             = blkdev_open,

           .release    = blkdev_close,

           .llseek            = block_llseek,

           .read              = generic_file_read,

           .write             = blkdev_file_write,

          .aio_read = generic_file_aio_read,

          .aio_write       = blkdev_file_aio_write,

           .mmap           = generic_file_mmap,

           .fsync             = block_fsync,

           .unlocked_ioctl      = block_ioctl,

    #ifdef CONFIG_COMPAT

           .compat_ioctl  = compat_blkdev_ioctl,

    #endif

           .readv            = generic_file_readv,

           .writev           = generic_file_write_nolock,

           .sendfile  = generic_file_sendfile,

           .splice_read    = generic_file_splice_read,

           .splice_write   = generic_file_splice_write,

    };

     

    块设备文件对应页高速缓存的函数来自def_blk_aops全局变量,也位于同一文件中:

     

    const struct address_space_operations def_blk_aops = {

           .readpage = blkdev_readpage,

           .writepage      = blkdev_writepage,

           .sync_page      = block_sync_page,

           .prepare_write = blkdev_prepare_write,

           .commit_write       = blkdev_commit_write,

           .writepages     = generic_writepages,

           .direct_IO       = blkdev_direct_IO,

    };

     

    bdev特殊文件系统中,块设备使用address_space对象,该对象存放在对应块设备索引节点的i_data字段。不像普通文件(在address_space对象中它的readpage方法依赖于文件所属的文件系统的类型),块设备文件的readpage方法总是相同的。它是由blkdev_readpage()函数实现的,该函数调用block_read_full_page()

    int blkdev_readpage(struct file * file, struct * page page)

    {

        return block_read_full_page(page, blkdev_get_block);

    }

     

    正如你看到的,这个函数又是一个封装函数,这里是block_read_full_page()函数的封装函数。block_read_full_page函数,来自fs/buffer.c

     

    2063int block_read_full_page(struct page *page, get_block_t *get_block)

    2064{

    2065        struct inode *inode = page->mapping->host;

    2066        sector_t iblock, lblock;

    2067        struct buffer_head *bh, *head, *arr[MAX_BUF_PER_PAGE];

    2068        unsigned int blocksize;

    2069        int nr, i;

    2070        int fully_mapped = 1;

    2071

    2072        BUG_ON(!PageLocked(page));

    2073        blocksize = 1 << inode->i_blkbits;

    2074        if (!page_has_buffers(page))

    2075                create_empty_buffers(page, blocksize, 0);

    2076        head = page_buffers(page);

    2077

    2078        iblock = (sector_t)page->index << (PAGE_CACHE_SHIFT - inode->i_blkbits);

    2079        lblock = (i_size_read(inode)+blocksize-1) >> inode->i_blkbits;

    2080        bh = head;

    2081        nr = 0;

    2082        i = 0;

    2083

    2084        do {

    2085                if (buffer_uptodate(bh))

    2086                        continue;

    2087

    2088                if (!buffer_mapped(bh)) {

    2089                        int err = 0;

    2090

    2091                        fully_mapped = 0;

    2092                        if (iblock < lblock) {

    2093                                WARN_ON(bh->b_size != blocksize);

    2094                                err = get_block(inode, iblock, bh, 0);

    2095                                if (err)

    2096                                        SetPageError(page);

    2097                        }

    2098                        if (!buffer_mapped(bh)) {

    2099                                void *kaddr = kmap_atomic(page, KM_USER0);

    2100                                memset(kaddr + i * blocksize, 0, blocksize);

    2101                                flush_dcache_page(page);

    2102                                kunmap_atomic(kaddr, KM_USER0);

    2103                                if (!err)

    2104                                        set_buffer_uptodate(bh);

    2105                                continue;

    2106                        }

    2107                        /*

    2108                         * get_block() might have updated the buffer

    2109                         * synchronously

    2110                         */

    2111                        if (buffer_uptodate(bh))

    2112                                continue;

    2113                }

    2114                arr[nr++] = bh;

    2115        } while (i++, iblock++, (bh = bh->b_this_page) != head);

    2116

    2117        if (fully_mapped)

    2118                SetPageMappedToDisk(page);

    2119

    2120        if (!nr) {

    2121                /*

    2122                 * All buffers are uptodate - we can set the page uptodate

    2123                 * as well. But not if get_block() returned an error.

    2124                 */

    2125                if (!PageError(page))

    2126                        SetPageUptodate(page);

    2127                unlock_page(page);

    2128                return 0;

    2129        }

    2130

    2131        /* Stage two: lock the buffers */

    2132        for (i = 0; i < nr; i++) {

    2133                bh = arr[i];

    2134                lock_buffer(bh);

    2135                mark_buffer_async_read(bh);

    2136        }

    2137

    2138        /*

    2139         * Stage 3: start the IO.  Check for uptodateness

    2140         * inside the buffer lock in case another process reading

    2141         * the underlying blockdev brought it uptodate (the sct fix).

    2142         */

    2143        for (i = 0; i < nr; i++) {

    2144                bh = arr[i];

    2145                if (buffer_uptodate(bh))

    2146                        end_buffer_async_read(bh, 1);

    2147                else

    2148                        submit_bh(READ, bh);

    2149        }

    2150        return 0;

    2151}

     

    block_read_full_page函数的第二个参数也指向一个函数,该函数把相对于文件开始处的文件块号转换为相对于块设备开始处的逻辑块号。不过,对于块设备文件来说,这两个数是一致的;因此,blkdev_get_block()函数就比较简单了,来自:

     

    109blkdev_get_block(struct inode *inode, sector_t iblock,

     110                struct buffer_head *bh, int create)

     111{

     112        if (iblock >= max_block(I_BDEV(inode))) {

     113                if (create)

     114                        return -EIO;

     115

     116                /*

     117                 * for reads, we're just trying to fill a partial page.

     118                 * return a hole, they will have to call get_block again

     119                 * before they can fill it, and they will get -EIO at that

     120                 * time

     121                 */

     122                return 0;

     123        }

     124        bh->b_bdev = I_BDEV(inode);

     125        bh->b_blocknr = iblock;

     126        set_buffer_mapped(bh);

     127        return 0;

     128}

     

    blkdev_get_block函数112行检查页中第一个块的块号是否超过块设备的最后一块的索引值(通过max_block函数将存放在bdev->bd_inode->i_size中的块设备大小除以存放在bdev->bd_block_size中的块大小得到该索引值;bdev指向块设备描述符)。如果超过,那么对于写操作它返-EIO,而对于读操作它返回0。(超出块设备读也是不允许的,但不返回错误代码,内核可以对块设备的最后数据试着发出读请求,而得到的缓冲区页只被部分映射)。

     

    124行设置缓冲区首部的b_dev字段为b_dev125行设置缓冲区首部的b_blocknr字段为文件块号,它将被作为参数传给本函数。最后126行把缓冲区首部的BH_Mapped标志置位,以表明缓冲区首部的b_devb_blocknr字段是有效的。

     

    回到函数block_read_full_page(),这个函数以一次读一块的方式读一页数据。正如我们已看到的当读块设备文件或磁盘上块不相邻的普通文件时都使用该函数。它执行如下步骤:

     

    首先2074行通过检查页描述符的标志PG_private,如果置位,则说明该页与描述组成该页的块的缓冲区首部链表相关;否则,调用create_empty_buffers()来为该页所含所有块缓冲区分配缓冲区首部。页中第一个缓冲区的缓冲区首部地址存放在page->private字段中。每个缓冲区首部的b_this_page字段指向该页中下一个缓冲区的缓冲区首部:

     

    void create_empty_buffers(struct page *page,

                         unsigned long blocksize, unsigned long b_state)

    {

           struct buffer_head *bh, *head, *tail;

     

           head = alloc_page_buffers(page, blocksize, 1);

           bh = head;

           do {

                  bh->b_state |= b_state;

                  tail = bh;

                  bh = bh->b_this_page;

           } while (bh);

           tail->b_this_page = head;

     

           spin_lock(&page->mapping->private_lock);

           if (PageUptodate(page) || PageDirty(page)) {

                  bh = head;

                  do {

                         if (PageDirty(page))

                                set_buffer_dirty(bh);

                         if (PageUptodate(page))

                                set_buffer_uptodate(bh);

                         bh = bh->b_this_page;

                  } while (bh != head);

           }

           attach_page_buffers(page, head);

           spin_unlock(&page->mapping->private_lock);

    }

     

    2078行,从相对于页的文件偏移量(page->index字段)计算出页中第一块的文件块号。2084行进入一个循环,对该页中每个缓冲区的缓冲区首部,执行如下子步骤:

    a) 2085行如果标志BH_Uptodate置位,则跳过该缓冲区继续处理该页的下一个缓冲区。

    b) 2088行如果标志BH_Mapped未置位,并且该块未超出文件尾,则调用作为参数传递进来的blkdev_get_block函数。对于块设备文件,不同的是blkdev_get_block函数把文件块号当作逻辑块号。不管是普通文件还是块设备文件,函数都将逻辑块号存放在相应缓冲区首部的b_blocknr字段中,并将标志BH_Mapped置位(访问普通文件时,如果一个数据块处于“文件洞”中,get_block函数就可能找不到这个块。此时,函数用0填充这个块缓冲区并设置缓冲区首部的BH_Uptodate标志。)。

    c) 2098行再检查标志BH_Uptodate,因为依赖于文件系统的get_block函数可能已触发块I/O操作而更新了缓冲区。如果BH_Uptodate置位,则继续处理该页的下一个缓冲区。

    d) 2114行将缓冲区首部的地址存放在局部数组arr中,继续该页的下一个缓冲区。

     

    2117行假如上一步中没遇到“文件洞”,则2118行将该页的标志PG_mappedtodisk置位。

     

    现在局部变量arr中存放了一些缓冲区首部的地址,与其对应的缓冲区的内容不是最新的。如果数组,即2120行判断nr0为空,那么页中的所有缓冲区都是有效的,因此,该函数设置页描述符的PG_uptodate标志,调用unlock_page()对该页解锁并返回。

     

    局部数组arr非空。对数组中的每个缓冲区首部,block_read_full_page()再执行下列子步骤:

    a) 2134行将BH_Lock标志置位。该标志一旦置位,函数将一直等到该缓冲区释放。

    b) 2135行将缓冲区首部的b_end_io字段设为end_buffer_async_read()函数的地址(见下面),并将缓冲区首部的BH_Async_Read标志置位。

     

    2143-2149行对局部数组arr中的每个缓冲区首部调用submit_bh(),将操作类型设为READ。就像我们在前面看到的那样,该函数触发了相应块的I/O数据传输。

     

    函数end_buffer_async_read()是缓冲区首部的完成方法。对块缓冲区的I/O数据传输一结束,它就执行。假定没有I/O错误,函数将缓冲区首部的BH_Uptodate标志置位而将BH_Async_Read标志清0。那么,函数就得到包含块缓冲区的缓冲区页描述符(它的地址存放在缓冲区首部的b_page字段中),同时检查是否页中所有块是最新的;如果是,函数将该页的PG_uptodate标志置位并调用unlock_page()

     

    static void end_buffer_async_read(struct buffer_head *bh, int uptodate)

    {

           unsigned long flags;

           struct buffer_head *first;

           struct buffer_head *tmp;

           struct page *page;

           int page_uptodate = 1;

     

           BUG_ON(!buffer_async_read(bh));

     

           page = bh->b_page;

           if (uptodate) {

                  set_buffer_uptodate(bh);

           } else {

                  clear_buffer_uptodate(bh);

                  if (printk_ratelimit())

                         buffer_io_error(bh);

                  SetPageError(page);

           }

     

           /*

            * Be _very_ careful from here on. Bad things can happen if

            * two buffer heads end IO at almost the same time and both

            * decide that the page is now completely done.

            */

           first = page_buffers(page);

           local_irq_save(flags);

           bit_spin_lock(BH_Uptodate_Lock, &first->b_state);

           clear_buffer_async_read(bh);

           unlock_buffer(bh);

           tmp = bh;

           do {

                  if (!buffer_uptodate(tmp))

                         page_uptodate = 0;

                  if (buffer_async_read(tmp)) {

                         BUG_ON(!buffer_locked(tmp));

                         goto still_busy;

                  }

                  tmp = tmp->b_this_page;

           } while (tmp != bh);

           bit_spin_unlock(BH_Uptodate_Lock, &first->b_state);

           local_irq_restore(flags);

     

           /*

            * If none of the buffers had errors and they are all

            * uptodate then we can set the page uptodate.

            */

           if (page_uptodate && !PageError(page))

                  SetPageUptodate(page);

           unlock_page(page);

           return;

     

    still_busy:

           bit_spin_unlock(BH_Uptodate_Lock, &first->b_state);

           local_irq_restore(flags);

           return;

    }

     

    最新回复(0)