【嵌入式Linux应用开发基础】read函数与write函数

目录

一、read 函数

1.1. 函数原型

1.2. 参数说明

1.3. 返回值

1.4. 示例代码

二、write 函数

2.1. 函数原型

2.2. 参数说明

2.3. 返回值

2.4. 示例代码

三、关键注意事项

3.1 部分读写

3.2 错误处理

3.3 阻塞与非阻塞模式

3.4 数据持久化

3.5 线程安全

四、嵌入式场景应用

4.1. 文件数据读写

场景:嵌入式系统中的应用程序常常需要从配置文件中读取参数,以此来初始化系统。例如,网络设备的配置文件包含 IP 地址、子网掩码、网关等信息,应用程序需要读取这些信息来完成网络配置。代码示例

4.2. 设备驱动交互

4.3. 进程间通信(IPC)

4.4. 网络通信

五、常见问题

5.1. read函数常见问题

5.2. write函数常见问题

5.3 通用建议

六、总结


在嵌入式Linux应用开发中,readwrite函数是文件I/O操作中最基础、最常用的两个系统调用。它们用于从文件描述符(file descriptor)指向的文件或设备中读取数据和向其中写入数据。

一、read 函数

1.1. 函数原型

#include <unistd.h>ssize_t read(int fd, void *buf, size_t count);

1.2. 参数说明

  • fd:文件描述符,由 open 函数返回的一个非负整数,用于标识要读取数据的文件、设备等。
  • buf:指向用于存储读取数据的缓冲区的指针,数据将被读取到这个缓冲区中。
  • count:期望读取的字节数,即希望从文件描述符对应的文件或设备中读取的最大字节数。

1.3. 返回值

  • 大于 0:表示实际成功读取的字节数。
  • 等于 0:表示已经到达文件末尾(EOF),没有更多数据可供读取。
  • 等于 -1:表示读取操作失败,此时 errno 会被设置为相应的错误码,用于指示具体的错误原因。

1.4. 示例代码

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>int main() {int fd = open("example.txt", O_RDONLY);if (fd == -1) {perror("open");return 1;}char buffer[100];ssize_t bytesRead = read(fd, buffer, sizeof(buffer) - 1);if (bytesRead == -1) {perror("read");close(fd);return 1;}buffer[bytesRead] = '\0'; // 确保字符串以NULL结尾printf("Read %zd bytes: %s\n", bytesRead, buffer);close(fd);return 0;
}

二、write 函数

2.1. 函数原型

#include <unistd.h>ssize_t write(int fd, const void *buf, size_t count);

2.2. 参数说明

  • fd:文件描述符,标识要写入数据的文件、设备等。
  • buf:指向包含要写入数据的缓冲区的指针。
  • count:要写入的字节数,即希望将缓冲区中多少字节的数据写入到文件描述符对应的文件或设备中。

2.3. 返回值

  • 大于 0:表示实际成功写入的字节数。
  • 等于 0:通常表示没有写入任何数据,可能是由于某些特殊情况(如文件系统已满但还未返回错误)。
  • 等于 -1:表示写入操作失败,errno 会被设置为相应的错误码,用于指示具体的错误原因。

2.4. 示例代码

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>int main() {int fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);if (fd == -1) {perror("open");return 1;}const char *message = "Hello, World!\n";ssize_t bytesWritten = write(fd, message, strlen(message));if (bytesWritten == -1) {perror("write");close(fd);return 1;}printf("Written %zd bytes\n", bytesWritten);close(fd);return 0;
}

三、关键注意事项

3.1 部分读写

  • 原因:数据未就绪(如网络)、资源限制(如管道缓冲区满)、信号中断等。

  • 处理方式:循环调用函数,直至完成全部数据传输。

    示例代码(读操作):

ssize_t total_read = 0;
while (total_read < count) {ssize_t n = read(fd, buf + total_read, count - total_read);if (n == 0) break; // EOFif (n < 0 && errno != EINTR) break; // 非中断错误if (n > 0) total_read += n;
}

