Preface:
Nachos的全稱是“Not Another Completely Heuristic Operating System”,它是一個可修改和 trace 的操作系統教學軟件. 它給出了一個支持多線程和虛擬存儲的操作系統骨架,可讓學生在較短的時間內對操作系統中的基本原理和核心算法有一個全面和完整的了解. 該操作系統核心部分使用C++實現,代碼可讀性較強. 這邊我們實作的第一個實驗就是基於NachOS去撰寫 System Call.
實作簡介:
實作的內容是為 NachOS 添加幾個 System Call , 分別為 OSAdd(op1,op2), OSSub(op1,op2), OSDiv(op1,op2), OSMul(op1,op2) 與 Print(char *msg) (作用是在系統控制台上打印字符串). 從 code/threads/kernel.cc, code/machine/machine.h 與 code/userprog/synchconsole.h 可知,操作系統預設可以使用的函數或類別有:
- kernel->synchConsoleOut->PutChar( char ch); //向控制台輸出字符
- kernel->machine->ReadRegister( int num); //讀暫存器
- kernel->machine->WriteRegister( int num, int value); //寫暫存器
- kernel->machine->ReadMem( int addr, int size, int * value); //從用戶進程內存讀值
使用 Nachos 運行 user program 可以使用命令:
nachos 執行檔的 entry point 在 code/threads/main.cc啟動,創建了Kernel對象並進行初始化(啟動主線程、創建線程調度表、中斷處理模塊、CPU、控制台、文件系統、中斷等):
之後,程序創建了用戶空間程序,初始化的時候加載了指令集並調用了kernel->machine->WriteRegister 填寫CPU寄存器,然後開始執行。執行由 kernel->machine->Run 實現:
-
- ...
- void
- AddrSpace::Execute()
- {
- kernel->currentThread->space = this;
- this->InitRegisters(); // set the initial register values
- this->RestoreState(); // load page table register
- kernel->machine->Run(); // jump to the user progam
- ASSERTNOTREACHED(); // machine->Run never returns;
- // the address space exits
- // by doing the syscall "exit"
- }
- ...
接著使用 OneInstruction 函數模擬 CPU 的逐條指令執行過程:
-
- ...
- //----------------------------------------------------------------------
- // Machine::Run
- // Simulate the execution of a user-level program on Nachos.
- // Called by the kernel when the program starts up; never returns.
- //
- // This routine is re-entrant, in that it can be called multiple
- // times concurrently -- one for each thread executing user code.
- //----------------------------------------------------------------------
- void
- Machine::Run()
- {
- Instruction *instr = new Instruction; // storage for decoded instruction
- if (debug->IsEnabled('m')) {
- cout << "Starting program in thread: " << kernel->currentThread->getName();
- cout << ", at time: " << kernel->stats->totalTicks << "\n";
- }
- kernel->interrupt->setStatus(UserMode);
- for (;;) {
- OneInstruction(instr);
- kernel->interrupt->OneTick();
- if (singleStep && (runUntilTime <= kernel->stats->totalTicks))
- Debugger();
- }
- }
- ...
系統調用的實現過程:
如同 Linux 操作系統一樣,運行在虛擬機上的 NachOS 也使用中斷來實現 System Call(User Mode 程序調用操作系統內核服務的接口).實現方法為:
0. 為了方便 debug, 我新增了一個 debug 類型 "dbJohn" 如下
-
- ...
- const char dbJohn = 'j'; // Debug by John
- ...
1. 當 System Call 發生的時候,首先把函數參數、系統調用號存入寄存器,然後引發系統中斷,通過中斷調用進入 Kernel mode. 以 System Call "OSAdd" 為範例:
-
- .globl OSAdd /*聲明為外部函數*/
- .ent OSAdd /* OSAdd 函數開始*/
- OSAdd:
- addiu $2,$0,SC_OSAdd /* 將 system call 呼叫 case num 存入r2寄存器*/
- syscall /* all parameter of this system call will be stored in register 4,5,6 and 7 by MIPS machine automatically.*/
- j $31
- .end OSAdd
-
- case SC_OSAdd:
- DEBUG(dbJohn, "Add " << kernel->machine->ReadRegister(4) << " + " << kernel->machine->ReadRegister(5) << "\n");
- //int result;
- op1 = (int)kernel->machine->ReadRegister(4);
- op2 = (int)kernel->machine->ReadRegister(5);
- result = SysOSAdd(op1, op2);
- DEBUG(dbJohn, "Add returning with " << result << "\n");
- kernel->machine->WriteRegister(2, (int)result); /* Prepare Result */
- /* Modify return point */
- {
- /* set previous programm counter (debugging only)*/
- kernel->machine->WriteRegister(PrevPCReg, kernel->machine->ReadRegister(PCReg));
- /* set programm counter to next instruction (all Instructions are 4 byte wide)*/
- kernel->machine->WriteRegister(PCReg, kernel->machine->ReadRegister(PCReg) + 4);
- /* set next programm counter for brach execution */
- kernel->machine->WriteRegister(NextPCReg, kernel->machine->ReadRegister(PCReg)+4);
- }
- return;
- ASSERTNOTREACHED();
- break;
3. 將 System Call "OSAdd" 的函數 "SysOSAdd" 定義在 "./userprog/ksyscall.h":
-
- //----------------------------------------------------------------------
- // SysOSAdd
- // Operation Add.
- //
- // Add "op1" with "op2".
- //
- // "op1" and "op2" are operands of operation Add.
- // ex: SysOSAdd(1,2) = 1+2 = 3
- //----------------------------------------------------------------------
- int SysOSAdd(int op1, int op2)
- {
- int result = op1 + op2;
- return result;
- }
- ...
- PROGRAMS= add halt shell matmult sort segments project1
- ...
- project1.o:project1.c
$(CC) $(CFLAGS) -c project1.c - project1: project1.o start.o
$(LD) $(LDFLAGS) start.o project1.o - o project1.coff $(COFF2NOFF) project1.coff project1 - ...
5. 使用 nachos 虛擬機載入 project1, 執行命令如下:
System Call Print(char* input, int length) 的具體實現:
有了上面的介紹, System Call OSAdd(op1,op2), OSSub(op1,op2), OSDiv(op1,op2), OSMul(op1,op2) 的實現應該都不是問題, 這裡要針對 Print(char *msg) System Call 進行詳細實作說明. 第一步當然就是在 start.S 添加 System Call Print 的聲明:
-
- .globl Print
- .ent Print
- Print:
- addiu $2,$0,SC_Print
- syscall
- j $31
- .end Print
-
- #define SC_Print 60
-
- case SC_Print:
- DEBUG(dbJohn, "Print call...\n");
- result = SysPrint(/* int op1 */(char*)kernel->machine->ReadRegister(4),
- /* int op2 */(int)kernel->machine->ReadRegister(5));
- kernel->machine->WriteRegister(2, (int)result); /* Prepare Result */
- /* Modify return point */
- {
- /* set previous programm counter (debugging only)*/
- kernel->machine->WriteRegister(PrevPCReg, kernel->machine->ReadRegister(PCReg));
- /* set programm counter to next instruction (all Instructions are 4 byte wide)*/
- kernel->machine->WriteRegister(PCReg, kernel->machine->ReadRegister(PCReg) + 4);
- /* set next programm counter for brach execution */
- kernel->machine->WriteRegister(NextPCReg, kernel->machine->ReadRegister(PCReg)+4);
- }
- return;
- ASSERTNOTREACHED();
- break;
-
- #include "kernel.h"
- #include "synchconsole.h"
- #include "utility.h"
- #include "stdio.h"
- ...
- //----------------------------------------------------------------------
- // SysPrint
- // Print Message to Nachos Console Output.
- //
- // Output message from with
chars. - //
- // "input" is the char* which pointer to output message; "length" tells the length of output message.
- // ex: SysPrint("John", 4) -> Output "John" to console.
- //----------------------------------------------------------------------
- int SysPrint(char* input, int length)
- {
- int i;
- int memVal = 0;
- int memPt = (int)input;
- for(i=0; i
- {
- kernel->machine->ReadMem(int(memPt+i),1,&memVal); /* 從用戶程序內存空間讀取字符串的值*/
- if(0 == memVal) return i;
- kernel->synchConsoleOut->PutChar((char)memVal);
- }
- return length;
- }
接著來寫測試代碼:
-
- #include "syscall.h"
- int main()
- {
- Print("Hello, I am John K Lee.\n" , 24);
- Halt();
- }
參考事項:
- Duplicate definition on ConsoleInput & ConsoleOutput
在實作 SysPrint 函數時, 為了使用 kernel->synchConsoleOut 來輸出字元, 你可能會包含進 "synchconsole.h". 此時在編譯 Nachos 時可能會出現下面錯誤訊息:
而可能的原因是在 "
- #define ConsoleInput 0
- #define ConsoleOutput 1
- ...
- class ConsoleInput : public CallBackObj {
- ...
- class ConsoleOutput : public CallBackObj {
- ...
- #define SysCallConsoleInput 0
- #define SysCallConsoleOutput 1
- Nachos Debugging
基本上有兩種方法來 debug Nachos: external & internal. external debugger 透過外部載入 Nachos 的程式, 並使用 interactive 方法與使用者互動, 你可以一行行 debug 程式並檢查相關變數的設定, 常見的有 GDB. internal debugger 就是在你的程式加入 debug message, 透過這些訊息來判斷問題出在哪裡, 最常用也最沒有效率, 因為你無法一步步除錯, 且無法知道執行當下的變數(除非你把他們都印出來 orz)與執行環境. 詳細的介紹可以參考 [ Nachos 4.0 ] Debugging Nachos
作者已經移除這則留言。
回覆刪除如果system call print部分的test case是這樣的話
回覆刪除#include "syscall.h"
int main()
{
int n;
for (n=1;n<5;n++) {
Print(n);
}
Halt();
}
又要怎麼寫呢?