Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1217268
  • 博文数量: 298
  • 博客积分: 10050
  • 博客等级: 上将
  • 技术积分: 3277
  • 用 户 组: 普通用户
  • 注册时间: 2007-03-14 13:36
文章分类
文章存档

2015年(7)

2012年(1)

2010年(5)

2009年(55)

2008年(73)

2007年(160)

我的朋友

分类: LINUX

2007-06-11 00:17:51

7)普通文件读写和预读

   generic_file_read 负责普通文件的读取(系统调用read),即可以使用page

cache的一切文件系统。

   系统调用read在文件fs/read_write.c中

asmlinkage ssize_t sys_read(unsigned int fd, char * buf, size_t count)

   sys_read调用文件系统提供的read,我们以ext2为例就是

/*

* We have mostly NULL's here: the current defaults are ok for

* the ext2 filesystem.

*/

struct file_operations ext2_file_operations = {

llseek: ext2_file_lseek,

read: generic_file_read,

write: generic_file_write,

ioctl: ext2_ioctl,

mmap: generic_file_mmap,

open: ext2_open_file,

release: ext2_release_file,

fsync: ext2_sync_file,

};


   一般来讲,文件读取通过 generic_file_read来进行.generic_file_read建立

一个read descriptor,然后交给do_generic_file_read,做真正的读取工作.调用

这个函数的时候传递了一个函数指针:file_read_actor,其作用是复制page内指定

偏移和长度的数据到用户空间.

   先看看do_generic_file_read要处理的几个问题:

   1) page cache: 普通文件缓存于内核的page cahce,引发linux读写文件时将

      文件看作一个以page size为单位的逻辑页面.读取文件就是将用户读取的

      位置和大小转换成逻辑的页面,从page cache找到内存对应的页面,并将内

      容复制到用户缓冲区. 如果未缓存此文件的对应内容,就要从磁盘上的对应

      文件以文件系统自己的方式读取到内存页面并将此页面加入到page cache.

   2) 上面一条是将文件流切割成page 页,然后block_read_full_page(通常是

      这个函数)还会将页面切割为此文件独立的线性block num,最后通过具体的

      文件系统将文件线性的block转换成磁盘线性的block(硬件block num?).

   3) 预读: 用户读取文件的时候内核极力猜测用户的意图,试图在用户使用数据

      前就将数据准备好. 这样可以早期启动磁盘的io操作,以dma方式并行处理.

      并且成批的io操作可以提高吞吐量.linux内核的预读对于顺序读取模式应改

      很有效果.

   4) 隔离各种文件系统读取文件内容的方式. 就是通过给定文件关联的inode,利

      用函数指针mapping->a_ops->readpage读取文件内容. 具体的例子可以看ext2

      struct address_space_operations ext2_aops = {

       readpage: ext2_readpage,

       writepage: ext2_writepage,

       sync_page: block_sync_page,

       prepare_write: ext2_prepare_write,

       commit_write: generic_commit_write,

       bmap: ext2_bmap

     };


     ext2_readpage直接调用block_read_full_page(page,ext2_get_block).就

     是将文件内线性编址的page index 转换为文件线性编址的block(逻辑块).

     其中 ext2_get_block(*inode,iblock,*bh_result,create)将文件的逻辑块

     号转换为块设备的逻辑块号(块设备上线性编址的block num),最后提交设备

     驱动读取指定物理块.(驱动将设备块号转换为扇区编号..^_^)读写文件页面

     的过程仅做此简析,以后分析buffer相关的文件时再细细品味一下.



{[写到这里时候,发生了一些事情,耽搁了两周. 顺便看了看devfs.. go on]}

     具体再分析do_generic_file_read的时候就逻辑清晰了.

/*

* This is a generic file read routine, and uses the

* inode->i_op->readpage() function for the actual low-level

* stuff.

*

* This is really ugly. But the goto's actually try to clarify some

* of the logic when it comes to error handling etc.

*/

void do_generic_file_read(struct file * filp, loff_t *ppos, read_descriptor_t * desc,
read_actor_t actor)

