# Team10 ## Team member:李侑庭(110062172), 盧思樺(110062272) | 部分 | 李侑庭 | 盧思樺 | | -------- | -------- | -------- | | Trace code | 50% | 50% | | Implement | 60% | 40% | | Report | 70% | 30% | | Testing | 30% | 70% | # Part II-1:Trace code ## (a)SC_Halt ![](https://hackmd.io/_uploads/rk50zjIbp.png =50%x) When user program calls the system call SC_Halt, start.S places the code for SC_Halt in r2.If ExceptionHandler is called from Machine::OneInstruction() to execute SC_Halt in kernal mode, then it will call SysHalt. ### 1.Machine::Run(): * Execute instructions one by one in user-level after leaving the kernel-level program, and advance the simulated time. ```clike void Machine::Run() { Instruction *instr = new Instruction; //storage for decoded instruction kernel->interrupt->setStatus(UserMode); //transfer control to user mode for (;;) { OneInstruction(instr); //execute one decoded instruction from a user-level program kernel->interrupt->OneTick(); //advance simulated time if (singleStep && (runUntilTime <= kernel->stats->totalTicks)) Debugger(); } } ``` ### 2.Machine::OneInstruction(Instruction *instr): * Fetch, decode and execute an instruction.If there is an exception or interrupt, this function will call RaiseException function to invoke exception handler. * In the case of a system call instruction, this function will also call RaiseException function but with different arguments. * When call RaiseException function, it will pass the exception type and the virtual address causing the trap as arguments. ### 3.Machine::RaiseException(ExceptionType which, int badVAddr): * Store the virtual address causing the trap in a register, transfer control to kernel mode, invoke the ExceptionHandler function to deal with the system call or exception. * After ExceptionHandler function retruns, control returns to the user mode. ```clike void Machine::RaiseException(ExceptionType which, int badVAddr) { registers[BadVAddrReg] = badVAddr; //store the virtual address causing the trap in a register DelayedLoad(0, 0); // finish anything in progress kernel->interrupt->setStatus(SystemMode); //leave user-level program ExceptionHandler(which); // interrupts are enabled at this point kernel->interrupt->setStatus(UserMode); //leave kernel-level program } ``` ### 4.ExceptionHandler(ExceptionType which): * Handle exceptions based on the 'which' argument and the register values. * In the case of a system call, this function will utilize the code stored in register 2 to determine how to handle this system call and put back the result into register 2. Otherwise, it will only output messages. * For the SC_Halt system call, it will call the SysHalt function. ### 5.SysHalt(): * Call the Halt function which in Interrupt. ```clike void SysHalt() { kernel->interrupt->Halt(); } ``` ### 6.Interrupt::Halt(): * Implement the Halt function.Print some message and delete kernel to shut down Nachos. ```clike void Interrupt::Halt() { cout << "Machine halting!\n\n"; cout << "This is halt\n"; kernel->stats->Print(); delete kernel; // Never returns. } ``` ## (b)SC_Create ![](https://hackmd.io/_uploads/B1jxmsUWp.png =80%x) When user program calls the system call SC_Create, start.S places the code for SC_Halt in r2 and places a pointer to filename.If ExceptionHandler is called from Machine::OneInstruction() to execute SC_Create in kernal mode, then it will call SysCreate with the pointer as a parameter. ### 1.ExceptionHandler(ExceptionType which): * Handle exceptions based on the 'which' argument and the register values. * In the case of a system call, this function will utilize the code stored in register 2 to determine how to handle this system call and put back the result into register 2. Otherwise, it will only output messages. * For the SC_Create (1) Find the filename by using the value in register 4 as an address to access the filename in main memory. (2) Calling the SysCreate function with the filename as a parameter and get the status. (3) Storing this status in register 2 and update program counter. ### 2.SysCreate(char *filename): * Calling Create function in fileSystem with the filename as a parameter. ```clike int SysCreate(char *filename) { // return value // 1: success // 0: failed return kernel->fileSystem->Create(filename); } ``` ### 3.FileSystem::Create(char *name): * Calling OpenForWrite function in fileSystem with the name as a parameter. * OpenForWrite function will create file with name if it doesn't exist, or truncate it if it does.Store the return value as fileDescriptor. * If fileDescriptor is -1, then return false.Otherwise,close fileDescriptor and return true. ```clike bool Create(char *name) { int fileDescriptor = OpenForWrite(name); if (fileDescriptor == -1) return FALSE; Close(fileDescriptor); return TRUE; } ``` ## (C\)SC_PrintInt ![](https://hackmd.io/_uploads/SyQzXjIb6.png =80%x) When user program calls the system call SC_PrintInt, start.S places the code for SC_Halt in r2 and places interger in r4.If ExceptionHandler is called from Machine::OneInstruction() to execute SC_Create in kernal mode, then it will call SC_PrintInt with the interger as a parameter. ### 1.ExceptionHandler(ExceptionType which): * Handle exceptions based on the 'which' argument and the register values. * In the case of a system call, this function will utilize the code stored in register 2 to determine how to handle this system call and put back the result into register 2. Otherwise, it will only output messages.. * For the SC_PrintInt (1) find the val in register 4 (2) Call SysPrintInt with the val as a parameter (3) Update program counter. ### 2.SysPrintInt() * Call PutInt with the val as a parameter. ### 3.SynchConsoleOutput::PutInt(int value) * Change the type of the value to char and store the value in the str. * Use lock->Acquire to lock resource.Only the calling thread can use resource, the other threads will be blocked until lock release. * Call PutChar function for each character in 'str' until the character is '\0'. * Use waitfor->P() function to make the subsequent characters wait. * Release lock. ### 4.SynchConsoleOutput::PutChar(char ch) * Like PutInt function but only call PutChar function once with the character ch. ### 5.ConsoleOutput::PutChar(char ch) * Call WriteFile function to write ch into a file. * Set putBusy true to avoid prevent call to PutChar in the same time. * Call Schedule function to schedule this function with the ConsoleTime and the type as parameter. ### 6.Schedule(CallBackObj *toCall, int fromNow, IntType type) * Schedule for the CPU to be interrputed when simulated time reaches "now + when" . * Create PendingInterrupt object with parameter and insert it to pending interrupts list. * When interrputed toCall object need to be called. ### 7.Run() * Execute instructions one by one in user-level after leaving the kernel-level program, and advance the simulated time. ### 8.OneTick() * Advance simulated time by the status. * Turn off interrupts. * Call CheckIfDue function to check pending interrupts with false as parameter. * Turn on interrupts. * If the timer device want to a context switch, do it. ### 9.CheckIfDue(bool advanceClock) * If the pending interrupts list is empty, then return false. * Check if it's time for the first pending interrupt. If it's not and the ready queue is empty, then advance the clock to the next interrupt. Otherwise, return false. * Set inHandler true. * Remove the first pending interrupt from the list and call CallBack function until pending interrupts list is empty or it's not yet time for them. * Set inHandler false. * Return true. ### 10.ConsoleOutput::CallBack() * Set putBusy false to allow the next character can be output to the display. * Update numConsoleCharsWritten. * Call SynchConsoleOutput::CallBack(). ### 11.SynchConsoleOutput::CallBack() * Use waitFor->V() to make the subsequent characters can be sent to display. ## (d)Makefile * target file: the name before colon represents the file we want to create. * dependency file: the names after the colon represent the files needed to create the target file. * rule : on the next line of target and after 'tab', the instructions we use to create target file. If we write a test file in fileIO_test3, for which the executable is to be called fileIO_test3.Then makefile must be add: ``` fileIO_test3.o: fileIO_test3.c $(CC) $(CFLAGS) -c fileIO_test3.c fileIO_test3: fileIO_test3.o start.o $(LD) $(LDFLAGS) start.o fileIO_test3.o -o fileIO_test3.coff $(COFF2NOFF) fileIO_test3.coff fileIO_test3 ``` And change the PROGRAMS definition ``` PROGRAMS = halt shell matmult sort fileIO_test3 ``` # Part II-2:Implement four I/O system calls in NachOS ## syscall.h Uncomment the system call which we want to use. ``` #define SC_Open 6 #define SC_Read 7 #define SC_Write 8 #define SC_Close 10 ``` ## start.S Make user system calls to the Nachos kernel and place the code and arguments.We add SC_Open, SC_Write, SC_Close and SC_Read here. ``` .globl Open .ent Open Open: addiu $2,$0,SC_Open syscall j $31 .end Open .globl Write .ent Write Write: addiu $2,$0,SC_Write syscall j $31 .end Write .globl Close .ent Close Close: addiu $2,$0,SC_Close syscall j $31 .end Close .globl Read .ent Read Read: addiu $2,$0,SC_Read syscall j $31 .end Read ``` ## exception.cc We include our system calls in the SyscallException and pass the parameters stored in registers to the corresponding function in ksyscall.h.After that, update the program counter value. ```clike //system call code in r2, arg1 in r4, arg2 in r5, arg3 in r6 case SC_Open: val = kernel->machine->ReadRegister(4); { char *filename = &(kernel->machine->mainMemory[val]); status = SysOpen(filename); kernel->machine->WriteRegister(2, (int)status); } kernel->machine->WriteRegister(PrevPCReg, kernel->machine->ReadRegister(PCReg)); kernel->machine->WriteRegister(PCReg, kernel->machine->ReadRegister(PCReg) + 4); kernel->machine->WriteRegister(NextPCReg, kernel->machine->ReadRegister(PCReg) + 4); return; ASSERTNOTREACHED(); break; case SC_Write: val = kernel->machine->ReadRegister(4); { char *filename = &(kernel->machine->mainMemory[val]); status = SysWrite(filename, (int)kernel->machine->ReadRegister(5), (OpenFileId)kernel->machine->ReadRegister(6)); kernel->machine->WriteRegister(2, (int)status); } kernel->machine->WriteRegister(PrevPCReg, kernel->machine->ReadRegister(PCReg)); kernel->machine->WriteRegister(PCReg, kernel->machine->ReadRegister(PCReg) + 4); kernel->machine->WriteRegister(NextPCReg, kernel->machine->ReadRegister(PCReg) + 4); return; ASSERTNOTREACHED(); break; case SC_Read: val = kernel->machine->ReadRegister(4); { char *filename = &(kernel->machine->mainMemory[val]); status = SysRead(filename, (int)kernel->machine->ReadRegister(5), (OpenFileId)kernel->machine->ReadRegister(6)); kernel->machine->WriteRegister(2, (int)status); } kernel->machine->WriteRegister(PrevPCReg, kernel->machine->ReadRegister(PCReg)); kernel->machine->WriteRegister(PCReg, kernel->machine->ReadRegister(PCReg) + 4); kernel->machine->WriteRegister(NextPCReg, kernel->machine->ReadRegister(PCReg) + 4); return; ASSERTNOTREACHED(); break; case SC_Close: { // cout << filename << endl; status = SysClose((OpenFileId)kernel->machine->ReadRegister(4)); kernel->machine->WriteRegister(2, (int)status); } kernel->machine->WriteRegister(PrevPCReg, kernel->machine->ReadRegister(PCReg)); kernel->machine->WriteRegister(PCReg, kernel->machine->ReadRegister(PCReg) + 4); kernel->machine->WriteRegister(NextPCReg, kernel->machine->ReadRegister(PCReg) + 4); return; ASSERTNOTREACHED(); break; ``` ## ksyscall.h Interface for the system calls.We only call the function and pass the parameters to filesys.h. ```clike OpenFileId SysOpen(char *filename) { return kernel->fileSystem->OpenAFile(filename); } int SysWrite(char *buffer, int size, OpenFileId id) { return kernel->fileSystem->WriteFile(buffer, size, id); } int SysRead(char *buffer, int size, OpenFileId id) { return kernel->fileSystem->ReadFile(buffer, size, id); } int SysClose(OpenFileId id) { return kernel->fileSystem->CloseFile(id); } ``` ## filesys.h Implement our file operations in filesys.h via OpenFile data structure. ### (1)OpenAFile: * We check the idx to determine the number of opened files.If the idx exceeds 19, we return -1. * Otherwise, create an OpenFile data structure in OpenFileTable and return the idx. ### (2)WriteFile/ReadFile * We check the id to determine the number of opened files.If the id exceed 19, less than 0 or the file is closed, then return -1. * Use the function of OpenFile in the OpenFileTable to write or read a file. * Bacause this function will return the number of characters written or read from the file, we return it. ### (3)CloseFile * Check the id to determine the number of opened files.If the id exceed 19, less than 0 or the file is closed, then return -1. * Otherwise, delete the OpenFile with the id from the OpenFileTable and return 1. ```clike idx = 0 OpenFileId OpenAFile(char *name) { int fileDescriptor = OpenForReadWrite(name, false); if (idx >= 20 || fileDescriptor < 0) { return -1; } OpenFileTable[idx] = new OpenFile(fileDescriptor); fid[idx++] = fileDescriptor; return idx - 1; } int WriteFile(char *buffer, int size, OpenFileId id) { if (id >= 20 || id < 0 || OpenFileTable[id] == NULL) { return -1; } return OpenFileTable[id]->Write(buffer, size); } int ReadFile(char *buffer, int size, OpenFileId id) { if (id >= 20 || id < 0 || OpenFileTable[id] == NULL) { return -1; } return OpenFileTable[id]->Read(buffer, size); } int CloseFile(OpenFileId id) { if (id >= 20 || id < 0 || OpenFileTable[id] == NULL) { return -1; } delete OpenFileTable[id]; OpenFileTable[id] = NULL; return 1; } ``` # What difficulties did you encounter when implementing this assignment? ## 李侑庭 由於一開始trace code時,沒注意到filesys中有OpenFile的object,對如何維護OpenFileTable困擾了很久,還有原本以為fileDescriptor就會記錄已打開的檔案數量,但在testing中與實際數量不一樣,於是才修改code。 ## 盧思樺 一開始沒發現要修改start.S,所以有點找不到方向,後來不知道為甚麼編譯不過,才發現必須在start.S新增要實作的system call。