linux驱动启动顺序

首先,咱们能够查看Linux内核编译完成后的System.map文件,在这个文件中咱们能够看到macb(dm9161驱动模块)连接到了dm9000驱动以前,以下所示:linux

c03b6d40 t __initcall_tun_init6编程

c03b6d44 t __initcall_macb_init6vim

c03b6d48 t __initcall_dm9000_init6centos

c03b6d4c t __initcall_ppp_init6网络

c03b6d50 t __initcall_ppp_async_init6dom

 

   我尝试修改arch/arm/mach-at91/board-sam9260ek.c中DM9000和DM916设备添加的顺序,即先添加 dm9000,后添加dm9161。编译后运行发现,结果仍是同样。本身想了想,这也在情理之中。由于这个出现这个问题的主要缘由是这两个驱动加载的前后 顺序,而不是设备添加的前后顺序。async

 

 在Linux内核中维护着两个链,一个设备链,一个驱动链,他们两个就像情侣同样互相 依赖,互相纠缠在一块儿的。当咱们新添加一个设备时,他会被加入到设备链上,这时内核这个红娘会就会到驱动链上给他找他的另一半(驱动),看是否有哪一个驱 动看上了他(这个驱动是否支持这个设备),若是找到了这个驱动,那么设备就可以使用(你们纠缠到一块了,该干吗就干吗去了)。而若是没有找到,那么设备就 只能默默地在那里等待他的另外一半的出现。下面是arch/arm/mach-at91/board-sam9260ek.c添加设备的代码:函数

static void __init ek_board_init(void){       /* Serial */      oop

  at91_add_device_serial();    /* USB Host */     post

 at91_add_device_usbh(&ek_usbh_data);    /* USB Device */   

 at91_add_device_udc(&ek_udc_data);    /* SPI */          

 at91_add_device_spi(ek_spi_devices, ARRAY_SIZE(ek_spi_devices));    /* NAND */         

 ek_add_device_nand();    /* Ethernet */    ek_add_device_dm9000(); /* Add dm9000 driver by guowenxue, 2012.04.11 */

 at91_add_device_eth(&ek_macb_data);    /* MMC */    

at91_add_device_mmc(0, &ek_mmc_data);    /* I2C */     

at91_add_device_i2c(ek_i2c_devices, ARRAY_SIZE(ek_i2c_devices));    /* SSC (to AT73C213) */

#if defined(CONFIG_SND_AT73C213) || defined(CONFIG_SND_AT73C213_MODULE)     

at73c213_set_clk(&at73c213_data); /* Modify by guowenxue, 2012.04.11 */

#endif     

at91_add_device_ssc(AT91SAM9260_ID_SSC, ATMEL_SSC_TX);

#if 0 /* comment by guowenxue  */    /* LEDs */    

at91_gpio_leds(ek_leds, ARRAY_SIZE(ek_leds));     /* Push Buttons */   

 ek_add_device_buttons();

#endif

}

 

MACHINE_START(AT91SAM9260EK, "Atmel AT91SAM9260-EK")    /* Maintainer: Atmel */   

 .timer      = &at91sam926x_timer,    

.map_io     = at91_map_io,   

 .init_early = ek_init_early,   

 .init_irq   = at91_init_irq_default,   

 .init_machine   = ek_board_init,

MACHINE_END

 

MACHINE_START主要是定义了"struct machine_desc"的类型,放在 section(".arch.info.init"),是初始化数据,Kernel 起来以后将被丢弃。

其他各个成员函数在setup_arch()中被赋值到内核结构体,在不一样时期被调用:

1. .init_machine 在 arch/arm/kernel/setup.c 中被 customize_machine 调用,放在 arch_initcall() 段里面,会自动按顺序被调用。

2. .init_irq在start_kernel() --> init_IRQ() --> init_arch_irq()中被调用

3. .map_io 在 setup_arch() --> paging_init() --> devicemaps_init()中被调用

4. .timer是定义系统时钟,定义TIMER4为系统时钟,在arch/arm/mach-at91/at91sam926x_time.c中实现。在start_kernel() --> time_init()中被调用。

5. .boot_params是bootloader向内核传递的参数的位置,这要和bootloader中参数的定义要一致。

其余主要都在 setup_arch() 中用到。

 

  当在Linux内核启动调用ek_board_init()时,就会调用ek_add_device_dm9000()

和at91_add_device_eth(&ek_macb_data)来分别将dm9161和dm9000这两个设备添加到设备链上去。而后,他们就开始在链表上苦苦等待他的另外一半(相应驱动)的出现。

 

   这里咱们只是调整这两个网络设备在设备链上的位置,但问题的本质是驱动连接的位置是dm9161在前,dm9000在后,这样dm9161驱动先加载后 就找到设备dm9161,这样他使用了eth0这个设备;而dm9000的驱动后加载,这样他对应的设备名就是eth1了。这里来分析为何是先加载 dm9161,后加载dm9000这个驱动,只有了解了这个缘由,咱们才能调整他们的加载顺序。

 

   几乎每一个linux驱动都会调用module_init(它和module_exit一块儿定义在Init.h (/include/linux) 中。没错,驱动的加载就靠它。为何须要这样一个宏?缘由是按照通常的编程想法,各部分的初始化函数会在一个固定的函数里调用好比:

 

void init(void)

{   

    init_a();  

    init_b();

}

 

若是再加入一个初始化函数呢,那么在init_b()后面再加一行init_c();这样确实能完成咱们的功能,但这样有必定的问题,就是不能独立的添加初始化函数,每次添加一个新的函数都要修改init函数。能够采用另外一种方式来处理这个问题,只要用一个宏来修饰一下:

void init_a(void)

{

}

__initlist(init_a, 1);

 

它是怎么样经过这个宏来实现初始化函数列表的呢?先来看__initlist的定义:

#define __init __attribute__((unused, __section__(".initlist"))) 

#define __initlist(fn, lvl) /  

static initlist_t __init_##fn __init = { /  

 magic:    INIT_MAGIC, /  

 callback: fn, /  

level:   lvl }

 

请 注意:__section__(".initlist"),这个属性起什么做用呢?它告诉链接器这个变量存放在.initlist区段,这是 GNU/GCC的特性,关于这部份内容你们能够参考GNU链接器的说明文档。若是全部的初始化函数都是用这个宏,那么每一个函数会有对应的一个 initlist_t结构体变量存放在.initlist区段,也就是说咱们能够在.initlist区段找到全部初始化函数的指针。怎么找 到.initlist区段的地址呢?

 

extern u32 __initlist_start;

extern u32 __initlist_end;

 

这 两个变量起做用了,__initlist_start是.initlist区段的开始,__initlist_end是结束,经过这两个变量咱们就能够访 问到全部的初始化函数了。这两个变量在那定义的呢?在一个链接器脚本文件里(别告诉我说你不知道链接器脚本是啥,若是不知道,好好恶补一下)。

 

. = ALIGN(4);  .initlist : {   __initlist_start = .;   *(.initlist)   __initlist_end = .;  } 

 

这两个变量的值正好定义在.initlist区段的开始和结束地址,因此咱们能经过这两个变量访问到全部的初始化函数。

 

与 此相似,内核中也是用到这种方法,因此咱们写驱动的时候比较独立,不用咱们本身添加代码在一个固定的地方来调用咱们本身的初始化函数和退出函数,链接器已 经为咱们作好了。先来分析一下module_init。他在include/linux/init.h文件中定义以下:

 

#define module_init(x)  __initcall(x);#define __initcall(fn) device_initcall(fn)

#define pure_initcall(fn)       __define_initcall("0",fn,0)

#define core_initcall(fn)       __define_initcall("1",fn,1)

#define core_initcall_sync(fn)      __define_initcall("1s",fn,1s)

#define postcore_initcall(fn)       __define_initcall("2",fn,2)

#define postcore_initcall_sync(fn)  __define_initcall("2s",fn,2s)

#define arch_initcall(fn) __define_initcall("3",fn,3)

#define arch_initcall_sync(fn)      __define_initcall("3s",fn,3s)

#define subsys_initcall(fn) _define_initcall("4",fn,4)

#define subsys_initcall_sync(fn)    __define_initcall("4s",fn,4s)

#define fs_initcall(fn)         __define_initcall("5",fn,5)

#define fs_initcall_sync(fn)        __define_initcall("5s",fn,5s)

#define rootfs_initcall(fn)     __define_initcall("rootfs",fn,rootfs)

#define device_initcall(fn)     __define_initcall("6",fn,6)

#define device_initcall_sync(fn)    __define_initcall("6s",fn,6s)

#define late_initcall(fn)       __define_initcall("7",fn,7)

#define late_initcall_sync(fn)      __define_initcall("7s",fn,7s)

#define __define_initcall(level,fn,id) \    

static initcall_t __initcall_##fn##id __used \    

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

 

如 果某驱动想以func做为该驱动的入口,则能够以下声明:module_init(func);被上面的宏处理事后,变成 __initcall_func6 __used加入到内核映像的".initcall"区(这就是咱们上面System.map文件中__initcall_macb_init6和 __initcall_dm9000_init6的来历)。内核的加载的时候,会搜索".initcall"中的全部条目,并按优先级加载它们,普通驱动 程序的优先级是6。其它模块优先级列出以下:值越小,越先加载。从上能够看到,被声明为pure_initcall的最早加载。

 

