一、项目设计目的熟悉GeekOS的项目编译、调试和运行环境,掌握GeekOS运行工作过程。二、项目设计要求1、搭建GeekOS的编译和调试平台,掌握GeekOS的内核进程工作原理。2、熟悉键盘操作函数,编程实现一个内核进程。该进程的功能是:接收键盘输入的字符并显示到屏幕上,当输入ctrl+d时,结束进程的运行。第八章设计项目0三、项目0的实现主要由以下步骤完成(在项目0的/src/geekos/main.c中完成):1.编写一个C语言函数,函数功能是:接收键盘输入的按键,并将键值在显示器显示出来,当输入ctrl+d就退出;2.在Main函数体内调用Start_Kernel_Thread函数,将步骤1编写的函数地址传递给参数startFunc,利用Setup_Kernel_Thread函数建立一个待运行的线程。3.在Linux环境下编译系统得到GeekOS镜像文件。4.编写一个相应的bochs配置文件。5.在bochs中运行GeekOS系统显示结果。第九章设计项目1一、项目设计目的熟悉ELF文件格式,了解GeekOS系统如何将ELF格式的可执行程序装入到内存,建立内核进程并运行的实现技术。二、项目设计要求1、修改/geekos/elf.c文件:在函数Parse_ELF_Executable()中添加代码,分析ELF格式的可执行文件(包括分析得出ELF文件头、程序头,获取可执行文件长度,代码段、数据段等信息),并填充Exe_Format数据结构中的域值。2、在Linux环境下编译系统得到GeekOS镜像文件。3、编写一个相应的bochs配置文件。4、在bochs中运行GeekOS系统显示结果。1、ELF文件格式三、项目设计提示表1ELF目标文件格式连接程序视图执行程序视图ELF头部ELF头部程序头部表(可选)程序头部表节区1段1...节区n段2.........节区头部表节区头部表(可选)2、内存中的可执行文件镜像ELFheaderCodeDatasectionheaderCodesectionheaderDataCodeoffsetDataoffsetDatasizeCodesizeCodeDataStack(4096)ELF文件镜像内存中执行镜像3、内核线程的建立流程Spawn_Init_Process()Start_Kernel_Thread()Spawner()Read_Fully()Parse_ELF_Excutable()Spawn_Program()根据Exe_Format中的Exe_Segment结构提供的用户程序段信息,及用户进程堆栈大小计算用户进程所需的最大内存空间,即要分配给用户进程的内存空间;为用户程序分配内存空间,并全部初始化为零,否则系统后面运行可能出错;根据段信息将用户程序中的各段内容复制到分配的用户内存空间。根据Exe_Segment提供的用户段信息初始化代码段、数据段以及堆栈段的段描述符和段选择子。4、Spawn_Program函数的功能intParse_ELF_Executable(char*exeFileData,ulong_texeFileLength,structExe_Format*exeFormat)参数:exeFileData——已装入内存的可执行文件所占用空间的起始地址exeFileLength——可执行文件长度exeFormat——保存分析得到的elf文件信息的结构体指针根据ELF文件格式,用户可以从exeFileData指向的内容中得到ELF文件头,继续分析可以得到程序头,程序代码段等信息。5、Parse_ELF_Excutable函数第十章设计项目2一、项目设计目的扩充GeekOS操作系统内核,使得系统能够支持用户级进程的动态创建和执行。二、项目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)。1、GeekOS进程状态及转换三、项目设计提示currentrunwait出现需要等待的事件等待的事件发生调度时间片到等图5.1GeekOS进程状态转换GeekOS系统最早创建的内核进程有Idle、Reaper和Main三个进程,它们由Init_Scheduler函数创建:最先初始化一个核态进程mainThread,并将该进程作为当前运行进程,函数最后还调用Start_Kernel_Thread函数创建了两个系统进程Idle和Reaper。所以,Idle、Reaper和Main三个进程是系统中最早存在的进程。新建GeekOS的内核进程对象include/kthread.h中定义,具体结构如下:structKernel_Thread{ulong_tesp;//进程的内核堆栈esp指针volatileulong_tnumTicks;//计时器intpriority;//进程优先级DEFINE_LINK(Thread_Queue,Kernel_Thread);//指针指向进程队列下一进程void*stackPage;//内核堆栈页指针structUser_Context*userContext;//用户进程上下文structKernel_Thread*owner;//父进程指针intrefCount;//引用计数boolalive;//是否活跃structThread_QueuejoinQueue;//加入队列intexitCode;//返回代码intpid;//进程IDDEFINE_LINK(All_Thread_List,Kernel_Thread);//全局进程链表指针#defineMAX_TLOCAL_KEYS128constvoid*tlocalData[MAX_TLOCAL_KEYS];//本地信息intcurrentReadyQueue;//进程当前所在的运行队列的索引编号boolblocked;//是否被阻塞};2、GeekOS的用户态进程在GeekOS中为了区分用户态进程和内核进程,在Kernel_Thread结构体中设置了一个字段userContext,指向用户态进程上下文。对于内核进程来说,这个指针为空,而用户态进程都拥有自己的用户上下文(User_Context)。因此,在GeekOS中要判断一个进程是内核进程还是用户态进程,只要通过userContext字段是否为空来判断就可以了。图10.1用户态进程结构User_Context结构结构定义如下(在“include/geekos/user.h”中定义):structUser_Context{#defineNUM_USER_LDT_ENTRIES3structSegment_Descriptorldt[NUM_USER_LDT_ENTRIES];//用户LDTstructSegment_Descriptor*ldtDescriptor;//LDT描述符char*memory;//指向用户空间ulong_tsize;//用户空间的大小ushort_tldtSelector;//ldt选择子ushort_tcsSelector;//cs选择子ushort_tssSelector;//ss选择子ushort_tdsSelector;//ds选择子pde_t*pageDir;//页表指针ulong_tentryAddr;//用户程序入口地址ulong_targBlockAddr;//参数块地址ulong_tstackPointerAddr;//用户态进程的堆栈指针intrefCount;//引用数structFile*fileList[USER_MAX_FILES];//打开文件列表intfileCount;//打开文件计数};4、用户态进程空间每个用户态进程都拥有属于自己的内存段空间,如:代码段、数据段、堆栈段等,每个段有一个段描述符(segmentdescriptor),并且每个进程有一个段描述符表(LocalDescriptorTable),用于保存该进程的所有段描述符。操作系统中还设置一个全局描述符表(GDT,GlobalDescriptorTable),用于记录了系统中所有进程的ldt描述符。图10.2GDT、LDT和User_Context的关系(1)调用函数Allocate_Segment_Descriptor()新建一个LDT描述符;(2)调用函数Selector()新建一个LDT选择子;(3)调用函数Init_Code_Segment_Descriptor()新建一个文本段描述符;(4)调用函数Init_Data_Segment_Descriptor()新建一个数据段;(5)调用函数Selector()新建一个数据段选择子;(6)调用函数Selector()新建一个文本(可执行代码)段选择子。5、用户态进程创建LDT的步骤3、用户态进程创建流程Spawn()Read_Fully()Parse_ELF_Excutable()Start_User_Thread()Setup_User_Thread()Load_User_Program()Attach_User_Context()4、Spawn函数的功能intSpawn(constchar*program,constchar*command,structKernel_Thread**pThread)参数说明:Program对应的是要读入内存缓冲区的可执行文件,Command是用户执行程序执行时的命令行字符串,pThread是存放指向刚创建进程的指针。Spawn函数主要完成的主要功能是:(1)调用Read_Fully函数将名为program的可执行文件全部读入内存缓冲区。(2)调用Parse_ELF_Executable函数,分析ELF格式文件。