子系统的初始化那些入口函数Linu

内核选项的解析完成之后,各个子系统的初始化即进入第二部分—入口函数的调用。通常USB、PCI这样的子系统都会有一个名为subsys_initcall的入口,如果你选择它们作为研究内核的切入点,那么就请首先找到它。

朱德庸在《关于上班这件事》里说,要花前半生找入口,花后半生找出口。可见寻找入口对于咱们这一生,对于看内核代码这件事儿都是无比重要的。

但是很多时候,入口并不仅仅只有subsys_initcall一个,比如PCI。

#definepure_initcall(fn)__define_initcall(0,fn,1)#definecore_initcall(fn)__define_initcall(1,fn,1)#definecore_initcall_sync(fn)__define_initcall(1s,fn,1s)#definepostcore_initcall(fn)__define_initcall(2,fn,2)#definepostcore_initcall_sync(fn)__define_initcall(2s,fn,2s)#definearch_initcall(fn)__define_initcall(3,fn,3)#definearch_initcall_sync(fn)__define_initcall(3s,fn,3s)#definesubsys_initcall(fn)__define_initcall(4,fn,4)#definesubsys_initcall_sync(fn)__define_initcall(4s,fn,4s)#definefs_initcall(fn)__define_initcall(5,fn,5)#definefs_initcall_sync(fn)__define_initcall(5s,fn,5s)#definerootfs_initcall(fn)__define_initcall(rootfs,fn,rootfs)#definedevice_initcall(fn)__define_initcall(6,fn,6)#definedevice_initcall_sync(fn)__define_initcall(6s,fn,6s)#definelate_initcall(fn)__define_initcall(7,fn,7)#definelate_initcall_sync(fn)__define_initcall(7s,fn,7s)#define__initcall(fn)device_initcall(fn)

这些入口有个共同的特征,它们都是使用__define_initcall宏定义的。它们的调用也不是随便的,而是按照一定顺序的,这个顺序就取决于__define_initcall宏。__define_initcall宏用来将指定的函数指针放到.initcall.init节里。

.initcall.init节

内核可执行文件由许多链接在一起的对象文件组成。对象文件有许多节,如文本、数据、init数据、bass等等。这些对象文件都是由一个称为链接器脚本的文件链接并装入的。这个链接器脚本的功能是将输入对象文件的各节映射到输出文件中;换句话说,它将所有输入对象文件都链接到单一的可执行文件中,将该可执行文件的各节装入到指定地址处。vmlinux.lds是存在于arch/target/目录中的内核链接器脚本,它负责链接内核的各个节并将它们装入内存中特定偏移量处。在vmlinux.lds文件里查找initcall.init就可以看到下面的内容

__inicall_start=.;.initcall.init:AT(ADDR(.initcall.init)–0xC){*(.initcall1.init)*(.initcall2.init)*(.initcall3.init)*(.initcall4.init)*(.initcall5.init)*(.initcall6.init)*(.initcall7.init)}__initcall_end=.;

这就告诉我们.initcall.init节又分成了7个子节,而xxx_initcall入口函数指针具体放在哪一个子节里边儿是由xxx_initcall的定义中,__define_initcall宏的参数决定的,比如core_initcall将函数指针放在.initcall1.init子节,device_initcall将函数指针放在了.initcall6.init子节等等。各个子节的顺序是确定的,即先调用.initcall1.init中的函数指针再调用.initcall2.init中的函数指针,等等。不同的入口函数被放在不同的子节中,因此也就决定了它们的调用顺序。

do_initcalls()函数

那些入口函数的调用由do_initcalls函数来完成。

do_initcall函数通过for循环,由__initcall_start开始,直到__initcall_end结束,依次调用识别到的初始化函数。而位于__initcall_start和__initcall_end之间的区域组成了.initcall.init节,其中保存了由xxx_initcall形式的宏标记的函数地址,do_initcall函数可以很轻松的取得函数地址并执行其指向的函数。

.initcall.init节所保存的函数地址有一定的优先级,越前面的函数优先级越高,也会比位于后面的函数先被调用。

由do_initcalls函数调用的函数不应该改变其优先级状态和禁止中断。因此,每个函数执行后,do_initcalls会检查该函数是否做了任何变化,如果有必要,它会校正优先级和中断状态。

另外,这些被执行的函数有可以完成一些需要异步执行的任务,flush_scheduled_work函数则用于确保do_initcalls函数在返回前等待这些异步任务结束。

staticvoid__initdo_initcalls(void){initcall_t*call;intcount=preempt_count();for(call=__initcall_start;call__initcall_end;call++){ktime_tt0,t1,delta;char*msg=NULL;charmsgbuf[40];intresult;677if(initcall_debug){printk(Callinginitcall0x%p,*call);print_fn_descriptor_symbol(:%s(),(unsignedlong)*call);printk(/n);t0=ktime_get();}result=(*call)();687if(initcall_debug){t1=ktime_get();delta=ktime_sub(t1,t0);printk(initcall0x%p,*call);print_fn_descriptor_symbol(:%s(),(unsignedlong)*call);printk(returned%d./n,result);printk(initcall0x%pranfor%Ldmsecs:,*call,(unsignedlonglong)delta.tv);print_fn_descriptor_symbol(%s()/n,(unsignedlong)*call);}if(resultresult!=-ENODEVinitcall_debug){sprintf(msgbuf,errorcode%d,result);msg=msgbuf;}if(preempt_count()!=count){msg=preemptionimbalance;preempt_count()=count;}if(irqs_disabled()){msg=disabledinterrupts;local_irq_enable();}if(msg){printk(KERN_WARNINGinitcallat0x%p,*call);print_fn_descriptor_symbol(:%s(),(unsignedlong)*call);printk(:returnedwith%s/n,msg);}}/*Makesurethereisnopendingstufffromtheinitcallsequence*/flush_scheduled_work();}









































全身白癜风
北京治疗白癜风要花多少钱呀



转载请注明:http://www.xxcyfilter.com/zyfz/3711.html