操作系统课程设计

  • 格式:doc
  • 大小:675.00 KB
  • 文档页数:25

课程设计说明书学院:计算机科学与工程学院专业:计算机科学与技术姓名:杨天驹学号: 07指导教师:黄廷辉2012年 3 月 5 日操作系统课程设计报告GeekOS操作系统的研究与实现(项目0-------项目2)一、实验目的:熟悉GeekOS项目编译运行环境、核态进程的实现、用户态进程的实现、进程调度策略和算法实现、分页存储管理的实现和文件系统的实现等。

二、项目设计要求:GeekOS设计项目0:1.搭建GeekOS的编译和调试平台,掌握GeekOS的内核进程工作原理。

2.熟悉键盘操作函数,编程实现一个内核进程。

该进程的功能是:接受键盘输入的字符并显示到屏幕上,当输入Ctrl+D时,结束进程的运行。

GeekOS设计项目1:1.修改/geekos/文件:在函数Parse_ELF_Executable()中添加代码,分析ELF格式的可执行文件(包括分析得出ELF文件头、程序头,获取可执行文件长度、代码段、数据段等信息),并填充Exe_Format数据结构中的域值。

2.掌握GeekOS在核心态运行用户程序的原理,为项目2的实现做准备。

GeekOS设计项目2:本项目要求用户对以下几个文件进行修改:1.src/GeekOS/文件中的函数Spawn(),其功能是生成一个新的用户级进程。

2.src/GeekOS/文件中的函数Switch_To_User_Context(),调度程序在执行一个新的进程前调用该函数以切换用户地址空间。

3.src/GeekOS/文件中的函数Parse_ELF_Executable()。

该函数的实现要求和项目1相同。

4.src/GeekOS/文件中主要是实现一些为实现对src/GeekOS/中高层操作支持的函数。

(1)Destroy_User_Context()函数的功能是释放用户态进程占用的内存资源。

(2)Load_User_Program()函数的功能是通过加载可执行文件镜像创建新进程的User_Context结构。

(3)Copy_From_User()和Copy_To_User()函数的功能是在用户地址空间和内核地址空间之间复制函数,在分段存储器管理模式下,只要段有效,调用memcpy函数就可以实现这两个函数的功能。

(4)Switch_To_Address_Space()函数的功能是通过将进程的LDT装入到LDT寄存器来激活用户的地址空间。

5. src/GeekOS/文件中的Start_User_Thread函数和Setup_User_Thread函数。

(1)Setup_User_Thread()函数的功能是为进程初始化内核堆栈,堆栈中是为进程首次进入用户态运行时设置处理器状态要使用的数据。

(2)Start_User_Thread()是一个高层操作,该函数使用User_Context 对象开始一个新进程6. src/GeekOS/文件中主要是实现用户程序要求内核进行服务的一些系统调用函数定义。

要求用户实现的有Sys_Exit()函数、Sys_PrintString()函数、Sys_GetKey()、Sys_SetAttr()、Sys_Getcursor()、Sys_PutCursor()函数、Sys_Wait()函数和Sys_GetPID()函数。

这些函数在文件中有详细的注释,按照提示用户可以很好实现它们的功能。

最后,需要在文件中改写生成第一个用户态进程的函数调用:Spawn_Init_Process(void)。

需要注意的是:作为与用户沟通的界面,GeekOS 提供了一个简单的Shell,保存在PFAT文件系统内,所以GeekOS系统启动后,应启动shell程序/c/运行,所以需要将/c/作为可执行文件传递给Spawn 函数的program参数,创建第一个用户态进程,然后由它来创建其他进程。

添加代码运行成功后,GeekOS就可以挂载shell,并能运行测试文件和。

三、如何建立开发环境:(一)利用linux安装盘安装了版本的linux操作系统环境;(二)联网后通过系统里的更新管理器更新了系统,并安装了语言包和必要的驱动。

(三)在ubuntu软件中心下载安装了NASM汇编器、Bochs PC模拟器以及bochs-x插件(保证环境下的bochs正常运行)。