3.2 错误处理

  • 常见errno

    • EAGAIN/EWOULDBLOCK:非阻塞模式下无数据可读或写缓冲区满。

    • EINTR:操作被信号中断。

    • EBADF:无效文件描述符。

  • 处理建议

    • EINTR需重试操作。

    • 对非阻塞I/O的EAGAIN需结合select/poll等待就绪。

3.3 阻塞与非阻塞模式

  • 设置非阻塞模式

int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);

3.4 数据持久化

  • 立即同步:调用fsync(fd)强制将内核缓冲区数据写入存储设备。

3.5 线程安全

  • 多线程操作同一文件描述符需加锁(如pthread_mutex)。

四、嵌入式场景应用

4.1. 文件数据读写

①配置文件读取

  • 场景:嵌入式系统中的应用程序常常需要从配置文件中读取参数,以此来初始化系统。例如,网络设备的配置文件包含 IP 地址、子网掩码、网关等信息,应用程序需要读取这些信息来完成网络配置。

  • 代码示例

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#define BUFFER_SIZE 1024int main() {int fd = open("config.txt", O_RDONLY);if (fd == -1) {perror("open");return 1;}char buffer[BUFFER_SIZE];ssize_t bytes_read = read(fd, buffer, BUFFER_SIZE);if (bytes_read == -1) {perror("read");} else if (bytes_read > 0) {buffer[bytes_read] = '\0';// 处理读取到的配置信息}close(fd);return 0;
}

 ②数据文件写入

  • 场景:在数据采集系统中,需要将采集到的数据存储到文件中,以便后续分析和处理。比如,温度传感器每隔一段时间采集一次温度数据,应用程序将这些数据写入到文件中。
  • 代码示例
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>#define DATA "25.5"int main() {int fd = open("data.txt", O_WRONLY | O_CREAT | O_APPEND, 0644);if (fd == -1) {perror("open");return 1;}ssize_t bytes_written = write(fd, DATA, strlen(DATA));if (bytes_written == -1) {perror("write");}close(fd);return 0;
}

4.2. 设备驱动交互

①传感器数据读取

  • 场景:嵌入式系统通常会连接各种传感器,如加速度计、陀螺仪等。应用程序通过 read 函数从相应的设备文件中读取传感器数据。
  • 代码示例
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>#define SENSOR_DEVICE "/dev/sensor"
#define BUFFER_SIZE 32int main() {int fd = open(SENSOR_DEVICE, O_RDONLY);if (fd == -1) {perror("open");return 1;}char buffer[BUFFER_SIZE];ssize_t bytes_read = read(fd, buffer, BUFFER_SIZE);if (bytes_read == -1) {perror("read");} else if (bytes_read > 0) {buffer[bytes_read] = '\0';// 处理传感器数据}close(fd);return 0;
}

②设备控制命令写入

  • 场景:对于一些可控制的设备,如 LED 灯、电机等,应用程序可以通过 write 函数向设备文件写入控制命令,从而实现对设备的控制。
  • 代码示例
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>#define LED_DEVICE "/dev/led"
#define COMMAND "ON"int main() {int fd = open(LED_DEVICE, O_WRONLY);if (fd == -1) {perror("open");return 1;}ssize_t bytes_written = write(fd, COMMAND, strlen(COMMAND));if (bytes_written == -1) {perror("write");}close(fd);return 0;
}

③串口/UART通信

串口设备(如/dev/ttyS0)是嵌入式系统中常见的通信接口,readwrite用于收发数据:

// 配置串口后...
char tx_data[] = "Hello UART!";
write(uart_fd, tx_data, strlen(tx_data)); // 发送数据char rx_data[32];
ssize_t len = read(uart_fd, rx_data, sizeof(rx_data)); // 接收数据

4.3. 进程间通信(IPC)

①管道通信

  • 场景:在嵌入式系统中,不同进程之间可能需要进行数据交换。管道是一种简单的进程间通信方式,一个进程通过 write 函数向管道写入数据,另一个进程通过 read 函数从管道读取数据。
  • 代码示例
#include <stdio.h>
#include <unistd.h>
#include <string.h>#define BUFFER_SIZE 1024int main() {int pipefd[2];if (pipe(pipefd) == -1) {perror("pipe");return 1;}pid_t pid = fork();if (pid == -1) {perror("fork");return 1;}if (pid == 0) {// 子进程:读取数据close(pipefd[1]);char buffer[BUFFER_SIZE];ssize_t bytes_read = read(pipefd[0], buffer, BUFFER_SIZE);if (bytes_read == -1) {perror("read");} else if (bytes_read > 0) {buffer[bytes_read] = '\0';printf("Child process read: %s\n", buffer);}close(pipefd[0]);} else {// 父进程:写入数据close(pipefd[0]);const char *message = "Hello from parent!";ssize_t bytes_written = write(pipefd[1], message, strlen(message));if (bytes_written == -1) {perror("write");}close(pipefd[1]);}return 0;
}

4.4. 网络通信

套接字数据读写

  • 场景:在嵌入式网络应用中,通过套接字进行网络通信时,使用 read 函数接收网络数据,使用 write 函数发送网络数据。
  • 代码示例(简单 TCP 客户端)
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8080
#define BUFFER_SIZE 1024int main() {int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd == -1) {perror("socket");return 1;}struct sockaddr_in server_addr;server_addr.sin_family = AF_INET;server_addr.sin_port = htons(SERVER_PORT);inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr);if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {perror("connect");close(sockfd);return 1;}const char *message = "Hello, server!";ssize_t bytes_written = write(sockfd, message, strlen(message));if (bytes_written == -1) {perror("write");}char buffer[BUFFER_SIZE];ssize_t bytes_read = read(sockfd, buffer, BUFFER_SIZE);if (bytes_read == -1) {perror("read");} else if (bytes_read > 0) {buffer[bytes_read] = '\0';printf("Received from server: %s\n", buffer);}close(sockfd);return 0;
}

五、常见问题

5.1. read函数常见问题

①读取到的字节数少于请求数

  • 原因
    • 读普通文件时,在读到请求字节数之前已到达文件尾端。
    • 从终端设备读时,通常一次最多读一行。
    • 从网络读时,网络中的缓冲机构可能造成返回值小于请求读的字节数。
    • 某些面向记录的设备(如磁带),一次最多返回一个记录。
  • 解决方案
    • 在读取文件时,需要检查返回值是否小于请求字节数,并处理文件尾端的情况。
    • 对于从终端设备或网络读取的数据,需要采用适当的缓冲机制来处理数据。

②读取操作失败

  • 原因
    • 文件描述符无效或没有读权限。
    • 提供的缓冲区指针无效。
    • 文件已被其他进程锁定或删除。
  • 解决方案
    • 确保文件描述符有效且具有读权限。
    • 检查缓冲区指针的有效性。
    • 使用文件锁或其他同步机制来避免文件被其他进程锁定或删除。

③读取的数据不准确

  • 原因
    • 文件指针未正确设置。
    • 文件内容在读取过程中被其他进程修改。
  • 解决方案
    • 在读取文件之前,确保文件指针已正确设置到所需的位置。
    • 使用文件锁或其他同步机制来避免文件内容在读取过程中被其他进程修改。

5.2. write函数常见问题

①写入操作失败

  • 原因
    • 文件描述符无效或没有写权限。
    • 磁盘已满或文件系统已满。
    • 提供的缓冲区指针无效。
  • 解决方案
    • 确保文件描述符有效且具有写权限。
    • 检查磁盘和文件系统的剩余空间。
    • 检查缓冲区指针的有效性。

②写入的字节数少于请求数

  • 原因
    • 磁盘已满或文件系统限制导致无法写入更多数据。
    • 网络或设备缓冲区已满,导致写入操作被阻塞或提前返回。
  • 解决方案
    • 在写入文件之前,检查磁盘和文件系统的剩余空间。
    • 对于网络或设备写入操作,需要采用适当的缓冲机制和重试策略来处理写入失败的情况。

③写入的数据未立即生效

  • 原因:数据被写入内核缓冲区,而尚未被刷新到磁盘。
  • 解决方案:使用fsyncfdatasync函数来强制将缓冲区中的数据同步到磁盘。

