嵌入式Linux应用层开发:环境搭建、文件IO
文件 I/O(Input/Output,即输入 / 输出)是计算机编程中用于处理文件的重要操作,主要涉及到将数据从文件读取到内存,以及将内存中的数据写入文件。:偏底层控制、需要精确设置文件的权限和打开标志、操作设备文件(如串口、磁盘设备等)需要特殊的权限和控制、对性能有极高要求、想自行管理缓冲区等等。:系统调用是操作系统提供给用户程序的接口,用于访问底层的硬件资源和操作系统服务;:库函数是高级语言
从Hello World到文件IO调用:嵌入式开发环境全搭建
【本文基于 linux平台 验证通过,适用于主流嵌入式Linux平台】
👉 文末提供完整代码仓库+交叉编译工具链下载!
一、嵌入式Linux开发环境搭建
1.1 交叉编译链配置
交叉编译知识点
交叉编译是在一个平台上生成另一个平台可执行代码的过程,宿主平台:进行交叉编译的计算机系统,通常要求有足够的计算资源和存储空间来支持编译过程;目标平台:明确要生成可执行代码的目标硬件平台和操作系统;
为什么需要交叉编译?
嵌入式设备资源有限,无法直接在设备上高效编译代码。交叉编译允许在x86_64主机上生成ARM/RISC-V等架构的可执行文件。
配置步骤(宿主平台以ubuntu20.04版本,目标平台以ARM64为例)
-
下载工具链(以Linaro GCC为例)
wget https://releases.linaro.org/components/toolchain/binaries/latest-7/aarch64-linux-gnu/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar.xz -
解压并安装
tar -xvf gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar.xz sudo mv gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu /opt/ -
配置环境变量
# 添加到 ~/.bashrc vim ~/.bashrc # 将下面一行添加到末尾 export PATH=/opt/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin:$PATH # 执行 source ~/.bashrc -
验证安装
aarch64-linux-gnu-gcc --version # 输出应包含:gcc version 7.5.0 (Linaro GCC 7.5-2019.12)
1.2 Hello World测试
创建第一个嵌入式程序 hello.c:
#include <stdio.h>
int main() {
printf("Hello Embedded Linux!\n");
return 0;
}
交叉编译并通过一定的方式把可执行文件传输到设备上:
aarch64-linux-gnu-gcc -static hello.c -o hello # 静态链接
# 我这里使用的是adb push 方式
# 在设备上给可执行文件权限 chmod +x hello 然后运行
./hello # 输出:Hello Embedded Linux!
二、文件IO精讲
文件 I/O(Input/Output,即输入 / 输出)是计算机编程中用于处理文件的重要操作,主要涉及到将数据从文件读取到内存,以及将内存中的数据写入文件。下面将从系统调用和库函数的角度详细介绍文件 I/O。
2.1系统调用基础
系统调用:系统调用是操作系统提供给用户程序的接口,用于访问底层的硬件资源和操作系统服务;是用户程序与操作系统内核之间进行交互的桥梁。
适合场景:偏底层控制、需要精确设置文件的权限和打开标志、操作设备文件(如串口、磁盘设备等)需要特殊的权限和控制、对性能有极高要求、想自行管理缓冲区等等。
| 文件IO函数 | 作用 | 典型返回值 |
|---|---|---|
| open | 打开/创建文件 | 成功返回fd,失败-1 |
| read | 从文件读取数据 | 成功返回字节数,失败-1 |
| write | 向文件写入数据 | 成功返回写入字节数,失败-1 |
| lseek | 移动文件读写指针的位置 | 成功返回文件偏移量(从文件开头字节数),失败-1 |
| fstat | 获取文件的状态信息 | 成功返回0,失败-1 |
| chmod | 改变文件权限 | 成功返回0,失败-1 |
| close | 关闭文件描述符 | 成功0,失败-1 |
open函数
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
pathname:要打开或创建的文件路径名,可以是绝对路径或者相对路径flags:文件的打开方式,可以组合使用,常见的标志有O_RDONLY:以只读方式打开文件O_WRONLY:以只写方式打开文件O_RDWR:以读写方式打开文件O_CREAT:若文件不存在,则创建该文件,此时需要第三个参数 mode 来指定文件的权限O_TRUNC:如果文件已存在且以写方式打开,则将文件截断为零长度O_APPEND:以追加方式打开文件,每次写操作都将数据追加到文件末尾
mode:当使用O_CREAT标志时,需要此参数来指定新创建文件的权限。权限用八进制数表示,例如0644表示文件所有者有读写权限,组用户和其他用户有读权限。
read函数
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
fd:要读取数据的文件的文件描述符,通常由open函数返回buf:指向用于存储读取数据的缓冲区的指针count:要读取的最大字节数。
write函数
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
fd:要写入数据的文件的文件描述符,通常由open函数返回buf:指向用于存储写入数据的缓冲区的指针count:要写入的字节数。
lseek函数
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
fd:要操作的文件的文件描述符offset:相对于whence参数指定的位置的偏移量,以字节为单位。可以是正数(向后偏移)、负数(向前偏移)或零whence:指定偏移量的起始位置,常用的值有SEEK_SET:从文件开头开始计算偏移量SEEK_CUR:从当前文件指针位置开始计算偏移量SEEK_END:从文件末尾开始计算偏移量。
fstat函数
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int fstat(int fd, struct stat *statbuf);
fd:要获取状态信息的文件的文件描述符statbuf:指向struct stat结构体的指针,用于存储文件的状态信息。struct stat结构体包含了文件的各种属性,如文件大小、权限、修改时间等。
chmod函数
#include <sys/stat.h>
int chmod(const char *pathname, mode_t mode);
pathname:要改变权限的文件的路径名mode:新的文件权限,用八进制数表示
close函数
#include <unistd.h>
int close(int fd);
fd:要关闭的文件描述符。
2.2 库函数基础
库函数:库函数是高级语言或开发库提供的函数,他们通常是基于系统调用实现的,目的是为了程序员方便开发,提供更高级、更易用的接口。
适合场景:跨平台开发(可移植性强)、简单文件读写、格式化的数据存储与读取等等。
| 文件IO函数 | 作用 | 典型返回值 |
|---|---|---|
| fopen | 按指定模式打开文件 | 成功返回 FILE* 指针,失败返回 NULL |
| fread | 从文件流读数据到缓冲区 | 成功返回读取元素个数,出错或到文件尾可能小于指定值 |
| fwrite | 将缓冲区数据写入文件流 | 成功返回写入元素个数,出错可能小于指定值 |
| fgets | 从文件流读一行到缓冲区 | 成功返回缓冲区指针,遇文件尾或出错返回 NULL |
| fputs | 将字符串写入文件流 | 成功返回非负整数,出错返回 EOF |
| fscanf | 从文件流按格式读数据到变量 | 成功返回匹配赋值的输入项数量,出错或到文件尾返回 EOF |
| fprintf | 按格式将数据写入文件流 | 成功返回写入字符数,出错返回负数 |
| fseek | 设置文件流的读写位置 | 成功返回 0,出错返回非 0 |
| fclose | 关闭打开的文件流 | 成功返回 0,出错返回 EOF |
fopen函数
#include <stdio.h>
FILE *fopen(const char *pathname, const char *mode);
pathname:要打开或创建的文件路径名,可以是绝对路径或者相对路径mode:字符串,用来指定文件的打开模式,常见的模式有"r":以只读模式打开文件,文件必须存在"w":以只写模式打开文件,若文件存在则将其内容清空;若文件不存在则创建新文件"a":以追加模式打开文件,若文件存在,写入的数据添加到文件末尾;若文件不存在则创建新文件"r+":以读写模式打开文件,文件必须存在"w+":以读写模式打开文件,若文件存在则清空内容;若不存在则创建新文件"a+":以读写模式打开文件,若文件存在,写入的数据会添加到文件末尾;若不存在则创建新文件。
fread函数
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
ptr:指向用于存储读取数据的内存缓冲区的指针size:每个要读取的元素的大小(以字节为单位)nmemb:要读取的元素个数stream:指向 FILE 对象的指针,代表要从中读取数据的文件流,通常为fopen返回值。
fwrite函数
#include <stdio.h>
size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
ptr:指向包含要写入数据的内存缓冲区的指针size:每个要写入的元素的大小(以字节为单位)nmemb:要写入的元素个数stream:指向 FILE 对象的指针,代表要将数据写入的文件流,通常为fopen返回值。
fgets函数
#include <stdio.h>
char *fgets(char *s, int size, FILE *stream);
s:指向用于存储读取的字符串的字符数组的指针size:要读取的最大字符数(包含字符串结束符'\0')stream:指向 FILE 对象的指针,代表要从中读取数据的文件流。
fputs函数
#include <stdio.h>
int fputs(const char *s, FILE *stream);
s:指向要写入文件流的以'\0'结尾的字符串的指针stream:指向 FILE 对象的指针,代表要将字符串写入的文件流。
fscanf函数
#include <stdio.h>
int fscanf(FILE *stream, const char *format, ...);
stream:指向 FILE 对象的指针,代表要从中读取数据的文件流format:格式控制字符串,用于指定输入数据的格式...:可变参数列表,用于存储从文件流中读取的数据。
fprintf函数
#include <stdio.h>
int fprintf(FILE *stream, const char *format, ...);
stream:指向 FILE 对象的指针,代表要将数据写入的文件流format:格式控制字符串,用于指定输出数据的格式...:可变参数列表,包含要按照 format 格式写入文件流的数据。
fseek函数
#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);
fd:要获取状态信息的文件的文件描述符statbuf:指向struct stat结构体的指针,用于存储文件的状态信息。struct stat结构体包含了文件的各种属性,如文件大小、权限、修改时间等。stream:指向 FILE 对象的指针,代表要操作的文件流offset:相对于 whence 指定位置的偏移量(以字节为单位),可为正、负或零whence:指定偏移量的起始位置,有以下取值SEEK_SET:从文件开头开始计算偏移量SEEK_CUR:从当前文件指针位置开始计算偏移量SEEK_END:从文件末尾开始计算偏移量。
fclose函数
#include <stdio.h>
int fclose(FILE *stream);
stream:指向 FILE 对象的指针,代表要关闭的文件流。
2.3 系统调用代码示例
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define BUFFER_SIZE 100
int main() {
int fd;
char buffer[BUFFER_SIZE] = "Hello, this is a test using system calls.";
char read_buffer[BUFFER_SIZE];
// 打开文件,如果文件不存在则创建,以读写模式打开
fd = open("test_sys.txt", O_RDWR | O_CREAT, 0666);
if (fd == -1) {
perror("open");
return EXIT_FAILURE;
}
// 写入数据到文件
ssize_t bytes_written = write(fd, buffer, strlen(buffer));
if (bytes_written == -1) {
perror("write");
close(fd);
return EXIT_FAILURE;
}
// 将文件指针移动到文件开头
if (lseek(fd, 0, SEEK_SET) == -1) {
perror("lseek");
close(fd);
return EXIT_FAILURE;
}
// 从文件中读取数据
ssize_t bytes_read = read(fd, read_buffer, BUFFER_SIZE);
if (bytes_read == -1) {
perror("read");
close(fd);
return EXIT_FAILURE;
}
read_buffer[bytes_read] = '\0';
printf("Read from file: %s\n", read_buffer);
// 关闭文件
if (close(fd) == -1) {
perror("close");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
2.4 库函数代码示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFFER_SIZE 100
int main() {
FILE *fp;
char buffer[BUFFER_SIZE] = "Hello, this is a test using library functions.";
char read_buffer[BUFFER_SIZE];
// 打开文件,以读写模式打开
fp = fopen("test_lib.txt", "w+");
if (fp == NULL) {
perror("fopen");
return EXIT_FAILURE;
}
// 写入数据到文件
size_t items_written = fwrite(buffer, sizeof(char), strlen(buffer), fp);
if (items_written != strlen(buffer)) {
perror("fwrite");
fclose(fp);
return EXIT_FAILURE;
}
// 将文件指针移动到文件开头
if (fseek(fp, 0, SEEK_SET) != 0) {
perror("fseek");
fclose(fp);
return EXIT_FAILURE;
}
// 从文件中读取数据
size_t items_read = fread(read_buffer, sizeof(char), BUFFER_SIZE, fp);
if (items_read == 0 && ferror(fp)) {
perror("fread");
fclose(fp);
return EXIT_FAILURE;
}
read_buffer[items_read] = '\0';
printf("Read from file: %s\n", read_buffer);
// 关闭文件
if (fclose(fp) != 0) {
perror("fclose");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
三、实战:文件复制器和文本编辑器
3.1 文件数据复制代码
/****** 文本复制器 ******/
#include <stdio.h> // 标准输入输出库
#include <stdlib.h> // 提供 EXIT_SUCCESS 和 EXIT_FAILURE
#include <fcntl.h> // 文件控制选项(如 O_RDONLY, O_WRONLY, O_CREAT, O_TRUNC)
#include <unistd.h> // 提供 `read`, `write`, `close` 等系统调用
#define BUFFER_SIZE 1024 // 定义缓冲区大小,每次读取 1024 字节,提高效率
int main(int argc, char *argv[])
{
// 检查命令行参数是否正确(程序名 + 2 个参数)
if (argc != 3) {
fprintf(stderr, "Usage: %s <source_file> <destination_file>\n", argv[0]);
return EXIT_FAILURE; // 参数错误,退出程序
}
const char *source_file = argv[1]; // 获取源文件名
const char *destination_file = argv[2]; // 获取目标文件名
// 以只读模式打开源文件
int source_fd = open(source_file, O_RDONLY);
if (source_fd == -1) { // 检查是否打开失败
perror("Failed to open source file"); // 输出错误信息
return EXIT_FAILURE; // 退出程序
}
// 以写入模式打开目标文件(如果不存在则创建,如果存在则清空)
int destination_fd = open(destination_file, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (destination_fd == -1) { // 检查是否打开失败
perror("Failed to open destination file");
close(source_fd); // 关闭源文件,防止资源泄漏
return EXIT_FAILURE;
}
char buffer[BUFFER_SIZE]; // 用于存储读取的文件内容
ssize_t bytes_read; // 存储 `read` 返回的字节数
// 逐块读取源文件,并写入目标文件
while ((bytes_read = read(source_fd, buffer, BUFFER_SIZE)) > 0) {
ssize_t bytes_written = write(destination_fd, buffer, bytes_read); // 写入文件
if (bytes_written == -1) { // 写入失败
perror("Failed to write to destination file");
close(source_fd);
close(destination_fd);
return EXIT_FAILURE;
}
if (bytes_written != bytes_read) { // 检查是否完全写入
fprintf(stderr, "Warning: Written bytes do not match read bytes\n");
}
}
// 检查 `read` 是否遇到错误
if (bytes_read == -1) {
perror("Failed to read from source file");
close(source_fd);
close(destination_fd);
return EXIT_FAILURE;
}
// 关闭源文件
if (close(source_fd) == -1) {
perror("Failed to close source file");
close(destination_fd);
return EXIT_FAILURE;
}
// 关闭目标文件
if (close(destination_fd) == -1) {
perror("Failed to close destination file");
return EXIT_FAILURE;
}
printf("File copied successfully.\n"); // 复制成功
return EXIT_SUCCESS;
}
3.2 交叉编译与部署
# 交叉编译
aarch64-linux-gnu-gcc -static file_copier.c -o copy
# 使用自己的方式将可执行文件传递到开发板
# 给权限后执行
./copy 1.txt 2.txt
# 输出:File copied successfully.
文本复制、文本编辑器源码都在github上,自行下载。
四、资源下载
- 完整代码仓库:GitHub链接
- 工具链合集:
- ARMv8:Linaro GCC 7.5
- RISC-V:SiFive Freedom Tools
🔥 下篇预告:《进程与线程:多任务开发的避坑指南》——揭秘嵌入式系统中的并发陷阱!
📢 关注专栏,评论区留言“嵌入式”获取《嵌入式Linux开发速查手册》!
“掌握环境搭建,就握住了嵌入式开发的第一把钥匙。从今天起,让你的代码在硬件上起舞!” 💻🔧
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐

所有评论(0)