百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

「Linux」深入理解文件IO操作 linux 文件操作

liebian365 2024-10-27 13:21 26 浏览 0 评论

一、如何用C接口进行文件操作

i. 基础函数

打开文件:fopen

关闭文件:fclose

读取文件数据:fread

写入数据:fwrite

FILE *fopen(const char *path, const char *mode);//打开函数
int fclose(FILE *fp);//关闭函数
size_t fread ( void *buffer, size_t size, size_t count, FILE *stream);//读取函数
size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);//写入函数

ii. 细节补充

关于C接口进行文件操作的操作和用法已有很多资料参考,这里不再赘述,在这里我补充关于当前路径的理解。

举个例子:假设你写了个程序,其功能是在当前目录创建一个新文件。当你在这个程序所处目录运行这个程序,那么该目录就生成了一个新文件,这没啥问题。那假如你在返回到根目录运行该程序,请问生成的文件是在刚刚那个目录下还是根目录下呢?

答案是在根目录下,由此得出的结论是:当前路径并不是可执行程序所处的路径,而是可执行程序启动的路径,如在根目录下启动该程序,则当前路径为根目录。所以我们要深刻意识到:当前路径是和进程有关的

二、如何用系统接口进行文件操作

i. 打开文件函数open

int open(const char *pathname, int flags, mode_t mode);

Linux中通过 man 2 open 命令查看

头文件与函数原型

返回值

如果成功返回file descriptor:文件描述符,是一个数字;失败返回-1,并设置errno

参数解读

pathname:要打开的文件所处的路径,可使用相对路径

mode:可选项,用于设置新建一个文件时的默认权限,如777则为可读可写可执行,同时注意要修改umask

flags:设置以何种形式打开文件,即类似于C接口的可读/可写/可读写打开

The argument flags must include one of the following access modes:O_RDONLY, O_WRONLY, or O_RDWR. These request opening the file read-only,write-only, or read/write, respectively.

常见的选项有:O_RDONLY, O_WRONLY, or O_RDWR,O_CREAT(若文件不存在则创建)

如何在传参标志位传多个选项,如同时传O_WRONLY和O_CREAT?

事实上flags参数设计的很巧妙,通过按位或来实现传递多个参数,首先它是个int型数,即有32位,操作系统设计的时候把某个位置位1,其他位置为0作为一种状态,那么它可以表示32种状态,这里我只用16个bit位来举例,假设0x0001表示写权限记作O_WRONLY,0x0004表示可创建文件记作O_CREAT,我们调用open函数打开某文件想给它写权限和若该文件没有则创建,则将O_WRONLY|O_CREAT传递给flags,将O_WRONLY和O_CREAT按位与得到的int数在bit[0]和bit[2]都为1,达到只用一个参数传入两种权限的效果

ii. 向文件读取函数read

头文件与函数原型

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

参数解读

fd:要读取的文件的文件描述符,每个文件都有一个文件描述符,open函数打开一个文件返回值也是文件描述符

buf:读取到的数据存放之处

count:buf的大小,byte为单位

返回值

若读取成功,返回读取到数据的大小(byte为单位)

若读取失败(如读取过程中被信号中断)返回-1,并设置errno

iii. 向文件写入函数write

头文件与函数原型

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

参数解读

fd:要写入的文件的文件描述符

buf:要写入的数据存放之处,指针

count:要写入内容buf的大小,byte为单位

返回值

若写入成功,返回写入了的数据的大小(byte为单位)

若写入失败返回-1,并设置errno

相关视频推荐

3个linux内核的秘密,让你彻底搞懂文件系统

linux内存管理-庞杂的内存问题,如何理出自己的思路出来

学习地址:C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂

需要C/C++ Linux服务器架构师学习资料加群812855908获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

三、深入理解文件描述符

i. 何为文件描述符

当我们打开文件时,该文件的相关信息会被加载到内存,如创建时间,所属者,操作权限等等,OS对这些信息则需用数据结构维护起来,从而有了struct file(用于描述内存中的文件),多个打开的文件就有多个struct,它们管理起来形成双向链表,结构体里存放文件的属性信息。每个进程进程都拥有task_struct进程控制块,task_struct里面有个指针files,用于指向一张表files_struct,该表包含了一个指针数组,而这数组里每个指针指向一个内存中的文件struct file,从而实现了进程与文件的连接,而文件描述符就是该数组的下标,只要知道文件描述符就可以找到内存中的文件

FILE结构体部分成员:

新视角阐释fopen做了什么

1.给调用的用户申请FILE结构体并返回地址(FILE)

2.底层调用系统级函数open得到fd,将fd填充到FILE变量中的fileno

ii. 文件流指针和文件描述符

文件流指针是标准库操作句柄:FILE*,文件描述符是系统调用接口句柄:int fd,文件描述符是每个文件的唯一标识,文件流指针底层封装了文件描述符,在文件流指针结构体中可以看到 _fileno保存了fd

iii. 文件描述符的数字规律

看一个文件打开的程序,观察file descriptor

我们发现这个文件的文件描述符fd=3

再看一个打开多个文件的程序,观察file descriptor

