安庆大理运城常德铜陵江西
投稿投诉
江西南阳
嘉兴昆明
铜陵滨州
广东西昌
常德梅州
兰州阳江
运城金华
广西萍乡
大理重庆
诸暨泉州
安庆南充
武汉辽宁

linux的initcall机制应用层用链接脚本实现

11月3日 游鱼坊投稿
  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
投诉 评论 转载

特斯拉陶琳特斯拉本身没有问题,建议加强消费者教育都知道未来是新能源汽车的天下,大众、本田、丰田、现代等全球知名车企都开始往电气化转型,而豪华品牌也是如此,奔驰、奥迪也加入到了新能源汽车大军,而宝马更是研发起氢燃料电池汽车,可……骁龙888Plus拥有自研架构AdrenoGPU,面对高强度最近这几年,手游产业一直处于上升趋势,几乎每个人的手机里都装着这么一两款好玩的游戏。大型游戏包括《和平精英》、《原神》等,休闲小游戏如《开心消消乐》、《极品飞车》。对于普通玩家……华为员工首曝猛料!自研5G射频芯片成了Mate50首发搭载将【9月19日讯】相信大家都知道,自从华为P50系列手机发布以后,由于全系机型都遗憾缺席了5G网络,也让华为全球第一的5G实力遭受到了质疑,甚至还有不少网友们嘲讽到:华为拥有全球……土星五号是50年前的产物,为何其它国家还没搞出与土星五号性能历史的不一定是落后的。某些历史产物,因其所处的特定历史时期,为实现某一特定用途而研发,从而成为当时集尖端科技、巨额资金、人力物力于一体的神器,它们自身所蕴含的科技成果,即使放在……最后的挣扎?滴滴应用下架后首次出手,媒体早该如此了文C君科讯排版C君科讯头条号原创文章,禁止抄袭,违者必究滴滴应用下架后的局面7月2网络安全审查办公室对滴滴出行进行审查,7月4日网信办给出审查结果,证实滴滴出行AP……具备赛博朋克根基元素,雷柏VT960无线游戏鼠标采用神经机械从虚拟世界奇景中光怪陆离的霓虹灯光,到人类和电子机械的融合超现实符号,在顶级芯片内部彼此控制。密集的钢筋丛林,在真实与梦幻并存的电子竞技时代,随时……linux的initcall机制应用层用链接脚本实现initcall机制的由来我们都知道,linux对驱动程序提供静态编译进内核和动态加载两种方式,当我们试图将一个驱动程序编译进内核时,开发者通常提供一个xxxinit()……新设计,更智能F30Mini智显屏冲牙器相信大家都有所了解,电动冲牙器是十分成熟的牙齿清洁产品,其最先在国外兴起,引进国内至今也发展了极为长久的一段时间。很多注重自己口腔健康的人相信也早已使用过冲牙器,体验过一冲洁净……使用华为手机拍照,一定要开启这些设置,分分钟拍出大片既视感现在很多人都喜欢用手机拍照记录生活的一些美好的瞬间,如果你用的是华为手机,那么今天有福了,小编给大家安利几个华为手机拍照的必备设置,有了这些功能的加持,相信你也能拍出美美的照片……有耳鸣戴助听器后耳鸣声音能不能消失?你好:很高兴回答你问题1、可以明确地说耳鸣的病人选配了助听器后耳鸣还是不能消失。2、助听器是一种补偿听力的工具,不是特别的助听器对耳鸣是没有治疗作用的,但如果助听器……来个大哥推荐下用的好的游戏鼠标?抓握,趴握,指握,或者是接近这三种姿势?姿势不同,鼠标选择就不同。我手中的神器,到你那就变成垃圾打游戏用?什么游戏?有什么要求?302,403,703都不错,502慎选。……Docker部署mrdoc以及备份搬家该服务前言:我们在一台服务器A用docker安装了mrdoc服务,所有数据都在此台服务器,那么如果有一天因为某种原因需要更换mrdoc服务到服务器B那么如何操作呢今天就给……
有什么好的副业可以做呢?五角大楼工程师为何指出,中美军事技术之争已经结束?iPhone14Pro渲染图伸缩相机10倍长焦,再次将果粉口2022年互联网股买什么?EvercoreISI给出其最佳选有没有可能,地球是全宇宙最高等文明?恒大汽车上海车展大秀实力数百家投资机构纷纷点赞移动电源CQC认证,充电宝CQC认证,移动电源CB认证,充电2021年上半年,国产电视哪几款性价比最高?这三款高票当选吉利成立远程智能交通技术公司,注册资本1000万元在10平米的房间,用1P和1。5P一级能效变频空调,耗电上和快手的存在对社会有没有造成影响?行程码是怎么知道你去过哪些地方的呢?

友情链接:中准网聚热点快百科快传网快生活快软网快好知文好找七猫云易事利