{

struct inode *inode = filp->f_dentry->d_inode;

struct address_space *mapping = inode->i_mapping;

unsigned long index, offset;

struct page *cached_page;  /*不存在于page cache的时候分配的页面,可能用不到

                           *因为获取锁的时候可能等待,被其他执行流抢了先.

                           */

int reada_ok;

int error;

int max_readahead = get_max_readahead(inode);



    /*

     * 在字节流内的位置转换成线性的文件页面流索引

     */

cached_page = NULL;

index = *ppos >> PAGE_CACHE_SHIFT;

offset = *ppos & ~PAGE_CACHE_MASK;



/*

* 看看预读是否有效,及时调整预读量.

* 如果还未曾预读或者被重置,调整read-ahead max的过程就是预读的初始化

*/

/*

* If the current position is outside the previous read-ahead window,

* we reset the current read-ahead context and set read ahead max to zero

* (will be set to just needed value later),

* otherwise, we assume that the file accesses are sequential enough to

* continue read-ahead.

*/

if (index > filp->f_raend || index + filp->f_rawin < filp->f_raend) {

                        /*index < filp->raend - filp->rawin*/

/*如果用户读取范围超出预读窗口则重新计算预读量和起始位置*/

reada_ok = 0;

filp->f_raend = 0;

filp->f_ralen = 0;

filp->f_ramax = 0;

filp->f_rawin = 0;

} else {

reada_ok = 1;

}

/*

* Adjust the current value of read-ahead max.

* If the read operation stay in the first half page, force no readahead.

* Otherwise try to increase read ahead max just enough to do the read request.

* Then, at least MIN_READAHEAD if read ahead is ok,

* and at most MAX_READAHEAD in all cases.

*/

if (!index && offset + desc->count <= (PAGE_CACHE_SIZE >> 1)) {

/*读取文件的前半个页面,不进行预读*/

filp->f_ramax = 0;

} else {

unsigned long needed;

        /*计算需要读入的页面个数, 注*ppos在页面index的offset位置*/

needed = ((offset + desc->count) >> PAGE_CACHE_SHIFT) + 1;



if (filp->f_ramax < needed)

filp->f_ramax = needed; /*预读量至少要满足这次读取请求*/



if (reada_ok && filp->f_ramax < MIN_READAHEAD)

filp->f_ramax = MIN_READAHEAD;

if (filp->f_ramax > max_readahead)

filp->f_ramax = max_readahead;

}



  /*

   * 根据用户要求读取所有请求的页面

   */

for (;;) {

struct page *page, **hash;

unsigned long end_index, nr;



        /*nr:本页面读取的字节数*/

end_index = inode->i_size >> PAGE_CACHE_SHIFT;

if (index > end_index)

break;

nr = PAGE_CACHE_SIZE;

if (index == end_index) {

nr = inode->i_size & ~PAGE_CACHE_MASK;

if (nr <= offset)

break;

}



nr = nr - offset;



/*

* Try to find the data in the page cache..

*/

/* (先在page cache寻找指定文件页) */

hash = page_hash(mapping, index);



spin_lock(&pagecache_lock);

page = __find_page_nolock(mapping, index, *hash);

if (!page)

goto no_cached_page; /*分配页面加入page cache 跳转到 page_ok*/

                        /*如果睡眠后其他执行流将文件的page块加入到

                                *page cache就跳转到 found_page

        */

found_page:

page_cache_get(page); /*先get页面*/

spin_unlock(&pagecache_lock);/*后解锁page cache*/



if (!Page_Uptodate(page))

goto page_not_up_to_date; /*预读,读取本页,然后返回到page_ok*/

generic_file_readahead(reada_ok, filp, inode, page);

page_ok:

/* If users can be writing to this page using arbitrary

* virtual addresses, take care about potential aliasing

* before reading the page on the kernel side.

*/

if (mapping->i_mmap_shared != NULL)

flush_dcache_page(page);



/*

* Ok, we have the page, and it's up-to-date, so

* now we can copy it to user space...

*

* The actor routine returns how many bytes were actually used..

* NOTE! This may not be the same as how much of a user buffer

* we filled up (we may be padding etc), so we can only update

* "pos" here (the actor routine has to update the user buffer

* pointers and the remaining count).

*/

nr = actor(desc, page, offset, nr);

offset += nr;

/*计算下一个要读的页面和偏移*/

index += offset >> PAGE_CACHE_SHIFT;

offset &= ~PAGE_CACHE_MASK;



page_cache_release(page);

if (nr && desc->count)  /*需要继续*/

continue;

break;  /*读取结束*/

       /*

        * for 循环的主流程结束

        */

/*

* 页面没有含有有效数据的情况

*/

/*

* Ok, the page was not immediately readable, so let's try to read ahead while we're
at it..

*/

page_not_up_to_date:

generic_file_readahead(reada_ok, filp, inode, page);



if (Page_Uptodate(page))

goto page_ok;



/* Get exclusive access to the page ... */

lock_page(page);



/* Did it get unhashed before we got the lock? */

if (!page->mapping) {

UnlockPage(page);

page_cache_release(page);

continue;

}



/* Did somebody else fill it already? */

if (Page_Uptodate(page)) {

UnlockPage(page);

goto page_ok;

}



readpage:/*无有效数据和页面不在page cache 的情况也许都要read page (no_cached_page)*/

/* ... and start the actual read. The read will unlock the page. */

error = mapping->a_ops->readpage(filp, page);



if (!error) {

if (Page_Uptodate(page))

goto page_ok;



/* Again, try some read-ahead while waiting for the page to finish.. */

generic_file_readahead(reada_ok, filp, inode, page);

wait_on_page(page);

if (Page_Uptodate(page))

goto page_ok;

error = -EIO;

}



/* UHHUH! A synchronous read error occurred. Report it */

desc->error = error;

page_cache_release(page);

break;

/*

* 未在page cache 发现指定页面,只有分配一个了

*/



no_cached_page:

/*

* Ok, it wasn't cached, so we need to create a new

* page..

*

* We get here with the page cache lock held.

*/

if (!cached_page) {

spin_unlock(&pagecache_lock);

cached_page = page_cache_alloc();

if (!cached_page) {

desc->error = -ENOMEM;

break;

}



/*

* Somebody may have added the page while we

* dropped the page cache lock. Check for that.

*/

spin_lock(&pagecache_lock);

page = __find_page_nolock(mapping, index, *hash);

if (page)

goto found_page;

}



/*

* Ok, add the new page to the hash-queues...

*/

page = cached_page;

__add_to_page_cache(page, mapping, index, hash);

spin_unlock(&pagecache_lock);

cached_page = NULL;



goto readpage;

} /*end for*/



*ppos = ((loff_t) index << PAGE_CACHE_SHIFT) + offset;

filp->f_reada = 1;

if (cached_page)

page_cache_free(cached_page);

UPDATE_ATIME(inode);

}



    函数的分析就是上面的注释.另外一个问题就是预读. do_generic_file_read

