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
通常printk不能直接输出到终端,须要看终端的配置及你printk的打印机别。 可是一定会输出到log文件,你能够用dmesg 命令查看log message。#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()
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模块有个主要的认识。
下一篇文章将对模块的其它特性进行解说