就跟super_block结构一样,每一个inode都有一个i_op的字段用来记录一组操做inode的函式。
struct inode_operations *i_op;
接下来,我们就来看看inode_operations结构里各个函式是做什么用的:
· create(dir,dentry,mode)
当我们要产生一个新的档案时,Kernel必须要先为这个档案产生一个inode,当然,配置inode内存这种事是属于VFS的工作范围,但是产生一个inode这件事跟档案系统本身有蛮大的关系,因此,VFS会呼叫档案系统里i_op->create()来做些额外的事,那i_op这个字段是打那儿来的呢? 因为一个档案一定是位于某个目录底下,所以,i_op这个字段就是从档案所在目录的inode里取出来的。而传给create()的dir就是那个目录的dentry指针,dentry则是我们要产生的档案的dentry (此时dentry已经配置好,但内部的数据却还没填入),而mode则是产生档案时所给的模式。我们知道Linux里有很多种的inode,有的是代表普通档案,有的则是代表目录,还有代表socket,pipe的。不同种类的inode其i_op所提供的函式都不尽相同,像一个普通的档案,我们根本不可能去呼叫它的create()函式,因为它不是目录,它没办法在目录底下产生一个inode。而像代表目录的inode就必须要提供create()才行,不然没办法在其底下产生子目录或档案。
· lookup(dir,dentry)
这个函式也是代表目录的inode所应该提供的。比方说我们有一个档案叫/usr/tmp/hello.txt,如果我们想读取这个档案的内容时,第一步就是要开启这个档案,如果要开启这个档案,我们首先就得先找到这个档案的inode。那Kernel是怎么找到它的inode的呢? 它会呼叫根目录的inode->i_op->lookup()找到/usr的dentry,则呼叫/usr目录的inode->i_op_lookup()找到/usr/tmp的dentry,接着再呼叫/usr/tmp的inode->i_op->lookup()找到/usr/tmp/hello.txt的dentry。而从它的dentry我们自然可以取得它的inode。而lookup()的用处就是从dir目录底下找到名称跟dentry指定的相同的档案dentry,基本上,这是属于档案系统应该做的事,VFS只负责帮你配置好dentry结构,并填入要找的文件名称。
· link(old_dentry,dir,dentry)
在Linux里,除了symbolic link之外,还有一种叫hard link的东西,symbolic link有它自己的inode,只是其内容指到别的档案的路径而已,但是hard link却是跟指到的档案共享一个inode,但是,hard link只能跟指到的档案位于同一个档案系统而已。当被指到的档案被删除时,只是你看不到那个档案而已,事实上,档案仍然是存在的,你可以使用之前建立的hard link来读取它。系统有提供一个叫ln的命令可以产生hard link,有兴趣的朋友可以试试看。而就programmer来讲,系统也提供了一个叫link()的系统呼叫来做hard link。link()在准备好一切之后,会呼叫i_op->link()去处理档案系统方面要做的事,i_op->link()至少应该要将inode->i_nlink的值加一才行。在i_op->link()的参数里,old_dentry是指被指到档案的dentry,dir是指我们所要产生的link所在目录的dentry,至于dentry则是要产生的link的dentry。这个函式是代表目录的inode所应该提供的。
· unlink(dir,dentry)
相信很多人都用过unlink()这个系统呼叫,这是用来将dir指到的目录底下的dentry档案删除掉。在真正删除之前,它会去检查dentry->d_inode->i_nlink是否归0,只有在nlink的值是0时才会删除。unlink()系统呼叫最后会呼叫i_op->unlink()去做档案系统额外要做的事,它至少应该把dentry->d_inode->i_nlink的值减一才对。跟i_op->link()一样,i_op->unlink()也是目录型别的inode所应提供的。
· symlink(dir,dentry,symname)
这个函式故名思义就是用来产生symbolic link用的。dir是symbolic link所在的目录dentry,symname则是symbolic link的内容,通常是个路径名称,至于dentry则是symbolic link本身的dentry。系统提供了一个symlink()的系统呼叫,就是用来做symbolic link的,它最后也是呼叫i_op->symlink()来处理。当然,每个档案系统内部要如何产生symbolic link的方式不尽相同,以ext2来讲,如果symname的长度小于60个byte的话,那在Ext2而言,这是一个fast symbolic link,因为路径名称就直接存在inode结构里,不用另外读取disk,所以,当i_op->symblink()被呼叫时,它的工作就是将路径名称加到inode里,但是如果大于等于60个byte,那就称为slow symbolic link,symname的内容会被放到disk上的block里,此时,i_op->symlink()就需要配置一个block存放symname的内容。这个函式也是目录型别的inode所应提供的,当然,如果你不想提供的话,也可以直接设成NULL。
· mkdir(dir,dentry,mode)
这个函式就是在产生一个目录时用,系统有提供mkdir()系统呼叫来产生目录,而这个系统呼叫最后会呼叫i_op->mkdir()来做底层的事情。dir是指我们要产生的目录所在的目录,至于dentry则是要产生的目录dentry,mode则是目录的权限。之前我们曾说过,每个inode->i_nlink记录了hard link的个数,而事实上,在代表目录的inode里,i_nlink的意义则跟它很像,它的意思是指目录里有几个档案或子目录,所以,每个目录刚产生时,它的i_nlink的都是2,因为,每个目录至少有二个子目录,分别是"."和".."。当产生完子目录之后,dir->d_inode->i_nlink的值也应该加1才对。如果inode是目录的话,那它应该提供这个函式才对。
· rmdir(dir,dentry)
i_op->rmdir()所做的事是跟mkdir()是相反的。跟mkdir()一样,i_op->rmdir()最后也会被rmdir()系统呼叫所使用。当VFS要呼叫rmdir()之前,它会先替我们把要删除的目录名称dentry找到,并把其父目录的dentry也找到,其中dir就是其父目录dentry,dentry就是指要删除的目录的dentry。当然,在呼叫i_op->rmdir()去删除目录时,VFS会先呼叫permission()并检查我们是否可以删除此目录并检查目录此时的状态,比方像目录是否现在被mount,是否为系统根目录,以及使用者要删除的是否为目录等等。所以,i_op->rmdir()要做的事就是纯粹检查目录是否为空的,是否目前还有别人在使用它,并做好删除目录的事情。如果inode是目录的话,那它应该提供这个函式才对。
· mknod(dir,dentry,mode,rdev)
在Linux里,也有一个命令是叫mknod。mknod命令主要是用来产生的special file,像是character device,block device,或fifo,socket之类的东西。同时,系统里也有一个系统呼叫mknod(),这个mknod()系统呼叫不尽可以产生特殊档案,也可以产生一般的档案,详情可见其man page。而事实上,mknod()系统呼叫最后也是呼叫档案系统的mknod()函式。mknod()系统呼叫会先检查user给的参数是否对,像是如果你指定要产生一个目录,VFS就先把你踢掉,除此之外,VFS还会先替你产生一个空的dentry用来放要产生的档案,当然,它也会检查user是否有权力产生这个档案,最后,它会把重头戏都交给i_op->mknod()去做。而i_op->mknod()要做什么呢? 当然,这部分是跟各个档案系统内部有关,基本上,这个函式需要在dir这个目录底下产生一个inode,其模式为mode,如果mknod()要产生的档案是device的话,那rdev就这个device的major number与minor number组合。除此之外,最重要的一件事就是要根据mode,在inode->i_op填入适当的值,比方说,如果产生一个character device,那inode->i->op应该指定一组操作character device的函式,如果产生的是普通档案的话,那inode->i_op也应填入操作普通档案inode的函式。这个函式对代表inode的目录而言,也是应该要提供的。
· rename(old_dir,old_dentry,new_dir,new_dentry)
这个函式是用来将位于old_dir里的old_dentry档案改名为new_dir里的new_dentry文件名称。档案系统所提供的rename()要做的事就是根据系统的implementation把改名字的事情做好,其它像是权限的检查在上层VFS会帮我们做好。要注意的是,old_dir->d_inode->i_nlink的值应该减一,而new_dir->d_inode->i_nlink的值则是应该加一。这个函式在Kernel里只有被系统呼叫rename()呼叫而已。有的人可能会以为这个函式应该由代表档案的inode提供,但事实上,这个函式必须要由代表目录的inode提供。理由就留给各位去想了。
· readlink(dentry,buffer,buflen)
只有当inode是代表一个symbolic link时,才需要提供这个函式,其它诸如档案或目录是不用提供这个函式的。这个函式的用处在于读取symbolic link的内容,也就是读取symbolic link指到的档案路径。跟上面其它的函式一样,这个函式最后也是会被系统呼叫readlink()所呼叫。至于readlink()要如何做是跟档案系统的implementation有关。像ext2,当档案路径的长度小于60个时,会直接从inode里读出资料,如果不是,则会读取disk上记录路径的block内容。dentry是代表symbolic link的inode,buffer是要路径存放的位置,至于buflen则是buffer的长度。
· follow_link(dentry,base,follow)
跟前一个函式一样,follow_link()这个函式只有symbolic link的inode需要提供。我们知道,当我们读到一个symbolic link叫a时,如果a指到/usr/hello.txt的话,那当我们读a时,事实上会读到/usr/hello.txt。这部分的工作就是由follow_link()完成的。这部分的转换就使用者的观点来看是不会感觉到的。在Linux里,并没有一个系统呼叫会呼叫follow_link()的。这个函式事实上是由lookup_dentry()呼叫do_follow_link(),再由do_follow_link()呼叫i_op->follow_link()。在Kernel里,寻找某个档案的inode是由namei(),再由它呼叫lookup_dentry()完成的,lookup_dentry()会由