by cszhao1980
我们已经知道文件会占用很多资源,如磁盘inode资源、盘块资源,访问时还要占用inode
数组资源,等等。
除此之外,unix v6还使用file数组来记录整个系统中被open开的文件,file数组的定义如下:
5507: struct file
5508: {
5509: char f_flag;
5510: char f_count; /* reference count */
5511: int f_inode; /* pointer to inode structure */
5512: char *f_offset[2]; /* read/write character pointer */
5513: } file[NFILE];
而每个进程都记录了本进程“引用”的文件,记录在u_ofile数组之中:
0438: int u_ofile[NOFILE]; /* pointers to file structures of open files */
数组中每一项都指向一个file数组中的一个file。
而falloc()函数就用来申请这两种资源:
(1) 首先调用u falloc()在进程的u_ofile数组中占用一个空闲项,其index为i;
(2) 然后在file数组中占用一空闲项,指针为fp;
(3) 使u_ofile[i] = fp。
为进一步了解资源占用情况,我们来看几个例子。
首先是open() sys call,用来打开一个已经存在的文件,该函数的实现非常简单:
5765: open()
5766: {
5767: register *ip;
5768: extern uchar;
5769:
5770: ip = namei(&uchar, 0);
5771: if(ip == NULL)
5772: return;
5773: u.u_arg[1]++;
5774: open1(ip, u.u_arg[1], 0);
5775: }
首先,调用namei读取了文件的inode并占用了inode数组资源;
然后,调用open1()完成剩余的操作。
【注】: 5773行,莱昂解释说“这是由在用户程序设计约定和内部数据表示之间失配而造成的。”
举例说明就比较容易理解了:
(1) 用户接口中,使用0、1、2表示3种情况;
(2) 内部使用1、2、3代指这3种情况。
所以,对用户接口传入的该参数,使用前需要+1。
open1(ip, mode, trf)相对比较复杂,它有三个参数:
(1) ip:要open的文件的inode;
(2) mode: open模式——FREAD、FWRITE等;
(3) trf
i. 0:要打开一个已存在的文件;
ii. 2:输入的inode为新建inode(文件内容不存在);
iii. 1:文件存在,但需要删除,然后复用该inode。
i用于open sys call。
ii、iii用于create sys call,我们呆会儿会讲到。
(1) 5813 ~ 5821进行权限控制,如果trf为2,则该文件其实还不存在,故无需作此种检查;
(2) if(trf) itrunc(rip);
删除原有文件,其实将条件改为trf==1更好——对trf==2的情况,itrunc等同于空操作;
(3) 5827 ~ 5830 分配file数组和u_ofile数组资源,并进行设置;
(4) 5831~5832 行调用的openi()函数用于特殊设备处理,但普通文件视为空操作。
下面看看create sys call。
5781: creat()
5782: {
5783: register *ip;
5784: extern uchar;
5785:
5786: ip = namei(&uchar, 1);
5787: if(ip == NULL) {
5788: if(u.u_error)
5789: return;
5790: ip = maknode(u.u_arg[1]&07777&(~ISVTX));
5791: if (ip==NULL)
5792: return;
5793: open1(ip, FWRITE, 2);
5794: } else
5795: open1(ip, FWRITE, 1);
5796: }
相比较open,它稍显复杂——分为两种情况:
1. 文件已经存在
调用open1(ip, FWRITE, 1)——删除原有文件,复用其inode;
2. 文件不存在
(1) 调用maknode()新建一inode;
makenode(7455)是个简单的函数,它内部调用ialloc()分配磁盘inode资源,进行简单
设置后,调用wdir()将此inode写入磁盘。
(2) 调用open1(ip, FWRITE, 2)分配file数组和u_ofile数组资源。
当操作成功时,open与create函数没有显式的返回,这很令人惊奇。熟悉unix的同学都知道,
这里应该返回所谓的“文件描述符”。呵呵,如果您足够细心的话,您会在ufalloc中找到答案
——该函数将u_ar0[R0]设置为其所分配的u_ofile数组项的index——即“文件描述符”。
getf(f)将“文件描述符”转化为file数组指针——很简单,返回u_ofile[f]即可。
接着让我们看一下close()函数,它是open的“反”函数,用于释放各种资源:
5846: close()
5847: {
5848: register *fp;
5849:
5850: fp = getf(u.u_ar0[R0]); //得到file指针
5851: if(fp == NULL)
5852: return;
5853: u.u_ofile[u.u_ar0[R0]] = NULL; //释放占用的u_ofile数组项
5854: closef(fp);
5855: }
6643: closef(fp)
6644: int *fp;
6645: {
6646: register *rfp, *ip;
6647:
6648: rfp = fp;
6649: if(rfp->f_flag&FPIPE) { //管道,skip
….
6654: }
6655: if(rfp->f_count <= 1)
6656: closei(rfp->f_inode, rfp->f_flag&FWRITE); //对普通设备,等同于调用iput(),释放inode资源
6657: rfp->f_count--; //引用计数减1
6658: }
很奇怪,这里似乎缺少了释放file数组项的代码——其实不然,当引用计数为0时,则表示该数组项空闲。
最后,让我们看一下文件的读写函数。
首先是read和write,它们是个壳函数,通过内部调用rdwr完成真正的操作。莱昂对这些函数进行了详细
的介绍——包括其参数的传递方式,在此不再赘述。
最后,看一下seek,即所谓的随机读写函数。它有三个参数:
(1) 文件描述符;
(2) 文件Offset;
(3) 模式;
其参数传递同前者类似,即参数1由r0传入;其余参数由u_arg[]数组传入。
对unix/linux比较熟悉的同学会马上想起lseek函数——seek完成的确实是类似的功能。但相较而言,seek
显得更复杂——其主要原因在于unix v6使用两个word模拟一个32bit数来描述文件offset;而我们熟悉
lseek则使用1个long型值(我们熟悉的32位机上,就是64 bit)就搞定了。
这个函数的解析就留给大家吧。
博客地址:http://blog.csdn.net/cszhao1980
博客专栏地址:http://blog.csdn.net/column/details/lions-unix.html