设备的内核升级到最新的Linux-3.3上去了,这个版本主要新特性包括:合并了来自Android项目的内核代码,支持新的架构TI C6X,改进了Btrfs文件系统的balance操作、新的除错工具和RAID restripping;新的虚拟网络交换机Open vSwitch;支持EFI引导固件;改进内存管理等,这里我们比较感兴趣的是:更快更具弹性的网络设备接口teaming;通过字节序列限制改进网络延迟;网络优先控制组允许管理员动态设置网络流量的优先次序。
在移植好linux-3.3并跑起来后发现,以前(内核<=linux-3.0)设备上的两张网卡DM9000和DM9161分别对应的网络设备是eth0和eth1,由于在linux-3.3中内核调整了drivers/net/ethernet的目录结构,结果编译后运行新的内核发现,设备上的DM9000对应到eth1上,而DM9161对应到了eth0上。这与以前的网卡的对应关系不一致,所以希望调整这两个模块的链接顺序保证与以前的兼容。
首先,我们可以查看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 */ e
k_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
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
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
……
而我们在用户空间加载模块时常用的命令是insmod和modprobe,这两个命令主要是通过系统调用sys_init_module()
来完成主要的工作,用户层做的更多的是对参数的处理,以及将插入的模块加入到内存中。系统调用sys_init_module()
将大部分工作委托给load_module()
函数来完成,load_module()
中的操作,大部分是围绕着ELF文件的格式来完成的,所以如果对ELF文件了解的话,看load_module()
的过程很容易。 下面将对load_module(
)的一些理解注释比较详细:
/* Allocate and load the module: note that size of section 0 is always
zero, and we rely on this for optional sections. */
/*
* load_module()负责最艰苦的模块加载全过程。sys_init_module()调用load_module(),
* 后者将在内核空间利用vmalloc分配一块大小同样为len的地址空间。然后通过
* copy_from_user函数的调用将用户空间的文件数据复制到内核空间中,从而在内核空间
* 构造出内核模块的一个ELF静态的内存视图。接下来的操作都将以此视图为基础,为使
* 叙述简单起见,我们称该视图为HDR视图。HDR视图所占用的内存空间在load_module结束时
* 通过vfree予以释放。
*/
static noinline struct module *load_module(void __user *umod,
unsigned long len,
const char __user *uargs)
{
/*
* ELF文件头地址。
*/
Elf_Ehdr *hdr;
/*
* 段首部表地址
*/
Elf_Shdr *sechdrs;
char *secstrings, *args, *modmagic, *strtab = NULL;
char *staging;
unsigned int i;
unsigned int symindex = 0;
unsigned int strindex = 0;
unsigned int modindex, versindex, infoindex, pcpuindex;
struct module *mod;
long err = 0;
void *percpu = NULL, *ptr = NULL; /* Stops spurious gcc warning */
unsigned long symoffs, stroffs, *strmap;
mm_segment_t old_fs;
DEBUGP("load_module: umod=%p, len=%lu, uargs=%p\n",
umod, len, uargs);
/*
* 如果len小于ELF文件首部长度,则返回ENOEXEC错误。
*/
if (len < sizeof(*hdr))
return ERR_PTR(-ENOEXEC);
/* Suck in entire file: we'll want most of it. */
/* vmalloc barfs on "unusual" numbers. Check here */
/*
* 64 * 1024 * 1024应该是模块文件的最大大小。
*/
if (len > 64 * 1024 * 1024 || (hdr = vmalloc(len)) == NULL)
return ERR_PTR(-ENOMEM);
/*
* 将模块文件从用户空间拷贝到分配的hdr中。
*/
if (copy_from_user(hdr, umod, len) != 0) {
err = -EFAULT;
goto free_hdr;
}
/* Sanity checks against insmoding binaries or wrong arch,
weird elf version */
/*
* 检查文件标识是否是ELFMAG,检查模块目标文件是否是可重定向文件,
* 检查目标文件的体系结构类型,检查ELF首部中段首部表中表项的大小,
* 如果其中一项检查失败,则返回ENOEXEC。
*/
if (memcmp(hdr->e_ident, ELFMAG, SELFMAG) != 0
|| hdr->e_type != ET_REL
|| !elf_check_arch(hdr)
|| hdr->e_shentsize != sizeof(*sechdrs)) {
err = -ENOEXEC;
goto free_hdr;
}
/*
* hdr->e_shnum * sizeof(Elf_Shdr)计算的是ELF文件中段首部表的大小,
* 加上偏移的值如果大于len,则说明模块目标文件被截断了,跳转到
* truncated标签处处理
*/
if (len < hdr->e_shoff + hdr->e_shnum * sizeof(Elf_Shdr))
goto truncated;
/* Convenience variables */
/*
* 计算段首部表的地址.
*/
sechdrs = (void *)hdr + hdr->e_shoff;
/*
* 计算段名称字符串表的地址,其中hdr->e_shstrndx是段名称字符串表在段首部表中
* 的索引,sh_offset是当前段相对于文件头的偏移。
*/
secstrings = (void *)hdr + sechdrs[hdr->e_shstrndx].sh_offset;
/*
* 将第一个段在执行时的虚拟地址设为0,不使用段首部表中的第一个表项。
*/
sechdrs[0].sh_addr = 0;
/*
* 开始遍历段首部表, hdr->e_shnum是段首部表表项的数量
*/
for (i = 1; i < hdr->e_shnum; i++) {
/*
* 如果索引为i的段需要在文件中占据空间,但是文件长度小于
* 段的偏移加上段大小(也就是说文件长度不够),则跳转到
* truncated标签处处理
*/
if (sechdrs[i].sh_type != SHT_NOBITS
&& len < sechdrs[i].sh_offset + sechdrs[i].sh_size)
goto truncated;
/* Mark all sections sh_addr with their address in the
temporary image. */
/*
* 将段在执行时的虚拟地址设为他们在临时内存映像中的地址.
*/
sechdrs[i].sh_addr = (size_t)hdr + sechdrs[i].sh_offset;
/* Internal symbols and strings. */
/*
* 如果索引为i的段是符号表,则做相应的处理.目前目标文件只能有一个符号表,
* 这个限制以后可能会有变化,所以下面的语句只会执行一次。
*/
if (sechdrs[i].sh_type == SHT_SYMTAB) {
/*
* 用来保存符号表在段首部表中的索引
*/
symindex = i;
/*
* strindex存储的是与当前段段相关的字符串表段的索引。
*/
strindex = sechdrs[i].sh_link;
/*
* strtab存储的是与当前段相关的字符串表段的地址。
*/
strtab = (char *)hdr + sechdrs[strindex].sh_offset;
}
#ifndef CONFIG_MODULE_UNLOAD
/* Don't load .exit sections */
/*
* 如果当前段是".exit"段(前缀是".exit"),则在段的标志中移除SHF_ALLOC
* 标志,意思是当前段在执行过程中不需要占用内存。
*/
if (strstarts(secstrings+sechdrs[i].sh_name, ".exit"))
sechdrs[i].sh_flags &= ~(unsigned long)SHF_ALLOC;
#endif
}
/*
* 查找".gnu.linkonce.this_module"段在段首部表中的索引
*/
modindex = find_sec(hdr, sechdrs, secstrings,
".gnu.linkonce.this_module");
if (!modindex) {
printk(KERN_WARNING "No module found in object\n");
err = -ENOEXEC;
goto free_hdr;
}
/* This is temporary: point mod into copy of data. */
/*
* 将模块的地址暂时设为临时映像中段给出的地址。
*/
mod = (void *)sechdrs[modindex].sh_addr;
/*
* 如果没有找到符号表段,则跳转到free_hdr处处理
*/
if (symindex == 0) {
printk(KERN_WARNING "%s: module has no symbols (stripped?)\n",
mod->name);
err = -ENOEXEC;
goto free_hdr;
}
/*
* 查找__versions段在段首部表中的索引
*/
versindex = find_sec(hdr, sechdrs, secstrings, "__versions");
/*
* 查找.modinfo段在段首部表中的索引
*/
infoindex = find_sec(hdr, sechdrs, secstrings, ".modinfo");
/*
* 查找".data.percpu"段在段首部表中的索引
*/
pcpuindex = find_pcpusec(hdr, sechdrs, secstrings);
/* Don't keep modinfo and version sections. */
/*
* "__versions"和".modinfo"段在执行时不需要,因此移除SHF_ALLOC标志。
*/
sechdrs[infoindex].sh_flags &= ~(unsigned long)SHF_ALLOC;
sechdrs[versindex].sh_flags &= ~(unsigned long)SHF_ALLOC;
/* Check module struct version now, before we try to use module. */
/*
* 检查模块的版本信息。
*/
*
if (!check_modstruct_version(sechdrs, versindex, mod)) {
err = -ENOEXEC;
goto free_hdr;
}
/*
* 在.modinfo段查找vermagic变量对应的值。
*/
modmagic = get_modinfo(sechdrs, infoindex, "vermagic");
/* This is allowed: modprobe --force will invalidate it. */
if (!modmagic) {
/*
* 如果没有找到vermagic变量,则尝试强制加载模块。
* 但是try_to_force_load()函数的实现依赖于CONFIG_MODULE_FORCE_LOAD
* 宏是否定义。而该宏默认是没有定义的,所以这里会
* 返回失败,看来内核并不推荐强制加载模块。
*/
err = try_to_force_load(mod, "bad vermagic");
if (err)
goto free_hdr;
} else if (!same_magic(modmagic, vermagic, versindex)) {
printk(KERN_ERR "%s: version magic '%s' should be '%s'\n",
mod->name, modmagic, vermagic);
err = -ENOEXEC;
goto free_hdr;
}
/*
* 在.modinfo段查找staging变量对应的值。
*/
staging = get_modinfo(sechdrs, infoindex, "staging");
if (staging) {
/*
* 从2.6.28版本起,内核代码的drivers下增加了一个staging目录,
* 这个目录也是用来存放驱动程序,只是这里的驱动程序
* 和上层目录不同,加载的时候内核日志会打印如下的语句:
* MODULE_NAME: module is from the staging directory, the quality is unknown, you have been warned.
* Greg KH于2008年6月10号在Linux内核邮件列表里发出一封信,宣布建
* 立了另外一棵kernel tree,这就是Linux staging tree。Greg解释到,staging tree
* 建立之目的是用来放置一些未充分测试或者因为一些其他原因
* 未能进入内核的新增驱动程序和新增文件系统。
*/
add_taint_module(mod, TAINT_CRAP);
printk(KERN_WARNING "%s: module is from the staging directory,"
" the quality is unknown, you have been warned.\n",
mod->name);
}
/* Now copy in args */
/*
* 将插入模块时指定的参数从用于空间拷贝到args中。
*/
args = strndup_user(uargs, ~0UL >> 1);
if (IS_ERR(args)) {
err = PTR_ERR(args);
goto free_hdr;
}
/*
* 为与符号表相关的字符串表段在内存中分配用于映射的空间。
* sechdrs[strindex].sh_size是与符号表相关的字符串表段的大小。
* 这里分配的是一个位图,用于符号表中的符号名称的
* 映射。
*/
strmap = kzalloc(BITS_TO_LONGS(sechdrs[strindex].sh_size)
* sizeof(long), GFP_KERNEL);
if (!strmap) {
err = -ENOMEM;
goto free_mod;
}
/*
* 查找当前要加载的模块是否已经存在,如果存在,则
* 跳转到free_mod标签处。
*/
if (find_module(mod->name)) {
err = -EEXIST;
goto free_mod;
}
mod->state = MODULE_STATE_COMING;
/* Allow arches to frob section contents and sizes. */
/*
* err总是为0
*/
err = module_frob_arch_sections(hdr, sechdrs, secstrings, mod);
if (err < 0)
goto free_mod;
/*
* 如果存在.data.percpu段,则为该段在内存中分配空间。
* 分配成功后,移除SHF_ALLOC标志,并且初始化module实例
* 的percpu成员。
*/
if (pcpuindex) {
/* We have a special allocation for this section. */
percpu = percpu_modalloc(sechdrs[pcpuindex].sh_size,
sechdrs[pcpuindex].sh_addralign,
mod->name);
if (!percpu) {
err = -ENOMEM;
goto free_mod;
}
sechdrs[pcpuindex].sh_flags &= ~(unsigned long)SHF_ALLOC;
mod->percpu = percpu;
}
/* Determine total sizes, and put offsets in sh_entsize. For now
this is done generically; there doesn't appear to be any
special cases for the architectures. */
/*
* 对core section和init section中的大小及代码段的信息进行
* 统计
*/
layout_sections(mod, hdr, sechdrs, secstrings);
/*
* 处理符号表中的符号,返回值是core section尾部的
* 符号表的偏移。
*/
symoffs = layout_symtab(mod, sechdrs, symindex, strindex, hdr,
secstrings, &stroffs, strmap);
/* Do the allocs. */
/*
* 为core section分配内存,初始化后存储在module实例
* 的module_core成员中。
*/
ptr = module_alloc_update_bounds(mod->core_size);
/*
* The pointer to this block is stored in the module structure
* which is inside the block. Just mark it as not being a
* leak.
*/
kmemleak_not_leak(ptr);
if (!ptr) {
err = -ENOMEM;
goto free_percpu;
}
memset(ptr, 0, mod->core_size);
mod->module_core = ptr;
/*
* 为init section分配内存,初始化后存储在module实例
* 的module_init成员中。
*/
ptr = module_alloc_update_bounds(mod->init_size);
/*
* The pointer to this block is stored in the module structure
* which is inside the block. This block doesn't need to be
* scanned as it contains data and code that will be freed
* after the module is initialized.
*/
kmemleak_ignore(ptr);
if (!ptr && mod->init_size) {
err = -ENOMEM;
goto free_core;
}
memset(ptr, 0, mod->init_size);
mod->module_init = ptr;
/* Transfer each section which specifies SHF_ALLOC */
DEBUGP("final section addresses:\n");
/*
* 遍历段首部表,拷贝需要占用内存的段到
* init section 或core section,并且调整各个段的运行
* 时地址。
*/
for (i = 0; i < hdr->e_shnum; i++) {
void *dest;
/*
* 如果当前段执行时不占用内存,
* 则不处理
*/
if (!(sechdrs[i].sh_flags & SHF_ALLOC))
continue;
/*
* 如果段首部的sh_entsize的最高位设置的话,
* 表示该段属于init section,则从module_init开始的内存中获取
* 当前段应该存储的地址,否则从module_core开始的内存
* 中获取当前段应该存储的地址。
*/
if (sechdrs[i].sh_entsize & INIT_OFFSET_MASK)
dest = mod->module_init
+ (sechdrs[i].sh_entsize & ~INIT_OFFSET_MASK);
else
dest = mod->module_core + sechdrs[i].sh_entsize;
/*
* 将当前段的内容从ELF文件头拷贝到指定的
* 段(init section或core section)中
*/
if (sechdrs[i].sh_type != SHT_NOBITS)
memcpy(dest, (void *)sechdrs[i].sh_addr,
sechdrs[i].sh_size);
/* Update sh_addr to point to copy in image. */
/*
* 更改段的运行时地址,sh_addr原先存储的地址是
* 相对于ELF文件头的地址
*/
sechdrs[i].sh_addr = (unsigned long)dest;
DEBUGP("\t0x%lx %s\n", sechdrs[i].sh_addr, secstrings + sechdrs[i].sh_name);
}
/* Module has been moved. */
mod = (void *)sechdrs[modindex].sh_addr;
kmemleak_load_module(mod, hdr, sechdrs, secstrings);
#if defined(CONFIG_MODULE_UNLOAD) && defined(CONFIG_SMP)
/*
* 初始化多处理下用于引用计数的refptr成员
*/
mod->refptr = percpu_modalloc(sizeof(local_t), __alignof__(local_t),
mod->name);
if (!mod->refptr) {
err = -ENOMEM;
goto free_init;
}
#endif
/* Now we've moved module, initialize linked lists, etc. */
/*
* 初始化卸载模块时的处理
*/
module_unload_init(mod);
/* add kobject, so we can reference it. */
/*
* 在sysfs中创建模块对应的对象,可以在通过/sys/module/module_name
* 查看。
*/
err = mod_sysfs_init(mod);
if (err)
goto free_unload;
/* Set up license info based on the info section */
/*
* 从.modinfo段获取license对应的值,检查是否兼容
*/
set_license(mod, get_modinfo(sechdrs, infoindex, "license"));
/*
* ndiswrapper is under GPL by itself, but loads proprietary modules.
* Don't use add_taint_module(), as it would prevent ndiswrapper from
* using GPL-only symbols it needs.
*/
if (strcmp(mod->name, "ndiswrapper") == 0)
add_taint(TAINT_PROPRIETARY_MODULE);
/* driverloader was caught wrongly pretending to be under GPL */
if (strcmp(mod->name, "driverloader") == 0)
add_taint_module(mod, TAINT_PROPRIETARY_MODULE);
/* Set up MODINFO_ATTR fields */
/*
* 根据.modinfo段设置模块信息。
*/
setup_modinfo(mod, sechdrs, infoindex);
/* Fix up syms, so that st_value is a pointer to location. */
/*
* 解决当前模块对其他模块的符号引用问题,
* 并找到符号对应的值的地址
*/
err = simplify_symbols(sechdrs, symindex, strtab, versindex, pcpuindex,
mod);
if (err < 0)
goto cleanup;
/* Now we've got everything in the final locations, we can
* find optional sections. */
/*
* 获取__param段的运行时地址,及其存储的
* 对象的个数。
*/
mod->kp = section_objs(hdr, sechdrs, secstrings, "__param",
sizeof(*mod->kp), &mod->num_kp);
/*
* 获取__ksymtab段的运行时地址,及其存储的
* 对象的个数。
*/
mod->syms = section_objs(hdr, sechdrs, secstrings, "__ksymtab",
sizeof(*mod->syms), &mod->num_syms);
/*
* 获取__kcrctab段的运行时地址。
*/
mod->crcs = section_addr(hdr, sechdrs, secstrings, "__kcrctab");
/*
* 获取__ksymtab_gpl段的运行时地址,及其存储的
* 对象的个数。
*/
mod->gpl_syms = section_objs(hdr, sechdrs, secstrings, "__ksymtab_gpl",
sizeof(*mod->gpl_syms),
&mod->num_gpl_syms);
/*
* 获取__kcrctab_gpl段的运行时地址。
*/
mod->gpl_crcs = section_addr(hdr, sechdrs, secstrings, "__kcrctab_gpl");
/*
* 获取__ksymtab_gpl_future段的运行时地址,及其存储的
* 对象的个数。
*/
mod->gpl_future_syms = section_objs(hdr, sechdrs, secstrings,
"__ksymtab_gpl_future",
sizeof(*mod->gpl_future_syms),
&mod->num_gpl_future_syms);
/*
* 获取__kcrctab_gpl_future段的运行时地址。
*/
mod->gpl_future_crcs = section_addr(hdr, sechdrs, secstrings,
"__kcrctab_gpl_future");
#ifdef CONFIG_UNUSED_SYMBOLS
/*
* 获取__ksymtab_unused段的运行时地址,及其存储的
* 对象的个数。
*/
mod->unused_syms = section_objs(hdr, sechdrs, secstrings,
"__ksymtab_unused",
sizeof(*mod->unused_syms),
&mod->num_unused_syms);
/*
* 获取__kcrctab_unused段的运行时地址。
*/
mod->unused_crcs = section_addr(hdr, sechdrs, secstrings,
"__kcrctab_unused");
/*
* 获取__ksymtab_unused_gpl段的运行时地址,及其存储的
* 对象的个数。
*/
mod->unused_gpl_syms = section_objs(hdr, sechdrs, secstrings,
"__ksymtab_unused_gpl",
sizeof(*mod->unused_gpl_syms),
&mod->num_unused_gpl_syms);
/*
* 获取__kcrctab_unused_gpl段的运行时地址。
*/
mod->unused_gpl_crcs = section_addr(hdr, sechdrs, secstrings,
"__kcrctab_unused_gpl");
#endif
#ifdef CONFIG_CONSTRUCTORS
/*
* 获取.ctors段的运行时地址,及其存储的
* 对象的个数。
*/
mod->ctors = section_objs(hdr, sechdrs, secstrings, ".ctors",
sizeof(*mod->ctors), &mod->num_ctors);
#endif
#ifdef CONFIG_TRACEPOINTS
/*
* 获取__tracepoints段的运行时地址,及其存储的
* 对象的个数。
*/
mod->tracepoints = section_objs(hdr, sechdrs, secstrings,
"__tracepoints",
sizeof(*mod->tracepoints),
&mod->num_tracepoints);
#endif
#ifdef CONFIG_EVENT_TRACING
/*
* 获取_ftrace_events段的运行时地址,及其存储的
* 对象的个数。
*/
mod->trace_events = section_objs(hdr, sechdrs, secstrings,
"_ftrace_events",
sizeof(*mod->trace_events),
&mod->num_trace_events);
#endif
#ifdef CONFIG_FTRACE_MCOUNT_RECORD
/* sechdrs[0].sh_size is always zero */
/*
* 获取__mcount_loc段的运行时地址,及其存储的
* 对象的个数。
*/
mod->ftrace_callsites = section_objs(hdr, sechdrs, secstrings,
"__mcount_loc",
sizeof(*mod->ftrace_callsites),
&mod->num_ftrace_callsites);
#endif
#ifdef CONFIG_MODVERSIONS
if ((mod->num_syms && !mod->crcs)
|| (mod->num_gpl_syms && !mod->gpl_crcs)
|| (mod->num_gpl_future_syms && !mod->gpl_future_crcs)
#ifdef CONFIG_UNUSED_SYMBOLS
|| (mod->num_unused_syms && !mod->unused_crcs)
|| (mod->num_unused_gpl_syms && !mod->unused_gpl_crcs)
#endif
) {
err = try_to_force_load(mod,
"no versions for exported symbols");
if (err)
goto cleanup;
}
#endif
/* Now do relocations. */
for (i = 1; i < hdr->e_shnum; i++) {
const char *strtab = (char *)sechdrs[strindex].sh_addr;
unsigned int info = sechdrs[i].sh_info;
/* Not a valid relocation section? */
/*
* 如果当前段附加的段的索引大于段的数目,
* 则info不是一个有效的索引,不做处理。
*/
if (info >= hdr->e_shnum)
continue;
/* Don't bother with non-allocated sections */
/*
* 如果段在执行过程中不占内存,则
* 不需要进行处理。
*/
if (!(sechdrs[info].sh_flags & SHF_ALLOC))
continue;
/*
* 如果当前段包含重定向表项,但是没有补齐内容
* 则调用apply_relocate来处理。(只关心64位系统)。
*/
if (sechdrs[i].sh_type == SHT_REL)
err = apply_relocate(sechdrs, strtab, symindex, i,mod);
/*
* 如果当前段包含重定向表项,但是可能有补齐内容
* 则调用apply_relocate_add来处理。
*/
else if (sechdrs[i].sh_type == SHT_RELA)
err = apply_relocate_add(sechdrs, strtab, symindex, i,
mod);
if (err < 0)
goto cleanup;
}
/* Find duplicate symbols */
/*
* 检查模块导出的符号在内核导出的或其他模块
* 导出的符号是否有重复的。
*/
err = verify_export_symbols(mod);
if (err < 0)
goto cleanup;
/* Set up and sort exception table */
/*
* 获取__ex_table段的运行时地址,及其存储的
* 对象的个数。
*/
mod->extable = section_objs(hdr, sechdrs, secstrings, "__ex_table",
sizeof(*mod->extable), &mod->num_exentries);
sort_extable(mod->extable, mod->extable + mod->num_exentries);
/* Finally, copy percpu area over. */
percpu_modcopy(mod->percpu, (void *)sechdrs[pcpuindex].sh_addr,
sechdrs[pcpuindex].sh_size);
/*
* 初始化模块中字符串表、符号表相关的成员,
* 初始化core section中的字符串表和符号表。
*/
add_kallsyms(mod, sechdrs, hdr->e_shnum, symindex, strindex,
symoffs, stroffs, secstrings, strmap);
/*
* 释放用于字符串表名称映射的位图
*/
kfree(strmap);
strmap = NULL;
if (!mod->taints) {
/*
* 处理用于debug的段,不关注这个。
*/
struct _ddebug *debug;
unsigned int num_debug;
debug = section_objs(hdr, sechdrs, secstrings, "__verbose",
sizeof(*debug), &num_debug);
if (debug)
dynamic_debug_setup(debug, num_debug);
}
err = module_finalize(hdr, sechdrs, mod);
if (err < 0)
goto cleanup;
/* flush the icache in correct context */
/*
* get_fs是用来获取当前进程的地址限制,当当前的限制是
* KERNEL_DS时,内核不会检查参数中的地址类型
*/
old_fs = get_fs();
set_fs(KERNEL_DS);
/*
* Flush the instruction cache, since we've played with text.
* Do it before processing of module parameters, so the module
* can provide parameter accessor functions of its own.
*/
/*
* flush_icache_range函数中没有任何操作,不用考虑。
*/
if (mod->module_init)
flush_icache_range((unsigned long)mod->module_init,
(unsigned long)mod->module_init
+ mod->init_size);
flush_icache_range((unsigned long)mod->module_core,
(unsigned long)mod->module_core + mod->core_size);
set_fs(old_fs);
mod->args = args;
if (section_addr(hdr, sechdrs, secstrings, "__obsparm"))
printk(KERN_WARNING "%s: Ignoring obsolete parameters\n",
mod->name);
/* Now sew it into the lists so we can get lockdep and oops
* info during argument parsing. Noone should access us, since
* strong_try_module_get() will fail.
* lockdep/oops can run asynchronous, so use the RCU list insertion
* function to insert in a way safe to concurrent readers.
* The mutex protects against concurrent writers.
*/
list_add_rcu(&mod->list, &modules);
/*
* 解析插入模块时指定的参数。
*/
err = parse_args(mod->name, mod->args, mod->kp, mod->num_kp, NULL);
if (err < 0)
goto unlink;
/*
* 在sysfs中创建模块相应的项
*/
err = mod_sysfs_setup(mod, mod->kp, mod->num_kp);
if (err < 0)
goto unlink;
/*
* 添加段属性
*/
add_sect_attrs(mod, hdr->e_shnum, secstrings, sechdrs);
/*
* 添加注解属性
*/
add_notes_attrs(mod, hdr->e_shnum, secstrings, sechdrs);
/* Get rid of temporary copy */
vfree(hdr);
trace_module_load(mod);
/* Done! */
return mod;
unlink:
/* Unlink carefully: kallsyms could be walking list. */
list_del_rcu(&mod->list);
synchronize_sched();
module_arch_cleanup(mod);
cleanup:
free_modinfo(mod);
kobject_del(&mod->mkobj.kobj);
kobject_put(&mod->mkobj.kobj);
free_unload:
module_unload_free(mod);
#if defined(CONFIG_MODULE_UNLOAD) && defined(CONFIG_SMP)
percpu_modfree(mod->refptr);
free_init:
#endif
module_free(mod, mod->module_init);
free_core:
module_free(mod, mod->module_core);
/* mod will be freed with core. Don't access it beyond this line! */
free_percpu:
if (percpu)
percpu_modfree(percpu);
free_mod:
kfree(args);
kfree(strmap);
free_hdr:
vfree(hdr);
return ERR_PTR(err);
truncated:
printk(KERN_ERR "Module len %lu truncated\n", len);
err = -ENOEXEC;
goto free_hdr;
}