initcall机制的由来 我们都知道,linux对驱动程序提供静态编译进内核和动态加载两种方式,当我们试图将一个驱动程序编译进内核时,开发者通常提供一个xxxinit()函数接口以启动这个驱动程序同时提供某些服务。 那么,根据常识来说,这个xxxinit()函数肯定是要在系统启动的某个时候被调用,才能启动这个驱动程序。 最简单直观地做法就是:开发者试图添加一个驱动程序时,在内核启动init程序的某个地方直接添加调用自己驱动程序的xxxinit()函数,在内核启动时自然会调用到这个程序。 但是,回头一想,这种做法在单人开发的小系统中或许可以,但是在linux中,如果驱动程序是这么个添加法,那就是一场灾难,这个道理我想不用我多说。 不难想到另一种方式,就是集中提供一个地方,如果你要添加你的驱动程序,你就将你的初始化函数在这个地方进行添加,在内核启动的时候统一扫描这个地方,再执行这一部分的所有被添加的驱动程序。 那到底怎么添加呢?直接在C文件中作一个列表,在里面添加初始化函数?我想随着驱动程序数量的增加,这个列表会让人头昏眼花。 当然,对于linus大神而言,这些都不是事,linux的做法是: 底层实现上,在内核镜像文件中,自定义一个段,这个段里面专门用来存放这些初始化函数的地址,内核启动时,只需要在这个段地址处取出函数指针,一个个执行即可。 对上层而言,linux内核提供xxxinit(initfunc)宏定义接口,驱动开发者只需要将驱动程序的initfunc使用xxxinit()来修饰,这个函数就被自动添加到了上述的段中,开发者完全不需要关心实现细节。 对于各种各样的驱动而言,可能存在一定的依赖关系,需要遵循先后顺序来进行初始化,考虑到这个,linux也对这一部分做了分级处理。 initcall的源码 在平台对应的init。h文件中,可以找到xxxinitcall的定义:Onlyforbuiltincode,notmodules。defineearlyinitcall(fn)defineinitcall(fn,early)definepureinitcall(fn)defineinitcall(fn,0)definecoreinitcall(fn)defineinitcall(fn,1)definecoreinitcallsync(fn)defineinitcall(fn,1s)definepostcoreinitcall(fn)defineinitcall(fn,2)definepostcoreinitcallsync(fn)defineinitcall(fn,2s)definearchinitcall(fn)defineinitcall(fn,3)definearchinitcallsync(fn)defineinitcall(fn,3s)definesubsysinitcall(fn)defineinitcall(fn,4)definesubsysinitcallsync(fn)defineinitcall(fn,4s)definefsinitcall(fn)defineinitcall(fn,5)definefsinitcallsync(fn)defineinitcall(fn,5s)definerootfsinitcall(fn)defineinitcall(fn,rootfs)definedeviceinitcall(fn)defineinitcall(fn,6)definedeviceinitcallsync(fn)defineinitcall(fn,6s)definelateinitcall(fn)defineinitcall(fn,7)definelateinitcallsync(fn)defineinitcall(fn,7s) xxxinitcall(fn)的原型其实是defineinitcall(fn,n),n是一个数字或者是数字s,这个数字代表这个fn执行的优先级,数字越小,优先级越高,带s的fn优先级低于不带s的fn优先级。 继续跟踪代码,看看defineinitcall(fn,n):definedefineinitcall(fn,id)staticinitcalltinitcallfnidusedattribute((section(。initcallid。init))) 值得注意的是,attribute()是gnuC中的扩展语法,它可以用来实现很多灵活的定义行为,这里不细究。 attribute((section(。initcallid。init)))表示编译时将目标符号放置在括号指定的段中。 而在宏定义中的作用是将目标字符串化,在宏定义中的作用是符号连接,将多个符号连接成一个符号,并不将其字符串化。 used是一个宏定义, defineusedattribute((used)) 使用前提是在编译器编译过程中,如果定义的符号没有被引用,编译器就会对其进行优化,不保留这个符号,而attribute((used))的作用是告诉编译器这个静态符号在编译的时候即使没有使用到也要保留这个符号。 为了更方便地理解,我们拿举个例子来说明,开发者声明了这样一个函数:pureinitcall(testinit); 所以pureinitcall(testinit)的解读就是: 首先宏展开成:defineinitcall(testinit,0) 然后接着展开:staticinitcalltinitcalltestinit0这就是一个简单的变量定义。 同时声明initcalltestinit0这个变量即使没被引用也保留符号,且将其放置在内核镜像的。initcall0。init段处。 需要注意的是,根据官方注释可以看到earlyinitcall(fn)只针对内置的核心代码,不能描述模块。 xxxinitcall修饰函数的调用 既然我们知道了xxxinitcall是怎么定义而且目标函数的放置位置,那么使用xxxinitcall()修饰的函数是怎么被调用的呢? 我们就从内核C函数起始部分也就是startkernel开始往下挖,这里的调用顺序为:startkernelrestinit();kernelthread(kernelinit,NULL,CLONEFS);kernelinit()kernelinitfreeable();dobasicsetup();doinitcalls(); 这个doinitcalls()就是我们需要寻找的函数了,在这个函数中执行所有使用xxxinitcall()声明的函数,接下来我们再来看看它是怎么执行的:staticinitcalltinitcalllevels〔〕initdata{initcall0start,initcall1start,initcall2start,initcall3start,initcall4start,initcall5start,initcall6start,initcall7start,initcallend,};intinitormoduledooneinitcall(initcalltfn){。。。if(initcalldebug)retdooneinitcalldebug(fn);elseretfn();。。。}staticvoidinitdoinitcalllevel(intlevel){。。。for(fninitcalllevels〔level〕;fninitcalllevels〔level1〕;fn)dooneinitcall(fn);}staticvoidinitdoinitcalls(void){for(level0;levelARRAYSIZE(initcalllevels)1;level)doinitcalllevel(level);} 在上述代码中,定义了一个静态的initcalllevels数组,这是一个指针数组,数组的每个元素都是一个指针。 doinitcalls()循环调用doinitcalllevel(level),level就是initcall的优先级数字,由for循环的终止条件ARRAYSIZE(initcalllevels)1可知,总共会调用doinitcalllevel(0)doinitcalllevel(7),一共七次。 而doinitcalllevel(level)中则会遍历initcalllevels〔level〕中的每个函数指针,initcalllevels〔level〕实际上是对应的initcalllevelstart指针变量,然后依次取出initcalllevelstart指向地址存储的每个函数指针,并调用dooneinitcall(fn),实际上就是执行当前函数。 可以猜到的是,这个initcalllevelstart所存储的函数指针就是开发者用xxxinitcall()宏添加的函数,对应。initcalllevel。init段。 dooneinitcall(fn)的执行:判断initcalldebug的值,如果为真,则调用dooneinitcalldebug(fn);如果为假,则直接调用fn。事实上,调用dooneinitcalldebug(fn)只是在调用fn的基础上添加一些额外的打印信息,可以直接看成是调用fn。 那么,在initcall源码部分有提到,在开发者添加xxxinitcall(fn)时,事实上是将fn放置到了。initcalllevel。init的段中,但是在doinitcall()的源码部分,却是从initcalllevelslevel取出,initcalllevels〔level〕是怎么关联到。initcalllevel。init段的呢? 答案在vmlinux。lds。h中:defineINITCALLSLEVEL(level)VMLINUXSYMBOL(initcalllevelstart)。;KEEP((。initcalllevel。init))KEEP((。initcalllevels。init))defineINITCALLSVMLINUXSYMBOL(initcallstart)。;KEEP((。initcallearly。init))INITCALLSLEVEL(0)INITCALLSLEVEL(1)INITCALLSLEVEL(2)INITCALLSLEVEL(3)INITCALLSLEVEL(4)INITCALLSLEVEL(5)INITCALLSLEVEL(rootfs)INITCALLSLEVEL(6)INITCALLSLEVEL(7)VMLINUXSYMBOL(initcallend)。; 在这里首先定义了initcallstart,将其关联到。initcallearly。init段。 然后对每个level定义了INITCALLSLEVEL(level),将INITCALLSLEVEL(level)展开之后的结果是定义initcalllevelstart,并将 initcalllevelstart关联到。initcalllevel。init段和。initcalllevels。init段。 到这里,initcalllevelstart和。initcalllevel。init段的对应就比较清晰了,所以,从initcalllevels〔level〕部分一个个取出函数指针并执行函数就是执行xxxinitcall()定义的函数。 总结 便于理解,我们需要一个示例来梳理整个流程,假设我是一个驱动开发者,开发一个名为beagle的驱动,在系统启动时需要调用beagleinit()函数来启动启动服务。 我需要先将其添加到系统中: coreinitcall(beagleinit) coreinitcall(beagleinit)宏展开为defineinitcall(beagleinit,1),所以beagleinit()这个函数被放置在。initcall1。init段处。 在内核启动时,系统会调用到doinitcall()函数。根据指针数组initcalllevels〔1〕找到initcall1start指针,在vmlinux。lds。h可以查到:initcall1start对应。initcall1。init段的起始地址,依次取出段中的每个函数指针,并执行函数。 添加的服务就实现了启动。 可能有些C语言基础不太好的朋友不太理解doinitcalllevel()函数中依次取出地址并执行的函数执行逻辑: for(fninitcalllevels〔level〕;fninitcalllevels〔level1〕;fn)dooneinitcall(fn); fn为函数指针,fn相当于函数指针1,相当于:内存地址sizeof(fn),sizeof(fn)根据平台不同而不同,一般来说,32位机上是4字节,64位机则是8字节(关于指针在操作系统中的大小可以参考另一篇博客:不同平台下指针大小)。 而initcalllevels〔level〕指向当前。initcalllevels。init段,initcalllevels〔level1〕指向。initcall(level1)s。init段,两个段之间的内存就是存放所有添加的函数指针。 也就是从。initcalllevels。init段开始,每次取一个函数出来执行,并累加指针,直到取完。 写个测试用例吧,应用层: cinit。cDescription:Version:2。0Autor:lshDate:2022022519:48:58LastEditors:lshLastEditTime:2022022520:00:04includeunistd。hincludestdint。hincludestdio。htypedefvoid(initcall)(void);Thesetwovariablesaredefinedinlinkscript。defineinitattribute((unused,section(。myinit)))defineDECLAREINIT(func)initcallfnfuncinitfuncstaticvoidCinit(void){printf(Cinit);}DECLAREINIT(Cinit); ldscript。ldsScriptforzcombreloczseparatecode:combineandsortrelocsectionswithseparatecodesegmentCopyright(C)20142018FreeSoftwareFoundation,Inc。Copyinganddistributionofthisscript,withorwithoutmodification,arepermittedinanymediumwithoutroyaltyprovidedthecopyrightnoticeandthisnoticearepreserved。OUTPUTFORMAT(elf64x8664,elf64x8664,elf64x8664)OUTPUTARCH(i386:x8664)ENTRY(start)SEARCHDIR(usrlocallibx8664linuxgnu);SEARCHDIR(libx8664linuxgnu);SEARCHDIR(usrlibx8664linuxgnu);SEARCHDIR(usrlibx8664linuxgnu64);SEARCHDIR(usrlocallib64);SEARCHDIR(lib64);SEARCHDIR(usrlib64);SEARCHDIR(usrlocallib);SEARCHDIR(lib);SEARCHDIR(usrlib);SEARCHDIR(usrx8664linuxgnulib64);SEARCHDIR(usrx8664linuxgnulib);SECTIONS{Readonlysections,mergedintotextsegment:PROVIDE(executablestartSEGMENTSTART(textsegment,0x400000));。SEGMENTSTART(textsegment,0x400000)SIZEOFHEADERS;。interp:{(。interp)}。note。gnu。buildid:{(。note。gnu。buildid)}。hash:{(。hash)}。gnu。hash:{(。gnu。hash)}。dynsym:{(。dynsym)}。dynstr:{(。dynstr)}。gnu。version:{(。gnu。version)}。gnu。versiond:{(。gnu。versiond)}。gnu。versionr:{(。gnu。versionr)}。rela。dyn:{(。rela。init)(。rela。text。rela。text。。rela。gnu。linkonce。t。)(。rela。fini)(。rela。rodata。rela。rodata。。rela。gnu。linkonce。r。)(。rela。data。rela。data。。rela。gnu。linkonce。d。)(。rela。tdata。rela。tdata。。rela。gnu。linkonce。td。)(。rela。tbss。rela。tbss。。rela。gnu。linkonce。tb。)(。rela。ctors)(。rela。dtors)(。rela。got)(。rela。bss。rela。bss。。rela。gnu。linkonce。b。)(。rela。ldata。rela。ldata。。rela。gnu。linkonce。l。)(。rela。lbss。rela。lbss。。rela。gnu。linkonce。lb。)(。rela。lrodata。rela。lrodata。。rela。gnu。linkonce。lr。)(。rela。ifunc)}。rela。plt:{(。rela。plt)PROVIDEHIDDEN(relaipltstart。);(。rela。iplt)PROVIDEHIDDEN(relaipltend。);}。ALIGN(CONSTANT(MAXPAGESIZE));。init:{KEEP((SORTNONE(。init)))}。plt:{(。plt)(。iplt)}。plt。got:{(。plt。got)}。plt。sec:{(。plt。sec)}。text:{(。text。unlikely。text。unlikely。text。unlikely。)(。text。exit。text。exit。)(。text。startup。text。startup。)(。text。hot。text。hot。)(。text。stub。text。。gnu。linkonce。t。)。gnu。warningsectionsarehandledspeciallybyelf32。em。(。gnu。warning)}。fini:{KEEP((SORTNONE(。fini)))}PROVIDE(etext。);PROVIDE(etext。);PROVIDE(etext。);。ALIGN(CONSTANT(MAXPAGESIZE));Adjusttheaddressfortherodatasegment。Wewanttoadjustuptothesameaddresswithinthepageonthenextpageup。。SEGMENTSTART(rodatasegment,ALIGN(CONSTANT(MAXPAGESIZE))(。(CONSTANT(MAXPAGESIZE)1)));。rodata:{(。rodata。rodata。。gnu。linkonce。r。)}。rodata1:{(。rodata1)}。ehframehdr:{(。ehframehdr)(。ehframeentry。ehframeentry。)}。ehframe:ONLYIFRO{KEEP((。ehframe))(。ehframe。)}。gccexcepttable:ONLYIFRO{(。gccexcepttable。gccexcepttable。)}。gnuextab:ONLYIFRO{(。gnuextab)}ThesesectionsaregeneratedbytheSunOracleCcompiler。。exceptionranges:ONLYIFRO{(。exceptionranges。exceptionranges)}Adjusttheaddressforthedatasegment。Wewanttoadjustuptothesameaddresswithinthepageonthenextpageup。。DATASEGMENTALIGN(CONSTANT(MAXPAGESIZE),CONSTANT(COMMONPAGESIZE));Exceptionhandling。ehframe:ONLYIFRW{KEEP((。ehframe))(。ehframe。)}。gnuextab:ONLYIFRW{(。gnuextab)}。gccexcepttable:ONLYIFRW{(。gccexcepttable。gccexcepttable。)}。exceptionranges:ONLYIFRW{(。exceptionranges。exceptionranges)}ThreadLocalStoragesections。tdata:{PROVIDEHIDDEN(tdatastart。);(。tdata。tdata。。gnu。linkonce。td。)}。tbss:{(。tbss。tbss。。gnu。linkonce。tb。)(。tcommon)}。preinitarray:{PROVIDEHIDDEN(preinitarraystart。);KEEP((。preinitarray))PROVIDEHIDDEN(preinitarrayend。);}。initarray:{PROVIDEHIDDEN(initarraystart。);KEEP((SORTBYINITPRIORITY(。initarray。)SORTBYINITPRIORITY(。ctors。)))KEEP((。initarrayEXCLUDEFILE(crtbegin。ocrtbegin?。ocrtend。ocrtend?。o)。ctors))PROVIDEHIDDEN(initarrayend。);}。finiarray:{PROVIDEHIDDEN(finiarraystart。);KEEP((SORTBYINITPRIORITY(。finiarray。)SORTBYINITPRIORITY(。dtors。)))KEEP((。finiarrayEXCLUDEFILE(crtbegin。ocrtbegin?。ocrtend。ocrtend?。o)。dtors))PROVIDEHIDDEN(finiarrayend。);}。ctors:{gccusescrtbegin。otofindthestartoftheconstructors,sowemakesureitisfirst。Becausethisisawildcard,itdoesntmatteriftheuserdoesnotactuallylinkagainstcrtbegin。o;thelinkerwontlookforafiletomatchawildcard。Thewildcardalsomeansthatitdoesntmatterwhichdirectorycrtbegin。oisin。KEEP(crtbegin。o(。ctors))KEEP(crtbegin?。o(。ctors))Wedontwanttoincludethe。ctorsectionfromthecrtend。ofileuntilafterthesortedctors。The。ctorsectionfromthecrtendfilecontainstheendofctorsmarkeranditmustbelastKEEP((EXCLUDEFILE(crtend。ocrtend?。o)。ctors))KEEP((SORT(。ctors。)))KEEP((。ctors))}。dtors:{KEEP(crtbegin。o(。dtors))KEEP(crtbegin?。o(。dtors))KEEP((EXCLUDEFILE(crtend。ocrtend?。o)。dtors))KEEP((SORT(。dtors。)))KEEP((。dtors))}。jcr:{KEEP((。jcr))}。data。rel。ro:{(。data。rel。ro。local。gnu。linkonce。d。rel。ro。local。)(。data。rel。ro。data。rel。ro。。gnu。linkonce。d。rel。ro。)}。dynamic:{(。dynamic)}。got:{(。got)(。igot)}。DATASEGMENTRELROEND(SIZEOF(。got。plt)24?24:0,。);。got。plt:{(。got。plt)(。igot。plt)}。data:{(。data。data。。gnu。linkonce。d。)SORT(CONSTRUCTORS)}。data1:{(。data1)}edata。;PROVIDE(edata。);。。;initstart。;。myinit:{(。myinit)}initend。;bssstart。;。bss:{(。dynbss)(。bss。bss。。gnu。linkonce。b。)(COMMON)Alignheretoensurethatthe。bsssectionoccupiesspaceuptoend。Alignafter。bsstoensurecorrectalignmentevenifthe。bsssectiondisappearsbecausetherearenoinputsections。FIXME:Whydoweneedit?Whenthereisno。bsssection,wedontpadthe。datasection。。ALIGN(。!0?648:1);}。lbss:{(。dynlbss)(。lbss。lbss。。gnu。linkonce。lb。)(LARGECOMMON)}。ALIGN(648);。SEGMENTSTART(ldatasegment,。);。lrodataALIGN(CONSTANT(MAXPAGESIZE))(。(CONSTANT(MAXPAGESIZE)1)):{(。lrodata。lrodata。。gnu。linkonce。lr。)}。ldataALIGN(CONSTANT(MAXPAGESIZE))(。(CONSTANT(MAXPAGESIZE)1)):{(。ldata。ldata。。gnu。linkonce。l。)。ALIGN(。!0?648:1);}。ALIGN(648);end。;PROVIDE(end。);。DATASEGMENTEND(。);Stabsdebuggingsections。。stab0:{(。stab)}。stabstr0:{(。stabstr)}。stab。excl0:{(。stab。excl)}。stab。exclstr0:{(。stab。exclstr)}。stab。index0:{(。stab。index)}。stab。indexstr0:{(。stab。indexstr)}。comment0:{(。comment)}DWARFdebugsections。SymbolsintheDWARFdebuggingsectionsarerelativetothebeginningofthesectionsowebeginthemat0。DWARF1。debug0:{(。debug)}。line0:{(。line)}GNUDWARF1extensions。debugsrcinfo0:{(。debugsrcinfo)}。debugsfnames0:{(。debugsfnames)}DWARF1。1andDWARF2。debugaranges0:{(。debugaranges)}。debugpubnames0:{(。debugpubnames)}DWARF2。debuginfo0:{(。debuginfo。gnu。linkonce。wi。)}。debugabbrev0:{(。debugabbrev)}。debugline0:{(。debugline。debugline。。debuglineend)}。debugframe0:{(。debugframe)}。debugstr0:{(。debugstr)}。debugloc0:{(。debugloc)}。debugmacinfo0:{(。debugmacinfo)}SGIMIPSDWARF2extensions。debugweaknames0:{(。debugweaknames)}。debugfuncnames0:{(。debugfuncnames)}。debugtypenames0:{(。debugtypenames)}。debugvarnames0:{(。debugvarnames)}DWARF3。debugpubtypes0:{(。debugpubtypes)}。debugranges0:{(。debugranges)}DWARFExtension。。debugmacro0:{(。debugmacro)}。debugaddr0:{(。debugaddr)}。gnu。attributes0:{KEEP((。gnu。attributes))}DISCARD:{(。note。GNUstack)(。gnudebuglink)(。gnu。lto)}} main。cDescription:内核模块原理Version:2。0Autor:lshDate:2022022517:47:46LastEditors:lshLastEditTime:2022022519:54:20includeunistd。hincludestdint。hincludestdio。htypedefvoid(initcall)(void);Thesetwovariablesaredefinedinlinkscript。defineinitattribute((unused,section(。myinit)))defineDECLAREINIT(func)initcallfnfuncinitfuncstaticvoidAinit(void){write(1,Ainit,sizeof(Ainit));}DECLAREINIT(Ainit);staticvoidBinit(void){printf(Binit);}DECLAREINIT(Binit);staticvoidCinit(void){printf(Cinit);}DECLAREINIT(Cinit);DECLAREINITlikebelow:staticinitcallfnAinitattribute((unused,section(。myinit)))AstaticinitcallfnCinitattribute((unused,section(。myinit)))CstaticinitcallfnBinitattribute((unused,section(。myinit)))Bvoiddoinitcalls(void){for(;initptr){printf(initaddress:p,initptr);(initptr)();}}intmain(void){doinitcalls();return0;} 编译教程 执行:gcccsection。cosection。o编译应用源码。 执行:readelfSsection。o查看段信息。可以看到,段〔6〕是我们自定义的数据段。 执行:gccTldscript。ldssection。oosection链接成可执行的bin文件 执行:readelfSsection查看bin文件的段分布情况在我链接成的可执行bin中,在〔25〕段中存在我们自定义的段。 运行后可以看到打印为调用了所有3个函数。 版权声明:本文为CSDN博主若灮的原创文章,遵循CC4。0BYSA版权协议,转载请附上原文出处链接及本声明。 原文链接:https:blog。csdn。netu011164819articledetails123281172