[Linux] 基础 IO

Posted 一个正直的男孩

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Linux] 基础 IO相关的知识,希望对你有一定的参考价值。

文件想大家都不陌生吧,计算机中所有的数据都存在文件中,但是前面是一个普通人对文件的理解,那么现在我就带你看看程序员眼中的文件



文章目录

1 语言层面上的文件(C语言)


1.2 C语言接口介绍

在C语言中想要对文件进行操作那么需要用到一下的接口(fopen,fclose,fwrite,fred)对文件进行读写操作

如图所示 : >

代码实现

写形式打开文件

int main()

  FILE * fd=fopen("log.txt","w");//打开文件
  const  char *str="hello word";
  fwrite(str,1,strlen(str),fd);//写文件
  fclose(fd);//关闭文件
  return 0;

结果:

写/读形式读打开文件

代码:

int main()

  FILE * fd=fopen("log.txt","w");//W打开文件
  FILE * fd=fopen("log.txt","r");//打开文件
  char str[1024];
  size_t ret=fread((char*)str,1,1024,fd);
  str[ret]='\\0';//c语言中字符串以\\0结尾

  printf("%s\\n",str);
  fclose(fd);//关闭文件
  return 0;

读结果

写结果

追加追加形式写文件

代码

int main()

  FILE * fd=fopen("log.txt","a");//打开文件

    const char *str="hahahaha~";
    fwrite(str,1,strlen(str),fd);
    fclose(fd);//关闭文件
    return 0;

追加结果:


1.2 系统接口介绍

不知大家是否记得,就是用户是无法访问硬件的(要访问则一定要贯穿整个计算机体系结构),一般系统会暴露接口,则库为了方便使用会进行封装,上述介绍的接口,但其实我们也可以直接调用这些系统接口

如图所示 : >

写的形式打开,不存在则创建文件

int mian()

  int fd = open("log.txt",O_CREAT|O_WRONLY,0x644);//打开文件没有则创建权限为644

   char * str="hello word";
  write(fd,str,strlen(str));


  close(fd);


结果

读形式打开,不存在则创建(读一般情况都在吧)

int main()

  int fd = open("log.txt",O_CREAT|O_RDONLY,0x644);//打开文件没有则创建权限为644
  char str[1024];
  ssize_t ret=read(fd,str,1024);
  str[ret]='\\0';
  printf("%s \\n",str);
  close(fd);


结果

追加形式打开

int main()

   int fd = open("log.txt",O_WRONLY|O_APPEND);
   char * str="hahaha~~~";
   write(fd,str,strlen(str));

结果

仔细观察系统接口与C语言的接口大致相同,那以后使用C的接口时你就会想到他的底层是如何运作的。但是他们还是有所区别,系统调用返回的是 一个整形(文件描述符)而C语言是一个FILE * 的指针,后文介绍文件描述符


1.3 三个默认打开输入输出流

不知道大家还记不记得三个标准输入输出流,分别是标准输入,标准输出,标准错误(stdin,stdout,stderr)所对应的硬件是键盘,显示器,显示器

那么为啥要默认打开这三个输入输出流?

都有输入输出的要求,且方便操作(如人生第一个程序hello world和你说输入要打开标准输入,打印时要打开标准输出,从入门到入土)

c语言就有对应的接口fprintf,fsanf他们就需自己指定的流输入输出:
如图所示 : >

计算体系结构图 : >




2 系统层面上理解文件


一个文件其实可以划分成俩个板块

  1. 属性
  2. 内容(数据)

创建一个文件不写入任何数据,会占用内存吗?

占用,上述说过一个文件分为俩部分属性与内容,而创建文件时显然属性也一定创建了


2.1如何理解linux一切皆文件

大家或多或少动听过linux下一切皆文件,那么这个要如何理解呢?其实只需要理解这六个字先描述在组织


当一个进程执行时,是否会打开各种文件,硬件(驱动)?

当然会啊,那么那么如果文件和硬件(驱动)打开的多,操作系统(作用: 文件、驱动、进程、内存管理)则需要讲他们管理起来,则管理的本质就是 先描述在组织


对文件的操作是??

emmm对文件的操作其实就是读写


