# 作業系統 report 1
資工大三 109062123 曹瀚文
資工大三 109062309 李昕威
| 工作項目 | 分工 |
| -------- | -------- |
| Trace Code | 曹瀚文 |
| 文件撰寫 | 曹瀚文 & 李昕威 |
| 功能實作 | 曹瀚文 |
## (a). Trace the SC_Halt system call to understand the implementation of a system call.
### Machine::Run
:::info
以無窮迴圈模擬程式碼的執行
:::
1. 先切換到 UserMode。
2. 每一個 Tick 會去抓執行程式中的下一個 instruction。
3. 呼叫 `OneInstruction` 去執行該 instruction。
4. 等到下一個 Tick 後再重複執行同樣的操作。
### Machine:: OneInstruction
:::info
判斷 instruction 並執行
:::
1. 到 registers 中找 r34 所指向的位置。
2. 根據該位置讀入4個 bytes 當作接下來執行的 instruction
3. 將讀入的 instruction 切成 opCode、rs、rt、rd。
4. 根據 opCode 的數值來決定要模擬 CPU 執行的操作。
5. opCode 為 OP_SYSCALL 時會呼叫 `RaiseException` 來模擬拋出 interrupt。
### Machine::RaiseException
:::info
遇到 system call 或 error 的時候切換到 System Mode
:::
1. 如果是因為記憶體問題而引發的 Exception 則會先記錄違法存取的記憶體位置。
2. 切換到 System Mode 來並且呼叫 `ExceptionHandler` 處理 Exception。
3. 處理完後切換回 User Mode。
## ExceptionHandler
:::info
根據 exception 的類型去呼叫 ksyscall.h (OS介面) 裡的函式
:::
1. 讀取 Register r2 的數值當作 SystemException 的類型。
2. 檢查目前呼叫 ExceptionHandler 是否是因為 system call 或是其他的 Exception。
3. SC_halt 範例是呼叫 SystemcallException,並且 type 為 SC_Halt。
4. 因此ExceptionHandler 會呼叫 SysHalt() 並將程式結束。
### SysHalt
:::info
呼叫 kernel 的 interrupt 的 Halt 功能。
:::
```cpp=
kernel->interrupt->Halt();
```
### Interrupt::Halt
:::info
會輸出以下固定資訊,然後將kernel刪除掉。
:::

## (b). Trace the SC_Create system call to understand the basic operations and data structure in a file system. (Sample code: createFile.c)
### ExceptionHandler
:::info
呼叫 ksyscall.h (OS介面) 裡的 SysCreate
:::
1. 在 SC_Create 的範例中 ExceptionHandler 會執行 SC_Create 的 case
2. 首先會先讀取 r4 register 中的數值當作參數
3. 然後將該參數對應到的記憶體位置當作檔案的名稱
4. 並且呼叫 SysCreate 來創建檔案
5. 接著更新 r2 的 register,讓 OS 來表示創建檔案的狀態
6. 並在最後更新 PCReg 的相關參數狀態。
### SysCreate
會直接呼叫 kernel 中的 filesystem 的 Create 來完成工作。
```cpp=
kernel->fileSystem->Create(filename);
```
### FileSystem::Create
1. 在 filesys.h 中有定義兩種 FileSystem,本次作業中會在編譯指令中加入 FILESYS_STUB 因此會執行以下程式碼。

2. 在 Create 裡面會呼叫 OpenForWrite(),如果正常創立就會關閉檔案並回傳 1 如果沒有則回傳 0。
3. 在OpenForWrite則是會直接呼叫系統功能來完成創立檔案。

