在Linux里,每一个档案都有一个file结构和inode结构,inode结构是用来让Kernel做管理的,而file结构则是我们平常对档案读写或开启,关闭所使用的。当然,从user的观点来看是看不出什么的。在Linux里,档案的观念应用的蛮广泛的,甚至是写一个driver你也只要提供一组的file operations就可以完成了。我们现在来看看File结构的内容。
struct file {
struct file *f_next,**f_pprev;
struct dentry *f_dentry;
struct file_operations *f_op;
mode_t f_mode;
loff_t f_pos;
unsigned int f_count,f_flags;
unsigned long f_reada,f_ramax,f_raend,f_ralen,f_rawin;
struct fown_struct f_owner;
unsigned long f_version;
void *private_data;
};
比起super_block和inode结构,file结构就显得小多了,file结构也是用串行来管理,f_next会指到下一个file结构,而f_pprev则是会指到上一个file结构地址的地址,不过,这个字段的用法跟一般指到前一个file结构的用法不太一样,有机会再跟各位讨论,f_dentry会记录其inode的dentry地址,f_mode为档案存取的种类,f_pos则是目前档案的offset,每次读写都从offset记录的位置开始读写,f_count是此file结构的reference cout,f_flags则是开启此档案的模式,f_reada,f_ramax,f_raend,f_ralen,f_rawin则是控制read ahead的参数,f_owner记录了要接收SIGIO和SIGURG的行程ID或行程群组ID,private_data则是tty driver所使用的字段。最后,我们来看看f_op这个字段。这个字段记录了一组的函式是专门用来使用档案的。
· llseek(file,offset,where)
我们写程序会呼叫lseek()系统呼叫设定从档案那个位置开始读写,这个函式你可以不用提供,因为系统已经有一个写好的,但是系统提供的llseek()没有办法让你将where设为SEEK_END,因为系统不知道你的档案长度是多少,所以没办法提供这样的服务。如果你不提供llseek()的话,那系统会直接使用它已经有的llseek()。llseek()必须要将file->offset的值做改新。
· read(file,buf,buflen,poffset)
当我们读取一个档案时,最终就是会呼叫read()这个函式来读取档案内容。这些参数VFS会替我们准备好,至于poffset则是offset的指针,这是要告诉read()从那里开始读,读完之后必须更新poffset的内容。请注意,在这里buf是一个地址,而且是一个位于user space的地址。
· write(file,buf,buflen,poffset)
write()的动作就跟read()是相反的,参数也都一样,buf依然是位于user space的地址。
· readdir(file,dirent,filldir)
这是用来读取目录的下一个direntry的,其中file是file结构地址,dirent则是一个readdir_callback结构,这个结构里包含了使用者呼叫readdir()系统呼叫时所传过去的dirent结构地址,filldir则是一个函式指针,这个函式在VFS已经有提供了,这个函式其实是增加了kernel在读取dirent方面的弹性。当档案系统的readdir()被呼叫时,在它把下一个dirent取出来之后,应该要呼叫filldir(),让它把所需的资料写到user space的dirent结构里,也许还会多做些处理。有兴趣的朋友可以参考的filldir()函式。
· poll(file,poll_table)
之前的Kernel版本本来是在file_operations结构里有select()函式而不是poll()函式的。但是,这并不代表Linux不提供select()系统呼叫,相反的,Linux仍然提供select()系统呼叫,只不过select()系统呼叫implement的方式是使用poll()函式来做的。
· ioctl(inode,file,cmd,arg)
ioctl()这个函式其实有很大的用途,尤其它可以做为user space的程序对Kernel的一个沟通管道。那ioctl()是什么时候被呼叫呢? 还记得平常写程序时偶而会用到ioctl()系统呼叫来直接控制档案或device吗? 是的,ioctl()系统呼叫最后就是把命令交给档案的f_op->ioctl()来执行。f_op->ioctl()要做的事很简单,只要根据cmd的值,做出适当的行为,并传回值即可。但是,ioctl()系统呼叫其实是分几个步骤的,第一,系统有几个内定的command它自己可以处理,在这种情形下,它不会呼叫f_op->ioctl()来处理。如果user指定的command是以下的一种,那VFS会自己处理。
o FIONCLEX
清除档案的close-on-exec位。
o FIOCLEX
设定档案的close-on-exec位。
o FIONBIO
如果arg传过来的值为0的话,就将档案的O_NONBLOCK属性去掉,但是如果不等于0的话,就将O_NONBLOCK属性设起来。
o FIOASYNC
如果arg传过来的值为0的话,就将档案的O_SYNC属性去掉,但是如果不等于0的话,就将O_SYNC属性设起来。只是在Kernel 2.2.1时,这个属性的功能还没完成。
如果cmd的值不是以上数种,而且如果file所代表的不是普通的档案的话,像是device之类的特殊档案,VFS会直接呼叫f_op->ioctl()去处理。但是如果file代表普通档案的话,那VFS会呼叫file_ioctl()做另外的处理。何谓另外的处理呢? file_ioctl()会再过澽一次cmd的值,如果是以下数种,它会先做些处理,然后再呼叫f_op->ioctl(),不管怎么样,file_ioctl()最后都会再呼叫f_op->ioctl()去处理。
o FIBMAP
先将arg指到的档案block number取出来,并呼叫f_op->bmap()计算出其disk上的block number,最后再将计算出来的block number放到arg参数里。
o FIGETBSZ
先取得档案系统block的大小并放入arg的参数里。
o FIONREAD
将档案剩下尚未读取的长度写到arg里。比方说档案大小是1000,而f_op->offset的值是300,表示还有700个byte尚未读取,所以,将700写到arg参数里。
· mmap(file,vmarea)
这个函式是用来将档案的部分内容映像到内存中的,file是指要被映像的档案,而vmarea则是用来描述到映像到内存的那里。
· open(inode,file)
当我们呼叫open()系统呼叫来开启档案时,open()会把所有的事都做好,最后则会呼叫f_op->open()看档案系统是否要做些什么事,一般来讲,VFS已经把事做好了,所以很多系统事实上根本不提供这个函式,当然,你要提供也可以,比方说,你可以在这个函式里计算这个档案系统的档案被使用过多少次等。
· flush(file)
这个函式也是新增加的,这个函式是在我们呼叫close()系统呼叫来关闭档案时所呼叫的。只要你呼叫close()系统呼叫,那close()就会呼叫flush(),不管那个时候f_count的值是否为0。这个函式我不是很确定在做什么的,事实上,在Ext2里也没有提供这么一个函式,也许是在关闭档案之前,VFS允许档案系统先做些backup的动作吧。
· release(inode,file)
这个函式也是在close()系统呼叫里使用的,当然,不尽在close()中使用,在别的地方也是有使用到。基本上,这个函式的定位跟open()很像,不过,当我们对一个档案呼叫close()时,只有当f_count的值归0时,VFS才会呼叫这个函式做处理。一般来讲,如果你在open()时配置了一些东西,那应该在release()时将配置的东西释放掉。至于f_count的值则是不用在open()和release()中控制,VFS已经在fget()和fput()中增减f_count了。
· fsync(file,dentry)
fsync()这个函式主要是由buffer cache所使用,它是用来跟file这个档案的资料写到disk上。事实上,Linux里有两个系统呼叫,fsync()和fdatasync(),都是呼叫f_op->fsync()。它们几乎是一模一样的,差别在于fsync()呼叫f_op->fsync()之前会使用semaphore将f_op->fsync()设成critical section,而fdatasync()则是直接呼叫f_op->fsync()而不设semaphore。
· fasync(fd,file,on)
当我们呼叫fcntl()系统呼叫,并使用F_SETFL命令来设定档案的参数时,VFS就会呼叫fasync()这个函式,而当读写档案的动作完成时,行程会收到SIGIO的讯息。
· check_media_change(dev)
这个函式只对可以使用可移动的disk的block device有效而已,像是MO,CDROM,floopy disk等等。为什么对这些可以把disk随时抽取的需要提供这么一个函式呢? 其实,从字面上我们大概可以知道,这是用来检查disk是否换过了,以CDROM为例,每一个光盘片都代表一个档案系统,如果今天我们把光盘片换掉了,那表示这个档案系统不存在了,如果user此时去读取这个档案系统的资料,那会发生什么事? 很有可能系统就这么出事了。所以,对于这种的device,每当在mount时,我们就必须检查其中的disk是否换过了,如何检查呢? 当然只有档案系统本身才知道,所以,档案系统必须提供此函式。
· revalidate(dev)
这个函式跟上面的check_media_change()有着相当的关系。当user执行mount要挂上一个档案系统时,mount会先呼叫里的check_disk_change(),如果档案系统所属的device有提供这个函式的话,那check_disk_change()会先呼叫f_op->check_media_change()来检查是否其中的disk有换过,如果有则呼叫invalidate_inodes()和invalidate_buffers()将跟原本disk有关的buffer或inode都设为无效,如果档案系统所属的device还有提供revalidate()的话,那就再呼叫revalidate()将此device的资料记录好。
· lock(file,cmd,file_lock)
这个函式也是新增加的,在Linux里,我们可对一