《Greekos操作系统实验.doc》由会员分享,可在线阅读,更多相关《Greekos操作系统实验.doc(20页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、计算机系统实验软件报告题 目: GeekOS操作系统的研究与实现院 (系): 研究生学院 专 业: 计算机应用技术 学生姓名: 李 敏 学 号: 092031137 指导教师: 黄 廷 辉 题目类型: 理论研究 实验研究 工程设计 工程技术研究 软件开发2010年7月2日目录1 实验目的42 项目设计要求43 如何建立开发环境54 项目设计原理55 项目设计的具体实现(编写的代码)96 系统编译运行的原理及结果197 遇到问题及解决方法218 实验总结211 实验目的 项目0:熟悉GeekOS的项目编译、调试和运行环境,掌握GeekOS运行工作过程。项目1:熟悉ELF文件格式,了解GeekOS
2、系统如何将ELF格式的用户可执行程序装入到内存,建立内核进程并运行的实现技术。项目2: 扩充GeekOS操作系统内核, 使得系统能够支持用户级进程的动态创建和执行。2 项目设计要求项目0:(1)搭建GeekOS的编译和调试平台,掌握GeekOS的内核进程工作原理。(2)熟悉键盘操作函数,编程实现一个内核进程。该进程的功能是:接收键盘输入的 字符并显示到屏幕上,当输入Ctrl+D时,结束进程的运行。项目1:(1)修改/geekos/elf.c文件:在函数Parse_ELF_Executable( )中添加代码,分析ELF格式的可执行文件(包括分析得出ELF文件头、程序头,获取可执行文件长度,代码
3、段、数据段等信息),并填充Exe_Format数据结构中的域值。 (2) 掌握GeekOS在核心态运行用户程序的原理,为项目2的实现做准备。项目2:开始本项目前需要阅读/src/geekos目录中的以下程序:(1)Entry.c: 用户程序外壳,用户程序的入口地址就在这里,此文件在编译时与用户程序一起编译。(2)Lowlevel.asm:其中Handle Interrupt是中断处理的总调度程序,该函数根据传递的中断向量查找并调用相关的中断处理程序,并实现调度进程的选择。Switch_To_Thread函数用于实现进程的切换。(3)Kthread.c:内核进程有关函数以及进程调度算法都在此实现
4、。(4)Userseg.c:其中要关注的函数有Destroy_User_Context ()函数功能是释放User_Context空间,Detach_User_Context()调用该函数。Load_User_Program()函数功能是对用户进程的User_Context结构初始化,并对用户态进程的初始化,Spawn()函数中调用该函数。阅读完毕并对系统有一定的了解后就可以开始编写扩充部分的代码,本项目要求用户对/src/geekos/中的文件进行以下修改:(1)user.c:完成函数Spawn(),其功能是生成一个新的用户级进程;还有函数Switch_To_User_Context(),功
5、能是调度程序在执行一个新的进程前调用该函数以切换用户地址空间。(2)elf.c:完成函数Parse_ELF_Executable(),要求与项目1相同。(3)userseg.c: 完成函数Destroy_User_Context(),释放用户态进程占用的内存资源;函数Load_User_Program()的功能是通过加载可执行文件镜像创建新进程的User_Context结构;Copy_From_User()和Copy_To_User()函数的功能是在用户地址空间和内核地址空间之间复制数据;Switch_To_Address_Space()函数的功能是通过将进程的LDT装入到LDT寄存器来激活用
6、户的地址空间。(4)kthread.c:完成函数Setup_User_Thread()和Start_User_Thread()。(5)syscall.c:完成函数Sys_Exit()、Sys_PrintString()、Sys_GetKey()、Sys_SetAttr()、Sys_GetCursor()、Sys_PutCursor()、Sys_Spawn()、Sys_Wait()和Sys_GetPID()。(6)main.c:改写Spawn_Init_Process(void),改写时将“/c/shell.exe”作为可执行文件传递给Spawn函数的program参数,创建第一个用户态进程,然
7、后由它来创建其它进程。添加代码运行成功后,GeekOS就可以挂载shell,并能运行测试文件c.exe和b.exe。3 如何建立开发环境GeekOS项目设计的第一步是要搭建系统开发环境,在开始编写程序代码前,还需要熟悉一些开发工具的安装和使用,掌握一些调试技巧,这样做可以让以后的工作得到事半功倍的效果。(1)在PC机上直接安装Linux进行开发调试;(2)将下载好的GeekOS-0.3.0解压到/home/username/geekos-0.3.0目录(3)Linux系统会自动安装gcc编译器,但默认安装的gcc4.3,geekos 0.3.0会有一大堆错误,这里建议使用 gcc3.4来编译。
8、打开终端输入 sudo apt-get install gcc-3.4命令,安装好gcc3.4包之后,输入ls /usr/bin/gcc* l命令,我们从终端中显示的信息可以判断系统中安装有3.4和4.3版本的gcc,然后删除原有gcc连接,输入sudo rm gcc命令,再输入sudo ln s gcc-3.4 gcc,创建新的gcc连接,gcc编译器主要用于编译C语言程序代码;(4)安装NASM,在终端输入sudo apt-get install nasm,即可安装成功,它主要用来编译汇编语言程序代码;(5)安装Bochs,在终端依次输入sudo apt-get install bochs
9、和 sudo apt-get install bochs-x;(6)GNU gdb调试器:用来查找程序代码错误,Linux会系统自动安装;(7)Bochs PC模拟器:用来运行GeekOS系统。在Linux系统中需先解压软件包,然后再配置编译生成系统文件,为了模拟一台计算机执行一个操作系统软件,bochs需要几个文件来代替 PC机硬件的不同部分:bochs模拟器程序本身;BIOS-bochs-lastest模拟bochs硬件的BIOS;VGABIOS-lgpl-lastest模拟bochs显示系统的BIOS;bochsrc.txt描述模拟器硬件配置的配置文件;disk image(.img)包
10、含了一个模拟器能引导的操作系统镜像。 4 项目设计原理项目0:项目0的实现主要由以下步骤完成(在项目0的/src/geekos/main.c中完成):(1)编写一个C语言函数,函数功能是:接收键盘输入的按键,并将键值在显示器显示出来,当输入ctrl+d就退出;(2)在Main函数体内调用Start_Kernel_Thread函数,将步骤1编写的函数地址传递给参数startFunc,利用Setup_Kernel_Thread函数建立一个待运行的线程。 (3)在Linux环境下编译系统得到GeekOS镜像文件,编写一个相应的bochs配置文件,在bochs中运行GeekOS系统显示结果。键盘设备驱
11、动程序提供了一系列的高级接口来使用键盘。键盘事件的逻辑关系为:用户按键引发键盘中断,根据是否按下Shift键,分别在键值表中寻找扫描码对应的按键值,经过处理后将键值放入键盘缓冲区s_queue中,最后通知系统重新调度进程。若用户进程需要从键盘输入信息,可调用Wait_For_Key()函数,该函数首先检查键盘缓冲区是否有按键。如果有,就读取一个键码,如果此时键盘缓冲区中没有按键,就将进程放入键盘事件等待队列s_waitQueue,由于按键触发了键盘中断,键盘中断处理函数Keyboard_Interrupt_Handler就会读取用户按键,将低级键扫描码转换为含ASCII字符的高级代码,并刷新键
12、盘缓冲区,最后唤醒等待按键的进程继续运行。项目1:ELF文件格式:表1 ELF目标文件格式连接程序视图 执行程序视图 ELF 头部ELF 头部 程序头部表(可选) 程序头部表 节区1 段 1 . 节区 n 段 2 . . . 节区头部表 节区头部表(可选) ELF文件是x86 Linux系统下的一种常用目标文件(object file)格式,有三种主要类型:(1) 适于连接的可重定位文件(relocatable file),可与其它目标文件一起创建可执行文件和共享目标文件。 (2) 适于执行的可执行文件(executable file),用于提供程序的进程映像,加载的内存执行。 (3) 共享目
13、标文件(shared object file),连接器可将它与其它可重定位文件和共享目标文件连接成其它的目标文件,动态连接器又可将它与可执行文件和其它共享目标文件结合起来创建一个进程映像。为了方便和高效,ELF文件内容有两个平行的视图:一个是程序连接角度,另一个是程序运行角度。GeekOS中的用户程序全部在系统的编译阶段完成编译和连接,形成可执行文件,用户可执行文件保存在PFAT文件系统中。内存中的可执行文件镜像: 内核线程的建立流程:Spawn_Init_Process() Start_Kernel_Thread()Spawner()Read_Fully()Parse_ELF_Excutab
14、le()Spawn_Program()本项目要完成的就是在系统启动后,从PFAT文件系统将可执行文件装入内存,建立进程并运行得到相应的输出。此过程主要由Spawner函数实现,先调用Read_Fully函数将文件读入内存,后调用Parse_ELF_Executable函数分析ELF文件,最后调用Spawn_Program函数将可执行程序的代码段和数据段等装入内存,此后便可以开始运行一个内核级进程了。在本项目中,我们要完成Parse_ELF_Executable函数,此函数的作用为根据ELF文件格式,从exeFileData指向的内容中得到ELF文件头,继续分析可得到程序头和程序代码段等信息。项
15、目2:进程是可并发执行的程序在某个数据集合上的一次计算活动,也是操作系统资源分配和保护的基本单位。进程和程序有着本质的区别,程序是一些能保存在磁盘上的指令的有序集合,没有任何执行的概念;而进程是程序执行的过程,包括了创建、调度和消亡的整个过程。因此,对系统而言,当用户在系统中输入命令执行一个程序时,它将启动一个进程。在GeekOS中,进程的执行过程分为运行态、就绪态和等待态。GeekOS为不同状态的进程准备了不同的进程队列(Thread_Queue)。如果一个进程正处于就绪态,就会在队列s_runQueue中出现;如果一个进程处于等待态,就会在s_reaperWaitQueue队列中出现;如果
16、一个进程准备被销毁,就会在s_graveyardQueue队列中出现。由于处于运行态的进程最多只能有一个,所以没有队列,由指针g_currentThread指向此进程。系统中每个进程有且仅有一个进程控制块(PCB),它记录了有关进程的所有信息,GeekOS的PCB用数据结构Kernel_Thread来表示。GeekOS最早创建的内核级进程是Idle、Reaper和Main。GeekOS在几种情况下会进行进程切换:一是时间片用完时;二是执行进程Idle时;三是进程退出调用Exit函数时;四是进程进入等待态调用Wait函数时。用户进程切换通过Switch_To_User_Context函数实现,此
17、函数负责检测当前进程是否为用户级进程,若是就切换至用户进程空间,它由我们自己实现。在GeekOS中为了区分用户级进程与内核级进程,在Kernel_Thread结构体中设置了一个字段userContext,它指向用户态进程上下文。对于内核级进程来说,此指针为空。因此,要判断一个进程是用户级的还是内核级的,只要判断userContext字段是否为空就行了。新建和注销User_Context结构的函数分别是Create_User_Context函数和Destroy_User_Context函数,它们都由我们自己实现。每个用户态进程都要占用一段物理上连续的内存空间,存储用户级进程的数据和代码。所以,为
18、了实现存取访问控制,每个用户级进程都有属于自己的内存段空间,每一个段有一个段描述符,并且每一个进程都有一个段描述符表(LDT),它用于保存此进程的所有段描述符。为用户级进程创建LDT的步骤是: (1) 调用Allocate_Segment_Descriptor()新建一个LDT描述符; (2) 调用Selector()新建一个LDT选择子; (3) 调用Init_Code_Segment_Descriptor()新建一个文本段描述符; (4) 调用Init_Data_Segment_Descriptor()新建一个数据段描述符; (5) 调用Selector()新建一个数据段选择子; (6)
19、调用Selector()新建一个文本段选择子。从以上描述可知,用户态进程除了需要数据段和代码段用于装入可执行文件和数据外,还需要另外两个数据结构的存储空间,一个是进程的堆栈,另一个是参数块数据结构。在用户态进程首次被调度前,系统必须初始化用户态进程的堆栈,使之看上去像进程刚被中断运行一样,因此需要使用Push函数将以下数据压入堆栈:数据选择子、堆栈指针、Eflags、文本选择子、程序计数器、错误代码、中断号、通用寄存器、DS寄存器、ES寄存器、FS寄存器和GS寄存器。GeekOS系统初始内核不支持用户态进程,因此我们必须创建用户态进程。GeekOS的用户态进程创建过程可以描述如下:(1) Sp
20、awn函数导入用户程序并初始化:调用Load_User_Program进行User_Context的初始化及用户态进程空间的分配及用户程序各段的装入; (2) Spawn函数调用Start_User_Thread(),初始化一个用户态进程,包括初始化进程Kernel_Thread结构以及调用Setup_User_Thread初始化用户态进程的内核堆栈;Set_User_Thread 内部需要调用Attach_User_Context加载用户上下文。(3) 最后Spawn函数退出,这时用户级进程已被添加至系统运行进程队列,可以被调度了。5 项目设计的具体实现(编写的代码)项目0:在main.c中
21、新加个函数,命名为projecto,函数的代码如下:Void project0 () Print (To Exit hit Ctrl + d.n); Keycode keycode; While (1) if( Read Key(&keycode) ) /读取键盘按键状态 if (! ( (keycode & KEY_SPECIAL_FLAG) | (keycode & KEY_RELEASE_FLAG) ) /只处理非特殊按键的按下事件 in asciiCode = keycode & 0xff; /低8位为ASCII码 if( (keycode & KEY_CTRL_FLAG)=KEY_C
22、TRL_FLAG & asciiCode=d) /按下Ctrl键 Print (n-BYE!-n); Exit (1); else Print (%c, (asciiCode=r)? n: asciiCode); 再修改Main函数,将TODO(“.”)这一行替换为以下代码:Struct Kernel_Thread *thread;thread = Start_Kernel_Thread (&project0, 0, PRIORITY_NORMAL, false); 项目1:在函数Prase_ELF_Executable()中添加代码:int Parse_ELF_Executable (cha
23、r *exeFileData, ulong_t exeFileLength,struct Exe_Format *exeFormat) int i;elf Header *hdr = (elf Header*) exeFileData;program Header *phdr= (program Header *) (exeFileData + hdr-phoff);struct Exe_Segment * segment= exeFormat-segment List; for (i=0; iphnum; i+) segment-offsetInFile = phdr-offset; seg
24、ment-lengthInFile = phdr-fileSize; segment-start Address = phdr-vaddr; segment-sizeInMemory = phdr-memSize; phdr+; segment+; exeFormat-numSegments = hdr-phnum; exeFormat-entryAddr = hdr-entry; return 0; 项目2:对以下几个文件进行修改:(1)/src/geekos/user.c/函数功能:生成一个新的用户级进程,若成功则返回0,否则返回错误信息int Spawn (const char *pro
25、gram, const char *command, struct Kernel_Thread *pThread) /标记各函数的返回值,为0则表示成功,否则失败 int result; /保存在内存缓冲中的用户程序可执行文件 char *exeFileData = 0; /可执行文件的长度 ulong_t exeFileLength; /调用Parse_ELF_Executable函数得到的可执行文件信息 struct Exe_Format exeFormat; /指向User_Conetxt的指针 struct User_Context *userContext = 0; /指向Kerne
26、l_Thread *pThread的指针 struct Kernel_Thread *pNewThread = 0; /调用Read_Fully函数将名为program的可执行文件全部读入内存缓冲区 if (result = Read_Fully (program, (void *) &exeFileData, &exeFileLength) != 0) Print (Failed to Read File %s! n, program); goto fail; /调用Parse_ELF_Executable函数分析ELF格式文件 if (result = Parse_ELF_Executab
27、le (exeFileData, exeFileLength, &exeFormat) != 0) Print (Failed to Parse ELF File! n); goto fail; /调用Load_User_Program将可执行程序的程序段和数据段装入内存 if (result = Load_User_Program (exeFileData, exeFileLength, &exeFormat, command, &userContext)! = 0) Print (Failed to Load User Program! n); goto fail; /在堆分配方式下释放内
28、存并再次初始化exeFileData Free (exeFileData); ExeFileData = 0; /调用Start_User_Thread函数创建一个进程并使其进入准备运行队列 PNewThread = Start_User_Thread (user Context, false); /不是核心级进程(即为用户级进程) if (pNewThread != 0) *pThread = pNewThread; /记录当前进程的ID result = pNewThread-pid; else /project2includegeekoserrno.h /超出内存空间 result =
29、ENOMEM; return result;/如果新进程创建失败则注销User_Context对象fail: if (exeFileData! = 0) /释放内存 Free (exeFileData); if (user Context!= 0) /销毁进程对象 Destroy_User_Context (user Context); return result;/函数功能:调度程序在执行一个新进程前调用此函数以切换用户地址空间void Switch_To_User_Context (struct Kernel_Thread* kthread, struct Interrupt_State*
30、 state) /指向User_Conetxt的指针,并初始化为准备切换的进程 struck User Context *user Context = kthread-user Context; /判断进程的类型,user Context为0表示此进程为核心态进程 /如果是核心态进程,就不用切换地址空间 if (0 = user Context) return; /为用户态进程时则切换地址空间 Switch_To_Address_Space (user Context); /设置内核堆栈指针 /PAGE_POWER=12 /PAGE_SIZE= (1stackPage+PAGE_SIZE);(
31、2)/src/geekos/elf.c(Parse_ELF_Executable函数的实现要求与项目1中的相同,可直接拷贝项目1的)(3)/src/geekos/userseg.c/函数功能:释放一个用户态进程所占用的资源void Destroy_User_Context (struct User_Context* userContext) /释放占用的LDT Free (userContext-ldtDescriptor); UserContext-ldtDescriptor=0; /释放内存空间 Free (userContext-memory); UserContext-memory=0
32、; /释放userContext本身占用的内存 Free (userContext); UserContext=0;/函数功能:通过加载可执行文件镜像创建新进程的User_Context结构/对用户态进程的User_Context结构初始化,并对用户态进程初始化int Load_User_Program (char *exeFileData, ulong_t exeFileLength,struct Exe_Format *exeFormat, const char *command,struct User_Context *pUserContext) ulong_t maxva = 0; /
33、要分配的最大内存空间 unsigned numArgs; /进程数目 ulong_t argBlockSize; /参数块的大小 ulong_t virtSize, argBlockAddr; /参数块地址 struct User_Context * UserContext = 0; int i; /计算用户态进程所需的最大内存空间 for (i = 0; i numSegments; i+) /elf.h struct Exe_Segment * segment = &exeFormat-segment Listi; ulong_t topva = segment-start Address
34、 + segment-sizeInMemory; if (topva maxva) maxva = topva; /获取参数块信息 Get_Argument_Block_Size(command, &numArgs, &argBlockSize); /用户进程大小=参数块总大小 + 进程堆栈大小(8192) virtSize = Round_Up_To_Page(maxva) + DEFAULT_USER_STACK_SIZE; argBlockAddr = virtSize; virtSize += argBlockSize; /按相应大小创建一个进程 UserContext = Creat
35、e_User_Context(virtSize); /如果为核心态进程 if (0 = UserContext) return -1; for (i = 0; i numSegments; +i) struct Exe_Segment *segment = &exeFormat-segment Listi; /根据段信息将用户程序中的各段内容复制到分配的用户内存空间 memcpy (UserContext-memory + segment-start Address, ExeFileData + segment-offsetInFile, segment-lengthInFile); /格式化
36、参数块Format_Argument_Block (UserContext-memory + argBlockAddr, numArgs, argBlockAddr, command); /初始化数据段,堆栈段及代码段信息 UserContext-argBlockAddr = argBlockAddr; UserContext-stackPointerAddr = argBlockAddr; UserContext-entryAddr = exeFormat-entryAddr; /将初始化完毕的User_Context赋给*pUserContext *pUserContext = UserC
37、ontext; return 0; /成功/函数功能:从用户地址空间把数据复制到内核地址空间中bool Copy_From_User(void* destInKernel, ulong_t srcInUser, ulong_t bufSize) struct User_Context * UserContext = g_currentThread-userContext; if (! Validate_User_Memory (UserContext, srcInUser, bufSize) return false; memcpy (destInKernel, UserContext-mem
38、ory + srcInUser, bufSize); return true;/函数功能:从内核地址空间把数据复制到用户地址空间中bool Copy_To_User (ulong_t destInUser, void* srcInKernel, ulong_t bufSize) struct User_Context * UserContext = g_currentThread-userContext; if (! Validate_User_Memory (UserContext, destInUser, bufSize) return false; memcpy (UserContext
39、-memory + destInUser, srcInKernel, bufSize); return true;/函数功能:通过将进程的LDT装入到LDT寄存器来激活用户的地址空间void Switch_To_Address_Space (struct User_Context *userContext) ushort_t ldtSelector = userContext-ldtSelector; _asm_ _volatile_ ( lldt %0 : : a (ldtSelector) );此外,还需在此文件中增加一个函数,此函数的功能是按给定的大小创建一个用户级进程上下文,具体实现如
40、下:/函数功能:按给定的大小创建一个用户级进程上下文static struct User_Context* Create_User_Context (ulong_t size) struct User_Context * UserContext; size = Round_Up_To_Page (size);UserContext = (struct User_Context *) Malloc (sizeof (struct User_Context); /为用户态进程 if (UserContext! = 0) UserContext-memory = Malloc (size); /为核
41、心态进程 else goto fail; /内存为空 if (0 = UserContext-memory) goto fail; memset (UserContext-memory, 0, size); UserContext-size = size; /以下为用户态进程创建LDT(段描述符表) /新建一个LDT描述符 UserContext-ldtDescriptor = Allocate_Segment_Descriptor (); if (0 = UserContext-ldtDescriptor) goto fail; /初始化段描述符 Init_LDT_Descriptor(Us
42、erContext-ldtDescriptor,UserContext-ldt, NUM_USER_LDT_ENTRIES); /新建一个LDT选择子 UserContext-ldtSelector=Selector (KERNEL_PRIVILEGE, true, Get_Descriptor_Index (UserContext-ldtDescriptor); /新建一个文本段描述符 Init_Code_Segment_Descriptor ( &UserContext-ldt 0, (ulong_t) UserContext-memory, size / PAGE_SIZE, USER_PRIVILEGE ); /新建一个数据段 Init_Data_Segment_Descriptor ( &UserCo