## (c\). Trace the SC_PrintInt system call to understand how NachOS implements asynchronized I/O using CallBack functions and register schedule events.
### ExceptionHandler
:::info
根據 exception 的類型去呼叫 ksyscall.h (OS介面) 裡的函式
:::
1. 為了執行 `result = Add(42, 23)` 會先拋出一個 Exception 的 SC_Add 計算 register 中 r4 和 r5 相加的值並存在 r2。
2. 下一次 call Syscall 的類型是 SC_PrintInt。
3. 會先從 register 中的 r4 讀取數值當作要輸出的參數。
4. 然後呼叫 SysPrintInt 交給 OS 處理。
5. 並在最後更新 PCReg 的相關參數狀態。
### SysPrintInt
:::info
在 SysPrintInt 會將執行 kernel 中 synchConsoleOut 的 PutInt。
:::
```cpp=
kernel->synchConsoleOut->PutInt(val);
```
### SynchConsoleOutput::PutInt
:::info
將數字轉成字元後 putchar 列印
:::
1. 首先將要輸出的數字用 `sprintf` 轉成 char 陣列。
2. 在 PutInt 中會先將取得一個互斥鎖(lock),確保目前沒人在使用否則會暫時將 thread 呼叫 sleep 暫停住。
3. 逐字元的執行接下來的操作,先呼叫 PutChar 來執行輸出
4. 然後將 waitFor 的互斥鎖卡住
5. 等待 IO 結束後呼叫 callback 來解開並繼續執行同樣操作
6. 直到結束後再釋放一開始的互斥鎖。
### SynchConsoleOutput::PutChar
:::info
將字元列印到 console 上
:::
基本上跟 PutInt 一樣
但因為只要輸出一個字元,因此只會執行一次 `consoleOutput->PutChar(ch);` 後就結束。
### ConsoleOutput::PutChar
:::info
將字元寫到顯示器上,並設定中斷(interrupt)的時程
:::
1. 首先會先以 `putBusy` 來確認沒有人同時使用 IO。
2. 接下來會用系統內建功能(`WriteFile`)將輸出字串寫入 buffer 之中。
3. 設定 putBusy 為 TRUE 避免同時使用。
4. 然後將目前的事件加入 Interrupt::Schedule 裡面。
### Interrupt::Schedule
:::info
宣告一個 PendingInterrupt 並把他加入 pending 裡面。
:::
```cpp=
pending->Insert(toOccur);
```
### Machine::Run
接下來系統會繼續執行跑 Machine::Run,其中會呼叫 OneTick。
### Machine::OneTick
:::info
根據當前的 mode 來增加模擬的時間
:::
1. 根據當前的 mode 來增加模擬的時間
2. 在 OneTick 裡面會呼叫 CheckIfDue 檢查是否有在 pending 的 interrupts。
3. 如果沒有,則 re-enable interrupts。
### Interrupt::CheckIfDue
:::info
檢查是否有要執行的 interrupt,有的話就執行
:::
1. 檢查 pending 裡面是否有還未執行的 interrupt。
2. 檢查是否已經暫停新增 interrupt,避免 interrupt 互相打斷。
3. 模擬 interrupt 讓 CPU idle 的狀況。
4. 最後呼叫其 interrupt 的 callback function。
### ConsoleOutput::CallBack
:::info
可以 output 後會呼叫這個函式
:::
1. 設定 putBusy 為 FALSE 開放使用。
2. 增加統計的字數。
3. 呼叫 SynchConsoleOutput 的 CallBack `callWhenDone->CallBack()`。
### SynchConsoleOutput::CallBack
1. 將 waitFor 釋放出來 `waitFor->V()`。
## Trace the Makefile in code/test/Makefile to understand how test files are compiled.
在 test 裡面的 Makefile 首先會先 include Makefile.dep,讀取要使用的程式的位置或是設定編譯要用的 flag。
接著設定 gcc、as、ld 的路徑位置,然後設定需要 include 的 library 的路徑和編譯用的參數。
然後定義 all 工作編譯所有測試程式,所有測試程式在一開始都會檢查是否有把 start.S 編譯成 start.o,然後再將 start.o 跟每一個程式編譯出來的 Object 檔案做 link,最後將結果用 COFF2NOFF 編譯出執行檔案。
以 halt 為例:
```cpp=
start.o: start.S ../userprog/syscall.h
$(CC) $(CFLAGS) $(ASFLAGS) -c start.S
halt.o: halt.c
$(CC) $(CFLAGS) -c halt.c
halt: halt.o start.o
$(LD) $(LDFLAGS) start.o halt.o -o halt.coff
$(COFF2NOFF) halt.coff halt
```
## (b). Explain how the arguments of system calls are passed from user program to kernel in each of the above use cases.
已Halt程式爲例,以下爲Halt編譯出來的組合語言。
```
At PC = 0 JAL 68
At PC = 4 SLL r0,r0,0
At PC = 272 ADDIU r29,r29,-24
At PC = 276 SW r31,20(r29)
At PC = 280 SW r30,16(r29)
At PC = 284 ADDU r30,r29,r0
At PC = 288 JAL 65
At PC = 292 SLL r0,r0,0
At PC = 260 JR r0,r31
At PC = 264 SLL r0,r0,0
At PC = 296 JAL 5
At PC = 300 SLL r0,r0,0
At PC = 20 ADDIU r2,r0,0
At PC = 24 SYSCALL
```
可以發現在倒數第二行中將r2設定爲r0+0,其中0在syscall.h中是定義爲SC_Halt。
而最後一行爲SYSCALL的instruction。
在MIPS Machine simulator中,mipssim.cc實作如果讀到SYSCALL將會call Machine::RaiseException拋出一個Exception。

