操作系统课设报告 桂林电子科技大学.docx
《操作系统课设报告 桂林电子科技大学.docx》由会员分享,可在线阅读,更多相关《操作系统课设报告 桂林电子科技大学.docx(32页珍藏版)》请在冰豆网上搜索。
操作系统课设报告桂林电子科技大学
《GeekOS操作系统》
课程设计说明书
题目:
GeekOS操作系统的研究与实验
学院:
计算机科学与工程学院
专业:
信息安全
姓名:
学号:
指导教师:
2015年06月12日
目录
1GeekOS简介1
1.1GeekOS系统源代码结构1
2课程设计环境2
3项目0的设计实现3
3.1项目设计目的3
3.2项目设计要求3
3.3项目实现原理3
3.4项目实现过程3
3.5运行结果5
4项目1的设计实现6
4.1项目设计目的6
4.2项目设计要求6
4.3项目实现原理6
4.4项目实现过程8
4.5运行结果9
5项目2的设计实现10
5.1项目设计目的10
5.2项目设计目的10
5.3项目实现原理11
5.4项目实现过程13
5.5运行结果23
6遇到问题及解决方法23
7课程设计总结24
1GeekOS简介
GeekOS是一个基于X86架构的PC上运行的微操作系统内核,由美国马理兰大学的教师开发,主要用于操作系统课程设计,目的是使学生能够实际动手参与到一个操作系统的开发工作中。
出于教学目的,这个系统内核设计简单,却又兼备实用性,它可以运行在真正的X86PC硬件平台。
作为一个课程设计平台,GeekOS由一个基本的操作系统内核作为基础,提供了操作系统与硬件之间的所有必备接口,实现了系统引导,实模式到保护模式的转换,中断调用及异常处理,基于段式的内存管理,FIFO进程调度算法以及内核进程,基本的输入输出(键盘作为输入设备,显示器作为输出设备),以及一个用于存放用户程序的只读文件系统PFAT。
学生可以在Linux或Unix环境下对其进行功能扩充,且其针对进程、文件系统、存储管理等操作系统核心内容分别设计了7个难度逐渐增加的项目供学生选择。
1.1GeekOS系统源代码结构
GeekOS操作系统源文件geekos-0.3.0.zip可以从下载。
图1.1GeekOS系统主目录
在doc目录里的文件hacking.pdf和index.htm是GeekOS系统的参考文档。
Scripts目录下有startProject和removeEmptyConflicts两个脚本文件。
GeekOS系统的源文件在src目录下,分为7个项目:
Project0到Project7。
在build文件夹中,包含系统编译后的可执行文件的文件、软盘镜像或是硬盘镜像、makefile项目管理文件。
在include文件夹中有GeekOS和libc两个子目录,在GeekOS子目录中有kthread.h、keyboard.h等文件。
图1.2项目文件结构图
2课程设计环境
1.虚拟机软件:
VMwareWorkstation10.0。
2.虚拟系统:
linux系统CentOS6.0。
3.NASM汇编器。
4.GNUgcc编译器。
5.GNUgdb调试器。
6.SourseInsight:
程序编辑器和代码浏览器。
7.Bochs:
GeekOS运行于Windows(或Linux)下的BochsPC模拟器,Bochs是用C++开发的可移植的IA-32(x86)PC模拟器,它包括对Intelx86CPU、通用I/O设备和可定制的BIOS的模拟,几乎可以运行在所有流行的平台上。
在本次课设中使用的是bochs2.6。
图2.1课设环境
3项目0的设计实现
3.1项目设计目的
熟悉GeekOS的项目编译、调试和运行环境,掌握GeekOS运行工作过程。
3.2项目设计要求
1.搭建GeekOS的编译和调试平台,掌握GeekOS的内核进程工作原理。
2.熟悉键盘操作函数,编程实现一个内核进程。
该进程的功能是:
接收键盘输入的字符并显示到屏幕上,当输入ctrl+d时,结束进程的运行。
3.3项目实现原理
项目0主要要求设计一个函数对键盘的中断进行响应。
这主要通过使用GeekOS提供的键盘响应函数Wait_Kernel_Thread进行键盘中断的响应和返回键值。
该函数首先检查键盘缓冲区是否有按键,如果有,就读取一个键码,如果此时键盘缓冲区没有键值,就将线程放入键盘事件等待队列。
于是可分为两步完成:
1.编写函数EchoCount,函输功能是:
接受键盘输入的按键,并将键值显示在显示器,当输入Ctrl+D退出。
2.在Main函数体内调用Start_Kernel_Thread函数,将编写的函数地址传递给startFunc,建立一个内核进程。
3.4项目实现过程
1.添加代码
(1)在Main函数中编写一个函数,函数功能是:
接收键盘输入的按键,并将键值显示到显示器的函数,当输入Ctrl+D就退出。
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':
keycode);
}
} } }
}//放在main函数之前
(2) 在Main函数体内调用Start_Kernel_Thread函数,将步骤1编写的函数地址传递给参数startFunc,建立一个内核级进程。
voidMain(structBoot_Info*bootInfo)
{
//TODO("Startakernelthreadtoechopressedkeysandprintcounts");
structKernel_Thread*thread;
thread=Start_Kernel_Thread(&project0,0,PRIORITY_NORMAL,false);
}
2.编译GeekOS项目project0
(1)shell>>#cd/…/geekos-0.3.0/src/project0/build
(2)shell>>#makedepend
生成depend.mak文件。
图3.1makedepend执行过程
(3)shell>>#make
成功之后在build目录下生成fd.img文件。
图3.2make执行过程
3.配置启动Bochs
(1)创建bochs配置文件
shell>>#geditbochsrc
(2)在编辑器中输入一下配置内容
gdbstub:
enabled=1,port=1234,text_base=0,data_base=0,bss_base=0
romimage:
file=$BXSHARE/BIOS-bochs-latest
megs:
8
boot:
a
floppya:
1_44=fd.img,status=inserted
log:
./bochs.out
(3)保存,直接退出gedit
3.5运行结果
(1)启动bochs
shell>>#bochs-bochsrc
(2)选择beginsimulation
(3)结果:
图3.3项目0运行结果
4项目1的设计实现
4.1项目设计目的
熟悉ELF文件格式,了解GeekOS系统如何将ELF格式的可执行程序装入到内存,建立内核进程并运行的实现技术。
4.2项目设计要求
1.修改/geekos/elf.c文件:
在函数Parse_ELF_Executable()中添加代码,分析ELF格式的可执行文件(包括分析得出ELF文件头、程序头,获取可执行文件长度,代码段、数据段等信息),并填充Exe_Format数据结构中的域值。
2.在Linux环境下编译系统得到GeekOS镜像文件。
3.编写一个相应的bochs配置文件。
4.在bochs中运行GeekOS系统显示结果。
4.3项目实现原理
1.ELF文件格式。
连接程序视图
执行程序视图
ELF头部
ELF头部
程序头部表(可选)
程序头部表
节区1
段1
…
节区n
段2
…
…
…
节区头部表
节区头部表(可选)
表4.1ELF目标文件格式
2.内存中的可执行文件镜像
GeekOS中的用户进程全部在系统的编译阶段完成编译和链接,形成可执行文件,用户可执行文件保存在PFAT文件系统中。
项目1要完成的事系统启动后,从PFAT文件系统将可执行文件装入内存,建立进程并运行得到相应的输出。
如下图:
图4.1文件镜像
3.内核线程的建立流程
该过程主要由Spawner函数实现,其主要经过:
调用Read_Fully函数将文件读入内存,后调用Parse_ELF_Executable函数分析ELF文件,最后调用Spawn_Program函数将可执行程序的代码段和数据段等装入内存,此后便可以开始运行一个内核级进程了。
如下图:
图4.2建立流程
4.4项目实现过程
1.添加代码
修改/geekos/elf.c文件。
在函数Parse_ELF_Executalbe()中添加代码,分析ELF格式的可执行文件(包括分析得出ELF文件头、程序头,获取可执行文件长度,代码段、数据段等信息),并填充Exe_Format数据结构中的域值。
intParse_ELF_Executable(char*exeFileData,ulong_texeFileLength,
structExe_Format*exeFormat)
{
//TODO("ParseanELFexecutableimage");
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.编译GeekOS项目project0
(1)执行makedepend
shell>>#makedepend
生成depend.mak文件
(2)执行make
shell>>#make
成功之后再build目录下生成fd.img和disk.img文件。
3.配置启动Bochs
(1)创建bochs配置文件
shell>>#geditbochsrc
(2)在编辑器输入一下配置内容
romimage:
file=$BXSHARE/BIOS-bochs-latest
megs:
8
boot:
a
floppya:
1_44=fd.img,status=inserted
ata0-master:
type=disk,mode=flat,path="diskc.img",cylinders=0
log:
./bochs.out
(3)保存,直接退出gedit
4.5运行结果
(1)启动bochs
shell>>#bochs–fbochsrc
(2)选择beginsimulation
(3)结果:
图4.3项目1运行结果
5项目2的设计实现
5.1项目设计目的
扩充GeekOS操作系统内核,使得系统能够支持用户级进程的动态创建和执行。
5.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)。
5.3项目实现原理
1.GeekOS进程状态及转换
图5.1GeekOS进程转换
GeekOS系统最早创建的内核进程有Idle、Reaper和Main三个进程,它们由Init_Scheduler函数创建:
最先初始化一个核态进程mainThread,并将该进程作为当前运行进程,函数最后还调用Start_Kernel_Thread函数创建了两个系统进程Idle和Reaper。
所以,Idle、Reaper和Main三个进程是系统中最早存在的进程。
2.GeekOS的用户态进程
在GeekOS中为了区分用户态进程和内核进程,在Kernel_Thread结构体中设置了一个字段userContext,指向用户态进程上下文。
对于内核进程来说,这个指针为空,而用户态进程都拥有自己的用户上下文(User_Context)。
因此,在GeekOS中要判断一个进程是内核进程还是用户态进程,只要通过userContext字段是否为空来判断就可以了。
图5.2用户进程
3.用户进程空间
每个用户态进程都拥有属于自己的内存段空间,如:
代码段、数据段、堆栈段等,每个段有一个段描述符(segmentdescriptor),并且每个进程有一个段描述符表(LocalDescriptorTable),用于保存该进程的所有段描述符。
操作系统中还设置一个全局描述符表(GDT,GlobalDescriptorTable),用于记录了系统中所有进程的ldt描述符。
图5.3用户进程空间
4.用户态进程创建LDT的步骤
(1)调用函数Allocate_Segment_Descriptor()新建一个LDT描述符;
(2)调用函数Selector()新建一个LDT选择子;
(3)调用函数Init_Code_Segment_Descriptor()新建一个文本段描述符;
(4)调用函数Init_Data_Segment_Descriptor()新建一个数据段;
(5)调用函数Selector()新建一个数据段选择子;
(6)调用函数Selector()新建一个文本(可执行代码)段选择子。
图5.4用户进程创建流程
5.4项目实现过程
1.添加代码
(1)修改src/GeekOS/user.c文件中的函数Spawn(),其功能是生成一个用户级进程。
(2)src/GeekOS/user.c文件中的函数Switch_To_User_Contex(),调度程序在执行一个新的进程前调用该函数以切换用户地址空间。
(3)src/GeekOS/elf.c文件中的函数Prase_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相关函数的修改。
(7)src/GeekOS/syscall.c”文件中主要是实现用户程序要求内核进行服务的一些系统调用函数定义。
要求用户实现的有Sys_Exit()函数、Sys_PrintString()函数、Sys_GetKey()、Sys_SetAttr()、Sys_GetCursor()、Sys_PutCursor()、Sys_Spawn()函数、Sys_Wait()函数和Sys_GetPID()函数。
(8)在main.c文件中改写生成第一个用户态进程的函数调用:
Spawn_Init_Process(void)
==================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将可执行程序的程序段和数据段装入内存