Linux基础IO——文件系统(上)

Posted 风起、风落

tags:

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

文章目录

1.了解磁盘的物理结构

磁盘计算机上唯一的一个机械设备,同时它还是外设
机械磁盘很便宜,虽然效率会慢一些,所以企业一般使用机械磁盘,因为便宜
磁盘不仅仅外设,还是一个机械设备(盘片、磁头),所以磁盘一定非常慢


盘片:一片两面,有一摞盘片


磁头:一面一个磁头
一个磁头负责一面的读取


马达比如说剃须刀,或者手机的震动等
所以盘片就可以 以顺时针的方式高速旋转
同时在磁头位置也存在一个马达,控制磁头左右来回摆动
磁头是共进退的,要不一块过去,要不就一块不去


磁盘盘片上有无数个基本单元,每一个基本单元按照特定的空间排布好的,每一个单元就是磁铁
南极表示1,北极表示0,
向磁盘写入:把北极改成南极 (N->S)对内容做磁化
删除磁盘数据:把数据从南极设置为北极 (S->N)
这样就可以完成微观上 一个比特位的读和写
磁头本质上 是对数据做写入和读取,更改基本元素的南北极,读取南北极

磁盘的具体物理存储结构

整体结构


抽象的一面结构


磁盘中存储的基本单元是扇区,一般扇区的大小为512字节或者4KB字节
一般磁盘所有的扇区都是512字节大小
同半径的所有的扇区即为磁道


在一面上,如何在硬件上定位一个扇区?

1.先定位在哪一个磁道—由半径决定


2.再确定在该磁道,在哪一个扇区,根据扇区的编号,来定位一个扇区

所以首先要定位哪一个面


磁头是共进退的,半径相同的每一个面上的磁道共同在抽象上就会形成一个柱面
只需要确定用哪一个磁头读取,磁头的编号就表示哪一个面
所以定位任意一个扇区,需要确定 磁头head 、柱面 cylinder、扇区 sector 即CHS定位法

普通文件中包含属性和数据,都可以看做数据(0,1),占用一个或者多个扇区,来进行自己的数据存储
既然能用CHS定位为任意一个扇区,就能定位任意多个扇区,从而将文件从硬件角度进行读取或者写入

2.逻辑抽象

OS内部是不是直接使用CHS地址?不是
第一点:因为OS是软件,磁盘是硬件,硬件定位一个地址,用的是CHS,但是如果OS直接用了这个地址,万一硬件改变,OS也要发生变化,所以OS要和硬件做好解耦工作


第二点:即便是扇区512字节,单位IO的基本数据量也是很小的,所以硬件是按照512字节处理,
操作系统实际进行IO,基本单位是4KB
操作系统和磁盘进行交互时,基本以4KB为单位,
基本大小:进行磁盘读取和磁盘写入时,必须以基本单位为基本大小,来与外设进行交互


哪怕只修改一个比特位,也要把一个比特位所在的4KB全部读到内存,
把一个比特位改完,再把4KB内容全部写到目标文件中,要以块的方式整体与外设进行交互
所以一般把磁盘称为 块设备
所以OS需要有一套新的地址,来进行块级别的访问


把盘面抽象成一种线性结构
以一个盘面举例
相同磁道的东西一定放在一起的
整体可以看作是数组,设置数组名为 sector_disk1


初步完成了从物理逻辑到线性逻辑抽象的过程
因为看作是一个数组,而数组都是有下标的
假设数组下标为n,定位一个扇区,只需要数组下标就可以定位一个扇区了


OS是以4KB为单位进行IO的,一个操作系统对应的文件块要包括8个扇区
计算机常规访问方式:采用起始地址+偏移量的方式
只需要知道数据块起始位置的地址,即第一个扇区的下标地址 + 4KB(块的类型)
可以把数据块看作一种类型

所以块的地址本质就是数组的一个下标N
N的地址在OS中被叫做LBA,即逻辑块地址

磁盘只认CHS,LBA如何跟磁盘地址互相转化?

