✨前言✨
📘 博客主页:to Keep博客主页
🙆欢迎关注,👍点赞,📝留言评论
⏳首发时间:2024年10月16日
📨 博主码云地址:渣渣C
📕参考书籍:C语言程序与设计 和 数据结构(C语言版)
📢编程练习:牛客网+力扣网
Linux中文件的理解
- 一 系统调用接口
- 1 open函数(打开操作)
- 1.1 flag标志位
- 1.2 文件权限
- 2 write函数(写入操作)
- 3 read函数(读取文件)
- 4 close函数(关闭文件)
- 5 文件描述符
- 6 dup2函数(重定向)
- 二 缓冲区
- 三 文件系统
- 四 软硬链接
~~~~~~~~ 对于文件我们都知道,文件=文件内容+文件属性!我们都知道要对文件进行操作,我们第一步就是要打开文件,根据我们之前提及的冯诺依曼体系结构,该文件就必须加载到内存当中,也就是说是对应的进程打开文件的,而文件我们一般可以分为两种,一种就是被加载到内存中的,一种是位于磁盘上的!我们先来学习内存文件,在C语言中,也提供了一系列的库函数来对文件进行操作!
读写操作
打开操作
关闭操作
我们主要介绍以下三种打开文件的模式:
以w方式打开文件时候,在对文件进行写入操作的时候,是会覆盖掉原文件有的内容的,相当于我们之前说过的重定向!而使用a方式打开文件,在对文件写入操作就可以在原文件有内容的基础上增加内容的,也就是我们所说的追加重定向!
一 系统调用接口
实际上,C语言层面上的一系列对文件的操作函数底层其实都是封装了系统调用函数,来针对文件进行操作!
1 open函数(打开操作)
pathname:指的就是文件所在的路径(通常就是以绝对路径给出,如果设置了对应的环境变量,也可以相对路径),默认就是我们当前进程创建时的路径!
flag:是一个标志位,可以用来标识我们打开文件的模式,下面会详细介绍!
mode:创建文件时,给文件设置的权限
返回值:文件描述符
1.1 flag标志位
实际上,标志位就是一个位图,用32位来标识不同的模式,如下图所示:
原理如下图所示,通过与操作,确定哪一个模式,然后执行对应的功能!
1.2 文件权限
在使用open函数的时候,如果对权限不做处理就创建文件,那么文件的权限就会乱掉,我们就来复习一下Linux中的权限,在Linux中的权限中,我们就知道,目录的默认权限是777,文件的默认权限是666!而我们Linux系统中默认的权限掩码是002,所以就可以得到下表的内容:
计算规则为:最终权限=默认权限 & (~umask)
在使用open函数,如果我们对文件掩码不进行设置,那么文件的权限可能就会乱掉!所以我们一般采用第二个open函数,设置自己想要的文件权限!如下图所示:
我们一般结合umask函数与自己设置的权限结合得到该文件的权限!如果不使用umask,默认就会使用Linux系统中默认的掩码!
2 write函数(写入操作)
fd:文件描述符
buf:要写入文件的内容
count:要写入内容的长度
3 read函数(读取文件)
fd:文件描述符
buf:将文件中读取的内容存放在buf中
count:选择读取的长度
4 close函数(关闭文件)
fd:文件描述符
5 文件描述符
从上述系统调用接口中的参数,我们就可以发现其实文件描述符就是一个整数!其实在C语言中,FILE是一个结构体,里面其实也必须包含文件描述符!如下图所示:
那么文件描述符既然是一个数字,哪我们怎么知道哪个数字是对应哪个文件的呢?事实上,文件加载到内存中,就会被管理起来,和我们的进程一样,用链表形式管理起来,我们会用一个文件描述符表(相当于一个数组)记录下文件所在的位置,而我们的进程就会指向这个文件描述符表!如下图所示:
而每一个文件被加载到内存中,C语言会默认的打开三个文件,那就是标准输入,标准输出,标准错误!对应的文件描述符默认值分别就是0,1,2!
6 dup2函数(重定向)
简单了解了文件描述符的原理,并且知道了在C语言中,会默认打开三个文件,这样我们就可以知道输出重定向(追加重定向)的原理了,就是利用上层C语言在标准输入,标准输出,标准错误三个文件结构体中,给文件描述符默认值就是0,1,2。那么我们利用系统调用函数(底层可以直接改变文件描述符的函数)就可以关闭标准输出这个文件,然后最新打开的文件(加载到内存中的文件)就会被文件描述符表中的1所指向了(因为文件描述符表的原则就是将最小的并且没有被使用的数组下标,会分配给最新打开的文件)!但是在C语言上层并没有改变,他的标准输出文件描述符还是在1,所以向显示器输出,转而向文件输出了!从而实现了输出重定向(输入重定向的原理也类似,就是关闭文件描述符0)!
dup2函数的原理就不用关闭这么麻烦,上述只是为了让我们更好的理解什么是重定向!dup2函数可以将一个已存在的文件描述符复制到另一个文件描述符上,并且可以自定义新文件描述符的编号。具体的可以参考dup2函数的原理,如下图所示(本来是向显示器写入的,结果向文件写入了):
二 缓冲区
从上面我们就了解到了加载到内存中的文件我们是如何操作的!并且内存中文件的本质其实就是数组下标!对我们操作过的文件是如何刷新到磁盘上的呢?这就不得不提到C语言中的缓冲区了!它本质也就是一块内存区域!为什么会要有缓冲区呢?我们都知道,调用系统调用是有系统开销的!如果我们频繁的去使用系统调用,就会导致我们的对于文件的操作效率就会下降!这是我们不希望的!这里我就简单说下缓冲区大概一个原理(如下图所示):
我们对文件操作,使用fwrite函数等,并没有立即调用系统调用函数刷新到操作系统中的缓冲区!而是要满足一定的条件!具体可以概括为以下三种情况:
1️⃣无刷新,无缓冲!直接调用底层的系统调用
2️⃣行刷新,遇到\n就刷新数据
3️⃣全缓冲,全刷新。等缓冲区满了就刷新!
当然我们也可以手动调用系统调用函数采用强制刷新,或者进程退出后会进行自动的刷新数据!一般遇到第二种或者第三种情况比较多,这就是缓冲区的一个大致原理!
三 文件系统
在了解完内存中文件的操作,以及是通过缓冲区刷新到磁盘上的!我们在来了解一下对于磁盘上的文件我们是如何管理的!实际上,磁盘上的文件就是利用文件系统来进行管理的!简单来说,就是我们操作系统会对磁盘进行分区,分区之后在进行分组,然后在对分组进行管理,从而实现对文件系统的管理!文件系统的原理图如下所示:
在介绍分组中的参数前,我们还需要了解一样东西,就是文件的inode编号,我们操作系统对于文件的增删查改都是基于这个inode编号来实现了,它在当前分区下是唯一的但是在分组里面并不是唯一的(这是因为进入分组之后,我们都要用文件的inode编号减去分组中的第一个inode编号,也就是意味着,在每一个分组中,inode bitmap中的都是从1开始计数的)!inode结构的伪代码如下所示(所占空间大小固定为128字节,该内容是保存在inode Table中的):
struct inode
{
权限
类型
大小
......
inode编号
int blcok[15]
}
在Xshell中我们也可以查看到文件的inode
1️⃣BootBlock:启动块,一般是在第一个分区的第一个扇区上,也就是我们C盘独有的一个数据块!
2️⃣inode Bitmap:用来标记有那些inode还没有被占用
3️⃣Block Bitmap:用来标记那些数据块是空闲的,可以放数据。
4️⃣Data Block:存放数据(也就是文件内容)。
5️⃣inode Table:存放inode有关信息(也就是文件属性)。
6️⃣Group Descriptor Table:用来标识当前组inode与数据块的空闲等的使用情况。
7️⃣Super Block:用来标识该分区的inode与数据块的空闲等的整体使用情况,并不是每一个分组都有,而是某些组里面有!
倘若我们要创建一个文件然后写入数据,我们首先就是根据操作系统给文件分配inode编号,找到它所分配到的组,然后inode bitmap找到一个空位然后改为1(表示被占用),然后根据的文件属性,初始化inode结构体,然后保存到inode table对应的位置!然后根据block bitmap找到空闲的数据块改为1,然后将文件内容写入到Data block中!如果要删除文件,那就简单了,根据inode编号将对应的文件的inode bitmap与block bitmap对应的位置置为0,表示数据可以被覆盖就表示删除了!
总的来说,对文件系统就是将Super Block加载到内存中,从而实现对磁盘上的文件管理!所以,每一个分区是可以使用不同的文件系统进行管理的!只是在Linux是采用了Super Block这种方式进行管理!
四 软硬链接
软链接命令:
ln -s 被链接文件 链接之后取的文件名字
硬链接命令:
ln 被链接文件 链接之后取的文件名字
我们在来看看,通过软硬链接之后,查看一下文件的inode编号
可以发现,软连接是新创建了一个文件,那是因为与原来的文件的inode编号不一样,而硬链接inode编号是与原先的文件一致!并且硬链接数变成了2,软链接的硬链接数还是1。这是因为软链接里面放的内容是目标文件的路径,硬链接就是在其目录文件上(因为目录也是文件,目录的文件内容就是保存里面文件的文件名与文件的inode的映射关系),增加了文件名与新文件名的映射关系,然后让inode的引用计数++!在这里需要说一下的一个小常识就是,我们看一个目录里面有多少个子目录可以用硬链接数-2。
原理就是在我们默认创建的目录中,硬链接数就是2,一个就是目录文件名本身,另一个就是目录文件中包含的隐藏文件目录.