我们发现打开多个文件,它们的文件描述符fd从3依次递增,我们知道open函数出现错误返回-1,那么请问0,1,2去哪了呢???

其实当任何进程在运行的时候,默认打开三个输入输出流stdin,stdout,stderr,而它们对应的的file descriptor就是0,1,2,所以再打开文件时分配的fd只能从3递增了

stdin,stdout,stderr分别对应键盘,显示器,显示器,OS打开它们很好地体现了LInux中一切皆文件的思想
总结:文件描述符的分配遵循最小未占用原则:从下标0开始逐位分配,若已被使用则不分配

四、重定向

i. 原理

本质是修改files_struct表中的指针数组中指针所指向的内容

ii. 通过关闭文件描述符实现重定向

  • 代码演示
#include<stdio.h>    
#include<unistd.h>       
#include<sys/types.h>    
#include<sys/stat.h>    
#include<fcntl.h>    
int main()    
{    
   //演示重定向基本原理                                                     
   umask(0);      
   close(1);//关闭显示器      
   int fd=open("log.txt",O_WRONLY|O_CREAT,0666);//fd=1      
   if(fd<0){ return 1;} //打开失败     
   write(1,"hello world!\n",13);//写到1号文件      
   write(1,"hello world!\n",13);      
   write(1,"hello world!\n",13);      
   write(1,"hello world!\n",13);      
   write(1,"hello world!\n",13);           
   close(fd);
   return 0;
}

运行结果:本该写到标准输出的内容被重定向到log.txt

原理分析:

在打开新的文件前,我们先close(1);关闭显示器,即上图打红叉的线被取消,所以当新的文件被打开它的文件描述符为1,即上图蓝色的线被连上,所以当往1号文件写入会被写入到log.txt而不会在显示器显示

iii.使用 dup2 系统调用实现重定向

a. 函数原型

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

参数理解:

newfd为oldfd的拷贝,这里的copy是拷贝fd_array数组对应oldfd下标的内容,理解为对后者的操作变为对前者的操作

b. 用法示例

dup(fd,1) //fd为某个新打开文件的描述符

这里newfd是1,oldfd是fd,new为old的拷贝,即new也指向分段,所以本要输出到1的内容重定向到fd去

五、剖析缓冲区

i. 三种缓冲机制

  1. 行缓冲->见于显示器刷新数据的策略
  2. 全缓冲->见于对文件 (理解为磁盘文件) 写入数据的策略
  3. 无缓冲->如stderr

ii. 什么情况下缓冲区会被刷新

  1. 调用exit()结束进程时会刷新缓冲区
  2. 当缓冲区满了也会被刷新出来
  3. 可通过fflush刷出来
  4. 流被关闭时也会被刷出来
  5. 行缓冲遇见’\n’会被刷新出来

行缓冲遇到上述五种情形会刷新缓冲区,全缓冲遇到除最后一种情形会刷新缓冲区

一份代码验证缓冲区:

理论上应该是先打印“hello world”再sleep,但观察到的现象是先sleep再打印。这是因为字符串被保留在缓冲区,不满足上述五种刷新条件所以没有被立即刷新出来,当进程结束后“hello world”才出现

iii. 关于缓冲区的理解

内存和硬盘中进行数据交互(写入/读取)是需要代价的,所以攒够一定量才交互,这个攒的量就放在缓冲区。对于文件,可以攒很多才交互,这样交互效率最高;而对于显示器采用行缓冲,因为人是想收到及时反馈的,所以行缓冲可理解为数据交互效率和用户体验感的平衡

iv. 提出三个问题

  1. 这个缓冲区在哪?
  2. 这个缓冲区是谁提供的?
  3. OS也有缓冲,与文件缓冲的对比

这三个问题将在下面分析一个奇怪现象中给出答案

v. 一个奇怪的现象

1.代码:

#include<stdio.h>    
#include<string.h>    
#include<unistd.h>    
    
int main() 
{    
    //C函数接口    
    printf("hello printf\n");    
    fprintf(stdout,"hello fprintf\n");    
    
    //system call    
    const char*msg="hello write\n";   
    write(1,msg,strlen(msg));    
    
    fork();//加了fork重定向C接口内容打印2次,系统接口内1次 
    return 0;  
} 

观察重定向和不重定向的运行结果

2.总结出现的现象:

  1. 重定向和不重定向改变了进程的缓冲方式
  2. C接口打印了两次,OS接口打印了一次

3.分析:

为什么C接口的显示了两次?

代码顺序执行,printf和fprintf的内容存放到缓冲区,因为不是往显示器打,所以不是行刷新,此时缓冲区存在两句话。当fork时创建了子进程,父子进程代码数据共享,缓冲区也是内存上的一块。若OS先调用父进程,则父进程先刷新缓冲区,打印出两句话,到调用子进程时,因为进程具有独立性互不干扰,子进程也要刷新缓冲区,则先写时拷贝再刷新,则又打印出两句话

为什么OS接口的没有显示两次?

系统调用函数如write没有缓冲区,所以不会发生写时拷贝导致缓冲区内容被打印了两次

解释第二个问题

对比可得到结论缓冲区语言自带的,不是OS级别的

