
🎬 渡水无言 :个人主页渡水无言
❄专栏传送门 :linux专栏
⭐️流水不争先,争的是滔滔不绝
目录
[一、C 库文件操作基本函数](#一、C 库文件操作基本函数)
[1.1、open 函数](#1.1、open 函数)
[1.2read 函数](#1.2read 函数)
[1.3write 函数](#1.3write 函数)
[1.4、close 函数](#1.4、close 函数)
[二、编写测试 APP 程序](#二、编写测试 APP 程序)
[三、编译驱动程序和测试 APP](#三、编译驱动程序和测试 APP)
[2、编译测试 APP](#2、编译测试 APP)
前言
上一期博客我们编写完成了chrdevbase 字符设备驱动开发实验的代码,这一期博客,我们开始编写测试 APP的代码。
一、C库文件操作基本函数
编写测试 APP 就是编写 Linux 应用,需要用到 C 库里面和文件操作有关的一些函数,比如open、read、write 和 close 这四个函数。
1.1、open函数
open 函数原型如下:
int open(const char *pathname, int flags)
open 函数参数含义如下:
pathname:要打开的设备或者文件名。
flags**:**文件打开模式,以下三种模式必选其一:
O_RDONLY 只读模式
O_WRONLY 只写模式
O_RDWR 读写模式
因为我们要对 chrdevbase 这个设备进行读写操作,所以选择 O_RDWR。除了上述三种模式以外还有其他的可选模式,通过逻辑或来选择多种模式
返回值:如果文件打开成功的话返回文件的文件描述符。
在 Ubuntu 中输入" man 2 open "即可查看 open 函数的详细内容,如下图 所示:

1.2read****函数
read 函数原型如下:
ssize_t read(int fd, void *buf, size_t count)
read 函数参数含义如下:
fd:要读取的文件描述符,读取文件之前要先用 open 函数打开文件,open 函数打开文件成
功以后会得到文件描述符。
buf**:**数据读取到此 buf 中。
count**:**要读取的数据长度,也就是字节数。
**返回值:**读取成功的话返回读取到的字节数;如果返回 0 表示读取到了文件末尾;如果返回负值,表示读取失败。在 Ubuntu 中输入"man 2 read"命令即可查看 read 函数的详细内容。

1.3write****函数
write 函数原型如下:
ssize_t write(int fd, const void *buf, size_t count);
write 函数参数含义如下:
fd:要进行写操作的文件描述符,写文件之前要先用 open 函数打开文件,open 函数打开文
件成功以后会得到文件描述符。
buf**:**要写入的数据。
count**:**要写入的数据长度,也就是字节数。
**返回值:**写入成功的话返回写入的字节数;如果返回 0 表示没有写入任何数据;如果返回
负值,表示写入失败。在 Ubuntu 中输入"man 2 write"命令即可查看 write 函数的详细内容。

1.4、close****函数
close 函数原型如下:
int close(int fd);
close 函数参数含义如下:
fd:要关闭的文件描述符。
**返回值:**0 表示关闭成功,负值表示关闭失败。在 Ubuntu 中输入"man 2 close"命令即可
查看 close 函数的详细内容。
二、编写测试APP程序
驱动编写好以后是需要测试的,一般编写一个简单的测试 APP,测试 APP 运行在用户空
间。测试 APP 很简单通过输入相应的指令来对 chrdevbase 设备执行读或者写操作。在 1_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"
8
9 /********************************************
11 文件名 : chrdevbaseApp.c
13 版本 : V1.0
14 描述 : chrdevbase 驱动测试APP。
15 其他 : 使用方法: ./chrdevbaseApp /dev/chrdevbase <1>|<2>
16 ********************************************/
17
18 static char usrdata[] = {"usr data!"};
19
20 /*
21 * @description : main 主程序
22 * @param - argc : argv 数组元素个数
23 * @param - argv : 具体参数
24 * @return : 0 成功;其他 失败
25 */
26 int main(int argc, char *argv[])
27 {
28 int fd, retval;
29 char readbuf[100], writebuf[100];
30 static char usrdata[] = {"usr data!"};
31
32 if(argc != 3){
33 printf("Error Usage!\r\n");
34 return -1;
35 }
36
37 char *filename = argv[1];
38
39 /* 打开驱动文件 */
40 fd = open(filename, O_RDWR);
41 if(fd < 0){
42 printf("Can't open file %s\r\n", filename);
43 return -1;
44 }
45
46 if(atoi(argv[2]) == 1){ /* 从驱动文件读取数据 */
47 retval = read(fd, readbuf, 50);
48 if(retval < 0){
49 printf("read file %s failed!\r\n", filename);
50 }else{
51 /* 读取成功,打印出读取成功的数据 */
52 printf("read data:%s\r\n",readbuf);
53 }
54 }
55
56 if(atoi(argv[2]) == 2){ /* 向设备驱动写数据 */
57 memcpy(writebuf, usrdata, sizeof(usrdata));
58 retval = write(fd, writebuf, 50);
59 if(retval < 0){
60 printf("write file %s failed!\r\n", filename);
61 }
62 }
63
64 /* 关闭设备 */
65 retval = close(fd);
66 if(retval < 0){
67 printf("Can't close file %s\r\n", filename);
68 return -1;
69 }
70
71 return 0;
72 }
第 18 行,数组 usrdata 是测试 APP 要向 chrdevbase 设备写入的数据。
第 32行,判断运行测试 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 写数据。
第 37行,获取要打开的设备文件名字,argv[1]保存着设备名字。
第 40行,调用 C 库中的 open 函数打开设备文件:/dev/chrdevbase。
第 46行,判断 argv[2]参数的值是 1 还是 2,因为输入命令的时候其参数都是字符串格式的,因此需要借助 atoi 函数将字符串格式的数字转换为真实的数字。
第 47 行,当 argv[2]为 1 的时候表示要从 chrdevbase 设备中读取数据,一共读取 50 字节的数据,读取到的数据保存在 readbuf 中,读取成功以后就在终端上打印出读取到的数据。
第 56 行,当 argv[2]为 2 的时候表示要向 chrdevbase 设备写数据。
第 65 行,对 chrdevbase 设备操作完成以后就关闭设备。
chrdevbaseApp.c 内容还是很简单的,就是最普通的文件打开、关闭和读写操作。
三、编译驱动程序和测试****APP
1**、编译驱动程序**
首先编译驱动程序,也就是 chrdevbase.c 这个文件,我们需要将其编译为.ko 模块,创建 Makefile 文件,然后在其中输入如下内容:
1 KERNELDIR := /home/duan/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
2 CURRENT_PATH := $(shell pwd)
3 obj-m := chrdevbase.o
4
5 build: kernel_modules
6
7 kernel_modules:
8 $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
9 clean:
10 $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
第 1 行,KERNELDIR 表示开发板所使用的 Linux 内核源码目录,使用绝对路径(需要自己修改)。
第 2 行,CURRENT_PATH 表示当前路径,直接通过运行"pwd"命令来获取当前所处路径。
第 3 行,obj-m 表示将 chrdevbase.c 这个文件编译为 chrdevbase.ko 模块。
第 8 行,具体的编译命令,后面的 modules 表示编译模块,-C 表示将当前的工作目录切
换到指定目录中,也就是 KERNERLDIR 目录。M 表示模块源码目录,"make modules"命令
中加入 M=dir 以后程序会自动到指定的 dir 目录中读取模块的源码并将其编译为.ko 文件。
Makefile 编写好以后输入"make"命令编译驱动模块,编译过程如图 所示:

编译成功以后就会生成一个叫做 chrdevbaes.ko 的文件,此文件就是 chrdevbase 设备的驱动
模块。至此,chrdevbase 设备的驱动就编译成功。
2**、编译测试****APP**
测试 APP 比较简单,只有一个文件,因此就不需要编写 Makefile 了,直接输入命令编译。
因为测试 APP 是要在 ARM 开发板上运行的,所以需要使用 arm-linux-gnueabihf-gcc 来编译,
输入如下命令:
arm-linux-gnueabihf-gcc chrdevbaseApp.c -o chrdevbaseApp
编译完成以后会生成一个叫做 chrdevbaseApp 的可执行程序,输入如下命令查看
chrdevbaseAPP
结果如下图所示:

