目的
熟悉ELF文件格式,了解GeekOS系统如何将ELF格式的可执行程序装入到内存,建立内核进程并运行的实现技术。
要求
1) 修改/geekos/elf.c文件:在函数Parse_ELF_Executable( )中添加代码,分析ELF格式的可执行文件(包括分析得出ELF文件头、程序头,获取可执行文件长度,代码段、数据段等信息),并填充Exe_Format数据结构中的域值。
2) 在Linux环境下编译系统得到GeekOS镜像文件。
3) 编写一个相应的bochs配置文件。
4) 在bochs中运行GeekOS系统显示结果。
分析
1、ELF文件格式
2、内核线程的建立流程
1) Spawn_Init_Process函数的功能是调用Start_Kernel_Thread(),它以Spawner函数为进程体建立一个核心级进程,并使之准备就绪。
2) Start_Kernel_Thread函数通过调用Create_Thread函数、Setup_Kernel_Thread、Make_Runnable_Atomic来创建内核进程。
*Create_Thread函数主要功能为通过Alloc_Page函数为Kernel_Thread结构体和内核进程栈区分配内存空间,并通过Init_Thread函数初始化Kernel_Thread结构体。最后将该进程结构体通过Add_To_Back_Of_All_Thread_List函数加入进程队列末尾。
*Setup_Kernel_Thread函数主要是初始化进程栈区,包括参数地址、返回地址、入口地址、寄存器值等。
*Make_Runnable_Atomic函数将该进程设置为就绪态,加入等待执行队列。
3) Spawner函数运行过程为:通过Read_Fully函数将ELF可执行文件读入内存缓冲区;通过Parse_ELF_Executable函数解析ELF文件, 并通过Spawn_Program函数执行ELF文件。最后通过Free函数释放内存缓冲区。
4) Spawn_Program函数根据Exe_Format中的Exe_Segment结构提供的用户程序段信息及用户进程栈大小计算用户进程所需的内存大小,分配对应的内存空间,并全部初始化为零;根据Exe_Segment中的segmentList数组,将数据段和代码段从内存缓冲区复制到用户内存空间;生成数据段和代码段的段描述符、段选择子;通过Trampoline函数执行用户进程。
函数Spawn_Program在/src/geekos/lprog.c文件中已经实现。参数的数据结构Exe_Format和Exe_Segment的定义在elf.h中。如下:
struct Exe_Format{
struct Exe_Segment segmentList[EXE_MAX_SEGMENTS];
int numSegments; //定义了ELF文件中段的个数
ulong_t entryAddr; //代码入口地址
};
struct Exe_Segment{
ulong_t offsetInFile; //段在可执行文件中的偏移值
ulong_t lengthInFile; //段在可执行文件中的长度
ulong_t startAddress; //段在内存中的起始地址
ulong_t sizeInMemory; //段在内存中的大小
int protFlags; //保护标志
};
3、Parse_ELF_Excutable函数
由于系统已经实现了内核的所有函数,本项目设计只需完成Parse_ELF_Executable即可。Parse_ELF_Excutable函数声明为:
int Parse_ELF_Executable(char *exeFileData, ulong_t exeFileLength,struct Exe_Format *exeFormat)
参数:exeFileData——已装入内存的可执行文件所占用空间的起始地址
exeFileLength——可执行文件长度
exeFormat——保存分析得到的elf文件信息的结构体指针
根据ELF文件格式,用户可以从exeFileData指向的内容中得到ELF文件头,继续分析可以得到程序头,程序代码段等信息。
4、a.c文件
在项目1的./src/user目录下有一个a.c文件,编译GeekOS后(即make后)可以得到可执行程序a.exe,并写进PFAT文件系统,路径为c/a.exe。项目将此作为待装入的可执行文件,启动Bochs运行a.exe。
在a.c文件中,既有全局变量也有局部变量,这里还要注意一下局部变量如何输出。
实现
1)编写一个相应的bochs配置文件。
cd ~/geekos-0.3.0/src/project1/build/
gedit .bochsrc
megs: 8
boot: a
floppya: 1_44=fd.img, status=inserted
Log: ./bochs.out
ata0-master: type=disk, path=diskc.img, mode=flat, cylinders=40, heads=8, spt=64
2)修改/project1/src/geekos/elf.c 文件中的 Parse_ELF_Executable()函数,实现对 ELF 格式文件的分析,并将分析结果保存到 Exe_Format 结构体中,以便系统使用,实现代码如下:
int Parse_ELF_Executable(char *exeFileData, ulong_t exeFileLength,struct Exe_Format *exeFormat)
{
//利用ELF头部结构体指向可执行文件头部,便于获取相关信息
elfHeader *ehdr = (elfHeader*)exeFileData;
//段的个数
exeFormat->numSegments = ehdr->phnum;
//代码入口地址
exeFormat->entryAddr = ehdr->entry;
//获取头部表在文件中的位置,便于读取信息
programHeader *phdr = (programHeader*)(exeFileData + ehdr->phoff);
//填充Exe_Segment
unsigned int i;
for(i = 0; i < exeFormat->numSegments; i++, phdr++)
{
struct Exe_Segment *segment = &exeFormat->segmentList[i];
//获取该段在文件中的偏移量*
segment->offsetInFile = phdr->offset;
//获取该段的数据在文件中的长度
segment->lengthInFile = phdr->fileSize;
//获取该段在用户内存中的起始地址
segment->startAddress = phdr->vaddr;
//获取该段在内存中的大小
segment->sizeInMemory = phdr->memSize;
//获取该段的保护标志位
segment->protFlags = phdr->flags;
}
return 0;
}
3)修改/project1/src/geekos/lprog.c 文件中的 Spawn_Program()函数,将其中的 virtSize 局部变量修改为静态全局变量,即在文件头部添加“static unsigned long virtSize;”一行代码。
4)修改 lprog.c 文件中的 Printrap_Handler()函数,将其改为如下内容:
static void Printrap_Handler( struct Interrupt_State* state )
{
char *msg;
/* 此处修改以下内容以正确显示 a.c 中的局部变量 */
if (state->eax <= virtSize)
msg = (char *)virtSpace + state->eax;
else
msg = (char *)state->eax;
print(msg);
g_needReschedule = true;
return;
}
5) 进入“/project1/build/”目录,在终端中执行“bochs”命令,启动运行后得到如下结果:
如图所示,运行后,屏幕显示a.c文件中的输出内容以及spawner函数中的输出内容,项目一设计完成。
附完整代码下载链接:GeekOS课程设计-project1-CSDN下载 http://download.csdn.net/download/qq_35008279/10190758