基础IO+文件系统(从软件到硬件再到软件)

目录

  • 一、回顾C文件接口
    • 1.1 C语言如何写文件?
    • 1.2 C语言如何读文件?
    • 1.3 C语言打开文件的方式
  • 二、系统文件IO
    • 2.1 接口介绍
    • 2.2 open的返回值
    • 2.3 文件描述符(重中之重)
      • 2.3.1 Linux进程默认打开的输入输出流(012)
      • 2.3.2 文件描述符的分配规则
    • 2.4 重定向(重中之重)
      • 2.4.1 使用 dup2 系统调用
    • 2.5 FILE
  • 三、Linux下的ext2文件系统
  • 四、软硬链接
    • 4.1 硬链接
    • 4.2 软链接
  • 五、基础IO+文件系统(从软件到硬件再到软件整体脉络一览图)

一、回顾C文件接口

C语言文件操作

1.1 C语言如何写文件?

基础IO+文件系统(从软件到硬件再到软件)_第1张图片

1.2 C语言如何读文件?

基础IO+文件系统(从软件到硬件再到软件)_第2张图片

1.3 C语言打开文件的方式

基础IO+文件系统(从软件到硬件再到软件)_第3张图片
基础IO+文件系统(从软件到硬件再到软件)_第4张图片
C默认会打开三个输入输出流,分别是stdin, stdout, stderr
仔细观察发现,这三个流的类型都是FILE*, fopen返回值类型,文件指针。

二、系统文件IO

操作文件,除了上述C接口(当然,C++也有接口,其他语言也有),我们还可以采用系统接口来进行文件访问,先来直接以代码的形式,实现和上面一模一样的代码:
基础IO+文件系统(从软件到硬件再到软件)_第5张图片
基础IO+文件系统(从软件到硬件再到软件)_第6张图片

2.1 接口介绍

#include 
#include 
#include 
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
pathname: 要打开或创建的目标文件
flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。
参数:
O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR : 读,写打开
这三个常量,必须指定一个且只能指定一个
O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
O_APPEND: 追加写
返回值:
成功:新打开的文件描述符
失败:-1

mode_t理解:直接 man 手册查看,比什么都清楚。
open 函数具体使用哪个,和具体应用场景相关,如目标文件不存在,需要open创建,则第三个参数表示创建文件的默认权限,否则,使用两个参数的open。

2.2 open的返回值

在认识返回值之前,先来认识一下两个概念: 系统调用 和 库函数。
fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数(libc)。
而, open close read write lseek 都属于系统提供的接口,称之为系统调用接口

基础IO+文件系统(从软件到硬件再到软件)_第7张图片

通过以上这张图,系统调用和库函数之间的关系就一目了然了,
可以认为,所有的f*系列的函数都是对系统调用接口进行了封装,方便二次开发,因为系统调用接口用起来的难度很大,所以库函数做了封装使用起来就变得更简单一些。

2.3 文件描述符(重中之重)

2.3.1 Linux进程默认打开的输入输出流(012)

Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2.
0,1,2对应的物理设备一般是:键盘,显示器,显示器。
所以输入输出还可以采用如下方式:
基础IO+文件系统(从软件到硬件再到软件)_第8张图片
基础IO+文件系统(从软件到硬件再到软件)_第9张图片
文件描述符的含义: 我们现在知道,文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了struct file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。所以每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件,进行访问了。

2.3.2 文件描述符的分配规则

在files_struct结构体中维护的struct file* fd_array数组当中,找到当前没有被使用的最小的一个下标,作为新打开文件的文件描述符。
基础IO+文件系统(从软件到硬件再到软件)_第10张图片

2.4 重定向(重中之重)

基础IO+文件系统(从软件到硬件再到软件)_第11张图片
此时,我们发现,本来应该输出到显示器上的内容,输出到了文件 myfile 当中,其中,fd=1。这种现象叫做输出重定向。常见的重定向有:>, >>, <

那么重定向的本质是什么???
基础IO+文件系统(从软件到硬件再到软件)_第12张图片

2.4.1 使用 dup2 系统调用

基础IO+文件系统(从软件到硬件再到软件)_第13张图片
使用场景:
基础IO+文件系统(从软件到硬件再到软件)_第14张图片
printf是C库当中的IO函数,一般往 stdout 中输出,但是stdout底层访问文件的时候,找的还是fd:1, 但此时,fd:1下标所表示内容,已经变成了myfile的地址,不再是显示器文件的地址,所以,输出的任何消息都会往文件中写入,进而完成输出重定向。那追加和输入重定向是如何完成呢?追加重定向的原理和输出重定向的原理是一样的,仅仅是open打开文件时的方式不同,追加重定向是不清空文件,输出重定向是先清空文件再输出。

追加重定向:
基础IO+文件系统(从软件到硬件再到软件)_第15张图片
追加重定向和输出重定向的代码在其它的地方没有一点区别。

