GeekOS操作系统实验报告.doc_第1页
GeekOS操作系统实验报告.doc_第2页
GeekOS操作系统实验报告.doc_第3页
GeekOS操作系统实验报告.doc_第4页
GeekOS操作系统实验报告.doc_第5页
已阅读5页,还剩14页未读 继续免费阅读

下载本文档

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

文档简介

GeekOS操作系统的研究与实现 题 目: GeekOS操作系统的研究与实现 系 别: 计算机科学与工程学院 专 业: xxxxx 学生姓名: xxxxx 学 号: xxxxx 指导教师: xxxxx 2011年 6 月 30 日GeekOS操作系统的研究与实现一、 实验目的计算机操作系统是管理计算机系统软件、硬件资源,控制程序运行,改善人机界面,提供各种服务,合理组织计算机工作流程和为用户有效使用计算机提供良好的运行环境的系统软件,它为用户使用计算机提供一个方便、灵活、安全、可靠的工作环境,也是其他应用软件赖以存在的基础。不仅是高校计算机专业学生需要了解它,从事计算机行业的人员也需要深入了解它。由于目前高校开设的计算机操作系课程中偏重对理论知识的要求,实践环境有限,并偏重注重学生对一些经典算法的实践,学生并没有机会了解、实践操作系统的内部结构和实现技术。GeekOs操作实验是一个用C语言编写开发的操作系统,学生可以在Linux或UNIX环境下对其进行功能扩展。通过本次实验,学生可以深入了解计算机操作系统的概念和作用;理解PC启动原理,深入体会计算机启动过程,包括操作系统的引导、内核程序的导入;进一步了解和参与实践操作系统进程的管理,理解进程的状态的转换、进程的调度等;通过本实验,学生还可以理解存储器分段分页管理机制和文件系统相关的概念和技术。本次实验目的的具体目的为:熟悉GeekOS的项目编译、调试和运行环境,掌握GeekOS工作过程;熟悉ELF文件格式,了解GeekOS系统如何将ELF格式的用户可执行文件程序装入到内存,建立内核进程并运行的实现技术;扩充GeekOS操作系统内核,使得系统支持用户进程的动态创建和执行。学生参与到操作系统的开发工作中,实现操作系统的功能改进或再开发,以增加学生对操作系统核心技术的时间,达到理论与实践相结合。二、 设计环境GeekOS是一个基于X86架构的PC上运行的微操作系统内核,由美国马理兰大学的教师开发,主要用于操作系统课程设计,目的是使学生能够实际动手参与到一个操作系统的开发工作中。出于教学目的,这个系统内核设计简单,却又兼备实用性,它可以运行在真正的X86 PC硬件平台。作为一个课程设计平台,GeekOS由一个基本的操作系统内核作为基础,提供了操作系统与硬件之间的所有必备接口,实现了系统引导、实模式到保护模式的转换、中断调用及异常处理、基于段式的内存管理、FIFO进程调度算法以及内核进程、基本的输入输出(键盘作为输入设备,显示器作为输出设备),以及一个用于存放用户程序的只读文件系统PFAT。 本实验要求学生使用GeekOS作为操作系统框架,学生可以使用Linux或者UNIX环境对GeekOS其进行扩充,也可以使用Windows下的Cygwin工具进行开发。三、 项目设计要求为达到实验目的效果,实验要求针对进程管理等操作系统核心内容进行相应的3项目要求。其项目和要求为:1. project01) 建GeekOs的编译和调试平台,掌握GeekOs的内核进程工作原理;2) 熟悉键盘操作函数,编程实现一个内核进程。该进程的功能是:接收键盘输入的字符并显示到屏幕中,当输入Ctrl+D时,结束进程运行。2. project11) 修改/geekos/elf.c文件:在函数Parse_ELF_Executable()中添加代码,分析ELF格式文件(包括分析得出ELF文件头、程序头,获取可执行文件长度,代码段、数据段等信息),并填充Exe_Format数据结构中的域值。2) 掌握GeekOs在核心态用户程序的原理,为实现项目2的实现做准备。3. project2本项目需要阅读/src/geekos目录中的entry.c、lowlevel.asm、kthread.c、userseg.c,其中在userseg.c中主要关注Destroy_User_Context()和Load_User_Program()两个函数。项目要求为:1)user.c:完成函数Spawn()和Switch_To_User_Context()。2)elf.c:完成函数Parse_ELF_Executable(),要求与项目1相同。3)userseg.c:完成函数Destroy_User_Context()、Load_User_Program()、Copy_From_User()、Copy_To_User()和Switch_To_Address_Space()。4)kthread.c:完成函数Setup_User_Thread()和Start_User_Thread()。5)syscall.c:完成函数Sys_Exit()、Sys_PrintString()、Sys_GetKey()、Sys_SetAttr()、S s_GetCursor()、Sys_PutCursor()、Sys_Spawn()、Sys_Wait()和Sys_GetPID()。6)main.c:改写Spawn_Init_Process(void),改写时将“/c/shell.exe”作为可执行文件传递给Spawn函数的program参数,创建第一个用户态进程,然后由它来创建其它进程。四、 项目设计原理1. project0Cygwin是一个在Windows平台上运行的UNIX模拟环境,是Cygnus Solution公司开发的自由软件。Cygwin把gcc、gdb、gas等开发工具进行改进,使它们能生成并解释Win32目标文件,然后写了一个共享库,把Win32中没有的UNIX风格的调用封装在里面,把封装的工具源代码与共享库连接在一起,皆可以使用类UNIX主机上的交叉编译器来生成Windows平台上运行的工具集。因此,可以使用Windows上的Cygwin作为开发环境。要创建开发环境,也可以使用Linux作为GeekOS开发调试环境,可以使用Red Hat 7.0以上的Linux版本。而安装Linux平台可以直接安装Linux操作系统到主机上,也可以先在Windows上安装VMware虚拟机,在通过在VMware虚拟机安装Linux操作系统。拥有可以编译GeekOS的操作环境,还需要一个可以调试GeekOS的PC虚拟机,由于目前GeekOS只能支持在Bochs虚拟机上运行,因此,需要安装Bochs。项目0中,要求实现接收键盘输入的字符并显示到屏幕中的内核进程。而键盘设备驱动程序提供了一系列的高级接口来使用键盘。键盘事件的逻辑关系为:用户按键引发键盘中断,根据是否按下Shift键,分别在键值表中寻找扫描码对应的按键值,经过处理后将键值放入键盘缓冲区s_queue中,最后通知系统重新调度进程。若用户进程需要从键盘输入信息,可调用Wait_For_Key()函数,该函数首先检查键盘缓冲区是否有按键。如果有,就读取一个键码,如果此时键盘缓冲区中没有按键,就将进程放入键盘事件等待队列s_waitQueue,由于按键触发了键盘中断,键盘中断处理函数Keyboard_Interrupt_Handler就会读取用户按键,将低级键扫描码转换为含ASCII字符的高级代码,并刷新键盘缓冲区,最后唤醒等待按键的进程继续运行。2. project1项目2要求熟悉ELF文件格式,了解GeekOS系统如何将ELF格式的用户可执行文件程序装入到内存,并建立内核进程并运行的实现技术。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文件头,继续分析可以得到程序头,程序代码段等信息。3. project2进程是在计算机系统引入多道程序设计技术后,为描述和控制系统内部各程序运行而引入的一个概念。后来为提高程序运行的并行度和系统的执行效率,又引进了线程的概念。而GeekOS的线程实质上是进程,GeekOS的内核只提供核心级进程,但可以开发扩展使之支持核心级进程和用户级进程。、GeekOS进程状态及转化在操作系统中,可以多个任务可以共享CPU,但一个进程在整个生命周期中有时候能占有CPU,有时候并不能占用CPU。GeekOS系统中,进程在一个生命周期中分为3中不同的状态。即当前运行态、准备运行态和等待状态。其住哪个台之间的转化如图3所示图3 GeekOS进程状态转换、GeekOS初始化的进程在GeekOS中最早初始化的Main进程,由Main执行初始化调度函数Init_Scheduler()来执行Start_Kernel_Thread()函数来创建Idle和Reaper。其中Idel实际上什么都没做,但它的作用是保证准备运行的进程队列中有可调度的进程。而Reaper是负责消亡进程的善后工作,负责释放消亡进程所占有资源,内存、堆栈等等。、进程的调度GeekOS会在以下几种情况下调度进程切换:1)时间片用完;2)执行内核进程Idel;3)进程退出调用Exit函数时;4)进入等待状态Wait函数被调用时。后三中情况都会调用Sceduler()函数。而Sceduler()函数是调用底层操作lowllevel.asm里的Switch_To_Thread函数切换内核进程。用户进程的调度比内核江南城调度复杂,原因是用户进程又有自己的地址空间,并不在内核的代码段范围内。用户进程的切换同样适用到Switch_To_Thread函数,但在此过程中多了对Activate_User_Context的调用,负责检测当前进程是否为用户进程,若是则切换到用户进程空间。、进程调度的策略GeekOS的初始进程调度策略是时间片轮换调度,所有准备运行的进程都放在一个FIFO的队列里,按先来先出的顺序排队。当发生进程调度时,系统在这个队列中查找最高级别的进程进入运行。五、 项目设计的具体实现1. project0、在Windows上安装VMware从网上下载VMware,笔者下载的是汉化版的VMware,压缩包内含有免费的License。解压压缩包,按照提示完成安装。值得注意的一点是,在设置虚拟磁盘时,建议初学者使用Create a new virtual disk。、在虚拟机VMware安装Linux安装VMware完成后,当点击VMware上的开机按钮时,虚拟机将开机运行,但可以看到机器没有操作系统无法继续运行。本实验采用Red Hat 9.0作为操作系统安装虚拟机。具体安装步骤在此不做详细说明。但值得一提的是在“选择软件包组”的时候,必须选择在“开发工具”里勾选GCC和NASM以保证编译环境正确建立。、Bochs PC模拟器的安装GeekOS是运行于Linux下的Bochs PC模拟器上,Bochs是使用C+开发的可一直的IA-32 PC模拟器,几乎可以运行在所有主流平台上。a.从/ 上下载一个 bochs-2.1.1.i386.rpm,b.开始安装,如:rootlocalhost sh# rpm -ivh bochs-2.1.1.i386.rpmPreparing. # 100% 1:bochs # 100%rootlocalhost sh# 已经安装目录/usr/share/bochs下。c. 编辑 .bochsrc 文件:# An example .bochsrc file.# You will need to edit these lines to reflect your system.vgaromimage: /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=0#newharddrivesupport: enabled=1# Uncomment this to write all bochs debugging messages to# bochs.out. This produces a lot of output, but can be very# useful for debugging the kernel.#debug: action=report到此bochs安装完毕。、编写键盘功能函数在/project0/src/geekos/main.c的代码文件中添加一个函数,主要是用于接收键盘输入,并显示已输入字符串的功能。函数名为project0(),其代码为:void project0() Print(To Exit hit Ctrl + d.n); int i=0; 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 /统计字数 if(asciiCode != r) Print(%c,keycode);i+; else Print(%s%d%s,n You have press(, i ,) keysn); i=0; 然后再main函数中作相应的修改,即在TODO(Start a kernel thread to echo pressed keys and print counts);下面添加程序为:struct Kernel_Thread *thread;thread = Start_Kernel_Thread(&project0,0,PRIORITY_NORMAL,false);并把TODO提示语句给注释掉。2. project1该项目需要实现对/geekos/elf.c文件,在函数Parse_ELF_Excuteable()中添加代码。其代码实现为: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;3. project2、修改user.c文件的Spawn()函数,生成一个新的用户级进程,其代码: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;、src/geekos/user.c中的Swith_To_User_Context()函数,其实现代码为: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为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; 、src/geekos/userseg.c中的一些实现对user.c中搞成操作支持的函数。Load_User_Program函数,用于加载用户程序: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;/成功Copy_From_Use函数: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; Validate_User_Memory(NULL,0,0); /* delete this; keeps gcc happy */Copy_To_User函数:bool Copy_To_User(ulong_t destInUser, void* srcInKernel, ulong_t bufSize) /* * Hints: same as for Copy_From_User() */ /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;Switch_To_Address_Space函数: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);Destroy_User_Context函数,用于撤销用户上下文,其代码为: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;、实现/src/geekos/kthread.c中的Start_User_Thread和Setup_User_Thread函数。Start_User_Thread(struct User_Context* userContext, bool detached) /TODO(Start user thread); struct Kernel_Thread* kthread = Create_Thread(PRIORITY_USER, detached); /为用户态进程 if (kthread != 0) Setup_User_Thread(kthread, userContext); Make_Runnable_Atomic(kthread); return kthread;static void Setup_Kernel_Thread(struct Kernel_Thread* kthread,Thread_Start_Func startFunc, ulong_t arg) Push(kthread, arg); Push(kthread, (ulong_t) &Shutdown_Thread); /* Push the address of the start function. */ Push(kthread, (ulong_t) startFunc); Push(kthread, 0UL); /* EFLAGS */ Push(kthread, KERNEL_CS); Push(kthread, (ulong_t) &Launch_Thread); /* Push fake error code and interrupt number. */ Push(kthread, 0); Push(kthread, 0); /* Push initial values for general-purpose registers. */ Push_General_Registers(kthread); Push(kthread, KERNEL_DS); /* ds */ Push(kthread, KERNEL_DS); /* es */ Push(kthread, 0); /* fs */ Push(kthread, 0); /* gs */、实现syscall.c文件中的相应函数a. Sys_PrintString函数:static int Sys_PrintString(struct Interrupt_State* state) /TODO(PrintString system call); 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;b. Sys_GetKey函数:Sys_GetKey(struct Interrupt_State* state) / TODO(GetKey system call); return Wait_For_Key(); /返回按键码keyboard.c/Wait_For_Key()c. Sys_SetAttr函数:static int Sys_SetAttr(struct Interrupt_State* state) /TODO(SetAttr system call); Set_Current_Attr(uchar_t) state-ebx); return 0;d. Sys_GetCursor函数:static int Sys_GetCursor(struct Interrupt_State* state) / TODO(GetCursor system call); 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;e. Sys_Spawn函数:tatic int Sys_Spawn(struct Interrupt_State* state) / TODO(Spawn system call); 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;f. Sys_Wait函数:static

温馨提示

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

评论

0/150

提交评论