概述:
linux采用整体时的内核结构,一般不支持动态增加新功能,为此linux提供了一套叫做模块的全新机制,可以根据需要在不重新编译连接内核的情况下将新的安装模块动态的插入/移除运行态的内核,使得内核映像保持最小化,从而达到灵活性及扩展性.
模块化并不仅局限于驱动模块同时也支持文件系统,但常见与驱动或与驱动紧密联系的模块,内核提供了4个系统调用以支持动态安装卸载:create_module(),init_module(),query_module(),delete_modlue().通常用户采用/sbin/insmod,/sbin/rmmod来安装卸载而非直接与上述系统调用打交道.insmod应用源码参考:insmod.c
系统调用主要工作:
1.读取.o(.ko实际是.o的打包集合)入内核态并获取相关模块的符号(函数和变量名)信息
符号信息:模块自身定义,引用外部模块定义
针对每个符号,记录下符号名及符号地址,如是外部符号(则外部符号所在模块为当前模块的依赖模块,载入时需先载入,模块一旦载入后会在内核符号表内添加该模块的符号表)则遍历内核符号表找寻当前符号的模块拿到符号地址(调用query_module()),单个符号名及符号地址封装为符号信息结构体(struct module_symbol),所有符号信息加入数组形成单个模块的符号表(struct module->syms,数组大小由struct module->nsyms指明)
模块依赖信息:
模块A引用模块B的符号,称为A依赖B或B被A引用,此时会将这种依赖信息也记录下来便于运行时寻找外部符号地址并将该地址指令调入cpu运行,模块的依赖信息还有一个用处就是卸载(如卸载A时发现B还被其他模块引用,则此时B不能被卸载直到B不再被其他模块引用才能卸载)
模块结构体:
struct module_symbol //符号信息
{
unsigned long value;
const char *name;
};
struct module_ref //依赖信息
{
struct module *dep; /* "parent" pointer */
struct module *ref; /* "child" pointer */
struct module_ref *next_ref;
};
/* TBD */
struct module_persist;
struct module //模块信息
{
unsigned long size_of_struct; /* == sizeof(module) */
struct module *next; //module_list链接地址
const char *name;
unsigned long size;
union
{
atomic_t usecount; //使用状态,如在使用则不能卸载
long pad;
} uc; /* Needs to keep its size - so says rth */
unsigned long flags; /* AUTOCLEAN et al */
unsigned nsyms; //符号信息数组大小
unsigned ndeps; //deps依赖模块数组大小
struct module_symbol *syms;
struct module_ref *deps;
struct module_ref *refs;
int (*init)(void); //模块初始化函数 - hello_mod_init
void (*cleanup)(void);//模块卸载函数 - hello_mod_exit
const struct exception_table_entry *ex_table_start;
const struct exception_table_entry *ex_table_end;
#ifdef __alpha__
unsigned long gp;
#endif
/* Members past this point are extensions to the basic
module support and are optional. Use mod_member_present()
to examine them. */
const struct module_persist *persist_start;
const struct module_persist *persist_end;
int (*can_unload)(void);
int runsize; /* In modutils, not currently used */
const char *kallsyms_start; /* All symbols for kernel debugging */
const char *kallsyms_end;
const char *archdata_start; /* arch specific data for module */
const char *archdata_end;
const char *kernel_data; /* Reserved for kernel internal use */
};
nm命令用法:
nm [option(s)] [file(s)]
有用的options:
-A 在每个符号信息的前面打印所在对象文件名称;
-C 输出demangle过了的符号名称;
-D 打印动态符号;
-l 使用对象文件中的调试信息打印出所在源文件及行号;
-n 按照地址/符号值来排序;
-u 打印出那些未定义的符号;
常见的符号类型:
A 该符号的值在今后的链接中将不再改变;
B 该符号放在BSS段中,通常是那些未初始化的全局变量;
D 该符号放在普通的数据段中,通常是那些已经初始化的全局变量;
T 该符号放在代码段中,通常是那些全局非静态函数;
U 该符号未定义过,需要自其他对象文件中链接进来;
W 未明确指定的弱链接符号;同链接的其他对象文件中有它的定义就用上,否则就用一个系统特别指定的默认值。
nm命令查看当前hello.ko符号:
root@leon: /home/workplace/sc2410_android/kernel/drivers/hello# nm hello.ko -A
hello.ko:00000000 t $a
hello.ko:00000038 t $a
hello.ko:00000170 t $a
hello.ko:00000250 t $a
hello.ko:00000000 t $a
hello.ko:000002b0 t $a
hello.ko:00000000 t $a
hello.ko:00000000 t $a
hello.ko: U __aeabi_unwind_cpp_pr0
hello.ko: U __aeabi_unwind_cpp_pr1
hello.ko: U cdev_add
hello.ko: U cdev_del
hello.ko: U cdev_init
hello.ko: U __class_create
hello.ko: U class_destroy
hello.ko:00000000 T cleanup_module
hello.ko: U __copy_from_user
hello.ko: U __copy_to_user
hello.ko:0000002c t $d
hello.ko:00000000 r $d
hello.ko:00000000 r $d
hello.ko:00000144 t $d
hello.ko:00000238 t $d
hello.ko:000002a4 t $d
hello.ko:000000fc t $d
hello.ko:00000000 r $d
hello.ko:000002ec t $d
hello.ko:0000011c t $d
hello.ko:00000000 r $d
hello.ko:00000054 t $d
hello.ko:00000000 r $d
hello.ko:00000000 d $d
hello.ko:00000000 b $d
hello.ko:00000010 N $d
hello.ko:00000000 d $d
hello.ko:00000000 r $d
hello.ko:00000000 r $d
hello.ko:00000040 r $d
hello.ko: U device_create
hello.ko: U device_destroy
hello.ko:00000024 b devnum
hello.ko: U down_interruptible
hello.ko:00000028 b hello_cdev
hello.ko:00000064 b hello_class
hello.ko:00000000 t hello_mod_exit
hello.ko:00000000 D hello_mod_fops
hello.ko:00000000 t hello_mod_init
hello.ko:00000038 t hello_mod_ioctl
hello.ko:00000250 t hello_mod_open
hello.ko:000002b0 t hello_mod_read
hello.ko:00000000 t hello_mod_read.part.1
hello.ko:00000000 t hello_mod_release
hello.ko:00000170 t hello_mod_write
hello.ko:00000018 b inbuffer
hello.ko:00000000 T init_module
hello.ko:00000064 b __key.14649
hello.ko: U kfree
hello.ko: U kmalloc_caches
hello.ko: U kmem_cache_alloc_trace
hello.ko:00000004 b langtype
hello.ko: U memset
hello.ko: U __memzero
hello.ko:00000040 r __module_depends
hello.ko:00000000 b open_count
hello.ko:00000020 b outbuffer
hello.ko: U printk
hello.ko: U _raw_spin_lock
hello.ko: U _raw_spin_unlock
hello.ko: U register_chrdev_region
hello.ko:00000008 b sem
hello.ko:0000001c b spin
hello.ko: U sprintf
hello.ko:00000000 D __this_module
hello.ko:00000034 r __UNIQUE_ID_author0
hello.ko:0000000c r __UNIQUE_ID_description1
hello.ko:00000049 r __UNIQUE_ID_intree1
hello.ko:00000000 r __UNIQUE_ID_license2
hello.ko:00000052 r __UNIQUE_ID_vermagic0
hello.ko: U unregister_chrdev_region
hello.ko: U up
执行insmod hello.ko后才看内核符号表:
00000000 t $a [hello]
shell@rk322x_box#cat /proc/pkallsyms
......
00000000 t hello_mod_release [hello]
00000000 t $d [hello]
00000000 r $d [hello]
00000000 r $d [hello]
00000000 t $a [hello]
00000000 t hello_mod_ioctl [hello]
00000000 t $d [hello]
00000000 t $a [hello]
00000000 t hello_mod_write [hello]
00000000 t $d [hello]
00000000 t $a [hello]
00000000 t hello_mod_open [hello]
00000000 t $d [hello]
00000000 t $a [hello]
00000000 t hello_mod_read.part.1 [hello]
00000000 t $d [hello]
00000000 r $d [hello]
00000000 t $a [hello]
00000000 t hello_mod_read [hello]
00000000 t $d [hello]
00000000 r $d [hello]
00000000 t $a [hello]
00000000 t hello_mod_exit [hello]
00000000 t $d [hello]
00000000 r $d [hello]
00000000 d $d [hello]
00000000 b $d [hello]
00000000 b open_count [hello]
00000000 b langtype [hello]
00000000 b sem [hello]
00000000 b inbuffer [hello]
00000000 b spin [hello]
00000000 b outbuffer [hello]
00000000 b devnum [hello]
00000000 b hello_cdev [hello]
00000000 b __key.14649 [hello]
00000000 b hello_class [hello]
00000000 d $d [hello]
00000000 r $d [hello]
00000000 d __this_module [hello]
00000000 t cleanup_module [hello]
00000000 d hello_mod_fops [hello]
......
理解上述信息后我们发现再来看4个系统调用即简单明了
create_module():在用户态创建module结构体,并将解析出来的符号表信息对应填充
init_module():内核态创建module结构体,并将上步的用户态module拷贝过来,将module挂入内核模块链表(module_list),并执行module->init()(即hello_mod_init())执行具体模块初始化动作
query_module():针对外部符号,调用该函数查找内核符号表并获取符号信息
delete_module():调用module->cleanup()(即hello_mod_exit())释放具体模块资源,删除模块结构体并从module_list脱链