当然是进行文件的预读的最好的时机.在这里建立预读的context(一直在想contex

的最佳译法),检查预读是否有效.



    为了搞清楚预读的各个变量我们分三遍读do_generic_file_read,分别对应:

第一次读取文件,第二次读取文件顺序读取,所以预读命中,第三次读取文件,超出

预读窗口. 来看看和generic_file_readahead如何配合.

   条件:

       1) 假设读取不是从0字节开始,比如从8k的地方读

       2) 假设读取的时候进行加锁都比较快,io没有很快完成(这应该是一般

          情况,ide硬盘怎么会有那么快)





第一次读取文件:(假设page cache 无此页面)

   +----do_generic_file_read()

     {

           .......

           if (index > filp->f_raend ||....) {..}

               reada_ok = 0; //read 8k,so exceed reada context

            else{ }

          

       if (!index && offset ...) {     

        }

        else {

             unsigned long needed;

               

              needed = ....;



              if (filp->f_ramax < needed)

               filp->f_ramax = needed;  //f_ramax init

            }

           

        readpage:

            假设第一次读取,所以page cache没有此页面,需要从hd读入,页面已

            锁.

            if (!error) {

            if (Page_Uptodate(page))

                  goto page_ok;//我们假设读取没有很快完成也是很

                  //合理的,哪有那么快

    //所以进行预读的时候页面是加了锁的,reada_ok为0

        generic_file_readahead(reada_ok, filp, inode, page);

      wait_on_page(page);

      if (Page_Uptodate(page))

              goto page_ok;

      error = -EIO;

        }



      }     

           