假设LBA地址为6500 ,单片大小为5000
首先确定在那一面,也就相当于在哪一个磁头上
H(磁头): int n=6500/5000=1 说明H的地址在第2面上
C(柱面):6500/1000=6 1000为磁道的大小,对应的H就在6号磁道上
S(扇区): 6500 %1000=500


所求扇区 就在 2号面中的6号磁道的第500个扇区
连续读取8个扇区,就能得到块

3.文件系统

为了方便管理,就把数组拆分为一个个的区域
若感觉管理500GB太难了,就减少管理,从而管理150GB,将管理小的经验复制,从而使每一个小的都管理好
从而使500GB整体管理好 ,这种思想就叫做分区


虽然分完区小了很多,但依旧很大,所以操作系统对整个分区还会在做分组
假设每个区为150GB,就把15GB为一组,把其中一个组管理好了,按照一个组的管理方式就把所有组管理好了
由于每个区都可以分组,就有了 Block group



一个分区当中最开始有一个Boot Block
会保存与操作系统启动相关的内容,如分区表和操作系统镜像地址

一个组的结构

一个组中分为 Super Block(超级块) 、Group Descriptor Table(组描述符)、Block Bitmap、inode Bitmap(位图)、inode Table (inode表)、Data blocks(数据块)


Super Block 保存的是文件系统的所有属性信息 如文件系统的类型、整个分组的情况
Super Block在各个分组里面可能都会存在,而且是统一更新的
为了防止Super Block区域坏掉,如果出现故障,整个分区不可以被使用,所以要做好备份


Group Descriptor Table
GDT:组描述符 – 改组内的详细统计等属性信息,用来描述整个


一般而言,一个文件内部所有属性的集合,被称为inode节点 ,大小一般为128字节
一个文件,一个inode,一个分区内部也会存在大量的文件即会存在大量的inode节点,一个group,需要有一个区域专门保存该group的所有文件的inode节点 即 inode table -----inode表


文件的内容是变化的,用数据块对文件内容保存的,所以一个有效文件要保存内容就需要1/n数据块
若有多个文件就需要更多的数据块,数据块称为 Data blocks


linux查找一个文件,是要根据inode编号,来进行文件查找到,包括读取内容
一个inode对应一个文件,而该文件inode属性和该文件的数据块是由映射关系的


inode Bitmap
共有4096*8个比特位,按照从低向高扫描位图时,比特位的位置对应inode表中的inode
为1表示inode正常工作,为0表示inode不正常工作
每一个比特位表示 一个inode是否空闲可用


Block Bitmap
每一个bit位表示data block是否空闲可用


细节问题

1.inode与文件名
Linux系统只认inode编号,文件的inode属性中,并不存在文件名
文件名是给用户用的


2.目录是文件么?
是的,目录有inode和内容


3.任何一个文件,一定在目录内部,所以目录的内容是什么?
目录要有内容就需要数据块,目录的数据块里面保存的是该目录下 文件名和inode编号对应的映射关系
在目录内,文件名和inode编号互为key值


4.当我们访问一个文件的时候,是在特定目录下访问的 cat log.txt
1.先要在当前目录下,找到log.txt 的 inode编号
2.一个目录也是一个文件,也一定属于 一个分区,在该分区中找到分组,在该分组中对应的inode table中,找到文件的inode
3. 通过inode与 对应的data block的映射关系,找到该文件的数据块 ,并加载到OS,并完成到显示器

[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——文件系统(上)

linux云自动化运维基础知识22(系统恢复)

Linux基础入门--IO重定向及管道

Linux系统编程:基础IO 上简单复习C语言文件接口 | 学习系统文件接口 | 认识文件描述符 | Linux下,一切皆文件 | 重定向原理

Linux系统编程:基础IO 上简单复习C语言文件接口 | 学习系统文件接口 | 认识文件描述符 | Linux下,一切皆文件 | 重定向原理

Linux系统编程:基础IO 上简单复习C语言文件接口 | 学习系统文件接口 | 认识文件描述符 | Linux下,一切皆文件 | 重定向原理