前面六篇大致描述了UNIX内核中关于文件系统的内部实现部分。那么,如何让用户使用内核提供的文件系统服务呢?这就是FS的系统调用interface。这个interface中最重要的就是:open(),read(),write(),lseek(),close(),用于访问文件数据;creat(),mknod(),用于创建新文件;chdir(),chroot(),chown(),chmod(),stat(),fstat(),用于操作inode。还有一些更高级的如:pipe()和dup()用于实现shell
里的管道;mount()和umount()来扩展FS树;link()和unlink()用于改变FS层次结构,等等……在下图中提供了这些系统调用与前六篇所描述的主要操作的关系:
总的说来,上面的接口可以分成以下几大类:
- 返回fd的系统调用;
- 使用namei()来解析路径名的;
- 申请或者释放inode的;
- 设置或修改文件属性的;
- 执行I/O的;
- 改变FS结构的;
- 允许进程更改它对FS树视图的;
为了执行这些操作,内核中提供了三种数据结构:
- fd table。就是文件描述符表,一个进程一个表,每个文件描述符对应其中的一个slot。
- file table。每个打开的文件对应其中一个slot;
- mount table。包含每个活动的FS的信息。一般来说可以将其看成in core inode表。
下面就来描述一下这些系统调用。
打开文件
进程要存取一个文件数据所必需采取的第一步。(注意,在做某些文件操作时不需要打开文件)
interface为:fd = open(pathname, flags, modes);
在内核中,其实现为:
FileDescriptor open(string pathname, OpenParams openParams, Permission aPerm)
{
INode * anINode = namei(pathname);
if (文件不存在 || 权限不允许)
{
return error; // 通常为-1
}
为inode分配file对象,设置该对象的reference count和offset,fileObj->inode = anInode;
在用户(进程)文件描述符表中分配表项,在其中存放指向该file对象的指针;
if (设定了truncate标志)
{
free所有文件数据块;
}
unlock(anINode);
return 用户(进程)文件描述符表中该fileObj的索引;
}
下图显示了打开一些文件后,某进程在内核中关于FS的数据结构:
inode表和file table是全系统惟一的,而进程文件描述符表是每个进程分别拥有一个。然而,每次调用open,都会有一个惟一的fd和file object。只有使用dup时才会出现公用file object的情况。最初Thompson引入file table的目的是为了在若干进程文件描述符之间共享文件偏移量,但在Linux中,情况就要复杂一些,file object中还承担了一些file operation之类的操作集,这样大大方便了fs的扩展和驱动程序的编写——驱动程序被当作二进制的plugin动态/静态加载到内核中。
Linux 0.99.15中open()的实现如下:
int do_open(const char * filename,int flags,int mode)
{
struct inode * inode;
struct file * f;
int flag,error,fd;
for(fd=0 ; fd if (!current->filp[fd])
break;
if (fd>=NR_OPEN)
return -EMFILE; // 打开文件个数已达上限
FD_CLR(fd,¤t->close_on_exec);
f = get_empty_filp(); // 获取file object指针
if (!f)
return -ENFILE;
current->filp[fd] = f; // 将file object指针放到进程文件描述符表中索引为fd的slot中
f->f_flags = flag = flags; // 设置标志:创建/truncate/exclude
f->f_mode = (flag+1) & O_ACCMODE;
if (f->f_mode)
flag++;
if (flag & (O_TRUNC | O_CREAT))
flag |= 2;
error = open_namei(filename,flag,mode,&inode,NULL); // 获取inode
if (error) {
current->filp[fd]=NULL;
f->f_count--;
return error;
}
f->f_inode = inode; // file object中inode指针指向inode
f->f_pos = 0; // 偏移量清0
f->f_reada = 0;
f->f_op = NULL;
if (inode->i_op)
f->f_op = inode->i_op->default_file_ops; // 设置file operation集
if (f->f_op && f->f_op->open) {
error = f->f_op->open(inode,f); // 打开文件
if (error) {
iput(inode);
f->f_count--;
current->filp[fd]=NULL;
return error;
}
}
f->f_flags &= ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC);
return (fd);
}
关闭文件
对一个文件的读写操作完成后,需要关闭该文件。通过调用close(int fd)来关闭一个打开的文件。内核将处理文件描述符、file object以及inode。其伪代码如下:
Status close(FileDescriptor fd)
{
if (fd不是打开的文件描述符)
{
return errorStatus;
}
FileObj * aFileObj = fileObjTable[fd];
if (0 < --aFileObj->count)
{
--aFileObj->inode->count;
return successStatus;
}
清空fileObjTable[fd];
iput(aFileObj->inode);
释放aFileObj;
return successStatus;
}
Linux 0.99.15的实现如下:
int close_fp(struct file *filp, unsigned int fd)
{
struct inode *inode;
if (filp->f_count == 0) { // 非正常情况
printk("VFS: Close: file count is 0\n");
return 0;
}
inode = filp->f_inode;
if (inode && S_ISREG(inode->i_mode)) // 删除锁。因为文件对进程不再可用。
fcntl_remove_locks(current, filp, fd);
if (filp->f_count > 1) { // 被dup或继承到子进程中。
filp->f_count--;
return 0;
}
if (filp->f_op && filp->f_op->release) // 释放资源
filp->f_op->release(inode,filp);
filp->f_count--;
filp->f_inode = NULL;
iput(inode);
return 0;
}
参考:
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 raof01. 本文可以用于除商业用途外的所有用途。若要用于商业用途,请与作者联系。
阅读(3113) | 评论(1) | 转发(0) |