输入重定向:
基础IO+文件系统(从软件到硬件再到软件)_第16张图片

2.5 FILE

因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过fd访问的。所以C库当中的FILE结构体内部,必定封装了fd。
基础IO+文件系统(从软件到硬件再到软件)_第17张图片

我们发现 printf 和 fwrite (库函数)都输出了2次,而 write(系统调用) 只输出了一次。为什么呢?肯定和fork有关!

一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲。
printf 和 fwrite 等库函数会自带缓冲区(之前的进度条例子就可以说明),当发生重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲。所以我们放在缓冲区中的数据,就不会被立即刷新,甚至fork之后,但是进程退出之后,会统一刷新,写入文件当中。
但是fork的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的一份数据,即子进程也会刷新缓冲区,所以printf和fwite会产生两份数据。write 没有变化,说明系统调用接口没有所谓的缓冲区。

综上: printf fwrite 等库函数会自带缓冲区,而 write 系统调用没有带缓冲区。另外,我们这里所说的缓冲区,都是用户级缓冲区。其实为了提升整机性能,OS也会提供相关内核级缓冲区,不过不再我们这的讨论范围之内。
那这个缓冲区谁提供呢? printf fwrite 是库函数, write 是系统调用,库函数在系统调用的“上层”, 是对系统调用的“封装”,但是 write 没有缓冲区,而 printf fwrite 有,足以说明,该缓冲区是二次加上的,又因为是C库函数,所以该缓冲区由C标准库提供。

在/usr/include/libio.h中有FILE结构体的定义:
基础IO+文件系统(从软件到硬件再到软件)_第18张图片

三、Linux下的ext2文件系统

基础IO+文件系统(从软件到硬件再到软件)_第19张图片
Linux ext2文件系统,上图为磁盘文件系统图(内核内存映像肯定有所不同),磁盘是典型的块设备,硬盘分区被划分为一个个的block。一个block的大小是由格式化的时候确定的,并且不可以更改。例如mke2fs的-b选项可以设定block大小为1024、2048或4096字节。而上图中启动块(Boot Block)的大小是确定的。

Block Group:ext2文件系统会根据分区的大小划分为数个Block Group。而每个Block Group都有着相同的结构组成。例如学校管理学院一样。

1、超级块(Super Block):存放文件系统本身的结构信息,是描述整个分区的所有基本信息的。记录的信息主要有:bolck 和 inode的总量,未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个文件系统结构就被破坏了,但是一般在多个Block Group中都保存着Super Block,所以某个Super Block信息被破环,整个文件系统也不会立即被破坏了。

2、GDT,Group Descriptor Table:块组描述符,描述块组属性信息,例如该块组中有多少个inode被使用了,剩余多少个,有多少个data block被使用了,剩余多少个,inode编号是从多少开始的,该分组一共多大等关于该块组的所有基本信息。

3、块位图(Block Bitmap):Block Bitmap中记录着Data Block中哪个数据块已经被占用,哪个数据块没有被占用。

4、inode位图(inode Bitmap):每个bit表示一个inode是否空闲可用。

5、inode table:存放文件属性的结构体数组,数组中的每一个元素是一个inode结构体,结构体存文件的信息, 如 文件大小,所有者,最近修改时间等。

6、Data blocks :存放着以块为单位的内存块,用于保存文件内容,一个块等于8个扇区。

基础IO+文件系统(从软件到硬件再到软件)_第20张图片

四、软硬链接

4.1 硬链接

我们看到,真正找到磁盘上文件的并不是文件名,而是inode。 其实在linux中可以让多个文件名对应于同一个inode。 基础IO+文件系统(从软件到硬件再到软件)_第21张图片
test.txt和hard_link的链接状态完全相同,他们被称为指向文件的硬链接。内核记录了这个连接数,inode 657073的硬连接数为2。
我们在删除文件时干了两件事情:1.在目录中将对应的记录删除,2.将硬连接数-1,如果为0,则在对应的磁盘释放。

硬链接不是一个独立的文件,因为它没有独立的inode,建立硬链接只是在该目录下增加了一个文件名与inode的映射关系而已。所以硬链接数起始就是指有多少个文件名指向同一个inode的意思。

4.2 软链接

基础IO+文件系统(从软件到硬件再到软件)_第22张图片
软链接是一个独立的文件,因为它有独立的inode,软链接文件中的内容是指向的文件的路径,在这里可以认为soft_link中保存的内容是file.txt的路径信息。

五、基础IO+文件系统(从软件到硬件再到软件整体脉络一览图)




你学会了吗?如果感觉到有所收获,那就点点小心心点点关注呗!后期还会持续更新Linux操作系统的相关知识哦!我们下期见!

你可能感兴趣的:(Linux系统编程,数据库,linux,c++,c语言)