Linux FILE 結構詳解
來源:互聯網
上載者:User
在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裡,我們可對一