就跟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()會由目錄的最上層一層一層的找,如果找到的檔案是symbolic link時,它最後會呼叫symbolic link的follow_link(),而follow_link()應該要讀取所指到的檔案路徑,並且再呼叫lookup_dentry()去找這個檔案,找到之後,再把它的dentry傳回去。 · readpage(file,page) 在Linux裡,每一個inode都代表一個檔案或目錄,而每一個檔案在系統中則是由一個file結構所記錄,readpage()就是將此file裡的page內容讀進來。基本上,VFS已經提供了一個readpage()的函式叫generic_readpage(),定義在,可以直接使用這個函式。 · writepage(file,page) 這個函式則是跟readpage()相反,是將page中的內容寫回file裡。但是,在VFS裡並沒有提供這樣的一個函式可供使用,所以,如果有需要的話,需要自己提供。 · bmap(inode,block) bmap()主要是用在做內存映像時用的。block是一個數字,它代表的是inode所代表的檔案邏輯上的第幾個block,bmap()負責將這個block的序號轉換成disk上的區塊序號。 · truncate(inode) truncate()的作用就是用來將inode所代表的檔案長度減小或增加,當然,詳細的implementation是要依照系統而有所不同。至於最後的長度應該是多少,則是由VFS在呼叫i_op->truncate()之前將想要改變的長度填在inode->i_size裡。 · permission(inode,mask) 故名思義,這個函式用來檢查inode的權限,一般來講,i_op->permission()在Kernel裡並不會被直接呼叫,VFS提供一個也叫permission()函式,這個函式會去呼叫i_op->permission()。一般系統裡如果要檢查權限都是直接呼叫VFS提供的permission(),再由VFS的permission()去呼叫i_op->permission()。如果檔案系統有提供i_op->permission()時,那就以i_op->permission()的結果為准。如果沒有,就依照VFS的標准來做。mask的可以是MAY_READ,MAY_WRITE,和MAY_EXEC這三個值的OR組合。 · smap(inode,sector) smap()的作用跟bmap()很像,但是,sector在這裡指到是disk上的sector number。而不是邏輯上的block number。大部分的檔案系統都沒有提供這個函式,除了在umsdos有提供之外。 · updatepage(file,page,offset) 關於這個函式我也不太清楚,不過,可以知道的是,這個函式目前只有NFS檔案系統有提供。有興趣的朋友可以參考NFS的原始碼。 · revalidate(dentry) 由於NFS有cache的問題,所以,這個函式主要也是在NFS中所使用的,為的是將dentry->i_node的內容做refresh。在裡有一個函式叫do_revalidate()就會呼叫這個函式,很多系統呼叫像stat等都會呼叫do_revalidate()對inode做refresh。 如果我們去看inode_operations結構的內容,就可以發現第一個字段是default_file_ops。其實這也是一組的函式,在Linux裡,每一個檔案都會有一個file結構來描述,而每一個file結構都會定義一組的函式來操作file結構,在inode裡,也同時記錄了用來操作inode所代表的檔案的函式。在開始講操作file結構的函式之前,讓我們來看看file結構的內容。