当前位置: 首页 > news >正文

Linux之基础IO(上)

目录

库函数文件操作

写文件

读文件

系统调用文件操作 

写文件

读文件

文件描述符fd 

深刻理解linux下一切皆文件

重定向原理 


在c语言中我们学习了fopen,fread,fwrite接口,用于进行文件相关的操作,在之前我们学习了计算机的相关结构,由下往上依次为硬件层,驱动层,操作系统层,系统调用层,用户层,c语言中的接口是处于用户层的,用户层是不能直接跨过操作系统从而对对应的硬件设备进行读写操作,必须从上往下贯穿整个操作系统才能进行读写操作,本期我们将详细学习这一部分知识的基本原理。

库函数文件操作

写文件

#include<stdio.h>
#include<string.h>
int main()
{FILE* fp=fopen("./log.txt","w");if(!fp){printf("open fail\n");}else
{const char* msg="hello world\n";
//fwrite为实际写了多少字节的数据fwrite(msg,strlen(msg),1,fp); }fclose(fp);return 0;
}

fopen打开文件之后,返回的是一个文件指针,文件指针指向了一个文件结构体,文件结构体里包含了打开的文件相关信息。

读文件

int main()
{FILE* fp=fopen("./log.txt","r");if(!fp){printf("open fail\n");}else{char buf[1024];const char* msg="hello yjd\n";//一次读取一个字节,msg的一个元素一个字节,总共读取strlen(msg)个字节,返回值为实际读到的字节的个数。ssize_t s= fread(buf,1,strlen(msg),fp); if(s){printf("%s\n",buf); }}fclose(fp);return 0;
}

log.txt中的文件。

 代码中的ptintf想必大家都很熟悉,就是将要打印的文件打印到标准输出上,那么什么是标准输出呢?下来我们一一进行解答。

在C中我们有三个输出流,标准输入标准输出和标准错误,分别对应了键盘文件,显示器文件和显示器文件。

不难发现这三个流的返回类型也是FILE*,我们发现打开文件的返回类型也是FILE*,这个FILE*究竟是何种神圣呢?

其实,流中的操作其实都可以理解为对流文件的读写操作,标准输入流就是从键盘文件中进行读取,标准输出流和标准错误流都是往显示器文件中进行写入操作。所有的读取和写入的前提都是先打开文件。既然进程要打开文件,那么操作系统为了进行管理就必须创建打开文件对应的数据结构,从而对打开的文件进行管理,也即我们一直所讲述的先描述后组织。所以fopen和三个流的返回值类型为FILE*也就可以理解了,在C++中也一样,stdin,stdout和stderr分别对应cin,cout和cerr。

上述的对文件的读写操作都是站在用户的角度对文件进行读写的,实质上是对系统调用文件读写接口进行了封装,下来我们将学习系统调用文件读写接口。 

系统调用文件操作 

上一个标题我们使用的是库函数中的文件接口进行文件操作,实际上库函数接口的实现往往是基于系统调用接口。

写文件

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>int main()
{int fd=open("./log.txt",O_WRONLY|O_CREAT,0644);if(fd<0){printf("open err\n");return 1;}else{const char* msg="hello YJD\n";int len=strlen(msg);//write函数的返回值是实际写了多少字节的数据write(fd,msg,len);}close(fd);return 0;
}

代码中的fd表示的是文件描述符,至于什么是文件描述符,我们下面会讲到。 

读文件

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>int main()
{int fd=open("./log.txt",O_RDONLY);if(fd<0){printf("open err\n");return 1;}else{char buff[1024];const char* msg="hello YJD\n";int len=strlen(msg);//read函数的返回值是实际读了多少字节的数据ssize_t s = read(fd,buff,len);if(s<0){printf("read err\n");return 1;}else{printf("%s",buff);}}close(fd);return 0;
}

以上便是系统调用文件操作的相关接口。

文件描述符fd 

在上个标题我们提出了文件描述符的概念,那么究竟什么是文件描述符呢?

在一个文件没有被打开时,这个文件是在磁盘上的,当一个文件被进程打开时,就被加载到了内存中,我们知道操作系统是系统软件和硬件的管理者,当一个文件被打开时,也就意味着一个软件被打开,所以操作系统要管理被打开的文件,就得请出我们的六字真言“先描述,在组织”,所以当一个文件被打开加载到内存时,操作系统就得为这些打开的文件创建对应的数据结构,从而方便进行管理。被打开的文件的数据结构我们用 struct file来表示,每打开一个文件,就为这个文件创建对应的struct file结构体对象,这个数据结构中存储的是对应的打开的文件的相关属性数据。以图示为大家进一步解释。

 进程相关的属性和数据存储在进程控制块中。进程再打开文件时,一次可以打开多个文件,那么进程如何知道自己打开的多个文件是什么文件呢,所以在进程控制块中有一个struct files_struct*的结构体指针,指向了struct files_struct结构体,在这个结构体里面有一个指针数组,数组的每一个元素都是一个struct file*类型的结构体指针,分别指向了该进程在内存中打开的文件。所以进程通过这样一个结构体指针的方式能够清晰的知道自己打开了哪些文件。

在图示中我们会发现一个struct file* fd_arry[fd]的指针数组,我们上述所讨论的文件描述符就是这个数组的下标,所以当进程在内存中打开一个文件,对应就会在指针数组中分配一个下标,让该下标所对应的元素指向被打开的文件。

阅读下面的代码。

#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{int fd = open("./log.txt",O_RDONLY);printf("%d\n",fd);return 0;
}

通过运行截图我们不难看出,这个fd的值竟然是3,为什么是3呢?下标难道不应该是从0开始的吗?

我们之前提到了标准输入stdin(键盘文件),标准输出stdout(显示器文件),标准错误stderr(显示器文件)。其实,fd就是从0开始的,但是我们所有的进程都是1号进程bash的子进程,bash是终端进程,终端就意味着要输入命令,要输出结果,要输出错误。所以bash终端进程就默认会打开键盘文件和显示器文件,所以操作系统也就会为bash进程默认分配0,1,2这个文件描述符,对应打开的键盘文件,显示器文件和显示器文件。这里还有一个知识点,就是所有的子进程都会继承父进程的数据结构,所以父子进程的 struct files_struct结构体也是两份相同的数据结构,所以对应的指针数组也是相同的,因为所有进程都是bash的子进程,所以这些进程的指针数组和bash进程的指针数组也是相同的,也就意味着所有的进程都会默认打开三个对应的文件,都会默认占用0,1,2这三个文件描述符。

深刻理解linux下一切皆文件

图示如下。

上层通过open接口打开文件,通过write和read接口进行文件的读写操作,但是实际上这个write和read接口有很多种,因为文件的种类有很多种。struct file是打开的文件的数据结构,这些数据结构中对应了多种文件,键盘文件,显示器文件,磁盘文件,显卡文件,其它文件。上层open打开一个文件就会为这个打开的文件分配一个文件描述符,后期通过文件描述符找到对应打开的文件,然后调用write和read方法进行读写,write方法和read方法对于每种文件是不一样的,通过虚拟文件层的函数指针找到具体种类文件的读写方法,这些读写方法存在于操作系统中的驱动层。所以上层的read和write方法就类似于后期我们学习的C++语法中的多态的基本概念,每种文件的读写方法对系统调用的读写方法进行了重写,在调用读写方法时,可以根据不同的文件种类调用不同文件种类的读写方法。

综上,操作系统是不考虑打开的文件是哪种类型文件的,在虚拟文件层都把不同种类的文件统一用struct file类型的结构体进行了描述,基于此,我们称在linux操作系统下,一切皆文件。

重定向原理 

阅读下面代码。

int main()
{close(1);int fd = open("./log.txt",O_WRONLY|O_CREAT,0644);printf("hello yjd\n");return 0;
}

运行上述代码,我们发现再运行了可执行程序之后,终端并没有打印hello yjd,但是我们惊奇的发现在log.txt中,竟然出现了hello yjd这个字符串,这究竟是为什么呢?

其实,整个过程就是重定向的原理,将在显示器上打印的数据写入了文件中。整个过程的原理如图所示。

 上图展示的是输出重定向的全过程。

printf函数是向标准输出文件(显示器文件)写入数据,从而在显示器上进行显示。标准输出文件对应的文件描述符为1,对于上述代码,当我们关闭1号文件描述符,即让显示器文件与1号文件描述符对应的结构体指针取消链接关系,然后我们打开了log.txt文件,然后让给其分配了1号文件描述符。这便是问题所在,与其说printf函数是向标准输出文件写入数据,不如说,printf是向1号文件描述符所对应的结构体指针指向的文件写入数据,因为此时1号文件描述符对应的结构体指针指向的文件时log.txt,所以此时printf就会向log.txt中写入数据。

这便是我们打印字符串,在显示器上看不见字符串,但是在log.txt中可以看到对应字符串现象的原理解释。输入重定向的原理也是类似的。

除了close描述符,还有dup2函数。代码如下。

 int main()
{int fd = open("./log.txt",O_WRONLY|O_CREAT,0644);dup2(fd,1);printf("hello yjd\n");return 0;
}

dup2函数的含义为,让第二个参数(文件描述符)指向第一个参数(文件描述符)对应的结构体指针所指向的文件。

以上便是本期的所有内容。

本期内容到此结束^_^

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • TeraTerm 使用技巧
  • 什么是单例模式,有哪些应用?
  • 模板、STL 简介(深度剖析)
  • VisualRules-Web案例展示(一)
  • mysql、oracle、db2数据库连接参数
  • SpringSecurity通用权限管理系统
  • 关于合芯新通RTK配置的方法记录7.23
  • k8s部署rabbitmq集群
  • Json结构解析比较
  • 69、ncnn学习onnx2ncnn不支持带三维算子相乘gemm/repeat转换方法学习
  • CH04_依赖项属性
  • day02 mybatis
  • 微信小程序数组绑定使用案例(二)
  • 《流程引擎原理与实践》开源电子书
  • 【大数据专题】数据仓库
  • [微信小程序] 使用ES6特性Class后出现编译异常
  • C# 免费离线人脸识别 2.0 Demo
  • electron原来这么简单----打包你的react、VUE桌面应用程序
  • java小心机(3)| 浅析finalize()
  • jquery ajax学习笔记
  • Ruby 2.x 源代码分析:扩展 概述
  • SegmentFault 社区上线小程序开发频道,助力小程序开发者生态
  • Tornado学习笔记(1)
  • 给新手的新浪微博 SDK 集成教程【一】
  • 类orAPI - 收藏集 - 掘金
  • 利用阿里云 OSS 搭建私有 Docker 仓库
  • 算法-图和图算法
  • 微信端页面使用-webkit-box和绝对定位时,元素上移的问题
  • 译有关态射的一切
  • 译自由幺半群
  • 400多位云计算专家和开发者,加入了同一个组织 ...
  • ​LeetCode解法汇总2182. 构造限制重复的字符串
  • #70结构体案例1(导师,学生,成绩)
  • #Js篇:单线程模式同步任务异步任务任务队列事件循环setTimeout() setInterval()
  • #我与Java虚拟机的故事#连载13:有这本书就够了
  • (02)Hive SQL编译成MapReduce任务的过程
  • (11)工业界推荐系统-小红书推荐场景及内部实践【粗排三塔模型】
  • (14)Hive调优——合并小文件
  • (6) 深入探索Python-Pandas库的核心数据结构:DataFrame全面解析
  • (安全基本功)磁盘MBR,分区表,活动分区,引导扇区。。。详解与区别
  • (附源码)php投票系统 毕业设计 121500
  • (附源码)ssm经济信息门户网站 毕业设计 141634
  • (排序详解之 堆排序)
  • (十六)一篇文章学会Java的常用API
  • (原創) 如何刪除Windows Live Writer留在本機的文章? (Web) (Windows Live Writer)
  • (转)jdk与jre的区别
  • (转)PlayerPrefs在Windows下存到哪里去了?
  • (转载)跟我一起学习VIM - The Life Changing Editor
  • .NET Core日志内容详解,详解不同日志级别的区别和有关日志记录的实用工具和第三方库详解与示例
  • .NET/C# 使用反射调用含 ref 或 out 参数的方法
  • .NET程序集编辑器/调试器 dnSpy 使用介绍
  • .net图片验证码生成、点击刷新及验证输入是否正确
  • ;号自动换行
  • @NotNull、@NotEmpty 和 @NotBlank 区别
  • @开发者,一文搞懂什么是 C# 计时器!