在上一个章节中,我们编写了一个驱动程序,这里我们要编写一个APP应用程序,实现在应用层调用驱动底层的 open 和 write 函数。
一、APP和驱动程序的区别与分工
1. 驱动程序(Driver)
- 工作在内核空间,是操作系统和硬件之间的桥梁。
- 直接控制硬件、管理硬件资源,对上层(如APP)屏蔽硬件细节。
- 只能通过内核API与其他内核部分通信,一般不能直接和用户交互。
- 典型功能:收发数据、寄存器读写、中断处理等。
- 例子:你之前写的
chrdev_test字符设备驱动。
2. 应用程序(APP)
- 工作在用户空间,直接与用户交互。
- 调用系统调用(如 open、read、write、close)来间接访问硬件,真正操作硬件时请求会流转到驱动程序。
- 负责编写界面、业务逻辑,而不是底层硬件细节。
- 例子:你即将写的测试程序,调用
/dev/device_test完成 open 和 write。
分工理解
- 应用程序像“司机”,提出“我要开车(open)、加油(write)、看仪表盘(read)”这种需求。
- 驱动程序像“发动机技术员”,根据指令完成车的具体操作,但不关心是谁让它做的。
二、APP的编写
1、c源码
APP应用,使用的是标准的C库,就是和我们之前刚学习C语言的时候用的一个风格。
在 05_char_device/ 文件夹下创建一个 test_chrdev_app.c 文件,编写下面的代码:
c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
int fd;
const char *devname = "/dev/device_test";
const char *data = "hello, driver!";
// 1. 打开设备
fd = open(devname, O_WRONLY);
if(fd < 0) {
printf("test_chrdev_app: open device failed.\n");
return -1;
}
printf("test_chrdev_app: Device opened successfully.\n");
// 2. 写数据到驱动
int ret = write(fd, data, strlen(data));
if(ret < 0) {
printf("test_chrdev_app: write device failed.\n");
close(fd);
return -1;
}
printf("test_chrdev_app: Device write successfully.\n");
// 3. 关闭设备
close(fd);
printf("test_chrdev_app: Device closed.\n");
return 0;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
2、源码讲解
这个应用程序负责 “ 打开 ” 在 /dev/ 下挂载好的device_test字符设备,并尝试将一串数据 hello, driver! 写入设备。它不会直接操作硬件,也无法直接调用内核代码,只能通过标准的系统调用间接与设备驱动交互。
c
int main()
{
int fd;
const char *devname = "/dev/device_test";
const char *data = "hello, driver!";1
2
3
4
5
2
3
4
5
- 主函数开始。
fd:文件描述符,存放open()的返回值。devname:指定要访问的设备节点路径,和前面驱动创建的/dev/device_test保持一致。data:要写入到设备的数据字符串。
c
// 1. 打开设备
fd = open(devname, O_WRONLY);
if(fd < 0) {
printf("test_chrdev_app: open device failed.\n");
return -1;
}
printf("test_chrdev_app: Device opened successfully.\n");1
2
3
4
5
6
7
2
3
4
5
6
7
- 使用系统调用
open打开设备文件,第二个参数O_WRONLY表明只写模式。 - 如果返回值 < 0,说明打开失败(比如没有驱动、节点不存在、无权限),此时打印错误并退出。
- 成功则打印“打开成功”。
原理说明
- 用户空间的
open()其实会被内核转换成对/dev/device_test这个设备节点的操作。内核通过设备号查表,定位到对应的驱动,并最终调用你写的驱动函数chrdev_open。
c
// 2. 写数据到驱动
int ret = write(fd, data, strlen(data));
if(ret < 0) {
printf("test_chrdev_app: write device failed.\n");
close(fd);
return -1;
}
printf("test_chrdev_app: Device write successfully.\n");1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
- 调用
write()向打开的设备文件写入数据,返回值是实际写入的字节数(<0则是错误)。 - 如果失败(如驱动没有实现write、权限问题等),则报错并提前关闭文件退出。
- 成功则提示“写入成功”。
原理说明
- 这里的
write()会被内核拦截,通过设备号查找你的驱动,最终会调用驱动代码中的chrdev_write()。 - 实测时你可通过
dmesg查看驱动日志信息,验证两者的调用关系。
c
// 3. 关闭设备
close(fd);
printf("test_chrdev_app: Device closed.\n");
return 0;1
2
3
4
2
3
4
- 用标准
close()释放之前打开的文件描述符。 - 关闭后会触发驱动的
chrdev_release()(如果有实现)。 - 最后返回0,表示程序顺利结束。
三、APP编译
使用下面的命令进行编译:
bash
/home/lckfb/TaishanPi-3-Linux/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gcc test_chrdev_app.c -o test_chrdev_app1
命令格式是:
<SDK的gcc交叉编译器> <源码.c文件> -o <最终生成的可执行文件名字>
-o重名的意思,后面紧跟着最终想要生成的名字。
- SDK的gcc交叉编译器:这个就和之前我们在
Makefile中编写的路径一致只不过变为了aarch64-none-linux-gnu-gcc,不单单是只有前缀了。
最终就是这样的:
四、测试
将这个 test_chrdev_app 复制到开发板中(U盘、TF卡、SSH都可以),并运行:
bash
sudo ./test_chrdev_app1
最终的效果是这样的:
APP程序依次运行了 open、write 和 close 这三个函数操作,而我们的驱动也相应的做出了三个一样的动作:
- open
- write
- release
所以我们在APP的调用中通过 /dev/device_test 这个设备,实现了APP的 open、write 和 close 三个函数和驱动的三个 open write release 函数的对应调用。