首先, 我们可以查看 Linux 内核编译完成后的 System.map 文件, 在这个文件中我们可以看到 macb(dm9161 驱动模块)链接到了 dm9000 驱动之前, 如下所示:
- c03b6d40 t __initcall_tun_init6
- c03b6d44 t __initcall_macb_init6
- c03b6d48 t __initcall_dm9000_init6
- c03b6d4c t __initcall_ppp_init6
- c03b6d50 t __initcall_ppp_async_init6
我尝试修改 arch/arm/mach-at91/board-sam9260ek.c 中 DM9000 和 DM916 设备添加的顺序, 即先添加 dm9000, 后添加 dm9161. 编译后运行发现, 结果还是一样. 自己想了想, 这也在情理之中. 因为这个出现这个问题的主要原因是这两个驱动加载的先后 顺序, 而不是设备添加的先后顺序.
在 Linux 内核中维护着两个链, 一个设备链, 一个驱动链, 他们两个就像情侣一样互相 依赖, 互相纠缠在一起的. 当我们新添加一个设备时, 他会被加入到设备链上, 这时内核这个红娘会就会到驱动链上给他找他的另外一半(驱动), 看是否有哪个驱 动看上了他(这个驱动是否支持这个设备), 如果找到了这个驱动, 那么设备就能够使用(大家纠缠到一块了, 该干嘛就干嘛去了). 而如果没有找到, 那么设备就 只能默默地在那里等待他的另一半的出现. 下面是 arch/arm/mach-at91/board-sam9260ek.c 添加设备的代码:
- static void __init ek_board_init(void){ /* Serial */
- at91_add_device_serial(); /* USB Host */
- 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.
来源: http://www.bubuko.com/infodetail-2769386.html