系列文章 : [6.1810] 跟著 MIT 6.1810 學習基礎作業系統觀念
介紹 `struct-inode`, `struct-dinode`, `itable`。
`inode` 在這裡可能有兩個意思。
on-disk-inode 被打包在 disk hardware 上的連續空間,被稱為 `inode blocks`。每一個 inode 都有相同的大小,所以只要給我們一個數字 `n`,我們就能很容易地找到在 disk 上的 `nth` inode。
這個數字 n,被稱為 `inode-number` 或是 `i-number`。在 xv6-riscv file-system 的實作中,使用 `i-number` 來辨別 inodes。
There are four lock or lock-like mechanisms in xv6’s inode code.
介紹 iget, iput, ilock, iunlock, iupdate。
// On-disk inode structure
struct dinode {
short type; // File type
short major; // Major device number (T\_DEVICE only)
short minor; // Minor device number (T\_DEVICE only)
short nlink; // Number of links to inode in file system
uint size; // Size of file (bytes)
uint addrs\[NDIRECT+1\]; // Data block addresses
};
這是會放到 disk 裡面的 inode structure。
// in-memory copy of an inode
struct inode {
uint dev; // Device number
uint inum; // Inode number
int ref; // Reference count
struct sleeplock lock; // protects everything below here
int valid; // inode has been read from disk?
short type; // copy of disk inode
short major;
short minor;
short nlink;
uint size;
uint addrs\[NDIRECT+1\];
};
`struct inode` 是 `struct dinode` 的 in-memory copy。
而 `struct inode` 這個 in-memory 依據 xv6-riscv system software 的需求,多開了幾個欄位 ( e.g. dev, inum, ref … )。
struct-inode 是 struct-dinode( 在 disk hardware ) 在 memory 裡的副本。僅在有人使用 C pointer 指向一個 struct-inode 的時候,才會把 struct-dinode ( in disk hardware ) 複製到 struct-inode ( in memory )。
ref 欄位 : 計算 C pointers 指向 in-memory inode 的數量。 當 ref 歸 0 的時候,kernel 會丟棄這個 inode。
struct {
struct spinlock lock;
struct inode inode\[NINODE\];
} itable;
xv6-riscv 會把目前在 memory 裡面有的 active inodes,存放到 `itable` 裡面。
要注意到 struct-inode->inum 指的`並不是` itable->inode[N] 陣列的 index。
{ https://github.com/mit-pdos/xv6-riscv/blob/xv6-riscv-rev5/kernel/fs.c#L183 }
// Find the inode with number inum on device dev
// and return the in-memory copy. Does not lock
// the inode and does not read it from disk.
static struct inode\*
iget(uint dev, uint inum)
{
這個 function 會嘗試用 `dev` 以及 `inum` 去拿出一個 in-memory copy struct-inode。
雖然拿到了 struct-inode,但這不代表這個 inode 的資訊已經從 disk hardware 讀取出來了 ( 需要用 ilock 讀取出來 )。
struct inode \*ip, \*empty;
acquire(\&itable.lock);
// Is the inode already in the table?
empty \= 0;
for(ip \= \&itable.inode\[0\]; ip \< \&itable.inode\[NINODE\]; ip++){
if(ip-\>ref \> 0 && ip-\>dev \== dev && ip-\>inum \== inum){
ip-\>ref++;
release(\&itable.lock);
return ip;
}
if(empty \== 0 && ip-\>ref \== 0\) // Remember empty slot.
empty \= ip;
}
嘗試去 itable.inode 裡面看看,想要找的 inode 是不是已經在這個陣列裡面了。
並且同時找找看有沒有空的 itable.inode[N] entry。
// Recycle an inode entry.
if(empty \== 0\)
panic("iget: no inodes");
ip \= empty;
ip-\>dev \= dev;
ip-\>inum \= inum;
ip-\>ref \= 1;
ip-\>valid \= 0;
release(\&itable.lock);
return ip;
}
在 itable.inode[N] 裡面找不到的話,就回收那個空的 entry ( 在這裡以 empty 變數表示 )。
把 dev, inum, ref, valid 初始化,要注意到這時候 `type, major, minor, nlink, size, addrs` 都還沒初始化,要等待未來的某個時刻,從 disk hardware 讀取。
跟 ialloc 相比
// Drop a reference to an in-memory inode.
// If that was the last reference, the inode table entry can
// be recycled.
// If that was the last reference and the inode has no links
// to it, free the inode (and its content) on disk.
// All calls to iput() must be inside a transaction in
// case it has to free the inode.
void
iput(struct inode \*ip)
{
這個 function 表示某個 C pointer 不需要再指向這個 inode 了,
acquire(\&itable.lock);
if(ip-\>ref \== 1 && ip-\>valid && ip-\>nlink \== 0){
// inode has no links and no other references: truncate and free.
// ip-\>ref \== 1 means no other process can have ip locked,
// so this acquiresleep() won't block (or deadlock).
acquiresleep(\&ip-\>lock);
因為這邊準備操作這個 struct-inode 所代表的 disk hardware 內的 data blocks,所以需要取用該 inode 的 sleeplock。
release(\&itable.lock);
itrunc(ip);
ip-\>type \= 0;
iupdate(ip);
ip-\>valid \= 0;
releasesleep(\&ip-\>lock);
acquire(\&itable.lock);
}
ip-\>ref--;
release(\&itable.lock);
}
因為少一個 C pointer 指向這個 inode,所以 ip->ref--
// Increment reference count for ip.
// Returns ip to enable ip \= idup(ip1) idiom.
struct inode\*
idup(struct inode \*ip)
{
acquire(\&itable.lock);
ip-\>ref++;
release(\&itable.lock);
return ip;
}
單純的把 ip->ref++,表示給定某一個 struct-inode,有新的 process 也想要有個 C pointer 指向該 inode。
// Lock the given inode.
// Reads the inode from disk if necessary.
void
ilock(struct inode \*ip)
{
struct buf \*bp;
struct dinode \*dip;
這個 function 會嘗試去拿取一個 in-memory-struct-inode 的 sleep-lock,
並且需要的話,從 disk-hardware 裡面讀取相對應的 struct-dinode 的內容。
if(ip \== 0 || ip-\>ref \< 1\)
panic("ilock");
沒有人正在用這個 struct-inode,怎麼還會需要拿這個 struct-inode 的 sleep-lock ? 怪怪的,直接發一個 panic。
acquiresleep(\&ip-\>lock);
if(ip-\>valid \== 0){
bp \= bread(ip-\>dev, IBLOCK(ip-\>inum, sb));
dip \= (struct dinode\*)bp-\>data \+ ip-\>inum%IPB;
ip-\>type \= dip-\>type;
ip-\>major \= dip-\>major;
ip-\>minor \= dip-\>minor;
ip-\>nlink \= dip-\>nlink;
ip-\>size \= dip-\>size;
memmove(ip-\>addrs, dip-\>addrs, sizeof(ip-\>addrs));
brelse(bp);
ip-\>valid \= 1;
if(ip-\>type \== 0\)
panic("ilock: no type");
}
}
接下來把 struct-dinode 的資料載入到相對應的 in-memory-struct-inode。
// Unlock the given inode.
void
iunlock(struct inode \*ip)
{
if(ip \== 0 || \!holdingsleep(\&ip-\>lock) || ip-\>ref \< 1\)
panic("iunlock");
releasesleep(\&ip-\>lock);
}
釋放這個 in-memory-struct-inode 的 sleep-lock,喚醒其他等待這個 sleep-lock 的 process。
// Common idiom: unlock, then put.
void
iunlockput(struct inode \*ip)
{
iunlock(ip);
iput(ip);
}
就是把 iunlock 跟 iput 放在一起。
這個順序很常被使用,索性放在同一個 function。