您的位置:首页 > 运维架构 > Linux

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 驱动