4412的驱动内核模块

内核模块

序言

linux驱动开发的特点

1.linux驱动是内核级别的开发,驱动程序的任何问题都会引起整个系统崩溃。(若驱动对一个非法指针进行解引用,通常会引起整个内核的崩溃)
2.驱动程序通常要进行中断处理。在中断上下文(或类似的原子上下文)中的编程有严格的限制,处理不好也会内核崩溃。
3.驱动程序有更多的并发环境需要考虑。比如中断、多处理器下的系统编程。
4.驱动程序是被动接受上层调用的代码,为上层提供服务的代码,所以在驱动中有很多注册和注销函数。
5.驱动的关键就是构造核心的数据结构然后注册到内核。主要学习这些核心的数据结构和与之向相关的一套API
6.内核源码中有同一类设备驱动的很好的实现范例,面对不熟悉的驱动框架,可以参考内核源码,从而掌握该类驱动的开发。
7.驱动能独立于内核之外,并能动态加载卸载。
8.有一类设备访问按字节流方式进行,访问单位可小到字节,对这类设备叫字符设备驱动。
在类UNIX系统中,设备也被当做文件来对待,或者说将设备抽象成了文件。这样就可以统一应用层代码对普通文件和设备文件的访问接口。

1.1 内核模块程序

内核模块就是被单独编译的一段内核代码,它可以在需要的时候动态加载到内核,从而动态地增加内核的功能。只要新加载的驱动不会使内核崩溃,就不需要重启系统。

