要注意这里描述的link()与ln命令的一些关系。ln命令可以建立两种链接:软链接和硬链接。其中硬链接便是用link()来实现的。所谓的link,就是将一个文件链接到一个新的名字上,并为这个名字分配一个目录项,填写到相应的目录中,而不是软连接所做的:新建一个文件,其内容为链接目标文件的路径名。注意链接有一个问题,那就是无穷循环的问题:如果链接一个目录到比起在fs层次中低级的目录项上,那么遍历该目录下的文件将会导致无限循环。因此,只允许superuser对目录进行链接。
ReturnStatus Link(string srcName, string destName)
{
INode * pSrc = namei(srcName);
if (链接数量太多 || 非superuser链接目录)
{
iput(pSrc);
return Error;
}
++pSrc->linkCount;
更新磁盘上的pSrc拷贝;
Unlock(pSrc); // 为了避免死锁——稍后将描述竞态条件
INode * pParentofDest = namei(destName + "/..");
if (pParentofDest->Exists(destName) || srcName、destName在其他的FS上)
{
取消Unlock()操作之前的操作;
return Error;
}
为destName分配inode,将destName和该inode的号码放到pParentofDest的数据中;
iput(pParentofDest);
iput(pSrc);
return Success;
}
下面就来描述一下竞态条件。
有两个进程A和B:
A执行link("a/b/c/d", "e/f/g"),B执行link("e/f","a/b/c/d/ee")。
当A执行namei()查找到d时,它将锁住d的inode,然后它调用namei()去查找e/f的inode,此时假设B执行完了e/f的查找,开始查找a的inode。此时A要获得f的inode,将进入睡眠,因为f的inode被B锁定了——namei()返回的是锁住的inode。由于A进程在锁住d的inode的情况下睡眠,而B将继续执行。当B执行namei()查找到d时,会发现d被锁住,因此B也进入睡眠。这样就形成了死锁。——namei()在查找过程中会释放中间节点(或者叫组件)的inode,但是最终的inode返回的是锁住的inode。所以,在更新完pSrc在磁盘上的拷贝之后就要释放namei()返回的inode的锁。
为了删除一个文件,可以使用unlink()系统调用。unlink()从目录项中移除一个文件的目录项。如果该目录项是最后一个指向该inode的链接——即linkCount降为0,内核将释放该文件的数据,并释放inode。为了释放数据,需要遍历该inode的数据——直接的、间接的数据块——并释放这些数据块。对于间接的数据块,递归地释放数据块——先释放更加直接的数据块。不多说了,先来看看unlink()的伪代码:
ReturnStatus Unlink(string pathName)
{
INode * pParent = namei(pathName + "/..");
if (pathName的最后一个节点是".")
{
++pParent->refCount; // 没看懂为什么,希望有高手指点!
}
else
{
INode * pToUnlink = iget(pathName);
}
if (pToUnlink->IsDir() && !IsSuperUser())
{
iput(pToUnlink);
iput(pParent);
return Error;
}
if (是共享的文本段 && link count为1)
{
从region表中删除; // 后续的文章将解释region相关的东西
}
更新pParent:将移除的文件对应的inode号和名字置为0;
iput(pParent);
--pToUnlink->linkCount;
iput(pToUnlink); // iput()会检查linkCount是否为0并执行相应的操作
// ——回忆一下对iput()的描述。
return Success;
}
unlink()的用处之一就是:创建临时文件之后立刻unlink()它,由于此时改inode的refCount并不为0,因此,最后的iput()调用不会释放数据块——释放数据块的前提是refCount为0且linkCount也为0。当进程退出之后,将释放inode,则inode的refCount为0,那么系统将自动回收inode及其占用的数据块——是否自动回收这一点我也不是很确定,盼高手赐教!这样做的一个好处就是,系统崩溃的话,该临时文件将不再存在,不需要做清理。而系统对fs的维护将识别出该inode处于无人使用的状态——尽管其refCount不为0,将会回收该inode并释放数据块。
关于FS模型的一点补充:
内核中定义的inode是一个generic的inode,其实应该看作一个抽象,它包含了所有文件系统对inode定义的共性的东西,并包含有一组指向具体FS的inode操作的函数指针——前面有些描述,细心的人也许会发现,如file_operation结构体(在描述Linux内核代码时提了一下)包含的就是这样的文件指针。
到目前为止,fs的主要系统调用基本上介绍完了,关于系统调用的实现以及其他一些知识,后面将陆续描述。
参考:
The Design of The UNIX Operation System, by Maurice J. Bach
Linux Kernel Source Code v0.99.15, by Linus Torvalds
Linux Kernel Source Code v2.6.22, by Linus Torvalds and Linux community.
Understanding The Linux Kernel, 3rd edition, by Daniel P. Bovet, Marco Cesati
Copyleft (C) 2007, 2008 raof01. 本文可以用于除商业用途外的所有用途。若要用于商业用途,请与作者联系。
阅读(3402) | 评论(2) | 转发(0) |