GeekOS操作系统课程设计报告完美版.doc_第1页
GeekOS操作系统课程设计报告完美版.doc_第2页
GeekOS操作系统课程设计报告完美版.doc_第3页
GeekOS操作系统课程设计报告完美版.doc_第4页
GeekOS操作系统课程设计报告完美版.doc_第5页
已阅读5页,还剩22页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

编号: GeekOS操作系统的研究与实现 题 目:GeekOS操作系统的研究与实现系 别: 计算机科学与工程学院 专 业: 计算机科学与技术 学生姓名: 学 号: 指导教师: 注:高分操作系统课程设计报告,当初为我加了不少分哦!2012年3月17目 录1 实验目的12 项目的设计要求12.1 Project0项目设计要求12.2 Project1项目设计要求22.3 Project2项目设计要求23 开发环境的建立33.1 在虚拟机上安装Linux操作系统33.2 安装c/c+编译环境34 项目设计原理44.1 Project0设计原理44.2 Project1设计原理44.3 Project2设计原理65 项目设计的具体实现95.1 Project0项目的具体实现95.2 Project1项目的具体实现105.3 Project2项目的具体实现116 系统编译运行的原理及结果216.1 Project0运行结果216.2 Project1运行结果226.3 Project2运行结果237 遇到的问题及解决方法23八 课程设计总结24参考文献251 实验目的计算机操作系统是管理计算机系统软件、硬件资源,控制程序运行,改善人机界面,提供各种服务,合理组织计算机工作流程和为用户有效使用计算机提供良好的运行环境的系统软件,它为用户使用计算机提供一个方便、灵活、安全、可靠的工作环境,也是其他应用软件赖以存在的基础。不仅是高校计算机专业学生需要了解它,从事计算机行业的人员也需要深入了解它。由于目前高校开设的计算机操作系课程中偏重对理论知识的要求,实践环境有限,并偏重注重学生对一些经典算法的实践,学生并没有机会了解、实践操作系统的内部结构和实现技术。经过一个学期的操作系统理论学习,我们基本上掌握了操作系统的理论基础知识,但是,操作系统课程的内容不仅仅涉及理论、算法,更重要的是用技术去实现算法并对其加以实现和应用。 此次课程设计,通过搭建Linux实验平台,对专门为操作系统课程教学而设计的操作系统GeekOS的项目代码的补充和完善,从浅到深,由表面到内涵地去理解操作系统的设计思想,理解操作系统内核工作的基本原理。在完成项目的过程中,通过动手操作,使得我们能够在动手查阅资料、思考排难等探索性活动中进一步理解操作系统的抽象概念,并进一步理解操作系统复杂的结构和工作原理。 具体的来说,对于项目0,应该熟悉GeekOS的项目编译,调试和运行环境,掌握GeekOS的运行工作过程;对于项目1,应该熟悉ELF文件格式,了解GeekSO系统如何将ELF格式的用户可执行程序装入到内存,建立内核进程并运行的实现技术;对于项目2,扩充GeekOS操作系统内核,使得系统能够支持用户级进程的动态创建和执行。2 项目的设计要求2.1 Project0项目设计要求(1) 搭建GeekOS的编译和调试平台,掌握GeekOS的内核进程工作原理。(2) 熟悉键盘操作函数,编程实现一个内核进程。该进程的功能是:接受键盘输入的字符并显示在屏幕上,当按ctrl+D时,结束进程的运行。2.2 Project1项目设计要求1、修改/geekos/elf.c文件:在函数Parse_ELF_Executable( )中添加代码,分析ELF格式的可执行文件(包括分析得出ELF文件头、程序头,获取可执行文件长度,代码段、数据段等信息),并填充Exe_Format数据结构中的域值。2、在Linux环境下编译系统得到GeekOS镜像文件。3、编写一个相应的bochs配置文件。4、在bochs中运行GeekOS系统显示结果。2.3 Project2项目设计要求项目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) 。3 开发环境的建立3.1 在虚拟机上安装Linux操作系统GeekOS是一个基于X86架构的PC机上运行的微操作系统内核,内核简单却又兼备实用性,是一个用C语言开发的操作系统,可以运行在真正的X86PC硬件平台。每个项目需要在Linux环或者UNIX环境下对其进行功能扩充。Ubuntu 是一个由社区开发的基于linux的操作系统,其包含了我们一般所需的所有程序:无论是文字处理和电子邮件,还是 Web 服务和编程工具。所以选择其作为Linux系统进行安装。首先先安装VMware workstation,运行Vmware,新建一个虚拟机,以便安装Linux系统。然后在虚拟机上安装Ubuntu系统。系统安装的时候会默认安装了GCC,所以后面不必再重新安装GCC。3.2 安装c/c+编译环境(1)修改使用国内镜像更新源。在命令行终端中输入sudo gedit /etc/apt/sources.list,打开“文本编辑器编辑源列表”。在最后一行添加下列国内镜像更新源:deb-src /Ubuntu/ jaunty-updates main multiverse restricted universe 保存,即可。(2)终端中输入sudo apt-get update 重新获取软件包列表。(3)在终端输入 sudo apt-get intall build-essential 下载安装 build-essential包。(4)安装NASM在终端执行udo apt-get install nasm,(5)安装Bochs:在终端执行 sudo apt-get install bochs在终端执行 sudo apt-get install bochs-x4 项目设计原理4.1 Project0设计原理利用GeekOS提供的键盘处理函数keyboard.h与keyboard.c等进行键盘常用功能的模拟。其中,在keyboard.c里面提供了一个功用函数Keycode Wait_For_Key(void),循环等待一个键盘事件,然后返回一个16位的数据 Keycode型的, 在keyboard.h里定义了所有的键盘代码。Read_Key(Keycode* keycode)函数可以处理队列键盘按键,可以保存到队列中并输出。另外,需要重点关Start_Kernel_Thread函数。函数的参数startFunc是一个Thread_Start_Func类型的函数指针,其定义在kthread.h中:Typedef void (*Thread_Start_Func) (ulong_t,arg);该函数指针指向一个无返回值,参数为ulong_t类型的函数。函数的功能是以参数Start_Func指向的代码为进程体生成一个内核进程。再有,对于Read_Key()函数,其定义为:bool Read_Key(Keycode *keycode);作用是轮查键盘事,如果捕获到键盘事件,则返回true,并且将按键码保存到参数 keycode地址中。4.2 Project1设计原理 ELF(Executable and linking format)文件是UNIX系统实验室作为应用程序二进制接口而开发的可执行文件,是x86 Linux系统下的一种常用目标文件(object file)格式。ELF文件格式如下表1。表1 ELF目标文件格式连接程序视图 执行程序视图 ELF 头部ELF 头部 程序头部表(可选) 程序头部表 节区1 段 1 . 节区 n 段 2 . . . 节区头部表 节区头部表(可选) ELF文件在磁盘中的映象和在内存中的执行程序镜像的对应关系如下图:图1 ELF文件和内存中的可执行文件镜像内核进程的创建流程如下图:图2 内核进程流程图Parse_ELF_Excutable函数的定义为:int Parse_ELF_Executable(char *exeFileData,ulong_t exeFileLength,truct Exe_Format *exeFormat)参数:exeFileData已装入内存的可执行文件所占用空间的起始地址 exeFileLength可执行文件长度 exeFormat保存分析得到的elf文件信息的结构体指针根据ELF文件格式,用户可以从exeFileData指向的内容中得到ELF文件头,继续分析可以得到程序头,程序代码段等信息。4.3 Project2设计原理Geekos进程状态及转化: 图3 GeekOS进程状态转换GeekOS系统最早创建的内核进程有Idle、Reaper和Main三个进程,它们由Init_Scheduler函数创建:最先初始化一个核态进程mainThread,并将该进程作为当前运行进程,函数最后还调用Start_Kernel_Thread 函数创建了两个系统进程Idle和Reaper。 所以,Idle、Reaper和Main三个进程是系统中最早存在的进程。在GeekOS中为了区分用户态进程和内核进程,在Kernel_Thread结构体中设置了一个字段 userContext,指向用户态进程上下文。对于内核进程来说,这个指针为空,而用户态进程都拥有自己的用户上下文(User_Context)。因此,在GeekOS中要判断一个进程是内核进程还是用户态进程,只要通过userContext字段是否为空来判断就可以了。图10.1 用户态进程结构每个用户态进程都拥有属于自己的内存段空间,如:代码段、数据段、堆栈段等,每个段有一个段描述符(segment descriptor),并且每个进程有一个段描述符表(Local Descriptor Table),用于保存该进程的所有段描述符。操作系统中还设置一个全局描述符表(GDT,Global Descriptor Table),用于记录了系统中所有进程的ldt描述符。图10.2 GDT、LDT和User_Context的关系用户态进程创建LDT的步骤:(1)调用函数Allocate_Segment_Descriptor()新建一个LDT描述符;(2)调用函数Selector()新建一个LDT选择子;(3)调用函数Init_Code_Segment_Descriptor()新建一个文本段描述符;(4)调用函数Init_Data_Segment_Descriptor()新建一个数据段;(5)调用函数Selector()新建一个数据段选择子;(6)调用函数Selector()新建一个文本(可执行代码)段选择子。用户态进程创建流程:5 项目设计的具体实现5.1 Project0项目的具体实现在project/project0/src/geekos/main.c中添加一个函数。函数的功能是:接收键盘输入的按键,并将键值显示到显示器的函数,当输入Ctrl+D就退出。函数代码如下:= 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; /低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 : asciiCode); = (2) 首先注释Main()函数中TODO宏,并调用Start_Kernel_Thread函数,将步骤1编写的函数地址传递给参数,建立一个内核级进程 struct Kernel_Thread *thread; thread = Start_Kernel_Thread(&project0,0,PRIORITY_NORMAL,false);(3) 执行make depend及make命令,此时会在build目录下生成fd.img(4) 编写brochs 配置文件vgaromimage: file=/usr/share/bochs/VGABIOS-lgpl-latestromimage: file=/usr/share/bochs/BIOS-bochs-latest, address=0xf0000megs: 8boot: afloppya: 1_44=fd.img, status=inserted#floppya: 1_44=fd_aug.img, status=insertedlog: ./bochs.outkeyboard_serial_delay: 200floppy_command_delay: 500vga_update_interval: 300000ips: 1000000mouse: enabled=0private_colormap: enabled=0i440fxsupport: enabled=05.2 Project1项目的具体实现修改project/project1/src/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(exeFileLengthhead-ehsize+head-phentsize*head-phnum); KASSERT(head-entry%4=0); exeFormat-numSegments=head-phnum; exeFormat-entryAddr=head-entry; for(i=0;iphnum;i+) exeFormat-segmentListi.offsetInFile=proHeader-offset; exeFormat-segmentListi.lengthInFile=proHeader-fileSize; exeFormat-segmentListi.startAddress=proHeader-vaddr; exeFormat-segmentListi.sizeInMemory=proHeader-memSize; exeFormat-segmentLtFlags=proHeader-flags; proHeader+;return 0;=2、编译,成功后生成两个镜像文件:fd.img和diskc.img。其中,Diskc.img为模拟器能引导的操作系统镜像 。 3、编写相应的bochs配置文件 由于生成了diskc.img,因此配置文件需加上以下内容config_interface: textconfigmegs: 8vgaromimage: file=$BXSHARE/VGABIOS-lgpl-latestromimage: file=$BXSHARE/BIOS-bochs-latestfloppya: 1_44=./fd.img, status=insertedata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14ata1: 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=9ata0-master: type=disk, path=diskc.img, mode=flat, cylinders=40, heads=8, spt=64#ata0-slave: type=cdrom, path=/dev/cdrom, status=insertedboot: aips: 1000000log:./bochs.outvga_update_interval: 300000keyboard_serial_delay: 250keyboard_paste_delay: 100000private_colormap: enabled=0 5.3 Project2项目的具体实现1、添加代码= user.c =/产生一个进程(用户态)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; /标记各函数的返回值,为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(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将可执行程序的程序段和数据段装入内存 Print(Failed to Load User Program!n); goto fail; /在堆分配方式下释放内存并再次初始化exeFileData Free(exeFileData); exeFileData = 0;/* 开始用户进程,调用Start_User_Thread函数创建一个进程并使其进入准备运行队列*/ process = Start_User_Thread(userContext, false); if (process != 0) /不是核心级进程(即为用户级进程) KASSERT(process-refCount = 2);/* 返回核心进程的指针 */ *pThread = process; rc = process-pid;/记录当前进程的ID else/超出内存 project2includegeekoserrno.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(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为0表示此进程为核心态进程就不用切换地址空间 return; if (userContext != s_currentUserContext) ulong_t esp0; /if (userDebug) Print(A%pn, kthread); Switch_To_Address_Space(userContext);/为用户态进程时则切换地址空间 esp0 = (ulong_t) kthread-stackPage) + PAGE_SIZE; /if (userDebug) / Print(S%lxn, esp0);/* 新进程的核心栈. */ Set_Kernel_Stack_Pointer(esp0);/设置内核堆栈指针/* New user context is active */ s_currentUserContext = userContext; = elf.c =同 project1= userseg.c =/需在此文件各函数前增加一个函数,此函数的功能是按给定的大小创建一个用户级进程上下文,具体实现如下:/函数功能:按给定的大小创建一个用户级进程上下文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-ldt0, (ulong_t) UserContext-memory, size / PAGE_SIZE, USER_PRIVILEGE ); /新建一个数据段 Init_Data_Segment_Descriptor( &UserContext-ldt1, (ulong_t) UserContext-memory, size / PAGE_SIZE, USER_PRIVILEGE ); /新建数据段和文本段选择子 UserContext-csSelector = Selector(USER_PRIVILEGE, false, 0); UserContext-dsSelector = Selector(USER_PRIVILEGE, false, 1); /将引用数清0 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(Destroy a User_Context); /释放占用的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(Load a user executable into a user memory space using segmentation); int i; ulong_t maxva = 0;/要分配的最大内存空间 unsigned numArgs;/进程数目 ulong_t argBlockSize;/参数块的大小 ulong_t size, argBlockAddr;/参数块地址 struct User_Context *userContext = 0; /计算用户态进程所需的最大内存空间 for (i = 0; i numSegments; +i) /elf.h struct Exe_Segment *segment = &exeFormat-segmentListi; 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 numSegments; +i) struct Exe_Segment *segment = &exeFormat-segmentListi; /根据段信息将用户程序中的各段内容复制到分配的用户内存空间 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(Copy memory from user buffer to kernel buffer); 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;-/将内核态的进程复制到用户态bool Copy_To_User(ulong_t destInUser, void* srcInKernel, ulong_t bufSize) /TODO(Copy memory from kernel buffer to user buffer) 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(Switch to user address space using segmentation/LDT); ushort_t ldtSelector= userContext-ldtSelector;/* Switch to the LDT of the new user context */ _asm_ _volatile_ (lldt %0:a(ldtSelector); = kthread.c =添加头文件 #include -/创建一个用户进程/*static*/ 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=userC

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论