5.3 通用建议

  • 错误处理
    • 在使用readwrite函数时,务必检查返回值,并根据返回值进行相应的错误处理。
    • 可以使用errno变量来获取更详细的错误信息。
  • 资源管理
    • 在使用文件描述符时,要确保在不再需要时关闭它们,以释放系统资源。
    • 对于网络套接字或其他资源,也需要进行适当的资源管理和释放。
  • 同步与并发
    • 在多进程或多线程环境中,需要确保对文件或其他资源的访问是同步的,以避免数据竞争和不一致性。
    • 可以使用文件锁、信号量或其他同步机制来实现这一点。

六、总结

readwrite函数是嵌入式Linux应用开发中用于文件I/O操作的基础工具。通过这两个函数,可以实现从文件或设备读取数据和向文件或设备写入数据。了解并正确使用这些函数,对于开发稳定、高效的嵌入式应用程序至关重要。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/16504.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

进程状态

目录 1.进程排队 硬件的队列 进程排队 2.进程的三大状态 什么是状态 运行状态 阻塞状态 挂起状态 3.Linux系统中的进程状态 4.僵尸状态 5.孤儿进程 1.进程排队 硬件的队列 计算机是由很多硬件组成的&#xff0c;操作系统为了管理这些硬件&#xff0c;通常需要为这…

项目复盘:提炼项目成功与失败的经验

项目复盘&#xff0c;顾名思义&#xff0c;就是在项目结束后&#xff0c;对整个项目过程进行全面、系统、深入的回顾与总结。它不仅仅是对项目成果的简单评价&#xff0c;更是对项目执行过程中所有细节、决策、挑战与解决方案的深入剖析。通过复盘&#xff0c;我们可以清晰地看…

Rhel Centos环境开关机自动脚本

Rhel Centos环境开关机自动脚本 1. 业务需求2. 解决方法2.1 rc.local2.2 rc.d2.3 systemd2.4 systemd附着的方法2.5 tuned 3. 测试 1. 业务需求 一台较老的服务器上面业务比较简单,提供一个简单的网站,但已经没有业务的运维人员. 想达到的效果: 由于是非标准的apache或者nginx…

网络安全威胁是什么

1.网络安全威胁的概念 网络安全威胁指网络中对存在缺陷的潜在利用&#xff0c;这些缺陷可能导致信息泄露、系统资源耗尽、非法访问、资源被盗、系统或数据被破坏等。 2.网络安全威胁的类型 物理威胁系统漏洞威胁身份鉴别威胁线缆连接威胁有害程序危险 &#xff08;1&#x…

网络工程师 (30)以太网技术

一、起源与发展 以太网技术起源于20世纪70年代&#xff0c;最初由Xerox公司的帕洛阿尔托研究中心&#xff08;PARC&#xff09;开发。最初的以太网采用同轴电缆作为传输介质&#xff0c;数据传输速率为2.94Mbps&#xff08;后发展为10Mbps&#xff09;&#xff0c;主要用于解决…

Java 循环结构进阶

二重循环 1.一个循环体内又包含另一个完整的循环结构 2.外城循环变量变化一次&#xff0c;内层循环变量要变化一遍。 二重循环-冒泡排序

SSL域名证书怎么申请?

在数字化时代&#xff0c;网络安全已成为企业和个人不可忽视的重要议题。SSL&#xff08;Secure Sockets Layer&#xff0c;安全套接层&#xff09;域名证书&#xff0c;作为保障网站数据传输安全的关键工具&#xff0c;其重要性日益凸显。 一、SSL域名证书&#xff1a;网络安…

玩转观察者模式

文章目录 什么是观察者模式解决方案结构适用场景实现方式观察者模式优缺点优点:缺点:什么是观察者模式 观察者模式通俗点解释就是你在观察别人,别人有什么变化,你就做出什么调整。观察者模式是一种行为设计模式,允许你定义一种订阅机制,可在对象事件发生时通知多个“观察…

使用mermaid画流程图

本文介绍使用mermaid画流程图&#xff0c;并给出几个示例。 背景 目前&#xff0c;除有明确格式要求的文档外&#xff0c;笔者一般使用markdown写文档、笔记。当文档有图片时&#xff0c;使用Typora等软件可实时渲染&#xff0c;所见即所得。但如果文档接收方没有安装相关工具…

