structbuffer_head*bh=lru->bhs[i];
if(bh&&bh->b_bdev==bdev&&
bh->b_blocknr==block&&bh->b_size==size){
if(i){
while(i){
lru->bhs[i]=lru->bhs[i-1];
i--;
}
lru->bhs[0]=bh;
}
get_bh(bh);
ret=bh;
break;
}
}
bh_lru_unlock();
returnret;
从代码可以看到,内核将最近访问的buffer_head放入bh_lrus链表中,在寻找buffer_head的时候,首先从这个链表中寻找。
bh_lrus链表的长度由BH_LRU_SIZE确定,默认设置为8。
在对bh_lrus链表操作之前要使用bh_lru_lock()加锁,操作完成之后,要使用bh_lru_unlock()解锁。
目的是保护中间的这一段操作不被打断。
那么对于单CPU系统来说,只需要在操作前内核抢占关闭即可,因为关闭抢占之后,除非进程自己放弃CPU,不会发生调度。
而对于多CPU,则需要关闭中断,以防止发生调度。
→→→→→__find_get_block_slow()
如果在bh_lrus链表中没有找到目标bh,那么调用__find_get_block_slow()通过inode->I_mapping来查找,我们之前说,块缓冲是放在页高速缓存页中的,所以先找到indoe对应的页高速缓存页,然后再在高速缓存页中寻找块缓冲。
structinode*bd_inode=bdev->bd_inode;
structaddress_space*bd_mapping=bd_inode->i_mapping;
structbuffer_head*ret=NULL;
pgoff_tindex;
structbuffer_head*bh;
structbuffer_head*head;
structpage*page;
intall_mapped=1;
index=block>>(PAGE_CACHE_SHIFT-bd_inode->i_blkbits);
page=find_get_page(bd_mapping,index);
if(!
page)
gotoout;
spin_lock(&bd_mapping->private_lock);
if(!
page_has_buffers(page))
gotoout_unlock;
head=page_buffers(page);
bh=head;
do{
if(bh->b_blocknr==block){
ret=bh;
get_bh(bh);
gotoout_unlock;
}
if(!
buffer_mapped(bh))
all_mapped=0;
bh=bh->b_this_page;
}while(bh!
=head);
函数先调用find_get_page获得要访问的block对应的page,这其中涉及到对address_space中的基数的操作,不做叙述。
接下来通过page_has_buffers()宏判断page是否存有buffer_head,如果有,则通过page_buffers()将buffer_head取出。
事实上,page->private就是用来存放buffer_head结构的,因此page_has_buffers()和page_buffers()就是通过访问page->private来获得buffer_head的。
得到page中的buffer_headlist之后,通过while循环查找目标bh。
→→→→__find_get_block()
回到函数__find_get_block(),如果找到了目标bh,则通过bh_lru_install()将bh插入到lru链表中。
→→→__getblk()
回到函数__getblk(),如果通过__find_get_block函数没有找到目标bh,则进入__getblk_slow(),创建一个新的bh。
→→→→__getblk_slow()
for(;;){
structbuffer_head*bh;
intret;
bh=__find_get_block(bdev,block,size);
if(bh)
returnbh;
ret=grow_buffers(bdev,block,size);
if(ret<0)
returnNULL;
if(ret==0)
free_more_memory();
}
在进入该函数之前,调用了migth_sleep(),可能因此发生了调度,所以__getblk_slow函数首先检查一遍是否其他进程已经创建了该bh,如果没有创建,则调用grow_buffers()函数来生成。
→→→→→grow_buffers(structblock_device*bdev,sector_tblock,intsize)
Grow_buffer为block_devicebdev创建一个块缓冲,该缓冲区的位置和大小由block和size确定。
→→→→→→grow_dev_page()
Createthepage-cachepagethatcontainstherequestedblock.Thisisuserpurelyforblockdevmappings.
→→→→→→→find_or_create_page((structaddress_space*mapping,
pgoff_tindex,gfp_tgfp_mask)
该函数根据address_space和index查找对应的页高速缓存页page,如果找不到,则使用gfp_mask创建page,并将page加入到lru链表中。
其调用了以下三个函数:
Find_lock_page(),查找目标page
__page_cache_alloc(),分配page
Add_to_page_cache_lru,将page插入到lru链表中。
→→→→→→grow_dev_page()
回到函数grow_dev_page,在得到page后,函数继续判断该page是否已经有buffer存在,如果有,并且是目标buffer,则返回page,如果不是目标buffer,则先释放当前buffer。
if(page_has_buffers(page)){
bh=page_buffers(page);
if(bh->b_size==size){
init_page_buffers(page,bdev,block,size);
returnpage;
}
if(!
try_to_free_buffers(page))
gotofailed;
}
/*
*Allocatesomebuffersforthispage
*/
bh=alloc_page_buffers(page,size,0);
→→→→→→→alloc_page_buffers(page,size,0)
在得到page,并且确定page无buffer后,内核调用alloc_page_buffers()在page中分配大小为size的buffer。
→→→→→→→link_dev_buffers()
→→→→→→→Init_page_buffers()
接着将page和buffer的关系联系起来。
并且在init_page_buffers()函数中将这些buffers
设置为uptodate,也就说这些buffer中的数据目前是无效数据,需要从物理设备中读取。
→→structbuffer_head*__bread((structblock_device*bdev,sector_tblock,unsignedsize)
现在回到函数__bread()。
if(likely(bh)&&!
buffer_uptodate(bh))
bh=__bread_slow(bh);
在得到buffer_head后,检查buffer_head中数据的有效性,如果数据无效,则通过__bread_slow()从物理设备中读取数据。
→→→__bread_slow()
lock_buffer(bh);
if(buffer_uptodate(bh)){
unlock_buffer(bh);
returnbh;
}else{
get_bh(bh);
bh->b_end_io=end_buffer_read_sync;
submit_bh(READ,bh);
wait_on_buffer(bh);
if(buffer_uptodate(bh))
returnbh;
}
brelse(bh);
returnNULL;
函数的主体是submit_bh(),即向实际物理设备提交读请求。
→→→→Submit_bh()
/*
*fromhereondown,it'sallbio--dotheinitialmapping,
*submit_bio->generic_make_requestmayfurthermapthisbioaround
*/
bio=bio_alloc(GFP_NOIO,1);
bio->bi_sector=bh->b_blocknr*(bh->b_size>>9);
bio->bi_bdev=bh->b_bdev;
bio->bi_io_vec[0].bv_page=bh->b_page;
bio->bi_io_vec[0].bv_len=bh->b_size;
bio->bi_io_vec[0].bv_offset=bh_offset(bh);
bio->bi_vcnt=1;
bio->bi_idx=0;
bio->bi_size=bh->b_size;
bio->bi_end_io=end_bio_bh_io_sync;
bio->bi_private=bh;
bio_get(bio);
submit_bio(rw,bio);
if(bio_flagged(bio,BIO_EOPNOTSUPP))
ret=-EOPNOTSUPP;
bio_put(bio);
returnret;
这一部分的代码进入到通用块设备层,我们暂时不关注,只需明白内核在此向物理设备发出了读取请求。
在发出请求后,内核调用wait_on_buffer()等待请求的完成。
并将读取的buffer返回。
这样整个读取数据的过程完成。
2,ext2_lookup(structinode*dir,structdentry*dentry,structnameidata*nd)
该函数会在VFS的do_lookup()函数中被调用,用于从目录dir中找到dentry->d_name指定的文件。
VFS的查找过程在VFS学习笔记中有分析。
如果VFS在内存中找不到目标文件的inode的话,就会进入实际的文件系统去查找。
其函数指针为dir->I_op->lookup()。
如果是ext2文件系统,则会进入此函数。
ino=ext2_inode_by_name(dir,&dentry->d_name);
inode=NULL;
if(ino){
inode=ext2_iget(dir->i_sb,ino);
if(IS_ERR(inode))
returnERR_CAST(inode);
}
returnd_splice_alias(inode,dentry);
函数首先调用ext2_inode_by_name()得到目标文件的inode的设备节点号,然后通过ext2_iget()函数得到inode对象,最后调用d_splice_alias()将inode和dentry联系起来。
→ext2_lookup()
→→ext2_inode_by_name()
ino_tres=0;
structext2_dir_entry_2*de;
structpage*page;
de=ext2_find_entry(dir,child,&page);
if(de){
res=le32_to_cpu(de->inode);
ext2_put_page(page);
}
returnres;
→→→ext2_find_entry()
该函数读取目录dir中child对应的目录项,用ext2_dir_entry_2结构来表示。
在Ext2文件系统中,目录是一种特殊的文件,其文件的数据块存放的就是一堆ext2_dir_entry_2数据结构,
structext2_dir_entry_2{
__le32inode;/*Inodenumber*/
__le16rec_len;/*Directoryentrylength*/
__u8name_len;/*Namelength*/
__u8file_type;
charname[EXT2_NAME_LEN];/*Filename*/
};
通过比较ext2_dir_entry_2中的name和目标文件的name,我们就可以得到目标文件的inode号。
constchar*name=child->name;
intnamelen=child->len;
unsignedreclen=EXT2_DIR_REC_LEN(namelen);
unsignedlongstart,n;
unsignedlongnpages=dir_pages(dir);
structpage*page=NULL;
structext2_inode_info*ei=EXT2_I(dir);
ext2_dirent*de;
intdir_has_error=0;
if(npages==0)
gotoout;
/*OFFSET_CACHE*/
*res_page=NULL;
start=ei->i_dir_start_lookup;
if(start>=npages)
start=0;
n=start;
do{
char*kaddr;
page=ext2_get_page(dir,n,dir_has_error);
if(!