+--generic_file_readahead()   

   {  

           raend = filp->f_raend; /*=0 */

         max_ahead = 0;  /*本次要启动io的页面之数量*/

          

           if (PageLocked(page)) { //第一次读取文件所以filp->f_ralen 为0

                  if (!filp->f_ralen || index >= raend || index +
filp->f_rawin < raend) {

               //重新建立预读窗口

              raend = index;  //假设"上次预读"结束于当前页面(正在读取的页面)

                              //即,当前锁定的页面是在"预读"

              if (raend < end_index)

                max_ahead = filp->f_ramax; //本次预读filp->f_ramax个页面,

                        //在do_generic_file_read 中已经初始化

              filp->f_rawin = 0; //预读窗口为0,因为还没有预读过(或重新建立预读)

              filp->f_ralen = 1; //上次"预读"了1个页面

              if (!max_ahead) {

                   filp->f_raend  = index +
filp->f_ralen;/*上次预读窗口外的第一个页面*/

                   filp->f_rawin += filp->f_ralen;/*连续有效预读的总个数*/

              }

              }

              }else if (reada_ok ...)

              }

           ahead = 0; /*本次预读的页面个数*/

           while (ahead < max_ahead) {

          在max_ahead个页面上启动预读

         } /*ahead 保持为0*/

           if (ahead) {

               if (reada_ok == 2) {//我们这次reada_ok为0}

           filp->f_ralen += ahead; //f_ralen代表上次预读的个数,这里为此记录

          filp->f_rawin += filp->f_ralen; //f_rawin代表所有连续有效预读的总量

          filp->f_raend = raend + ahead + 1;//f_raend是预读窗口外第一个页面

           filp->f_ramax += filp->f_ramax;//预读有效,下次预读量加倍

              .....

         }

  }



分析: 第一次读取文件page 2,offset 0,filep各项为0,do_generic_file_read将

reada_ok置0. 将filp->f_ramax置为用户读取的页面个数(有上限).

generic_file_readahead为第一次读取文件建立预读档案并预读一定数量的页面.





第二次读取文件:上次进行了预读,假设page cache 已经有此页面,并且是顺序读

取,命中了预读窗口.

   +----do_generic_file_read()

     {

       .......

       if (index > filp->f_raend ||....) {..}

       else{ //命中预读窗口

          reada_ok = 1;   

       }

          

   if (!index && offset ...) {/*读取文件的前半个页面,不进行预读*/

    我们早就不是读前半个页面了     

   }

   else {

         unsigned long needed;

               

          needed = ....;

              //假设上次预读量已经足够了,所以这次f_ramax没有被重置

              //是上次读取量的两倍

          if (filp->f_ramax < needed)

              filp->f_ramax = needed;

      }

           

     for (;;) {

     //我们已经假设page cache存在此页面

     found_page:

       ....

   if (!Page_Uptodate(page))

    goto page_not_up_to_date; /*假设预读已经完成(没有完成也一样)*/

    //所以进行预读的时候页面是没有加锁的,reada_ok为1

      generic_file_readahead(reada_ok, filp, inode, page);

          ............

     }     

  }     

  // reada_ok =1 代表此次读取命中预读窗口(但不一定命中上次预读窗)          

+--generic_file_readahead()   

{  

       raend = filp->f_raend; /*=0 */

     max_ahead = 0;  /*本次要启动io的页面之数量*/

          

       if (PageLocked(page)) {

         //这次没有加锁,^_^          

       }else  if (reada_ok && filp->f_ramax && raend >= 1
&&

        index <= raend && index + filp->f_ralen >= raend) {

   /*命中预读窗口,并且命中上次预读的那部分页面,用户真是步

     *步紧逼啊.我们这次读取如果不是如此,就不会再进行任何预读

     *临时决定就假设如此吧.

     */

      /*页面未锁,或许读取完成,或许还没有开始--->*/

        raend -= 1; /*见注释,保持和同步预读有着同样的io max size*/

        if (raend < end_index)

          max_ahead = filp->f_ramax + 1;

        if (max_ahead) {

         filp->f_rawin = filp->f_ralen;

         filp->f_ralen = 0; /*将上次预读长度(即"上次预读"窗口)清空*/

         reada_ok      = 2; /*--->所以或许要督促一下,尽快开始读取*/

        }

    }

           

      ahead = 0; /*本次预读的页面个数*/

      while (ahead < max_ahead) {

     .....

     if (page_cache_read(filp, raend + ahead) < 0)

       break;

    } /*ahead 保持为0*/

      if (ahead) {

      if (reada_ok == 2) { /*强制unplug*/

           run_task_queue(&tq_disk);

      }

       filp->f_ralen += ahead; //f_ralen代表上次预读的个数,这里为此记录

    filp->f_rawin += filp->f_ralen; //f_rawin代表所有连续有效预读的总量

    filp->f_raend = raend + ahead + 1;//f_raend是预读窗口外第一个页面

    filp->f_ramax += filp->f_ramax;//预读有效,下次预读量加倍color=blue>

              .....

   } 

}



