MIT-6.S081-2020實驗(xv6-riscv64)九:fs
概述
這次實驗涉及檔案系統,重點是對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讀寫真磁碟來完成本實驗情況會好一些。