概述
這次實驗涉及文件系統,重點是對inode節點的操作。
內容
Large files
這個任務主要目的是支持更大的文件。和內存映射類似,文件系統中也有一個類似“頁表”的結構,每個文件(inode)都有自己的一個“頁表”,維護自己文件占用的文件塊。和內存不同的是,這個“頁表”的級別是自定義的,原始的xv6的表有13項,前12項直接包含文件塊的地址,第13項是二級表的地址,二級表包含256項,每項都是文件塊的地址,所以單個文件最大為12+256個文件塊,為了支持更大的文件,需要將直接包含文件塊地址的表項中取一項支持三級表,這樣單個文件就可以擴大到11+256+256*256塊。整體思路還是很清晰的,bmap函數添加:
bn -= NINDIRECT;
if(bn < NININDIRECT){
int lev1 = bn / NINDIRECT, lev2 = bn % NINDIRECT;
// Load indirect block, allocating if necessary.
if((addr = ip->addrs[NDIRECT + 1]) == 0)
ip->addrs[NDIRECT + 1] = addr = balloc(ip->dev);
bp = bread(ip->dev, addr);
a = (uint*)bp->data;
if((addr = a[lev1]) == 0){
a[lev1] = addr = balloc(ip->dev);
log_write(bp);
}
brelse(bp);
bp = bread(ip->dev, addr);
a = (uint*)bp->data;
if((addr = a[lev2]) == 0){
a[lev2] = addr = balloc(ip->dev);
log_write(bp);
}
brelse(bp);
return addr;
}
panic("bmap: out of range");
itrunc函數也做相應的添加:
if(ip->addrs[NDIRECT + 1]){
bp = bread(ip->dev, ip->addrs[NDIRECT + 1]);
a = (uint*)bp->data;
for(j = 0; j < NINDIRECT; j++) {
if(a[j]) {
bp2 = bread(ip->dev, a[j]);
a2 = (uint*)bp2->data;
for(k = 0; k < NINDIRECT; k++) if (a2[k]) bfree(ip->dev, a2[k]);
brelse(bp2);
bfree(ip->dev, a[j]);
}
}
brelse(bp);
bfree(ip->dev, ip->addrs[NDIRECT + 1]);
ip->addrs[NDIRECT + 1] = 0;
Symbolic links
這個任務主要實現軟符號鏈接,個人還是覺得有點難度,需要對inode節點的各類操作比較熟悉。首先是給inode和dinode添加一個字符串屬性用來存儲符號鏈接中的目標路徑,比如dinode:
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+2]; // Data block addresses
char target[MAXTARGET];
};
主要這個MAXTARGET這個常量是有講究的,因為dinode是一個一個排布在一個硬盤塊里面的,所以硬盤塊的大小必須是dinode結構體大小的倍數,硬盤塊的大小是常量BSIZE的大小,即1024字節,當沒有target屬性時,dinode的大小為2*4+4+4*13=64,剛好被1024整除,同時查看param.h可以發現xv6規定的路徑長度最大可以為128,所以MAXTARGET還得大於128,因此我取MAXTARGET=192,192+64=256,被1024整除。
然后就是sys_symlink:
uint64 sys_symlink(void) { char target[MAXPATH], path[MAXPATH];
if(argstr(0, target, MAXPATH) < 0 || argstr(1, path, MAXPATH) < 0)
return -1;
begin_op();
struct inode *ip = create(path, T_SYMLINK, 0, 0);
if (ip == 0) {
end_op(); return -1;
}
memmove(ip->target, target, sizeof(target));
iupdate(ip); iunlockput(ip);
end_op(); return 0;
}
首先需要注意獲得傳入系統調用的字符串需要用argstr,不能用argaddr獲得地址后自己復制,因為那個地址是用戶內存的虛擬地址,現在在內核態,頁表已經被換掉了。這里調用了create函數來創建符號文件,因為create函數里沒有對inode的target屬性賦值,所以需要在這里處理。另外就是create函數返回的有效inode是已經經過iget和ilock的了,所以這里create完就直接賦值target屬性並更新到對應的dinode,然后iunlock解鎖再iput釋放inode指針(合起來是iunlockput函數)。
接着是對open函數的修改,主要就是添加一個沿着符號鏈接不斷查找的過程,經過查找后得到的路徑才是需要open的真正路徑:
if(omode & O_CREATE){
......
} else {
int cnt = 0;
for (;;) {
ip = namei(path);
if (ip == 0) {
end_op(); return -1;
}
ilock(ip);
if(ip->type != T_SYMLINK || (omode & O_NOFOLLOW)) break;
memmove(path, ip->target, MAXPATH);
iunlockput(ip);
cnt++;
if (cnt > 9) {
end_op(); return -1;
}
}
......
namei得到的inode是經過iget但沒ilock的,所以取屬性和修改屬性需先ilock。另外就是memmove參數的最后一個參數需要是MAXPATH而不是sizeof(ip->target),因為inode的target屬性我跟隨dinode的target屬性都設成192字節的字符串,大小大於MAXPATH即128,所以如果按sizeof來復制會溢出。
由於我使用的是虛擬機,雖然我已經通過使用無桌面版arch linux+ssh控制來盡可能減少CPU和內存的消耗了,但文件讀寫還是非常難頂。雖然程序單獨測試正確,思路也和網上別人通過的程序思路基本一樣,最后make grade的時候還是超時了,不得已,把測試腳本里的bigfile和usertests測試時限都改成10分鍾才通過(usertests里的writebig好像在這次實驗里格外耗時)。估計使用真機或WSL讀寫真磁盤來完成本實驗情況會好一些。