您的位置:首页 > 其它

从编译和链接来看kernel驱动注册的过程

2012-02-07 17:01 267 查看
=============================================================================================================

先不管编译,我们先看看kernel的链接过程。在这里我们使用arm-eabi-ld来进行链接,链接过程需要使用到一个链接脚本,这个脚本就是vmlinux.lds了。vmlinux.lds决定了如何将各个输入文件的section放入最后的vmlinux里面,并控制各部分在程序地址空间中的布局,这里的链接都是静态链接(kernel的链接没有动态的概念?)。由此可以看出来vmlinux.lds实际上就是vmlinux内核镜像的一个简略表达方式,它从程序地址空间和文件的角度来描述了kernel
image。

首先我们看看vmlinux.lds的生成,vmlinux.lds是由arch/arm/kernel/vmlinux.lds.S生成的,vmlinx.lds.S使用到了很多变量,而这些变量都定义在include/asm-generic/vmlinx.lds.h里面,在top Makefile里面有:

vmlinux-lds := arch/$(SRCARCH)/kernel/vmlinux.lds

而在arch/arm/Makefile里面定义了生成vmlinx.lds的FLAG:

CPPFLAGS_vmlinux.lds = -DTEXT_OFFSET=$(TEXT_OFFSET)

具体如何来生成vmlinux.lds我们不做考虑,我们现在来看看vmlinux.lds的内容,了解一下vmlinx里面各个section的分布:

1. OUTPUT_ARCH(arm)  
   2. ENTRY(stext)  
   3. jiffies = jiffies_64;  
   4. SECTIONS  
   5. {  
   6.  . = 0x80000000 + 0x00008000;  
   7.  .text.head : {   
   8.   _stext = .;  
   9.   _sinittext = .;  
  10.   *(.text.head)  
  11.  }  
  12.  .init : { /* Init code and data        */    
  13.    *(.init.text) *(.cpuinit.text) *(.meminit.text)  
  14.   _einittext = .;  
  15.   __proc_info_begin = .;  
  16.    *(.proc.info.init)  
  17.   __proc_info_end = .;  
  18.   __arch_info_begin = .;  
  19.    *(.arch.info.init)  
  20.   __arch_info_end = .;  
  21.   __tagtable_begin = .;  
  22.    *(.taglist.init)  
  23.   __tagtable_end = .;  
  24.   . = ALIGN(16);  
  25.   __setup_start = .;  
  26.    *(.init.setup)  
  27.   __setup_end = .;  
  28.   __early_begin = .;  
  29.    *(.early_param.init)  
  30.   __early_end = .;  
  31.   __initcall_start = .;  
  32.    *(.initcallearly.init) __early_initcall_end = .; *(.initcall0.init) *(.initcall0s.init) *(.initcall1.init) *(.initcall1s.init) *(.initcall2.init) *(.initcall2s.init) *(.     initcall3.init) *(.initcall3s.init) *(.initcall4.init) *(.initcall4s.init) *(.initcall5.init) *(.initcall5s.init) *(.initcallrootfs.init) *(.initcall6.init) *(.initcall6s.      init) *(.initcall7.init) *(.initcall7s.init)  
  33.   __initcall_end = .;  
  34.   __con_initcall_start = .;  
  35.    *(.con_initcall.init)  
  36.   __con_initcall_end = .;  
  37.   __security_initcall_start = .;  
  38.    *(.security_initcall.init)  
  39.   __security_initcall_end = .;  
  40.   . = ALIGN(32);  
  41.   __initramfs_start = .;  
  42.    usr/built-in.o(.init.ramfs)  
  43.   __initramfs_end = .;  
  44.   . = ALIGN(4096);  
  45.   __per_cpu_start = .;  
  46.    *(.data.percpu)  
  47.    *(.data.percpu.shared_aligned)  
  48.   __per_cpu_end = .;  
  49.   __init_begin = _stext;  
  50.   *(.init.data) *(.cpuinit.data) *(.cpuinit.rodata) *(.meminit.data) *(.meminit.rodata) __start___verbose_strings = .; *(__verbose_strings) __stop___verbose_strings = .; . =    ALIGN(8); __start___verbose = .; *(__verbose) __stop___verbose = .;  
  51.   . = ALIGN(4096);  
  52.   __init_end = .;  
  53.  }  
  54.  /DISCARD/ : { /* Exit code and data        */  
  55.   *(.exit.text) *(.cpuexit.text) *(.memexit.text)  
  56.   *(.exit.data) *(.cpuexit.data) *(.cpuexit.rodata) *(.memexit.data) *(.memexit.rodata)  
  57.   *(.exitcall.exit)  
  58.  }  
  59.  .text : { /* Real text segment     */  
  60.   _text = .; /* Text and read-only data */  
  61.    __exception_text_start = .;  
  62.    *(.exception.text)  
  63.    __exception_text_end = .;  
  64.    . = ALIGN(8); *(.text.hot) *(.text) *(.ref.text) *(.text.init.refok) *(.exit.text.refok) *(.devinit.text) *(.devexit.text) *(.text.unlikely)  
  65.    . = ALIGN(8); __sched_text_start = .; *(.sched.text) __sched_text_end = .;  
  66.    . = ALIGN(8); __lock_text_start = .; *(.spinlock.text) __lock_text_end = .;  
  67.    . = ALIGN(8); __kprobes_text_start = .; *(.kprobes.text) __kprobes_text_end = .;  
  68.    *(.fixup)  
  69.    *(.gnu.warning)  
  70.    *(.rodata)  
  71.    *(.rodata.*)  
  72.    *(.glue_7)  
  73.    *(.glue_7t)  
  74.   *(.got) /* Global offset table        */  
  75.  }


要阅读这个.lds文件必须对.lds的语法有一定了解,我这里只贴出部分,详细的大家可以去看一下自己生成的文件。在这里我们只看.init section部分的内容,我们可以看看init段的一个基本的执行过程(虽然虚拟地址空间不一定反映执行的过程,但我们在这里可以大致的这么理解),先是.init.text / .cpuinit.text /.meminit.text ---> .proc.info.init ---> .arch.info.init --->
.taglist.init ---> .init.setup ---> .early_param.init ---> .initcall……







了解了链接的过程之后,我们来看看driver注册的过程是如何和这个vmlinux.lds联系在一起的,我们首先来跟踪一下module_init这个宏,仔细跟踪一下我们就会发现它最后是被映射到了/include/linux/init.h里面:

module_init <--- #define module_init(x) __initcall(x); <--- #define __initcall(fn) device_initcall(fn) <--- #define device_initcall(fn) __define_initcall("6",fn,6)

最后这个__define_initcall :

#define __define_initcall(level,fn,id) /

static initcall_t __initcall_##fn##id __used /

__attribute__((__section__(".initcall" level ".init"))) = fn

这样我们就可以看到module_init实际上就是声明了一个可以放在.initcall
[s].init段里面的函数指针,这个n实际上就是优先级,s是sync的意思。

那么这些函数实际上是在什么地方被调用的呢,我们来看看start_kernel():

首先setup_arch()会调用msm7x2x_init()(源码在board_msm7x27.c中),初始化device数组platform_add_devices(devices,
ARRAY_SIZE(devices));
调用的语句是:

/*

* Set up various architecture-specific pointers

*/

init_arch_irq = mdesc->init_irq;

system_timer = mdesc->timer;

init_machine = mdesc->init_machine;

这里为什么为调用msm7x2x_init()呢?

原因是在board_msm7x27.c的最小面有这样的语句:

MACHINE_START(MSM7X27_FFA, "QCT MSM7x27 FFA")

#ifdef CONFIG_MSM_DEBUG_UART

.phys_io = MSM_DEBUG_UART_PHYS,

.io_pg_offst = ((MSM_DEBUG_UART_BASE) >> 18) & 0xfffc,

#endif

.boot_params = PHYS_OFFSET + 0x100,

.map_io = msm7x2x_map_io,

.init_irq = msm7x2x_init_irq,

.init_machine = msm7x2x_init,

.timer = &msm_timer,

MACHINE_END

找到MACHINE_START,MACHINE_END宏贴换过来就知道是怎么回事了。(原理就是注册了一个machine_desc结构体)

然后我们着重看看rest_init();

start_kernel --> res_init ---> kernel_init ---> do_basic_setup ---> do_initcalls();

1. extern initcall_t __initcall_start[], __initcall_end[], __early_initcall_end[];  
   2. static void __init do_initcalls(void)  
   3. {  
   4.     initcall_t *call;  
   5.     for (call = __early_initcall_end; call < __initcall_end; call++)  
   6.         do_one_initcall(*call);  
   7.     /* Make sure there is no pending stuff from the initcall sequence */  
   8.     flush_scheduled_work();  
   9. }


哈哈,看到do_initcall里面的for循环是不是很熟悉阿,对的,它就是vmlinux.lds里面定义的几个变量,实际上驱动注册的函数实际上都在这里被统一的调用。看到这里我们基本就可以知道如何修改我们驱动注册的顺序了,下面我们就看看具体如何操作。







从上面的知识我们可以看出所有驱动注册的函数module_init实际上都是注册优先级为6的initcall section,因此最简单的我们可以调整我们的优先级以实现驱动注册的前后,除了已有的几个优先级之外我们甚至能加自己的优先级,比如我们如果加一个7 优先级的section,我们首先在init.h里面添加相应的宏:

#define my_initcall(fn) __define_initcall("8",fn,8)

#define my_initcall(fn) __define_initcall("8s",fn,8s)

然后我们在vmlinux.lds.h里面添加相应的部分,在#define INITCALLS宏的末尾添加,顺序是很重要的,如果想让它的优先级为8必须放在末尾:

*(.initcall8.init) /

*(.initcall8s.init)

这样我们就可以用我们自己定义的宏my_initcall了~~不过这样调整驱动注册的优先级以及添加自己的优先级不是推荐的方式,没有确切测试过会对 kernel造成什么影响,下面我们就从编译的角度来看看如何改变驱动注册的顺序。kernel通过遍历目录中的kmakefile来将各个.o包含进去进行链接,不过我们可以通过另外一种方式了解kernel遍历的过程,那就是 kernel编译生成的system.map文件。system.map实际上是由nm
vmlinux生成的,这个命令是比较强大的,默认的system.map只能看到Symbol的虚拟地址,我们可以重新使用nm命令来生成,这样我们还能看到各个symbol在哪个文件里面。除了system.map之外我们还可以用arm-eabi-objdump -S 来查看vmlinux里面的symbol信息,只是生成的东西比较多~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: