【Linux】深入理解文件操作:从C语言接口到系统调用与缓冲区管理

文章目录

  • 前言:
  • 1. 铺垫
    • 1.1. 对文件表述符的理解
  • 2. 重新使用C文件接口:对比一下重定向
    • 2.1. 什么叫当前路径?
    • 2.2. 写入文件
    • 2.3. 读文件
    • 2.4. 程序默认打开的文件流
    • 2.5. 输出
    • 2.6. 输入
  • 3. 系统调用提供的文件接口
    • 3.1. open 打开文件
    • 3.2. open函数返回值
  • 3.3. C语言 与 系统调用 文件读写操作
  • 4. 缓冲区问题
  • 总结:

前言:

在计算机编程中,文件操作是基础且至关重要的技能之一。无论是在系统编程、网络编程还是数据处理,文件的读写操作都是不可或缺的。本文将深入探讨文件操作的底层原理,从C语言层面的文件接口到操作系统层面的系统调用,再到缓冲区机制的实现,逐步揭示文件操作的全貌。通过对比C语言的文件接口和系统调用,以及对缓冲区问题的深入分析,本文旨在帮助读者建立一个清晰的文件操作概念框架,从而在实际开发中更加得心应手。

1. 铺垫

a. 文件 = 内容 + 属性
b. 访问文件之前,都得先打开。修改文件,都是通过指向代码的方式完成修改,文件必须加载到内存中
c. 谁打开文件?进程在打开文件
d. 一个进程可以打开多少个文件呢?可以打开多个文件

  • 一定时间内,系统中存在多个进程,也可能同时存在更多的被打开文件,OS要不要管理多个被进程打开的文件呢?肯定的
  • 如何管理呢?先组织,再描述!

e. 进程和文件的关系,struct task_struct 和 struct XXX?

a~e 被打开文件都是:内存文件

f. 系统中是不是所有的文件都被进程打开了?不是!没有被打开文件?就在磁盘中

1.1. 对文件表述符的理解

在Linux系统中,文件描述符(File Descriptor,FD)是一个用于引用打开文件和其他类型的I/O资源的整数。以下是对Linux文件描述符的几个关键理解:

  • 唯一标识:文件描述符为每个打开的文件或I/O资源提供了一个唯一的标识符,通常是一个非负整数。

  • 文件描述符表:每个进程都有自己的文件描述符表,这是一个内核数据结构,用于跟踪进程打开的所有文件和I/O资源。

  • 系统调用:文件描述符通常通过系统调用如openreadwriteclose等进行操作。open调用返回一个新的文件描述符,read和write使用文件描述符来读取或写入数据,而close用于释放文件描述符。

  • 标准流:Linux为标准输入(stdin)、标准输出(stdout)和标准错误(stderr)分别分配了文件描述符0、1和2。

  • 缓冲机制:Linux内核可能会对通过文件描述符进行的I/O操作使用缓冲机制,以提高性能和减少实际的磁盘I/O操作。

  • 错误处理:当系统调用失败时,会返回-1,并且全局变量errno会被设置为表示错误的特定值。

  • 多路复用:文件描述符可以用于I/O多路复用机制,如select、poll和epoll,允许进程同时监控多个文件描述符上的I/O状态。

  • 继承性:当创建新进程时,子进程会继承父进程的文件描述符表中的文件描述符,除非它们在子进程中被显式地关闭。

  • 重定向:文件描述符可以通过dup、dup2等函数进行重定向,允许将一个文件描述符的引用复制到另一个文件描述符上。

  • 文件锁:文件描述符可以用于对文件加锁,以控制对文件的并发访问。

文件描述符是Linux系统中进程与文件和I/O资源交互的基础,它们提供了一种统一的方式来处理各种类型的I/O操作,包括文件、管道、网络连接等。

2. 重新使用C文件接口:对比一下重定向

 FILE *fp = fopen("./log.txt", "w");	//以只写的方式打开会把该文件清空if (fp == NULL){perror("fopen");return 1;}//文件操作const char *str = "hallo file!\n";fputs(str, fp);fclose(fp);return 0;

在这里插入图片描述
以 w 方式打开文件的时候,该文件会被自动清空。

echo "hello bit" > log.txt		//hello bit,本质上就是写入
> log.txt		//文件直接被清空,是因为在输出重定向时需要先把文件打开

以 a 方式打开文件,就类似于重定向中的追加。

 FILE *fp = fopen("./log.txt", "a");	//以追加的形式打开echo "hello bit" >> log.txt			//对比追加重定向

重定向:
在Linux系统中,重定向是通过文件描述符(File Descriptor)来实现的。文件描述符是内核用来跟踪打开文件和I/O流的一种机制。以下是Linux重定向的实现原理:

  • 标准文件描述符:每个进程都有三个预分配的标准文件描述符:

标准输入(stdin,文件描述符为0)
标准输出(stdout,文件描述符为1)
标准错误(stderr,文件描述符为2)

文件描述符表:进程启动时,内核会为每个进程创建一个文件描述符表,表中记录了所有打开的文件和I/O设备的引用。

  • 重定向操作:在shell中,可以使用特定的语法来重定向命令的输出或输入。例如,>用于将输出重定向到文件,<用于将输入重定向自文件。

  • 文件描述符复制:当执行重定向操作时,shell会使用dup2系统调用来复制文件描述符。dup2(oldfd, newfd)会将oldfd指向的文件或设备复制到newfd,如果newfd已经打开,它会被关闭并重新指向oldfd所指向的文件或设备。

  • 关闭和打开文件:在重定向过程中,shell可能首先会关闭一个文件描述符(如果它已经打开),然后打开一个新的文件或设备,并将其文件描述符复制到原来的位置。

  • 缓冲机制:标准I/O库(如C语言的stdio库)使用缓冲区来提高I/O效率。重定向操作可能会影响这些缓冲区的状态,特别是当改变标准输出或标准错误时。

  • 错误处理:在执行重定向操作时,需要检查文件操作和dup2调用的返回值,以确保没有错误发生。

  • 临时文件描述符:有时,重定向操作会涉及创建一个临时文件描述符,用于在执行重定向之前存储当前文件描述符的状态。

通过这种方式,Linux系统允许用户和程序灵活地控制数据流的方向,无论是将输出写入文件、从文件读取输入,还是将错误消息重定向到不同的目的地。

2.1. 什么叫当前路径?

在进程文件里 ls /proc/29065 -l
在这里插入图片描述
在进程启动时,会记录自己启动时所在的路径。

2.2. 写入文件

  const char *msg = "hallo file!\n";int cnt = 5;while (cnt){int n = fwrite(msg, strlen(msg), 1, fp);printf("write %d block, pid is : %d\n", n, getpid());cnt--;sleep(20);}

