一、首先是编写应用程序APP
首先需要说明的是,编写测试 APP 就是编写 Linux 应用,需要用到 C 库
里面和文件操作有关的一些函数,比如open
、read
、write
和 close
这四个函数。
我们可以使用man
命令来使用编程手册,也即它可以查询很多函数应该如何使用。包括应该包含哪些头文件
,函数原型
,参数
以及返回值
等等。这是非常好用的一个命令,具体的使用方法我们以后再说,你也可以去网上搜一下,有很多关于man
命令如何使用的文章。
测试 APP 简单通过输入相应的指令来对chrdevbase
设备执行读或者写操作。在驱动程序的同级目录下创建chrdevbaseApp.c
文件,在此文件中输入如下内容:
1 #include "stdio.h"
2 #include "unistd.h"
3 #include "sys/types.h"
4 #include "sys/stat.h"
5 #include "fcntl.h"
6 #include "stdlib.h"
7 #include "string.h"
21 static char usrdata[] = {"usr data!"};
22
23 /*
24 * @description : main 主程序
25 * @param - argc : argv 数组元素个数
26 * @param - argv : 具体参数
27 * @return : 0 成功;其他 失败
28 */
29 int main(int argc, char *argv[])
30 {
31 int fd, retvalue;
32 char *filename;
33 char readbuf[100], writebuf[100];
34
35 if(argc != 3){
36 printf("Error Usage!\r\n");
37 return -1;
38 }
39
40 filename = argv[1];
41
42 /* 打开驱动文件 */
43 fd = open(filename, O_RDWR);
44 if(fd < 0){
45 printf("Can't open file %s\r\n", filename);
46 return -1;
47 }
48
49 if(atoi(argv[2]) == 1){ /* 从驱动文件读取数据 */
50 retvalue = read(fd, readbuf, 50);
51 if(retvalue < 0){
52 printf("read file %s failed!\r\n", filename);
53 }
else{
54 /* 读取成功,打印出读取成功的数据 */
55 printf("read data:%s\r\n",readbuf);
56 }
57 }
58
59 if(atoi(argv[2]) == 2){
60 /* 向设备驱动写数据 */
61 memcpy(writebuf, usrdata, sizeof(usrdata));
62 retvalue = write(fd, writebuf, 50);
63 if(retvalue < 0){
64 printf("write file %s failed!\r\n", filename);
65 }
66 }
67
68 /* 关闭设备 */
69 retvalue = close(fd);
70 if(retvalue < 0){
71 printf("Can't close file %s\r\n", filename);
72 return -1;
73 }
74
75 return 0;
76 }
第 21 行
,数组 usrdata
是测试 APP 要向 chrdevbase
设备写入的数据。
第 35 行
,判断运行测试 APP 的时候输入的参数是不是为 3 个,main 函数的 argc
参数表示参数数量,argv[]
保存着具体的参数,如果参数不为 3 个的话就表示测试 APP 用法错误。比如,现在要从 chrdevbase
设备中读取数据,需要输入如下命令:./chrdevbaseApp /dev/chrdevbase 1
上述命令一共有三个参数 “./chrdevbaseApp”
、“/dev/chrdevbase”
和“1”
,这三个参数分别对应argv[0]
、argv[1]
和 argv[2]
。第一个参数表示运行 chrdevbaseAPP
这个软件,第二个参数表示测试APP要打开/dev/chrdevbase
这个设备。第三个参数就是要执行的操作,1
表示从chrdevbase
中读取数据,2
表示向 chrdevbase
写数据。
第 40 行
,获取要打开的设备文件名字,argv[1]
保存着设备名字。
第 43 行
,调用 C 库
中的 open
函数打开设备文件:/dev/chrdevbase
。
第 49 行
,判断 argv[2]
参数的值是 1
还是2
,因为输入命令的时候其参数都是字符串格式的,因此需要借助 atoi
函数将字符串格式的数字转换为真实的数字。
第 50 行
,当 argv[2]
为1
的时候表示要从 chrdevbase
设备中读取数据,一共读取 50 字节的
数据,读取到的数据保存在readbuf
中,读取成功以后就在终端上打印出读取到的数据。
第 59 行
,当 argv[2]
为 2
的时候表示要向 chrdevbase
设备写数据。
第 69 行
,对 chrdevbase
设备操作完成以后就关闭设备。
chrdevbaseApp.c
内容还是很简单的,就是最普通的文件打开、关闭和读写操作。
二、编译应用程序APP
上一篇文章我们已经对驱动程序进行了编译,这里我们开始编译测试 APP 。因为只有一个文件,因此就不需要编写 Makefile
了,直接输入命令编译。因为测试 APP 是要在 ARM
开发板上运行的,所以需要使用 arm-linux-gnueabihf-gcc
来编译,输入如下命令:
arm-linux-gnueabihf-gcc chrdevbaseApp.c -o chrdevbaseApp
注意,这是在驱动程序
和应用程序
的同级目录下执行的命令,所以直接使用的相对路径。
编译完成以后会生成一个叫做 chrdevbaseApp
的可执行程序,注意也需要把它移动到/lib/modules/4.1.15
目录下,此时应用程序
和.ko驱动模块
在一个文件夹里。
三、运行测试
1、加载驱动模块
modprobe chrdevbase.ko
此时我们会看到打印出的字符串"chrdevbase init!"
(这是我们写驱动程序的时候在初始化module_init
函数中实现的),然后我们执行lsmod
命令,如果前面这些操作都正确的话,就会显示有chrdevbase
。
2、创建设备节点文件
驱动加载成功需要在/dev
目录下创建一个与之对应的设备节点文件,应用程序就是通过操作这个设备节点文件来完成对具体设备的操作。输入如下命令创建/dev/chrdevbase
这个设备节点文件:
mknod /dev/chrdevbase c 200 0
其中mknod
是创建节点命令,/dev/chrdevbase
是要创建的节点文件,c
表示这是个字符设备,200
是设备的主设备号,0
是设备的次设备号。创建完成以后就会存在/dev/chrdevbase
这个文件,可以使用ls /dev/chrdevbase -l
命令查看。
如果 chrdevbaseAPP
想要读写 chrdevbase
设备,直接对/dev/chrdevbase
进行读写操作即可。相当于/dev/chrdevbase
这个文件是 chrdevbase
设备在用户空间中的实现。前面一直说 Linux 下一切皆文件,包括设备也是文件。
3、chrdevbase 设备操作测试
使用 chrdevbaseApp
软件操作chrdevbase
这个设备,看看读写是否正常,分别输入如下命令即可:
./chrdevbaseApp /dev/chrdevbase 1
应该首先输出出"kernel senddata ok!"
,紧接着输出"read data:kernel data!"
,如果是这样的结果,就说明程序是正确的。
./chrdevbaseApp /dev/chrdevbase 2
应该输出"kernel recevdata:usr data!"
4、卸载模块
如果不再使用某个设备的话可以将其驱动卸载掉,比如输入如下命令卸载掉 chrdevbase
这个设备:
rmmod chrdevbase.ko
此时我们再执行lsmod
命令,就会发现chrdevbase
这个模块已经没有了,也就证明我们卸载成功。
四、注释:
1、下面我们仍然假设使用虚拟机来开发程序,然后使用开发板上的linux系统来执行,那么在上面所说的一些操作中,需要在开发板上的linux系统操作的是:
- 加载模块
- 创建设备节点文件
- chrdevbase 设备操作测试
- 卸载模块
也即除了编写和编译应用程序,其余的操作都要在开发板上的linux系统中进行操作。但是如果你只使用一个系统,那么上述所有的操作肯定都是在这一个系统中来完成。
2、再来通俗的说一下应用程序中读写操作是如何与驱动程序的读写操作联系到一块的?
- 首先,因为在linux中一切皆文件,即使是我们的设备也是文件,所以我们一开始是通过应用程序中的
open
打开了/dev/chrdevbase
这个设备文件,就在此时,该应用程序就和chrdevbase
驱动程序正式的联系到一块了。与此同时驱动程序中的xxx_open
也会执行。 - 联系上之后,因为应用程序的
read
以及write
函数和驱动程序中的xxx_open
,xxx_write
函数是对应的,它们的参数也都是一样的,除了一些分属两个程序的变量之外,其实你可以理解成是在直接操作驱动程序中的对应函数。 - 这里以应用程序中的
write
函数为例,当然和read
等其他函数的道理都是一样的,要学会类比。在应用程序中,我们执行retvalue = write(fd, writebuf, 50);
这里的writebuf
是应用程序中的writebuf
,而在驱动程序中,看下面的代码片段:
71 static ssize_t chrdevbase_write(struct file *filp,const char __user *buf,
size_t cnt, loff_t *offt)
72 {
73 int retvalue = 0;
74 /* 接收用户空间传递给内核的数据并且打印出来 */
75 retvalue = copy_from_user(writebuf, buf, cnt);
76 if(retvalue == 0){
77 printk("kernel recevdata:%s\r\n", writebuf);
78 }
else{
79 printk("kernel recevdata failed!\r\n");
80 }
81
82 printk("chrdevbase write!\r\n");
83 return 0;
84 }
因为两个函数的参数是一样的,所以可以看出应用程序的writebuf
就是驱动程序中的形参buf
,50
就是驱动程序中的cnt
,最后会通过copy_from_user(writebuf, buf, cnt)
将形参buf
指向的数据拷贝到驱动程序中的writebuf
,注意这里的两个writebuf
是不一样的,一定要区分开。就这样,实现了将应用程序的writebuf
里的数据写到驱动程序中的writebuf
。这里只是恰巧名字是一样的,其实你也可以定义成名字不一样的变量,只要理解整个程序是如何执行的就可以了。
好了,该篇文章到此结束,作为小白也是刚开始学linux驱动开发,如果有什么问题,还请指正!!!