GeekOS操作系统课程设计报告完美版.docx
《GeekOS操作系统课程设计报告完美版.docx》由会员分享,可在线阅读,更多相关《GeekOS操作系统课程设计报告完美版.docx(30页珍藏版)》请在冰豆网上搜索。
GeekOS操作系统课程设计报告完美版
编号:
GeekOS操作系统的研究与实现
题目:
GeekOS操作系统的研究与实现
系别:
计算机科学与工程学院
专业:
计算机科学与技术
学生姓名:
学号:
指导教师:
注:
高分操作系统课程设计报告,当初为我加了不少分哦!
!
2012年3月17
1实验目的
计算机操作系统是管理计算机系统软件、硬件资源,控制程序运行,改善人机界面,提供各种服务,合理组织计算机工作流程和为用户有效使用计算机提供良好的运行环境的系统软件,它为用户使用计算机提供一个方便、灵活、安全、可靠的工作环境,也是其他应用软件赖以存在的基础。
不仅是高校计算机专业学生需要了解它,从事计算机行业的人员也需要深入了解它。
由于目前高校开设的计算机操作系课程中偏重对理论知识的要求,实践环境有限,并偏重注重学生对一些经典算法的实践,学生并没有机会了解、实践操作系统的内部结构和实现技术。
经过一个学期的操作系统理论学习,我们基本上掌握了操作系统的理论基础知识,但是,操作系统课程的内容不仅仅涉及理论、算法,更重要的是用技术去实现算法并对其加以实现和应用。
此次课程设计,通过搭建Linux实验平台,对专门为操作系统课程教学而设计的操作系统——GeekOS的项目代码的补充和完善,从浅到深,由表面到内涵地去理解操作系统的设计思想,理解操作系统内核工作的基本原理。
在完成项目的过程中,通过动手操作,使得我们能够在动手查阅资料、思考排难等探索性活动中进一步理解操作系统的抽象概念,并进一步理解操作系统复杂的结构和工作原理。
具体的来说,对于项目0,应该熟悉GeekOS的项目编译,调试和运行环境,掌握GeekOS的运行工作过程;对于项目1,应该熟悉ELF文件格式,了解GeekSO系统如何将ELF格式的用户可执行程序装入到内存,建立内核进程并运行的实现技术;对于项目2,扩充GeekOS操作系统内核,使得系统能够支持用户级进程的动态创建和执行。
2项目的设计要求
2.1Project0项目设计要求
(1)搭建GeekOS的编译和调试平台,掌握GeekOS的内核进程工作原理。
(2)熟悉键盘操作函数,编程实现一个内核进程。
该进程的功能是:
接受键盘输入的字符并显示在屏幕上,当按ctrl+D时,结束进程的运行。
2.2Project1项目设计要求
1、修改/geekos/elf.c文件:
在函数Parse_ELF_Executable()中添加代码,分析ELF格式的可执行文件(包括分析得出ELF文件头、程序头,获取可执行文件长度,代码段、数据段等信息),并填充Exe_Format数据结构中的域值。
2、在Linux环境下编译系统得到GeekOS镜像文件。
3、编写一个相应的bochs配置文件。
4、在bochs中运行GeekOS系统显示结果。
2.3Project2项目设计要求
项目2要求用户对以下几个文件进行修改:
1)“src/GeekOS/user.c”文件中的函数Spawn(),其功能是生成一个新的用户级进程;
2)“src/GeekOS/user.c”文件中的函数Switch_To_User_Context(),调度程序在执行一个新的进程前调用该函数以切换用户地址空间;
3)“src/GeekOS/elf.c”文件中的函数Parse_ELF_Executable()。
该函数的实现要求和项目1相同。
4)“src/GeekOS/userseg.c”文件中主要是实现一些为实现对“src/GeekOS/user.c”中高层操作支持的函数。
Destroy_User_Context()函数的功能是释放用户态进程占用的内存资源。
Load_User_Program()函数的功能通过加载可执行文件镜像创建新进程的User_Context结构。
Copy_From_User()和Copy_To_User()函数的功能是在用户地址空间和内核地址空间之间复制数据,在分段存储器管理模式下,只要段有效,调用memcpy函数就可以实现这两个函数的功能。
Switch_To_Address_Space()函数的功能是通过将进程的LDT装入到LDT寄存器来激活用户的地址空间;
5)“src/GeekOS/kthread.c”文件中的Start_User_Thread函数和Setup_User_Thread函数。
Setup_User_Thread()函数的功能是为进程初始化内核堆栈,堆栈中是为进程首次进入用户态运行时设置处理器状态要使用的数据。
Start_User_Thread()是一个高层操作,该函数使用User_Context对象开始一个新进程。
6)“src/GeekOS/kthread.c”文件中主要是实现用户程序要求内核进行服务的一些系统调用函数定义。
要求用户实现的有Sys_Exit()函数、Sys_PrintString()函数、Sys_GetKey()、Sys_SetAttr()、Sys_GetCursor()、Sys_PutCursor()、Sys_Spawn()函数、Sys_Wait()函数和Sys_GetPID()函数。
7)在main.c文件中改写生成第一个用户态进程的函数调用:
Spawn_Init_Process(void)。
3开发环境的建立
3.1在虚拟机上安装Linux操作系统
GeekOS是一个基于X86架构的PC机上运行的微操作系统内核,内核简单却又兼备实用性,是一个用C语言开发的操作系统,可以运行在真正的X86PC硬件平台。
每个项目需要在Linux环或者UNIX环境下对其进行功能扩充。
Ubuntu是一个由社区开发的基于linux的操作系统,其包含了我们一般所需的所有程序:
无论是文字处理和电子邮件,还是Web服务和编程工具。
所以选择其作为Linux系统进行安装。
首先先安装VMwareworkstation,运行Vmware,新建一个虚拟机,以便安装Linux系统。
然后在虚拟机上安装Ubuntu系统。
系统安装的时候会默认安装了GCC,所以后面不必再重新安装GCC。
3.2安装c/c++编译环境
(1)修改使用国内镜像更新源。
在命令行终端中输入sudogedit/etc/apt/sources.list,打开“文本编辑器编辑源列表”。
在最后一行添加下列国内镜像更新源:
deb-srcjaunty-updatesmainmultiverserestricteduniverse保存,即可。
(2)终端中输入sudoapt-getupdate重新获取软件包列表。
(3)在终端输入sudoapt-getintallbuild-essential下载安装build-essential包。
(4)安装NASM
在终端执行udoapt-getinstallnasm,
(5)安装Bochs:
在终端执行sudoapt-getinstallbochs
在终端执行sudoapt-getinstallbochs-x
4项目设计原理
4.1Project0设计原理
利用GeekOS提供的键盘处理函数keyboard.h与keyboard.c等进行键盘常用功能的模拟。
其中,在keyboard.c里面提供了一个功用函数KeycodeWait_For_Key(void),循环等待一个键盘事件,然后返回一个16位的数据Keycode型的,在keyboard.h里定义了所有的键盘代码。
Read_Key(Keycode*keycode)函数可以处理队列键盘按键,可以保存到队列中并输出。
另外,需要重点关Start_Kernel_Thread函数。
函数的参数startFunc是一个Thread_Start_Func类型的函数指针,其定义在kthread.h中:
Typedefvoid(*Thread_Start_Func)(ulong_t,arg);该函数指针指向一个无返回值,参数为ulong_t类型的函数。
函数的功能是以参数Start_Func指向的代码为进程体生成一个内核进程。
再有,对于Read_Key()函数,其定义为:
boolRead_Key(Keycode*keycode);作用是轮查键盘事,如果捕获到键盘事件,则返回true,并且将按键码保存到参数keycode地址中。
4.2Project1设计原理
ELF(Executableandlinkingformat)文件是UNIX系统实验室作为应用程序二进制接口而开发的可执行文件,是x86Linux系统下的一种常用目标文件(objectfile)格式。
ELF文件格式如下表1。
表1ELF目标文件格式
连接程序视图
执行程序视图
ELF头部
ELF头部
程序头部表(可选)
程序头部表
节区1
段1
...
节区n
段2
...
...
...
节区头部表
节区头部表(可选)
ELF文件在磁盘中的映象和在内存中的执行程序镜像的对应关系如下图:
图1ELF文件和内存中的可执行文件镜像
内核进程的创建流程如下图:
图2内核进程流程图
Parse_ELF_Excutable函数的定义为:
intParse_ELF_Executable(char*exeFileData,ulong_texeFileLength,tructExe_Format*exeFormat)
参数:
exeFileData——已装入内存的可执行文件所占用空间的起始地址
exeFileLength——可执行文件长度
exeFormat——保存分析得到的elf文件信息的结构体指针根据ELF文件格式,用户可以从exeFileData指向的内容中得到ELF文件头,继续分析可以得到程序头,程序代码段等信息。
4.3Project2设计原理
Geekos进程状态及转化:
图3GeekOS进程状态转换
GeekOS系统最早创建的内核进程有Idle、Reaper和Main三个进程,它们由Init_Scheduler函数创建:
最先初始化一个核态进程mainThread,并将该进程作为当前运行进程,函数最后还调用Start_Kernel_Thread函数创建了两个系统进程Idle和Reaper。
所以,Idle、Reaper和Main三个进程是系统中最早存在的进程。
在GeekOS中为了区分用户态进程和内核进程,在Kernel_Thread结构体中设置了一个字段userContext,指向用户态进程上下文。
对于内核进程来说,这个指针为空,而用户态进程都拥有自己的用户上下文(User_Context)。
因此,在GeekOS中要判断一个进程是内核进程还是用户态进程,只要通过userContext字段是否为空来判断就可以了。
每个用户态进程都拥有属于自己的内存段空间,如:
代码段、数据段、堆栈段等,每个段有一个段描述符(segmentdescriptor),并且每个进程有一个段描述符表(LocalDescriptorTable),用于保存该进程的所有段描述符。
操作系统中还设置一个全局描述符表(GDT,GlobalDescriptorTable),用于记录了系统中所有进程的ldt描述符。
图10.2GDT、LDT和User_Context的关系
用户态进程创建LDT的步骤:
(1)调用函数Allocate_Segment_Descriptor()新建一个LDT描述符;
(2)调用函数Selector()新建一个LDT选择子;
(3)调用函数Init_Code_Segment_Descriptor()新建一个文本段描述符;
(4)调用函数Init_Data_Segment_Descriptor()新建一个数据段;
(5)调用函数Selector()新建一个数据段选择子;
(6)调用函数Selector()新建一个文本(可执行代码)段选择子。
用户态进程创建流程:
5项目设计的具体实现
5.1Project0项目的具体实现
在project/project0/src/geekos/main.c中添加一个函数。
函数的功能是:
接收键盘输入的按键,并将键值显示到显示器的函数,当输入Ctrl+D就退出。
函数代码如下:
==================main.c====================
voidproject0()
{
Print("ToExithitCtrl+d.\n");
Keycodekeycode;
while
(1)
{
if(Read_Key(&keycode))//读取键盘按键状态
{
if(!
((keycode&KEY_SPECIAL_FLAG)||(keycode&KEY_RELEASE_FLAG)))//只处理非特殊按键的按下事件
{
intasciiCode=keycode&0xff;//低8位为Ascii码
if((keycode&KEY_CTRL_FLAG)==KEY_CTRL_FLAG&&asciiCode=='d')//按下Ctrl键
{
Print("\n---------BYE!
--------\n");
Exit
(1);
}else
{
Print("%c",(asciiCode=='\r')?
'\n':
asciiCode);
}
}
}
}
}
=========================================
(2)首先注释Main()函数中TODO宏,并调用Start_Kernel_Thread函数,将步骤1编写的函数地址传递给参数,建立一个内核级进程
structKernel_Thread*thread;
thread=Start_Kernel_Thread(&project0,0,PRIORITY_NORMAL,false);
(3)执行makedepend及make命令,此时会在build目录下生成fd.img
(4)编写brochs配置文件
vgaromimage:
file=/usr/share/bochs/VGABIOS-lgpl-latest
romimage:
file=/usr/share/bochs/BIOS-bochs-latest,address=0xf0000
megs:
8
boot:
a
floppya:
1_44=fd.img,status=inserted
#floppya:
1_44=fd_aug.img,status=inserted
log:
./bochs.out
keyboard_serial_delay:
200
floppy_command_delay:
500
vga_update_interval:
300000
ips:
1000000
mouse:
enabled=0
private_colormap:
enabled=0
i440fxsupport:
enabled=0
5.2Project1项目的具体实现
修改project/project1/src/geekos/elf.c文件:
在函数Parse_ELF_Executable()中添加代码,分析
ELF格式的可执行文件(包括分析得出ELF文件头、程序头,获取可执行文
件长度,代码段、数据段等信息),并填充Exe_Format数据结构中的域值
1、elf.c:
将ELF格式的可执行程序装入到内存,建立内核进程并运行.
==================elf.c====================
intParse_ELF_Executable(char*exeFileData,ulong_texeFileLength,structExe_Format*exeFormat)
{
inti;
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;iphnum;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++;
}
return0;
}
=========================================
2、编译,成功后生成两个镜像文件:
fd.img和diskc.img。
其中,Diskc.img为模拟器能引导的操作系统镜像。
3、编写相应的bochs配置文件
由于生成了diskc.img,因此配置文件需加上以下内容
config_interface:
textconfig
megs:
8
vgaromimage:
file=$BXSHARE/VGABIOS-lgpl-latest
romimage:
file=$BXSHARE/BIOS-bochs-latest
floppya:
1_44=./fd.img,status=inserted
ata0:
enabled=1,ioaddr1=0x1f0,ioaddr2=0x3f0,irq=14
ata1:
enabled=0,ioaddr1=0x170,ioaddr2=0x370,irq=15
#ata2:
enabled=0,ioaddr1=0x1e8,ioaddr2=0x3e0,irq=11
#ata3:
enabled=0,ioaddr1=0x168,ioaddr2=0x360,irq=9
ata0-master:
type=disk,path="diskc.img",mode=flat,cylinders=40,heads=8,spt=64
#ata0-slave:
type=cdrom,path="/dev/cdrom",status=inserted
boot:
a
ips:
1000000
log:
./bochs.out
vga_update_interval:
300000
keyboard_serial_delay:
250
keyboard_paste_delay:
100000
private_colormap:
enabled=0
5.3Project2项目的具体实现
1、添加代码
==================user.c===============
//产生一个进程(用户态)
intSpawn(constchar*program,constchar*command,structKernel_Thread**pThread)
{
//TODO("Spawnaprocessbyreadinganexecutablefromafilesystem");
intrc;//标记各函数的返回值,为0则表示成功,否则失败
char*exeFileData=0;//保存在内存缓冲中的用户程序可执行文件
ulong_texeFileLength;//可执行文件的长度
structUser_Context*userContext=0;//指向User_Conetxt的指针
structKernel_Thread*process=0;//指向Kernel_Thread*pThread的指针
structExe_FormatexeFormat;//调用Parse_ELF_Executable函数得到的可执行文件信息
if((rc=Read_Fully(program,(void**)&exeFileData,&exeFileLength))!
=0)
{//调用Read_Fully函数将名为program的可执行文件全部读入内存缓冲区
Print("FailedtoReadFile%s!
\n",program);
gotofail;
}
if((rc=Parse_ELF_Executable(exeFileData,exeFileLength,&exeFormat))!
=0)
{//调用Parse_ELF_Executable函数分析ELF格式文件
Print("FailedtoParseELFFile!
\n");
gotofail;
}
if((rc=Load_User_Program(exeFileData,exeFileLength,&exeFormat,command,&userContext))!
=0)
{//调用Load_User_Program将可执行程序的程序段和数据段装入内存
Print("FailedtoLoadUserProgram!
\n");
gotofail;
}
//在堆分配方式下释放内存并再次初始化exeFileData
Free(exeFileData);
exeFileData=0;
/*开始用户进程,调用Start_User_Thread函数创建一个进程并使其进入准备运行队列*/
process=Start_User_Thread(userContext,false);
if(process!
=0){//不是核心级进程(即为用户级进程)
KASSERT(process->refCount==2);
/*返回核心进程的指针*/
*pThread=process;
rc=process->pid;//记录当前进程的ID
}else//超出内存project2\include\geekos\errno.h
rc=ENOMEM;
returnrc;
fail:
//如果新进程创建失败则注