Linux内核模块--笔记
2015-11-07 20:34
393 查看
Linux 内核模块
Linux模块的组成部分-模块加载函数(一般需要)
-模块卸载函数(一般需要)
-模块许可证申明(必须)
-模块参数(可选)
-模块导出符号(可选)
-模块作者等信息(可选)
示例代码
hello.c
#include <linux/init.h> #include <linux/module.h> static char *book_name = " dissecting Linux Device Driver "; static int num = 4000; /** * 实际上__init & __exit都是宏定义: * #define __init __attribute__ ((__setction__(".init.text")) * * #ifdef MODULE * #define __exit __attribute__ ((__setction__(".exit.text")) * #else * #define __exit __attribute_used_attribute__ ((__section__(".exit.text"))) * #endif * * 数据也可以被定义为__initdata & __exitdata * #define __initdata __attribute__((__section__(".init.data"))) * #define __exitdata __attribute__((__section__(".exit.data"))) * / /** *1) __init: __init 函数在链接的是时候会被放置到.init.text这个区段内,此外,所有的__init函数在区段.initcall.init中还保留一份函数指针,在初始化的时候内核通过这些函数指针调用这些__init函数,并在初始化完成之后,释放init区段(包括.init.text/.initcall.init等) *2) return value: *模块的init函数,成功则返回0;如果失败则返回一个负值,一般是定义在<linux/errno.h>中的一个错误号 *如:-ENODEV,-ENOMEM等,这样用户可以使用perror来输出对应的有意义的错误信息 */ static int __init hello_init(void) { printk(KERN_INFO "Hello world enter\n"); printk(KERN_INFO "Book name:%s\n", book_name); printk(KERN_INFO "Book num:%d\n", num); return 0; } /** * 1) 完成功能和加载函数相反,如果: * a/ 加载函数注册了xxx,卸载函数注销xxx * b/ 加载函数申请了内存,卸载函数释放内存 * c/ 加载函数申请了硬件资源(interrupt/dma/io/io memory),卸载函数释放这些硬件资源 * d/ 加载函数开启了硬件,卸载函数关闭硬件 * * 2)无返回值 */ static void __exit hello_exit(void) { printk(KERN_INFO " Hello World exit\n"); } module_init(hello_init); module_exit(hello_exit); /** * module_param(参数名,参数类型,参数读写权限) * 参数类型:byte, short, ushot, int, uint, long, ulong, charp(字符指针),bool,invbool(布尔的反) * 模块还可以有参数数组: * module_param_array(数组名,数组类型,数组长,参数读写权限) * *\sys\module 下有对应的sysfs文件节点可以查看,如果你使用的是0的权限,那么是没有对应的文件的 */ module_param(num, int, S_IRUGO); module_param(book_name, charp, S_IRUGO); /** * Linux 2.6 "\proc\kallsysms" 文件对应着内核符号表,它记录着符号以及符号所在的内存地址 * 模块可以使用如下宏调入到内核符号表中 * EXPORT_SYMBOL(symbol) * EXPORT_SYMBOL_GPL(symbol) */ int add_integar(int a, int b) { return a+b; } int sub_integar(int a, int b) { return a-b; } EXPORT_SYMBOL(add_integar); EXPORT_SYMBOL(sub_integar); /** * 模块的引用计数: * 2.6之后内核函数:int try_module_get(struct module *module); * void module_put(struct module *module); * 但是2.6内核之后一般为不同类型的设备定义了owner域; * struct module *owner;用来指向管理此设备的模块。 * 开始使用某个设备时,内核自动调用:try_module_get(dev->owner) * 停止对该设备的调用时:module_put(dev->owner) * * 作用:设备在使用时,管理该设备的模块时不能被卸载的,设备驱动工程师一般也不需要亲自调用try_module_get/module_put函数。因为设备的owner模块的计数管理由更底层的代码,如总线驱动或此类设备共用的核心模块来实现,从而简化了设备驱动开发。 */ MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); MODULE_LICENSE("Dual BSD/GPL"); MODULE_DESCRIPTION("A simple Hello World Module"); MODULE_ALIAS("a simplest module");
Makefile
# Get the kernel source dir KVERS = $(shell uname -r) # Kernel modules obj-m += hello.o # For multi file compilation. # modulename-objs := file1.o file2.o # Specify flags for the module compilation/debug. # EXTRA_CFLAGS=-g -O0 build: kernel_modules kernel_modules: make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules clean: make -C /lib/modules/$(KVERS)build M=$(CURDIR) clean
代码测试
#Make #sudo insmod hello.ko #sudo dmesg #sudo rmmod hello #sudo insmod hello.ko book_name="LDD3" num=560 查看对应/sys/modules下的sysfs参数节点 #sudo dmesg #sudo rmmod hello
扩展阅读:
__init和__exit对应的模块加载和卸载
linux-2.6.39 vim ./arch/x86/kernel/vmlinux.lds.init.data : AT(ADDR(.init.data) - 0xffffffff80000000) { (.init.data) . = ALIGN(8); __ctors_start = .; (.ctors) __ctors_end = .; (.init.rodata) . = ALIGN(8); __start_mcount_loc = .; (__mcount_loc) __stop_mcount_loc = .; . = ALIGN(8); __start_ftrace_events = .; (_ftrace_events) __stop_ftrace_events = .; . = ALIGN(8); __start_syscalls_metadata = .; (__syscalls_metadata) __stop_syscalls_metadata = .; . = ALIGN(32); __dtb_start = .; (.dtb.init.rodata) __dtb_end = .; . = ALIGN(16); __setup_start = .; (.init.setup) __setup_end = .; __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.ini t) (.initcall5.init) (.initcall5s.init) (.initcallrootfs.init) (.initcall6.init) (.initcall6s.init) (.initcall7.init) (.initcall7s.init) __initcall_end = .; __con_initcall_start = .; (.con_init call.init) __con_initcall_end = .; __security_initcall_start = .; (.security_initcall.init) __security_initcall_end = .; . = ALIGN(4); __initramfs_start = .; (.init.ramfs) . = ALIGN(8); (.init.ramfs .info) }
可见在__early_initcall_end和__initcall_end之间存在:
(见:include/linux/init.h和/arch/x86/kernel/vmlinux.lds)
core_initcall(fn) –>> .initcall1.init
postcore_initcall(fn) –>>.initcall2.init
arch_initcall(fn) —>> .initcall3.init
subsys_initcall(fn) —>> .initcall4.init
fs_initcall(fn) —>> .initcall5.init
device_initcall(fn) —>> .initcall6.init
late_initcall(fn) —>> .initcall7.init
参考:include/linux/init.h
#ifndef _LINUX_INIT_H #define _LINUX_INIT_H #include <linux/compiler.h> ... /* initcalls are now grouped by functionality into separate * subsections. Ordering inside the subsections is determined * by link order. * For backwards compatibility, initcall() puts the call in * the device init subsection. * * The `id' arg to __define_initcall() is needed so that multiple initcalls * can point at the same handler without causing duplicate-symbol build errors. */ #define __define_initcall(level,fn,id) \ static initcall_t __initcall_##fn##id __used \ __attribute__((__section__(".initcall" level ".init"))) = fn /* * Early initcalls run before initializing SMP. * * Only for built-in code, not modules. */ #define early_initcall(fn) __define_initcall("early",fn,early) /* * A "pure" initcall has no dependencies on anything else, and purely * initializes variables that couldn't be statically initialized. * * This only exists for built-in code, not for modules. */ #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 __initcall(fn) device_initcall(fn) //module载入的方式,用以下两个宏定义 #define module_init(x) __initcall(x); #define module_exit(x) __exitcall(x); #else /* MODULE */ //这些是不建议使用的 /* Don't use these in modules, but some people do... */ #define early_initcall(fn) module_init(fn) #define core_initcall(fn) module_init(fn) #define postcore_initcall(fn) module_init(fn) #define arch_initcall(fn) module_init(fn) #define subsys_initcall(fn) module_init(fn) #define fs_initcall(fn) module_init(fn) #define device_initcall(fn) module_init(fn) #define late_initcall(fn) module_init(fn) #endif /* _LINUX_INIT_H */
调用流程:
分为静态的加载还是模块动态加载:1)静态加载(系统启动):
如:
– 编译链接:
编译进内核:
以pure_initcall举例:
#define pure_initcall(fn) __define_initcall("0",fn,0) | #define __define_initcall("0",fn,0) \ static initcall_t __initcall_##fn##id __used \ __attribute__((__section__(".initcall" "0".init"))) = fn | vmlinux.lds中会将该函数指针添加到对应的.initdata段中
编译成模块:
#define module_init(x) __initcall(x); | #define __initcall(fn) device_initcall(fn) | 看来module_init中直接对应了.initcall6 #define device_initcall(fn) __define_initcall("6",fn,6) | #define __define_initcall("6",fn,6) \ static initcall_t __initcall_##fn##id __used \ __attribute__((__section__(".initcall" "6".init"))) = fn | vmlinux.lds中会将该函数指针添加到对应的.init.data section中
– 系统启动:
init/main.c ... | do_initcalls(void) | do_one_initcall(*fn) //fn from __initcall_start[] to __initcall_end[] 调用对应的initcall函数,即.init.data section中定义的函数指针
2)动态加载(模块):
–编译链接
使用insmod或者modprobe方式载入模块
问题来了,这里模块载入的时候,系统当然已经启动了,就不存在调用initdata section的可能了,那么模块载入之后init/exit函数何时被调用了呢?
insmod | sys_init_module //系统调用 | kernel/modules.c: /* This is where the real work happens */ SYSCALL_DEFINE3(init_module, void __user *, umod, unsigned long, len, const char __user *, uargs) | /* Start the module */ if (mod->init != NULL) ret = do_one_initcall(mod->init); | init/main.c: do_one_initcall(fn)
遗留问题:__exit对应的函数虽然也写到了对应的.exit.data段中,不过还不清楚具体如何调用的机制?或者说根本就没有调用,只是在使用insmod动态加载模块的时候,如果rmmod,会直接调用对应注册的mod->exit函数???
具体细节问题还有待以后学习,万事开头难,加油:
参考博客:
http://blog.csdn.net/maopig/article/details/7409870
http://blog.csdn.net/lihaoweiv/article/details/6601009
相关文章推荐
- Linux socket 初步
- linux lsof详解
- linux 文件权限
- Linux 执行数学运算
- 10 篇对初学者和专家都有用的 Linux 命令教程
- Linux 与 Windows 对UNICODE 的处理方式
- Ubuntu12.04下QQ完美走起啊!走起啊!有木有啊!
- 解決Linux下Android开发真机调试设备不被识别问题
- 运维入门
- 运维提升
- Linux 自检和 SystemTap
- Ubuntu Linux使用体验
- c语言实现hashmap(转载)
- Linux 信号signal处理机制
- linux下mysql添加用户
- Scientific Linux 5.5 图形安装教程
- 基于 Linux 集群环境上 GPFS 的问题诊断
- 谁是桌面王者?Win PK Linux三大镇山之宝