天天看點

UNIX核心(4):inode及其相關操作

本文轉自:http://blog.chinaunix.net/uid-7471615-id-83764.html

早期的UNIX系統最重要的兩大功能是:檔案存儲/通路,任務/程序排程(多任務)。由這兩大功能衍生出了記憶體管理,裝置管理,使用者接口等功能。在這裡就來說說其中第一個重要的功能:檔案系統。

在UNIX系統上,所有一切都被當成檔案來對待,包括裝置。是以,就需要一個系統來管理這些檔案,并提供一個統一的操作接口。inode就是用來管理檔案的一個資料結構(或者說是模型、類)。每個檔案在磁盤上都對應一個inode,在通路檔案時,inode會被加載到記憶體中,使用結束後記憶體中的inode被釋放。如果删除檔案(unlink),磁盤上的inode及檔案的資料都會被釋放。

inode是一個檔案在系統中的惟一辨別。一個檔案可以由多個名字(link),但是隻占用一個inode。當檔案處于active狀态時(比如被打開),記憶體中保留一份inode的拷貝,與inode一起保留在記憶體中的對象成為inode的核内拷貝(in-core copy,以下用核内inode代替)。

inode結構

如上所示,inode有磁盤inode和核内inode。其實大部分情況下說的都是存放在磁盤上的inode。一個inode對象包含如下成員:

檔案屬主。分成單個屬主群組屬主。

檔案類型:普通檔案、目錄、裝置檔案、FIFO等

通路權限

檔案通路時間:上次的檔案修改/通路/inode修改時間

連結數:至少是1

檔案資料塊的位址表

檔案大小

更改屬主、檔案類型、通路權限、連結設定,修改檔案内容都将導緻對inode的修改,而修改檔案内容還将更新檔案修改時間及通路時間。

核内inode(In-core copy of inode)

除了磁盤inode的資訊,核内inode還包括如下成員:

狀态。訓示是否鎖定、有程序等待、核内inode是否與磁盤inode不同(包括對檔案資料及inode本身的修改)、挂接點

邏輯裝置号

inode号

指向其他核内inode的指針。主要是用于将其放在hash Q與free list上進行操作。

引用計數

其中引用計數是一個相當重要的成員。它用來訓示有多少個活動的檔案執行個體,比如當一個檔案被第一次打開,inode被加載到核心并放到hash Q上,這是核内inode的引用計數為1。此時另一個程序再打開該檔案,則無須再在核心中保留一份拷貝,增加引用計數即可。當它為0時,inode被釋放——置入free list中。此時hash Q上依然有該inode,以便下一次通路時不必從硬碟加載資料。

Linux 0.99.15中,inode定義如下(inode和核内inode在此被統一了):

struct inode {

dev_t i_dev; // 裝置号,用于核内inode

unsigned long i_ino; // inode号:因為在磁盤上連續存儲,通過索引就可找到某個inode

// 用于核内inode

umode_t i_mode;

nlink_t i_nlink; // 連結設定

uid_t i_uid; // 屬主資訊

gid_t i_gid;

dev_t i_rdev;

off_t i_size;

time_t i_atime; // 時間戳

time_t i_mtime;

time_t i_ctime;

unsigned long i_blksize;

unsigned long i_blocks;

struct inode_operations * i_op; // 對inode及檔案的操作集合。該結構體包含對inode及檔案

// 操作的函數指針,針對不同的inode,

// 可以設定不同的操作集合。

// 這裡有一點多态的影子。做過裝置驅動的同學對此會有印象。

struct super_block * i_sb; // 超級塊,日後再議

struct wait_queue * i_wait; // 等待隊列。用于同步(睡眠/喚醒)

struct file_lock * i_flock;

struct vm_area_struct * i_mmap; // 映射的記憶體區

struct inode * i_next, * i_prev; // 用于核内inode

struct inode * i_hash_next, * i_hash_prev; // 用于核内inode

// 剩餘的資訊貌似與本文主題無關,暫不讨論。日後再議

// ……

unsigned short i_count; // 引用計數。用于核内inode

};

核内inode的配置設定與回收

配置設定:

INode iget(int fs_inode_no)

{

while (未完成)

if (inode在cache中)

if (inode被鎖住)

sleep (inode解鎖事件);

重試;

}

// 對挂接點的特殊處理。日後再議

if (inode在free list上)

從free list移出;

++引用計數;

傳回該inode;

// inode不在cache中

if (沒有可用inode)

傳回錯誤; // 因為inode占用時間較長,從檔案打開到關閉通常要很久,

// 此處程序選擇等待将導緻挂死。這裡處理與getblk()不同

重置inode号、檔案系統号;

放到新的hash Q中;

從磁盤上讀取bread();

初始化其他成員如引用計數;

由于inode的辨別是fs NO和inode NO,是以從磁盤上讀取時,需要做如下轉換(因為一次讀取一個磁盤塊,而inode通常比磁盤塊小):

塊号 = ((inode号 -1) / 每塊的inode數) + 磁盤inode首塊塊号

讀到inode所在塊後,就可以計算inode在塊中偏移量:

偏移量 = ((inode号 - 1) % 每塊的inode數) * 磁盤inode大小

還是來看看Linux 0.99.15的實作:

struct inode * __iget(struct super_block * sb, int nr, int crossmntp)

static struct wait_queue * update_wait = NULL;

struct inode_hash_entry * h;

struct inode * inode;

struct inode * empty = NULL;

if (!sb)

panic("VFS: iget with sb==NULL");

h = hash(sb->s_dev, nr); // 擷取fs号

repeat:

for (inode = h->inode; inode ; inode = inode->i_hash_next)

if (inode->i_dev == sb->s_dev && inode->i_ino == nr)

goto found_it; // 在hash Q上找到

// 未在hash Q上找到

if (!empty) {

h->updating++;

empty = get_empty_inode();

if (!--h->updating)

wake_up(&update_wait);

if (empty)

goto repeat;

return (NULL); // free list上無可用inode

inode = empty;

// 設定各成員值

inode->i_sb = sb;

inode->i_dev = sb->s_dev;

inode->i_ino = nr;

inode->i_flags = sb->s_flags;

put_last_free(inode);

// 放到新的hash Q上

insert_inode_hash(inode);

// 從磁盤讀取inode

read_inode(inode);

goto return_it;

found_it:

if (!inode->i_count)

nr_free_inodes--;

inode->i_count++;

wait_on_inode(inode);

if (inode->i_dev != sb->s_dev || inode->i_ino != nr) {

printk("Whee.. inode changed from under us. Tell Linus\n");

iput(inode);

// 跨挂接點

if (crossmntp && inode->i_mount) {

struct inode * tmp = inode->i_mount;

tmp->i_count++;

inode = tmp;

iput(empty);

return_it:

while (h->updating)

sleep_on(&update_wait);

return inode;

回收:

void iput (In_Core_Inode * iNode)

加鎖;

--引用計數;

if (引用計數為0)

if (連結計數為0)

釋放檔案所占磁盤塊;

設定檔案類型為0;

釋放磁盤inode,ifree();

if (inode被更新)

将更新寫到磁盤inode;

将該核内inode放到free list上;

解鎖;

Linux 0.99.15的實作:

void iput(struct inode * inode)

if (!inode)

return;

wait_on_inode(inode); // 等待該inode被釋放

if (inode->i_pipe)

wake_up_interruptible(&PIPE_WAIT(*inode));

if (inode->i_count>1) {

inode->i_count--; // 減少引用計數

wake_up(&inode_wait); // 解鎖

if (inode->i_pipe) {

unsigned long page = (unsigned long) PIPE_BASE(*inode);

PIPE_BASE(*inode) = NULL;

free_page(page);

if (inode->i_sb && inode->i_sb->s_op && inode->i_sb->s_op->put_inode) {

inode->i_sb->s_op->put_inode(inode);

if (!inode->i_nlink) // 連結數為0,此處并未釋放磁盤inode

if (inode->i_dirt) {

write_inode(inode); /* we can sleep - so do again */

inode->i_count--;

nr_free_inodes++;

由于Linux中不區分核内inode和磁盤inode,是以實作上與這裡描述的算法有較大差别。

參考:

The Design of The UNIX Operation System, by Maurice J. Bach

Linux Kernel Source Code v0.99.15, by Linus Torvalds

Linux Kernel Source Code v2.6.22, by Linus Torvalds and Linux community.

Understanding The Linux Kernel, 3rd edition, by Daniel P. Bovet, Marco Cesati

Copyleft (C) 2007 raof01. 本文可以用于除商業用途外的所有用途。若要用于商業用途,請與作者聯系。

繼續閱讀