module_init除了初始化加载以外,还有后期释放内存的做用。linux kernel中有很大一部分代码是设备驱动代码,这些驱动代码都有初始化和反初始化函数,这些代码通常都只执行一次,为了有更有效的利用内存,这些代码所占用的内存能够释放出来。

 

linux 就是这样作的,对只须要初始化运行一次的函数都加上__init属性,__init 宏告诉编译器若是这个模块被编译到内核则把这个函数放到(.init.text)段,module_exit的参数卸载时同__init相似,若是驱动被 编译进内核,则__exit宏会忽略清理函数,由于编译进内核的模块不须要作清理工做,显然__init和__exit对动态加载的模块是无效的,只支持 彻底编译进内核。

 

在kernel初始化后期,释放全部这些函数代码所占的内存空间。链接器把带__init属性的函数放在 同一个section里,在用完之后,把整个section释放掉。当函数初始化完成后这个区域能够被清除掉以节约系统内存。Kenrel启动时看到的消 息“Freeing unused kernel memory: xxxk freed”同它有关。

 

也就是在写驱动的时候,经过module_init()宏,告诉咱们的驱动函数入口放到.initcall节中的哪一个部分,那么Linux内核在启动的时候又是怎么调用咱们的这些驱动入口函数的呢?

 

Linux 系统使用两种方式去加载系统中的模块:动态和静态。这里咱们dm9161和dm9000的驱动是以静态的方式程序编译到Linux内核中,Linux系统 启动时会进入C函数入口(下面函数都在init/main.c文件 中)start_kernel()->rest_init()->kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND)->kernel_init()->do_basic_setup()->do_initcalls().

下面是do_initcalls()的定义:

 

extern initcall_t __initcall_start[], __initcall_end[], __early_initcall_end[];

static void __init do_initcalls(void){    

      initcall_t *fn;

    for (fn = __early_initcall_end; fn < __initcall_end; fn++)        

    do_one_initcall(*fn);

}

 

do_initcalls 函数中会将在__early_initcall_end 和__initcall_end之间定义的各个模块依次加载。那么在__early_initcall_end 和 __initcall_end之间都有些什么呢?咱们能够查看arch/arm/kernel/vmlinux.lds文件中关 于.initcall.init段:

  __initcall_start = .; *(.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) __initcall_end = .;

 

可 以看出在这两个宏之间依次排列了14个等级的宏,因为这其中的宏是按前后顺序连接的,因此也就表示,这14个宏有优先 级:0>0s>1>1s>2>2s………>7>7s,这里的优先级也就意味着谁的优先级高,那么谁就会被先加 载。关于这宏有什么具体的意义呢,这就要看咱们以前提到的include/linux/init.h文件中的定义了:

#define module_init(x)  __initcall(x);#define __initcall(fn) device_initcall(fn)

#define pure_initcall(fn)       __define_initcall("0",fn,0)

#define core_initcall(fn)       __define_initcall("1",fn,1)

#define core_initcall_sync(fn)      __define_initcall("1s",fn,1s)

#define postcore_initcall(fn)       __define_initcall("2",fn,2)

#define postcore_initcall_sync(fn)  __define_initcall("2s",fn,2s)

#define arch_initcall(fn)       __define_initcall("3",fn,3)

#define arch_initcall_sync(fn)      __define_initcall("3s",fn,3s)

#define subsys_initcall(fn)     __define_initcall("4",fn,4)

#define subsys_initcall_sync(fn)    __define_initcall("4s",fn,4s)

#define fs_initcall(fn)         __define_initcall("5",fn,5)

#define fs_initcall_sync(fn)        __define_initcall("5s",fn,5s)

#define rootfs_initcall(fn)     __define_initcall("rootfs",fn,rootfs)

#define device_initcall(fn)     __define_initcall("6",fn,6)

#define device_initcall_sync(fn)    __define_initcall("6s",fn,6s)

#define late_initcall(fn)       __define_initcall("7",fn,7)

#define late_initcall_sync(fn)      __define_initcall("7s",fn,7s)

 

从 上面分析,咱们能够看到,咱们的DM9000和DM9161都是使用module_init()来定义的,那么他们最终都是在同一个级别( __define_initcall("6",fn,6))中加载。对于这些函数指针的顺序也是和连接的顺序有关的,但具体是不肯定的(不通目录下的连接 顺序),但我经过修改Makefile中的编译顺序,把DM9000的编译放在DM9161以前就OK了。这样能够看出,对于同一目录下的驱动文件,咱们 能够经过调整他们在Makefile中编译的顺序来解决这个问题:

[guowenxue@centos6 linux-3.3]$ vim drivers/net/ethernet/Makefile 

obj-$(CONFIG_DM9000) += davicom/

obj-$(CONFIG_NET_CADENCE) += cadence/

 

编译后再看System.map文件:

c03b6d40 t __initcall_tun_init6

c03b6d44 t __initcall_dm9000_init6

c03b6d48 t __initcall_macb_init6

c03b6d4c t __initcall_ppp_init6

c03b6d50 t __initcall_ppp_async_init

 

系统启动打印:

......

bonding: Ethernet Channel Bonding Driver: v3.7.1 (April 27, 2011)

tun: Universal TUN/TAP device driver, 1.6

tun: (C) 1999-2004 Max Krasnyansky <maxk@qualcomm.com>

dm9000 Ethernet Driver, V1.31

eth0: dm9000a at c4896000,c489e044 IRQ 111 MAC: 00:30:c2:12:03:19 (chip)

macb macb: (unregistered net_device): invalid hw address, using random

MACB_mii_bus: probed

macb macb: eth1: Cadence MACB at 0xfffc4000 irq 21 (6e:cf:99:c4:e4:5b)

macb macb: eth1: attached PHY driver [Generic PHY] (mii_bus:phy_addr=macb-ffffffff:00, irq=-1)

PPP generic driver version 2.4.2

PPP BSD Compression module registered

PPP Deflate Compression module registered

PPP MPPE Compression module registered

NET: Registered protocol family 24

usbcore: registered new interface driver rt2800usb

ohci_hcd: USB 1.1 'Open' Host Controller (OHCI) Driver

at91_ohci at91_ohci: AT91 OHCI

at91_ohci at91_ohci: new USB bus registered, assigned bus number 1

at91_ohci at91_ohci: irq 20, io mem 0x00500000

hub 1-0:1.0: USB hub found

hub 1-0:1.0: 2 ports detected

Initializing USB Mass Storage driver...

 

........

 

若是这种方法不能解决的话,那么咱们能够修改dm9161的驱动,将module_init宏改为device_initcall_sync,这样加载的级别下降后也能改变他们的加载顺序,以下。

[guowenxue@centos6 linux-3.3]$ vim drivers/net/ethernet/cadence/macb.c

....

//module_init(macb_init);

device_initcall_sync(macb_init);

module_exit(macb_exit);

 

这样编译后的System.map文件显示:

c03b6d40 t __initcall_tun_init6

c03b6d44 t __initcall_dm9000_init6

c03b6d48 t __initcall_ppp_init6

c03b6d4c t __initcall_ppp_async_init6

c03b6d50 t __initcall_bsdcomp_init6

 

....

c03b6f6c t __initcall_macb_init6s

c03b6f70 t __initcall_at91_clock_reset7

c03b6f74 t __initcall_init_oops_id7

c03b6f78 t __initcall_printk_late_init7

c03b6f7c t __initcall_sched_init_debug7

c03b6f80 t __initcall_ubifs_init7

 

 

这时系统启动的过程:

....

bonding: Ethernet Channel Bonding Driver: v3.7.1 (April 27, 2011)

tun: Universal TUN/TAP device driver, 1.6

tun: (C) 1999-2004 Max Krasnyansky <maxk@qualcomm.com>

dm9000 Ethernet Driver, V1.31

eth0: dm9000a at c4896000,c489e044 IRQ 111 MAC: 00:30:c2:12:03:19 (chip)

PPP generic driver version 2.4.2

PPP BSD Compression module registered

PPP Deflate Compression module registered

PPP MPPE Compression module registered

NET: Registered protocol family 24

.......

Bridge firewalling registered

lib80211: common routines for IEEE802.11 drivers

macb macb: (unregistered net_device): invalid hw address, using random

MACB_mii_bus: probed

macb macb: eth1: Cadence MACB at 0xfffc4000 irq 21 (b6:6c:eb:6a:dc:cc)

macb macb: eth1: attached PHY driver [Generic PHY] (mii_bus:phy_addr=macb-ffffffff:00, irq=-1)

rtc-ds1307 0-0068: setting system clock to 2000-01-01 00:00:00 UTC (946684800)

RAMDISK: gzip image found at block 0

EXT2-fs (ram0): warning: mounting unchecked fs, running e2fsck is recommended

VFS: Mounted root (ext2 filesystem) on device 1:0.

Freeing init memory: 140K

 

.....

 

device 先注册,driver后注册。由于device 对应的MACHINE_START 对应的arch_initcall优先级为3.

module_init对应的优先级是6.

相关文章
相关标签/搜索