【嵌入式Linux应用开发基础】文件I/O基础编程

ops/2025/2/12 13:09:46/

目录

一、文件I/O简介

二、文件描述符

2.1. 唯一性

2.2. 抽象性

2.3. 有限性

三、文件操作函数

四、标准文件I/O函数

五、文件执行权限

5.1. 权限类型

5.2. 权限分配对象

5.3. 权限表示方法

5.4. 权限设置命令

5.5. 权限设置的重要性

5.6. 实例说明

六、设备文件读写

6.1. 设备文件类型

6.2. 设备文件的命名

6.3. 设备文件的读写操作

6.4. 示例:串口通信


在嵌入式 Linux 应用开发中,文件 I/O(Input/Output)基础编程是非常重要的一部分,它允许程序与文件系统进行交互,实现数据的读取、写入和管理等操作。

一、文件I/O简介

Linux文件I/O是操作系统中处理文件读写操作的基本机制。在Linux系统中,文件I/O操作是通过系统调用实现的,这些系统调用允许用户空间的程序与内核空间的文件系统进行交互。一个通用的IO模型通常包括打开文件、读写文件、关闭文件这些基本操作。

  • 文件描述符(File Descriptor):在 Linux 系统中,每个打开的文件都由一个非负整数的文件描述符来标识。当程序打开一个现有文件或者创建一个新文件时,内核会返回一个文件描述符。标准输入(stdin)、标准输出(stdout)和标准错误输出(stderr)的文件描述符分别是 0、1 和 2。
  • 缓冲区(Buffer):为了提高文件 I/O 的效率,系统通常会使用缓冲区。缓冲区是一块内存区域,用于临时存储从文件读取或要写入文件的数据。

二、文件描述符

文件描述符(File Descriptor)是Linux和UNIX系统编程中的一个重要概念,它是一个用于标识打开文件或其他输入/输出资源的非负整数。文件描述符允许程序通过一个抽象的数字来引用文件和其他输入输出资源,而不是直接使用文件名或设备名。

2.1. 唯一性

在进程的生命周期内,每个打开的文件或设备都会分配一个唯一的文件描述符。这些描述符是从3开始分配的,因为0、1、2已经被系统预留给标准输入(stdin)、标准输出(stdout)和标准错误(stderr)了。

例如,如果进程首先打开一个文件,它将被分配文件描述符3;接着打开第二个文件,则分配文件描述符4,以此类推。

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>int main() {int fd1 = open("file1.txt", O_RDONLY);int fd2 = open("file2.txt", O_RDONLY);int fd3 = open("file3.txt", O_RDONLY);if (fd1 == -1 || fd2 == -1 || fd3 == -1) {perror("open");return 1;}printf("File descriptors: file1.txt = %d, file2.txt = %d, file3.txt = %d\n", fd1, fd2, fd3);close(fd1);close(fd2);close(fd3);return 0;
}

我们打开了三个文件,并打印了它们的文件描述符。通过运行,可以观察到文件描述符是从3开始递增分配的(假设0、1、2没有被占用或重定向)。 

2.2. 抽象性

文件描述符提供了一种抽象机制,使得程序可以通过简单的数字来引用复杂的I/O资源。这种抽象性简化了编程模型,因为程序员不需要关心底层的设备或文件实现细节。

文件描述符的这种抽象性也支持了重定向和管道等高级I/O操作。例如,可以将一个进程的标准输出重定向到一个文件,或者将一个进程的输出作为另一个进程的输入,这些操作都可以通过操作文件描述符来实现。

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>int main() {int fd = open("example.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);if (fd == -1) {perror("open");return 1;}const char *text = "Hello, file descriptor!\n";ssize_t bytes_written = write(fd, text, strlen(text));if (bytes_written == -1) {perror("write");close(fd);return 1;}close(fd);return 0;
}

打开(或创建)了一个文件,并使用write函数通过文件描述符向其中写入数据。文件描述符在这里作为I/O操作的抽象引用。 

2.3. 有限性

文件描述符的数量是有限的,这个限制通常由系统设置决定。在Linux系统中,可以使用ulimit -n命令来查看和设置当前shell进程的文件描述符限制。

默认情况下,这个限制可能比较低(如1024),但在现代系统中,这个限制通常可以被提高。提高文件描述符限制对于需要打开大量文件的服务器程序来说是非常重要的。

需要注意的是,虽然系统允许提高文件描述符限制,但这也受到系统资源(如内存)的限制。打开过多的文件可能会导致系统资源耗尽,从而影响系统的稳定性和性能。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/resource.h>int main() {struct rlimit rl;// 获取当前文件描述符限制if (getrlimit(RLIMIT_NOFILE, &rl) == -1) {perror("getrlimit");exit(EXIT_FAILURE);}printf("Current file descriptor limit: soft = %lld, hard = %lld\n",(long long)rl.rlim_cur, (long long)rl.rlim_max);// 尝试提高软限制(在硬限制范围内)rl.rlim_cur = rl.rlim_max; // 或者设置为一个较小的值,但不超过硬限制if (setrlimit(RLIMIT_NOFILE, &rl) == -1) {perror("setrlimit");exit(EXIT_FAILURE);}// 再次获取限制以确认更改if (getrlimit(RLIMIT_NOFILE, &rl) == -1) {perror("getrlimit");exit(EXIT_FAILURE);}printf("New file descriptor limit: soft = %lld, hard = %lld\n",(long long)rl.rlim_cur, (long long)rl.rlim_max);return 0;
}

首先获取了当前的文件描述符限制(软限制和硬限制),然后尝试将软限制提高到硬限制的值。请注意,硬限制是由系统管理员设置的,普通用户可能无法更改它。如果尝试设置一个超过硬限制的值,setrlimit调用将失败。 

三、文件操作函数

在Linux系统中,文件操作主要涉及到以下几个函数:

  • open()函数:用于打开文件。其原型为int open(const char *pathname, int flags, mode_t mode)。其中,pathname是文件名或路径,flags用于指定文件的打开模式(如只读、只写、读写等),mode用于设置文件权限(当创建新文件时)。
  • read()函数:用于从文件中读取数据。其原型为ssize_t read(int fd, void *buf, size_t count)。其中,fd是文件描述符,buf是指向存储读取数据的缓冲区的指针,count是要读取的字节数。
  • write()函数:用于向文件中写入数据。其原型为ssize_t write(int fd, const void *buf, size_t count)。参数含义与read()函数类似。
  • close()函数:用于关闭文件。其原型为int close(int fd)。其中,fd是文件描述符。
  • lseek()函数:用于移动文件指针。其原型为off_t lseek(int fd, off_t offset, int whence)。其中,fd是文件描述符,offset是偏移量,whence用于指定偏移的基准位置(如文件开头、当前位置、文件末尾等)。
  • creat()函数:用于创建文件。其原型为int creat(const char *pathname, mode_t mode)。其中,pathname是文件名或路径,mode用于设置文件权限。不过,在现代Linux系统中,creat()函数已经被open()函数所取代,因为open()函数提供了更丰富的功能。

四、标准文件I/O函数

除了上述低级的文件操作函数外,Linux还提供了一套标准的文件I/O函数,这些函数封装了复杂的底层细节,便于用户进行日常文件操作。标准文件I/O函数主要包括:

  • fopen()函数:用于打开文件。其原型为FILE *fopen(const char *filename, const char *mode)
  • fclose()函数:用于关闭文件。其原型为int fclose(FILE *stream)
  • fread()函数:用于从文件中读取数据。其原型为size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
  • fwrite()函数:用于向文件中写入数据。其原型为size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
  • fgets()函数:用于从文件中读取一行字符。其原型为char *fgets(char *str, int n, FILE *stream)
  • fputs()函数:用于向文件中写入字符串。其原型为int fputs(const char *str, FILE *stream)

五、文件执行权限

在Linux系统中,文件执行权限是控制用户可以对文件执行哪些操作的重要机制。正确设置文件权限对于系统的安全性至关重要

5.1. 权限类型

Linux文件权限主要分为三类:

  • 读权限(r):允许用户读取文件内容或列出目录内容。
  • 写权限(w):允许用户修改文件内容或在目录中创建、删除文件。
  • 执行权限(x):允许用户执行文件(如果文件是可执行文件)或进入目录。

5.2. 权限分配对象

这些权限可以被分配给以下三个对象:

  • 文件所有者(owner):文件的创建者或拥有者,对文件具有最高的控制权限。
  • 文件所属组(group):文件所属的用户组,组内的所有用户共享这些权限。
  • 其他用户(others):既不是文件所有者,也不属于文件所属组的用户。

5.3. 权限表示方法

Linux系统提供两种表示文件权限的方法:数字表示法和符号表示法。

  • 数字表示法

    • 读权限(r)= 4
    • 写权限(w)= 2
    • 执行权限(x)= 1
    • 将这三种权限的数字相加,就可以得到每个用户类别的权限值。例如,7表示读、写和执行权限(4+2+1),5表示读和执行权限(4+1)。
  • 符号表示法:使用字符来表示权限,通常与用户名、组名一起显示在ls -l命令的输出中。例如,-rwxr-xr--表示一个普通文件,所有者有读、写和执行权限,组用户有读和执行权限,其他用户只有读权限。

5.4. 权限设置命令

在Linux中,可以使用chmod命令来设置或修改文件权限。

  • 符号表示法设置权限

    • chmod u+x 文件名:给文件的所有者添加执行权限。
    • chmod g+w,o+r 文件名:给用户组增加写权限,给其他用户增加读权限。
    • chmod a=r 文件名:将文件的权限设置为所有人仅具有读权限。
  • 数字表示法设置权限

    • chmod 755 文件名:设置文件所有者为读写执行权限(7),用户组和其他用户为读执行权限(5)。
    • chmod 644 文件名:设置文件所有者为读写权限(6),用户组和其他用户为读权限(4)。

5.5. 权限设置的重要性

正确设置文件权限对于Linux系统的安全性至关重要。通过合理设置文件权限,可以控制不同用户对文件和目录的访问和操作,防止未经授权的访问和修改,从而保护系统资源的安全。

5.6. 实例说明

假设有一个名为script.sh的Shell脚本文件,需要给其所有者添加执行权限,以便能够执行该脚本。可以使用以下命令:

chmod u+x script.sh

或者,也可以使用数字表示法来设置权限: 

chmod 755 script.sh

这样,script.sh文件的所有者将拥有读、写和执行权限,而用户组和其他用户将拥有读和执行权限(虽然对于脚本文件来说,写权限通常不是必需的,但这里为了演示目的而包含)。

六、设备文件读写

在嵌入式Linux系统中,设备文件是一种将硬件设备抽象为普通文件的机制。这种抽象使得用户空间程序可以通过标准的文件I/O操作(如openreadwriteclose等)来与硬件设备进行交互。设备文件通常位于/dev目录下,并且根据其特性被分类为字符设备或块设备。

6.1. 设备文件类型

  • 字符设备:字符设备以字符为单位进行数据传输,如串口(UART)、键盘、鼠标等。对字符设备的读写操作通常不会涉及缓存,因为数据是即时处理的。
  • 块设备:块设备以块(通常是512字节或更大)为单位进行数据传输,如硬盘、SD卡等。对块设备的读写操作可能会涉及缓存,以提高性能。

6.2. 设备文件的命名

  • 设备文件通常以设备类型加上设备编号的形式命名。例如,/dev/ttyS0可能表示第一个串口设备,而/dev/sda1可能表示第一个SCSI硬盘的第一个分区。

6.3. 设备文件的读写操作

在嵌入式编程中,对设备文件的读写操作通常涉及以下步骤。

  • 打开设备文件:使用open函数打开设备文件,指定操作模式(如读、写或读写)。
  • 配置设备(如果需要):对于某些设备,可能需要通过ioctl函数发送控制命令来配置设备参数。
  • 读写操作
    • 使用read函数从设备读取数据。
    • 使用write函数向设备写入数据。
  • 处理错误:检查每个系统调用的返回值,以处理可能的错误情况。
  1. 关闭设备文件:使用close函数关闭设备文件,释放资源。

6.4. 示例:串口通信

以下是一个简单的示例,展示如何通过读写串口设备文件来进行通信:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>int main() {int fd;struct termios options;// 打开串口设备文件fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_SYNC);if (fd < 0) {perror("open");exit(EXIT_FAILURE);}// 配置串口参数tcgetattr(fd, &options);cfsetispeed(&options, B9600); // 设置输入波特率cfsetospeed(&options, B9600); // 设置输出波特率options.c_cflag |= (CLOCAL | CREAD); // 启用接收器,忽略调制解调器控制线options.c_cflag &= ~PARENB; // 无奇偶校验options.c_cflag &= ~CSTOPB; // 一个停止位options.c_cflag &= ~CSIZE;options.c_cflag |= CS8; // 8个数据位tcsetattr(fd, TCSANOW, &options);// 写入数据到串口const char *msg = "Hello, UART!\n";write(fd, msg, strlen(msg));// 从串口读取数据(这里只是示例,实际应用中可能需要循环读取)char buf[256];int n = read(fd, buf, sizeof(buf) - 1);if (n > 0) {buf[n] = '\0'; // 确保字符串以null结尾printf("Received: %s", buf);} else if (n < 0) {perror("read");}// 关闭串口设备文件close(fd);return 0;
}

首先打开了/dev/ttyS0设备文件,配置了串口参数(如波特率、数据位、停止位等),然后向串口写入了数据,并从串口读取了数据(虽然在实际应用中,读取操作通常是在一个循环中进行的)。最后,关闭了设备文件。

综上所述,嵌入式Linux应用开发中的文件I/O基础编程涉及到文件描述符、文件操作函数、标准文件I/O函数以及文件执行权限等多个方面。掌握这些基础知识对于进行嵌入式Linux应用开发至关重要。


http://www.ppmy.cn/ops/157779.html

相关文章

DeepSeek 助力 Vue 开发:打造丝滑的步骤条

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 Deep…

DeepSeek本地部署(DeepSeek服务器繁忙解决方案)

前言 最近DeepSeek非常火爆,也非常好用但用的人实在太多了,以至于经常服务器繁忙。那有没有解决方案呢,当然是有的:本地部署 下面正文开始: 一、安装ollama Ollama 是一个让你在本地设备上运行大型语言模型的平台。它不依赖远程服务器,而是让你在自己的机器上运行这些模…

蓝桥杯算法日记|贪心、双指针

3412 545 2928 2128 贪心学习总结&#xff1a; 1、一般经常用到sort&#xff08;a&#xff0c;an&#xff09;&#xff1b;【a[n]】排序&#xff0c;可以给整数排&#xff0c;也可以给字符串按照字典序排序 2、每次选最优 双指针 有序数组、字符串、二分查找、数字之和、反转字…

基于Spring Boot的网上蛋糕售卖店管理系统设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

python基础入门:附录:常用第三方库推荐(NumPy、Django等)

Python常用第三方库全景指南&#xff1a;从基础到前沿工具集 一、数据科学核心套件 数值计算三剑客 # NumPy数组操作示例 import numpy as np arr np.arange(1, 10).reshape(3,3) print(arr arr.T) # 矩阵乘法# Pandas数据分析示例 import pandas as pd df pd.DataFrame…

Excel大数据量导入导出

github源码 地址&#xff08;更详细&#xff09; : https://github.com/alibaba/easyexcel 文档&#xff1a;读Excel&#xff08;文档已经迁移&#xff09; B 站视频 : https://www.bilibili.com/video/BV1Ff4y1U7Qc 一、JAVA解析EXCEL工具EasyExcel Java解析、生成Excel比较…

消费电子产品中的噪声对TPS54202的影响

本文章是笔者整理的备忘笔记。希望在帮助自己温习避免遗忘的同时&#xff0c;也能帮助其他需要参考的朋友。如有谬误&#xff0c;欢迎大家进行指正。 一、概述 在白色家电领域&#xff0c;降压转换器的应用非常广泛&#xff0c;为了实现不同的功能就需要不同的电源轨。TPS542…

Maven 下载与配置教程:附百度网盘地址

一、引言 在 Java 开发领域&#xff0c;Maven 是一款广泛使用的项目管理和构建工具。它能够帮助开发者自动化项目的构建、依赖管理和文档生成等任务&#xff0c;从而提高开发效率和项目质量。本文将详细介绍 Maven 的下载方法、安装步骤、配置教程以及使用技巧&#xff0c;并提…