1.模块的含义
linux 是采用模块化的方式构建的,允许内核在运行时动态地向其中插入或从中删除代码,这些代码(包扩函数,数据,模块入口函数,模块出口函数)被一并组合
在一个单独的二进制镜像,就是所谓的可装载内核模块。
模块可以是基本的内核镜像尽可能小,同时可以方便地对新功能进行调试,还可以实现热插拔(后续会学习如何实现设备的热插拔功能,暂时无需深究),和内核的核心子系统不一样,模块文件需要有入口点和出口点。
模块与应用程序的区别:
a.模块和库函数类似,一个模块通常包含若干函数和数据,每个函数提供特定的服务,决定完成哪些功能则是应用程序的事情,模块只是用来为应用程序提供服务。也就是策略与机制的关系。模块提供机制,应用程序根据模块提供的机制实现策略。
b.模块的注册函数只是把模块注册到内核中,模块中的函数如何被调用是应用程序的事情。
c.应用程序退出是可以不管资源的释放和其他清理工作(通常我们还是会处理),但模块的退出函数必须仔细撤销初始化函数所作的一切。
d.模块被链接到内核,它只能调用哪些被内核导出的函数(以后会讲到如何导出内核函数),使用的头文件是内核源代码中include/目录下的头文件,如:
#include<linux/init.h> ======>include/linux/init.h
e.模块不会和任何库链接
f.模块编程必须慎重考虑并发问题,通常必须是可重入的。
2.模块的编译,装载和卸载。
请参考linux内核稳定,有非常详细的描述。
Documentation/kbuild/modules.txt
因为文档是英文,我简单描述一下。
a.首先运行makefile编译模块,简单分析make的编译参数
make -C 将目录改变到内核源代码所在目录,其中有内核的顶层makefile文件,
M=‘pwd’ 要求顶层makefile在构造modules目标之前返回到模块的源代码目录。
modules 只想obj-m变量中设定的模块,就是告诉编译系统,此make 是要编译一个内核模块。
内核转载和卸载的命令
insmod
46 通过insmod将模块的代码和数据安装到系统正在运行的内核中。一旦安装成功,则模块就可以访问内核的公共符号(包括函数和变量)。insmod和ld类似,但insmod不
会修改模块的磁盘文件,只会修改内存中的副本。
rmmod
通过rmmod卸载模块,注意,只有root权限才可以安装和卸载模块。如果内核认为模块仍然处于使用状态,有可能无法卸载。在编译内核时需要将Loadable module s upport-->Module unloading选中,否则内核不支持模块卸载。
lsmod
lsmod程序列出了当前装载到内核中的所以模块,还列出了其他信息,如其他模块是否在使用某个特点模块。lsmod通过读取/proc/modules获得这些信息。
上面的基本知识大家有个大概的印象即可,下面示例分析一下,一些程序学习的起源都是Hello world, 那么我们也从Hello world开始,创建一个简单的Hello world 模块。
首先构建模块编译的makefile文件
#Makefile obj-m = hello.o KERN = /share/arm/linux-3.2 #基于源代码树的模块编译,源代码树必须要先编译完成后,才能进行模块编译 # KERN = /lib/modules/`uname -r`/build/ ##如果你在x86平台的linux系统,直接可以编译基于x86平台的模块 all: make -C $(KERN) M=`pwd` modules clean: make -C $(KERN) M=`pwd` modules clean rm -rf modules.order看看首个模块代码helloWorld.c
#include <linux/init.h> #include <linux/module.h> //模块必须的两个头文件 MODULE_LICENSE("GPL"); //GPL协议要求 MODULE_AUTHOR("MJ"); //option 描述作者信息 MODULE_DESCRIPTION("just for test"); //option 描述模块的 static __init int hello_init(void) { printk(KERN_EMERG,"hello kernel !\n"); //printk 类似应用程序的printf, 用法稍有不同,可百度一下。 } static __exit void hello_exit(void) { printk(KERN_EMERG,"bye, cruel world!\n"); } module_init(hello_init); module_exit(hello_exit);通常printk不能直接输出到终端,需要看终端的配置及你printk的打印机别。 但是一定会输出到log文件,你可以用dmesg 命令查看log message。
printk()
a.介绍
printk是内核中非常好用的一种信息输出方法,和printf很象,不同的是printk可以附加不同的日志级别,从而可以根据消息的严重程度分类。如:
printk(KERN_DEBUG "Here I am: %s:%i\n", __FILE__, __LINE__);
b.日志级别
printk()定义在/kernel/printk.c中,消息级别和原型声明都在<linux/kernel.h>中。缺省级别在printk.c中指定。
支持的日志级别:
KERN_EMERG: 紧急情况
KERN_ALERT: 需要立即被注意到的错误
KERN_CRIT: 临界情况
KERN_ERR: 错误
KERN_WARNING: 警告
KERN_NOTICE: 注意
KERN_INFO: 非正式的消息
KERN_DEBUG: 调试信息(冗余信息)
c.printk向用户空间的输出
两个守护进程syslogd和klogd用于处理日志信息。小于控制台级别的信息会被输出到console,而大于控制台级别的信息不输出,只是写入/var/log/messages和/proc/kmsg中。可以通过dmesg查看。
修改控制台级别:
$>echo 8 > /proc/sys/kernel/printk
或
$>klogd -c 8
/*需要先关闭klogd,用ps -C klogd 查出这个进程的pid,然后kill)
printk函数将消息写到一个长度为__LOG_BUF_LEN字节的循环缓冲区中,然后唤醒任何睡眠在syslog系统调用或正在读取/proc/kmsg的进程。循环缓冲区满了以后,会绕>回开始处填写新的内容。printk()可以在中断中调用。
当printk()的调用频率过高时,还可以通过printk_ratelimit()限制输出。
(7)模块的初始化和清除
<linux/init.h>中包含了和模块的初始化和清除相关的信息。
初始化
a.module_init
用于注册模块的初始化函数
module_init(hello_init);
b.__init
用于修饰模块的初始化函数,表明该函数只在模块的初始化期间使用。初始化完成后,这个函数所占的内存会被释放。
static int __init hello_init(void)
{
...
}
c.__initdata
用于修饰模块的初始化数据结构。
int __initdata array[100];
清除
a.module_exit
用于注册模块的清除函数
module_exit(hello_exit);
b.__exit
static void __exit hello_exit(void)
{
...
清除函数没有返回值,被__exit修饰的模块只能用于模块卸载(编译器会把该函数放在特殊的ELF段中),其他时候调用清除函数都是错的。
本文对linux 模块进行基本的介绍,让大家对linux模块有个基本的认识。
下一篇文章将对模块的其他特性进行讲解
linux 驱动开发-模块的构建,布布扣,bubuko.com
原文地址:http://blog.csdn.net/mj5742356/article/details/31740487