Linux之基础IO(上)

news2024/11/25 15:42:34

目录

库函数文件操作

写文件

读文件

系统调用文件操作 

写文件

读文件

文件描述符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函数的含义为,让第二个参数(文件描述符)指向第一个参数(文件描述符)对应的结构体指针所指向的文件。

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

本期内容到此结束^_^

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

站在资本投资领域如何看待分布式光纤传感行业?

近年来&#xff0c;资本投资领域对于分布式光纤传感行业并不十分敏感。这主要是由于分布式光纤传感技术是一个专业且小众的领域&#xff0c;其生命周期相对较长&#xff0c;缺乏爆发性&#xff0c;与消费品或商业模式创新产业有所不同。此外&#xff0c;国内的投资环境也是影响…

webStorm 实时模板笔记

文章目录 1、单斜杠效果 2、双斜杠效果 3、控制台打印效果 1、单斜杠 /** $END$ */效果 2、双斜杠 /*** $END$* author Ikun* since $DATE$ $TIME$ */DATE date() ✔ TIME time() ✔效果 3、控制台打印 console.log("███████$EXPR_COPY$>>>>&a…

040403数据选择器比较器运算电路

数据选择器&比较器&运算电路 4.4.3数据选择器0.数据选择器的定义与功能1.4选1数据选择器2.集成电路数据选择器利用8选1数据选择器组成函数产生器的一般步骤 4.4.4 数值比较器1.1位数值比较器2.2位数值比较器3.集成数值比较器4.集成数值比较器的位数扩展 4.4.5算术运算电…

时序分解 | Matlab基于CEEMDAN-CPO-VMD的CEEMDAN结合冠豪猪优化算法(CPO)优化VMD二次分解

时序分解 | Matlab基于CEEMDAN-CPO-VMD的CEEMDAN结合冠豪猪优化算法&#xff08;CPO&#xff09;优化VMD二次分解 目录 时序分解 | Matlab基于CEEMDAN-CPO-VMD的CEEMDAN结合冠豪猪优化算法&#xff08;CPO&#xff09;优化VMD二次分解效果一览基本介绍程序设计参考资料 效果一览…

嵌入式linux学习的每一步具体要掌握什么?

嵌入式Linux是一门综合性很强的学科&#xff0c;涉及到操作系统、硬件、驱动程序、应用开发等多个方面。我收集归类了一份嵌入式学习包&#xff0c;对于新手而言简直不要太棒&#xff0c;里面包括了新手各个时期的学习方向编程教学、问题视频讲解、毕设800套和语言类教学&#…

【python基础知识】整除

熟练使用你所常用的开发语言是一个非常基本的要求。如果你日常需要使用Python&#xff0c;但是你对向上取整&#xff0c;向下取整&#xff0c;以及Python中的默认实现方式是什么都不知道的话&#xff0c;那么我就需要怀疑你的专业能力了。 1. 整除 讲解这个整除的知识&#xf…

JAVA毕业设计152—基于Java+Springboot+vue+小程序的个人健康管理系统小程序(源代码+数据库+15000字论文)

毕设所有选题&#xff1a; https://blog.csdn.net/2303_76227485/article/details/131104075 基于JavaSpringbootvue小程序的个人健康管理系统小程序(源代码数据库15000字论文)152 一、系统介绍 本项目前后端分离带小程序(可以改为ssm版本)&#xff0c;分为用户、管理员两种…

图片转换之heic转jpg(使用ImageMagick)

缘由&#xff1a;iphone的图库&#xff0c;用jpg拍照保存后内存占比较大&#xff0c;heic格式会微缩不少。问题来了&#xff0c;电脑不能直接小图预览heic。 分析&#xff1a;现在就是解决小图预览的问题&#xff08;大图用wps可以看&#xff09; 解决&#xff1a;查找了一些…

《0基础》学习Python——第二十二讲__网络爬虫/<5>爬取豆瓣电影封面图

一、爬取豆瓣电影的图片封面 1、经过上节课我们所爬取的豆瓣电影的电影名、年份、国家、导演、主演、剧情&#xff0c;那么接下来我们将学习如何去爬取这些电影的图片&#xff0c;并将这些图片存放在文件夹中。 2、过程实现&#xff1a; 2.1、获取网页源码 首先还是和爬取电影名…

Java之 jvm

jvm之管理内存 程序计数器&#xff1a;当前线程所执行的字节码的行号指示器。程序计数器是唯一一个不会出现 OutOfMemoryError 的内存区域&#xff0c;它的生命周期随着线程的创建而创建&#xff0c;随着线程的结束而死亡。Java虚拟机栈 方法调用 一个方法调用都会有对应的栈帧…

【如何在Jenkins的从节点切换NPM镜像源查看和切换】

【问题】 Jenkins打包时&#xff0c;前端npm构建时很慢&#xff0c;所有需要更换镜像源 【自查】 找到Jenkins从节点上的nodejs安装的路径&#xff0c;进入bin目录 执行./npm -v查看是不能正常查看&#xff0c; [rootlocalhost bin]# ./npm -v /usr/bin/env: ‘node’: No su…

华为OD机试2024年C卷D卷 - 山脉的个数/攀登者1 (Java)

华为OD机试&#xff08;C卷D卷&#xff09;2024真题目录 题目描述 攀登者喜欢寻找各种地图&#xff0c;并且尝试攀登到最高的山峰。 地图表示为一维数组&#xff0c;数组的索引代表水平位置&#xff0c;数组的元素代表相对海拔高度。其中数组元素0代表地面。 例如&#xff…

MySQL面试篇章——MySQL核心SQL(基本语法)

文章目录 结构化查询语句SQL库操作表操作CURD 操作insert 增加update 修改delete 删除 select 查询去重 distinct空值查询union 合并查询带 in 子查询分页查询排序 order by分组 group by笔试实践题 连接查询内连接查询外连接查询左连接查询右连接查询 结构化查询语句SQL SQL是…

内置对象的原型继承

Object.prototype&#xff08;原型链顶部&#xff09; console.log(ItShare.__proto__); console.log(ItShare.__proto__.__proto__); console.log(ItShare.__proto__.__proto__.__proto__);那么数组的原型链是什么呢&#xff1f; const arr [1, 2, 5, 4, 55, 66, 54]; cons…

matlab2018b安装

1.可先参考这个 2.激活 按上面教程安装后&#xff0c;打开matlab 可能会出现软件激活这个界面&#xff0c;需要按如下步骤进行操作。

Android Studio 中使用switch语句时报错Constant expression required

发现问题 Android Studio 中使用switch语句时报错Constant expression required 解决方法&#xff1a; 首先&#xff0c; 在gradle.properties配置文件下添加代码&#xff1a; android.nonFinalResIdsfalse之后&#xff0c; 点击&#xff1a;“Sync Now” 进行同步 效果 原…

《梦醒蝶飞:释放Excel函数与公式的力量》18.2 数据可视化技术

第18章&#xff1a;创建图表和数据可视化 18.2 数据可视化技术 数据可视化是将数据转化为图形和图表的过程&#xff0c;以便更好地理解和分析数据。有效的数据可视化可以揭示数据的模式、趋势和异常&#xff0c;从而帮助做出更明智的决策。以下将介绍几种常用的数据可视化技术…

【C++】————搜索二叉树

作者主页&#xff1a; 作者主页 本篇博客专栏&#xff1a;C 创作时间 &#xff1a;2024年7月22日 什么是二叉搜索树&#xff1f; 二叉搜索树又称二叉排序树&#xff0c;它或者是一棵空树&#xff0c;或者是具有以下性质的二叉树&#xff1a; 若它的左子树不为空&#xff0…

大模型学习笔记 - LLM指令微调

LLM 指令微调 LLM 指令微调 0. 概览1. 指令数据的构建 1.1 基于现有NLP任务数据集构建1.2 基于日常对话数据构建1.3 基于合成数据构建1.4 指令数据构建的提升方法 2. 指令微调的策略 2.1 优化设置2.2 数据组织策略 3. 参数高效的模型微调 3.1 低秩适配微调方法3.2 其他高效微调…

【跨链机制】哈希锁定原理

随着区块链多年来的发展&#xff0c;在区块链生态中已经形成了不同特性的区块链网络&#xff0c;这些网络之间存在着一定的隔离和孤立性。为了打破这种局面&#xff0c;跨链技术应运而生&#xff0c;促进了区块链间的数据流通和价值交换。目前&#xff0c;主流的跨链机制包括公…