2.3. 读文件

  char buffer[64];while(true) {char* r = fgets(buffer, sizeof(buffer), fp); // 按行读if (!r) break;printf("%s", buffer);}

2.4. 程序默认打开的文件流

stdin	//标准输入	键盘设备
stdout	//标准输出	显示器设备
stderr	//标准错误	显示器设备

2.5. 输出

  printf("hello printf\n");fputs("hello fputs", stdout);const char *msg = "hello fwrite\n";fwrite(msg, 1, strlen(msg), stdout);fprintf(stdout, "hello fprint\n"); 

2.6. 输入

  char buffer[64];fscanf(stdin, "%s", buffer);

3. 系统调用提供的文件接口

访问文件不仅仅有C语言上的文件接口,OS必须提供对应的访问文件的系统调用?
w: 清空文件、a: 追加文件、r: 读取文件内容

3.1. open 打开文件

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int open(const char *pathname, int flags); // falgs   是用位图进行传参,//哪个比特位被设置了就传递哪一个
int open(const char *pathname, int flags, mode_t mode); // 这里的mode 为权限掩码 umask
  • pathname: 要打开或创建的目标文件
  • flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。
  • 参数:
    O_RDONLY: 只读打开
    O_WRONLY: 只写打开
    O_RDWR : 读,写打开 这三个常量,必须指定一个且只能指定一个
    O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
    O_APPEND: 追加写
  • 返回值:
    成功:新打开的文件描述符
    失败:-1

设置文件创建的掩码:

   #include <sys/types.h>#include <sys/stat.h>mode_t umask(mode_t mask); // 设置我们对应的权限掩码
  • writereadcloselseek ,类比C文件相关接口。

3.2. open函数返回值

在认识返回值之前,先来认识一下两个概念: 系统调用库函数
结论1:C语言的文件接口,本质就是封装了系统调用。
FILE: C标准库中自己封装的一个结构体,必须 封装特定的 fd
C语言问什么要封装呢?为了保证自己的跨平台性。
认识 fd:数组下标?
文件描述符的本质就是数组下标。
在这里插入图片描述
题外话:如何理一切皆文件
通过struct file {…} 屏蔽掉了各种硬件的底层硬件差异 ,VFS(虚拟文件系统)

文件 fd 的分配规则 && 利用规则实现重定向
fd 的分配规则:从最小的没被使用的数组下标,会分 配给最新打开的文件!

想实现文件描述符的重定向,不用关闭再重新打开,OS必须提供“拷贝”接口。

   #include <unistd.h>int dup(int oldfd);int dup2(int oldfd, int newfd);

3.3. C语言 与 系统调用 文件读写操作

C语言文件操作:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>int main()
{FILE* fp;const char* filename = "bite";const char* message = "linux so easy";char buffer[256]; // 用于存储读取的数据size_t bytesRead; // 读取的字节数fp = fopen(filename, "w+");if (fp == NULL) {perror("Error opening file");}fwrite(message, sizeof(char), strlen(message), fp);// 移动文件指针到到文件开头fseek(fp, 0, SEEK_SET);bytesRead = fread(buffer, sizeof(char), strlen(message), fp);buffer[bytesRead] = '\0';printf("%s\n", buffer);fclose(fp);return 0;
}

系统调用文件操作:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>#include <fcntl.h>
#include <unistd.h>int main()
{int fd; // 文件描述符const char *filename = "bite";const char *message = "I like Linux!";ssize_t bytesRead;off_t offset;char buffer[256]; // 用于存储读取的数据fd = open(filename, O_RDWR | O_CREAT, 0644);if (fd == -1) {perror("Error opening file");return 1;}// 写入消息到文件bytesRead = write(fd, message, strlen(message));if (bytesRead == -1) {perror("Error writing to file");close(fd);return 1;}// 移动文件指针到文件开头,以便读取offset = lseek(fd, 0, SEEK_SET);if (offset == -1) {perror("Error seeking in file");close(fd);return 1;}// 打印读取的内容到标准输出// 从文件中读取内容bytesRead = read(fd, buffer, sizeof(buffer) - 1); // 确保有空间放置空字符'\0'buffer[bytesRead] = '\0'; // 确保字符串以空字符串结尾printf("%s\n", buffer);// 关闭文件描述符if (close(fd) == -1) {perror("Error closing file");return 1;}return 0;
}

4. 缓冲区问题

缓冲区它就是一块内存区域(用空间换时间)
为什么有? 提高使用者的效率
聚集数据,一次拷贝(刷新),提高整体效率。

调用系统调用是有成本的,时间&&空间

我门一直在说的缓冲区和内核中的缓冲区没有关系(尽管它有),语言层面的缓冲区,C语言自带缓冲区,缓冲到一定程度后再刷新到操作系统的缓冲区中。

  1. 无刷新,无缓冲
  2. 行刷新——显示器
  3. 全缓冲,全部刷新——普通文件,缓冲区被写满,才刷新。 还有两种刷新:强制刷新、进程退出的时候要自动刷新。

具体在哪里?

FILE *fp = fopen("log.txt", "w");
FILE *fp = ??

FILE : 其实是一个结构体(fd), 缓冲区是被FILE结构来维护的! (stdin,stdout,stderr

编码模拟:手动模拟一下 C标准库中的方法。

// mystdio.h
#pragma once#include <stdio.h>#define SIZE 4096
#define NONE_FLUSH (1<<1)
#define LINE_FLUSH (1<<2)
#define FULL_FLUSH (1<<3)typedef struct _myFILE
{// char inbuffer[];char outbuffer[SIZE];int pos;int cap;int fileno;  int flush_mode;
}myFILE;myFILE* my_fopen(const char* pathname, const char* mode);
int my_fwrite(myFILE *fp, const char* s, int size);
void my_fclose(myFILE* fp);
void my_fflush(myFILE* fp);
void DebugPrint(myFILE *fp);
// mystdio.c
#include "mystdio.h"
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>const char* toString(int flag)
{if(flag & NONE_FLUSH) return "None";else if(flag & LINE_FLUSH) return "Line";else if(flag & FULL_FLUSH) return "FULL";return "Unknow";
}void DebugPrint(myFILE *fp) 
{printf("outbuffer: %s\n", fp->outbuffer);printf("fd: %d\n", fp->fileno);printf("pos: %d\n", fp->pos);printf("flush_mode: %s\n", toString(fp->flush_mode));
}myFILE* my_fopen(const char* pathname, const char* mode)
{int flag = 0;if (strcmp(mode, "r") == 0){flag |= O_RDONLY;}else if(strcmp(mode, "w") == 0) {flag |= (O_CREAT| O_WRONLY | O_TRUNC);}else if(strcmp(mode, "a") == 0){flag |= (O_CREAT| O_WRONLY | O_APPEND);}else {return NULL;}int fd = 0;if(flag & O_WRONLY){umask(0);fd = open(pathname, flag, 0666);}else {fd = open(pathname, flag);}if (fd < 0) return NULL;myFILE *fp = (myFILE*)malloc(sizeof(myFILE));if(fp == NULL) return NULL;fp->fileno = fd;fp->cap = SIZE;fp->pos = 0;fp->flush_mode = LINE_FLUSH;return fp;
}void my_fflush(myFILE* fp)
{if (fp->pos == 0) return;write(fp->fileno, fp->outbuffer, fp->pos);fp->pos = 0;
}int my_fwrite(myFILE *fp, const char* s, int size) 
{// 1. 写入memcpy(fp->outbuffer + fp->pos, s, size);fp->pos += size;if ((fp->flush_mode & LINE_FLUSH) && fp->outbuffer[fp->pos-1] == '\n'){my_fflush(fp);}else if((fp->flush_mode & FULL_FLUSH) && fp->pos == fp->cap){my_fflush(fp);}return size;
}void my_fclose(myFILE* fp)
{my_fflush(fp);close(fp->fileno);free(fp);
}
// filetest.c
#include "mystdio.h"
#include <string.h>
#include <unistd.h>const char* filename = "./log.txt";int main()
{myFILE *fp = my_fopen(filename, "w");if (fp == NULL) return 1;int cnt = 5;char buffer[64];while (cnt){snprintf(buffer, sizeof(buffer), "helloword,hellohd,%d!!! ",cnt--); my_fwrite(fp, buffer, strlen(buffer));DebugPrint(fp);sleep(2);my_fflush(fp);} my_fclose(fp); return 0;
}

在这里插入图片描述

在这里插入图片描述

总结:

本文详细介绍了Linux系统中文件操作的基本概念和实践技巧。首先,我们讨论了文件描述符的作用和重要性,它是Linux内核用来跟踪进程打开的文件和I/O资源的关键机制。接着,我们通过对比C语言文件接口和系统调用,揭示了C语言文件操作实际上是对系统调用的封装,以实现跨平台兼容性。此外,我们探讨了文件缓冲区的概念,解释了为什么缓冲区能够提高文件操作的效率,并提供了一个简单的自定义文件操作的示例代码,展示了如何模拟C标准库中的文件操作。

通过本文的学习,读者不仅能够理解Linux文件系统的工作原理,还能够掌握文件操作的基本技巧,包括如何使用文件描述符进行高效的文件读写,如何利用缓冲区优化性能,以及如何通过系统调用来实现底层的文件操作。这些知识对于任何需要进行系统编程的开发者来说都是宝贵的,能够帮助他们编写出更高效、更可靠的文件处理程序。

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

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

相关文章

更新关于其宠物产品质量的电子学习课程

​我们受托更新关于其宠物产品质量的电子学习课程。我们决定采用流行的“Corporate Memphis”风格设计插图&#xff0c;这是一种适用于商业的友好卡通风格&#xff08;该名称来源于80年代因其亮丽的色彩和独特的项目方法而闻名的设计团体“Memphis”&#xff09;。我们选择“Co…

C# Web控件与数据感应之 填充 HtmlTable

目录 关于 HtmlTable HtmlTable与BaseDataList的区别 准备数据源 ​范例运行环境 FillTable 方法 设计与实现 模板样例输出 Automatic 模式填充 ​ DynamicRows 模式填充 StaticRows 模式填充 ​ 小结 关于 HtmlTable 数据感应也即数据捆绑&#xff0c;是…

Dell戴尔XPS 16 9640 Intel酷睿Ultra9处理器笔记本电脑原装出厂Windows11系统包,恢复原厂开箱状态oem预装系统

下载链接&#xff1a;https://pan.baidu.com/s/1j_sc8FW5x-ZreNrqvRhjmg?pwd5gk6 提取码&#xff1a;5gk6 戴尔原装系统自带网卡、显卡、声卡、蓝牙等所有硬件驱动、出厂主题壁纸、系统属性专属联机支持标志、系统属性专属LOGO标志、Office办公软件、MyDell、迈克菲等预装软…

SEACells:元细胞分析

元细胞是从单细胞测序数据中衍生的细胞分组&#xff0c;代表高度精细的不同细胞状态。在这里&#xff0c;作者介绍了单细胞细胞状态聚集 (SEACells)&#xff0c;这是一种用于识别元细胞的算法&#xff0c;它克服了单细胞数据的稀疏性&#xff0c;同时保留了传统细胞聚类所掩盖的…

LeetCode322.零钱兑换

文章目录 题目描述解题思路递归记忆化搜索动态规划另一种实现 题目描述 https://leetcode.cn/problems/coin-change/description/?envTypestudy-plan-v2&envIdtop-interview-150 给你一个整数数组 coins &#xff0c;表示不同面额的硬币&#xff1b;以及一个整数 amount …

Java商城免 费 搭 建:VR全景到SAAS,各种模式一网打尽!

一、技术选型 java开发语言&#xff1a;java是一种跨平台的编程语言&#xff0c;适用于大型企业级应用开发。使用java开发直播商城可以保证系统的稳定性和可扩展性。 spring boot框架&#xff1a;spring boot是一个快速构建spring应用的框架&#xff0c;简化了开发过程&#xf…

linux动态调试 dev_dbg

动态调试使用方法 打开内核动态调试开关&#xff0c;make menuconfig选中CONFIG_DYNAMIC_DEBUG以及CONFIG_DEBUG_FS Linux启动后&#xff0c;使用命令行挂载上dbgfs 1. mkdir /mnt/dbg 2. mount -t debugfs none /mnt/dbg 1.控制某个文件所有dev_dbg()&#xff0c; echo -n &q…

【React篇 】React项目中常用的工具库

我们可以从项目初始化、开发、构建、检查及发布的顺序总结react项目开发常用的工具库。 首先是初始化。 初始化工程项目一般用官方维护的 create-react-app&#xff0c;这个工具使用起来简单便捷&#xff0c;但 create-react-app 的配置隐藏比较深&#xff0c;修改配置时搭配…

wampserver安装与汉化

wampserver安装与汉化 文章目录 wampserver安装与汉化一、安装二、汉化1.升级软件并安装补丁 介绍&#xff1a; WampServer是一款由法国人开发的Apache Web服务器、PHP解释器以及MySQL数据库的整合软件包。免去了开发人员将时间花费在繁琐的配置环境过程&#xff0c;从而腾出更…

蒙层(css)

如何在 Vue 中实现一个包含图像和蒙层效果的组件&#xff1f;这个组件根据某个条件显示或隐藏蒙层&#xff0c;用于表示图像是否已读。 1. 创建基础模板 首先&#xff0c;我们在模板中使用 div 包裹我们的图像组件 GraphImage&#xff0c;并为最外层 div 设置 position: relat…

【MySQL数据库】MySQL 高可用搭建方案——MHA实战

MHA&#xff08;Master High Availability&#xff09; MHA实战 MHA&#xff08;Master High Availability&#xff09; 一、MHA简介二、MHA搭建准备要求&#xff1a;mha集群搭建&#xff0c;4台服务器&#xff0c;1主2从&#xff0c;1台mha2.1实验思路2.2实验准备 三、搭建MyS…

新手学习编程网站一站式合集

LTPP在线开发平台 探索编程世界的新天地&#xff0c;为学生和开发者精心打造的编程平台&#xff0c;现已盛大开启&#xff01;这个平台汇集了近4000道精心设计的编程题目&#xff0c;覆盖了C、C、JavaScript、TypeScript、Go、Rust、PHP、Java、Ruby、Python3以及C#等众多编程语…

语音深度鉴伪识别项目实战:基于深度学习的语音深度鉴伪识别算法模型(三)音频去噪算法大全+Python源码应用

前言 深度学习技术在当今技术市场上面尚有余力和开发空间的&#xff0c;主流落地领域主要有&#xff1a;视觉&#xff0c;听觉&#xff0c;AIGC这三大板块。 目前视觉板块的框架和主流技术在我上一篇基于Yolov7-LPRNet的动态车牌目标识别算法模型已有较为详细的解说。与AIGC相…

运维开发介绍

目录 1.什么是运维开发 2.作用 3.优点 4.缺点 5.应用场景 5.1.十个应用场景 5.2.网站和Web应用程序 6.案例 7.小结 1.什么是运维开发 运维开发&#xff08;DevOps&#xff09;是一种结合软件开发&#xff08;Development&#xff09;与信息技术运维&#xff08;Opera…

Unity Apple Vision Pro 开发(一):开发前期准备【软硬件要求 | 开发者模式 | 无线调试打包】

文章目录 &#x1f4d5;教程说明&#x1f4d5;硬件要求&#x1f4d5;软件要求⭐Xcode 15.2 及以上⭐visionOS 1.0 (21N301) SDK 或者更高版本⭐Unity 2022 LTS for Apple Silicon (2022.3.18f1及以上的版本)⭐Unity Pro/Unity Enterprise/Unity Industry的授权许可证 &#x1f…

天锐绿盾 | -办公加密系统、数据防泄密软件、图档加密、文件资料防泄密、源代码防止泄露!

天锐绿盾 |- 透明加密、数据防泄密系统、信息安全管理平台&#xff0c;旨在为用户提供全面的数据防泄露解决方案。该系统集成了文件透明加密技术、内网终端安全管理、以及私有云文档管理等功能&#xff0c;能够在不影响用户日常操作习惯和网络开放性的前提下&#xff0c;保护设…

HTML入门

HTML入门 注意&#xff0c;水文自用&#xff0c;//并非HTML注释语言&#xff0c;&#xff08;<&#xff01;–XXX->&#xff09;才是 初始文件结构 Vscode中 &#xff01; tab <!DOCTYPE html> <html lang"en"> //根元素&#xff0c;起始点 &l…

【机器学习基础】Python编程04:五个实用练习题的解析与总结

Python是一种广泛使用的高级编程语言,它在机器学习领域中的重要性主要体现在以下几个方面: 简洁易学:Python语法简洁清晰,易于学习,使得初学者能够快速上手机器学习项目。 丰富的库支持:Python拥有大量的机器学习库,如scikit-learn、TensorFlow、Keras和PyTorch等,这些…

“新高考”下分班怎么分?

来自安徽的张女士告诉我&#xff1a;上一年孩子升入了高中&#xff0c;但没想到才高一&#xff0c;孩子就面临了一个困难的挑选&#xff1a;312”分班&#xff01; 什么是312”分班呢&#xff1f;许多人或许不明白&#xff0c;便是要求学生在高一入学时&#xff0c;针对于3门必…

JVM双亲委派模型

在之前的JVM类加载器篇中说过&#xff0c;各个类加载器都有自己加载的范围&#xff0c;比如引导类加载器只加载Java核心库中的class如String&#xff0c;那如果用户自己建一个包名和类名与String相同的类&#xff0c;会不会被引导类加载器加载。可以通过如下代码测试&#xff0…