EXT2学习笔记.docx

上传人:b****5 文档编号:5099242 上传时间:2022-12-13 格式:DOCX 页数:16 大小:24.22KB
下载 相关 举报
EXT2学习笔记.docx_第1页
第1页 / 共16页
EXT2学习笔记.docx_第2页
第2页 / 共16页
EXT2学习笔记.docx_第3页
第3页 / 共16页
EXT2学习笔记.docx_第4页
第4页 / 共16页
EXT2学习笔记.docx_第5页
第5页 / 共16页
点击查看更多>>
下载资源
资源描述

EXT2学习笔记.docx

《EXT2学习笔记.docx》由会员分享,可在线阅读,更多相关《EXT2学习笔记.docx(16页珍藏版)》请在冰豆网上搜索。

EXT2学习笔记.docx

EXT2学习笔记

EXT2学习笔记

一,目标

以ULK和Linux内核源码情景分析为参考,通过阅读EXT2文件系统的代码,理清楚从VFS到块设备驱动中间这一段的机制与流程。

二,方式

边阅读代码,边记录,结合ULK,最后总结。

三,源码阅读

在VFS的学习过程中,凡是涉及到具体文件系统的函数接口,都只是理解其作用,对于其机制和流程没有深入研究。

这些接口包括:

●mount时get_sb操作,即file_system_type->get_sb()/kill_sb()

1,ext2_get_sb()→ext2_fill_super()

当mountext2文件系统时,VFS会调用ext2_get_sb()来获得其超级块,ext2_fill_super()则是函数中涉及实际操作的回调函数。

Ext2_fill_super()/fs/ext2/super.c

代码很复杂,不过大部分对我们来说不用去特别关注,像le16_to_cpu()类似的,都是用来做大小端字节序转换的,还有一部分出错处理的代码也拿去,那么主要代码如下:

………………

unsignedlongsb_block=get_sb_block(&data);

………………

if(!

(bh=sb_bread(sb,logic_sb_block))){

printk("EXT2-fs:

unabletoreadsuperblock\n");

gotofailed_sbi;

}

………………

//Initext2_sb_info

………………

/*

*setupenoughsothatitcanreadaninode

*/

sb->s_op=&ext2_sops;

sb->s_export_op=&ext2_export_ops;

sb->s_xattr=ext2_xattr_handlers;

root=ext2_iget(sb,EXT2_ROOT_INO);

if(IS_ERR(root)){

ret=PTR_ERR(root);

gotofailed_mount3;

}

if(!

S_ISDIR(root->i_mode)||!

root->i_blocks||!

root->i_size){

iput(root);

printk(KERN_ERR"EXT2-fs:

corruptrootinode,rune2fsck\n");

gotofailed_mount3;

}

sb->s_root=d_alloc_root(root);

if(!

sb->s_root){

iput(root);

printk(KERN_ERR"EXT2-fs:

getrootinodefailed\n");

ret=-ENOMEM;

gotofailed_mount3;

}

if(EXT2_HAS_COMPAT_FEATURE(sb,EXT3_FEATURE_COMPAT_HAS_JOURNAL))

ext2_warning(sb,__func__,

"mountingext3filesystemasext2");

ext2_setup_super(sb,es,sb->s_flags&MS_RDONLY);

return0;

代码可分解为两个部分:

读取超级块数据,初始化ext2_super_block和ext2_sb_info数据结构。

Ext2_fill_super()

→get_sb_block()

该函数会对mount操作传入参数中的sb部分进行判断,该参数会指定superblock的位置,查看mount命令,可以看到sb选项。

如果不指定,内核默认为BLOCK1(BLOCK0是引导块)。

→structbuffer_head*sb_bread(structsuper_block*sb,sector_tblock)

函数从super_block指定的block_device中读取其超级块。

该函数接受两个参数,分别是super_block数据结构和super_block所在的块Index。

函数返回buffer_head数据结构。

→→structbuffer_head*__bread((structblock_device*bdev,sector_tblock,unsignedsize)

函数从指定的block_device中从第block个BLOCK开始读取大小为size的数据。

该函数接受三个参数,分别是block_device数据结构,要读取的数据所在block,要读取的数据大小。

函数返回buffer_head数据结构。

这里对buffer_head数据简单介绍一下。

在Linux内核中,基本上所有的文件读和写操作都不是直接对物理设备进行操作,而是依赖于页高速缓存,当然directIO操作除外。

也就是说文件读写操作实际上是对页高速缓存的操作,读文件的时候,如果该文件已经在内核中缓存,并且包含有效数据,那么读操作不会向物理设备发读请求,直接将页高速缓存中的数据返回,如果该文件没有缓存,那么先生成针对该文件的页高速缓存,然后返回其中数据;在写文件的时候,数据并没有立即写入到物理设备中,而是先仅仅写入到页高速缓存,等待pdflush刷入物理设备中。

在2.2kernel的时候,还存在块缓冲区高速缓存,页高速缓存和块缓冲区高速缓存是两个不同类型的缓存,前者缓存的页面,后者缓存的是数据块。

在2.4kernel的时候,取消了块缓冲区缓存,块缓冲区高速缓存放在页高速缓存的页中。

具体见ULK。

数据结构address_space来表示一组页高速缓存的缓存页,buffer_head表示一个块缓冲区,位于某个页高速缓存缓存页中。

一个缓存页可以存放多个块缓冲。

回到函数__bread,函数很短,而且在ULK上都这部分的代码有简单介绍,有不清楚的可以去看ULK。

structbuffer_head*bh=__getblk(bdev,block,size);

if(likely(bh)&&!

buffer_uptodate(bh))

bh=__bread_slow(bh);

returnbh;

首先进入函数__getblk():

→→→__getblk()

→→→→__find_get_block()

structbuffer_head*bh=lookup_bh_lru(bdev,block,size);

if(bh==NULL){

bh=__find_get_block_slow(bdev,block);

if(bh)

bh_lru_install(bh);

}

if(bh)

touch_buffer(bh);

returnbh;

__find_get_block函数首先调用lookup_bh_lru函数在内核中已存在的buffer_head的LRU链表中寻找,是否有目标bh。

→→→→→lookup_bh_lru()

structbuffer_head*ret=NULL;

structbh_lru*lru;

unsignedinti;

check_irqs_on();

bh_lru_lock();

lru=&__get_cpu_var(bh_lrus);

for(i=0;i

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(!

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 总结汇报 > 实习总结

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1