四、项目设计原理:Make工作原理:在默认的方式下,只要输入make命令就可以工作。

具体的处理过程如下:(1)make会在当前目录下找文件名为“Makefile”或“makefile”的文件。

(2)如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,它会找到“edit”这个文件,并把这个文件作为最终的目标文件。

(3)如果edit文件不存在,或是edit所依赖的后面的.o文件的修改时间要比edit这个文件新,那么,就会执行后面所定义的命令来生成edit这个文件。

(4)如果edit所依赖的.o文件也不存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件(这有点像一个堆栈的过程)。

(5)如果指定的C文件和H文件是存在的, make会生成.o文件,然后再用.o文件生成make的最终任务,也就是链接生成执行文件edit。

GeekOS的makefile文件功能:(1)指定GeekOS如何被编译,哪些源文件被编译,哪些用户程序被编译等等。

通常不同项目的编译仅仅需要修改这一部分。

(2)定义了编译GeekOS要用到的工具程序。

(3)指定规则:描述系统如何编译源程序。

(4)指定系统编译生成的指定文件。

GeekOS项目的开发流程:1.开始一个GeekOS项目,第一步是添加相应的代码。

2.在Linux下利用make命令编译系统,生成系统镜像文件。

①$ cd ……/project0/build②$ make depend③$ make3.编写每个项目相应的Bochs的配置文件。

4.运行Bochs模拟器,执行GeekOS内核。

①$ cd ……/bochs②$ bochs③运行后,屏幕上会有一些提示。

