【嵌入式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. 文件数据读写

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; // EOF
    if (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 1024

int 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 32

int 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 1024

int 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 1024

int 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操作的基础工具。通过这两个函数,可以实现从文件或设备读取数据和向文件或设备写入数据。了解并正确使用这些函数,对于开发稳定、高效的嵌入式应用程序至关重要。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

计网-数据链路层

3.1数据链路层的概述 概念 链路就是从一个结点到相邻结点的一段物理线路&#xff0c;而中间没有任何其他的交换结点。 数据链路是指把实现通信协议的硬件和软件加到链路上&#xff0c;就构成了数据链路。 数据链路层以帧为单位传输和处理数据。 网络中的主机、路由器等都必须实…

Versal - 基础5(裸机开发 AIE-ML+Vitis2024.2界面aie report介绍)

目录 1. 简介 2. 申请 License 2.1 申请网址 2.2 填写 Host 2.3 导入 License 3. 示例 3.1 vivado_platform 3.1.1 由模板创建 3.1.2 模板参数 3.1.3 修改BD参数 3.1.4 配置CIPS和MC 3.1.5 导出PFM 3.2 vitis_platform 3.2.1 创建组件 3.2.2 导入XSA并配置 3.2…

Python 字典思维导图

在本章中&#xff0c;你将学习能够将相关信息关联起来的Python字典。你将学习如何访问和修改字典中的信息。鉴于字典可存储的信息量几乎不受限制&#xff0c;因此我们会演示如何遍 历字典中的数据。另外&#xff0c;你还将学习存储字典的列表、存储列表的字典和存储字典的字典。…

用pytorch实现一个简单的图片预测类别

前言&#xff1a; 在阅读本文之前&#xff0c;你需要了解Python&#xff0c;Pytorch&#xff0c;神经网络的一些基础知识&#xff0c;比如什么是数据集&#xff0c;什么是张量&#xff0c;什么是神经网络&#xff0c;如何简单使用tensorboard,DataLoader。 本次模型训练使用的是…

计算机视觉:卷积神经网络(CNN)基本概念(一)

第一章&#xff1a;计算机视觉中图像的基础认知 第二章&#xff1a;计算机视觉&#xff1a;卷积神经网络(CNN)基本概念(一) 第三章&#xff1a;计算机视觉&#xff1a;卷积神经网络(CNN)基本概念(二) 第四章&#xff1a;搭建一个经典的LeNet5神经网络 一、引言 卷积神经网络&…

PowerBI 矩阵 列标题分组显示(两行列标题)

先看效果 数据表如下&#xff1a; 我们在powerbi里新建一个矩阵&#xff0c;然后如图加入字段&#xff1a; 我们就会得到这样的矩阵&#xff1a; 我们在“可视化”->“列”&#xff0c;上双击&#xff0c;输入空格&#xff0c;就能消除左上角的"类别"两字 同理修…

报名丨Computer useVoice Agent :使用 TEN 搭建你的 Mac Assistant

与 TEN 相聚在「LET’S VISION 2025」大会&#xff0c;欢迎来展位上跟我们交流。这次我们还准备了一场聚焦「computer use」的工作坊&#xff0c;功能新鲜上线&#xff0c;线下首波体验&#xff01; &#x1f4c5; TEN 展位&#xff1a;2025年3月1日-2日 TEN workshop&#x…

python opencv基础使用总结

1.安装opencv库&#xff1a;pip install opencv-python 2.基础使用范例 import cv2 #图片的基本操作#1.读取一张图片 参数 1&#xff1a;图片的文件名如果图片放在当前文件夹下&#xff0c;直接写文件名就行&#xff0c;如lena.jpg否则需要给出绝对路径&#xff0c;如D:\Ope…

【STM32】舵机SG90

1.舵机原理 舵机内部有一个电位器&#xff0c;当转轴随电机旋转&#xff0c;电位器的电压会发生改变&#xff0c;电压会带动转一定的角度&#xff0c;舵机中的控制板就会电位器输出的电压所代表的角度&#xff0c;与输入的PWM所代表的角度进行比较&#xff0c;从而得出一个旋转…

OpenEuler学习笔记(三十三):在 OpenEuler 上搭建 OpenGauss 数据库环境

在 OpenEuler 上搭建 OpenGauss 数据库环境需要按照以下步骤进行。OpenGauss 是华为开源的一款高性能关系型数据库&#xff0c;支持高并发、高可用性和分布式部署。 1. 环境准备 确保你的 OpenEuler 系统满足以下要求&#xff1a; 操作系统&#xff1a;OpenEuler 20.03 LTS 或…

智能编程助手功能革新与价值重塑之:GitHub Copilot

引言&#xff1a; GitHub Copilot 的最新更新为开发者带来了显著变化&#xff0c;其中 Agent Mode 功能尤为引人注目。该模式能够自动识别并修复代码错误、自动生成终端命令&#xff0c;并具备多级任务推理能力&#xff0c;这使得开发者在开发复杂功能时&#xff0c;可大幅减少…

【数据结构】(9) 优先级队列(堆)

一、优先级队列 优先级队列不同于队列&#xff0c;队列是先进先出&#xff0c;优先级队列是优先级最高的先出。一般有两种操作&#xff1a;返回最高优先级对象&#xff0c;添加一个新对象。 二、堆 2.1、什么是堆 堆也是一种数据结构&#xff0c;是一棵完全二叉树&#xff0c…

AI大模型的文本流如何持续吐到前端,实时通信的技术 SSE(Server-Sent Events) 认知

写在前面 没接触过 SSE&#xff08;Server-Sent Events&#xff09;&#xff0c;AI大模型出来之后&#xff0c;一直以为文本流是用 WebSocket 做的偶然看到返回到报文格式是 text/event-stream,所以简单认知&#xff0c;整理笔记博文内容涉及 SSE 认知&#xff0c;以及对应的 D…

计时器任务实现(保存视频和图像)

下面是一个简单的计时器任务实现&#xff0c;可持续地每秒保存一幅图像&#xff0c;也可持续地每60秒保存一个视频&#xff0c;图像和视频均以当前时间命名&#xff1a; TimerTask类的实现如下&#xff1a; class TimerTask { public:TimerTask(const std::string& path):…

力扣19题——删除链表的倒数第 N 个结点

#题目 #代码 //定义虚拟头结点ListNode curnew ListNode(0,head); //定义两个指针指向虚拟头结点ListNode lcur;ListNode rcur;for(int i0;i<n;i){rr.next;}while(r!null){rr.next;ll.next;} //l.next就是我们要删除的那个元素if(l.next!null){l.nextl.next.next;}return c…

网络工程师 (42)IP地址

一、定义与功能 IP地址是IP协议提供的一种统一的地址格式&#xff0c;它为互联网上的每一个网络和每一台主机分配一个逻辑地址&#xff0c;以此来屏蔽物理地址的差异。这种地址分配方式确保了用户在连网的计算机上操作时&#xff0c;能够高效且方便地从众多计算机中选出自己所需…

记忆力训练day19

万能字母组合编码法 所有的文字和字母的背后都有画面 练的不是记单词&#xff0c;练的是注意力给到单词&#xff0c;出什么画面&#xff0c;然后画面与画面之间进行连接 拆的过程就是找熟词的过程 要关注自己的回忆路径是什么&#xff1f;也就是你是怎么回忆起来的&#xff0c…

flutter image_cropper插件安装后 打包apk 报错命名空间问题

本篇文章主要讲解&#xff0c;Flutter安装完新依赖打包apk报错 A problem occurred configuring project ‘:image_cropper’. 命名空间问题的解决办法及原因说明。 日期&#xff1a;2025年2月15日 作者&#xff1a;任聪聪 一、报错现象&#xff1a; 报文信息&#xff1a; FAI…

八、SPI读写XT25数据

8.1 SPI 简介 SPI&#xff08;Serial Peripheral Interface&#xff0c;串行外设接口&#xff09;是一种同步串行通信协议&#xff0c;广泛用于嵌入式系统中连接微控制器与外围设备&#xff0c;如传感器、存储器、显示屏等。 主要特点 1. 全双工通信&#xff1a;支持同时发送…

kibana es 语法记录 elaticsearch

目录 一、认识elaticsearch 1、什么是正向索引 2、什么是倒排索引 二、概念 1、说明 2、mysql和es的对比 三、mapping属性 1、定义 四、CRUD 1、查看es中有哪些索引库 2、创建索引库 3、修改索引库 4、删除索引库 5、新增文档 6、删除文档 5、条件查询 一、认识…