操作系统课设报告 桂林电子科技大学

更新时间:2024-06-05 13:52:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

桂林电子科技大学综合设计说明书用纸

《GeekOS操作系统》

课程设计说明书

题 目: GeekOS操作系统的研究与实验 学 院: 计算机科学与工程学院 专 业: 信息安全 姓 名: 学 号: 指导教师:

2015年 06月 12 日

桂林电子科技大学综合设计说明书用纸

目 录

1 GEEKOS简介 .......................................................................................................................................................... 1

1.1 GEEKOS 系统源代码结构 .................................................. 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

桂林电子科技大学综合设计说明书用纸 第 1 页

1 GeekOS简介

GeekOS是一个基于X86架构的PC上运行的微操作系统内核,由美国马理兰大学的教师开发,主要用于操作系统课程设计,目的是使学生能够实际动手参与到一个操作系统的开发工作中。出于教学目的,这个系统内核设计简单,却又兼备实用性,它可以运行在真正的X86 PC硬件平台。作为一个课程设计平台,GeekOS由一个基本的操作系统内核作为基础,提供了操作系统与硬件之间的所有必备接口,实现了系统引导,实模式到保护模式的转换,中断调用及异常处理,基于段式的内存管理,FIFO进程调度算法以及内核进程,基本的输入输出(键盘作为输入设备,显示器作为输出设备),以及一个用于存放用户程序的只读文件系统PFAT。

学生可以在Linux或Unix环境下对其进行功能扩充,且其针对进程、文件系统、存储管理等操作系统核心内容分别设计了7个难度逐渐增加的项目供学生选择 。 1.1 GeekOS 系统源代码结构

GeekOS操作系统源文件geekos-0.3.0.zip可以从http://geekos.sourceforge.net下载。

图 1.1 GeekOS系统主目录

在doc目录里的文件hacking.pdf和index.htm是GeekOS系统的参考文档。Scripts目录下有startProject和removeEmptyConflicts两个脚本文件。GeekOS系统的源文件在src目录下,分为7个项目:Project0到Project7。在build文件夹中,包含系统编译后的可执行文件的文件、软盘镜像或是硬盘镜像、makefile项目管理文件。在include文件夹中有GeekOS和libc两个子目录,在GeekOS子目录中有kthread.h、keyboard.h等文件。

桂林电子科技大学综合设计说明书用纸 第 2 页

图 1.2 项目文件结构图

2 课程设计环境

1. 虚拟机软件:VMware Workstation 10.0。 2. 虚拟系统:linux系统 CentOS 6.0。 3. NASM汇编器。 4. GNU gcc编译器。 5. GNU gdb调试器。

6. Sourse Insight:程序编辑器和代码浏览器。

7. Bochs:GeekOS运行于Windows(或Linux)下的Bochs PC模拟器,Bochs 是用 C++ 开发的可移植的 IA-32 (x86) PC 模拟器,它包括对 Intel x86 CPU 、通用 I/O 设备和可定制的 BIOS 的模拟,几乎可以运行在所有流行的平台上。在本次课设中使用的是bochs 2.6。

图 2.1 课设环境

桂林电子科技大学综合设计说明书用纸 第 3 页

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就退出。

void project0()

