资源描述
编号:
GeekOS操作系统的研究与实现
题 目: GeekOS操作系统的研究与实现
系 别: 计算机科学与工程学院
专 业: 网络工程
学生姓名:
学 号:
指导教师:
2011年 3 月 5 日
目 录
(三号、黑体、居中、目录两字空四格、与正文空一行)
一、课程设计环境 ……………………………………………………4
二、设计项目0 ……………………………………………………5
三、设计项目1 ………………………………………………………7
四、设计项2……………………………………………………………9
五、遇到问题及解决方法.……………………………………………23
六、总结…………………………………………………………………24
一、课程设计环境
本次课设是在虚拟机上安装Linux进行开发调试,具体安装使用方法如下:
<1>、安装linux虚拟机
本次课设的虚拟机是运行在Vmware WorkStation上的,网上下载及安装好
Vmware 后,下载Linux镜像文件后,即可按提示即可安装。
<2>、GeekOS:是一个基于X86架构的PC机上运行的微操作系统内核,由美国
马理兰大学的教师开发,是一个用C语言开发的操作系统, GeekOS主要
用于操作系统课程设计,目的是使学生能够实际动手参与到一个操作系统的
开发工作中。
GeekOS的使用:打开linux虚拟机,直接解压GeekOS压缩包就可使用,
无需安装。
<3>、Bochs安装和使用:在Linux系统中需先解压软件包,然后再配置编译生成系统文件。
二、设计项目0
一、项目设计目的
熟悉GeekOS的项目编译、调试和运行环境,掌握GeekOS运行工作过程。
二、设计任务
熟悉键盘操作函数,编程实现一个内核进程。该进程的功能是:接收键盘输入的字符并显示到屏幕上,当输入ctrl+d时,结束进程的运行。
三、具体过程
1、 ------------------------修改main.c的代码--------------------------------
//在main.c内增加该函数
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)) ) //只处理非特殊按键的按下事件
{
int asciiCode = keycode & 0xff; //低位为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);
}
}
}
}
}
//在main函数体内调用Start_Kernel_Thread函数
struct Kernel_Thread *thread;
thread = Start_Kernel_Thread(&project0,0,PRIORITY_NORMAL,false);
----------------------------------------------------
2、在build目录下编译得到镜像文件fd.img
#make
3、 编写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
4、在brochs中运行GeekOS系统显示结果
三、 设计项目1
一、项目设计目的
熟悉ELF文件格式,了解GeekOS系统如何将ELF格式的可执行程序装
入到内存,建立内核进程并运行的实现技术。
二、 具体过程
修改/geekos/elf.c文件:在函数Parse_ELF_Executable( )中添加代码,分析
ELF格式的可执行文件(包括分析得出ELF文件头、程序头,获取可执行文
件长度,代码段、数据段等信息),并填充Exe_Format数据结构中的域值
1、elf.c:将ELF格式的可执行程序装入到内存,建立内核进程并运行.
----------------------------------elf.c------------------------------------------
int Parse_ELF_Executable(char *exeFileData, ulong_t exeFileLength, struct Exe_Format *exeFormat)
{
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;
}
--------------------------------------------------------------------------------
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
4、在brochs中运行GeekOS系统显示结果
四、 设计项目二
一、项目设计目的
扩充GeekOS操作系统内核,使得系统能够支持用户级进程的动态创建和执行。
二、项目2要求用户对以下几个文件进行修改:
(1)“user.c” 文件中的函数Spawn(),其功能是生成一个新的用户级进程;
(2)“user.c” 文件中的函数Switch_To_User_Context(),调度程序在执行
一个新的进程前调用该函数以切换用户地址空间;
(3)“elf.c” 文件中的函数Parse_ELF_Executable() 。该函数的实现要求
和项目1 相同。
(4)“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寄存器来激活用户的地址空间;
三、 具体代码修改情况
-----------------------------elf.c--------------------------------------------
int Parse_ELF_Executable(char *exeFileData, ulong_t exeFileLength,struct Exe_Format *exeFormat)
{
int i;
elfHeader *hdr =(elfHeader*) exeFileData;
programHeader *phdr=(programHeader *)(exeFileData + hdr->phoff);
struct Exe_Segment * segment= exeFormat->segmentList;
for( i=0; i< hdr->phnum; i++)
{
segment->offsetInFile = phdr->offset;
segment->lengthInFile = phdr->fileSize;
segment->startAddress = phdr->vaddr;
segment->sizeInMemory = phdr->memSize;
phdr++;
segment++;
}
exeFormat->numSegments = hdr->phnum;
exeFormat->entryAddr = hdr->entry;
return 0;
TODO("Parse an ELF executable image");
}
------------------------------------------------------------------------------
----------------------------------------------------------------------------
-----------------------------kthred.c-------------------------------------------
--------------------------------------------------------------------------
//kthread.c文件中主要是实现用户程序要求内核进行服务的一些系统调用函数定义
---------------------------------------------------------------------------------------------------------------
//为进程初始化内核堆栈
//堆栈中是为进程首次进入用户态运行时设置处理器状态要使用的数据
//该函数内部调用Attach_User_Context加载用户上下文
void Setup_User_Thread(struct Kernel_Thread* kthread, struct User_Context* userContext)
{
//TODO("Create a new thread to execute in user mode");
ulong_t eflags = EFLAGS_IF;
unsigned csSelector=userContext->csSelector;//CS选择子
unsigned dsSelector=userContext->dsSelector;//DS选择子
Attach_User_Context(kthread, userContext);//调用Attach_User_Context加载用户上下文
//初始化用户态进程堆栈,使之看上去像刚被中断运行一样
//分别调用Push函数将以下数据压入堆栈
Push(kthread, dsSelector); //数据选择子
Push(kthread, userContext->stackPointerAddr); //堆栈指针
Push(kthread, eflags); //Eflags
Push(kthread, csSelector); //文本选择子
Push(kthread, userContext->entryAddr); //程序计数器
Push(kthread, 0); //错误代码(0)
Push(kthread, 0); //中断号(0)
//初始化通用寄存单元,将ESI用户传递参数块地址
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(kthread, dsSelector); /* ds */
Push(kthread, dsSelector); /* es */
Push(kthread, dsSelector); /* fs */
Push(kthread, dsSelector); /* gs */
}
----------------------------------------------------------------------------------------------------------------------------------------
//该函数使用User_Context对象开始一个新进程
//若成功,指针返回一个新进程
//Spawn函数调用该函数,以初始化一个用户态进程(包括初始化进程的Kernal_Thread结构以及调用Setup_User_Thread初始化用户态进程的内核堆栈)
struct Kernel_Thread*
Start_User_Thread(struct User_Context* userContext, bool detached)
{
//调用Create_Thread()建立新进程对象
//调用Setup_User_Thread()函数初始化新进程
//TODO("Start user thread");
struct Kernel_Thread* kthread = Create_Thread(PRIORITY_USER, detached);//调用Create_Thread()建立新用户态进程对象
if (kthread != 0){
Setup_User_Thread(kthread, userContext);//调用Setup_User_Thread()函数初始化新用户态进程的内核堆栈
Make_Runnable_Atomic(kthread);
}
return kthread;//创建成功,指针返回一个新进程
}
---------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------
---------------------------------------user.c--------------------------------------------------
-----------------------------------------------------------------------------------------------------------------------------------
//导入用户程序并初始化(包括调用Load_User_Program进行User_Context的初始化及用户态进程空间的分配、用户程序各段的装入)
//Spawn函数主要完成的主要功能是:
//调用Read_Fully函数将名为program的可执行文件全部读入内存缓冲区
//调用Parse_ELF_Executable函数分析ELF格式文件
//调用Load_User_Program将可执行程序的程序段和数据段装入内存,初始化User_Context数据结构
//调用Start_User_Thread函数创建一个进程并使其进入准备运行队列
int Spawn(const char *program, const char *command, struct Kernel_Thread **pThread)//生成新的用户级进程
{
//TODO("Spawn a process by reading an executable from a filesystem");
int rc; //标记各函数的返回值,为则表示成功,否则失败
char *exeFileData = 0;//保存在内存缓冲中的用户程序可执行文件
ulong_t exeFileLength;//可执行文件的长度
struct User_Context *userContext = 0;//指向User_Conetxt的指针
struct Kernel_Thread *process = 0;//指向Kernel_Thread *pThread的指针
struct Exe_Format exeFormat;//调用Parse_ELF_Executable函数得到的可执行文件信息
if ((rc = Read_Fully(program, (void**) &exeFileData, &exeFileLength)) != 0 )//调用Read_Fully函数将名为program的可执行文件全部读入内存缓冲区
{
Print("Failed to Read File %s!\n", program);
goto fail;
}
if((rc = Parse_ELF_Executable(exeFileData, exeFileLength, &exeFormat)) != 0 )//调用Parse_ELF_Executable函数分析ELF格式文件
{
Print("Failed to Parse ELF File!\n");
goto fail;
}
if((rc = Load_User_Program(exeFileData, exeFileLength, &exeFormat, command, &userContext)) != 0)//调用Load_User_Program将可执行程序的程序段和数据段装入内存,初始化User_Context数据结构
{
Print("Failed to Load User Program!\n");
goto fail;
}
//在堆分配方式下释放内存并再次初始化exeFileData
Free(exeFileData);
exeFileData = 0;
process = Start_User_Thread(userContext, false);//开始用户进程,调用Start_User_Thread函数创建一个进程并使其进入准备运行队列
if (process != 0)//不是核心级进程(即为用户级进程)
{
KASSERT(process->refCount == 2);
*pThread = process;返回核心进程的指针
rc = process->pid;//记录当前进程的ID
}
else//超出内存project2\include\geekos\errno.h
rc = ENOMEM;
return rc;
fail: //如果新进程创建失败则注销User_Context对象
if (exeFileData != 0)
Free(exeFileData);//释放内存
if (userContext != 0)
Destroy_User_Context(userContext);//销毁进程对象
return rc;
}
-------------------------------------------------------------------------------------------------------------------------------------
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.
*/
//TODO("Switch to a new user address space, if necessary");
static struct User_Context* s_currentUserContext; /* last user context used */
//extern int userDebug;
struct User_Context* userContext = kthread->userContext;//指向User_Conetxt的指针,并初始化为准备切换的进程
KASSERT(!Interrupts_Enabled());
if (userContext == 0) { //userContext为表示此进程为核心态进程就不用切换地址空间
return;
}
if (userContext != s_currentUserContext) {
ulong_t esp0;
//if (userDebug) Print("A[%p]\n", kthread);
Switch_To_Address_Space(userContext);//为用户态进程时则切换地址空间
esp0 = ((ulong_t) kthread->stackPage) + PAGE_SIZE;
//if (userDebug)
// Print("S[%lx]\n", esp0);
/* 新进程的核心栈. */
Set_Kernel_Stack_Pointer(esp0);//设置内核堆栈指针
/* New user context is active */
s_currentUserContext = userContext;
}
}
---------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
--------------------------------------userseg.c--------------------------------------------------
//userseg.c文件中主要为实现一些为实现对user.c中高层操作支持的函数
------------------------------------------------------------------------------------------------------
//新建一个给定长度的User_Context,参数是User_Context结构的大小
//User_Context的新建
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);
else//为核心态进程
goto fail;
if (0 == UserContext->memory)//内存为空
goto fail;
memset(UserContext->memory, '\0', size);
UserContext->size = size;
//以下为用户态进程创建LDT(段描述符表),步骤如下:
//调用函数Allocate_Segment_Descriptor()新建一个LDT描述符
//调用函数selector()新建一个LDT选择子
//调用函数Init_Code_Segment_Descriptor()新建一个文本段描述符
//调用函数Init_Data_Segment_Descriptor()新建一个数据段
//调用函数selector()新建一个数据段选择子
//调用函数selector()新建一个文本(可执行代码)段选择子
//新建一个LDT描述符
UserContext->ldtDescriptor = Allocate_Segment_Descriptor();
if (0 == UserContext->ldtDescriptor)
goto fail;
//初始化段描述符
Init_LDT_Descriptor(UserContext->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( &UserContext->ldt[1],(ulong_t) UserContext->memory,size / PAGE_SIZE,USER_PRIVILEGE );
//新建数据段和
展开阅读全文