Linux内核模块--笔记

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
为了透彻理解Linux的工作机理,以及为何它在各种系统上能顺畅运行,你需要深入到内核的心脏。cPu与外部世界的所有交互活动都是由内核处理的,哪些程序会分享处理器的时间,以什么样的顺序来分享。内核不遗余力地管理有限的内存,以使数以千计的进程有效地共享系统资源。内核还精心组织数据传送,使得 cPu不再受限于慢速硬盘。 《深入理解Linux内核》第三版将引领你畅游内核中用到的最主要数据结构、算法和编程技巧。如果你的确想了解计算机内部的实现机理,那么作者透过现象探寻本质,提供了颇有价值的深入分析。本书针对具体的Intel平台,讨论了其重要特征,逐行剖析了相关的代码片段。但是,本书涵盖的内容不仅仅局限于代码的机理,还解释了Linux运作方式的理论支撑。 本书第三版涵盖Linux 2.6,从中可以看到几乎内核每个子系统都有相当大的变化,首当其冲的是内存管理和块设备部分。本书集中讨论了如下内容: 内存管理,包括文件缓冲、进程交换以及直接内存访问(DMA) 虚拟文件系统层和第二及第三扩展文件系统 进程创建及调度   信号、中断及设备驱动程序的主要接口   定时   内核中的同步   进程间通信(IPC)   程序执行   本书将使你熟悉Linux所有的内在工作机理,但本书不仅仅是一种学术演练。你将了解到什么条件会促使Linux产生最佳性能,你还会看到,Linux 在各种环境下如何满足进程调度、文件访问及内存管理期间系统提出的快速响应要求。本书有助于你充分展现Linux系统的魅力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值