运行GeekOS选择Beginsimulation,如果GeekOS 编译成功,并且bochs的配置也没问题,将会看到一个模拟VGA的文本窗口,Geekos就能运行程序输出相应信息(每个环境具体运行的命令格式会有一些不同)内核线程的建立流程:用户态进程创建流程五、项目设计的具体实现(程序代码):GeekOS设计项目0:文件:#include <geekos/>#include <geekos/>#include <geekos/>#include <geekos/>#include <geekos/>#include <geekos/>#include <geekos/>#include <geekos/>#include <geekos/>#include <geekos/>#include <geekos/>/** Kernel C code entry point.* Initializes kernel subsystems, mounts filesystems,* and spawns init process.*/void Main(struct Boot_Info* bootInfo){Init_BSS();Init_Screen();Init_Mem(bootInfo);Init_CRC32();Init_TSS();Init_Interrupts();Init_Scheduler();Init_Traps();Init_Timer();Init_Keyboard();Set_Current_Attr(ATTRIB(BLACK, GREEN|BRIGHT));Print("Welcome to GeekOS!\n");Set_Current_Attr(ATTRIB(BLACK, GRAY));void EchoCount(){Keycode keycode;int count;count=0;while (1){if ( Read_Key( &keycode ) ){if((keycode & 0x4000) == 0x4000){if((Wait_For_Key() & 0x00ff) == 'd'){*/Exit(0);}GeekOS设计项目1:文件:#include <geekos/>#include <geekos/>#include <geekos/>#include <geekos/> /* for debug Print() statements */#include <geekos/>#include <geekos/>#include <geekos/>#include <geekos/>/*** From the data of an ELF executable, determine how its segments* need to be loaded into memory.* @param exeFileData buffer containing the executable file* @param exeFileLength length of the executable file in bytes* @param exeFormat structure describing the executable's segments* and entry address; to be filled in* @return 0 if successful, < 0 on error*/int Parse_ELF_Executable(char *exeFileData, ulong_t exeFileLength,struct Exe_Format *exeFormat){/* TODO("Parse an ELF executable image");*/int i;elfHeader *head=(elfHeader*)exeFileData;programHeader *proHeader=(programHeader *)(exeFileData+head->phoff);KASSERT(exeFileData!=NULL);KASSERT(exeFileLength>head->ehsize+head->phentsize*head->phnum);KASSERT(head->entry%4==0);exeFormat->numSegments=head->phnum;exeFormat->entryAddr=head->entry;for(i=0;i<head->phnum;i++){exeFormat->segmentList[i].offsetInFile=proHeader->offset;exeFormat->segmentList[i].lengthInFile=proHeader->fileSize;exeFormat->segmentList[i].startAddress=proHeader->vaddr;exeFormat->segmentList[i].sizeInMemory=proHeader->memSize;exeFormat->segmentList[i].protFlags=proHeader->flags;proHeader++;return 0;}GeekOS设计项目2:Src/GeekOS/文件中的函数Spawn():int Spawn(const char *program, const char *command, struct Kernel_Thread **pThread){/** Hints:* - Call Read_Fully() to load the entire executable into a memory buffer* - Call Parse_ELF_Executable() to verify that the executable is * valid, and to populate an Exe_Format data structure describing * how the executable should be loaded* - Call Load_User_Program() to create a User_Context with the loaded * program* - Call Start_User_Thread() with the new User_Context** If all goes well, store the pointer to the new thread in* pThread and return 0. Otherwise, return an error code.*/int rc;char *exeFileData = 0;ulong_t exeFileLength;struct User_Context *userContext = 0;struct Kernel_Thread *process = 0;struct Exe_Format exeFormat;/** Load the executable file data, parse ELF headers,* and load code and data segments into user memory.*/if ((rc = Read_Fully(program, (void**) &exeFileData, &exeFileLength)) != 0 ||(rc = Parse_ELF_Executable(exeFileData, exeFileLength, &exeFormat)) != 0 ||(rc = Load_User_Program(exeFileData, exeFileLength, &exeFormat, command, &userContext)) != 0)goto fail;/** User program has been loaded, so we can free the* executable file data now.*/Free(exeFileData);exeFileData = 0;/* Start the process! */process = Start_User_Thread(userContext, false);if (process != 0) {KASSERT(process->refCount == 2);/* Return Kernel_Thread pointer */*pThread = process;} elserc = ENOMEM;return rc;fail:if (exeFileData != 0)Free(exeFileData);if (userContext != 0)Destroy_User_Context(userContext);return rc;}Src/GeekOS/文件中的函数Switch_To_User_Context():void Switch_To_User_Context(struct Kernel_Thread* kthread, struct Interrupt_State* state){/** Hint: Before executing in user mode, you will need to call* the Set_Kernel_Stack_Pointer() and Switch_To_Address_Space()* functions.*/static struct User_Context* s_currentUserContext; /* last user context used */struct User_Context* userContext = kthread->userContext;/** FIXME: could avoid resetting ss0/esp0 if not returning* to user space.*/KASSERT(!Interrupts_Enabled());if (userContext == 0) {/* Kernel mode thread: no need to switch address space. */ return;}/* Switch only if the user context is indeed different */if (userContext != s_currentUserContext) {ulong_t esp0;/* Switch to address space of user context */Switch_To_Address_Space(userContext);/** By definition, when returning to user mode there is no * context remaining on the kernel stack.*/esp0 = ((ulong_t) kthread->stackPage) + PAGE_SIZE;/* Change to the kernel stack of the new process. */Set_Kernel_Stack_Pointer(esp0);/* New user context is active */s_currentUserContext = userContext;}}src/GeekOS/文件中的函数Parse_ELF_Executable():int Parse_ELF_Executable(char *exeFileData, ulong_t exeFileLength, struct Exe_Format *exeFormat){elfHeader *hdr;programHeader *phdr;int i;hdr = (elfHeader *) exeFileData;/** FIXME: when checking offsets, we really ought to be* checking overflow cases. Need to use functions from* (which needs to be implemented, too)*/if (exeFileLength < sizeof(elfHeader) ||strncmp(exeFileData, "\x7F""ELF", 4) != 0) {if (elfDebug) Print("Not an ELF executable\n");return ENOEXEC;}if (hdr->phnum > EXE_MAX_SEGMENTS) {if (elfDebug) Print("Too many segments (%d) in ELF executable\n", hdr->phnum);return ENOEXEC;}if (exeFileLength < hdr->phoff + (hdr->phnum * sizeof(programHeader))) {if (elfDebug) Print("Not enough room for program header\n"); return ENOEXEC;}exeFormat->numSegments = hdr->phnum;exeFormat->entryAddr = hdr->entry;phdr = (programHeader *) (exeFileData + hdr->phoff);for (i = 0; i < hdr->phnum; ++i) {struct Exe_Segment *segment = &exeFormat->segmentList[i];/** Fill in segment offset, length, address* FIXME: should check that segments are valid*/segment->offsetInFile = phdr[i].offset;segment->lengthInFile = phdr[i].fileSize;segment->startAddress = phdr[i].vaddr;segment->sizeInMemory = phdr[i].memSize;if (segment->lengthInFile > segment->sizeInMemory) {if (elfDebug) Print("Segment %d: length in file (%lu) exceeds size in memory (%lu)\n",i, segment->lengthInFile, segment->sizeInMemory);return ENOEXEC;}}/* Groovy */return 0;}src/GeekOS/文件中的函数Destroy_User_Context()void Destroy_User_Context(struct User_Context* userContext){/** Hints:* - you need to free the memory allocated for the user process* - don't forget to free the segment descriptor allocated* for the process's LDT*/*/size = Round_Up_To_Page(maxva) + DEFAULT_USER_STACK_SIZE;argBlockAddr = size;size += argBlockSize;/* Create User_Context */userContext = Create_User_Context(size);if (userContext == 0)return -1;/* Load segment data into memory */for (i = 0; i < exeFormat->numSegments; ++i) {struct Exe_Segment *segment = &exeFormat->segmentList[i];memcpy(userContext->memory + segment->startAddress,exeFileData + segment->offsetInFile,segment->lengthInFile);}/* Format argument block */Format_Argument_Block(userContext->memory + argBlockAddr, numArgs, argBlockAddr, command);/* Fill in code entry point */userContext->entryAddr = exeFormat->entryAddr;/** Fill in addresses of argument block and stack* (They happen to be the same)*/userContext->argBlockAddr = argBlockAddr;userContext->stackPointerAddr = argBlockAddr;*pUserContext = userContext;return 0;}src/GeekOS/文件中的函数Setup_User_Thread()void Setup_User_Thread(struct Kernel_Thread* kthread, struct User_Context* userContext) {/** Hints:* - Call Attach_User_Context() to attach the user context* to the Kernel_Thread* - Set up initial thread stack to make it appear that* the thread was interrupted while in user mode* just before the entry point instruction was executed* - The esi register should contain the address of* the argument block*//** Interrupts in user mode MUST be enabled.* All other EFLAGS bits will be clear.*/ulong_t eflags = EFLAGS_IF;unsigned csSelector = userContext->csSelector;unsigned dsSelector = userContext->dsSelector;Attach_User_Context(kthread, userContext);/** Make the thread's stack look like it was interrupted* while in user mode.*//* Stack segment and stack pointer within user mode. */Push(kthread, dsSelector); /* user ss */Push(kthread, userContext->stackPointerAddr); /* user esp *//* eflags, cs, eip */Push(kthread, eflags);Push(kthread, csSelector);Push(kthread, userContext->entryAddr);Print("Entry addr=%lx\n", userContext->entryAddr);/* Push fake error code and interrupt number. */Push(kthread, 0);Push(kthread, 0);/** Push initial values for general-purpose registers.* The only important register is esi, which we use to* pass the address of the argument block.*/Push(kthread, 0); /* eax */Push(kthread, 0); /* ebx */Push(kthread, 0); /* edx */Push(kthread, 0); /* edx */Push(kthread, userContext->argBlockAddr); /* esi */Push(kthread, 0); /* edi */Push(kthread, 0); /* ebp *//* Push initial values for the data segment registers. */Push(kthread, dsSelector); /* ds */Push(kthread, dsSelector); /* es */Push(kthread, dsSelector); /* fs */Push(kthread, dsSelector); /* gs */}src/GeekOS/文件中的函数Start_User_Thread()struct Kernel_Thread*Start_User_Thread(struct User_Context* userContext, bool detached) {/** Hints:* - Use Create_Thread() to create a new "raw" thread object * - Call Setup_User_Thread() to get the thread ready to* execute in user mode* - Call Make_Runnable_Atomic() to schedule the process* for execution*/struct Kernel_Thread* kthread = Create_Thread(PRIORITY_USER, detached);if (kthread != 0) {/* Set up the thread, and put it on the run queue */Setup_User_Thread(kthread, userContext);Make_Runnable_Atomic(kthread);}return kthread;}src/GeekOS/文件:#include <geekos/>#include <geekos/>#include <geekos/>#include <geekos/>#include <geekos/>#include <geekos/>#include <geekos/>#include <geekos/>#include <geekos/>#include <geekos/>#include <geekos/>#include <geekos/>/** Allocate a buffer for a user string, and* copy it into kernel space.* Interrupts must be disabled.*/static int Copy_User_String(ulong_t uaddr, ulong_t len, ulong_t maxLen, char **pStr){int rc = 0;char *str;/* Ensure that string isn't too long. */if (len > maxLen)return EINVALID;/* Allocate space for the string. */str = (char*) Malloc(len+1);if (str == 0) {rc = ENOMEM;goto done;}/* Copy data from user space. */if (!Copy_From_User(str, uaddr, len)) {rc = EINVALID;Free(str);goto done;}str[len] = '\0';/* Success! */*pStr = str;done:return rc;}/** Null system call.* Does nothing except immediately return control back * to the interrupted user program.* Params:* state - processor registers from user mode** Returns:* always returns the value 0 (zero)*/static int Sys_Null(struct Interrupt_State* state){return 0;}/** Exit system call.* The interrupted user process is terminated.* Params:* state->ebx - process exit code* Returns:* Never returns to user mode!*/static int Sys_Exit(struct Interrupt_State* state){Exit(state->ebx);}/** Print a string to the console.* Params:* state->ebx - user pointer of string to be printed* state->ecx - number of characters to print* Returns: 0 if successful, -1 if not*/static int Sys_PrintString(struct Interrupt_State* state){int rc = 0;uint_t length = state->ecx;uchar_t* buf = 0;if (length > 0) {/* Copy string into kernel. */if ((rc = Copy_User_String(state->ebx, length, 1023, (char**) &buf)) != 0)goto done;/* Write to console. */Put_Buf(buf, length);}done:if (buf != 0)Free(buf);return rc;}/** Get a single key press from the console.* Suspends the user process until a key press is available.* Params:* state - processor registers from user mode* Returns: the key code*/static int Sys_GetKey(struct Interrupt_State* state){return Wait_For_Key();}/** Set the current text attributes.* Params:* state->ebx - character attributes to use* Returns: always returns 0*/static int Sys_SetAttr(struct Interrupt_State* state){* Params:* state->ebx - pointer to user int where row value should be stored * state->ecx - pointer to user int where column value should be stored * Returns: 0 if successful, -1 otherwise*/static int Sys_GetCursor(struct Interrupt_State* state){int row, col;Get_Cursor(&row, &col);if (!Copy_To_User(state->ebx, &row, sizeof(int)) ||!Copy_To_User(state->ecx, &col, sizeof(int)))return -1;return 0;}/** Set the current cursor position.* Params:* state->ebx - new row value* state->ecx - new column value* Returns: 0 if successful, -1 otherwise*/static int Sys_PutCursor(struct Interrupt_State* state){* Params:* state->ebx - user address of name of executable* state->ecx - length of executable name* state->edx - user address of command string* state->esi - length of command string* Returns: pid of process if successful, error code (< 0) otherwise */static int Sys_Spawn(struct Interrupt_State* state){int rc;char *program = 0;char *command = 0;struct Kernel_Thread *process;/* Copy program name and command from user space. */if ((rc = Copy_User_String(state->ebx, state->ecx, VFS_MAX_PATH_LEN, &program)) != 0 ||(rc = Copy_User_String(state->edx, state->esi, 1023, &command)) != 0)goto done;Enable_Interrupts();/** Now that we have collected the program name and command string * from user space, we can try to actually spawn the process.*/rc = Spawn(program, command, &process);if (rc == 0) {KASSERT(process != 0);rc = process->pid;}Disable_Interrupts();done:if (program != 0)Free(program);if (command != 0)Free(command);return rc;}/** Wait for a process to exit.* Params:* state->ebx - pid of process to wait for* Returns: the exit code of the process,* or error code (< 0) on error*/static int Sys_Wait(struct Interrupt_State* state){int exitCode;struct Kernel_Thread *kthread = Lookup_Thread(state->ebx); if (kthread == 0)return -12;Enable_Interrupts();exitCode = Join(kthread);Disable_Interrupts();return exitCode;}/** Get pid (process id) of current thread.* Params:* state - processor registers from user mode* Returns: the pid of the current thread*/static int Sys_GetPID(struct Interrupt_State* state){return g_currentThread->pid;}/** Global table of system call handler functions.*/const Syscall g_syscallTable[] = {Sys_Null,Sys_Exit,Sys_PrintString,Sys_GetKey,Sys_SetAttr,Sys_GetCursor,Sys_PutCursor,Sys_Spawn,Sys_Wait,Sys_GetPID,};/** Number of system calls implemented.*/const int g_numSyscalls = sizeof(g_syscallTable) / sizeof(Syscall);src/GeekOS/文件:#include <geekos/>#include <geekos/>#include <geekos/>#include <geekos/>#include <geekos/>#include <geekos/>#include <geekos/>#include <geekos/>#include <geekos/>#include <geekos/>#include <geekos/>#include <geekos/>#include <geekos/>#include <geekos/>#include <geekos/>#include <geekos/>#include <geekos/>/** Define this for a self-contained boot floppy* with a PFAT filesystem. (Target "" in* the makefile.)*//*#define FD_BOOT*/#ifdef FD_BOOT# define ROOT_DEVICE "fd0"# define ROOT_PREFIX "a"#else# define ROOT_DEVICE "ide0"# define ROOT_PREFIX "c"#endif#define INIT_PROGRAM "/" ROOT_PREFIX "/"static void Mount_Root_Filesystem(void);static void Spawn_Init_Process(void);/** Kernel C code entry point.* Initializes kernel subsystems, mounts filesystems, * and spawns init process.*/void Main(struct Boot_Info* bootInfo){Init_BSS();Init_Screen();Init_Mem(bootInfo);Init_CRC32();Init_TSS();Init_Interrupts();Init_Scheduler();Init_Traps();Init_Timer();Init_Keyboard();Init_DMA();Init_Floppy();Init_IDE();Init_PFAT();Mount_Root_Filesystem();Set_Current_Attr(ATTRIB(BLACK, GREEN|BRIGHT));Print("Welcome to GeekOS!\n");Set_Current_Attr(ATTRIB(BLACK, GRAY));Spawn_Init_Process();/* Now this thread is done. */Exit(0);}static void Mount_Root_Filesystem(void){if (Mount(ROOT_DEVICE, ROOT_PREFIX, "pfat") != 0)Print("Failed to mount /" ROOT_PREFIX " filesystem\n");elsePrint("Mounted /" ROOT_PREFIX " filesystem!\n");}static void Spawn_Init_Process(void){int rc;struct Kernel_Thread *initProcess;/* Load and run , the "init" process */Print("Spawning init process (%s)\n", INIT_PROGRAM);rc = Spawn(INIT_PROGRAM, INIT_PROGRAM, &initProcess);if (rc != 0) {Print("Failed to spawn init process: error code = %d\n",rc); } else {/* Wait for it to exit */int exitCode = Join(initProcess);Print("Init process exited with code %d\n", exitCode);}}六、系统编译运行的原理及结果:Make工作原理:在默认的方式下,只要输入make命令就可以工作。