您好,欢迎来到九壹网。
搜索
您的当前位置:首页linux filesystem

linux filesystem

来源:九壹网
1

linux文件系统中的超级块结构和inode超级块

超级块结构表示一个文件系统。它包含管理文件系统所需的信息,包括文件系统名称(比如ext2)、文件系统的大小和状态、块设备的引用和元数据信息(比如空闲列表等等)。超级块通常存储在存储媒体上,但是如果超级块不存在,也可以实时创建它。可以在./linux/include/linux/fs.h中找到超级块结构(见图4)。超级块结构和inode操作

超级块中的一个重要元素是超级块操作的定义。这个结构定义一组用来管理这个文件系统中的inode的函数。例如,可以用alloc_inode分配inode,用destroy_inode删除inode。可以用read_inode和write_inode读写inode,用sync_fs执行文件系统同步。可以在./linux/include/linux/fs.h中找到super_operations结构。每个文件系统提供自己的inode方法,这些方法实现操作并向VFS层提供通用的抽象。

inode和dentry

inode表示文件系统中的一个对象,它具有惟一标识符。各个文件系统提供将文件名映射为惟一inode标识符和inode引用的方法。图5显示inode结构的一部分以及两个相关结构。请特别注意inode_operations和file_operations。这些结构表示可以在这个inode上执行的操作。inode_operations定义直接在inode上执行的操作,而file_operations定义与文件和目录相关的方法(标准系统调用)。inode结构和相关联的操作

2

inode和目录缓存分别保存最近使用的inode和dentry。注意,对于inode缓存中的每个inode,在目录缓存中都有一个对应的dentry。可以在./linux/include/linux/fs.h中找到inode和dentry结构。WritingaSimpleFileSystemAuthor:RaviKiranUVS1.Objective

Wewillwriteafilesystemwithverybasicfunctionality.ThisistounderstandtheworkingofcertainkernelcodepathsoftheVirtualFileSystem(VFS).Thisfilesystemhasonlyonefile'hello.txt'.Wecanread/writeintoit.2.Introduction

TheLinuxVFSsupportsmultiplefilesystems.Thekerneldoesmostoftheworkwhilethefilesystemspecifictasksaredelegatedtotheindividualfilesystemsthroughthehandlers.InsteadofcallingthefunctionsdirectlythekernelusesvariousOperationTables,whichareacollectionofhandlersforeachoperation(theseareactuallystructuresoffunctionpointersforeachhandlers/callbacks).Thekernelcallsthehandlerpresentinthetablefortheoperation.Thisenablesdifferentfilesystemstoregisterdifferenthandlers.Thisalsoenablesthecommontaskstobedonebeforecallingthehandlers.Thisreducestheburdenonthehandlerswhichcanthenfocusontheoperationspecifictothatfilesystem.

Filesystemsareidentifiedbytheirnames.Thesupportedfilesystemscanbe

3

seenusing'cat/proc/filesystems'.Thefirststepistoregisterthefilesystemwiththekernel.Sinceweareusingakernelmodule,thefilesystemregistrationisdoneduringthemoduleinitialization.Thisregistershandlerswhichwillbecalledtofillthesuperblockstructurewhilemounting,ahandlertodothecleanupduringunmountingthefilesystem.Thereareotherhandlersbutthesetwoareessential.

Thesuperblockoperationsaresetatthetimeofmounting.Theoperationtablesforinodesandfilesaresetwhentheinodeisopened.Thefirststepbeforeopeninganinodeislookup.Theinodeofafileislookedupbycallingthelookuphandleroftheparentinode.Butwhatabouttheroot-mostinodeofthenewfilesystem?Thishastobeallocatedatthetimeofmountingi.e.,duringthesuperblockinitialization.

Oncetheoperationtablesaresetonthedatastructures,thekernelcallsthehandlersdependingontheoperation.3.DataStructures

Thisisabriefdescriptionaboutthedatastructuresusedinimplementingourfilesystem.

a.FileSystemType(structfile_system_type)Definitionfoundininclude/linux/fs.h

