这里写目录标题
文件存储理论补充
dentry、inode
首先我们知道,inode,在创建硬链接的时候用到过,如下图:
当我们创建一个文件的硬链接时,该硬链接会有一个与文件一样的Inode
但是Indoe是什么:
Inode是一个结构体,存储着文件属性的结构体,他会存储一个文件的各种属性,以及“该文件在磁盘中的位置”,但是没有存储文件名
而dentry,存储着文件名+该文件的inode,相当于是一个目录,其名字也是"目录项"
当我们描述一个文件时,首先会根据dentry,找到他的inode,之后就知道了该文件的各种属性,而通过inode记录的文件的磁盘位置,就可以看到文件的内容了
而我们平时创建硬链接的底层原理是:
创建一个硬链接,就会创建出一个dentry,其文件名就是硬链接的文件名,匹配的就是目标文件的inode,这样就建立了硬链接,这也是为什么硬链接会与原文件有相同的inode
而删除硬链接,以及删除文件:
删除硬链接,其实就是删除dentry,删除一个dentry,其硬链接计数就会减一
当删除完所有的硬链接,然后又删除了文件,那么就会把所有的dentry都删除了,那么这个文件的inode就没有dentry引用了
但是此时,在磁盘中的文件安然无恙,也就是说,删除文件并不会真正的去磁盘中删除文件,而这个磁盘的内容,只有在没有dentry引用其inode的情况下,会被其他文件所覆盖,覆盖了才是真正意义的删除
数据恢复:
如果磁盘文件内容还没有被覆盖,我们就可以对该部分数据再次建立一个inode以及dentry,就可以做到对数据的恢复
文件其他操作
stat函数
man 2卷
作用
获取文件的各种属性
函数原型
参数一:文件路径(精确到文件名)
参数二:传出参数,指向struct stat类型的变量的指针
其中 stat结构体的内容:(实际上就是inode的内容,也就是我们平时ls -l (小L)显示的内容)
代码(以获取文件大小为例)
最终在sbuf结构体中已经有了该文件的所有属性,想打印哪个就打印哪个
这里我们打印文件的大小,这个可以实现与“lseek获取文件大小”相同的效果,且该方式是推荐的方法
注意使用%ld进行打印,其类型为unsinged long int
补充(获取文件类型)
拿到stat结构体变量中的数据后,在该结构体中有一个st_mode属性,利用该属性,配合其提供的宏函数,我们可以完成上图中的一些判断(如判断该文件是否是一个普通文件、还是一个目录?…)
而宏函数一般只返回0或1,也就是返回真或者假,他会告诉你是或不是
代码:
但是:
当我们测试其他类型没有问题,但当我们测试“符号链接”类型时,出现了问题:
首先我们创建两个符号链接:
结果是:
可以看到,最终打印的都是符号链接所指向的最终的实体文件,该现象称为stat穿透,默认情况下stat是穿透的,如何解决,看下一个函数
lstat函数
作用
解决stat函数的“穿透”现象
函数原型
与stat函数原型一样
代码
与stat函数使用规则一样,只不过加了一个 “l” (小L)
结果:
实际上,我们的vim、cat,都是穿透的(作用到最终指向的实体文件)
而ls -l 是不穿透的 (作用于“链接本身”)
补充(获取文件权限)
在man中的demo没有使用宏函数进行文件类型的判断,而是使用了位运算
简单解释一下:
这里还是用到了位图:
一个位图,有2字节,16位,从右向左,每三个为一组,一共三组,共9位,分别代表不同组的文件权限(用户、用户组、其他用户)
再往左三位是特殊权限位
最开始的四位是文件类型,四位,转为八进制其总数就是17(即017,0是八进制的前缀)
上图中的S_IFMI就是017,也就是其二进制为1111 000 000 000 000,由于他前面都是1,所以可以作为掩码使用,所以st_mode & S_IFMT,最终得到S_IFMT下面的宏,就对应其表示的信息,由此可见,使用该方式,不仅可以获取到文件类型(宏函数可以获取),还可以获取到不同组的执行权限等信息(宏函数获取不到):
总结
因为lstat与stat没有其他区别,而且lstat不会进行穿透,所以,以后我们直接使用lstat就可以了
tips
在一些重要的函数的man手册中,我们点击“G”,会跳到最下面,这里可能会有一个当前函数的demo示例
link函数
man 2卷
作用
我们之前创建硬链接,都是通过命令行的ln命令,但是在代码中,我们不可能去到终端敲命令,所以就有了link函数,他就是负责创建硬链接的,实际上,是为文件新建一个dentry项
通过上面这张图我们也能知道,硬链接不像软链接,他在ll时,并不会表现为链接文件名,而是类似于创建了一个新文件,这也符合硬链接的原理:创建多个dentry,指向inode,所以,一个文件被刚创建时,系统就自动的为其创建了一个dentry,这就是我们常见的文件
简介
建立硬链接,也就是新建dentry项,而使用mv命令修改文件名时,也是修改了dentry,并不对真正的文件做操作(利用原先的dentry,创建出一个新的dentry,之后将原先的系统创建的dentry删除,就完成了文件的改名)
函数原型
参数一:原先的文件的dentry路径(精确到文件名)
当我们创建一个新的文件时,系统在磁盘写入了文件之后,会自动创建一个dentry,这个就是我们平时看到的文件
参数二:新的dentry路径(若没有,系统自动创建,一般是没有,因为硬链接都是新建的)
代码(模拟mv命令进行文件重命名)
效果:
先利用原先系统创建的dentry,建立新的dentry,之后将原先的那个删掉,使用unlink函数,直接传入文件路径即可
unlink
作用
解除一个硬链接(或者当前文件只有一个dentry时(也就是硬链接计数为1,即文件刚创建的状态),对当前文件进行“删除”(只是让其具备删除条件))
简介
可以看到,这里也说了,当一个文件的目录项(dentry)没有时,该文件会被操作系统择机释放,因此,我们删除一个文件,只是让文件具备了被释放的条件,什么时候释放,由系统决定,且打开该文件的进程必须关闭了,才能被释放
代码
需求:我们要在一个程序运行时,创建并打开一个文件,在程序结束时,这个文件要被销毁
我们在程序的末尾,在return之前,将该文件unlink掉,就可以实现上述的需求了
在程序第35行,设置了一个getchar(),该函数的作用是与用户进行IO交互,这里相当于“阻塞”,阻塞程序,不让程序结束,这样可以方便我们进行测试,(例如在另一个终端查看程序未结束时,那个临时文件在不在)
但是,假如说我们的程序在执行到39行之前,程序崩了,那么无法执行后续的代码便使得主函数退出了,这样的话,那个临时文件就不会被删除,如下图:
假如说,我们在34行,对p这个只读变量进行写入操作,那么会发生段错误(访问非法内存,或更改只读变量),程序崩溃,由此一来,那个临时文件不会被删除
如何解决:
直接将该部分unlink代码,放到open后面
我们不用担心在这里把临时文件unlink掉之后,后续的代码就找不到文件了,这个是不会的,因为unlink,只是将dentry删除,文件还在磁盘,等待所有使用该文件的进程结束了,系统才会择机释放,当前进程使用着临时文件,所以该文件不会被释放,仍然存在,这样,就不怕后续发生程序崩溃导致unlink没执行了
而如果程序正常进行,此时就算有阻塞,我们开另一个终端去查该临时文件,也是找不到的,而向该文件写入的内容,是被写入到了内核缓冲区里面
补充:隐式回收
问题产生
我们程序发生了段错误,程序崩溃,后续的close没有执行,那这个fd会一直占用着系统资源吗(占位符3,0、1、2是标准输入输出出错)
解决问题
答案是不会,因为程序的进程一旦被退出,那么该进程所有的内存资源都会被回收,这一过程称为“隐式回收”,也就是一个进程被关闭时,所有的内存会被释放(包括泄露的和不泄露的),但是也不能忽视内存泄漏,因为很多程序是一直处于开启状态进行监听的,程序一直运行,无法关闭,就无法隐式回收
readlink
在我们创建一个软链接后,其软链接的大小是指:创建连接时,敲入的链接文件的路径,被他当做字符串存了起来
而我们如何查看一个已有的软链接的指向呢,使用cat是不行的,因为他会进行穿透
所以使用一个命令:readlink,就可以拿到软链接链接的文件路径(创建时使用的相对路径会显示相对路径,创建时使用的绝对路径,就会显示绝对路径)
该命令也有一个函数,将结果返回到缓冲区buf中