{Print(\ Keycode keycode; while(1) {

if( Read_Key(&keycode) )

{//读取键盘按键状态

if(!( (keycode & KEY_SPECIAL_FLAG) || (keycode & KEY_RELEASE_FLAG)) )

{ //只处理非特殊按键的按下事件

int asciiCode = keycode & 0xff; //低8位为Ascii码

if( (keycode & KEY_CTRL_FLAG)==KEY_CTRL_FLAG && asciiCode=='d') {//按下Ctrl键

Print(\ Exit(1);

}else{

桂林电子科技大学综合设计说明书用纸 第 4 页

Print(\ } } } } }//放在main函数之前

(2) 在Main函数体内调用Start_Kernel_Thread函数,将步骤1编写的函数地址传递给参数startFunc,建立一个内核级进程。

void Main(struct Boot_Info* bootInfo) {

//TODO(\ struct Kernel_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>># make depend 生成depend.mak文件。

图 3.1 make depend执行过程

(3)shell>># make

成功之后在build目录下生成fd.img文件。

桂林电子科技大学综合设计说明书用纸 第 5 页

图 3.2 make执行过程

3.配置启动Bochs

(1)创建bochs配置文件 shell>># gedit bochsrc

(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)选择 begin simulation (3)结果:

桂林电子科技大学综合设计说明书用纸 第 6 页

图 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 头部 程序头部表(可选) 节区1 段 1 … 执行程序视图 ELF 头部 程序头部表 桂林电子科技大学综合设计说明书用纸 第 7 页 节区 n 段 2 … … 节区头部表 … 节区头部表(可选) 表 4.1 ELF目标文件格式

2. 内存中的可执行文件镜像

GeekOS中的用户进程全部在系统的编译阶段完成编译和链接,形成可执行文件,用户可执行文件保存在PFAT文件系统中。项目1要完成的事系统启动后,从PFAT文件系统将可执行文件装入内存,建立进程并运行得到相应的输出。如下图:

图 4.1 文件镜像

3. 内核线程的建立流程

该过程主要由Spawner函数实现,其主要经过:调用Read_Fully函数将文件读入内存,后调用Parse_ELF_Executable函数分析ELF文件,最后调用Spawn_Program函数将可执行程序的代码段和数据段等装入内存,此后便可以开始运行一个内核级进程了。如下图:

桂林电子科技大学综合设计说明书用纸 第 8 页

图4.2 建立流程

4.4项目实现过程 1.添加代码

修改/geekos/elf.c文件。

在函数Parse_ELF_Executalbe()中添加代码,分析ELF格式的可执行文件(包括分析得出ELF文件头、程序头,获取可执行文件长度,代码段、数据段等信息),并填充Exe_Format数据结构中的域值。

int Parse_ELF_Executable(char *exeFileData, ulong_t exeFileLength, struct Exe_Format *exeFormat) {

//TODO(\ 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;iphnum;i++)

桂林电子科技大学综合设计说明书用纸 第 9 页

{

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. 编译GeekOS项目project0 (1)执行make depend shell>># make depend 生成depend.mak文件 (2)执行make shell>># make

成功之后再build目录下生成fd.img和disk.img文件。 3. 配置启动Bochs

(1)创建bochs配置文件 shell>># gedit bochsrc (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=\log: ./bochs.out

(3)保存,直接退出gedit 4.5 运行结果 (1)启动bochs

shell>># bochs –f bochsrc

(2)选择begin simulation (3)结果:

桂林电子科技大学综合设计说明书用纸 第 10 页

图 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寄存器来激活

桂林电子科技大学综合设计说明书用纸 第 11 页

用户的地址空间;

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.1 GeekOS进程转换

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字段是否为空来判断就可以了。

桂林电子科技大学综合设计说明书用纸 第 12 页

图 5.2 用户进程

3.用户进程空间

每个用户态进程都拥有属于自己的内存段空间,如:代码段、数据段、堆栈段等,每个段有一个段描述符(segment descriptor),并且每个进程有一个段描述符表(Local Descriptor Table),用于保存该进程的所有段描述符。操作系统中还设置一个全局描述符表(GDT,Global Descriptor Table),用于记录了系统中所有进程的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()新建一个文本(可执行代码)段选择子。

桂林电子科技大学综合设计说明书用纸 第 13 页

图 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寄存器来激活用户的地址空间

桂林电子科技大学综合设计说明书用纸 第 14 页

(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 =============== //产生一个进程(用户态)

int Spawn(const char *program, const char *command, struct Kernel_Thread **pThread) {

//TODO(\ int rc; //标记各函数的返回值,为0则表示成功,否则失败

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(\ goto fail; }

if((rc = Parse_ELF_Executable(exeFileData, exeFileLength, &exeFormat)) != 0 ) {//调用Parse_ELF_Executable函数分析ELF格式文件

Print(\ goto fail; }

if((rc = Load_User_Program(exeFileData, exeFileLength, &exeFormat, command, &userContext)) != 0) {//调用Load_User_Program将可执行程序的程序段和数据段装入内存 Print(\ goto fail; }

//在堆分配方式下释放内存并再次初始化exeFileData

Free(exeFileData); exeFileData = 0;

/* 开始用户进程,调用Start_User_Thread函数创建一个进程并使其进入准备运行队列*/ process = Start_User_Thread(userContext, false); if (process != 0) { //不是核心级进程(即为用户级进程)

桂林电子科技大学综合设计说明书用纸 第 15 页

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) {

//TODO(\

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为0表示此进程为核心态进程就不用切换地址空间 return; }

if (userContext != s_currentUserContext) { ulong_t esp0;

//if (userDebug) Print(\

Switch_To_Address_Space(userContext);//为用户态进程时则切换地址空间 esp0 = ((ulong_t) kthread->stackPage) + PAGE_SIZE; //if (userDebug)

// Print(\/* 新进程的核心栈. */

Set_Kernel_Stack_Pointer(esp0);//设置内核堆栈指针

/* New user context is active */

s_currentUserContext = userContext; } }

================== elf.c ==================== copy project1

=================== userseg.c =================== //需在此文件各函数前增加一个函数,此函数的功能是按给定的大小创建一个用户级进程上下文,具体实现如下:

桂林电子科技大学综合设计说明书用纸 第 16 页

//函数功能:按给定的大小创建一个用户级进程上下文

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(段描述符表) //新建一个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 );

//新建数据段和文本段选择子

UserContext->csSelector = Selector(USER_PRIVILEGE, false, 0); UserContext->dsSelector = Selector(USER_PRIVILEGE, false, 1); //将引用数清0

桂林电子科技大学综合设计说明书用纸 第 17 页

UserContext->refCount = 0; return UserContext; fail:

if (UserContext != 0){

if (UserContext->memory != 0){ Free(UserContext->memory); }

Free(UserContext); }

return 0; }

-------------------------------------------- //摧毁用户上下文

void Destroy_User_Context(struct User_Context* userContext) { //TODO(\// KASSERT(userContext->refCount == 0); /* Free the context's LDT descriptor */

// Free_Segment_Descriptor(userContext->ldtDescriptor); /* Free the context's memory */ // Disable_Interrupts();

// Free(userContext->memory); // Free(userContext); // Enable_Interrupts(); //释放占用的LDT

Free_Segment_Descriptor(userContext->ldtDescriptor); userContext->ldtDescriptor=0;

//释放内存空间

Free(userContext->memory); userContext->memory=0;

//释放userContext本身占用的内存 Free(userContext);

userContext=0; }

----------------------------------------------

int Load_User_Program(char *exeFileData, ulong_t exeFileLength,struct Exe_Format *exeFormat, const char *command,

struct User_Context **pUserContext)

{ //TODO(\ int i;

ulong_t maxva = 0;//要分配的最大内存空间 unsigned numArgs;//进程数目

ulong_t argBlockSize;//参数块的大小 ulong_t size, argBlockAddr;//参数块地址 struct User_Context *userContext = 0;

桂林电子科技大学综合设计说明书用纸 第 18 页

//计算用户态进程所需的最大内存空间

for (i = 0; i < exeFormat->numSegments; ++i) { //elf.h

struct Exe_Segment *segment = &exeFormat->segmentList[i];

ulong_t topva = segment->startAddress + segment->sizeInMemory; /* FIXME: range check */ if (topva > maxva) maxva = topva; }

Get_Argument_Block_Size(command, &numArgs, &argBlockSize);//获取参数块信息

size = Round_Up_To_Page(maxva) + DEFAULT_USER_STACK_SIZE;//用户进程大小=参数块总大小 + 进程堆栈大小(8192)

argBlockAddr = size; size += argBlockSize;

userContext = Create_User_Context(size);//按相应大小创建一个进程 if (userContext == 0)//如果为核心态进程 return -1;

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(userContext->memory + argBlockAddr, numArgs, argBlockAddr, command); //初始化数据段,堆栈段及代码段信息

userContext->entryAddr = exeFormat->entryAddr; userContext->argBlockAddr = argBlockAddr; userContext->stackPointerAddr = argBlockAddr; //将初始化完毕的User_Context赋给*pUserContext *pUserContext = userContext; return 0;//成功

}

---------------------------------------------- //将用户态的进程复制到内核缓冲区

bool Copy_From_User(void* destInKernel, ulong_t srcInUser, ulong_t bufSize) { //TODO(\

struct User_Context * UserContext = g_currentThread->userContext; //--: check if memory if validated

if (!Validate_User_Memory(UserContext,srcInUser, bufSize)) return false; //--:user->kernel

memcpy(destInKernel, UserContext->memory + srcInUser, bufSize); return true; }

----------------------------------------- //将内核态的进程复制到用户态

桂林电子科技大学综合设计说明书用纸 第 19 页

bool Copy_To_User(ulong_t destInUser, void* srcInKernel, ulong_t bufSize) { //TODO(\

struct User_Context * UserContext = g_currentThread->userContext; //--: check if memory if validated

if (!Validate_User_Memory(UserContext, destInUser, bufSize)) return false;

//--:kernel->user

memcpy(UserContext->memory + destInUser, srcInKernel, bufSize); return true; }

---------------------------------------- //切换到用户地址空间

void Switch_To_Address_Space(struct User_Context *userContext) { //TODO(\

ushort_t ldtSelector= userContext->ldtSelector;/* Switch to the LDT of the new user context */ __asm__ __volatile__ (\

}

================= kthread.c =============== 添加头文件 #include ---------------------------------- //创建一个用户进程

/*static*/ void Setup_User_Thread(struct Kernel_Thread* kthread, struct User_Context* userContext) { //TODO(\ ulong_t eflags = EFLAGS_IF;

unsigned csSelector=userContext->csSelector;//CS选择子 unsigned dsSelector=userContext->dsSelector;//DS选择子 Attach_User_Context(kthread, userContext);

//初始化用户态进程堆栈,使之看上去像刚被中断运行一样 //分别调用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 */

桂林电子科技大学综合设计说明书用纸 第 20 页

Push(kthread, 0); /* ebp */

//初始化数据段寄存单元

Push(kthread, dsSelector); /* ds */ Push(kthread, dsSelector); /* es */ Push(kthread, dsSelector); /* fs */ Push(kthread, dsSelector); /* gs */ }

//开始用户进程

struct Kernel_Thread* Start_User_Thread(struct User_Context* userContext, bool detached) { //TODO(\

struct Kernel_Thread* kthread = Create_Thread(PRIORITY_USER, detached); //为用户态进程

if (kthread != 0){

Setup_User_Thread(kthread, userContext); Make_Runnable_Atomic(kthread);

}

return kthread; }

================ syscall.c =================

//需在此文件别的函数前增加一个函数,函数名为Copy_User_String,它被函数Sys_PrintString调用,具体实现如下:

static int Copy_User_String(ulong_t uaddr, ulong_t len, ulong_t maxLen, char **pStr) { int rc = 0; char *str;

//超过最大长度 if (len > maxLen){ return EINVALID; }

//为字符串分配空间 str = (char*) Malloc(len+1); if (0 == str){ rc = ENOMEM; goto fail; }

//从用户空间中复制数据

if (!Copy_From_User(str, uaddr, len)){ rc = EINVALID; Free(str); goto fail; }

str[len] = '\\0'; //成功 *pStr = str;

桂林电子科技大学综合设计说明书用纸 第 21 页

fail:

return rc; }

-----------------------------------------

static int Sys_Exit(struct Interrupt_State* state) { //TODO(\ Exit(state->ebx); }

-----------------------------------------

static int Sys_PrintString(struct Interrupt_State* state) { //TODO(\ 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; }

----------------------------------------------

static int Sys_GetKey(struct Interrupt_State* state) { //TODO(\

return Wait_For_Key(); //返回按键码keyboard.c/Wait_For_Key() }

---------------------------------------------

static int Sys_SetAttr(struct Interrupt_State* state)

{ //TODO(\

Set_Current_Attr((uchar_t) state->ebx); return 0; }

---------------------------------------------

static int Sys_GetCursor(struct Interrupt_State* state) { //TODO(\ 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; }

桂林电子科技大学综合设计说明书用纸 第 22 页

-----------------------------------------------

static int Sys_PutCursor(struct Interrupt_State* state) { //TODO(\

return Put_Cursor(state->ebx, state->ecx) ? 0 : -1; }

-----------------------------------------------

static int Sys_Spawn(struct Interrupt_State* state) { //TODO(\ 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) {//从用户空间复制进程名称 goto fail; }

if(rc = Copy_User_String(state->edx, state->esi, 1023, &command)) != 0) {//从用户空间复制用户命令

goto fail; }

Enable_Interrupts(); //开中断

rc = Spawn(program, command, &process);//得到进程名称和用户命令后便可生成一个新进程 if (rc == 0) {//若成功则返回新进程ID号 KASSERT(process != 0); rc = process->pid; }

Disable_Interrupts();//关中断 fail://返回小于0的错误代码 if (program != 0) Free(program); if (command != 0) Free(command);

return rc; }

-----------------------------------------

static int Sys_Wait(struct Interrupt_State* state) { //TODO(\ int exitCode;

struct Kernel_Thread *kthread = Lookup_Thread(state->ebx); if (kthread == 0) return -12;

Enable_Interrupts();

exitCode = Join(kthread); Disable_Interrupts();

桂林电子科技大学综合设计说明书用纸 第 23 页

return exitCode;

}

---------------------------------------

static int Sys_GetPID(struct Interrupt_State* state) { //TODO(\ return g_currentThread->pid; }

================= main.c ================== static void Spawn_Init_Process(void) { //TODO(\ struct Kernel_Thread *pThread;

Spawn(\}

2.编译GeekOS项目project2

操作步骤同前,此处略。

5.5运行结果

操作步骤同前,此处略。 结果:

图 5.5 项目2运行结果

6 遇到问题及解决方法

虚拟系统在做计算机网络课设已经安装配置好,而在本次课设的各个软件安装上遇到了一些麻烦,安装顺序好像有讲究,如果没有首先安装NASM汇编器则安装其他软件时提示缺少系统包。Bochs安装过程也出现缺少很多系统包,通过挂载系统盘查询缺少的包,没有

桂林电子科技大学综合设计说明书用纸 第 24 页

的则去网上下,最后居然出现循环缺少的三个包,在网上找到一条命令同时安装三个包,具体过程由于没有截图和原网页已经不记得了。

在项目的操作过程,对代码的修改也比较容易出错,老师给的文档里具体有修改的方法。

7 课程设计总结

其实这次课设收获并不大,上学期的操作系统实验课要求并不高,我们在课后也没有太多的实践,以至于在实际操作上很欠缺,涉及到的具体操作步骤网上都有详细的介绍,代码也都有提供,只要稍微修改就可以用了。

不过通过这次课设还是对操作系统的进程调度,文件管理有了直观上的理解。项目2中涉及到进程,做的过程中也对进程有了一定了解。

在操作过程中思维一定要广,此路行不通要及时在网上查资料或者和同学交流,否则很容易浪费大把的时间在无用的琢磨。

桂林电子科技大学综合设计说明书用纸 第 24 页

的则去网上下,最后居然出现循环缺少的三个包,在网上找到一条命令同时安装三个包,具体过程由于没有截图和原网页已经不记得了。

在项目的操作过程,对代码的修改也比较容易出错,老师给的文档里具体有修改的方法。

7 课程设计总结

其实这次课设收获并不大,上学期的操作系统实验课要求并不高,我们在课后也没有太多的实践,以至于在实际操作上很欠缺,涉及到的具体操作步骤网上都有详细的介绍,代码也都有提供,只要稍微修改就可以用了。

不过通过这次课设还是对操作系统的进程调度,文件管理有了直观上的理解。项目2中涉及到进程,做的过程中也对进程有了一定了解。

在操作过程中思维一定要广,此路行不通要及时在网上查资料或者和同学交流,否则很容易浪费大把的时间在无用的琢磨。

本文来源:https://www.bwwdw.com/article/egi6.html

Top