作業系統lab6實驗報告
實驗文件-lab6
一、思考題彙總
思考1:
示例程式碼中,父程序操作管道的寫端,子程序操作管道的讀端。如果現在想讓父程序作為“讀者”,程式碼應當如何修改?
答:在fork
函式中,返回後會先執行父程序,再執行子程序,所以父程序執行寫入操作。因此在父程序開始執行時,先切換到子程序執行,就能讓子程序作為寫者,父程序作為讀者。
#include <stdlib.h> #include <unistd.h> int fildes[2]; /* buf size is 100 */ char buf[100]; int status; int main() { status = pipe(fildes); if (status == -1) { /* an error occurred */ printf("error\n"); } switch (fork()) { case -1: /* Handle error */ break; case 0: /* Child - writes to pipe */ close(fildes[0]); /* Read end is unused */ write(fildes[1], "Hello world\n", 12); /* Write data on pipe */ close(fildes[1]); /* Child will see EOF */ exit(EXIT_SUCCESS); default: /* Parent - reads from pipe */ yield(); close(fildes[1]); /* Write end is unused */ read(fildes[0], buf, 100); /* Get data from pipe */ printf("child-process read:%s", buf); /* Print the data */ close(fildes[0]); } }
思考2:
上面這種不同步修改pp_ref
而導致的程序競爭問題在user/fd.c
中的dup
函式中也存在。請結合程式碼模仿上述情景,分析一下我們的dup
函式中為什麼會出現預想之外的情況?
答:dup
函式的功能是將一個檔案描述符所對應的內容對映到另一個檔案描述符中。當我們將一個管道的讀/寫端對應的檔案描述符(記作fd[0]
)對映到另一個檔案描述符。在進行對映之前,f[0]
,f[1]
與pipe
的引用次數分別為1,1,2。按照dup
函式的執行順序,會先將fd[0]
引用次數+1,再將pipe
引用次數+1,如果fd[0]
的引用次數+1後恰好發生了一次時鐘中斷,程序切換後,另一程序呼叫_pipeisclosed
函式判斷管道寫端是否關閉,此時pageref(fd[0]) = pageref(pipe) = 2
思考3:
閱讀上述材料並思考:為什麼系統呼叫一定是原子操作呢?如果你覺得不是所有的系統呼叫都是原子操作,請給出反例。希望能結合相關程式碼進行分析。
答:在進行系統呼叫時,系統陷入核心,會關閉時鐘中斷,以保證系統呼叫不會被打斷,因此係統呼叫都是原子操作。
.macro CLI
mfc0 t0, CP0_STATUS
li t1, (STATUS_CU0 | 0x1)
or t0, t1
xor t0, 0x1
mtc0 t0, CP0_STATUS
.endm
思考4:
我們考慮之前那個預想之外的情景,它出現的最關鍵原因在於:pipe 的引用次數總比 fd 要高。當管道的 close 進行到一半時,若先解除 pipe
的對映,再解除 fd 的對映,就會使得 pipe 的引用次數的-1 先於 fd。這就導致在兩個 unmap 的間隙,會出現pageref(pipe) == pageref(fd) 的情況。那麼若調換 fd 和 pipe 在 close 中的 unmap 順序,能否解決這個問題呢?
仔細閱讀上面這段話,並思考下列問題:
- 按照上述說法控制
pipeclose
中fd
和pipe unmap
的順序,是否可以解決上述場景的程序競爭問題?給出你的分析過程。 - 我們只分析了
close
時的情形,在fd.c
中有一個dup
函式,用於複製檔案內容。試想,如果要複製的是一個管道,那麼是否會出現與close
類似的問題?請模仿上述材料寫寫你的理解。
答:可以解決。如果程式正常執行,pipe
的pageref
是要大於fd
的,在執行unmap
操作時,優先解除fd
的對映,這樣就可保證嚴格大於關係恆成立,即使發生了時鐘中斷,也不會出現執行錯誤。
dup
同理。
思考5:
bss
在 ELF 中並不佔空間,但 ELF 載入進記憶體後,bss
段的資料佔據了空間,並且初始值都是 0。請回答你設計的函式是如何實現上面這點的?
答:處理bss
段的函式是lab3中的load_icode_mapper
。在這個函式中,我們要對bss
段進行記憶體分配,但不進行初始化。當bin_size < sgsize
時,會將空位填0,在這段過程中為bss
段的資料全部賦上了預設值0。
思考6:
為什麼我們的 *.b 的 text 段偏移值都是一樣的,為固定值?
答:在user
的link script
檔案中對.text
段的地址做了統一的約定。
思考7:
在哪步,0 和 1 被 “安排” 為標準輸入和標準輸出?請分析程式碼執行流程,給出答案。
答:在user\init.c
中。
二、實驗難點圖示
難點1:填寫有關管道的函式,即piperead
,pipewrite
和_pipeisclosed
函式。
_pipeisclosed
:這是一個判斷函式,這個函式的判斷機制主要是看pageref(fd)
和pageref(page)
的大小,為保證這兩個值在讀取過程中沒有切換,需要不斷進行env_runs
的判斷,直到穩定為止。piperead
:在p_rpos >= p_wpos
時不能立刻返回,而是應該根據_pipeisclosed()
的返回值判斷管道是否關閉,若未關閉,應執行程序切換。pipewrite
:在p_wpos - p_rpos >= BY2PIPE
時不能立刻返回,而是應該根據_pipeisclosed()
的返回值判斷管道是否關閉,若未關閉,應執行程序切換。
難點2:spawn
函式。
此函式的填寫從理解上來說十分複雜,個人認為是整個OS課設中最難理解的一個。中間用到了lab3
的那個複雜的load_icode_mapper
所對應的系統呼叫,在此過程中,需要利用一個臨時頁面以支援在使用者態進行二進位制檔案的載入。
三、體會與感想
關於lab6
lab6的內容相對較少,但理解上的難度較高,很多函式都給人一種難以下手的感覺,在弄懂函式原理的過程中,花費了很多的功夫。
在lab6中依然少不了每次必有的祖傳bug挖掘環節,這次找到了一個在lab5中的bug,即在fsformat.c
中的create_file
內沒有遍歷充分。
此外,lab6的程式碼給人一種OO第二單元多執行緒的感覺,在許多地方都要考慮由於程序切換所造成的影響等,這些可能的情況指導書基本已經給出,需要詳盡的考慮。
關於OS課程
OS的實驗終於在這裡告一段落,這應該是我接觸計算機以來,第一次接觸有關核心的知識,第一次去一步步探索計算機的底層程式碼如何執行,雖然說中間不管是課下實驗還是課上實驗都遇到了些許困難,但最終的收穫還是很大的。