各种硬件不是有不同的读写方式吗?如键盘只有读,我从来没看到过写数据到键盘上的,那么如何组织,如何如题所说一切皆文件呢?

确实各种硬件都有不同的读写方式,但是我们可以先描述在组织用,那么操作系统管理硬件,那么就可以用相同的方式操作
如图所示 : >


stdin对应的是键盘,stdout,stderr对应显示器


2.2 文件描述符 与 FILE

用系统的接口调用返回的一个整数也就是文件描述符,那是如何根据文件描述符来找对对应的文件呢?

代码

int main()
  int fd1=open("log1.txt",O_CREAT|O_WRONLY,0x666);
  int fd2=open("log2.txt",O_CREAT|O_WRONLY,0x666);
  int fd3=open("log3.txt",O_CREAT|O_WRONLY,0x666);
  int fd4=open("log4.txt",O_CREAT|O_WRONLY,0x666);
  int fd5=open("log5.txt",O_CREAT|O_WRONLY,0x666);
  int fd6=open("log6.txt",O_CREAT|O_WRONLY,0x666);
  int fd7=open("log7.txt",O_CREAT|O_WRONLY,0x666);
  int fd8=open("log8.txt",O_CREAT|O_WRONLY,0x666);

  printf("fd1 %d\\n",fd1);
  printf("fd2 %d\\n",fd2);
  printf("fd3 %d\\n",fd3);
  printf("fd4 %d\\n",fd4);
  printf("fd5 %d\\n",fd5);
  printf("fd6 %d\\n",fd6);
  printf("fd7 %d\\n",fd7);
  printf("fd8 %d\\n",fd8);

  return 0;

结果:

嗯居然是连续的,他们不会放在一起吧?为啥第一个文件描述符是从3开始的?

连续下标想到了啥?数组呀,没错这些文件底层其实是放在数组中(客观表示),文件描述符为啥从三开始,上述说过编译器会默认打开三个流(输入、输出、错误),自然后面创建的文件描述符只能从3开始

上述说用统一的方式看待硬件实现一切皆文件,但是文件一多就需要管理并用一种数据结构组织起来,这里用的数据结构就是数组
如图所示 : >

那么C是如何操作的呢?他的返回值不是一个FILE*吗?

其实FILE底层是一个结构体并且里面有俩个重要的部分1:C语言缓冲区 2:file_on(fd)
如图所示 : >

验证_filen 是否是fd

int main()

  printf("%d\\n",stdin->_fileno);
  printf("%d\\n",stdout->_fileno);
  printf("%d\\n",stderr->_fileno);
 int fd=open("log.txt",O_CREAT|O_WRONLY,0x666);//系统接口
  printf("%d\\n",fd->_fileno);

结果


