In the previous chapter, we wrote a driver program. Here, we need to write an APP application to call the driver's underlying open and write functions at the application layer.
1. Difference and Division of APP and Driver
1. Driver (Driver)
- Works in kernel space, is a bridge between operating system and hardware.
- Directly controls hardware, manages hardware resources, and shields hardware details from upper layers (such as APP).
- Can only communicate with other kernel parts through kernel APIs, generally cannot interact directly with users.
- Typical functions: sending/receiving data, register read/write, interrupt handling, etc.
- Example: The
chrdev_testcharacter device driver you wrote earlier.
2. Application (APP)
- Works in user space, directly interacts with users.
- Calls system calls (such as open, read, write, close) to indirectly access hardware. When actually operating hardware, requests flow to the driver.
- Responsible for writing interfaces and business logic, not low-level hardware details.
- Example: The test program you are about to write, calling
/dev/device_testto complete open and write.
Understanding the division
- Applications are like "drivers" who make requests like "I want to drive (open), refuel (write), check the dashboard (read)".
- Drivers are like "engine technicians" who complete specific car operations according to instructions but don't care who asked them to do it.
2. APP Writing
1. C source code
The APP application uses the standard C library, just like when we first learned C language.
Create a test_chrdev_app.c file in the 05_char_device/ folder and write the following code:
#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. Open device
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. Write data to driver
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 device
close(fd);
printf("test_chrdev_app: Device closed.\n");
return 0;
}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. Source Code Explanation
This application is responsible for " opening " the device_test character device mounted under /dev/, and trying to write a string of data hello, driver! to the device. It does not directly operate hardware nor directly call kernel code. It can only indirectly interact with the device driver through standard system calls.
int main()
{
int fd;
const char *devname = "/dev/device_test";
const char *data = "hello, driver!";2
3
4
5
- Main function starts.
fd: File descriptor, stores the return value ofopen().devname: Specifies the device node path to access, consistent with the/dev/device_testcreated by the driver earlier.data: The data string to write to the device.
// 1. Open device
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
3
4
5
6
7
- Use system call
opento open the device file. The second parameterO_WRONLYindicates write-only mode. - If return value < 0, it means opening failed (such as no driver, node doesn't exist, no permission). Print error and exit.
- On success, print "opened successfully".
Principle explanation
- The
open()in user space is actually converted by the kernel into an operation on the/dev/device_testdevice node. The kernel finds the corresponding driver through the device number lookup table and finally calls your driver functionchrdev_open.
// 2. Write data to driver
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");2
3
4
5
6
7
8
- Call
write()to write data to the opened device file. The return value is the actual number of bytes written (<0 means error). - If failed (such as driver didn't implement write, permission issue, etc.), report error and close file to exit early.
- On success, prompt "write successfully".
Principle explanation
- The
write()here will be intercepted by the kernel, which finds your driver through device number lookup and finally calls the driver code'schrdev_write(). - During testing, you can view driver log information through
dmesgto verify the calling relationship between the two.
// 3. Close device
close(fd);
printf("test_chrdev_app: Device closed.\n");
return 0;2
3
4
- Use standard
close()to release the previously opened file descriptor. - After closing, the driver's
chrdev_release()will be triggered (if implemented). - Finally return 0, indicating the program ended smoothly.
3. APP Compilation
Use the following command to compile:
/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_appCommand format:
<SDK's gcc cross compiler> <source.c file> -o <final executable file name>
-omeans rename, followed directly by the name you want to generate.
- SDK's gcc cross compiler: This is the same as the path we wrote in the
Makefilebefore, just changed toaarch64-none-linux-gnu-gcc, not just the prefix.
The result is like this:
4. Testing
Copy this test_chrdev_app to the development board (USB drive, TF card, SSH all work) and run:
sudo ./test_chrdev_appThe final effect is like this:
The APP program sequentially executed open, write, and close operations on the device, and our driver correspondingly made three identical actions:
- open
- write
- release
So through the device /dev/device_test in the APP call, we achieved the corresponding calls between the APP's three functions open, write, close and the driver's three functions open write release.