而在接到Exception後RaiseException會先切換成kernel mode後call ExceptionHandler處理Exception。
而在ExceptionHandler中會先看是否之前是否爲SyscallException並且讀出之前設定在r2的type值,而在這個範例裏面r2爲之前放入的SC_Halt,因此ExceptionHandler會在執行SC_Halt的程式碼後結束。

由於SC_Halt並沒有參數,但如果其他syscall有參數的話,會在SYSCALL之前的instruction將所需要的參數放入r4~r7的記憶體,之後在ExceptionHandler中讀出做使用,實作方法跟上述的type一樣。
而其syscall type的設定方法放置在start.S裏面,並設定Halt爲全域的函數,由於halt.c有include syscall.h因此可以知道Halt的定義,所以編譯後在Makefile裏面將start.o跟編譯出來的halt.o做link,讓halt.o可以執行start.S裏面定義的Halt。

## Implement four I/O system calls in NachOS
### syscall.h
取消註解實作部份功能的編碼定義。

### exception.cc
按照類似 SC_Create 的格式新增 SC_Open、SC_Write、SC_Read、SC_Close。
如果有參數的話,就依序從 r4、r5、r6... 去拿。

### start.S
按照 Create 的部份的格式去新增 Open、Write、Read、Close。

### ksyscall.h
新增 SysOpen、SysWrite、SysRead、SysClose,並呼叫 kernel->filesystem 來處理。

### filesys.h
Open 的部分使用了 lib/sysdep.cc 中定義的 OpenForReadWrite 來實作,並且掃過 OpenFileTable 來檢查是否有空個格子可以放入。
Write、Read、Close 的部分皆使用 OpenFile 這個 class 來實作,並在事前先檢查 id 是否在 [0, 19] 以內,還有 OpenFileTable 是否不是 NULL。
```cpp=
OpenFileId OpenAFile(char *name) {
int fileDescriptor = OpenForReadWrite(name, FALSE);
if (fileDescriptor == -1)
return -1;
OpenFileId flag = -1;
for (int i = 0; i < 20; i++)
{
if(OpenFileTable[i] == NULL){
OpenFileTable[i] = new OpenFile(fileDescriptor);
flag = i;
break;
}
}
return flag;
}
int WriteAFile(char *buffer, int size, OpenFileId id){
if(id < 0 || id >= 20 || OpenFileTable[id] == NULL || size < 0)
return -1;
return OpenFileTable[id]->Write(buffer, size);
}
int ReadAFile(char *buffer, int size, OpenFileId id){
if(id < 0 || id >= 20 || OpenFileTable[id] == NULL || size < 0)
return -1;
return OpenFileTable[id]->Read(buffer, size);
}
int CloseAFile(OpenFileId id){
if(id < 0 || id >= 20 || OpenFileTable[id] == NULL)
return -1;
delete OpenFileTable[id];
OpenFileTable[id] = NULL;
return true;
}
```
## Difficultiy
NachOS 在實作上有許多排版習慣跟常見的標準不一樣,因此在閱讀上常常會有理解錯誤的問題,後面使用 vscode 的 formate 即可解決。此外由於 NachOS 是比較大型的專案,因此分類功能較為細緻,有許多不同的功能是會分開寫在不同的地方追蹤上常常沒辦法馬上就找到相應的程式碼。
###### tags: `OS`