/*vser.c*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
int init_module(void)
{
	printk(“module init\n”);
	return 0;
}
void cleanup_module(void)
{
	printk("cleanup module\n");
}

一个模块程序都要直接或间接包含上述三个头文件。
模块初始化函数和模块清除函数
模块初始化函数:进行内存分配、驱动注册等
模块清除函数:完成内存释放、主动注销

ifeq($(KERNELRELEASE), )
ifeq($(ARCH),arm)
KERNELDIR ?=/home/
ROOTFS ?=/nfs/roofts
else
KERNELDIR ?=/lib/modules/$(shell uname -r )/build
endif
PWD:=$(shell pwd)
modules:
	$(MAKE) -C $(KERNELDIR)  M= $(PWD) modules
modules_install:
	$(MAKE) -C $(KERNELDIR)  M=$(PWD) INSTALL_MOD_PATH=$(ROOTFS)  modules_install
clean:
	rm -rf *.o *.ko .*.cmd *.mod .*modules.order Module .symvers .tmp_versions
else
obj-m=vser.o
endif
	

make 
make module_install
make ARCH=arm
make ARCH=arm_modules_install

若要编译运行在ARM目标机上的驱动,则用上述命令。

1.2 内核相关工具

1.模块加载

insmod vser.ko
dmesg
depmod
modprobe vser

使用modprobe 不指定路径和后缀。
2.模块信息
在安装模块并运行了depmod命令之后,可以不指定路径和后缀,也可查看某一指定.ko文件:

modinfo vser

3.模块卸载:

rmmod vser

内核模块的一般形式

需要添加

MODULE_LICENSE("GPL");

代表相应的许可证协议。
module_init和module_exit是两个宏,分别用于指定init_module的函数别名是vser_init,以及cleanup_module的别名vser_exit.
为避免因为重名而带来重复定义的问题,函数前可加static关键字修饰,经过static修饰后的函数链接属性为内部,这就是几乎所有的驱动程序函数前要加static的原因。
__init 和__exit每次仅会执行一次

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
static int __init vser_init(void)
{
	printk("vser_init\n");
	return 0;
}

static void __exit vser_exit(void)
{
	printk("vser_exit\n");
}
module_init(vser_init);
module_exit(vser_exit);
MODULE_LICENSE("GPL");

2.4将多个源文件编译成一个内核模块

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
extern void bar(void);
static int __init vser_init(void)
{
	printk("vser_init\n");
	bar();
	return 0;
}

static void __exit vser_exit(void)
{
	printk("vser_exit\n");
}
module_init(vser_init);
module_exit(vser_exit);
MODULE_LICENSE("GPL");
#include <linux/kernel.h>
void bar(void)
{
	printk("bar\n");
}

在Makelfie中添加

obj-m=vser.o
vser-objs=foo.o bar.o

2.5 内核模块参数

模块的初始化函数在模块被加载时调用,但是该函数不接受参数,要想在模块加载时对模块的行为进行控制,就不方便。模块参数允许用户通过命令行指定参数值,在模块加载过程中,加载程序会得到命令行参数,并转换成相应类型的值,然后赋值给对应变量。

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
static int baudrate=9600;
static int port[4]={0,12,3}static char *name="vser";
module_param(baudrate,int,S_IRUGO);
module_param_array(port,int,NULL,S_IRUGO);
module_param(name,charp,S_IRUGO);
static int __init vser_init(void)
{
	int i;
	printk("vser_init\n");
	printk("baudrate:%d\n",baudrate);
	printk("port: ");
	for(i=0;i<ARRAY_SIZE(port);i++){
	printk("%d",port[i]);
}
printk("name:%s \n",name);
return 0;
}
static void __exit vser_exit(void)
{
	printk("vser_exit\n");
}
module_init(vser_init);
MODULE_LICENSE("GPL");

module_param(name,type,perm);
module_param_array(name.type,nump,perm);
name :变量名
type:变量或数组元素的类型
nump:变量或数组个数的指针,可选。
perm:对应文件权限(和普通文件的权限一样)
modprobe vser baudrate=115200 port=1,2,3,4 name=“virtural-serial”
参看sysfs文件系统下的内容,可发现和模块参数对应的文件及相应权限

ls -l /sys/module/vser/parameters
cat /sys/module/vser/parameters/port

2.6 内核模块依赖

使用nm命令查看模块目标文件的符号信息时,可以看到vser_exit和vser_init的符号类型是t表示是函数;printk的符号类型为U,表示它是一个未决符号

EXPORT_SYMBOL(printk);

通过一个叫做EXPORT_SYMBOL的宏将printk导出,目的是为动态加载的模块提供printk的地址信息。可以发现,内核将会有大量的符号导出,为模块提供了丰富的基础设施。
若一个模块需要提供全局变量或函数给另外的模块使用,那么就需要将这些符号导出。这在一个驱动程序代码调用另一个驱动程序代码时比较常见。故模块间形成依赖关系,使用导出符号的模块将会依赖于导出符号的模块。

vser.c
extern int expval;
extern void expfun(void);
dep.c
#include <linux/kernel.h>
#include <linux/module.h>
static int expval=5;
EXPORT_SYMBOL(expval);
static void expfun(void)
{
	printk("expfun");
}
EXPORT_SYMBOL_GPL(expfun);
MODULE_LICENSE("GPL");
Makefile
obj-m+=dep.o

dep.c里定义了一个全局变量expval,定义了一个函数expfun,并分别使用
EXPORT_SYMBOL和·EXPORT_SYMBOL_GPL导出。在vser.c中首先用extern 声明这个函数和变量。在Makefile中添加对dep模块的编译。
几点说明:
1.若使用insmod命令加载模块,必须先加载dep模块,再加载vser模块。
2.modprobe 可以自动加载被依赖的模块。归功于depmod命令。
3.depmod命令将生成的模块依赖信息保存在/lib/modules/3.13.0-32-generic/modules.dep

cat /lib/modules/3.13.0-32-generic/modules.dep
......
extra/vser.ko:extra /dep.ko
extra/dep.ko

2.两个模块存在依赖关系,若分别编译两个模块,再加载,将报“invaild parameters”的错误。解决这个问题的方法是将两个模块放在一起编译,或将dep模块放在内核源码中,先在源码下编译,再编译vser。
3.卸载时要先卸载vser模块,再卸载dep模块。

2.7 关于内核模块的进一步讨论

在一个版本内核下编译的模块.ko文件无法在另一个版本内核加载。
1)内核模块运行在内核空间,而应用程序运行在用户空间
2)模块通常注册一些服务性质的函数供其他功能单元在之后调用,而应用程序则是顺序执行,通常进入一个循环往复调用某些函数。
3)内核C函数库之下,就不能调用C库函数。
4)内核模块要做一些清除性工作,比如在一个操作失败或在内核的清除函数中。
5)内核模块若产生了非法访问,将可能导致系统崩溃。
6)内核模块并发更多,比如中断、多处理器。
7)整个内核空间的调用链只有4KB或8KB的栈,若需要大的内存空间,通常应动态分配。
8)printk不支持浮点打印。

测试实例分析

Makefile 文件中需要注意:
在make的时候出现了“empty variable name”,

最后是如下原因:在Makefile中有如下一句话,出现这个错误的原因是“=”左边多了一个空格

 $(MAKE) -C $(KERNELDIR)  M= $(PWD) modules

如果在"="右边多一个空格,则会出现另外的错误:

  *** Error during update of the kernel configuration.

make[3]: *** [silentoldconfig] Error 1
make[2]: *** [silentoldconfig] Error 2
make[1]: Nothing to be done for `/home/chen/hello_module’.

make[1]: *** No rule to make target include/config/auto.conf', needed by include/config/kernel.release’. Stop.
make[1]: Leaving directory `/usr/src/linux-headers-2.6.35-22-generic’
make: *** [all] Error 2

sudo insmod vser.ko baudrate=1080

用modprobe vser 不能传递参数,建议以后仍用insmod

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值