无论是printf还是fprint都是打到stdout,而stdout是一个FILE结构体,里面不仅封装了fd,还维护了缓冲区

解释第一个和第三个问题

内核区和用户区都有缓冲区,我们这里所说的缓冲区, 都是用户级缓冲区,但要刷新的时候用户级缓冲区的数据不能直接加载到磁盘或显示器,得经过操作系统进入到内核区的缓冲区(有自己的机制),然后数据才被加载出去

相关推荐

“版本末期”了?下周平衡补丁!国服最强5套牌!上分首选

明天,酒馆战棋就将迎来大更新,也聊了很多天战棋相关的内容了,趁此机会,给兄弟们穿插一篇构筑模式的卡组推荐!老规矩,我们先来看10职业胜率。目前10职业胜率排名与一周前基本类似,没有太多的变化。平衡补丁...

VS2017 C++ 程序报错“error C2065:“M_PI”: 未声明的标识符&quot;

首先,程序中头文件的选择,要选择头文件,在文件中是没有对M_PI的定义的。选择:项目——>”XXX属性"——>配置属性——>C/C++——>预处理器——>预处理器定义,...

东营交警实名曝光一批酒驾人员名单 88人受处罚

齐鲁网·闪电新闻5月24日讯酒后驾驶是对自己和他人生命安全极不负责的行为,为守护大家的平安出行路,东营交警一直将酒驾作为重点打击对象。5月23日,东营交警公布最新一批饮酒、醉酒名单。对以下驾驶人醉酒...

Qt界面——搭配QCustomPlot(qt platform)

这是我第一个使用QCustomPlot控件的上位机,通过串口精确的5ms发送一次数据,再将读取的数据绘制到图表中。界面方面,尝试卡片式设计,外加QSS简单的配了个色。QCustomPlot官网:Qt...

大话西游2分享赢取种族坐骑手办!PK趣闻录由你书写

老友相聚,仗剑江湖!《大话西游2》2021全民PK季4月激燃打响,各PK玩法鏖战齐开,零门槛参与热情高涨。PK季期间,不仅各种玩法奖励丰厚,参与PK趣闻录活动,投稿自己在PK季遇到的趣事,还有机会带走...

测试谷歌VS Code AI 编程插件 Gemini Code Assist

用ClaudeSonnet3.7的天气测试编码,让谷歌VSCodeAI编程插件GeminiCodeAssist自动编程。生成的文件在浏览器中的效果如下:(附源代码)VSCode...

顾爷想知道第4.5期 国服便利性到底需优化啥?

前段时间DNF国服推出了名为“阿拉德B计划”的系列改版计划,截至目前我们已经看到了两项实装。不过关于便利性上,国服似乎还有很多路要走。自从顾爷回归DNF以来,几乎每天都在跟我抱怨关于DNF里面各种各样...

掌握Visual Studio项目配置【基础篇】

1.前言VisualStudio是Windows上最常用的C++集成开发环境之一,简称VS。VS功能十分强大,对应的,其配置系统较为复杂。不管是对于初学者还是有一定开发经验的开发者来说,捋清楚VS...

还嫌LED驱动设计套路深?那就来看看这篇文章吧

随着LED在各个领域的不同应用需求,LED驱动电路也在不断进步和发展。本文从LED的特性入手,推导出适合LED的电源驱动类型,再进一步介绍各类LED驱动设计。设计必读:LED四个关键特性特性一:非线...

Visual Studio Community 2022(VS2022)安装图文方法

直接上步骤:1,首先可以下载安装一个VisualStudio安装器,叫做VisualStudioinstaller。这个安装文件很小,很快就安装完成了。2,打开VisualStudioins...

Qt添加MSVC构建套件的方法(qt添加c++11)

前言有些时候,在Windows下因为某些需求需要使用MSVC编译器对程序进行编译,假设我们安装Qt的时候又只是安装了MingW构建套件,那么此时我们该如何给现有的Qt添加一个MSVC构建套件呢?本文以...

Qt为什么站稳c++GUI的top1(qt c)

为什么现在QT越来越成为c++界面编程的第一选择,从事QT编程多年,在这之前做C++界面都是基于MFC。当时为什么会从MFC转到QT?主要原因是MFC开发界面想做得好看一些十分困难,引用第三方基于MF...

qt开发IDE应该选择VS还是qt creator

如果一个公司选择了qt来开发自己的产品,在面临IDE的选择时会出现vs或者qtcreator,选择qt的IDE需要结合产品需求、部署平台、项目定位、程序猿本身和公司战略,因为大的软件产品需要明确IDE...

Qt 5.14.2超详细安装教程,不会来打我

Qt简介Qt(官方发音[kju:t],音同cute)是一个跨平台的C++开库,主要用来开发图形用户界面(GraphicalUserInterface,GUI)程序。Qt是纯C++开...

Cygwin配置与使用(四)——VI字体和颜色的配置

简介:VI的操作模式,基本上VI可以分为三种状态,分别是命令模式(commandmode)、插入模式(Insertmode)和底行模式(lastlinemode),各模式的功能区分如下:1)...

取消回复欢迎 发表评论: