目录
一、字符设备驱动
字符设备是Linux 驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节流进行读写操作的设备,读写数据是分先后顺序的。比如我们最常见的点灯、按键、IIC、SPI,LCD 等等都是字符设备,这些设备的驱动就叫做字符设备驱动。Linux 应用程序对驱动程序的调用
应用程序运行在用户空间,而Linux 驱动属于内核的一部分,因此驱动运行于内核空间。当我们在用户空间想要实现对内核的操作,比如使用open 函数打开/dev/led 这个驱动,因为用户空间不能直接对内核进行操作,因此必须使用一个叫做“系统调用”的方法来实现从用户空间“陷入”到内核空间,这样才能实现对底层驱动的操作。open、close、write 和read 等这些函数是由C 库提供的,在Linux 系统中,系统调用作为C 库的一部分。当我们调用open 函数的时候流程如图所示
二、字符设备驱动开发步骤
编写驱动的时候注意事项:
1、编译驱动的时候需要用到linux内核源码!因此要解压缩linux内核源码,编译linux内核源码。得到zImage和.dtb。需要使用编译后的到的zImage和dtb启动系统。
2、从SD卡启动,SD卡烧写了uboot。uboot通过tftp从ubuntu里面获取zimage和dtb,rootfs也是通过nfs挂载。
3、设置bootcmd和bootargs
setenv bootargs 'console=ttymxc0,115200
root=/dev/nfs rw
nfsroot=192.168.199.158:/home/denghengli/linux/nfs/rootfs
ip=192.168.199.20:192.168.199.158:192.168.199.1:255.255.255.0::eth0:off'
setenv bootcmd ‘tftp 80800000 zImage; tftp 83000000 imx6ull-alientek-emmc.dtb; bootz 80800000 - 83000000;’
4、将编译出来的.ko文件放到根文件系统里面 /lib/modules/<kernel-version> 目录中。
1、字符设备驱动模块编译
第一步:创建VSCode工程
第二步:为VSCode添加头文件路径
源码中的头文件路径。打开 VSCode,按下 Crtl+Shift+P”打开 VSCode的控制台,然后输入 C/C++: Edit configurations(JSON) ”,打开 C/C++编辑配置文件,打开以后会自动在 .vscode目录下生成一个名为 c_cpp_properties.json的文件,includePath 表示头文件路径,需要将 Linux源码里面的头文件路径添加进来
"${workspaceFolder}/**",
"/home/denghengli/linux/IMX6ULL/linux/nxptoalpha_linux/include",
"/home/denghengli/linux/IMX6ULL/linux/nxptoalpha_linux/arch/arm/include",
"/home/denghengli/linux/IMX6ULL/linux/nxptoalpha_linux/arch/arm/include/generated/"
第三步:新建chardevbase.c字符驱动文件,编写Makefile
KERNELDIR := /home/denghengli/linux/IMX6ULL/linux/nxptoalpha_linux
CURRENT_PATH := $(shell pwd)
#obj-m表示将 chardevbase.c这个文件编译为chardevbase.ko模块。
obj-m := chardevbase.o
build: kernel_modules
#具体的编译命令,后面的 modules表示编译模块,-C表示将当前的工作目录切
#换到指定目录中,也就是 KERNERLDIR目录。 M表示模块源码目录,“ make modules”命令
#中加入 M=dir以后程序会自动到指定的 dir目录中读取模块的源码并将其编译为 .ko文件。
kernel_modules :
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
完成之后,输入 make 就会编译出 chardevbase.ko 的动态模块文件
2、驱动模块的加载与卸载
在内核启动起来之后,进入根文件系统,使用insmod或modprobe加载驱动,insmod命令不能解决模块的依赖关系,modprobe会分析模块的依赖关系,然后将所有模块的依赖模块都加载到内核中,因此modprobe命令相比insmod要智能一些;modprobe命令默认会去根文件系统下的/lib/modules/<kernel-version>目录中查找模块。对于一个新的模块使用modprobe加载的时候需要先调用一下depmod命令。移除驱动使用命令rmmod。
比如加载chardevbase.ko
insmod chardevbase.ko
depmod modprobe chardevbase.ko
rmmod chardevbase
驱动模块加载成功以后可以使用lsmod查看一下。
lsmode
卸载模块使用rmmod命令
3、字符设备注册与注销具体实现
驱动模块加载和卸载 实际上 就是在向系统注册和注销这个字符设备。
1、我们需要在加载驱动模块函数chardevbase_init 中使用函数register_chrdev向系统注册一个字符设备,卸载驱动的时候需要注销掉前面注册的字符设备,使用函数unregister_chrdev在chardevbase_exit中注销字符设备
2、Linux内核将设备号分为两部分:主设备号和次设备号。主设备号占用前12位,次设备号占用低20位。设备号的操作函数或宏:
从dev_t获取主设备号和次设备号:MAJOR(dev_t),MINOR(dev_t)
使用主设备号和次设备号构成dev_t:MKDEV(major,minor)
如果使用静态分配设备号,在设置时需要查看下当前系统都用了哪些设备号,避免冲突,使用命令:
cat /proc/devices
3、添加LICENSE和作者信息
/*
模块输入与输出
*/
static int __init chardevbase_init(void)
{
int ret = 0;
printk("chardevbase_init\r\n");
/*注册字符设备*/
ret = register_chrdev(CHARDEVBASE_MAJOR, CHARDEVBASE_NAME, &chardevbase_fops);
if (ret < 0){
printk("chardevbase init failed!\r\n");
}
return 0;
}
static void __exit chardevbase_exit(void)
{
printk("chardevbase_exit\r\n");
/*卸载字符设备*/
unregister_chrdev(CHARDEVBASE_MAJOR, CHARDEVBASE_NAME);
}
module_init(chardevbase_init); /*入口*/
module_exit(chardevbase_exit); /*出口*/
MODULE_AUTHOR("denghengli");
MODULE_LICENSE("GPL");
4、实现设备的具体操作函数
在向系统注册完字符设备后,需要自行添加设备相关的 file_operations
/*
字符设备操作集合
*/
static struct file_operations chardevbase_fops = {
.owner = THIS_MODULE,
.open = chardevbase_open,
.release = chardevbase_release,
.read = chardevbase_read,
.write = chardevbase_write,
};
三、测试APP编写与测试
编写驱动程序的时候,可以使用Linux自带的 man 手册查看函数的使用方法。使用方法为 man [section] [command],例如 man 1 ls
1、Standard commands (标准命令)
2、System calls (系统调用)
3、Library functions (库函数)
4、Special devices (设备说明)
5、File formats (文件格式)
6、Games and toys (游戏和娱乐)
7、Miscellaneous (杂项)
8、Administrative Commands (管理员命令)
9 其他(Linux特定的), 用来存放内核例
1、编写 chardevbaseAPP.c 应用测试程序,编译成chardevbaseAPP执行文件,将其拷贝至 rootfs/lib/modules/4.1.15/
arm-linux-gnueabihf-gcc chardevbaseAPP.c -o chardevbaseAPP
sudo cp chardevbaseAPP /home/denghengli/linux/nfs/rootfs/lib/modules/4.1.15/ -f
2、加载驱动模块,查看chardevbase驱动设备的设备号
modprobe chardevbase.ko
cat /proc/devices
3、创建设备节点文件
驱动加载成功需要在 /dev目录下创建一个与之对应的设备节点文件,应用程序就是通过操作这个设备节点文件来完成对具体设备的操作。加载完驱动模块后,会自动在 /dev创建的,但是这里还没有注册,所以先手动注册一个。如果 chardevbaseAPP想要读写 chardevbase设备,直接对 /dev/chardevbase进行读写操作即可。相当于 /dev/chardevbase这个文件是 chardevbase设备在用户空间中的实现。
/*其中“ mknod”是创建节点命令 ,“/dev/chardevbase”是要创建的节点文件
*c表示这是个字符设备,200是设备的主设备号 0”是设备的次设备号。
*/
mknod /dev/chardevbase c 200 0
4、执行测试程序
./chardevbaseAPP /dev/chardevbase