分析: 第二次读取文件如果用户命中上次预读的那几个页面,证明预读有效,极有

可能是顺序读取,故进行预读(预读量是上次的两倍),并再次加倍预读量.(当然有

上限).

第三次读取:未命中预读窗口. 和第一次预读类似. 这里不再列举.

  

  预读分为两种: 同步预读和异步预读.从磁盘读取数据如果是DMA方式,总是异步

的.这里应该是数和用户读取文件同时进行的意思,也就是当前页面已经开始io的

情况之下,页面已经上锁,叫做同步.

  异步读取的时候,调用run_task_queue(&tq_disk), 到底干了些啥?

drivers/block/ll_rw_blk.c 函数generic_plug_device,将request_queue_t放入

task queue :tq_disk.块驱动的task queue里都是什么请求?当然是我们的读/写

啦. 印证一下: 同一个文件的函数

void blk_init_queue(request_queue_t * q, request_fn_proc * rfn)

{

INIT_LIST_HEAD(&q->queue_head);

INIT_LIST_HEAD(&q->request_freelist[READ]);

INIT_LIST_HEAD(&q->request_freelist[WRITE]);

elevator_init(&q->elevator, ELEVATOR_LINUS);

blk_init_free_list(q);

q->request_fn     = rfn;  /*note 0*/

q->back_merge_fn       = ll_back_merge_fn;

q->front_merge_fn      = ll_front_merge_fn;

q->merge_requests_fn = ll_merge_requests_fn;

q->make_request_fn = __make_request;

q->plug_tq.sync = 0;

q->plug_tq.routine = &generic_unplug_device; /*note 1*/

q->plug_tq.data = q;

q->plugged        = 0;

/*

* These booleans describe the queue properties.  We set the

* default (and most common) values here.  Other drivers can

* use the appropriate functions to alter the queue properties.

* as appropriate.

*/

q->plug_device_fn = generic_plug_device; /*note 2*/

q->head_active    = 1;

}


  负责初始化blk驱动的请求队列. 对于ide:见drivers/ide/ide-probe.c

static void ide_init_queue(ide_drive_t *drive)

{

request_queue_t *q = &drive->queue;



q->queuedata = HWGROUP(drive);

blk_init_queue(q, do_ide_request);

}


ide 请求队列中的

             q->request_fn = do_ide_request,

             q->plug_tq.routine = &generic_unplug_device;

             q->plug_device_fn = generic_plug_device;

             q->make_request_fn = __make_request;

首先我们请求读入:

  submit_bh->generic_make_request-> q->make_request_fn**__make_request:

__make_request()

{....

if (list_empty(head)) { //如果当前驱动无其他pending的请求

   //就将队列plug到task queue,这样,可以在一连串的请求都放入

    //请求队列后再开始io,从而可以将连续请求合并到一起


q->plug_device_fn(q, bh->b_rdev); /* is atomic */ /*generic_plug_device*/

goto get_rq;

}

....

add_request-> 将读写请求放入q.

out:

if (!q->plugged) /*如果plug了就不再直接调用request_fn*/

(q->request_fn)(q);  /* do_ide_request*/

    

}



然后当我们直接调用run_task_queue(&tq_disk)->__run_task_queue->

tq_disk->routine**generic_unplug_device->__generic_unplug_device->

q->request_fn**do_ide_request.                                           



  分析完了这些,就可以理解下面的注释了

generic_file_readahead ()

{

  ..........

/*

* .............

* If we tried to read ahead asynchronously,

*   Try to force unplug of the device in order to start an asynchronous

*   read IO request.

* ........

*/

if (ahead) {

if (reada_ok == 2) { /*强制unplug,真正开始异步io操作*/

run_task_queue(&tq_disk);

}

    ....

}  

}     
阅读(1615) | 评论(0) | 转发(1) |
0

上一篇:fsyn,数据读入

下一篇:悠长假期

给主人留下些什么吧!~~