首页 > 代码库 > linux 驱动开发-模块的构建

linux 驱动开发-模块的构建

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模块有个基本的认识。

下一篇文章将对模块的其他特性进行讲解