Linux_内核模块化编程

1.加载驱动的情况

1.1.静态编译到内核

1)执行make menuconfig,找到
Device Drivers —>
Character devices —>

将菜单前面的括号选择为 ’*’ ,然后保存退出。
2)分析linux-3.5/drivers/char/目录下的Makefile,寻找和tiny4412_hello_module.c有关的地方

3)CONFIG_TINY4412_HELLO_MODULE为一个宏,是在linux-3.5/.config内定义的

4)从这可以看出,解析linux-3.5/drivers/char/Makefile下的内容为
obj-y += tiny4412_hello_module.o
结论:最终执行make zImage时,就会把obj-y里面的文件全部编译到内核。(make的意思是编译生成所有的目标文件)
使用静态编译到内核的优点:功能稳定,在发布产品时使用。

1.2.动态加载到内核

1)执行make menuconfig ,找到
Device Drivers —>
Character devices —>

将菜单前面的括号选择为 ’M’ ,然后保存退出。
2)查看linux-3.5/.config内CONFIG_TINY4412_HELLO_MODULE的值

3)此时,再去解析linux-3.5/drivers/char/Makefile下的内容为
obj-m += tiny4412_hello_module.o
结论:最终执行make modules时,会将obj-m内的文件全部编译成模块文件(xx.ko文件)

动态加载到内核的优点:以动态的方式将驱动加载进内核。加载/卸载方便,在开发产品时使用
4)驱动模块相关命令

加载  insmod xxx.ko
查看  lsmod
卸载  rmmod xxx.ko

1.3.不加载到内核

1)执行make menuconfig ,找到
Device Drivers —>
Character devices —>

将菜单前面的括号选择为空,然后保存退出。
2)查看linux-3.5/.config内CONFIG_TINY4412_HELLO_MODULE的值

3)那么,linux-3.5/drivers/char/Makefile下的内容为
obj- += tiny4412_hello_module.o
最终,执行make zImage或者make modules时,是不会编译 obj- 中的文件的,因此这些文件既不会被编译到内核,也不会别编译成模块。

2.内核模块概念

2.1.什么是Linux内核模块

windows系统后期如果说增加一个硬件,一定安装对应的驱动程序。这个后期增加的驱动程序不是微软自带的,可能是网上下载或硬件厂家提供,在操作系统运行起来后动态安装,达到扩张系统功能的目的。
这种后期加载驱动来扩展系统的功能行为就是今天Linux内核模块编程要实现的功能。
Linux可以把硬件设备驱动编写成一个可以后期加载的文件,来达到扩充系统功能的目的。这个驱动文件就是称为模块文件,编写这样的驱动文件就称为模块编程。

2.2.为什么要使用模块编程

1)Linux系统功能很强大,可以支持各种各样的设备,如果说把系统所有功能都编译进内核映像(相当于Windows系统安装文件),会导致内核文件很大,有一些功能没有使用到,浪费了FLASH空间。通过建立一种可以后期根据需要动态加载驱动文件的机制,Linux就是通过模块编程方法实现的。
2)就算你不计较FLASH空间,你不可能一次性把后面你要使用到的功能都想到,如果后期需要给系统增加一个当前没有的功能,就需要重新内核,重新安装系统,这样很麻烦。
3)所以Linux内核给用户建立模块加载机制实现任意时刻动态扩展内核功能目的,而不需要重新安装系统。
2.3.使用Linux模块的优点
1.缩小出厂Linux内核系统体积大小,节省flash空间
2.后期增加硬件功能,不需要重新编译内核,重新安装系统,快捷,方便
3.当不需要一个硬件时候,可以随时卸载对应的驱动

3.内核模块编译

3.1.方法一:修改Kconfig和Makefile,通过menuconfig菜单选配

最原始的,最老套的方式,但是要懂
1)编写一个目标模块代码文件,存放在driver目录下的某一个子目录中(随便)
2)照前面讲的内核菜单配置方法做一个配置菜单:在上一步的目录中,修改其中的Kconfig和Makefile文件。修改Kconfig文件时,要将菜单选项数据类型设置为tristate(bool表示只有两种选择,tristate表示可以有三种选择)。
3)make menuconfig进入配置界面,然后配置目标选项为M,保存退出
4)make modules

3.2.方法二:只修改内核Makefile,跳过menuconfig菜单选配

跳过菜单,直接编译成模块,只需要修改目标文件目录中的Makefile即可
1)将obj-$(CONFIG_TINY4412_HELLO_MODULE) += tiny4412_hello_module.o修改为
obj-m += tiny4412_hello_module.o
2)make moeules
这种方式编译不需要做菜单,简单一些,但是也不能通过make menuconfig进行配置了。

同理,如果想把C文件编译到zImage中,也可以不做菜单,直接修改makefile文件,把变量名直接写成y,就可以了。
obj-m += tiny4412_hello_module.o修改为obj-y += tiny4412_hello_module.o这样就可以编译到内核。

3.3.方法三:模块化编程通用Makefile

以上两种内核模块编译方式都要去修改内核源码Makefile,太过于繁杂。实际上,我们可以通过编写一个Makefilke文件来编译指定的模块文件。
其Makefile文件内容如下所示:

obj-m += xyd_module.o
KERNEL_DIR = /tool/Linux/linux-3.5
all:
	make -C ${KERNEL_DIR} M=${PWD} modules
clean:
	make -C ${KERNEL_DIR} M=${PWD} clean

注:
-C ${KERNEL_DIR}:在/tool/Linux/linux-3.5目录下执行make(根据自己的源码目录进行修改)
M=${PWD}:指定模块文件所在路径,模块文件在当前同级目录下。
modules:编译模块规则
obj-m += xyd_module.o:指定被编译的模块文件

4.内核模块编写

4.1.包含相关头文件

#include <linux/kernel.h>
#include <linux/module.h>

4.2.模块加载与卸载函数的声明

module_init(tiny4412_hello_module_init);
module_exit(tiny4412_hello_module_cleanup);

4.3.模块加载与卸载函数的实现

static int __init tiny4412_hello_module_init(void)
{
     printk("Hello, Tiny4412 module is installed !\n");
     return 0;
}
static void __exit tiny4412_hello_module_cleanup(void)
{
     printk("Good-bye, Tiny4412 module was removed!\n");
}

模块加载函数:当模块被安装时候会自动执行的函数,函数类型必须为int型
模块卸载函数:当模块被卸载时候会自动执行的函数,函数类型必须为void型
补充说明:
当源文件被编译到ko模块时:module_init和module_exit起作用。
当源文件被编译到内核时:__init和__exit修饰词起作用。
为了方便兼容俩种情况,不需要根据配置情况修改代码,就俩个都写了。

4.4.GPL声明

MODULE_LICENSE("GPL");//遵循“GPL”协议规范
Linux系统是一个开源免费的操作系统,基于Liunx开发起来的软件必须要遵守相关的协议,咱们编写的模块也是基于Linux系统开发的,必须也要遵守这个协议。

4.5.Linux模块一些可选信息声明

在Linux内核模块中,有一些宏:

MODULE_AUTHOR(author);			//声明模块的作者
MODULE_DESCRIPTION(description);//声明模块的描述
MODULE_VERSION(version_string);	//声明模块的版本
MODULE_ALIAS ()					//声明模块的别名
MODULE_DEVICE_TABLE ()			//告诉用户空间这个模块所支持的设备

可以使用modinfo xxx.ko进行查看,
注意,第一次使用modinfo命令时,会出现以下情况:

解决方法:
1)在lib目录下创建modules文件夹:mkdir modules
2)在modules目录下创建3.5.0-FriendlyARM文件夹:mkdir 3.5.0-FriendlyARM
3)在3.5.0-FriendlyARM运行depmod命令

5.Linux内核模块编程形式

5.1.Linux内核模块单文件单模块编程

一个C文件编译生成一个.ko文件

5.2.Linux内核模块多文件单模块编程

多个C代码文件编译成一个ko文件。
要注意:
1.这些文件中只能有一个是以模块的形式编写,其他C文件只是像以前学习C语言一样,使用普通的方式编写,因为一个ko文件只能有一个加载函数和一个卸载函数。
2.Makefile写法和单文件.ko有点不一样,文件列表的写法有所不同
obj-m+=最终模块名.o
最终模块名-objs = 源文件列表

5.3.Linux内核模块间依赖编程(模块符号导出)

在内核之中,模块并不一定是相互独立的,模块与模块之间可能会相互调用。比如可以将一些共享代码封装在一个模块内,其他的模块去调用这个模块中的代码。类似于应用程序中的动态库。
要想一个模块内的函数被其他模块所调用,那么该模块要将被调用的函数进行导出:
EXPORT_SYMBOL() //这个宏是内核专门用来把一个模块的函数或变量导出,给其他模块使用
注意:
正确加载顺序:先加载被调用模块,在安装调用模块
正确卸载顺序:先卸载调用模块,再卸载被调用模块
对于被调用模块,不需要模块加载和卸载函数的定义和声明,可以直接定义好一些封装的函数
对于调用模块,要对所调用的函数进行声明。例如:extern void fun(void);
在Makefile中需要指定调用模块和被调用模块。

5.4.Linux内核模块传参

要想实现内核模块传参,需要调用以下函数:

module_param(name,type,perm)				//用来实现单个普通变量传递参数
name:表示参数的变量名字
type:表示参数的变量类型
perm:表示文件的访问权限,填写S_IRUGO即可
module_param_array(name,type,&num,perm)	//用来实现数组变量传递参数
	name:表示数组的名字
	type:表示数组元素的类型
	num:存放传入参数元素数量,使用时候这里要写一个变量的地址
	perm:表示文件的访问权限,填写S_IRUGO即可
	
type支持的基本类型:
bool:布尔类型    非0为真
//invbool:与布尔类型相反,0为真
charp:字符指针类型(char *int:整型
long:长整型
short:短整型
uint:无符号整型
ulong:无符号长整型
ushort:无符号短整型
perm就是用来指定文件的权限,安装模块后,如果模块代码中有参数可以传递,则会在
/sys/module/模块名/parameters/目录下生成“参数变量名”的文件,这个文件内容就是变量的值,
perm就是对这些文件的权限,权限在include/linux/stat.h中有定义。
#define S_IRWXU 00700	//用户有读写执行权限
#define S_IRUSR 00400	//用户有读权限
#define S_IWUSR 00200	//用户有写权限
#define S_IXUSR 00100	//用户有执行权限
#define S_IRWXG 00070	//组有读写执行权限
#define S_IRGRP 00040	//组有读权限
#define S_IWGRP 00020	//组有写权限	
#define S_IXGRP 00010	//组有执行权限
#define S_IRWXO 00007	//其他用户有读写执行权限
#define S_IROTH 00004	//其他用户有读权限
#define S_IWOTH 00002	//其他用户有写权限
#define S_IXOTH 00001	//其他用户有执行权限
使用 S_IRUGO 作为参数可以被所有人读取, 但是不能改变; S_IRUGO|S_IWUSR 允许 root 来改变参数. 

注意, 如果一个参数被 sysfs 修改, 你的模块看到的参数值也改变了, 但是你的模块没有任何其他的通知。你应当不要使模块参数可写, 除非你准备好检测这个改变并且因而作出反应.
加载驱动时传参格式:

6.地址映射

6.1.基本概念

因为SOC芯片都存在MMU(内存管理单元)外设。当Linux内核正在运行时,其MMU外设已经被打开。此时不管是在用户空间,还是内核空间,操作的都是虚拟地址。内核驱动程序不能直接通过物理地址访问外设相关寄存器。
必须先将外设相关寄存器映射到虚拟地址空间内,然后根据映射所得到的虚拟地址对这些外设进行控制。其中,进行地址映射需要调用ioremap函数,取消地址映射需要调用iounmap函数

6.2.函数使用说明

所需头文件:#include <linux/io.h>
地址映射:
void * ioremap(phys_addr_t offset, unsigned long size);
offset:需要映射的物理空间起始地址
size:需要映射的物理空间大小
返回值:成功返回得到的内核虚拟地址,失败返回NULL。
解除映射:
void iounmap(void * addr)
addr:要解除映射的虚拟空间起始地址,就是ioremap函数的返回值!

6.3.点亮LED灯示例

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/io.h>

static unsigned int *led_con;
static unsigned int *led_dat;

static int __init hello_init(void)
{
    printk("*****%s*****\n",__FUNCTION__);
	led_con = ioremap(0x110002E0, 4);
	led_dat = ioremap(0x110002E4, 4);
	*led_con &= 0xffff0000;
	*led_con |= 0xffff1111;
	*led_dat &= 0xfffff00;
    return 0;
}
static void __exit hello_exit(void)
{
    printk("*****%s*****\n",__FUNCTION__);
	*led_dat |= 0xffffffff;
	iounmap(led_con);
	iounmap(led_dat);

}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
	
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值