Thisstructureisusedtoregisterthefilesystemwiththekernel.Thisdatastructureisusedbythekernelatthetimeofmountingafilesystem.Wehavetofillthe'name'fieldwiththenameofourfilesystem(example\"rkfs\")andthehandlersget_sbandkill_sbtoallocateandreleasethesuperblockobjects.b.SuperBlock(structsuper_block)Definitionfoundininclude/linux/fs.h

Thisstorestheinformationaboutthemountedfilesystem.Theimportantfieldstobefilledaretheoperationtable(s_opsfield)andtherootdentry(s_root).Atthetimeofmountingafilesystem,thekernelcallstheget_sbfieldofthefile_system_typeobject(itidentifiesthecorrectfile_system_typeobjectbasedonthefilesystemname)togetasuperblockobject.c.SuperBlockOperations(structsuper_operations)Definitionfoundininclude/linux/fs.hSuperblockoperationstable.

4

d.Inode(structinode)

Definitionfoundininclude/linux/fs.h

Inodeobjectisthekernelrepresentationofthelowlevelfile.Wereturnthedentryoftherootofourfilesystem.Wehavetoattachaproperinodealsotothedentry.

Thisstructurehastwooperationtablesi_op,i_fopi.e.,inodeoperationsandfileoperationsrespectively.Wewillimplementoneoperationintheinode_operations-lookup.

Thisiscalledwhenthekernelisresolvingapath.Thekernelstartsfromtheancestor(thiscanbethecurrentworkingdirectoryforrelativepathsortherootmostdirectoryfortheabsolutepaths)andgetsthedentry(alsotheinode)ofanamecomponentofthepathfromitsparent.Thisisachievedbycallinginode_operations.lookupontheinodeoftheparententry.

Forexample,whenthekernelisresolving/parentdir/subdir,thelookupoperationreachestherootmostinodeofthefilesystem.Thiswasalreadyallocatedduringthesuperblockinitializationandstoredinthes_rootfield.Toresolvethe'parentdir'undertherootmostinode,thekernelcreatesanewdentryobject,setsthenameas'parentdir'andcallslookuphandleroninodeoftherootmostinode.Thehandlerissupposedtoattachtheinodetothedentryusingd_addandreturnNULLifitwassuccessfuloranerrorcodeotherwise.Similarly,thelookupfor'subdir'isdonebytheparentdirinode.Thedentrycacheandtheinodecachesavesrepeatedlookupsandbooststheperformance.

Itisimportantforustoimplementthelookupcallback.Thiswillbecalledbytheopensystemcall.

e.InodeOperations(structinode_operations)Definitionfoundininclude/linux/fs.h

Thisistheinodeoperationstablewitheachfieldcorrespondingtoafunctionpointertohandlethetask.Ithasfieldslikemkdir,lookupetc.Weareinterestedinlookup.

f.AddressSpaceOperations(structaddress_space_operations)Definitionfoundininclude/linux/fs.hAddressspaceoperationstable.g.DEntry(structdentry)

Definitionfoundininclude/linux/dcache.h

5

Thekernelusesdentriestorepresentthefilesystemstructure.dentriespointtoinodeobjects.Thishaspointerstostoretheparent-childrelationshipofthefiles.Inodesandfilesdonotstoreanyinformationaboutthehierarchy.h.File(structfile)

Definitionfoundininclude/linux/fs.h

Fileobjectisusedtostoretheprocess'sinformationaboutthefile.Wedonthavetofillanyfieldsofthefilesdirectly.Thekerneltakescareoffillingtheproperfieldsbutwehavetoimplementthefileoperationcallbacks.Weregisterthefileoperationtablewhenwereturntheinodeobjectduringlookup.Thefileoperationsarecopiedfromthei_fopfieldoftheinodeobjecttothefileobjectbythekernel.

Wewillimplementreaddirincaseofdirectories(whilereturningtheinode,wehavetosetthefileoperationtablebasedonthetypeofthefile)andread/writeinthecaseofregularfiles.Wewillhavetwofileoperationtablesonefordirectoriesandtheotherforregularfiles.

Therelationshipbetweenfiles,dentriesandobjectsislikethis:File--->DEntry--->Inode

6

ThefollowingfigureshowstheimportantrelationshipsbetweenvariousVFSdatastructures.Itdoesnotshowalltherelationshipsthough.Notethatsuperblockstructurehasalistofallopenfileobjects,alistofdirtyinodesandanotherlistoflockedinodesofthefilesystem.Alsothelistsusedforcache,lruandfreelistsarenotshown.

i.FileOperations(structfile_operations)Definitionfoundininclude/linux/fs.h

Thisisthefileoperationstablewitheachfieldcorrespondingtoafunctionpointertohandlethetask.Ithasfieldslikeread,write,readdir,llseeketc.Allthesestructureshavefieldsusedbythekernelinmaintaininginternaldatastructureslikelistsandhashtablesetc.So,wecannotuselocal/globalobects.Kernelallocatestheobjectandpassesittoourfunctionssothatwecanfilltherequiredfields.Ifwehavetoallocatetheobjects,weneedtousethecorrespondingallocatorfunctions.3.ImplementationFileSystemType

Thefilesystemisregisteredwiththekernelduringthemoduleinitialization.Duringthisstep,twohandlersget_sbandkill_sb.get_sbiscalledatthetimeofmountingthefilesystem.kill_sbiscalledatthetimeofunmountingthefilesystem.Theget_sbhandlerusesthekernelhelperfunctionget_sb_singletoallocatethesuperblock.Wepassacallback,rkfs_fill_super,tothisfunctionwhichwillbecalledtofillthesuperblockstructure.Sincewedonthaveanyspecifictasktodointhethekill_sbhandler,wecanusethekernelhelperfunctionkill_anon_super.

SuperBlockOperations

We'llregistertheread_inodeandwrite_inodecallbacksinthesuperoperationstable.read_inodewillbecalledwhentheinodeobjectisnewlyallocated.Inodesareidentifiedbytheinodenumbers.Basedontheinodenumber,thefilesystemwillhavetoresolvetheinodeobjectonthestorageandfillthefieldsoftheinodewiththecontentsonthestorage.Inourcaseitissimpleasweareimplementingamemorybasedfilesystem.Filesystemslikeext2willhavetoreadtheinodefromthediskusingtheinodenumber.Dependingonthefiletype,differenthandlerscanberegisteredforthefileoperationstable.

write_inodehandlerwillbecalledtosynctheinodecontentstothestorage.Inourcase,wewillupdatethefilesizevariableinmemory.InodeOperations

7

We'llregisterthelookupcallbackintheinodeoperationstable.Adentryobjectwillbeallocatedbythekernelandpassedtothehandler.Thenamecomponentissetonthedentrybythekernel.Inthehandler,wehavetocheckwhetheranentrybythatnameexistsundertheparentinode.Ifanentryexists,theinodeobjectisobtainedbypassingthesuperblockobjectandtheinodenumbertothefunctioniget.Thisusestheinodecacheandwillallocateanewinodeifitisnotavaiableintheinodecache.(Notethatwhentheinodeisnewlyallocated,theread_inodehandlerofthesuperblockiscalledtofilltheinode).Thisinodeisaddedtothedentryobjectusingd_add.ThereturnvalueshouldbeaNULLonsuccessfullookuporanerrorcodeotherwise.

FileOperations

We'llregisterread,writeandreaddirhandlersinthefileoperationstable.readandwritehandlersarecalledtoreadandwritedataintothefile.readdiriscalledtoreadthecontentsofadirectory.

Thefollowingtableshowsthefieldsweneedtofillintheabovedatastructures.

Thefollowingtableshowstheoperationtablesandthehandlersused.

8

4.Entrypointsa.init_module

Thisiscalledwhenthemoduleisloaded.Wehavetoregisterourfilesystemhere.Fillthefile_system_typestrucurewithnameandread_superfieldsandcallregister_filesystemwiththestructure.Forexample,staticstructfile_system_typerkfs={

name:\"rkfs\get_sb:rkfs_get_sb,kill_sb:rkfs_kill_sb,owner:};

intinit_module(void){

interr;

err=register_filesystem(&rkfs);returnerr;}

b.file_system_type.get_sb

Thiswillbecalledwhenthefilesystemismounted.Wehavetoreturnasuper

THIS_MODULE

9

block.Weusethehelperfunctionget_sb_simpletodothesuperblockallocationandalsopassingrkfs_fill_supercallbacktofillthesuperblockobject.Thes_opfieldissetwiththeaddressofthesuperblockoperationstablerkfs_sops.Therootmostinodeofthefilesystemhastobeallocatedatthisstage.Thedentryforitshouldbesetonthes_rootfieldofthesuperblock.Asmentionedearlier,thisistheentrypointoflookupoperationsintothefilesystem.

Theinodeobjectisallocatedusingthefunctioniget.Afterinitializingtheinode,thedentryisallocatedusingthefunctiond_alloc_root.Thisdentryissettothes_rootfieldofthesuperblock.

staticint

rkfs_fill_super(structsuper_block*sb,void*data,intsilent){

sb->s_blocksize=1024;sb->s_blocksize_bits=10;

sb->s_magic=RKFS_MAGIC;

sb->s_op=&rkfs_sops;//superblockoperationssb->s_type=&rkfs;//file_system_type

rkfs_root_inode=iget(sb,1);//allocateaninode

rkfs_root_inode->i_op=&rkfs_iops;//settheinodeopsrkfs_root_inode->i_mode=S_IFDIR|S_IRWXU;rkfs_root_inode->i_fop=&rkfs_fops;

if(!(sb->s_root=d_alloc_root(rkfs_root_inode))){

iput(rkfs_root_inode);

return-ENOMEM;}return0;}

staticstructsuper_block*

rkfs_get_sb(structfile_system_type*fs_type,intflags,constchar*devname,void*data){

/*rkfs_fill_superthiswillbecalledtofillthesuperblock*/returnget_sb_single(

fs_type,flags,data,

&rkfs_fill_super);

}

Letusassumethatourfilesystemismountedunder/mnt/rkfs.

10

c.file_system_type.kill_sb

Thisiscalledatthetimeofunmountingthefilesystem.Weusethehelperfunctionkill_anon_superforthis.d.super_operations.read_inode

Inodesobjectsshouldbeallocatedusingtheigetfunction.Thisusestheinodecache,adjuststherelationshipsoftheinodewithvariousdatastructures,updatesthecountetc.Iftheinodeisnotcached,itallocatesanewinodeandcallstheread_inodehandlerofthesuperblockifpresent.Inodesareidentifiedbytheinodenumbers.Thehandlerisexpectedtoinitializetheinodewiththecontentsoftheinodeonthebackend.e.super_operations.write_inode

Thiswillbecalledwhenthedirtyinodesareflushed.Thehandlerhastosynctheinodecontentstothebackend.d.inode_operations.lookup

Thiswillbecalledwhenthekernelisresolvingapath.Thelookuphandleroftheinodeoperationtableoftheparentinodeiscalledtoresolveachild.Rememberthatthedentryfortherootmostinodeisalreadyavailableins_rootfieldofthesuperblock.

Forexample,aftermountingthefilesystemunder'/mnt/rkfs'ifwewanttoseethecontentsusing'ls/mnt/rkfs',thekernelhastocreateafileobjectfortheinode'/mnt/rkfs'.Thekernelwillcreateafileobjectwiththedentryoftherootmostinodeofthefilesystem.Forthecommand'ls-l/mnt/rkfs/hello.txt',thekernelnamelookupreachestherootmostinodeandthelookuphandlerwillbecalledtosettheinodeof'hello.txt'.Thekernelallocatesthedentryobjectandpassestothehandler.Ifaninodeexistsforthenamecomponent,theinodehastobeaddedtothedentryusingd_addandNULLshouldbereturned.Ifthereissomeproblem,asuitableerrorcodehastobereturned.

staticstructdentry*

rkfs_inode_lookup(structinode*parent_inode,structdentry*dentry,structnameidata*nameidata){

structinode*file_inode;

if(parent_inode->i_ino!=rkfs_root_inode->i_ino)

returnERR_PTR(-ENOENT);if(dentry->d_name.len!=strlen(\"hello.txt\")||

11

strncmp(dentry->d_name.name,\"hello.txt\dentry->d_name.len))returnERR_PTR(-ENOENT);

file_inode=iget(parent_inode->i_sb,FILE_INODE_NUMBER);if(!file_inode)

returnERR_PTR(-EACCES);file_inode->i_size=file_size;

file_inode->i_mode=S_IFREG|S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH;file_inode->i_fop=&rkfs_fops;//file_inode->i_fopd_add(dentry,file_inode);returnNULL;}

e.file_operations.readdir

Thiswillbecalledwhenthekernelwantstoreadthecontentsofadirectory.Thereaddirhandlerofthefileoperationstableofthefilewillbecalledtoshowthecontentsofthedirectory.Thekernelpassesthefileobject,thedirentstructureandacallbacktofillthedirentstructurewiththevaluesofthecontentsofthedirectory.Thevaluesareaddedtothedirentstructureusingthe'filldir'callback.Sincewesupportonehardcodedfile'hello.txt',wejusthavetoreturnthevalues,'.','..'and'hello.txt'.Filesystemslikeext2willhavetofetchthecontentsfromthedisk.

intrkfs_f_readdir(structfile*file,void*dirent,filldir_tfilldir){

interr;

structdentry*de=file->f_dentry;

printk(\"rkfs:file_operations.readdircalled\\n\");

if(file->f_pos>0)

return1;

if(filldir(dirent,\".\1,file->f_pos++,de->d_inode->i_ino,DT_DIR)||

(filldir(dirent,\"..\2,file->f_pos++,de->d_parent->d_inode->i_ino,

DT_DIR)))

return0;

if(filldir(dirent,\"hello.txt\9,file->f_pos++,FILE_INODE_NUMBER,DT_REG))

return0;return1;}

Inourfilesystem,wearesupportingonlyonefilei.e.,hello.txt.So,theresultof'ls/mnt/rkfs'willbe...hello.txt

12

f.file_operations.read

Thiswillbecalledwhenthekernelgetsareadrequestforafileinourfilesystem.Thefileobjectofthefiletoberead,theuser-spacebufferaddress,themaximumsizeofthebufferandtheaddressoftheoffset(whichcontainsthecurrentoffsetandwhichhastobeupdatedaftersuccessfulreadoperation)arepassed.Thecontentsofthefilehavetobewrittentothebuffer.Notethatthisisintheuser-space.Thedataiscopiedtotheuser-spacebufferusingthefunctioncopy_to_user.

Therearetwowaysofsupportingthisoperation.Onewayistoprovideareadhandlerwhichwritesthedatatothebuffer.Butthedrawbackisthatwecannottakeadvantageofthepagecache.Filescanalsoberead/writtenusingthemmap(memorymapping).Withthisapproach,wecannotsupportthemmapwayofaccessingthefile(oritisverydifficulttoprovidetransparencyi.e.,filewrittenaftermappingandreadwith'read'systemcall).

Thesecondwayistoprovideaunifiedwaytoread/writetothefileforboththeapproachesi.e.,callingthesystemcallsdirectlyorbymappingthefileandreading/writingthecontentsinmemory).Thistakestheadvantageofpagecachealso.Thisisapplicabletothewriteoperationalso.

Letustakethesecondapproach(thecodeforthefirstapproachisalsoprovidedlater).Theapproachisslightlydifferentinthiscase.Thecontentsofaninodeareseenaschunksofpagesandrepresentedbyaddess_spaceobject.This'mapping'betweentheinodeandtheaddressspaceobjectisstoredinthei_mappingfieldoftheinode.Toreadsomedatafromthepage,thecorrespondingchunks/pageswhichholdsthedataareloadedintomemory.AddressSpaceOperationstableisusedtoperformdifferentoperationsontheaddressspaceobject(a_opsfield).Thereadpagehandlerofthetableisusedtoreadthecontentsofapageoftheinodeintomemory.Forexample,ifthepagesizeis4096,thedatafrom5000to6000bytesispresentinthe2ndpageoftheinode(similarly,thedatafrom4000to5000ispresentinthepages1and2).Sincetheactualworkofreadingthedataismovedtoaddress_space_operations.readpagehandler,wecanusethegeneric_file_readhelperfunctionasthereadhandler.Thisfunctiongetthepagesofthedataandcopiestotheuser-spacebuffer.Ifthepagesarenotinthecache,itwaitstillthepagesareloadedwiththedatausingthe'readpage'handler.g.address_space_operations.readpage

Thereadpagehandlerhastofillthepagewiththecontentsoftheinode.The

13

indexofthepageisobtainedfromthe'index'fieldofthepagestructure.staticint

rkfs_readpage(structfile*file,structpage*page){

printk(\"RKFS:readpagecalledforpageindex=[%d]\\n\(int)page->index);

if(page->index>0){

return-ENOSPC;}

SetPageUptodate(page);

memcpy(page_address(page),file_buf,PAGE_SIZE);if(PageLocked(page))

unlock_page(page);return0;}

h.file_operations.write

Weregistergeneric_file_writeasthewritehandler.Thisallocatespagesandcallsprepare_writehandlerontheaddressspaceobjectsothatbufferheadobjectscanbeallocatedforthepagetoperformtheI/Otothedevicelater.Itcopiesthedatafromtheuser-spacetothepagesandcallscommit_writeontheaddressspaceobject.

Filesystemslikeext2generallyhaveaspecificimplementationofprepare_write.Theallocationofbufferheadobjectsforthewriteoperationwillbesameformostofthefilesystemsexceptforthelocationofthebufferheads(bufferheadisthekernel'scopyofadiskblock).Inthiscase,theyusethehelper/wrapperfunctionblock_prepare_writeandpassingacallback(incaseofext2,itisext2_get_block)whichwillgivetheblocknumberforthefileoffset.

Sincewritingthebuffersassociatedwiththepagestothedeviceissimilartomostofthefilesystems,theynormallyusegeneric_commit_writehelperfunction.Thismarksthebufferasdirtysothatitwillbeflushedlatertothedevicebytheblockdevicelayer.

Thiswritesthedatafromuser-spacetothepages.Thepagesaresyncedlater.Notethattheread/writehappensonthecachedpages.i.address_space_operations.commit_write

Thegeneric_commit_writehelperfunctionsetsupthebufferstobewrittentothedisk.Sincewearenotusinganydevice,thishasbeenmodifiedtowriteintothememorybufferofthefile.staticint

14

rkfs_commit_write(structfile*file,structpage*page,

unsignedfrom,unsignedto){

structinode*inode=page->mapping->host;

loff_tpos=((loff_t)page->index<index==0){

memcpy(file_buf,page_address(page),PAGE_SIZE);

ClearPageDirty(page);}

/*

*Noneedtousei_size_read()here,thei_size

*cannotchangeunderusbecauseweholdi_sem.*/

if(pos>inode->i_size){

i_size_write(inode,pos);mark_inode_dirty(inode);}

SetPageUptodate(page);return0;}

i.address_space_operations.writepage

Thiswillbecalledwhenthedirtypagesareflushed.

staticint

rkfs_writepage(structpage*page,structwriteback_control*wbc){

printk(\"[RKFS]offset=%d\\n\(int)page->index);

memcpy(file_buf,page_address(page),PAGE_SIZE);ClearPageDirty(page);if(PageLocked(page))unlock_page(page);return0;}

k.cleanup_module

Thiswillbecalledwhenthemoduleisremoved.Wehavetounregisterourfilesystematthispoint.Themodulecountwillbeincrementedanddecrementedbythefilesystemcalls.Sothemodulewillnotberemovedifthemoduleusecount

15

isnotzero.Thekerneltakescareofthis,soweneednotdoanythingtocheckifourfilesystemisinuse.5.code

Note:thishassomedebuggingmessages...youcanremoveallthe'printk's.rkfs.c

6.Instructionstousethecode

Thiscodeworksonthe2.6kernels.Ihaven'ttestediton2.4kernels.

Compileusingthe2.6buildsystem.CreateaMakefilelikethis:(oryoucandownloadthecodehere)ifneq(${KERNELRELEASE},)obj-m+=rkfs.oelse

KERNEL_SOURCE:=/lib/modules/$(shelluname-r)/buildPWD:=$(shellpwd)

default:

$(MAKE)-C${KERNEL_SOURCE}SUBDIRS=$(PWD)modulesclean:

rm*.o*.koendif

make

Thisgeneratesafilerkfs.o.Loadthemoduleasrootusinginsmodrkfs.ko

Mountthefilesystemusingmount-trkfsnone/mnt/rkfsUnmountusingumount/mnt/rkfsUnloadthemoduleusingrmmodrkfsNote:

TherehavebeensomechangestothefilesystemAPIrecently(i'mnotsurefrom

16

whichversionexactly...i'mtryingtofigureoutbuti'dbegladifsomebodycanpointittome).Thegeneric_file_readandgeneric_file_writehavearenotavailableanymore.Thesametaskisachievedbyusingdo_sync_readanddo_sync_writewhichusestheasynci/ohandlers.Themodifiedcodecanbefoundhere.

从文件I/O看Linux的虚拟文件系

1引言

Linux中允许众多不同的文件系统共存,如ext2,ext3,vfat等。通过使用同一套文件I/O系统调用即可对Linux中的任意文件进行操作而无需考虑其所在的具体文件系统格式;更进一步,对文件的操作可以跨文件系统而执行。如图1所示,我们可以使用cp命令从vfat文件系统格式的硬盘拷贝数据到ext3文件系统格式的硬盘;而这样的操作涉及到两个不同的文件系统。图1.跨文件系统的文件操作

“一切皆是文件”是Unix/Linux的基本哲学之一。不仅普通的文件,目录、字符设备、块设备、套接字等在Unix/Linux中都是以文件被对待;它们虽然类型不同,但是对其提供的却是同一套操作界面。图2.一切皆是文件

17

而虚拟文件系统正是实现上述两点Linux特性的关键所在。虚拟文件系统(VirtualFileSystem,简称VFS),是Linux内核中的一个软件层,用于给用户空间的程序提供文件系统接口;同时,它也提供了内核中的一个抽象功能,允许不同的文件系统共存。系统中所有的文件系统不但依赖VFS共存,而且也依靠VFS协同工作。

为了能够支持各种实际文件系统,VFS定义了所有文件系统都支持的基本的、概念上的接口和数据结构;同时实际文件系统也提供VFS所期望的抽象接口和数据结构,将自身的诸如文件、目录等概念在形式上与VFS的定义保持一致。换句话说,一个实际的文件系统想要被Linux支持,就必须提供一个符合VFS标准的接口,才能与VFS协同工作。实际文件系统在统一的接口和数据结构下隐藏了具体的实现细节,所以在VFS层和内核的其他部分看来,所有文件系统都是相同的。图3显示了VFS在内核中与实际的文件系统的协同关系。

图3.VFS在内核中与其他的内核模块的协同关系

18

我们已经知道,正是由于在内核中引入了VFS,跨文件系统的文件操作才能实现,“一切皆是文件”的口号才能承诺。而为什么引入了VFS,就能实现这两个特性呢?在接下来,我们将以这样的一个思路来切入文章的正题:我们将先简要介绍下用以描述VFS模型的一些数据结构,总结出这些数据结构相互间的关系;然后选择两个具有代表性的文件I/O操作sys_open()和sys_read()来详细说明内核是如何借助VFS和具体的文件系统打交道以实现跨文件系统的文件操作和承诺“一切皆是文件”的口号。2VFS数据结构2.1一些基本概念

从本质上讲,文件系统是特殊的数据分层存储结构,它包含文件、目录和相关的控制信息。为了描述这个结构,Linux引入了一些基本概念:

文件一组在逻辑上具有完整意义的信息项的系列。在Linux中,除了普通文件,其他诸如目录、设备、套接字等也以文件被对待。总之,“一切皆文件”。目录目录好比一个文件夹,用来容纳相关文件。因为目录可以包含子目录,所以目录是可以层层嵌套,形成文件路径。在Linux中,目录也是以一种特殊文件被对待的,所以用于文件的操作同样也可以用在目录上。目录项

在一个文件路径中,路径中的每一部分都被称为目录项;如路径/

home/source/helloworld.c中,目录/,home,source和文件helloworld.c都是一个目录项。

索引节点用于存储文件的元数据的一个数据结构。文件的元数据,也就是文件的相关信息,和文件本身是两个不同的概念。它包含的是诸如文件的大小、拥有者、创建时间、磁盘位置等和文件相关的信息。

超级块用于存储文件系统的控制信息的数据结构。描述文件系统的状态、文件系统类型、大小、区块数、索引节点数等,存放于磁盘的特定扇区中。如上的几个概念在磁盘中的位置关系如图4所示。图4.磁盘与文件系统

19

关于文件系统的三个易混淆的概念:

创建以某种方式格式化磁盘的过程就是在其之上建立一个文件系统的过程。创建文现系统时,会在磁盘的特定位置写入关于该文件系统的控制信息。

注册向内核报到,声明自己能被内核支持。一般在编译内核的时侯注册;也可以加载模块的方式手动注册。注册过程实际上是将表示各实际文件系统的数据结构structfile_system_type实例化。

安装也就是我们熟悉的mount操作,将文件系统加入到Linux的根文件系统的目录树结构上;这样文件系统才能被访问。2.2VFS数据结构

VFS依靠四个主要的数据结构和一些辅助的数据结构来描述其结构信息,这些数据结构表现得就像是对象;每个主要对象中都包含由操作函数表构成的操作对象,这些操作对象描述了内核针对这几个主要的对象可以进行的操作。2.2.1超级块对象

存储一个已安装的文件系统的控制信息,代表一个已安装的文件系统;每次一个实际的文件系统被安装时,内核会从磁盘的特定位置读取一些控制信息来填充内存中的超级块对象。一个安装实例和一个超级块对象一一对应。超级块通过其结构中的一个域s_type记录它所属的文件系统类型。

根据第三部分追踪源代码的需要,以下是对该超级块结构的部分相关成员域的描述,(如下同):

20

清单1.超级块

structsuper_block{//超级块数据结构

structlist_heads_list;

……

structfile_system_type*s_type;structsuper_operations*s_op;……

structlist_head……

};

structsuper_operations{//超级块方法

……

//该函数在给定的超级块下创建并初始化一个新的索引节点对象

structinode*(*alloc_inode)(structsuper_block*sb);……

//该函数从磁盘上读取索引节点,并动态填充内存中对应的索引节点对象的剩余部

void(*read_inode)(structinode*);……

};

2.2.2索引节点对象

索引节点对象存储了文件的相关信息,代表了存储设备上的一个实际的物理文件。当一个文件首次被访问时,内核会在内存中组装相应的索引节点对象,以便向内核提供对一个文件进行操作时所必需的全部信息;这些信息一部分存储在磁盘特定位置,另外一部分是在加载时动态填充的。

清单2.索引节点

structinode{//索引节点结构

……

structinode_operations*i_op;/*索引节点操作表*/

structfile_operations*i_fop;/*该索引节点对应文件的文件操作集*/structsuper_block*i_sb;/*相关的超级块*/……};

structinode_operations{//索引节点方法

……

//该函数为dentry对象所对应的文件创建一个新的索引节点,主要是由open()系统调用来调用

int(*create)(structinode*,structdentry*,int,structnameidata*);

s_instances;

/*指向超级块链表的指针*//*文件系统类型*//*超级块方法*//*该类型文件系统*/

21

//在特定目录中寻找dentry对象所对应的索引节点

structdentry*(*lookup)(structinode*,structdentry*,structnameidata*);……};

2.2.3目录项对象

引入目录项的概念主要是出于方便查找文件的目的。一个路径的各个组成部分,不管是目录还是普通的文件,都是一个目录项对象。如,在路径/home/source/test.c中,目录/,home,source和文件test.c都对应一个目录项对象。不同于前面的两个对象,目录项对象没有对应的磁盘数据结构,VFS在遍历路径名的过程中现场将它们逐个地解析成目录项对象。

清单3.目录项

structdentry{//目录项结构

……

structinode*d_inode;structdentry*d_parent;structqstrd_name;……

structlist_headd_subdirs;

/*相关的索引节点*//*父目录的目录项对象*//*目录项的名字*//*子目录*/

……

structdentry_operations*d_op;/*目录项操作表*/structsuper_block*d_sb;/*文件超级块*/……};

structdentry_operations{//判断目录项是否有效;

int(*d_revalidate)(structdentry*,structnameidata*);//为目录项生成散列值;

int(*d_hash)(structdentry*,structqstr*);……};

2.2.4文件对象

文件对象是已打开的文件在内存中的表示,主要用于建立进程和磁盘上的文件的对应关系。它由sys_open()现场创建,由sys_close()销毁。文件对象和物理文件的关系有点像进程和程序的关系一样。当我们站在用户空间来看待VFS,我们像是只需与文件对象打交道,而无须关心超级块,索引节点或目录项。因为多个进程可以同时打开和操作同一个文件,所以同一个文件也可能存在多个对应的文件对象。文件对象仅仅在进程观点上代表已经打开的文件,它反过来指向目录项对象(反过来指向索引节点)。一个文件对应的文件对象可能

22

不是惟一的,但是其对应的索引节点和目录项对象无疑是惟一的。清单4.文件对象structfile{

……

structlist_headstructdentrystructvfsmount

f_list;*f_dentry;*f_vfsmnt;

/*文件对象链表*/

/*相关目录项对象*/

/*相关的安装文件系统*//*文件操作表*/

structfile_operations*f_op;……};

structfile_operations{

……

//文件读操作

ssize_t(*read)(structfile*,char__user*,size_t,loff_t*);……

//文件写操作

ssize_t(*write)(structfile*,constchar__user*,size_t,loff_t*);……

int(*readdir)(structfile*,void*,filldir_t);……

//文件打开操作

int(*open)(structinode*,structfile*);……};

2.2.5其他VFS对象2.2.5.1和文件系统相关

根据文件系统所在的物理介质和数据在物理介质上的组织方式来区分不同的文件系统类型的。file_system_type结构用于描述具体的文件系统的类型信息。被Linux支持的文件系统,都有且仅有一个file_system_type结构而不管它有零个或多个实例被安装到系统中。而与此对应的是每当一个文件系统被实际安装,就有一个vfsmount结构体被创建,这个结构体对应一个安装点。清单5.和文件系统相关structfile_system_type{

constchar*name;

structsubsystemsubsys;intfs_flags;

/*文件系统的名字*//*sysfs子系统对象*//*文件系统类型标志*/

23

/*在文件系统被安装时,从磁盘中读取超级块,在内存中组装超级块对象*/structsuper_block*(*get_sb)(structfile_system_type*,

int,constchar*,void*);

void(*kill_sb)(structsuper_block*);structmodule*owner;structfile_system_type*next;structlist_headfs_supers;

块对象链表*/};

structvfsmount{

structlist_headmnt_hash;

structvfsmount*mnt_parent;structdentry*mnt_mountpoint;structdentry*mnt_root;structsuper_block*mnt_sb;structlist_headmnt_mounts;structlist_headmnt_child;atomic_tmnt_count;intmnt_flags;

char*mnt_devname;structlist_headmnt_list;structlist_headmnt_fslink;

structnamespace*mnt_namespace;

};

2.2.5.2和进程相关

清单6.打开的文件集

structfiles_struct{//打开的文件集

atomic_tcount;

……

intmax_fds;intmax_fdset;intnext_fd;structfile**fd;……

};

structfs_struct{//建立进程与文件系统的关系

atomic_tcount;/*结构的使用计数*/rwlock_tlock;/*保护该结构体的锁*/

/*终止访问超级块*/

/*文件系统模块*/

/*链表中的下一个文件系统类型*//*具有同一种文件系统类型的超级

/*散列表*/

/*父文件系统*/

/*安装点的目录项对象*/

/*该文件系统的根目录项对象*//*该文件系统的超级块*//*子文件系统链表*//*子文件系统链表*//*使用计数*//*安装标志*/

/*设备文件名*//*描述符链表*/

/*具体文件系统的到期列表*/

/*相关的名字空间*/

/*结构的使用计数*//*文件对象数的上限*//*文件描述符的上限*//*下一个文件描述符*//*全部文件对象数组*/

24

intumask;

structdentry*root;structdentry*pwd;/*默认的文件访问权限*//*根目录的目录项对象*/

/*当前工作目录的目录项对象*/

structdentry*altroot;/*可供选择的根目录的目录项对象*/structvfsmount*rootmnt;/*根目录的安装点对象*/structvfsmount*pwdmnt;/*pwd的安装点对象*/structvfsmount*altrootmnt;/*可供选择的根目录的安装点对象*/

};

2.2.5.3和路径查找相关

清单7.辅助查找

structnameidata{

structdentry*dentry;

structvfsmount*mnt;structqstrlast;unsignedintflags;intlast_type;unsigneddepth;

*/

char

*saved_names[MAX_NESTED_LINKS+1];/

/*和嵌套symboliclink相关的pathname*/

/*目录项对象的地址*/

/*安装点的数据*/

/*路径中的最后一个component*//*查找标识*/

/*路径中的最后一个component的类型*/

/*当前symboliclink的嵌套深度,不能大于6

union{

structopen_intentopen;/*说明文件该如何访问*/}intent;/*专用数据*/

};

2.2.6对象间的联系

如上的数据结构并不是孤立存在的。正是通过它们的有机联系,VFS才能正常工作。如下的几张图是对它们之间的联系的描述。

如图5所示,被Linux支持的文件系统,都有且仅有一个file_system_type结构而不管它有零个或多个实例被安装到系统中。每安装一个文件系统,就对应有一个超级块和安装点。

超级块通过它的一个域s_type指向其对应的具体的文件系统类型。具体的文件系统通过file_system_type中的一个域fs_supers链接具有同一种文件类型的超级块。同一种文件系统类型的超级块通过域s_instances链接。图5.超级块、安装点和具体的文件系统的关系

25

从图6可知:进程通过task_struct中的一个域files_structfiles来了解它当前所打开的文件对象;而我们通常所说的文件描述符其实是进程打开的文件对象数组的索引值。文件对象通过域f_dentry找到它对应的dentry对象,再由dentry对象的域d_inode找到它对应的索引结点,这样就建立了文件对象与实际的物理文件的关联。最后,还有一点很重要的是,文件对象所对应的文件操作函数列表是通过索引结点的域i_fop得到的。图6对第三部分源码的理解起到很大的作用。

图6.进程与超级块、文件、索引结点、目录项的关系

26

3基于VFS的文件I/O

到目前为止,文章主要都是从理论上来讲述VFS的运行机制;接下来我们将深入源代码层中,通过阐述两个具有代表性的系统调用sys_open()和sys_read()来更好地理解VFS向具体文件系统提供的接口机制。由于本文更关注的是文件操作的整个流程,所以我们在追踪源代码时,对一些细节性的处理不予关心。又由于篇幅所限,只列出相关代码。本文中的源代码来自于linux-2.6.17内核版本。

在深入sys_open()和sys_read()之前,我们先概览下调用sys_read()的上下文。图7描述了从用户空间的read()调用到数据从磁盘读出的整个流程。当在用户应用程序调用文件I/Oread()操作时,系统调用sys_read()被激发,sys_read()找到文件所在的具体文件系统,把控制权传给该文件系统,最后由具体文件系统与物理介质交互,从介质中读出数据。图7.从物理介质读数据的过程

3.1sys_open()

sys_open()系统调用打开或创建一个文件,成功返回该文件的文件描述符。图8是sys_open()实现代码中主要的函数调用关系图。图8.sys_open函数调用关系图

27

由于sys_open()的代码量大,函数调用关系复杂,以下主要是对该函数做整体的解析;而对其中的一些关键点,则列出其关键代码。

a.从sys_open()的函数调用关系图可以看到,sys_open()在做了一些简单的参数检验后,就把接力棒传给do_sys_open():

1)、首先,get_unused_fd()得到一个可用的文件描述符;通过该函数,可知文件描述符实质是进程打开文件列表中对应某个文件对象的索引值;

2)、接着,do_filp_open()打开文件,返回一个file对象,代表由该进程打开的一个文件;进程通过这样的一个数据结构对物理文件进行读写操作。

3)、最后,fd_install()建立文件描述符与file对象的联系,以后进程对文件的读写都是通过操纵该文件描述符而进行。

b.do_filp_open()用于打开文件,返回一个file对象;而打开之前需要先找到该文件:1)、open_namei()用于根据文件路径名查找文件,借助一个持有路径信息的数据结构nameidata而进行;

2)、查找结束后将填充有路径信息的nameidata返回给接下来的函数nameidata_to_filp()从而得到最终的file对象;当达到目的后,nameidata这个数据结构将会马上被释放。c.open_namei()用于查找一个文件:

1)、path_lookup_open()实现文件的查找功能;要打开的文件若不存在,还需要有一个新建的过程,则调用path_lookup_create(),后者和前者封装的是同一个实际的路径查找函

28

数,只是参数不一样,使它们在处理细节上有所偏差;

2)、当是以新建文件的方式打开文件时,即设置了O_CREAT标识时需要创建一个新的索引节点,代表创建一个文件。在vfs_create()里的一句核心语句dir->i_op->create(dir,dentry,mode,nd)可知它调用了具体的文件系统所提供的创建索引节点的方法。注意:这边的索引节点的概念,还只是位于内存之中,它和磁盘上的物理的索引节点的关系就像位于内存中和位于磁盘中的文件一样。此时新建的索引节点还不能完全标志一个物理文件的成功创建,只有当把索引节点回写到磁盘上才是一个物理文件的真正创建。想想我们以新建的方式打开一个文件,对其读写但最终没有保存而关闭,则位于内存中的索引节点会经历从新建到消失的过程,而磁盘却始终不知道有人曾经想过创建一个文件,这是因为索引节点没有回写的缘故。

3)、path_to_nameidata()填充nameidata数据结构;

4)、may_open()检查是否可以打开该文件;一些文件如链接文件和只有写权限的目录是不能被打开的,先检查nd->dentry->inode所指的文件是否是这一类文件,是的话则错误返回。还有一些文件是不能以TRUNC的方式打开的,若nd->dentry->inode所指的文件属于这一类,则显式地关闭TRUNC标志位。接着如果有以TRUNC方式打开文件的,则更新nd->dentry->inode的信息3.1.1__path_lookup_intent_open()

不管是path_lookup_open()还是path_lookup_create()最终都是调用__path_lookup_intent_open()来实现查找文件的功能。查找时,在遍历路径的过程中,会逐层地将各个路径组成部分解析成目录项对象,如果此目录项对象在目录项缓存中,则直接从缓存中获得;如果该目录项在缓存中不存在,则进行一次实际的读盘操作,从磁盘中读取该目录项所对应的索引节点。得到索引节点后,则建立索引节点与该目录项的联系。如此循环,直到最终找到目标文件对应的目录项,也就找到了索引节点,而由索引节点找到对应的超级块对象就可知道该文件所在的文件系统的类型。从磁盘中读取该目录项所对应的索引节点;这将引发VFS和实际的文件系统的一次交互。从前面的VFS理论介绍可知,读索引节点方法是由超级块来提供的。而当安装一个实际的文件系统时,在内存中创建的超级块的信息是由一个实际文件系统的相关信息来填充的,这里的相关信息就包括了实际文件系统所定义的超级块的操作函数列表,当然也就包括了读索引节点的具体执行方式。当继续追踪一个实际文件系统ext3的ext3_read_inode()时,可发现这个函数很重要的一个工作就是为不同的文件类型设置不同的索引节点操作函数表和文件操作函数表。清单8.ext3_read_inode

voidext3_read_inode(structinode*inode){

……

//是普通文件

if(S_ISREG(inode->i_mode)){

inode->i_op=&ext3_file_inode_operations;inode->i_fop=&ext3_file_operations;

29

ext3_set_aops(inode);

}elseif(S_ISDIR(inode->i_mode)){

//是目录文件

inode->i_op=&ext3_dir_inode_operations;inode->i_fop=&ext3_dir_operations;}elseif(S_ISLNK(inode->i_mode)){

//是连接文件……}else{

//如果以上三种情况都排除了,则是设备驱动//这里的设备还包括套结字、FIFO等伪设备……

}

3.1.2nameidata_to_filp子函数:__dentry_open

这是VFS与实际的文件系统联系的一个关键点。从3.1.1小节分析中可知,调用实际文件系统读取索引节点的方法读取索引节点时,实际文件系统会根据文件的不同类型赋予索引节点不同的文件操作函数集,如普通文件有普通文件对应的一套操作函数,设备文件有设备文件对应的一套操作函数。这样当把对应的索引节点的文件操作函数集赋予文件对象,以后对该文件进行操作时,比如读操作,VFS虽然对各种不同文件都是执行同一个read()操作界面,但是真正读时,内核却知道怎么区分对待不同的文件类型。清单9.__dentry_open

staticstructfile*__dentry_open(structdentry*dentry,structvfsmount*mnt,

intflags,structfile*f,

int(*open)(structinode*,structfile*))

{

structinode*inode;

……

//整个函数的工作在于填充一个file对象……

f->f_mapping=inode->i_mapping;f->f_dentry=dentry;f->f_vfsmnt=mnt;

f->f_pos=0;

//将对应的索引节点的文件操作函数集赋予文件对象的操作列表f->f_op=fops_get(inode->i_fop);……

//若文件自己定义了open操作,则执行这个特定的open操作。if(!open&&f->f_op)

open=f->f_op->open;if(open){

error=open(inode,f);

30

if(error)

gotocleanup_all;……returnf;

}

3.2sys_read()

sys_read()系统调用用于从已打开的文件读取数据。如read成功,则返回读到的字节数。如已到达文件的尾端,则返回0。图9是sys_read()实现代码中的函数调用关系图。图9.sys_read函数调用关系图

对文件进行读操作时,需要先打开它。从3.1小结可知,打开一个文件时,会在内存组装一个文件对象,希望对该文件执行的操作方法已在文件对象设置好。所以对文件进行读操作时,VFS在做了一些简单的转换后(由文件描述符得到其对应的文件对象;其核心思想是返回current->files->fd[fd]所指向的文件对象),就可以通过语句file->f_op->read(file,buf,count,pos)轻松调用实际文件系统的相应方法对文件进行读操作了。4解决问题

4.1跨文件系统的文件操作的基本原理

到此,我们也就能够解释在Linux中为什么能够跨文件系统地操作文件了。举个例子,将vfat格式的磁盘上的一个文件a.txt拷贝到ext3格式的磁盘上,命名为b.txt。这包含两个过程,对a.txt进行读操作,对b.txt进行写操作。读写操作前,需要先打开文件。由前面的分析可知,打开文件时,VFS会知道该文件对应的文件系统格式,以后操作该文件时,VFS会调用其对应的实际文件系统的操作方法。所以,VFS调用vfat的读文件方法将a.txt的数据读入内存;在将a.txt在内存中的数据映射到b.txt对应的内存空间后,VFS调用ext3的写文件方法将b.txt写入磁盘;从而实现了最终的跨文件系统的复制操作。4.2“一切皆是文件”的实现根本

不论是普通的文件,还是特殊的目录、设备等,VFS都将它们同等看待成文件,通过同一套文件操作界面来对它们进行操作。操作文件时需先打开;打开文件时,VFS会知道该文件对应的文件系统格式;当VFS把控制权传给实际的文件系统时,实际的文件系统再做出具体区分,对不同的文件类型执行不同的操作。这也就是“一切皆是文件”的根本所在。

31

5总结

VFS即虚拟文件系统是Linux文件系统中的一个抽象软件层;因为它的支持,众多不同的实际文件系统才能在Linux存,跨文件系统操作才能实现。VFS借助它四个主要的数据结构即超级块、索引节点、目录项和文件对象以及一些辅助的数据结构,向Linux中不管是普通的文件还是目录、设备、套接字等都提供同样的操作界面,如打开、读写、关闭等。只有当把控制权传给实际的文件系统时,实际的文件系统才会做出区分,对不同的文件类型执行不同的操作。由此可见,正是有了VFS的存在,跨文件系统操作才能执行,Unix/Linux中的“一切皆是文件”的口号才能够得以实现。

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- 91gzw.com 版权所有 湘ICP备2023023988号-2

违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务