通过实现pwd了解文件系统

了解并实现UNIX常用命令有助于了解Unix的工作原理,如ls、tail等(参考https://www.cnblogs.com/blueclue/archive/2011/11/08/linux_head_tail.html)。这里我们通过实现一个pwd来了解Unix树状文件系统的内部结构。

以下图所示目录结构为例:

目录可以包含文件与子目录,且目录深度一般没有限制。那么就引出一个问题,大部分命令内存缓冲区是有限的,目录太深会导致命令执行异常。使用如下脚本测试:

count=0
while true
do
    mkdir deep-well
    if [ $? -ne 0 ];
    then
      echo "maxdir deepth :$count"
      break
    fi
    count=$((count+1))
    cd deep-well
done

对于创建出来的目录树,使用ls -R 或者 rm -r deep-well 之类的命令将不起作用。

文件系统结构

我们可以将磁盘划分为固定大小(512byte)的扇区,并使用连续的序列号给扇区进行编号。那么文件系统存储的文件内容、文件属性等不同类型数据是如何存储在被编号的磁盘块上的呢?之前参考《操作系统导论》写过文件系统的实现方式,unix实现原理比较简单,将这些磁盘块分成三部分:

1、超级块

存放文件系统本身的结构信息,如每个区域的大小、未被使用的磁盘块信息等;

2、i节点表

每个文件都有一些属性,如大小、访问权限、创作者等,这些文件属性记录在i节点中。i节点表中的每个i节点都通过位置来标识。i节点内容参考下图:

3、数据块

文件的内容保存在这个区域。

创建文件的过程

文件包括内容和属性两部分,内容放在数据区,属性放在i节点。新建文件过程如下图所示:

内核先找到一个空闲i节点(一般使用map表),上例为40号节点。然后在i节点里面记录文件属性;

假定我们新建的test文件需要三个磁盘块,因此系统从自由块列表中找到三个自由块,上例为120、150、170;

文件内容写入之后将磁盘块号码写入i节点的磁盘分布区列表中;

最后将文件名与i节点号写入当前目录内容下。

上例可以看到所有的目录都会保存自身和父目录的节点号,那么对于根目录,因为没有父目录,其父目录指向自己。

所有从文件读取数据的命令,如cat、cp等,都是将文件名传给open来访问文件内容。open会递归访问目录寻找文件名,然后根据目录的i节点号获取文件属性,最终找到文件内容。i节点会保存文件的访问权限,如果该用户没有对应权限,open会返回错误码EPERM。

大文件存储过程

因为i节点大小固定,所以其保存数据块编号的数组表大小也是固定,比如固定为13个项。那么对于需要超过14个数据块存储的大文件怎么办呢?解决方案就是将前10个数据块编号放在i节点中,将超出的数据块编号放入一个间接数据块中,然后i节点中分配列表的第11号元素存储该间接数据块的编号。参考下图:

因为数据块大小也是固定的(512byte),当间接数据块饱和时如何处理?我们可以将间接块的编号放入一个空闲块中国,然后i节点分配列表的第12号元素指向这个空闲块。这个空闲块被称为二级间接块。

当二级间接块饱和时如何处理?同样的找一个空闲数据块存储二级间接块的编号,将该空闲数据块编号存入i节点分配列表的第13号元素,这个空闲块称为三级间接块。

当三级间接块饱和时如何处理?没办法处理了,文件系统设计时就考虑了文件大小的极限,如果有存储超过该极限的文件,只能重新创建一个由更大磁盘块构成的文件系统。

文件系统的基本实现差不多讲完了,一般Unix发行版会考虑一些工程鲁棒性,比如超级快块的备份,自由块的分配效率等。

目录

从上面的i节点可以看出,文件名称并不保存在I’节点中,而是保存在目录文件的文件内容里面。所以如果在目录树中有两个文件名对应同一个文件,如下图的x文件与xlink文件对应同一个i节点,那么这两个文件被称为硬链接,同时i节点内部会保存节点自身的链接数。

文件系统相关系统调用

mkdir

int mkdir(const char *pathname, mode_t mode);

mkdir会寻找并分配一个i节点给该目录,分配一个磁盘块存储该目录内容;在目录中设置俩个个入口:“.”和”..”,并配置好节点号;在该目录的父目录中增加一个该节点的链接。

cd

cd用来改变进程的当前目录。Unix上每个进程都有一个当前目录,进程结构体(task_struct)内部有一个保存当前目录i节点号的变量,cd会调用chdir改变进程的当前目录

pwd的工作过程

要实现pwd,先来了解一下其工作过程。pwd首先查看当前目录“.”的i节点号,然后从其父目录找到该目录的名字,然后一直上溯。算法如下:

1、通过stat得到”.”的i节点号,称其为n;
2、通过chdir..进入当前目录的父目录;
3、找到i节点号n链接的名字(使用opendir、readdir)
4、重复直至抵达根目录退出

前面说过根目录的父目录对应的i节点号指向自己,因此程序退出的条件为“.”与“..”对应的i节点号相等。如何顺序显示目录名字?我们可以采用后序遍历,通过递归逐步到达树的顶端来一个接一个显示目录名,也就是借用函数栈保存目录名,不用自己进行字符串管理。

伪代码如下,为省事省去了异常校验:

这里注意,文件系统的根并不一定是整个计算机上整棵文件树的根,因为Unix允许不同文件系统挂载。不同的文件系统会出现一些新的问题,比如两个文件系统(大概率)可能存在相同的i节点号,如何处理呢?一般link、rename等系统调用拒绝创建跨设备的链接。硬链接不能指向其他系统的i节点,这个时候需要使用软链接(符号链接)。符号链接通过名字引用文件。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值