📄 006_mm_filemap_c.html
字号:
/* ui edited css */ body { font-family: Verdana; font-size: 10.0pt; line-height: normal; background-color: #ffffff; } .documentBG { background-color: #ffffff; } /* end ui edited css */</style> </head> <body revision="dcbsxfpf_47dqwb7fcm:31"> <div align=center id=xsuh>
<table align=center border=0 cellpadding=0 cellspacing=0 height=5716 id=e9op width=768>
<tbody id=l.5r>
<tr id=yjmc>
<td height=5716 id=rbiz valign=top width=100%>
<pre id=n8iy>2005-11-25 11:50<br id=s25d>mm/filemap.c<br id=y41v> page cache,buffer cache,lru cache,swap cache <br id=bb5v><br id=einp><font id=kz.q size=4><b id=ly1.>第一部分</b>--><b id=vi7x>综述</b>:</font><br id=x6lm><br id=kdb0> 首先概要介绍page cache和inode, page cache 和buffer cache,page cache和swap cache,page cache和lru cache, buffer cache<br id=i:sj>和lru的相互关系.<br id=dq06><br id=df90><b id=vozw>0.page cache, buffer cache和lru cache的组成 </b><br id=tl6.> filemap.c开头定义了一张hash表,是一个一维数组,每一项是一个指针,此指针指向page结构.进入此hash表的page页面基本上就进入了page<br id=t3l:>cache:<br id=v13c> struct page **page_hash_table;<br id=ak3h>page cache 还包括 struct address_space 内的几个队列(inode queue):<br id=h0is> struct list_head clean_pages; /* list of clean pages */<br id=m2sw> struct list_head dirty_pages; /* list of dirty pages */<br id=rt3m> struct list_head locked_pages; /* list of locked pages */<br id=dr1b> <br id=a4d4> fs/buffer.c也有类似的hash数组,那是buffer cache.<br id=htdc> mm/page_alloc.c定义了两个lru队列: <br id=k_mr> struct list_head active_list;<br id=x1q_> struct list_head inactive_dirty_list;<br id=xjdp>加上zone_t机构的<br id=cbrk> struct list_head inactive_clean_list;<br id=suig>构成lru cache.<br id=am48> <br id=tozj> page结构为这些cache 链表准备了几个成员变量:<br id=p-:q>typedef struct page {<br id=dixd> struct list_head list; /*由buddy或者inode queue使用*/<br id=x9q9> struct address_space *mapping;<br id=y.xw> unsigned long index;<br id=muoj> struct page *next_hash; /*page 在hash表中时,指向下一个page*/<br id=rbf2> atomic_t count;<br id=lvm8> unsigned long flags; <br id=b2wr> struct list_head lru; /*lru cache 使用*/<br id=muzt> unsigned long age;<br id=vkzo> wait_queue_head_t wait;<br id=i30l> struct page **pprev_hash;/*page在hash表中时,指向上一个节点<br id=le6x> *指向自己的指针<br id=zv39> */<br id=dxp5> struct buffer_head * buffers;<br id=f_7o> void *virtual; /* non-NULL if kmapped */<br id=gl4d> struct zone_struct *zone;<br id=r-0n>} mem_map_t;<br id=f2sv><br id=tjp9> <br id=l9lh><br id=ald.><b id=da25>1. page cache 和 inode</b><br id=axoo> page cache 在代码中又称 inode page cache, 足以显示page cache 和inode<br id=wdd:>紧密关联.加入page cache 和加入inode cache是同一个意思.加入page cache<br id=l033>意味着同时加入page cache hash表和inode queue(也建立了page和addr sapce<br id=alwh>的关系). 见函数add_to_page_cache_locked,__add_to_page_cache即可取证.<br id=jp_i>从page cache 删除在程序中叫__remove_inode_page,再次显示inode 和page <br id=e6hp>cache的"一体化".<br id=tu8-> 加入/离开page cache还涉及到如下几个函数:<br id=ewz.> add_page_to_hash_queue /*加入pache cache hash表*/<br id=zs.r> add_page_to_inode_queue /*加入inode queue即address_space*/<br id=jvw6> remove_page_from_inode_queue<br id=tjkk> remove_page_from_hash_queue<br id=prhf> __remove_inode_page /*离开inode queue和hash 表*/<br id=hlt1> remove_inode_page /*同上*/<br id=h9lx> add_to_page_cache_locked /*加入inode queue,hash 和lru cache*/<br id=glw:> __add_to_page_cache /*同上*/ <br id=f51w> 仅罗列函数add_page_to_hash_queue,以示完整:<br id=l36_>static void add_page_to_hash_queue(struct page * page, struct page **p)<br id=tr9t>{<br id=c6jk> struct page *next = *p;<br id=u9my><br id=kv.:> *p = page; /* page->newNode */<br id=wps0> page->next_hash = next; /* +-----+ */<br id=bozt> page->pprev_hash = p; /* p--> |hashp|-->|oldNode| */<br id=nash> if (next) /* next----+ */<br id=yb-1> next->pprev_hash = &page->next_hash;<br id=kzyy> if (page->buffers)<br id=glsg> PAGE_BUG(page); /*证明page 不会同时存在于page cache<br id=cmut> 和 buffer cache*/<br id=mg58> /*2.6 已经与此不同了*/<br id=ou13> atomic_inc(&page_cache_size);<br id=oa-z>}<br id=k8yh> <br id=ggut><br id=leaf><b id=snez>2. page cache 和buffer cache</b><br id=gdx1>page 不会同时存在于 buffer cache 和 page cache.add_page_to_hash_queue<br id=rd5l>将此思想显露无余.buffer_head 定义在fs.h,和文件系统有着更为紧密的关系.<br id=z_t0>从文件读写角度看buffer cache缓存文件系统的管理信息像root entry, inod等,<br id=i618>而page cache缓存文件的内容.看看read 一个普通文件的流程:<br id=z6a9> sys_read ->file->f_op->read(以ext2为例) <br id=j.rw> +<br id=rlwu> ext2_file_operations<br id=zkg:> +<br id=mbkd> generic_file_read->do_generic_file_read(this file,filemap.c)<br id=hxb7> +<br id=vdfw> 从page cache寻找指定页__find_page_nolock <br id=ltp9> +<br id=n6n:> 如果没有找到则从文件读取mapping->a_ops->readpage<br id=b9-n> +<br id=v9gu> ext2_aops<br id=wq0l> +------<<<---------------<------+<br id=aqqs> ext2_readpage->block_read_full_page(fs/buffer.c,buffer cache) <br id=hwfi> 注意函数block_read_full_page,虽然位于buffer.c,但并没有使用buffercache. 但是确实使用了buffer:只是再指定page上创建<br id=jan:>buffer提交底层驱动读取文件内容.这个流程有两个值得注意的地方,一是普通file的read通过pagecache进行,二是page cache读取的时<br id=mm68>候不和buffer cache进行同步,三是page cache的确使用了buffer,不过注意,buffer 不是buffer cache. <br id=e233> <br id=wiiu> mmap也使用page cache 缓冲文件,流程如下:<br id=p3y.> do_mmap->ext2_file_operations<br id=yfff> +<br id=vwfn> generic_file_mmap<br id=sukc> +<br id=csez> 以共享映射为例file_shared_mmap<br id=k56s> +<br id=lbpk> filemap_nopage(filemap,this file)先找page cache<br id=qtg2> +<br id=vfi3> ext2_aops 否则从文件读取<br id=rg6v> + <br id=qoiu> block_read_full_page<br id=sh7y> <br id=xj34>如果打开象/dev/hda1这种设备文件,其内容缓存于buffer cache,流程如下:<br id=j9-j> def_blk_fops<br id=pwkd> +<br id=d.we> block_read(fs/block_dev.c)<br id=jb8f> +----->先用函数getblk从buffer cache查找<br id=w85r> +----->否则使用ll_rw_block从驱动读取<br id=giki>注意到block_read和block_read_full_page都采用提交驱动的方式读取数据,<br id=kom2>验证了page cache和buffer cache间的确没有数据同步.<br id=vat8> buffer cache 提供了getblk和bread两个接口,从buffer cache获取数据<br id=xag->搜索调用者的话,可以看到ext2文件系统从buffer cache获取的内容没有普通<br id=xizz>文件的数据,而是inod,dentry等数据.<br id=hhzz><br id=h.9v><br id=zf51><br id=f041><b id=qrf5>3.swap cache和page cache </b><br id=pqdl>swap cache是一个特殊的page cache,不同之处在于address_space是swapper<br id=lr3h>_space.和page cache一样也挂入page cache hash queue.加入swap space的函<br id=saa9>数add_to_swap_cache其实就是调用add_to_page_cache_locked.<br id=iq1b><br id=utnc><br id=f7df><b id=n0xc>4.page cache 和 lru cache</b><br id=ps10> 进入page cache的页面必然加入lru cache(lru_cache_add).通过函数<br id=jr4o>__add_to_page_cache和add_to_page_cache_locked即可确信这一点.从page <br id=lqu8>cache 删除的时候也同时从lru cache删除. 搜索对__lru_cache_del的调用,<br id=e:ko>即可发现filemap,shmem,swap cache在使用到page cache的时候都是如此操作.<br id=xv3s> 注意,加入lru cache则不一定加入page cache,如 5)所述的buffer cache.<br id=c:xh>顺便述说一下lru cache相关的几个kthread和其大致作用:<br id=gvmy>*****kswapd (mm/vmscan.c)<br id=j:sw> +---->do_try_to_free_pages (如果内存已经不够用)<br id=pkab> +-->page_launder<br id=lvfg> | +-->扫描 <inactive_dirty_list><br id=sstu> | +-->对dirty页启动回写(包括mapping和buffer cache)<br id=mytx> +-->refill_inactive<br id=row0> +-->refill_inactive_scan <br id=u2jm> +-->扫描<active_list>,选择合适页面移入<br id=ebpm> <inactive_dirty_list><br id=vzyw> +-->swap_out,对进程启动页面换出<br id=jr8d> +-->try_to_swap_out将选中页面放入<br id=z6tj> <inactive_dirty_list><br id=qs8m> +----->refill_inactive_scan<br id=e-85><br id=ysyj>*****kreclaimd(mm/vmscan.c)<br id=ap8x> +----->遍历每个zone 用reclaim_page<br id=xp1m> 扫描zone->inactive_clean_pages,找出可以释放的页面<br id=w68.> 脱离lru cache<br id=erkg> +----->对reclaim_page找到的页面补充到buddy系统<br id=q2ok> <br id=pm0g>*****bdflush<br id=pgua> +---->flush_dirty_buffers (提交buffer cache到驱动)<br id=yd5b> +----->如页面短缺,进行page_launder<br id=n985> <br id=wed:> <br id=w.gb><b id=uc2.>5.buffer cache和lru队列</b><br id=y6ac> buffer cache 的确也使用了lru队列,grow_buffers调用lru_cache_add将页面加入lru队列.但是却没有加入到page cache.(请阅读代<br id=kmc5>码)<br id=ivfn> kreclaimd->reclaim_page将会尝试回收clean 页面到zone的buddy系统,如果page->buffers不空,代表page被buffer cache 使用,那<br id=sylu>么reclaim_page只是将页面转移到inactive_dirty_list.当reclaim_page发现buffer cache 的页面可以回收时,因为此种页面不在page <br id=wiuj>cache也不在swap cache, 只是从lru摘除,然后直接释放.<br id=ig7_> buffer cache如此使用lru cache,作为自己的垃圾回收方式. <br id=sf0r> page_launder处理inactive_dirty_list将页面写入"硬盘",使页面可以释放或者放入inactive_clean队列(大致描述).page_launder对<br id=vidd>buffer cache使用的页面做特殊处理 <br id=j57i> page_launder() ------------>mm/vmscan.c <br id=zqv9> if (page->buffers) {<br id=pmps> ...<br id=nt17> try_to_free_buffers<br id=rx_v> ...<br id=hxe4> }<br id=xuy7>try_to_free_buffers是buffer cache提供给lru的函数,buffer cache自己从不使用,这证实了buffer cache的确利用lru cache回收内存.<br id=p6es> <br id=vovf> <br id=fdg2><font id=f3bn size=3><b id=zvav>第二部分 ---> buffer cache vs page cache(page cache的演化)</b></font><br id=dwoz> 在2.2x时期,page cache和buffer cache是两套cache系统,之间有同步.但是linux不保证每个版本都如此.<br id=g86d> 如果现在/dev/hda1是根,如果hda1上有文件a.txt用dd dump /dev/hda1能够得到和open a.txt一样的结果.<br id=oebi>(见2.22:do_generic_file_read->inode->i_op->readpage**generic_readpage-> brw_page)<br id=kg6i> 到了2.4.x事情已经变得不是这样了,dd if=/dev/hda1 从buffer cache中获取数据,open打开的普通文件缓冲到page cache,两者没有任何<br id=d:y.>同步机制(meta data还是一致的). 合适的次序下,得到的结果不能保证正确性.<br id=hir4> 当然dump一个已经mount的,"live file system"是个愚蠢的做法,我们只是拿来讨论问题.<br id=dm.x> 到了2.5,文件的meta data也移到了page cache,事情进一步复杂了.在2.6的内核中page cache和buffer cache进一步结合,从此buffer <br id=dpfb>cache 消失,只有page cache了. buffer cache退化为一个纯粹的io entry.随了linus的心愿.<br id=vnto>可以看看linus的讨论<br id=j53m><a href=http://groups.google.com/group/fa.linux.kernel/browse_thread/thread/3d1be60ca2980479/0ca4533f7d0b73e4?hl=zh-CN& id=xiuf title=http://groups.google.com/group/fa.linux.kernel/browse_thread/thread/3d1be60ca2980479/0ca4533f7d0b73e4?hl=zh-CN&>http://groups.google.com/group/fa.linux.kernel/browse_thread/thread/3d1be60ca2980479/0ca4533f7d0b73e4?hl=zh-CN&</a> <br id=dgx9> 在2.4中buffer cache自己维护了一套类似page cache和lru队列的机制,对buffer cache做lru 缓冲处理,的确不是一个什么好东西.<br id=hi0t><br id=dl_0><br id=tt82><font id=n7tr size=3><b id=jcvy>第三部分---> mm/filemap.c</b></font><br id=knvc><br id=wao:><br id=eu_r> 通过上面的讨论,已经涉及了本文件的诸多函数,这里对已经有说明的文件一笔带过,对感兴趣的,做个分析注解.<br id=ilnc> 头六个函数就不多说了,见上面的分析.<br id=nn:z><br id=lm:8><b id=mroc>(1) page cache 初始化</b><br id=sfsr>/*<br id=atcu> * mempages: 物理页面个数<br id=orzc> */<br id=q8yu>void __init page_cache_init(unsigned long mempages)<br id=d1j3>{<br id=ie:s> unsigned long htable_size, order;<br id=f22w><br id=dxup> /*计算要为hash 表分配多少内存, 及其order值(power of 2)*/<br id=hgwq> htable_size = mempages;<br id=voi3> htable_size *= sizeof(struct page *); <br id=m0ym> for(order = 0; (PAGE_SIZE << order) < htable_size; order++)<br id=ooaa> ;<br id=n3wl><br id=wnty> /*计划分配一个能容下所有物理页的hash表,就看又没内存*/<br id=zox5> do {<br id=gytp> /*这个order能够容下的page个数数*/<br id=n:.6> unsigned long tmp = (PAGE_SIZE << order) / sizeof(struct page *);<br id=ixkc><br id=f3ls> /*计算这么大的表对应的hash值(hash表下标)最多有多少位*/<br id=n51o> page_hash_bits = 0;<br id=np2g> while((tmp >>= 1UL) != 0UL)<br id=h:jc> page_hash_bits++;<br id=lmhl> <br id=nr41> page_hash_table = (struct page **) /*看看有没有这么多连续内存*/<br id=qcge> __get_free_pages(GFP_ATOMIC, order);<br id=wpmh> } while(page_hash_table == NULL && --order > 0);/*没有的话尝试少分点*/<br id=peey><br id=l4vp> printk("Page-cache hash table entries: %d (order: %ld, %ld bytes)\n",<br id=q1zn> (1 << page_hash_bits), order, (PAGE_SIZE << order));<br id=gapo> if (!page_hash_table)<br id=rinm> panic("Failed to allocate page hash table\n");<br id=ruqo> memset((void *)page_hash_table, 0, PAGE_HASH_SIZE * sizeof(struct page *));<br id=sz6o>}<br id=bi6l><br id=qif.><b id=oiuo>(2) TryLockPage,lock_page和UnlockPage</b><br id=mljc><br id=ytzh>static inline int sync_page(struct page *page)<br id=v8xy> 逻辑简单,调用mapping->a_ops->sync_page(page),对于ext2就是ext2_aops<br id=f7bf>->block_sync_page->run_task_queue(&tq_disk)(fs/buffer.c).让磁盘有更多<br id=jv6i>机会运行回写,读入等任务.提供给 ___wait_on_page,__lock_page使用.<br id=uz-4>/* <br id=oxgg> * Wait for a page to get unlocked.<br id=rg3h> *<br id=vci9> * This must be called with the caller "holding" the page,<br id=y_es> * ie with increased "page->count" so that the page won't<br id=z7vg> * go away during the wait..<br id=fdxi> */<br id=xasb>void ___wait_on_page(struct page *page)<br id=zn7j>{<br id=tfvp> struct task_struct *tsk = current;<br id=u-2f> DECLARE_WAITQUEUE(wait, tsk);<br id=rts4><br id=nbrx> add_wait_queue(&page->wait, &wait); <br id=ni4i> do {<br id=q8th> sync_page(page); /*给磁盘(may be other dev)一点运行机会<br id=m2gw> *说不定就不用再等了<br id=hl4.> */<br id=a9v4> set_task_state(tsk, TASK_UNINTERRUPTIBLE);<br id=v4ru> if (!PageLocked(page))<br id=ln20> break;<br id=dtpa> run_task_queue(&tq_disk);/**/<br id=ju0k> schedule();<br id=axpp> } while (PageLocked(page));<br id=d6e5> tsk->state = TASK_RUNNING;<br id=zb:e> remove_wait_queue(&page->wait, &wait);<br id=bxyq>}<br id=je56> 也没有什么可以多说的,等待页面解锁时给页面同步相关的task queue多些运行<br id=o5he>时间. void lock_page(struct page *page)和static void __lock_page(struct<br id=t7x6> page *page)同此函数.<br id=j131>include/linux/mm.h定义了<br id=v6jv>#define UnlockPage(page) do { \<br id=vh2w> smp_mb__before_clear_bit(); \<br id=orpn> if (!test_and_clear_bit(PG_locked, &(page)->flags)) BUG(); \<br id=e197> smp_mb__after_clear_bit(); \<br id=fnbt> if (waitqueue_active(&page->wait)) \<br id=eb6v> wake_up(&page->wait); \<br id=pkwf> } while (0)<br id=z1yd> <br id=d3:c>并且注释也说明了两个barrier的作用,<br id=ko9.> 当用 <br id=r:5z> TryLockPage <br id=nd80> ......<br id=d_8l> UnlockPage <br id=dfax>组成一个临界区的时候,第一个barrier保证test_and_clear_bit在<br id=qe:n>test_and_set_bit之后执行,第二个barrier保证test_and_clear_bit和访问<br id=q9:4>wait_queue的次序.<br id=shu9> 问题是如何使用lock_page, UnlockPage,使用时机是什么?内核注释为"在进<br id=njtj>行page上的IO操作时必须lock_page",这种解释有些简略.正在进行io的页面有如<br id=sdfi>下特征(几个典型情况):<br id=w:f3> 1)如果页面归user space的进程使用,肯定是swap cache在进行io操作,并且页<br id=dfuh> 面已经从用户的页表断开.<br id=cqw0> 2)如果是user task进行文件读写操作,启动io的页面是page cache(normal file)<br id=ga2j> 或者buffer cache.<br id=rj7g> 3)如果是mmap,读写亦通过page cache进行.<br id=jy.d> <br id=wo4o> 4)首先page io在大部分情况下是一个异步操作,kernel不会"停下来"等待磁盘<br id=e6t9> 操作的完成. 如典型的page fault需要换入时,新分配一个页面,加入swap <br id=z.m1> cache,启动io,最后当前进程wait on page.有可能内核处理swap的几个线程<br id=cnn:> 会访问到此页,此种情况下需要进行互斥操作,不能在一个页面上启动两个io<br id=t516> 操作.<br id=i2oc> 5)或者SMP的情况下,一边进行io换入,另一个cpu也可以进行lru操作.<br id=zkpk> <br id=avt9> 我相信作者一开始的时候准备用page lock这个机制防止对page io的重入.<br id=aygv>但是此锁还同步了更多的东西:<br id=xvub> 看加入swap cache的情况:<br id=vair>void add_to_swap_cache(struct page *page, swp_entry_t entry)<br id=rpxc>{<br id=b-vp> unsigned long flags;<br id=d.ds><br id=uimf>#ifdef SWAP_CACHE_INFO<br id=z2nf> swap_cache_add_total++;<br id=a9-y>#endif<br id=h5w.> if (!PageLocked(page)) //如果页面未锁,禁止加入swap cache<br id=vofk> BUG(); //出现此种情况是内核的bug<br id=m22-> ..................<br id=bbny> <br id=fpwo>} <br id=kxeh>为何加入page cache需要上锁?看下面这个函数<br id=b7-j>void add_to_page_cache_locked(struct page * page, struct address_space<br id=ur_5>*mapping, unsigned long index)<br id=u2yc>{<br id=x4v0> if (!PageLocked(page))<br id=z3oe> BUG();<br id=s0mu><br id=fna-> page_cache_get(page); /*增加引用计数*/<br id=bkcl> <br id=e0vf>}<br id=omrc><br id=xd0g>恩,对页面的引用计数增一,想一想还操作了page->mapping.所以我的结论是,在<br id=tww8>以下情况下需要page lock:<br id=e-ab> 1.对page进行io操作 <br id=d43_> 2.某些特定目的情况下操作page->mapping和page引用计数的情形<br id=sr.0> <br id=q_pq>为了验证这个结论,搜索对lock_page的引用,绝大多数在进行page io操作,还有<br id=v1mr>部分处理加入/离开page cache,这些容易理解. 然后挑一个例子看看为什么也<br id=zywp>使用了lock_page,先看一个filemap.c中的函数<br id=kh_d>/*<br id=ouf-> * Get the lock to a page atomically.<br id=wlh3> */<br id=b9j->struct page * __find_lock_page (struct address_space *mapping,<br id=zijf> unsigned long offset, struct page **hash)<br id=jor8>{<br id=vaiy> struct page *page;<br id=wknk><br id=rj8w> /*<br id=ut2v> * We scan the hash list read-only. Addition to and removal from<br id=s79v> * the hash-list needs a held write-lock.<br id=nzvl> */<br id=kn7_>repeat:<br id=q3:t> spin_lock(&pagecache_lock); //操作page cache的锁<br id=e851> page = __find_page_nolock(mapping, offset, *hash);<br id=kpyo> if (page) {<br id=i6sa> page_cache_get(page);<br id=r8ha> spin_unlock(&pagecache_lock);<br id=a0-6><br id=bjch> lock_page(page); //判断page->mapping以求<br id=w8wz> //返回一个肯定在page cache<br id=te19> //的页面,必须锁定页面,否则<br id=i6s5> //可能被page cache清除<br id=p86r> /* Is the page still hashed? Ok, good.. */<br id=hx67> if (page->mapping)<br id=mmxw> return page;<br id=l0v3><br id=m_qk> /* Nope: we raced. Release and try again.. */<br id=ezco> UnlockPage(page);<br id=jfa-> page_cache_release(page);<br id=v:46> goto repeat;<br id=om1l> }<br id=kilc> spin_unlock(&pagecache_lock);<br id=ep81> return NULL;<br id=dw.y>}<br id=kbz3>使用page lock的原因已经写入注释,此函数返回一个保证还在page cache的页,<br id=etkn>并增加页面引用计数,可以直接拿来使用,如shmem_nopage.总之,如果你要保证<br id=m04t>page->mapping有效的话,必须lock_page然后进行判断,内核多处如此使用.<br id=nk:1> 接着分析一个特殊的例子<br id=j3gg>static int do_wp_page(struct mm_struct *mm, struct vm_area_struct * vma,<br id=eu6n> unsigned long address, pte_t *page_table, pte_t pte)<br id=fc53>{<br id=bn7q> struct page *old_page, *new_page;<br id=ki2c><br id=ky8o> old_page = pte_page(pte);<br id=g1q:> if (!VALID_PAGE(old_page))<br id=fh_f> goto bad_wp_page;<br id=hum0> <br id=d9xr> /*<br id=ziw_> * We can avoid the copy if:<br id=uqoa> * - we're the only user (count == 1)<br id=j0lk> * - the only other user is the swap cache,<br id=va2_> * and the only swap cache user is itself,<br id=x5qs> * in which case we can just continue to<br id=aa7s> * use the same swap cache (it will be<br id=rqrz> * marked dirty).<br id=m9_s> */<br id=aw_s> switch (page_count(old_page)) {<br id=t:xe> case 2:<br id=rqn2> /*<br id=fqjf> * Lock the page so that no one can look it up from<br id=lj_y> * the swap cache, grab a reference and start using it.<br id=rqr4> * Can not do lock_page, holding page_table_lock.<br id=xigj> */<br id=ugw.> if (!PageSwapCache(old_page) || TryLockPage(old_page))<br id=uua4> break;<br id=gt4c> if (is_page_shared(old_page)) {<br id=wsof> UnlockPage(old_page);<br id=fxxi> break;<br id=ad4l> }<br id=m2e_> UnlockPage(old_page); //解锁后如果有人从swap cache共享了页面呢?<br id=or.y> /* FallThrough */ <br id=znry> case 1:<br id=gls-> flush_cache_page(vma, address);<br id=epw2> establish_pte(vma, address, page_table, pte_mkyoung(pte_mkdirty(pte_mkwrite(pte))));<br id=s:fh> spin_unlock(&mm->page_table_lock);<br id=vbax> return 1; /* Minor fault */<br id=t:16> }<br id=vlz1><br id=u11i> ................<br id=d2-n>}<br id=y657> 这个地方注释详尽,为了避免其他执行流从swap cache(only swap)共享此页<br id=mhzu>面,对页面加锁.但解锁之后设置pte可写是否正确呢?(解锁了,其他人即可共享啊)<br id=ig5o>我认为:<br id=wbl.> 1)即使加锁后使pte可写,也无济于事,因为其他执行流照样可共享此页.<br id=h-oq> 2)其他执行流共享此页后,不可能直接容许写,但到COW处理,重入此函数后<br id=xhds> 引用计数大于2,必须copy. 故不会出错.<br id=x8q9> 3)如果计算是否是共享页面时不加锁则有可能两个进程同时拥有对此页面的<br id=ssy0> 写权限.<br id=hxnn>(不能够是如此复杂的解释,到底应该怎样理解同步与互斥?2.22的确简单,这里有个<br id=rz1x>smp的大锁,lock kernel)这个锁锁定了一个临界区,保证计算一个确定的状态,同<br id=gn7d>时保证这个函数重入后不会的到相同的计算结果。<br id=fudq> 另一个类似函数是<br id=c9-2>static int do_swap_page(struct mm_struct * mm,<br id=iq3w> struct vm_area_struct * vma, unsigned long address,<br id=eu8l> pte_t * page_table, swp_entry_t entry, int write_access)<br id=p_0z>{<br id=jj7a> ...........<br id=l1y1><br id=nzoz> /*<br id=mfil> * Freeze the "shared"ness of the page, ie page_count + swap_count.<br id=ru6s> * Must lock page before transferring our swap count to already<br id=j1_u> * obtained page count.<br id=mzyr> */<br id=mg9d> lock_page(page);<br id=x4p1> swap_free(entry);<br id=gus-> if (write_access && !is_page_shared(page))<br id=ir.0> pte = pte_mkwrite(pte_mkdirty(pte));<br id=gf7w> UnlockPage(page);<br id=yq7e><br id=wb5s> set_pte(page_table, pte);<br id=x9j_> ...............<br id=p:zw> return 1; /* Minor fault */<br id=rx.u>}<br id=v3gq><br id=ye2i><br id=g4sv> <br id=jf92><b id=o7n->(3) some func</b><br id=qfvh>static inline void set_page_dirty(struct page * page)<br id=h5h:> +<br id=qymp>__set_page_dirty :标记页面为dirty,调整页面在page cache中(mapping)队列<br id=yihp>的位置,并标记相关inode节点为dirty状态.调用者保证page在page cache之中.<br id=vyxh><br id=a6v1>void invalidate_inode_pages(struct inode * inode):<br id=xn4a>好像没有人用,正好也不看了.<br id=dr2l><br id=e3fz><br id=hr31><b id=pnoi>(4)file truncate related</b><br id=v9b7> truncate_inode_pages ( service entry for file truncate in filemap.c)<br id=y.2v> +--->truncate_list_pages<br id=c5_g> +-->truncate_partial_page<br id=gc4v> +-->truncate_complete_page<br id=x_qa> 这组函数和系统调用 truncate 相关(truncate file to specified len).入口<br id=gy.i>在 fs/open.c<br id=ip7m>asmlinkage long sys_truncate(const char * path, unsigned long length)<br id=rzbu>{<br id=i-p2> return do_sys_truncate(path, length);<br id=iopa>}<br id=zv:b>经过一系列的函数周转到do_truncate->notify_change->inode_setattr(ext2文<br id=p-y5>件系统没有提供setattr,采用通用逻辑)->vmtruncate,最终利用truncate_inode<br id=w-03>_pages清除page cache中相关的缓冲数据. 关于truncate不想再多说,只来看看:<br id=r1h9>static int truncate_list_pages(struct list_head *head, unsigned long <br id=qda4>start, unsigned *partial)<br id=btwk>/*注意一下加锁的顺序*/<br id=db9o>{<br id=k6tf> .........<br id=t:yh> while (curr != head) {<br id=p66t> unsigned long offset;<br id=qy5y><br id=i.8a> page = list_entry(curr, struct page, list);<br id=w7-b> curr = curr->next;<br id=u_8f> offset = page->index;<br id=vrr.><br id=h1ox> /* Is one of the pages to truncate? */<br id=rak7> if ((offset >= start) || (*partial && (offset + 1) == start)) {<br id=dm_j> if (TryLockPage(page)) {<br id=itlb> page_cache_get(page); /*先增加页面引用计数*/<br id=m0m-> spin_unlock(&pagecache_lock);/*然后才释放锁*/<br id=r3jx> wait_on_page(page);<br id=kk_c> page_cache_release(page);<br id=y.0o> return 1;<br id=y_ix> }<br id=ptn7> /*先增加页面引用计数,然后才释放锁,注意这个顺序*/<br id=pb.7> page_cache_get(page);<br id=k3cu> spin_unlock(&pagecache_lock);<br id=ro9s> .........<br id=o:5g> }<br id=k467> }<br id=qaze> return 0;<br id=xp4q>}<br id=jctm> <br id=ojq_> <br id=pjz8><b id=htpu>(5)fsync, fdatasync</b><br id=vtcn> 这两个系统调用将内核缓冲的文件数据同步到磁盘.系统调用的入口在buffer.c<br id=l7aj>sys_fsync,sys_fdatasync.区别在于sys_fsync将meta data也刷新到磁盘(atime<br id=a0qa>等),而sys_fdatasync只刷新"文件内容".两个系统调用都不保证包含他们的上级<br id=ztyl>目录的同步.如果需要,要明确的对对应目录调用fsync.<br id=vy7s> filemap.c中相关的函数是filemap_fdatasync,filemap_fdatawait.其作用是同<br id=u8uk>步page cache中的dirty页(mapping->dirty_pages)到磁盘.而inode meta data的<br id=vizw>同步依赖于特定的文件系统(见buffer.c sys_fsync,注意page cache无meta数据).<br id=d.80>filemap_fdatasync遍历dirty页面,提交系统驱动处理(mapping->a_ops->writepage<br id=c4ew>对ext2文件系统来讲就是ext2_aops -> ext2_writepage ->block_write_full_page<br id=yr7m>此函数也在buffer.c,请阅读此函数,注意page上的buffers并没有加入buffer cache)<br id=e9jb>filemap_fdatawait等待驱动完成page io操作.<br id=nxmp> 不再列出相关代码,阅读时候体会一下加锁和增加页面引用计数的顺序.<br id=z1_3> <br id=k7vt><br id=aqjf style=FONT-WEIGHT:bold><b id=ij4j>(6)page cache: 数据读入</b><br id=hg-b> 函数static inline int page_cache_read(struct file * file, unsigned <br id=x00->long offset)分配一个页面并提交磁盘驱动读入文件制定偏移的内容到page <br id=el7y>cache, 同时考虑到了其他执行流先于我们读入的情况.仔细阅读此函数调用的<br id=m8-x>add_to_page_cache_unique->__add_to_page_cache,注意在__add_to_page_cache<br id=a-rj>中对page加了锁. 这个锁比较隐蔽,还以为page_cache_read在未加锁的情况下<br id=l503>启动了page io呢.<br id=z0-k> 这是一个异步读取函数,应用于预读和其他需要异步读取的函数.<br id=d8ep> 函数read_cluster_nonblocking调用page_cache_read异步读区整个cluster.<br id=ckhi> read_cache_page从mapping读取指定的内容到页面,所不同的是使用指定的方<br id=lnt.>式更新页面的内容.同样考虑到了各种race的情况.他使用用的函数有点拗口,来<br id=hmz4>看看:<br id=atpi>static inline<br id=oyb_>struct page *__read_cache_page(struct address_space *mapping,<br id=h:u4> unsigned long index,<br id=w:t4> int (*filler)(void *,struct page*),<br id=ctpn> void *data)<br id=t978>{<br id=v.b9> struct page **hash = page_hash(mapping, index);<br id=acbx> struct page *page, *cached_page = NULL;<br id=uxuf> int err;<br id=myo1>repeat:<br id=k:gz> page = __find_get_page(mapping, index, hash);<br id=y5bf> if (!page) {/*未找到指定页面*/<br id=c9j_> if (!cached_page) {<br id=z8te> cached_page = page_cache_alloc();<br id=uex4> if (!cached_page)<br id=icm4> return ERR_PTR(-ENOMEM);<br id=elgt> }<br id=mthx> page = cached_page;<br id=it9p> /*<br id=k-ix> *add_to_page_cache_unique->__add_to_page_cache对页面进行了加锁<br id=ygf3> */<br id=q..s> if (add_to_page_cache_unique(page, mapping, index, hash))<br id=ibso> goto repeat;/*新页面加入cache的时候发现cache已经有了指定页面*/<br id=banl> cached_page = NULL;<br id=ez_b> err = filler(data, page); /*用指定方式更新页面*/<br id=ys3.> if (err < 0) {<br id=dfzr> page_cache_release(page);<br id=mdv0> page = ERR_PTR(err);<br id=nw.v> }<br id=fe:8> }<br id=o3b6> if (cached_page)<br id=hwzl> page_cache_free(cached_page);<br id=skf5> return page;<br id=e1ll>}<br id=vbdl>从语义上讲函数read_cache_page应该是"读取到page cache".<br id=dpu3> 还有一个逻辑上比较类似的函数grab_cache_page,此函数只是锁定一个指定<br id=arqu>区间的页面,返回给调用者.而不管是否update,也不提交给驱动读取页面.<br id=z.bg><br id=ghzj><b id=xj9j>(7)普通文件读写和预读</b><br id=yi_p> generic_file_read 负责普通文件的读取(系统调用read),即可以使用page <br id=v63t>cache的一切文件系统。<br id=ld95> 系统调用read在文件fs/read_write.c中<br id=ehvg>asmlinkage ssize_t sys_read(unsigned int fd, char * buf, size_t count)<br id=r8uh> sys_read调用文件系统提供的read,我们以ext2为例就是<br id=ngpb>/*<br id=ht_x> * We have mostly NULL's here: the current defaults are ok for<br id=ihiw> * the ext2 filesystem.<br id=j0n7> */<br id=y9wy>struct file_operations ext2_file_operations = {<br id=ylkv> llseek: ext2_file_lseek,<br id=clef> read: generic_file_read,<br id=mbn2> write: generic_file_write,<br id=l8cj> ioctl: ext2_ioctl,<br id=chke> mmap: generic_file_mmap,<br id=ed-z> open: ext2_open_file,<br id=qgsl> release: ext2_release_file,<br id=m5id> fsync: ext2_sync_file,<br id=t1h3>};<br id=w85c> 一般来讲,文件读取通过 generic_file_read来进行.generic_file_read建立<br id=vf2h>一个read descriptor,然后交给do_generic_file_read,做真正的读取工作.调用<br id=rvcv>这个函数的时候传递了一个函数指针:file_read_actor,其作用是复制page内指定<br id=g.s6>偏移和长度的数据到用户空间.<br id=t2xf> 先看看do_generic_file_read要处理的几个问题:<br id=a:di> 1) page cache: 普通文件缓存于内核的page cahce,引发linux读写文件时将<br id=qhs8> 文件看作一个以page size为单位的逻辑页面.读取文件就是将用户读取的<br id=xrlr> 位置和大小转换成逻辑的页面,从page cache找到内存对应的页面,并将内<br id=kc9v> 容复制到用户缓冲区. 如果未缓存此文件的对应内容,就要从磁盘上的对应<br id=jm4i> 文件以文件系统自己的方式读取到内存页面并将此页面加入到page cache.<br id=q0x1> 2) 上面一条是将文件流切割成page 页,然后block_read_full_page(通常是<br id=d0nn> 这个函数)还会将页面切割为此文件独立的线性block num,最后通过具体的<br id=ta4q> 文件系统将文件线性的block转换成磁盘线性的block(硬件block num?).<br id=d.oo> 3) 预读: 用户读取文件的时候内核极力猜测用户的意图,试图在用户使用数据<br id=fmip> 前就将数据准备好. 这样可以早期启动磁盘的io操作,以dma方式并行处理.<br id=zop1> 并且成批的io操作可以提高吞吐量.linux内核的预读对于顺序读取模式应改<br id=c3jm> 很有效果.<br id=ui:p> 4) 隔离各种文件系统读取文件内容的方式. 就是通过给定文件关联的inode,利<br id=vdzb> 用函数指针mapping->a_ops->readpage读取文件内容. 具体的例子可以看ext2<br id=el-:> struct address_space_operations ext2_aops = {<br id=be0l> readpage: ext2_readpage,<br id=t3of> writepage: ext2_writepage,<br id=po6g> sync_page: block_sync_page,<br id=f5lu> prepare_write: ext2_prepare_write,<br id=wygx> commit_write: generic_commit_write,<br id=n87o> bmap: ext2_bmap<br id=z4he> };<br id=rxo.> ext2_readpage直接调用block_read_full_page(page,ext2_get_block).就<br id=ucum> 是将文件内线性编址的page index 转换为文件线性编址的block(逻辑块).<br id=s:o2> 其中 ext2_get_block(*inode,iblock,*bh_result,create)将文件的逻辑块<br id=yg-c> 号转换为块设备的逻辑块号(块设备上线性编址的block num),最后提交设备<br id=il1d> 驱动读取指定物理块.(驱动将设备块号转换为扇区编号..^_^)读写文件页面<br id=dg0l> 的过程仅做此简析,以后分析buffer相关的文件时再细细品味一下.<br id=hnju><br id=i37f>{[写到这里时候,发生了一些事情,耽搁了两周. 顺便看了看devfs.. go on]}<br id=q.3o> 具体再分析do_generic_file_read的时候就逻辑清晰了.<br id=efve>/*<br id=jc53> * This is a generic file read routine, and uses the<br id=f7d2> * inode->i_op->readpage() function for the actual low-level<br id=g9fg> * stuff.<br id=mofj> *<br id=es.n> * This is really ugly. But the goto's actually try to clarify some<br id=hauo> * of the logic when it comes to error handling etc.<br id=otf0> */<br id=m.eh>void do_generic_file_read(struct file * filp, loff_t *ppos, read_descriptor_t * desc, read_actor_t actor)<br id=or9.>{<br id=sq8h> struct inode *inode = filp->f_dentry->d_inode;<br id=qkoy> struct address_space *mapping = inode->i_mapping;<br id=mbzb> unsigned long index, offset;<br id=n9xn> struct page *cached_page; /*不存在于page cache的时候分配的页面,可能用不到<br id=mflr> *因为获取锁的时候可能等待,被其他执行流抢了先.<br id=b7:g> */<br id=fvn:> int reada_ok;<br id=o.eg> int error;<br id=qi3z> int max_readahead = get_max_readahead(inode);<br id=ulze><br id=l-ep> /*<br id=a.xv> * 在字节流内的位置转换成线性的文件页面流索引<br id=vaol> */<br id=vq80> cached_page = NULL;<br id=hp00> index = *ppos >> PAGE_CACHE_SHIFT;<br id=hq4-> offset = *ppos & ~PAGE_CACHE_MASK;<br id=d7u2><br id=ec2.>/*<br id=e:gc> * 看看预读是否有效,及时调整预读量.<br id=ou4e> * 如果还未曾预读或者被重置,调整read-ahead max的过程就是预读的初始化<br id=x:8q> */<br id=unp7>/*<br id=k63q> * If the current position is outside the previous read-ahead window, <br id=sj7e> * we reset the current read-ahead context and set read ahead max to zero<br id=lf6i> * (will be set to just needed value later),<br id=rw-0> * otherwise, we assume that the file accesses are sequential enough to<br id=sn7z> * continue read-ahead.<br id=r7y1> */<br id=n8uc> if (index > filp->f_raend || index + filp->f_rawin < filp->f_raend) {<br id=b.-e> /*index < filp->raend - filp->rawin*/<br id=dxmn> /*如果用户读取范围超出预读窗口则重新计算预读量和起始位置*/<br id=ilph> reada_ok = 0;<br id=x_6-> filp->f_raend = 0;<br id=rmz6> filp->f_ralen = 0;<br id=dpop> filp->f_ramax = 0;<br id=ya41> filp->f_rawin = 0;<br id=v4_i> } else {<br id=piep> reada_ok = 1;<br id=vqps> }<br id=d1e_>/*<br id=wu4n> * Adjust the current value of read-ahead max.<br id=xbfx> * If the read operation stay in the first half page, force no readahead.<br id=x..1> * Otherwise try to increase read ahead max just enough to do the read request.<br id=tgcn> * Then, at least MIN_READAHEAD if read ahead is ok,<br id=w9_s> * and at most MAX_READAHEAD in all cases.<br id=nu-l> */<br id=yjro> if (!index && offset + desc->count <= (PAGE_CACHE_SIZE >> 1)) {<br id=h1:5> /*读取文件的前半个页面,不进行预读*/<br id=g7sf> filp->f_ramax = 0;<br id=nytt> } else {<br id=ms1l> unsigned long needed;<br id=o72-> /*计算需要读入的页面个数, 注*ppos在页面index的offset位置*/<br id=il2-> needed = ((offset + desc->count) >> PAGE_CACHE_SHIFT) + 1;<br id=nfaw><br id=fscw> if (filp->f_ramax < needed)<br id=i0-q> filp->f_ramax = needed; /*预读量至少要满足这次读取请求*/<br id=n_iu><br id=d2yu> if (reada_ok && filp->f_ramax < MIN_READAHEAD)<br id=v_8q> filp->f_ramax = MIN_READAHEAD;<br id=owkh> if (filp->f_ramax > max_readahead)<br id=qz5j> filp->f_ramax = max_readahead;<br id=ragc> }<br id=l9t3><br id=tlsj> /*<br id=fxy.> * 根据用户要求读取所有请求的页面<br id=ohtl> */<br id=e744> for (;;) {<br id=h3sh> struct page *page, **hash;<br id=heqh> unsigned long end_index, nr;<br id=fq.l><br id=z2o-> /*nr:本页面读取的字节数*/<br id=g65h> end_index = inode->i_size >> PAGE_CACHE_SHIFT;<br id=s63m> if (index > end_index)<br id=c3rm> break;<br id=qjia> nr = PAGE_CACHE_SIZE;<br id=nn23> if (index == end_index) {<br id=o:8p> nr = inode->i_size & ~PAGE_CACHE_MASK;<br id=e3yr> if (nr <= offset)<br id=r2h1> break;<br id=i0t9> }<br id=t6dw><br id=ufgd> nr = nr - offset;<br id=ta4f><br id=vw36> /*<br id=l7e.> * Try to find the data in the page cache..<br id=u.q3> */<br id=mqd5> /* (先在page cache寻找指定文件页) */<br id=sfq4> hash = page_hash(mapping, index);<br id=x1t9><br id=ut85> spin_lock(&pagecache_lock);<br id=lgsu> page = __find_page_nolock(mapping, index, *hash);<br id=g0oz> if (!page)<br id=zk7:> goto no_cached_page; /*分配页面加入page cache 跳转到 page_ok*/<br id=vesa> /*如果睡眠后其他执行流将文件的page块加入到<br id=ncsa> *page cache就跳转到 found_page<br id=rzbz> */<br id=sbww>found_page:<br id=zo9e> page_cache_get(page); /*先get页面*/<br id=ks3q> spin_unlock(&pagecache_lock);/*后解锁page cache*/<br id=x-8n><br id=f_n2> if (!Page_Uptodate(page))<br id=o.51> goto page_not_up_to_date; /*预读,读取本页,然后返回到page_ok*/<br id=vwaf> generic_file_readahead(reada_ok, filp, inode, page);<br id=o2t1>page_ok:<br id=m45j> /* If users can be writing to this page using arbitrary<br id=ckv1> * virtual addresses, take care about potential aliasing<br id=u8vk> * before reading the page on the kernel side.<br id=vgfz> */<br id=ds3j> if (mapping->i_mmap_shared != NULL)<br id=iu62> flush_dcache_page(page);<br id=ryrh><br id=dlmu> /*<br id=dhzc> * Ok, we have the page, and it's up-to-date, so<br id=jdzd> * now we can copy it to user space...<br id=g-yi> *<br id=a9gn> * The actor routine returns how many bytes were actually used..<br id=ms6f> * NOTE! This may not be the same as how much of a user buffer<br id=e9kj> * we filled up (we may be padding etc), so we can only update<br id=j.d:> * "pos" here (the actor routine has to update the user buffer<br id=yhbk> * pointers and the remaining count).<br id=xyfk> */<br id=z.b9> nr = actor(desc, page, offset, nr);<br id=q:35> offset += nr;<br id=r7ta> /*计算下一个要读的页面和偏移*/<br id=y_du> index += offset >> PAGE_CACHE_SHIFT;<br id=nl9n> offset &= ~PAGE_CACHE_MASK;<br id=os_j> <br id=jlr.> page_cache_release(page);<br id=g5ma> if (nr && desc->count) /*需要继续*/<br id=z63h> continue;<br id=zpdg> break; /*读取结束*/<br id=od:7> /*<br id=o9j2> * for 循环的主流程结束<br id=enh5> */<br id=m57a>/*<br id=j9js> * 页面没有含有有效数据的情况<br id=o_nx> */<br id=nwc_>/*<br id=l0-q> * Ok, the page was not immediately readable, so let's try to read ahead while we're at it..<br id=rpg:> */<br id=rh2h>page_not_up_to_date:<br id=dm.j> generic_file_readahead(reada_ok, filp, inode, page);<br id=u:5j><br id=y8y2> if (Page_Uptodate(page))<br id=cn7k> goto page_ok;<br id=ogn.><br id=fu1x> /* Get exclusive access to the page ... */<br id=p0e5> lock_page(page);<br id=d833><br id=l3xo> /* Did it get unhashed before we got the lock? */<br id=ix6-> if (!page->mapping) {<br id=k.kk> UnlockPage(page);<br id=zxto> page_cache_release(page);<br id=wf7z> continue;<br id=z6jt> }<br id=a75h><br id=ldg4> /* Did somebody else fill it already? */<br id=n89j> if (Page_Uptodate(page)) {<br id=q2td> UnlockPage(page);<br id=th2i> goto page_ok;<br id=jg6q> }<br id=p-7d><br id=ospz>readpage:/*无有效数据和页面不在page cache 的情况也许都要read page (no_cached_page)*/<br id=ht3-> /* ... and start the actual read. The read will unlock the page. */<br id=ck0j> error = mapping->a_ops->readpage(filp, page);<br id=zpq9><br id=phi5> if (!error) {<br id=baid> if (Page_Uptodate(page))<br id=wzvu> goto page_ok;<br id=ue:m><br id=lq1z> /* Again, try some read-ahead while waiting for the page to finish.. */<br id=a1ck> generic_file_readahead(reada_ok, filp, inode, page);<br id=akwf> wait_on_page(page);<br id=aex8> if (Page_Uptodate(page))<br id=o7gn> goto page_ok;<br id=umua> error = -EIO;<br id=n733> }<br id=m8rm><br id=cjdf> /* UHHUH! A synchronous read error occurred. Report it */<br id=sqyz> desc->error = error;<br id=z3_r> page_cache_release(page);<br id=fx.o> break;<br id=ybue>/*<br id=az7x> * 未在page cache 发现指定页面,只有分配一个了<br id=l83h> */<br id=gc.9><br id=ki46>no_cached_page:<br id=bip6> /*<br id=z-lh> * Ok, it wasn't cached, so we need to create a new<br id=q-u7> * page..<br id=c4ym> *<br id=dwg_> * We get here with the page cache lock held.<br id=n21e> */<br id=lcet> if (!cached_page) {<br id=ovms> spin_unlock(&pagecache_lock);<br id=y8nk> cached_page = page_cache_alloc();<br id=kg15> if (!cached_page) {<br id=udk1> desc->error = -ENOMEM;<br id=dats> break;<br id=fv3k> }<br id=l-hf><br id=kofx> /*<br id=vypo> * Somebody may have added the page while we<br id=ku8p> * dropped the page cache lock. Check for that.<br id=e3ql> */<br id=gbm9> spin_lock(&pagecache_lock);<br id=yl6u> page = __find_page_nolock(mapping, index, *hash);<br id=iyr4> if (page)<br id=beoz> goto found_page;<br id=m_7o> }<br id=u.q:><br id=kk8_> /*<br id=pb59> * Ok, add the new page to the hash-queues...<br id=sv9e> */<br id=bhjq> page = cached_page;<br id=z5qq> __add_to_page_cache(page, mapping, index, hash);<br id=cj1o> spin_unlock(&pagecache_lock);<br id=s58q> cached_page = NULL;<br id=o1p7><br id=vs.b> goto readpage;<br id=yn48> } /*end for*/<br id=ka1_><br id=e-cz> *ppos = ((loff_t) index << PAGE_CACHE_SHIFT) + offset;<br id=n09g> filp->f_reada = 1;<br id=y-rj> if (cached_page)<br id=ytj9> page_cache_free(cached_page);<br id=v_so> UPDATE_ATIME(inode);<br id=r71k>}<br id=tb8q><br id=jmgk> 函数的分析就是上面的注释.另外一个问题就是预读. do_generic_file_read<br id=z3l_>当然是进行文件的预读的最好的时机.在这里建立预读的context(一直在想contex<br id=sl57>的最佳译法),检查预读是否有效.<br id=y0fu><br id=u2eb> 为了搞清楚预读的各个变量我们分三遍读do_generic_file_read,分别对应:<br id=twzc>第一次读取文件,第二次读取文件顺序读取,所以预读命中,第三次读取文件,超出<br id=ktna>预读窗口. 来看看和generic_file_readahead如何配合.<br id=x1yc> 条件:<br id=ghz_> 1) 假设读取不是从0字节开始,比如从8k的地方读<br id=z38j> 2) 假设读取的时候进行加锁都比较快,io没有很快完成(这应该是一般<br id=p05b> 情况,ide硬盘怎么会有那么快)<br id=yk:3><br id=nvrl><br id=p0-x>第一次读取文件:(假设page cache 无此页面)<br id=gmr4> +----do_generic_file_read()<br id=pxe0> {<br id=ts4l> .......<br id=i8uj> if (index > filp->f_raend ||....) {..}<br id=rtfp> reada_ok = 0; //read 8k,so exceed reada context<br id=ft:o> else{ }<br id=kocd> <br id=g9le> if (!index && offset ...) { <br id=ypiy> }<br id=bhyx> else {<br id=gn0c> unsigned long needed;<br id=bp8k> <br id=uwcf> needed = ....;<br id=gf3r><br id=zin7> if (filp->f_ramax < needed)<br id=zdf0> filp->f_ramax = needed; //f_ramax init<br id=nes0> }<br id=qnp_> <br id=n83g> readpage:<br id=c:tg> 假设第一次读取,所以page cache没有此页面,需要从hd读入,页面已<br id=rtbv> 锁.<br id=qq7t> if (!error) {<br id=iyoz> if (Page_Uptodate(page))<br id=tz_b> goto page_ok; //我们假设读取没有很快完成也是很<br id=e49c> //合理的,哪有那么快<br id=zwe:> //所以进行预读的时候页面是加了锁的,reada_ok为0<br id=b5e6> generic_file_readahead(reada_ok, filp, inode, page);<br id=zwfa> wait_on_page(page);<br id=mnu2> if (Page_Uptodate(page))<br id=v:qp> goto page_ok;<br id=firx> error = -EIO;<br id=a:el> }<br id=ng7a><br id=l:qd> } <br id=lr83> <br id=u5.:> +--generic_file_readahead() <br id=ej1f> { <br id=r_ye> raend = filp->f_raend; /*=0 */<br id=b_5v> max_ahead = 0; /*本次要启动io的页面之数量*/<br id=raf.> <br id=s2h1> if (PageLocked(page)) { //第一次读取文件所以filp->f_ralen 为0<br id=nyl0> if (!filp->f_ralen || index >= raend || index + filp->f_rawin < raend) {<br id=o6:q> //重新建立预读窗口<br id=r677> raend = index; //假设"上次预读"结束于当前页面(正在读取的页面)<br id=aql:> //即,当前锁定的页面是在"预读"<br id=m8gv> if (raend < end_index)<br id=iqiu> max_ahead = filp->f_ramax; //本次预读filp->f_ramax个页面,<br id=pvro> //在do_generic_file_read 中已经初始化<br id=zni0> filp->f_rawin = 0; //预读窗口为0,因为还没有预读过(或重新建立预读)<br id=dvpi> filp->f_ralen = 1; //上次"预读"了1个页面<br id=v6oz> if (!max_ahead) {<br id=f2ma> filp->f_raend = index + filp->f_ralen;/*上次预读窗口外的第一个页面*/<br id=kb6s> filp->f_rawin += filp->f_ralen;/*连续有效预读的总个数*/<br id=k4.c> }<br id=s7i4> }<br id=q43i> }else if (reada_ok ...)<br id=vym0> }<br id=jz_j> ahead = 0; /*本次预读的页面个数*/<br id=s3so> while (ahead < max_ahead) {<br id=s0xu> 在max_ahead个页面上启动预读<br id=gbvs> } /*ahead 保持为0*/<br id=joen> if (ahead) {<br id=exff> if (reada_ok == 2) {//我们这次reada_ok为0}<br id=z1o3> filp->f_ralen += ahead; //f_ralen代表上次预读的个数,这里为此记录<br id=ewdf> filp->f_rawin += filp->f_ralen; //f_rawin代表所有连续有效预读的总量<br id=i:cf> filp->f_raend = raend + ahead + 1;//f_raend是预读窗口外第一个页面 <br id=fe9l> filp->f_ramax += filp->f_ramax;//预读有效,下次预读量加倍<br id=wny0> .....<br id=wnex> }<br id=sm::> }<br id=kqbw><br id=r.z:>分析: 第一次读取文件page 2,offset 0,filep各项为0,do_generic_file_read将<br id=f0sw>reada_ok置0. 将filp->f_ramax置为用户读取的页面个数(有上限).<br id=i2wr>generic_file_readahead为第一次读取文件建立预读档案并预读一定数量的页面.<br id=ronb><br id=z4er><br id=v.7j>第二次读取文件:上次进行了预读,假设page cache 已经有此页面,并且是顺序读<br id=o:ga>取,命中了预读窗口.<br id=ke72> +----do_generic_file_read()<br id=x:d0> {<br id=hnw_> .......<br id=hes_> if (index > filp->f_raend ||....) {..}<br id=j-pf> else{ //命中预读窗口<br id=w5l0> reada_ok = 1; <br id=hy-5> }<br id=xega> <br id=hfuq> if (!index && offset ...) {/*读取文件的前半个页面,不进行预读*/<br id=k0zz> 我们早就不是读前半个页面了 <br id=r2.m> }<br id=fzba> else {<br id=ecde> unsigned long needed;<br id=lntp> <br id=h3ql> needed = ....;<br id=ua9d> //假设上次预读量已经足够了,所以这次f_ramax没有被重置<br id=wqrk> //是上次读取量的两倍<br id=mha2> if (filp->f_ramax < needed)<br id=faa5> filp->f_ramax = needed; <br id=v70:> }<br id=ia9n> <br id=is.e> for (;;) {<br id=f.mz> //我们已经假设page cache存在此页面<br id=u1ok> found_page:<br id=ysdz> ....<br id=c01o> if (!Page_Uptodate(page))<br id=vm91> goto page_not_up_to_date; /*假设预读已经完成(没有完成也一样)*/<br id=mmv6> //所以进行预读的时候页面是没有加锁的,reada_ok为1<br id=s3v4> generic_file_readahead(reada_ok, filp, inode, page);<br id=xjyl> ............<br id=r2ro> } <br id=mgu8> } <br id=qs48> // reada_ok =1 代表此次读取命中预读窗口(但不一定命中上次预读窗) <br id=iqkc> +--generic_file_readahead() <br id=ee9:> { <br id=sorm> raend = filp->f_raend; /*=0 */<br id=we55> max_ahead = 0; /*本次要启动io的页面之数量*/<br id=esj7> <br id=pl7e> if (PageLocked(page)) { <br id=ncmu> //这次没有加锁,^_^ <br id=zr4u> }else if (reada_ok && filp->f_ramax && raend >= 1 &&<br id=hvpk> index <= raend && index + filp->f_ralen >= raend) {<br id=vqnl> /*命中预读窗口,并且命中上次预读的那部分页面,用户真是步<br id=s4cb> *步紧逼啊.我们这次读取如果不是如此,就不会再进行任何预读<br id=inu3> *临时决定就假设如此吧.<br id=z0lx> */<br id=e6tc> /*页面未锁,或许读取完成,或许还没有开始--->*/<br id=qv3t> raend -= 1; /*见注释,保持和同步预读有着同样的io max size*/<br id=cgl2> if (raend < end_index)<br id=iw.x> max_ahead = filp->f_ramax + 1;<br id=cy6e> if (max_ahead) {<br id=pafx> filp->f_rawin = filp->f_ralen;<br id=vqsh> filp->f_ralen = 0; /*将上次预读长度(即"上次预读"窗口)清空*/<br id=dxwq> reada_ok = 2; /*--->所以或许要督促一下,尽快开始读取*/<br id=qf2v> }<br id=avzt> }<br id=e6mp> <br id=hu_o> ahead = 0; /*本次预读的页面个数*/<br id=xuv6> while (ahead < max_ahead) {<br id=hcdj> .....<br id=l2j5> if (page_cache_read(filp, raend + ahead) < 0)<br id=ls7g> break;<br id=nkhf> } /*ahead 保持为0*/<br id=a554> if (ahead) {<br id=ezjo> if (reada_ok == 2) { /*强制unplug*/<br id=zutn> run_task_queue(&tq_disk);<br id=dywa> }<br id=u_1w> filp->f_ralen += ahead; //f_ralen代表上次预读的个数,这里为此记录<br id=ntul> filp->f_rawin += filp->f_ralen; //f_rawin代表所有连续有效预读的总量<br id=zt6m> filp->f_raend = raend + ahead + 1;//f_raend是预读窗口外第一个页面 <br id=pgfo> filp->f_ramax += filp->f_ramax;//预读有效,下次预读量加倍<br id=wfvm> .....<br id=p6nj> } <br id=l..1> }<br id=tg23><br id=qffl>分析: 第二次读取文件如果用户命中上次预读的那几个页面,证明预读有效,极有<br id=h-y6>可能是顺序读取,故进行预读(预读量是上次的两倍),并再次加倍预读量.(当然有<br id=oncv>上限).<br id=tpik>第三次读取:未命中预读窗口. 和第一次预读类似. 这里不再列举.<br id=e:gs> <br id=jk10> 预读分为两种: 同步预读和异步预读.从磁盘读取数据如果是DMA方式,总是异步<br id=walb>的.这里应该是数和用户读取文件同时进行的意思,也就是当前页面已经开始io的<br id=zoes>情况之下,页面已经上锁,叫做同步.<br id=e7ea> 异步读取的时候,调用run_task_queue(&tq_disk), 到底干了些啥?<br id=aj5c>drivers/block/ll_rw_blk.c 函数generic_plug_device,将request_queue_t放入<br id=g-mn>task queue :tq_disk.块驱动的task queue里都是什么请求?当然是我们的读/写<br id=izy1>啦. 印证一下: 同一个文件的函数<br id=l:ro>void blk_init_queue(request_queue_t * q, request_fn_proc * rfn)<br id=vzf4>{<br id=lhuc> INIT_LIST_HEAD(&q->queue_head);<br id=xz9o> INIT_LIST_HEAD(&q->request_freelist[READ]);<br id=vzvh> INIT_LIST_HEAD(&q->request_freelist[WRITE]);<br id=p3..> elevator_init(&q->elevator, ELEVATOR_LINUS);<br id=vfs8> blk_init_free_list(q);<br id=llnd> q->request_fn = rfn; /*note 0*/<br id=ltmd> q->back_merge_fn = ll_back_merge_fn;<br id=veu6> q->front_merge_fn = ll_front_merge_fn;<br id=bit0> q->merge_requests_fn = ll_merge_requests_fn;<br id=kbz1> q->make_request_fn = __make_request;<br id=niph> q->plug_tq.sync = 0;<br id=sber> q->plug_tq.routine = &generic_unplug_device; /*note 1*/<br id=wj_l> q->plug_tq.data = q;<br id=rh6s> q->plugged = 0;<br id=og4l> /*<br id=kz-9> * These booleans describe the queue properties. We set the<br id=ymd7> * default (and most common) values here. Other drivers can<br id=p_o3> * use the appropriate functions to alter the queue properties.<br id=dtqa> * as appropriate.<br id=xyzq> */<br id=m:75> q->plug_device_fn = generic_plug_device; /*note 2*/<br id=ypgb> q->head_active = 1;<br id=qoxq>}<br id=o7yl> 负责初始化blk驱动的请求队列. 对于ide:见drivers/ide/ide-probe.c<br id=l3t:>static void ide_init_queue(ide_drive_t *drive)<br id=s95b>{<br id=fq6f> request_queue_t *q = &drive->queue;<br id=g3yg><br id=omoa> q->queuedata = HWGROUP(drive);<br id=a2vb> blk_init_queue(q, do_ide_request);<br id=nc2l>}<br id=vpx7>ide 请求队列中的<br id=uco0> q->request_fn = do_ide_request,<br id=vf4_> q->plug_tq.routine = &generic_unplug_device;<br id=r06g> q->plug_device_fn = generic_plug_device;<br id=w_b9> q->make_request_fn = __make_request;<br id=owmc>首先我们请求读入:<br id=tdkj> submit_bh->generic_make_request-> q->make_request_fn**__make_request:<br id=vh2e>__make_request()<br id=y.4c>{....<br id=xstg>if (list_empty(head)) { //如果当前驱动无其他pending的请求<br id=xegas> //就将队列plug到task queue,这样,可以在一连串的请求都放入<br id=cec7> //请求队列后再开始io,从而可以将连续请求合并到一起<br id=rtin> q->plug_device_fn(q, bh->b_rdev); /* is atomic */ /*generic_plug_device*/<br id=wh4e> goto get_rq;<br id=k47k> }<br id=rg76>.... <br id=zxwt>add_request-> 将读写请求放入q.<br id=p_vt>out:<br id=k1ce> if (!q->plugged) /*如果plug了就不再直接调用request_fn*/<br id=gzgx> (q->request_fn)(q); /* do_ide_request*/<br id=lq9-> <br id=ac8s>} <br id=isws><br id=nbd:>然后当我们直接调用run_task_queue(&tq_disk)->__run_task_queue-><br id=v.g:>tq_disk->routine**generic_unplug_device->__generic_unplug_device-><br id=x:nb>q->request_fn**do_ide_request. <br id=yr13><br id=jbf7> 分析完了这些,就可以理解下面的注释了<br id=t-qn>generic_file_readahead ()<br id=nhqy>{<br id=s5qm> ..........<br id=ixnt>/*<br id=ou-3> * .............<br id=wudr> * If we tried to read ahead asynchronously,<br id=qk3e> * Try to force unplug of the device in order to start an asynchronous<br id=lhsa> * read IO request.<br id=lfgr> * ........<br id=jv9-> */<br id=t-y2> if (ahead) {<br id=ps9.> if (reada_ok == 2) { /*强制unplug,真正开始异步io操作*/<br id=nsk0> run_task_queue(&tq_disk);<br id=h1dw> }<br id=dw23> ....<br id=yf2g> } <br id=vhl9>} <br id=e92h><br id=fo_t> <br id=fu22>(8)sys_sendfile和普通文件的写操作<br id=w-uw> sys_sendfile :内核空间的文件拷贝. 系统完成从一个文件拷贝指定数据到另<br id=z0u7>一个文件的功能.不过这次使用<br id=b18e>do_generic_file_read(in_file, ppos, &desc, file_send_actor);<br id=l_d4> file_send_actor顺势就写入指定文件了.使用的函数是<br id=myld>written = file->f_op->write(file, kaddr + offset, size, &file->f_pos); <br id=y3f2> 对于ext2,就是generic_file_write.(不幸,也在这个文件内,too long):<br id=y05e>和generic_file_read类似,写操作也要转换文件字节流pos到文件页面index,同样<br id=c3ue>在具体的文件系统和vfs层有一个隔离. 我们只关心一下write和read的不同之处,<br id=ssyq>忽略一些和read类似的细节:<br id=b9ll>ssize_t<br id=jac9>generic_file_write(struct file *file,const char *buf,size_t count,loff_t *ppos)<br id=cdmk>{<br id=w:w0> .......<br id=e7tq> cached_page = NULL;<br id=d4f7><br id=pd9-> down(&inode->i_sem);<br id=xd3k><br id=gn6q> pos = *ppos;<br id=wkp4> ... // check something<br id=hzic> status = 0;<br id=to2g> if (count) {<br id=r5jz> remove_suid(inode);<br id=s9_x> inode->i_ctime = inode->i_mtime = CURRENT_TIME;<br id=mt3c> mark_inode_dirty_sync(inode); /*将inode移入super block的dirty队列*/<br id=lbf7> }<br id=npoz><br id=ghul> while (count) {<br id=mx2u> <br id=al91> /*计算页面索引(流地址到页面地址转换)*/<br id=zr9b> offset = (pos & (PAGE_CACHE_SIZE -1)); /* Within page */<br id=bgzv> index = pos >> PAGE_CACHE_SHIFT;<br id=pcbw> bytes = PAGE_CACHE_SIZE - offset;<br id=ma0q> ....<br id=rhyb> /*<br id=i0m6> * Bring in the user page that we will copy from _first_.<br id=l1c3> * Otherwise there's a nasty deadlock on copying from the<br id=k00v> * same page as we're writing to, without it being marked<br id=d.u9> * up-to-date.<br id=ecx2> */<br id=kk2e> { volatile unsigned char dummy; /*用户空间内可能跨两个页面<br id=o6wy> 存储同一个文件页面数据,故需要尝试访问两个页面*/<br id=bw4k> __get_user(dummy, buf);<br id=r-jj> __get_user(dummy, buf+bytes-1);<br id=i3bf> /*为何先访问一下,待会再续*/<br id=i40b> } <br id=h:qj><br id=sy03> <br id=xopn> /*看看page cache有无此页面,若无则分配一个并加入pache cache*/<br id=gifl> status = -ENOMEM; /* we'll assign it later anyway */<br id=d4rm> page = __grab_cache_page(mapping, index, &cached_page);<br id=zlwf> if (!page)<br id=rl1b> break;<br id=d:u4><br id=rysk> /* We have exclusive IO access to the page.. */<br id=h4lq> if (!PageLocked(page)) { /*防止我们操作的时候回写页面*/<br id=f3j3> PAGE_BUG(page);<br id=dmyd> }<br id=dw4n><br id=n.4l> status = mapping->a_ops->prepare_write(file, page, offset, offset+bytes);<br id=wiyx> if (status)<br id=xfv2> goto unlock;<br id=lm_t> kaddr = page_address(page);<br id=cwse> status = copy_from_user(kaddr+offset, buf, bytes);<br id=v718> flush_dcache_page(page);<br id=s78r> if (status)<br id=o0s6> goto fail_write;<br id=ep-0> status = mapping->a_ops->commit_write(file, page, offset, offset+bytes);<br id=lj0b> if (!status)<br id=ebth> status = bytes;<br id=t_-v><br id=cauk> .........<br id=vs5e>unlock:<br id=mpi2> /* Mark it unlocked again and drop the page.. */<br id=frl1> UnlockPage(page);<br id=gabw> if (deactivate) /*deactive 可以促使更快的回写dirty page.另外有可能是<br id=oaer> get_user所作的操作将页面swap in,用完后deactive很合理*/<br id=dhcf> deactivate_page(page);<br id=qp0-> page_cache_release(page);<br id=ps51><br id=ph:1> if (status < 0)<br id=vf6u> break;<br id=lh-7> }<br id=rpkr> *ppos = pos;<br id=l
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -