Waring:
字符设备、块设备和网络设备是相对的,如SPI驱动可写成字符设备驱动或块设备驱动、CAN设备驱动可写成字符设备驱动或网络设备驱动,具体实现方式应依据设备类型来定。当SPI连接存储设备时,由于块设备传输单位为 512B, 故应将其编写成块设备驱动,应用空间通过块操作来提高读写效率。块设备特性具体内容:参考块设备驱动框架详解
设备分类
字符设备
字符设备 >>>> 字符设备驱动 >>>> 字符设备文件 (/dev/生成字符设备文件)
(鼠标、键盘、显示……)
块设备
块设备 >>>> 块设备驱动 >>>> 块设备文件 (/dev/生成块设备文件)
(内存卡、SD卡、RAM……)
网络设备
网络设备 >>>> 网络设备驱动 (ifconfig -a 查看设备信息)
(以太网、WiFi、蓝牙、CAN……) 无设备文件
字符设备驱动实现步骤
1. 驱动编写
2. 驱动编译
3. 驱动使用
模块编写
(Linux内核以一定的格式进行驱动的编写)
三要素:
1. 入口(加载)
module_init(入口函数名); static int __init xxx_func(void){}
2. 出口(卸载)
module_exit(卸载函数名); static void __exit xxx_func(void){} //进口函数名和出口函数名不需要相同
3. GPL协议声明
MODULE_LICENSE("GPL");
4. 描述驱动的而一些信息(不必要)
MODULE_AUTHOR("zsj"); //驱动程序的作者
MODULE_VERSION("demo_1"); //模块版本
MODULE_DESCRIPTION("Fist_drive demo test"); //一些描述信息
MODULE_ALIAS("demo"); //模块别称
……
module_param(name, type, perm);
name:表示参数的名字;
type:表示参数的类型;
perm:表示参数的访问权限;
声明一个数组参数:
module_param_array(name, type, num, perm);
name:表示数组的名字;
type:表示参数的类型;
num :表示数组中元素数量;
perm:表示参数的访问权限;
【type支持的基本类型有】
bool :布尔类型
invbool:颠倒了值的bool类型;
charp :字符指针类型,内存为用户提供的字符串分配;
int :整型
long :长整型
short :短整型
uint :无符号整型
ulong :无符号长整型
ushort :无符号短整型
【perm参数 设定访问权限】
modlue_param和module_param_array中的perm用于设定该参数的访问权限;
perm表示该参数在sysfs文件系统中所对应的文件节点的属性;你用该使用<linux/stat.h>中定义的权限值;这个值控制
谁可以存取这些模块参数在sysfs文件系统中的表示;当perm为0时,表示此参数不存在sysfs文件系统下对应的文件节点;
否则,模块被加载后,在/sys/module/目录下将会出现以此模块名命名的目录,带有给定的权限;
比如:
#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 来改变参数.
上述模块的宏定义包含的头文件在Linux内核:
#include <linux/init.h>
#include <linux/module.h>
EXPORT_SYMBOL_GPL() 用来声明驱动之间的全局变量和函数
编译内核模块:
编译器: gcc/交叉编译工具
编写编译内核模块的Makefile
内部编译:将内核模块源文件放在内核源码中进行编译 ==>> 相关文件(Konfig,Makefile,make menuconfig)
静态编译:将内核模块编译进uImage中
外部编译:将内核模块源文件放在内核源码外进行编译
动态编译:编译生成动态模块 XXX.ko
模块编译makefile实例:
KERNDIR:= /lib/modules/'uname -r'/build/ //自己放置源码的目录,编译成对应的架构
PWD:=$(shell pwd) //编写的驱动所在路径
obj-m:= module_test.o //目标文件依赖所有 *.o 文件生成
all:
make -C $(KERNDIR) M=$(PWD) modules //去指定的路径编译成目标modules
clean:
make -C $(KERNDIR) M=$(PWD) clean //去指定的路径清除目标clean
(开始因 obj-m 错误操作,编译时导致提示没有头文件,谨慎每一步操作)
如果一个模块包含多个.c文件:
obj-m:= module_name.o
module_name-objs := file1.o file2.o …
Note:
嵌入式系统编译模块时,需下载好所需的内核源码,配置好,并编译一次(否则无法成功编译模块,及KERNDIR下的内核已经编译过一次)
原因:
编译驱动只编译了你的驱动代码,而且里面引用了内核的头文件,如果没编译内核,驱动程序链接时会找不到内核的二进制代码,会链接失败。
模块的使用:
查看内核模块信息的命令:
modinfo (作者,功能描述)
查看当前内核工作的模块:
lsmod
查看内核日志信息命令:
dmesg
-C 清除已有的内核日志信息
插入模块进入内核:
insmod / modprobe(可指定个别模块或一组相依的模块)
卸载内核模块:
rmmod
内核模块加载时,执行对应的加载函数,并只会执行一次