【JVM详解四】执行引擎

一、概述 Java程序运行时&#xff0c;JVM会加载.class字节码文件&#xff0c;但是字节码并不能直接运行在操作系统之上&#xff0c;而JVM中的执行引擎就是负责将字节码转化为对应平台的机器码让CPU运行的组件。 执行引擎是JVM核心的组成部分之一。可以把JVM架构分成三部分&am…

Vim操作笔记

注&#xff1a;本篇文章是追加笔记&#xff0c;用于记录自己的常用操作。 将文本中A字符串替换成B字符串 基本语法&#xff1a; :{范围}s/{目标}/{替换}/{标志} 作用范围 分为前行(:s)、全文(:%s)、选区(:start,ends)等。选区可以在Visual模式下选择区域后输入&#xff1a…

Linux之kernel(1)系统基础理论(2)

Linux之Kernel(1)系统基础理论(2) Author: Once Day Date: 2025年2月10日 一位热衷于Linux学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 全系列文章可参考专栏: Linux内核知识_Once-Day的…

git本地建的分支,删除后内容还能找回

前提&#xff1a; 需要有commit 动作 1、git reflog 命令查看所有分支操作记录&#xff1b; 2、git checkout -b 命令创建一个新的分支&#xff0c;将其指向被删除分支的最后一个提交记录‌&#xff1b; git checkout -b <branch-name> <commit-hash>旨在分享~…

【每日一题 | 2025】2.3 ~ 2.9

个人主页&#xff1a;GUIQU. 归属专栏&#xff1a;每日一题 文章目录 1. 【2.3】P8784 [蓝桥杯 2022 省 B] 积木画2. 【2.4】P8656 [蓝桥杯 2017 国 B] 对局匹配3. 【2.5】[ABC365D] AtCoder Janken 34. 【2.6】P8703 [蓝桥杯 2019 国 B] 最优包含5. 【2.7】P8624 [蓝桥杯 2015…

Unity URP后处理在Game窗口不显示

摄像机勾选这个就可以了&#xff1a; 参考&#xff1a;UNITY3D URP与后处理,在game窗口不显示问题_unity urp 半透明材质game看不到-CSDN博客

Java进阶14 TCP日志枚举

Java进阶14 TCP&日志&枚举 一、网络编程TCP Java对基于TCP协议得网络提供了良好的封装&#xff0c;使用Socket对象来代表两端的通信端口&#xff0c;并通过Socket产生IO流来进行网络通信。 1、TCP协议发数据 1.1 构造方法 方法 说明 Socket(InetAddress address…

C#02项目——Checked用法

知识点 本项目用到的知识点包括&#xff1a; checked。主要用来处理溢出错误 Try.Prarse。将数字的字符串表示形式转换为其等效的 32 位有符号整数。 返回值指示转换是否成功 public static bool TryParse (string? s, out int result);Try…Catch。用于捕捉异常&#xff0c…

WPF 设置宽度为 父容器 宽度的一半

方法1&#xff1a;使用 绑定和转换器 实现 创建类文件 HalfWidthConverter public class HalfWidthConverter : IValueConverter{public object Convert(object value, Type targetType, object parameter, CultureInfo culture){if (value is double width){return width / 4…

Windows 系统 GDAL库 配置到 Qt 上

在地理信息开发中广泛使用的开源库&#xff0c;GDAL(Geospatial Data Abstraction Library&#xff09;)库提供了读取和处理各种地理空间数据格式的能力。 准备阶段 下载 GDAL 库&#xff1a;前往 GDAL 的官方网站&#xff08;https://www.gisinternals.com/&#xff09;下载…

自己动手实现一个简单的Linux AI Agent

大模型带我们来到了自然语言人机交互的时代 1、安装本地大模型进行推理 下载地址&#xff1a; https://ollama.com/download 部署本地deepseek和嵌入模型 ollama run deepseek-r1:7b2、制定Linux操作接口指令规范 3、编写大模型对话工具 #!/usr/bin/python3 #coding: utf-8…