那么C语言中底层调用文件我们大致就可以猜想出来了(fopen调用open,open返回fd给 fopen ,fopen再把fd写入到 FILE*对象中的_fileon,后面通过接口对文件读写

如图所示 : >


2.3 文件描述符的分配规则

文件描述符的分配规则其实很简单,就是数组中最小空余的下标,上述例子已经可以看出(标准输入、输出、错误占用下标0,1,2,新开文件就在3下标处)

标准输入输出错误流可以关闭吗?

既然他是默认打开的,注意默认打开,那么也就是说是被打开的,那么就可以被关闭

代码:

int main()

	 close(0);
    int fd=open("log.txt",O_CREAT|O_WRONLY,0x666);
    printf("%d\\n",fd);

结果

如果问吧标准输给关闭了,是否会打印呢?应该会吧,标准错误也是显示器

其实是不会打印的,虽然stderr也是显示器,但是底层要把数据输显示器上,还是通过stdout去输出,但是stdout中的fileon还是2,但files_strcuct中的fd 数组下标为2的数据已经被 “抹除”,那么就找不到显示器文件

如图所示 : >

2.3.2 重定向

重定向:把本该输出到固定位置的数据输出到别出简称重定向

上述说,可以把stdout关闭,但stdout的_fileson还是2且文件描述符分配规则是没使用且下标最小的位置,那么我新打开一个文件在输出数据不就是到文件中了吗,这不就是重定向吗??

验证代码:

int main()

  close(1);//关闭显示器文件
  int fd = open("log.txt",O_WRONLY);//已经存在

  printf("hello world");//输出到stdout中

  return 0;


结果:

正常情况下我们不会像上面的操作来完成重定向,但是我们确实需要重定向的需求,C语言中有个接口可以帮助我们实现重定向dup2

接口介绍

接口使用

int main()

  int fd = open("log.txt",O_WRONLY);//已经存在
  dup2(fd,1);
  printf("hello world");

  return 0;

结果


2.4 创建一个进程的新理解

其实上述中所画的图是有一点错误的,其实FILE*其实是在代码也就是数据,他是在内存中的,根本不可能直接访问files_struct,他们其实用是PCB进行互相访问

如图所示 : >


2.5 缓冲区的理解

上述在介绍文件描述符与FILE*时提到过FILE 是一个结构体,它里面包含了俩个重要的部分 1. 缓冲区 2. files_on, 缓冲区(对IO数据的临时存储的区域)想必大家已经耳熟能详了吧!


怪异现象(程序结束后调用fork)

int main()

  const char *msg0="hello printf\\n";
  const char *msg1="hello fwrite\\n";
  const char *msg2="hello write\\n";

  printf("%s",msg0);
  fwrite(msg1, strlen(msg0), 1, stdout);
  write(1,msg2,strlen(msg2));
  fork();

输出结果 : >

那么把输出的结果重定向到文件中的时候 : >

这个应该就是所谓的怪异现象吧,C语言的接口所输出的数据重定向后输出了俩份,这个是为啥呢?

确实这就是所谓的怪异现象,其实这都是看似没有用处的fork 与 C语言缓冲区导致的

  1. 都知道C语言的缓冲区是遇到 \\n、\\r、fflush、程序结束、缓冲区满了 才会看时机吧数据刷新给内核中的缓冲区,且C缓冲区对显示器是行刷新,对普通文件是全刷新
  2. fork后父子数据是共享的,那么fork前父进程输出的数据没有刷新,那么就留在C缓冲区(FILE 结构体,也就是数据),当父亲将数据刷新时,那么就会写实拷贝


    这就是为啥输出到屏幕上是正常的,但是重定向后却变得怪异起来

解决怪异现象(提前全刷新,防止写实拷贝)

 int main()
  const char *msg0="hello printf\\n";
  const char *msg1="hello fwrite\\n";
  const char *msg2="hello write\\n";

	printf("%s",msg0);
	fwrite(msg1, strlen(msg0), 1, stdout);
	write(1,msg2,strlen(msg2));
    fflush(stdout);//提前进行全刷新
	fork();

结果

为啥要存在C语言缓冲区 ? 不是有内核系统自带的缓冲区吗?直接刷新的系统的不香吗?

我觉得是提高效率,听个故事,现在是早上,你爹现在每5分钟给你2元,并会给你5次,2元只可以买一个包子,那么你是会拿到钱直接去买包子再回来等,还是等钱全部到手再去统一一次性买呢?

刷新策略如图所示 : >


2.6 文件系统

上述介绍的都是打开的文件,那么下面来介绍一下没有打开呆在磁盘上的文件吧!!!

这里有一个问题就是打开的文件要管理,那么没打开的文件需要进行管理吗?


那一定要管理呀,不然你如果需要打开文件去哪里找?搜索整个磁盘吗?


硬件部分:
机械硬盘,固态硬盘(ssd),光盘,磁带。现在磁带光盘应该已经退出时代的舞台了,现在基本用的都是机械硬盘或者是固态硬盘。由于ssd的工艺太高,那么我就介绍一下机械硬盘吧!

如图所示 : >

那如何吧数据写入固态硬盘中?open(“盘片/磁道/扇区/文件名”,……)?

你不觉得这个很麻烦吗?且不通用吧,假如你有钱了换个存储器呢?ssd那不是又要改。所以一切都需要抽象一下,直接把底层看成一个线性结构,像磁带一样只是卷在一起了(数组等),一个盘对应一个数组,可以把他抽象为多维数组(in t arr[盘面][磁道][扇区],如何存储器都是如此),底层维护这种关系的是对应存储器的驱动


补充1 :

都知道,需要执行一个程序一定要把程序的加载到内存中,那么程序底层不过就是代码数据,那这些都是存在磁盘中的。内存IO大小是4字节,且他也是有自己基础单位是1字节

补充2 :

当存储器用统一的方式看待后,管理又是一个难题,假设存储器的大小为2个T,这时就是一个巨大的数组。分区不知道大家是否听过 (分区:把大空间的盘分为多个小空间的盘,方便操作系统使用和管理),没错这里就要进行分区。

//stata 文件 的截图


2. 6.2 文件系统

LINUX文件系统

  1. EXT2
  2. EXT3
  3. fs
  4. usb-fs
  5. sysfs、proc
  6. ……

每一个分区所使用的管理系统不一定是一样的这个要看OS

EXT2文件系统介绍

inode

每一个文件,目录,都有一个Inode,且Inode中存放的是文件的属性信息,但是不包括文件名,查看inode 在ls后面+i选项


通过上面的EXT2文件系统现在可以知道一个文件在磁盘上是如何存放的
inode table 存放文件的属性,Data blokcks 存放数据),但是他们是如何联系的呢?

