您的位置:首页 > 运维架构

Ldd3 学习笔记3 —file_operations

2012-08-30 19:52 477 查看
原文:http://tldp.org/LDP/lkmpg/2.4/html/c577.htm

Thefile_operationsstructureisdefinedinlinux/fs.h,andholdspointerstofunctionsdefinedbythedriverthatperform
variousoperationsonthedevice.Eachfieldofthestructurecorrespondstotheaddressofsomefunctiondefinedbythedrivertohandlearequestedoperation.

structfile_operations{

Forexample,everycharacterdriverneedstodefineafunctionthatreadsfromthedevice.Thefile_operationsstructureholdstheaddressofthemodule'sfunctionthatperformsthatoperation.Hereiswhatthedefinition
lookslikeforkernel2.4.2:

structfile_operations{structmodule*owner;loff_t(*llseek)(structfile*,loff_t,int);ssize_t(*read)(structfile*,char*,size_t,loff_t*);ssize_t(*write)(structfile*,constchar*,size_t,loff_t*);int(*readdir)(structfile*,void*,filldir_t);unsignedint(*poll)(structfile*,structpoll_table_struct*);int(*ioctl)(structinode*,structfile*,unsignedint,unsignedlong);int(*mmap)(structfile*,structvm_area_struct*);int(*open)(structinode*,structfile*);int(*flush)(structfile*);int(*release)(structinode*,structfile*);int(*fsync)(structfile*,structdentry*,intdatasync);int(*fasync)(int,structfile*,int);int(*lock)(structfile*,int,structfile_lock*);ssize_t(*readv)(structfile*,conststructiovec*,unsignedlong,loff_t*);ssize_t(*writev)(structfile*,conststructiovec*,unsignedlong,loff_t*);};


Someoperationsarenotimplementedbyadriver.Forexample,adriverthathandlesavideocardwon'tneedtoreadfromadirectorystructure.Thecorrespondingentriesinthefile_operationsstructureshouldbesettoNULL.

*owner指向拥有该结构的模块的指针,避免正在操作时被卸载,一般为初始化为THIS_MODULES
*llseek函数指针用来修改文件当前的读写位置,返回新位置
*read函数指针用来从设备中同步读取数据。读取成功返回读取的字节数。设置为NULL,调用时返回-EINVAL
*aio_read函数指针初始化一个异步的读取操作,为NULL时全部通过read处理
*write函数指针用来向设备发送数据。
*aio_write函数指针用来初始化一个异步的写入操作。
*readdir函数指针仅用于读取目录,对于设备文件,该字段为NULL
*poll函数指针返回一个位掩码,用来指出非阻塞的读取或写入是否可能。把pool设置为NULL,设备会被认为即可读也可写。
*ioctl函数指针提供一种执行设备特殊命令的方法。不设置入口点,返回-ENOTTY
*unlocked_ioctl函数指针只是在不使用BLK的文件系统,才此种函数指针代替ioctl
*unlocked_ioctl函数只是在64位系统上,32位的ioctl调用,将使用此函数指针代替
*mmap指针函数用于请求将设备内存映射到进程地址空间。如果无此方法,将访问-ENODEV。
*open指针函数用于打开设备如果为空,设备的打开操作永远成功,但系统不会通知驱动程序
*flush指针函数用于在进程关闭设备文件描述符副本时,执行并等待,若设置为NULL,内核将忽略用户应用程序的请求。
*release函数指针在file结构释放时,将被调用
*fsync函数指针用于刷新待处理的数据,如果驱动程序没有实现,fsync调用将返回-EINVAL
*aio_fsync函数对应异步fsync
*fasync函数指针用于通知设备FASYNC标志发生变化,如果设备不支持异步通知,该字段可以为NULL
*lock用于实现文件锁,设备驱动常不去实现此lock
ssize_t(*readv)(structfile*,conststructiovec*,unsignedlong,loff_t*);
ssize_t(*writev)(structfile*,conststructiovec*,unsignedlong,loff_t*);
//readv和writev分散/聚集型的读写操作,实现进行涉及多个内存区域的单次读或写操作。
ssize_t(*sendfile)(structfile*,loff_t*,size_t,read_actor_t,void*);
//实现sendfile调用的读取部分,将数据从一个文件描述符移到另一个,设备驱动通常将其设置为NULL
ssize_t(*sendpage)(structfile*,structpage*,int,size_t,loff_t*,int);
//实现sendfile调用的另一部分,内核调用将其数据发送到对应文件,每次一个数据页,设备驱动通常将其设置为NULL
unsignedlong(*get_unmapped_area)(structfile*,unsignedlong,unsignedlong,unsignedlong,
unsignedlong);
//在进程地址空间找到一个合适的位置,以便将底层设备中的内存段映射到该位置。大部分驱动可将其设置为NULL
int(*check_flags)(int);
//允许模块检查传递给fcntl(F_SETEL…)调用的标志
int(*dir_notify)(structfile*filp,unsignedlongarg);
//应用程序使用fcntl来请求目录改变通知时,调用该方法。仅对文件系统有效,驱动程序不必实现。
int(*flock)(structfile*,int,structfile_lock*);
//实现文件锁

Thereisagccextensionthatmakesassigningtothisstructuremoreconvenient.You'llseeitinmoderndrivers,andmaycatchyoubysurprise.Thisiswhatthenewwayofassigningtothestructurelookslike:

structfile_operationsfops={read:device_read,write:device_write,open:device_open,release:device_release};


However,there'salsoaC99wayofassigningtoelementsofastructure,andthisisdefinitelypreferredoverusingtheGNUextension.TheversionofgccI'mcurrentlyusing,2.95,supportsthenewC99syntax.Youshoulduse
thissyntaxincasesomeonewantstoportyourdriver.Itwillhelpwithcompatibility:

structfile_operationsfops={.read=device_read,.write=device_write,.open=device_open,.release=device_release};



Themeaningisclear,andyoushouldbeawarethatanymemberofthestructurewhichyoudon'texplicitlyassignwillbeinitializedtoNULLbygcc.

Apointertoastructfile_operationsiscommonlynamedfops.

Thefilestructure

Eachdeviceisrepresentedinthekernelbyafilestructure,whichisdefinedinlinux/fs.h.Beawarethatafileis
akernellevelstructureandneverappearsinauserspaceprogram.It'snotthesamethingasaFILE,whichisdefinedbyglibcandwouldneverappearinakernelspacefunction.Also,itsnameisabitmisleading;it
representsanabstractopen`file',notafileonadisk,whichisrepresentedbyastructurenamedinode.
Apointertoastructfileiscommonlynamedfilp.You'llalsoseeitreferedtoasstructfilefile.
Resistthetemptation.
Goaheadandlookatthedefinitionoffile.Mostoftheentriesyousee,likestructdentryaren'tusedbydevicedrivers,and
youcanignorethem.Thisisbecausedriversdon'tfillfiledirectly;theyonlyusestructurescontainedinfilewhicharecreatedelsewhere.

RegisteringADevice

Asdiscussedearlier,chardevicesareaccessedthroughdevicefiles,usuallylocatedin/dev[1].
Themajornumbertellsyouwhichdriverhandleswhichdevicefile.Theminornumberisusedonlybythedriveritselftodifferentiatewhichdeviceit'soperatingon,justincasethedriverhandlesmorethanonedevice.
Addingadrivertoyoursystemmeansregisteringitwiththekernel.Thisissynonymouswithassigningitamajornumberduringthemodule'sinitialization.Youdothisbyusingtheregister_chrdevfunction,
definedbylinux/fs.h.
intregister_chrdev(unsignedintmajor,constchar*name,structfile_operations*fops);

whereunsignedintmajoristhemajornumberyouwanttorequest,constchar*nameisthenameofthedeviceasit'llappearin/proc/devicesandstruct
file_operations*fopsisapointertothefile_operationstableforyourdriver.Anegativereturnvaluemeanstheregistertrationfailed.Notethatwedidn'tpasstheminornumbertoregister_chrdev.
That'sbecausethekerneldoesn'tcareabouttheminornumber;onlyourdriverusesit.
Nowthequestionis,howdoyougetamajornumberwithouthijackingonethat'salreadyinuse?TheeasiestwaywouldbetolookthroughDocumentation/devices.txtand
pickanunusedone.That'sabadwayofdoingthingsbecauseyou'llneverbesureifthenumberyoupickedwillbeassignedlater.Theansweristhatyoucanaskthekerneltoassignyouadynamicmajornumber.
Ifyoupassamajornumberof0toregister_chrdev,thereturnvaluewillbethedynamicallyallocatedmajornumber.Thedownsideisthatyoucan'tmakeadevice
fileinadvance,sinceyoudon'tknowwhatthemajornumberwillbe.Thereareacoupleofwaystodothis.First,thedriveritselfcanprintthenewlyassignednumberandwecanmakethedevicefilebyhand.Second,thenewlyregistereddevicewillhave
anentryin/proc/devices,andwecaneithermakethedevicefilebyhandorwriteashellscripttoreadthefileinandmakethedevicefile.Thethirdmethodiswecanhaveourdrivermakethethedevicefileusingthemknodsystem
callafterasuccessfulregistrationandrmduringthecalltocleanup_module.

UnregisteringADevice

Wecan'tallowthekernelmoduletobermmod'edwheneverrootfeelslikeit.Ifthedevicefileisopenedbyaprocessandthenweremovethekernelmodule,
usingthefilewouldcauseacalltothememorylocationwheretheappropriatefunction(read/write)usedtobe.Ifwe'relucky,noothercodewasloadedthere,andwe'llgetanuglyerrormessage.Ifwe'reunlucky,anotherkernelmodulewasloadedintothe
samelocation,whichmeansajumpintothemiddleofanotherfunctionwithinthekernel.Theresultsofthiswouldbeimpossibletopredict,buttheycan'tbeverypositive.
Normally,whenyoudon'twanttoallowsomething,youreturnanerrorcode(anegativenumber)fromthefunctionwhichissupposedtodoit.Withcleanup_modulethat's
impossiblebecauseit'savoidfunction.However,there'sacounterwhichkeepstrackofhowmanyprocessesareusingyourmodule.Youcanseewhatit'svalueisbylookingatthe3rdfieldof/proc/modules.Ifthisnumber
isn'tzero,rmmodwillfail.Notethatyoudon'thavetocheckthecounterfromwithincleanup_modulebecausethecheckwillbeperformedforyoubythesystemcallsys_delete_module,
definedinlinux/module.c.Youshouldn'tusethiscounterdirectly,buttherearemacrosdefinedinlinux/modules.hwhichletyouincrease,decreaseanddisplaythiscounter:

MOD_INC_USE_COUNT:Incrementtheusecount.

MOD_DEC_USE_COUNT:Decrementtheusecount.

MOD_IN_USE:Displaytheusecount.

It'simportanttokeepthecounteraccurate;ifyoueverdolosetrackofthecorrectusagecount,you'llneverbeabletounloadthemodule;it'snowreboottime,boysandgirls.Thisisboundto
happentoyousoonerorlaterduringamodule'sdevelopment.

chardev.c

Thenextcodesamplecreatesachardrivernamedchardev.Youcancatitsdevicefile(oropenthefilewithaprogram)andthedriverwillputthenumber
oftimesthedevicefilehasbeenreadfromintothefile.Wedon'tsupportwritingtothefile(likeecho"hi">/dev/hello),butcatchtheseattemptsandtelltheuserthattheoperationisn'tsupported.Don'tworry
ifyoudon'tseewhatwedowiththedatawereadintothebuffer;wedon'tdomuchwithit.Wesimplyreadinthedataandprintamessageacknowledgingthatwereceivedit.

Example4-1.chardev.c

/*chardev.c:Createsaread-onlychardevicethatsayshowmanytimes*you'vereadfromthedevfile**Copyright(C)2001byPeterJaySalzman**08/02/2006-UpdatedbyRodrigoRubiraBranco<rodrigo@kernelhacking.com>*/
/*KernelProgramming*/#defineMODULE#defineLINUX#define__KERNEL__
#ifdefined(CONFIG_MODVERSIONS)&&!defined(MODVERSIONS)#include<linux/modversions.h>#defineMODVERSIONS#endif#include<linux/kernel.h>#include<linux/module.h>#include<linux/fs.h>#include<asm/uaccess.h>/*forput_user*/#include<asm/errno.h>
/*Prototypes-thiswouldnormallygoina.hfile*/intinit_module(void);voidcleanup_module(void);staticintdevice_open(structinode*,structfile*);staticintdevice_release(structinode*,structfile*);staticssize_tdevice_read(structfile*,char*,size_t,loff_t*);staticssize_tdevice_write(structfile*,constchar*,size_t,loff_t*);
#defineSUCCESS0#defineDEVICE_NAME"chardev"/*Devnameasitappearsin/proc/devices*/#defineBUF_LEN80/*Maxlengthofthemessagefromthedevice*/

/*Globalvariablesaredeclaredasstatic,soareglobalwithinthefile.*/
staticintMajor;/*Majornumberassignedtoourdevicedriver*/staticintDevice_Open=0;/*Isdeviceopen?Usedtopreventmultipleaccesstothedevice*/staticcharmsg[BUF_LEN];/*Themsgthedevicewillgivewhenasked*/staticchar*msg_Ptr;
staticstructfile_operationsfops={.read=device_read,.write=device_write,.open=device_open,.release=device_release};

/*Functions*/
intinit_module(void){Major=register_chrdev(0,DEVICE_NAME,&fops);
if(Major<0){printk("Registeringthecharacterdevicefailedwith%d\n",Major);returnMajor;}
printk("<1>Iwasassignedmajornumber%d.Totalkto\n",Major);printk("<1>thedriver,createadevfilewith\n");printk("'mknod/dev/helloc%d0'.\n",Major);printk("<1>Tryvariousminornumbers.Trytocatandechoto\n");printk("thedevicefile.\n");printk("<1>Removethedevicefileandmodulewhendone.\n");
return0;}

voidcleanup_module(void){/*Unregisterthedevice*/intret=unregister_chrdev(Major,DEVICE_NAME);if(ret<0)printk("Errorinunregister_chrdev:%d\n",ret);}

/*Methods*/
/*Calledwhenaprocesstriestoopenthedevicefile,like*"cat/dev/mycharfile"*/staticintdevice_open(structinode*inode,structfile*file){staticintcounter=0;if(Device_Open)return-EBUSY;
Device_Open++;sprintf(msg,"Ialreadytoldyou%dtimesHelloworld!\n",counter++);msg_Ptr=msg;MOD_INC_USE_COUNT;
returnSUCCESS;}

/*Calledwhenaprocessclosesthedevicefile*/staticintdevice_release(structinode*inode,structfile*file){Device_Open--;/*We'renowreadyforournextcaller*/
/*Decrementtheusagecount,orelseonceyouopenedthefile,you'llnevergetgetridofthemodule.*/MOD_DEC_USE_COUNT;
return0;}

/*Calledwhenaprocess,whichalreadyopenedthedevfile,attemptstoreadfromit.*/staticssize_tdevice_read(structfile*filp,char*buffer,/*Thebuffertofillwithdata*/size_tlength,/*Thelengthofthebuffer*/loff_t*offset)/*Ouroffsetinthefile*/{/*Numberofbytesactuallywrittentothebuffer*/intbytes_read=0;
/*Ifwe'reattheendofthemessage,return0signifyingendoffile*/if(*msg_Ptr==0)return0;
/*Actuallyputthedataintothebuffer*/while(length&&*msg_Ptr){
/*Thebufferisintheuserdatasegment,notthekernelsegment;*assignmentwon'twork.Wehavetouseput_userwhichcopiesdatafrom*thekerneldatasegmenttotheuserdatasegment.*/put_user(*(msg_Ptr++),buffer++);
length--;bytes_read++;}
/*Mostreadfunctionsreturnthenumberofbytesputintothebuffer*/returnbytes_read;}

/*Calledwhenaprocesswritestodevfile:echo"hi">/dev/hello*/staticssize_tdevice_write(structfile*filp,constchar*buff,size_tlen,loff_t*off){printk("<1>Sorry,thisoperationisn'tsupported.\n");return-EINVAL;}
MODULE_LICENSE("GPL");


WritingModulesforMultipleKernelVersions

Thesystemcalls,whicharethemajorinterfacethekernelshowstotheprocesses,generallystaythesameacrossversions.Anewsystemcallmaybeadded,butusuallytheoldoneswillbehaveexactlyliketheyusedto.Thisisnecessaryforbackwardcompatibility
--anewkernelversionisnotsupposedtobreakregularprocesses.Inmostcases,thedevicefileswillalsoremainthesame.Ontheotherhand,theinternalinterfaceswithinthekernelcananddochangebetweenversions.

TheLinuxkernelversionsaredividedbetweenthestableversions(n.$<$evennumber$>$.m)andthedevelopmentversions(n.$<$oddnumber$>$.m).Thedevelopmentversionsincludeallthecoolnewideas,includingthosewhichwillbeconsideredamistake,orreimplemented,
inthenextversion.Asaresult,youcan'ttrusttheinterfacetoremainthesameinthoseversions(whichiswhyIdon'tbothertosupporttheminthisbook,it'stoomuchworkanditwouldbecomedatedtooquickly).Inthestableversions,ontheother
hand,wecanexpecttheinterfacetoremainthesameregardlessofthebugfixversion(themnumber).

Therearedifferencesbetweendifferentkernelversions,andifyouwanttosupportmultiplekernelversions,you'llfindyourselfhavingtocodeconditionalcompilationdirectives.ThewaytodothistocomparethemacroLINUX_VERSION_CODEto
themacroKERNEL_VERSION.Inversiona.b.cofthekernel,thevalueofthismacrowouldbe$2^{16}a+2^{8}b+c$.Beawarethatthismacroisnotdefinedforkernel2.0.35andearlier,soif
youwanttowritemodulesthatsupportreallyoldkernels,you'llhavetodefineityourself,like:

Example4-2.sometitle

#ifLINUX_KERNEL_VERSION>=KERNEL_VERSION(2,2,0)#defineKERNEL_VERSION(a,b,c)((a)*65536+(b)*256+(c))#endif


Ofcoursesincethesearemacros,youcanalsouse#ifndefKERNEL_VERSIONtotesttheexistenceofthemacro,ratherthantestingtheversionofthekernel.


Notes

[1]Thisisbyconvention.Whenwritingadriver,it'sOKtoputthedevicefileinyourcurrentdirectory.Justmakesureyouplaceitin/devforaproductiondriver
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: