資源
練習1: 完成讀文件操作的實現(需要編碼)
題目
首先了解打開文件的處理流程,然后參考本實驗后續的文件讀寫操作的過程分析,編寫在sfs_inode.c中sfs_io_nolock讀文件中數據的實現代碼。
請在實驗報告中給出設計實現“UNIX的PIPE機制”的概要設計方案,鼓勵給出詳細設計方案。
解答
了解打開文件的處理流程
已對打開文件的代碼流程進行了詳細分析,見lab8 源碼分析:用戶執行open的詳細流程
實現sfs_io_nolock讀文件數據的功能
根據提示不難完成編碼。原代碼中提供兩個接口sfs_rbuf和sfs_rblock,分別用於以字節和文件塊(實際上就是頁面大小)為單位來讀取文件。主要需要考慮到讀文件時的起始和結束位置可能沒與block起始位置對齊,對於不足一個block的部分調用sfs_rbuf來讀取內容,對於中間多個block的部分則調用sfs_rblock來讀取。
設計實現“UNIX的PIPE機制”(待完成)
練習2: 完成基於文件系統的執行程序機制的實現(需要編碼)
題目
改寫proc.c中的load_icode函數和其他相關函數,實現基於文件系統的執行程序機制。執行:make qemu。如果能看看到sh用戶程序的執行界面,則基本成功了。如果在sh用戶界面上可以執行“ls”、“hello”等其他放置在sfs文件系統中的其他執行程序,則可以認為本實驗基本成功。
請在實驗報告中給出設計實現基於“UNIX的硬鏈接和軟鏈接機制”的概要設計方案,鼓勵給出詳細設計方案。
解答
實現基於文件系統的執行程序機制
-
首先要獲取ELF文件的大小len,方法是:使用fd索引fd_array,得到對應的file,根據file->inode->sfs_inode->sfs_disk_inode->size得到ELF文件大小。
-
然后申請大小為len的緩沖區buf,接下來調用load_icode_read來讀取ELF文件的內容並復制到buf,接下來解析ELF文件內容並復制相應section,這部分操作與之前的實驗相同。
-
接下來還需要拷貝輸入參數argc和kargv到用戶棧頂,並根據輸入參數所占的內存大小修改tf_esp的值。這樣當程序沿着load_icode -> do_execve -> sys_exec -> syscall -> trap_dispatch -> trap -> trapret一路返回並執行iret時,就能從棧頂上面的幾個位置獲取到argc和argv的值。
設計實現基於“UNIX的硬鏈接和軟鏈接機制”(待完成)
Bug 1:運行用戶程序sh時內存訪問異常
問題描述
編碼完成后,執行sudo make qemu
,查看輸出日志,發現沒有進入sh界面,提示錯誤信息:“not valid addr b0000000, and can not find it in vma”。
定位流程
-
首先確認0xb0000000這個地址是何時被訪問的。印象中這是用戶棧頂的地址,查看load_icode的實現,果然發現在結尾處把tf_esp設置為USTACKTOP 0xb0000000,后面沿着do_execve -> sys_exec -> syscall -> trap_dispatch -> trap -> trapret一路返回,在trapret的結尾處執行iret,由於發生特權級轉換,這時會把esp寄存器設置為0xb0000000.使用gdb調試發現,在進入用戶程序的開頭,會訪問esp所指的內存,這時就發生缺頁異常。
-
分析可能的原因:
- USTACKTOP這個地址本來就不能訪問?
- 內核態到用戶態的切換不成功?
- 頁目錄表和頁表設置有誤?
- 用戶程序sh有問題?
- 用戶程序的輸入參數沒設置?
-
使用gdb調試,在執行iret的地方使用si逐步調試,發現執行iret后,首先跳到地址為0x008004e9的位置運行,打開sh.asm查看對應位置的匯編代碼,發現0x008004e9是start的起始位置,start的第二條指令
movl (%esp), ebx
會訪問到0xb0000000,目的是加載argc參數。那么原因大概確定了,應該是我在load_icode函數中沒設置好輸入參數,導致現在訪問輸入參數失敗。
_start:
# set ebp for backtrace
movl $0x0, %ebp
8004e9: bd 00 00 00 00 mov $0x0,%ebp
# load argc and argv
movl (%esp), %ebx
8004ee: 8b 1c 24 mov (%esp),%ebx
lea 0x4(%esp), %ecx
8004f1: 8d 4c 24 04 lea 0x4(%esp),%ecx
# move down the esp register since it may cause page fault in backtrace
subl $0x20, %esp
8004f5: 83 ec 20 sub $0x20,%esp
# save argc and argv on stack
pushl %ecx
8004f8: 51 push %ecx
pushl %ebx
8004f9: 53 push %ebx
# call user-program function
call umain
8004fa: e8 9d 04 00 00 call 80099c <umain>
- 那么為什么lab7不會有問題呢?於是回頭看lab7的代碼,首先查看lab7用戶程序的start的匯編代碼,發現果然有區別:lab8需要加載argc和argv,lab7則不需要加載。為什么會有這個差別?
_start:
# set ebp for backtrace
movl $0x0, %ebp
8003fb: bd 00 00 00 00 mov $0x0,%ebp
# move down the esp register
# since it may cause page fault in backtrace
subl $0x20, %esp
800400: 83 ec 20 sub $0x20,%esp
# call user-program function
call umain
800403: e8 f9 00 00 00 call 800501 <umain>
- 查看兩個lab的umain的實現,終於找到原因:lab8的umain函數定義含有argc和argv兩個輸入參數,lab7的umain函數定義無輸入參數。
// lab8
void umain(int argc, char *argv[]) {
int fd;
if ((fd = initfd(0, "stdin:", O_RDONLY)) < 0) {
warn("open <stdin> failed: %e.\n", fd);
}
if ((fd = initfd(1, "stdout:", O_WRONLY)) < 0) {
warn("open <stdout> failed: %e.\n", fd);
}
int ret = main(argc, argv);
exit(ret);
}
// lab7
void umain(void) {
int ret = main();
exit(ret);
}
-
但還是有疑問:即使我沒在棧頂設置好argc和argv,也應該能訪問棧頂吧?只是說訪問到的數據是不確定的而已啊。會不會是0xb0000000剛好是禁止訪問的邊界地址?我試着將0xb0000000 - 16賦值給tf_esp,再運行,竟然不報錯了!這時可以進入sh界面,輸入ls沒反應,輸入hello或forktest等命令則正常運行。總之,這基本證明了我的猜想是正確的。
-
接下來不難得到正確解法:0xb0000000是用戶地址邊界,如果要存儲argc和argv,則需要減去相應的值,在0xb0000000的前面來存儲。修改后再運行,果然能正常進入sh界面,而且執行ls也能正常輸出了。
-
最后的疑問:步驟6只是避免了訪問地址邊界,但沒有正確設置argc和argv(使用gdb調試時發現此時argc和argv都設置為0了),為什么大部分命令也能正常運行,唯獨ls運行異常(無輸出)?查看ls.c文件,發現當輸入argc為0時,確實不會執行任何操作;argc為1時,則會執行
ls .
.