创建一个文件那么就会申请Inode存文件的属性信息,在申请blocks存放数据内容,他们如何管理,每个inode里面有一个数组存的是blocks的编号(存放内容的block)
如图所示 : >


我好奇Block bitmap 和 Inode bitmap 是计数让我们知道Inode和blocks的使用情况?用一个整形来计数吗?

其实计数也不是不行但是,这里用了却更加妙,这里使用的位图(用一个bit位来标识状态),相比计数他可以很快的找到那个Inode和blocks使用或者没有使用 如图所示 : >


那么现在要操作一个文件我是如何找他的呢?

不知道大家是否还记得,目录结构其实是一个树状结构的,且上面也说过目录也是有自己的Inode(目录也是文件,也有自己的属性),那么他的内容是啥呢?当然是里面文件的文件喽(与文件对应的Inode)毕竟一个当父亲(目录)不可能不记得自己儿子(文件)的名字(文件名)和生日(inode)

树状结构如图所示 :>
目录Inode与blocks与文件的关系 如图所示 :>


目录也是文件,那我要找当前目录不是要找上级目录………

没错正是如此,他会一直一直的往上找知道根目录,执行过一次后就会缓存路径,现在可以解释一个现象了,就是当你第一次开机执行命令的时候是不是都比平时慢一点(自己验证下吧)?


文件名是否是一个文件的标识?理解ls后就可以看到存在磁盘上文件的属性信息?

当然不是表示一个文件的是Inode。
ls后,直接区当前目录找文件,并通inode返回文件的属性


现在理解一下创建一个文件需要的操作

1.inode bitmap 和 blocks bitmap申请所需大小个数,2. 存储到inode中的block组中当前文件内容所需block的编号 3. 把文件名和inode的映射关系存到目录的block中
如图所示 :>


2.6.3 软硬链接


想要进行软硬链接就需要用到命令 ln

软硬链接的理解

如果用C++来理解软连接是指针而硬连接是引用,也就是说软连接有自己的空间,而硬链接就是本体
如图so是软ha是硬 : >你会发现硬的inode和文件是一样的,而软的inode是不同的,那么就说inode也有对应的block,他的block中存的就是文件的路径(类似windows中的快捷方式)


怪异现象(创建文件硬链接是1,创建目录确实2)

是否还记得ls的一个选项就是-a显示全部包括隐藏的文件?你会发现有一个 .的文件 他就是代表当前文件夹,..的文件代表上级目录。当查看 .文件的inode时发现居然是一样的


完结🎉🎉🎉🎉

上面就是博主总结的IO博客,如果对你有所帮助,请给我点个赞吧,点赞,点赞,点赞,给大家跳个舞!!!




以上是关于[Linux] 基础 IO的主要内容,如果未能解决你的问题,请参考以下文章

Linux篇第九篇——基础IO(系统文件IO+文件描述符+重定向+文件系统+软硬链接)

Linux文件系统与基础IO

Linux文件系统与基础IO

Linux文件系统与基础IO

linux基础编程:IO模型:阻塞/非阻塞/IO复用 同步/异步 Select/Epoll/AIO(转载)

RAID原理基础及Linux下软件RAID配置