中国科学院大学操作系统考试思考题答案.docx

上传人:h**** 文档编号:1083028 上传时间:2018-12-02 格式:DOCX 页数:11 大小:215.75KB
下载 相关 举报
中国科学院大学操作系统考试思考题答案.docx_第1页
第1页 / 共11页
中国科学院大学操作系统考试思考题答案.docx_第2页
第2页 / 共11页
中国科学院大学操作系统考试思考题答案.docx_第3页
第3页 / 共11页
中国科学院大学操作系统考试思考题答案.docx_第4页
第4页 / 共11页
中国科学院大学操作系统考试思考题答案.docx_第5页
第5页 / 共11页
点击查看更多>>
资源描述

1、1. 为什么计算机启动最开始的时候执行的是 BIOS 代码而不是操作系统自身的代码?答:通常我们用 C 语言写的用户程序,必须在操作系统的平台上执行,即操作系统为应用程序创建进程并把应用程序的可执行代码加载到内存。计算机启动的时候,操作系统并没有在内存中,我们首先要把操作系统加载到内存,而这个工作最开始的部分,就是由 bios 程序来实现的。所以计算机启动最开始执行的是 bios 代码2. 为什么 BIOS 只加载了一个扇区,后续扇区却是由 bootsect 代码加载?为什么 BIOS 没有把所有需要加载的扇区都加载?答:对 BIOS 而言, “约定”在接到启动操作系统的命令后, “定位识别”

2、只从启动扇区把代码加载到 0x7c00 这个位置。后续扇区则由 bootsect 代码加载,这些代码由编写系统的用户负责,与 BIOS 无关。这样构建的好处是站在整个体系的高度,统一设计和统一安排,简单而有效。BIOS 和操作系统的开发都可以遵循这一约定,灵活地进行各自的设计。例如, BIOS 可以不用知道内核镜像的大小以及其在软盘的分布等等信息,减轻了 BIOS 程序的复杂度,降低了硬件上的开销。而操作系统的开发者也可以按照自己的意愿,内存的规划,等等都更为灵活。另外,如果要使用 BIOS 进行加载,而且加载完成之后再执行,则需要很长的时间,因此 Linux 采用的是边执行边加载的方法。3.

3、 为什么 BIOS 把 bootsect 加载到 0x07c00,而不是 0x00000?加载后又马上挪到 0x90000 处,是何道理?为什么不一次加载到位?答:因为 BIOS 首先会把中断向量表加载到 0x00000-0x003ff 的 1KB 的内存空间,在加载 bootsect 时约定加载到 0x07c00 处,符合内存布局,如下。加载之后挪到 0x90000 处的原因如下:首先内核会使用启动扇区中的一些数据,如第 508、509 字节处的 ROOT_DEV;其次,依据系统对内存的规划,内核占用 0x0000 开始的空间,因此 0x7c00 可能会被覆盖。因为加载到 0x07c00是

4、BIOS 约定好的,操作系统只能遵守这个约定。4. bootsect、 setup、head 程序之间是怎么衔接的?给出代码证据。答:bootsect 首先利用 int 0x13 中断分别加载 setup 程序及 system 模块,待bootsect 程序的任务完成之后,执行jmpi 0,SETUPSEG由于 bootsect 将 setup 段加载到了 SETUPSEG:0 的地方,在实模式下,该指令跳转到 setup 段的第一条指令。setup 执行了之后,内核被移到了 0x00000 处,系统进入了保护模式,并加载了中断描述符表和全局描述符表lidt idt_48lgdt gdt_48

5、在保护模式下,一个重要的特征就是根据 GDT 决定后续执行哪里的程序。开启保护模式后,执行jmpi 0, 8根据保护模式的机制,该指令执行后跳转到以 GDT 第 2 项中的 base_addr 为基地址,以 0 为偏移量的地方,其中 base_addr 为 0。由于 head 放置在内核的头部,因此程序跳转到 head 中执行5. setup 程序里的 cli 是为了什么?答:cli 是关中断指令。因为此时需要由 16 位实模式向 32 位保护模式转变,即将进行实模式下的中断向量表和保护模式下中断描述符表的交接工作,在保护模式的中断机制尚未完成时不允许响应中断,以免发生未知的错误。6. set

6、up 程序的最后是 jmpi 0,8 为什么这个 8 不能简单的当作阿拉伯数字 8 看待?答:这里 8 要看成二进制 1000,最后两位 00 表示内核特权级,第三位 0 表示 GDT 表,第四位 1 表示根据 GDT 中的第 2 项来确定代码段的段基址和段限长等信息。这样,我们可以得到代码是从段基址 0x00000000、偏移为 0 处开始执行的,即 head 的开始位置。注意到已经开启了保护模式的机制,这里的 8是保护模式下的段选择符,而不能当成简单的阿拉伯数字 8 来看待。7. 打开 A20 和打开 pe 究竟是什么关系,保护模式不就是 32 位的吗?为什么还要打开 A20?有必要吗?答

7、:有必要。A20 是 cpu 的第 21 位地址线,A20 未打开的时候,实模式下的最大寻址为 1MB+64KB,而第 21 根地址线被强制为 0,所以相当于 cpu“回滚”到内存地址起始处寻址。打开 A20 仅仅意味着 CPU 可以进行 32 位寻址,且最大寻址空间是 4GB,而打开 PE 是使能保护模式。打开 A20 是打开 PE 的必要条件;而打开 A20 不一定非得打开 PE。打开 PE 是说明系统处于保护模式下,如果不打开 A20 的话,可以访问的内存只能是奇数 1M 段,若要真正在保护模式下工作,必须打开 A20,实现 32 位寻址。8. Linux 是用 C 语言写的,为什么没有

8、从 main 还是开始,而是先运行 3 个汇编程序,道理何在?答:通常用 C 语言编写的程序都是用户应用程序,这类程序的执行必须在操作系统上执行,也就是说要由操作系统为应用程序创建进程,并把应用程序的可执行代码从硬盘加载到内存。而在计算机刚刚加电时,内存中没有操作系统程序,只有 BIOS 程序在运行,需要借助 BIOS 分别加载 bootsect、setup 及 system 模块,然后利用这 3 个程序来完成内存规划、建立 IDT 和 GDT、设置分页机制等等,并实现从开机时的 16 位实模式到 main 函数执行需要的 32 位保护模式之间的转换。当计算机处在 32 位的保护模式状态下时,

9、调用 main 的条件才算准备完毕。9. 为什么不用 call,而是用 ret“调用”main 函数?画出调用路线图,给出代码证据。答:CALL 指令会将 EIP 的值自动压栈,保护返回现场,然后执行被调函数,档执行到被调函数的 ret 指令时,自动出栈给 EIP 并还原现场,继续执行CALL 的下一行指令。在由 head 程序向 main 函数跳转时,是不需要 main 函数返回的;同时由于 main 函数已经是最底层的函数了,没有更底层的支撑函数支持其返回。所以要达到既调用 main 又不需返回,就不采用 call 而是选择了 ret“调用”了。调用线路图见 P42 图 1-46。代码如下

10、:(见 P36 最下面)setup_paging:ret10. 保护模式的“保护”体现在哪里?答:打开了保护模式后,CPU 的寻址模式发生了变化,需要依赖于 GDT 去获取代码或数据段的基址。从 GDT 可以看出,保护模式除了段基址外,还有段限长,这样相当于增加了一个段位寄存器。既有效地防止了对代码或数据段的覆盖,又防止了代码段自身的访问超限,明显增强了保护作用。同时,保护模式中特权级的引入对于操作系统内核提供了强有力的保护。Intel 从硬件上禁止低特权级代码段使用一些关键性指令,还提供了机会允许操作系统设计者通过一些特权级的设置,禁止用户进程使用 cli、sti 等对掌控局面至关重要的指令

11、。有了这些基础,操作系统可以把内核设计成最高特权级,把用户进程设计成最低特权级。这样,操作系统可以访问 GDT、LDT、TR,而 GDT、LDT 是逻辑地址形成线性地址的关键,因此操作系统可以掌控线性地址。物理地址是由内核将线性地址转换而成的,所以操作系统可以访问任何物理地址,而用户进程只能使用逻辑地址。 11. 特权级的目的和意义是什么?为什么特权级是基于段的?答:特权级是操作系统为了更好地管理内存空间及其访问控制而设的,提高了系统的安全性。保护模式中特权级的引入对于操作系统内核提供了强有力的保护。Intel 从硬件上禁止低特权级代码段使用一些关键性指令,还提供了机会允许操作系统设计者通过一

12、些特权级的设置,禁止用户进程使用 cli、sti 等对掌控局面至关重要的指令。有了这些基础,操作系统可以把内核设计成最高特权级,把用户进程设计成最低特权级。这样,操作系统可以访问 GDT、LDT、TR,而 GDT、 LDT 是逻辑地址形成线性地址的关键,因此操作系统可以掌控线性地址。物理地址是由内核将线性地址转换而成的,所以操作系统可以访问任何物理地址,而用户进程只能使用逻辑地址。在操作系统设计中,一般一个段实现的功能相对完整,可以把代码放在一个段,数据放在一个段,并通过段选择符(包括 CS、SS、DS 、ES、FS 和 GS)获取段的基址和特权级等信息。特权级基于段,这样当段选择子具有不匹配

13、的特权级时,按照特权级规则判断是否可以访问。特权级基于段,是结合了程序的特点和硬件实现的一种考虑。12. 在 setup 程序里曾经设置过一次 gdt,为什么在 head 程序中将其废弃,又重新设置了一个?为什么折腾两次,而不是一次搞好?答:见 P33 点评。13. 在 head 程序执行结束的时候,在 idt 的前面有 184 个字节的 head 程序的剩余代码,剩余了什么?为什么要剩余?答:在 idt 前面有 184 个字节的剩余代码,包含了 after_page_tables、 ignore_int 和 setup_paging 代码段,其中 after_page_tables 往栈中压

14、入了些参数,ignore_int 用做初始化中断时的中断处理函数,setup_paging 则是初始化分页。剩余的原因:after_page_tables 中压入了一些参数,为内核进入 main 函数的跳转做准备。为了谨慎起见,设计者在栈中压入了 L6,以使得系统可能出错时,返回到 L6 处执行。ignore_int 为中断处理函数,使用 ignore_int 将 idt 全部初始化,因此如果中断开启后,可能使用了未设置的中断向量,那么将默认跳转到 ignore_int 处执行。这样做的好处是使得系统不会跳转到随机的地方执行错误的代码,所以 ignore_int 不能被覆盖。setup_pag

15、ing 用于分页,在该函数中对 0x0000 和 0x5000 的进行了初始化操作。该代码需要“剩余”用于跳转到 main,即执行 ”ret ”指令。14. 进程 0 的 task_struct 在哪?具体内容是什么?给出代码证据。答:进程 0 的 task_struct 是操作系统设计者事先写好的,位于内核数据区,存储在 user_stack 中。( 因为在进程 0 未激活之前,使用的是 boot 阶段的user_stack。)static union task_union init_task=INIT_TASK;具体内容如下:包含了进程 0 的进程状态、进程 0 的 LDT、进程 0 的

16、TSS 等等。其中 ldt 设置了代码段和堆栈段的基址和限长(640KB),而 TSS 则保存了各种寄存器的值,包括各个段选择符。代码如下:INIT_TASK 的定义见 P68。15. 进程 0 创建进程 1 时,为进程 1 建立了自己的 task_struct、内核栈,第一个页表,分别位于物理内存 16MB 的顶端倒数第一页、第二页。请问,这个了页究竟占用的是谁的线性地址空间,内核、进程 0、进程 1、还是没有占用任何线性地址空间(直接从物理地址分配)?说明理由并给出代码证据。答:占用的是内核的线性地址空间。(先理解清楚,稍后补充)16. 假设:经过一段时间的运行,操作系统中已经有 5 个进

17、程在运行,且内核分别为进程 4、进程 5 分别创建了第一个页表,这两个页表在谁的线性地址空间?用图表示这两个页表在线性地址空间和物理地址空间的映射关系。答:在内核的线性地址空间。(图片自己画,参考如下图)17. 进程 0 开始创建进程 1,调用了 fork() ,跟踪代码时我们发现,fork 代码执行了两次,第一次,跳过 init()直接执行了 for(;) pause(),第二次执行fork 代码后,执行了 init() 。奇怪的是,我们在代码中并没有看见向后的goto 语句,也没有看到循环语句,是什么原因导致反复执行?请说明理由,并给出代码证据。答:进程 0 创建进程 1 采用了中断机制,

18、在中断发生时由硬件将 ss,esp,eflags,cs,eip 的值压入了内核栈,其中 eip 的值指向了 int 0x80 的下一条指令。在执行 fork 时,通过 0x80 号系统调用,内核执行 copy_process函数,为进程 1 准备其管理结构(task_struct) ,设置进程 1 的线性地址空间及物理页面,其中设置了进程 1 的 TSS 中 eax 的值为 0,状态为TASK_RUNNING,以及利用中断压栈的寄存器值设置进程 1 的 ss,esp,eflags,cs,eip。copy_process:p-pid = last_pid;p-tss.eip = eip;p-ts

19、s.eflags = eflags;p-tss.eax = 0;p-tss.esp = esp;p-tss.cs = cs p-tss.ss = ss p-state = TASK_RUNNING;return last_pid;函数 copy_process 的返回值是 last_pid,即进程 1 的 pid(pid 不为 0) 。在 fork返回到进程 0 后,进程 0 判断返回值非 0,因此执行代码for(;) pause();在 sys_pause 函数中,内核设置了进程 0 的状态为 TASK_INTERRUPTIBLE,并进行进程调度。由于只有进程 1 处于就绪态,因此调度执行进

20、程 1 的指令。由于进程 1 在 TSS 中设置了 eip 等寄存器的值,因此从 int 0x80 的下一条指令开始执行,且设定返回 eax 的值作为 fork 的返回值(值为 0) ,因此进程 1 执行了 init 的函数。导致反复执行,主要是利用了两个系统调用 sys_fork 和 sys_pause 对进程状态的设置,以及利用了进程调度机制。18. copy_process 函数的参数最后五项是:long eip,long cs,long eflags,long esp,long ss。查看栈结构确实有这五个参数,奇怪的是其他参数的压栈代码都能找得到,确找不到这五个参数的压栈代码,反汇编

21、代码中也查不到,请解释原因。答:在 fork()中,当执行“ int $0x80”时产生一个软中断,该中断使 CPU 硬件自动将 SS、ESP、EFLAGS、CS 、EIP 这 5 个寄存器的数值按照这个顺序压入进程 0 的内核栈。利用硬件进行压栈,可以确保 eip 的值指向正确的指令,以使在中断返回后,程序能够继续执行。19. 为什么 static inline _syscall0(type,name)中需要加上关键字 inline?答:inline 一般是用于定义内联函数,内联函数结合了函数以及宏的优点,在定义时和函数一样,编译器会对其参数进行检查;在使用时和宏类似,内联函数的代码会被直接

22、嵌入在它被调用的地方,这样省去了函数调用时的一些额外开销,比如保存和恢复函数返回地址等,可以加快速度。20. 根据代码详细说明 copy_process 函数的所有参数是如何形成的?答:一般在应用程序中,一个函数的参数是由函数定义的,而在操作系统底层中,函数参数可以由函数定义以外的程序通过压栈的方式“做”出来。copy_process 函数的所有参数正是通过压栈形成的。代码见 P83 页、P85 页、P86 页。21. 根据代码详细分析,进程 0 如何根据调度第一次切换到进程 1 的。答:通过 fork(),进程 0 创建进程 1,并将其状态设为 TASK_RUNNING,fork()函数执行

23、完毕后返回,进入for(;) pause();在 sys_pause()中,将当前进程 (进程 0)的状态设置为 TASK_INTERRUPTBLE,然后执行 schedule(),遍历 task数组,找到唯一的一个处于 TASK_RUNNING 的进程(进程 1),然后切换到进程 1 执行,即 switch_to(1)。代码见 P10622. 内核的线性地址空间是如何分页的?画出从 0x000000 开始的 7 个页(包括页目录表、页表所在页)的挂接关系图,就是页目录表的前四个页目录项、第一个个页表的前 7 个页表项指向什么位置?给出代码证据。答:先把页目录表和 4 个页表放在物理内存的起始

24、地址,从内存起始位置开始的 5 页空间内容全部清零(每页 4KB) 。然后设置页目录表的前 4 项,使之分别指向 4 个页表,将第 4 个页表的最后一个页表项指向寻址范围的最后一个页面,将第 4 个页表的倒数第二个页表项指向寻址范围的倒数第二个页面,从高地址向低地址方向填写 4 个页面,依次指向内存从高地址向低地址方向的各个页面。图见 P39(注意要画出 7 个页,参考如下)代码见 P39 最下面23. 用文字和图说明中断描述符表是如何初始化的,可以举例说明(比如:set_trap_gate(0,由于在内核 IOPL的初始值为 0,且未经改变。进程 0在 move_to_user_mode 中

25、,继承了内核的 eflags,如下:move_to_user_mode()“pushflnt“ “iretn“ 在进程 0的 TSS中,设置了 eflags中的 IOPL 位为 0,代码见 P68,后续进程 如果没有改动的话也是 0,即 IOPL=0。因此,通过设置 IOPL,可以限制 3特权级的进程代码使用 cli 指令。27.根据代码详细分析操作系统是如何获得一个空闲页的。答:代码见 P90 get_free_page函数。过程:(1)将 EAX 设置为 0,EDI 设置指向 mem_map 的最后一项(mem_map+PAGING_PAGES-1) ,std 设置扫描是从高地址向低地址。

26、从mem_map的最后一项反向扫描,找出引用次数为 0(AL)的页,如果没有则退出;如果找到,则将找到的页设引用数为 1;(2) ECX左移 12位得到页的相对地址,加 LOW_MEM得到物理地址,将此页最后一个字节的地址赋值给 EDI(LOW_MEM+4092) ;(3) stosl将 EAX的值设置到 ES:EDI所指内存,即反向清零 1024*32bit,将此页清空;(4) 将页的地址(存放在 EAX)返回。28.用户进程自己设计一套 LDT表,并与 GDT挂接,是否可行,为什么?答:不可行。GDT 和 LDT放在内核数据区,属于 0特权级,3 特权级的用户进程无权访问修改。此外,如果用

27、户进程可以自己设计 LDT的话,表明用户进程可以访问其他进程的 LDT,则会削弱进程之间的保护边界,容易引发问题。29. 保护模式下,线性地址到物理地址的转化过程是什么?答:保护模式下,线性地址到物理地址的转化是借助页目录表及页表完成的。其转化过程如图所示(见 P97 图 3-9)。Linux 0.11 中仅有一个页目录表,其地址存放在 CR3 寄存器中,通过线性地址中的“页目录项”数据及 CR3 寄存器就可以找到页目录表中对应的页目录项,通过该页目录项可以找到对应的页表,结合线性地址中的“页表项”数据就可以找到对应的页表项,通过该页表项可以找到对应的物理页面,最后通过线性地址中的“页内偏移”

28、落实到实际的物理地址值。30. 为什么 get_free_page()将新分配的页面清 0?答:Linux 在回收页面时并没有将页面清 0,只是将 mem_map 中与该页对应的位置 0。在使用 get_free_page 申请页时,也是遍历 mem_map 寻找对应位为 0的页,但是该页可能存在垃圾数据,如果不清 0 的话,若将该页用做页表,则可能导致错误的映射,引发错误,所以要将新分配的页面清 0。31. 内核和普通用户进程并不在一个线性地址空间内,为什么仍然能够访问普通用户进程的页面?答:虽然内核与普通进程并不在一个线性地址空间内,但是用户进程的页面最终要从物理内存上分配,而内核的分页机

29、制即页目录表、页表等,正好管理着16M 物理内存,所以内核可以访问普通用户进程的页面。32. 详细分析一个进程从创建、加载程序、执行、退出的全过程。答:可以参考课本 P273 页,其中的核心部分课上都进行了介绍,包括 fork()、copy_process()、do_execve()及 do_exit 等。参考:首先,shell 调用 fork 开始创建进程,产生 int 0x80 软中断,最终映射到sys_fork(),调用 find_empty_process(),为 str1 申请可用的 pid 和 task64空闲位置,接着调用 copy_process()为 str1 申请用来承载进

30、程 task_struct 和内核栈的一个页面,shell 把自己的 task_struct 复制给 str1 进程,然后修改 str1 的 task_struct的部分数据,包括时间片,TSS 字段等。接着调用 copy_mem()为进程分段(确定段基址和段限长等) ,然后调用 copy_page_tables 为 str1 进程另起一套页目录项和页表项,并指向 shell 的页面。还要解决文件继承的问题,然后将 str1 进程TSS 和 LDT 挂接在 GDT 的指定位置,完成这些后,将 str1 设为就绪态。接下来加载用户程序:首先要做一些检查工作,如可执行文件的数据长度和代码长度等;然

31、后调用 free_page_tables 解除与 shell 的页面共享关系,接着根据程序的长度重新设置 LDT,调整 str1 的 task_struct,最后调整 EIP 和 ESP。用户程序被调度执行,产生缺页中断,调用 do_no_page 为 str1 申请一个内存页面,并把它登记在 mem_map 中,将 str1 程序从硬盘加载到新分配的页面中,把它的物理地址映射到进程的线性地址空间内。执行加载到的程序,产生压栈动作,若栈空间不够,则产生缺页中断继续申请页面。最后,用户进程调用 exit 退出,释放程序所占页面,解除与文件有关的内容,并调用 tell_father 给父进程发信号

32、,退出后执行调度,shell 进程收到用户进程发送的信号设置为就绪态,待其执行时,释放掉用户进程 task_struct 所占用的页面,解除与 task64的关系,这时用户进程彻底退出。33. 详细分析多个进程(无父子关系)共享一个可执行程序的完整过程。答:依次创建 3 个用户进程,每个进程都有自己的 task。假设进程 1 先执行,需要压栈产生缺页中断,内核为其申请空闲物理页面,并映射到进程 1 的线性地址空间。这时产生时钟中断,轮到进程 2 执行,进程 2 也执行同样逻辑的程序。之后,又轮到进程 3 执行,也是压栈,并设置 text。可见,三个进程虽程序相同,但数据独立,用 TSS 和 L

33、DT 实现对进程的保护。34. 缺页中断是如何产生的,页写保护中断是如何产生的,操作系统是如何处理的?答:缺页中断:每个页目录项和页表项都有个标志位 P,如果和一个页面建立了映射关系,P 位置 1,否则置 0。MMU 在解析线性地址时,若发现某个表项的 P 位为零,说明没有对应页面,就会产生缺页中断。操作系统会调用_do_no_page 为进程申请空闲页面,将程序加载到新分配的页面中,并建立页目录表-页表-页面的三级映射管理关系。页写保护异常:假设两个进程共享一个页面,该页面处于写保护状态即只读,此时若某一进程执行写操作,就会产生“页写保护”异常。操作系统会调用_do_wp_page,为该进程

34、申请空闲页面,将该进程的页表指向新申请的页面,然后将原页表的数据复制到新页面中,同时将原页面的引用计数减 1。该进程得到自己的页面,就可以执行写操作。35. 为什么要设计缓冲区,有什么好处?答:缓冲区的作用主要体现在两方面:(1) 形成所有块设备数据的统一集散地,操作系统的设计更方便,更灵活;(2) 数据块复用,提高对块设备文件操作的运行效率。在计算机中,内存间的数据交换速度是内存与硬盘数据交换速度的 2 个量级,如果某个进程将硬盘数据读到缓冲区之后,其他进程刚好也需要读取这些数据,那么就可以直接从缓冲区中读取,比直接从硬盘读取快很多。如果缓冲区的数据能够被更多进程共享的话,计算机的整体效率就

35、会大大提高。同样,写操作类似。36. 操作系统如何利用 buffer_head 中的 b_data,b_blocknr,b_dev,b_uptodate,b_dirt,b_count,b_lock,b_wait 管理缓冲块的?答:b_data 指向缓冲块,用于找到缓冲块的位置。进程与缓冲区及缓冲区与硬盘之间都是以缓冲块为单位进行数据交互的,而b_blocknr,b_dev 唯一标识一个块,用于保证数据交换的正确性。另外缓冲区中的数据被越多进程共享,效率就越高,因此要让缓冲区中的数据块停留的时间尽可能久,而这正是由 b_blocknr,b_dev 决定的,内核在 hash 表中搜索缓冲块时,只看设备号与块号,只要缓冲块与硬盘数据的绑定关系还在,就认定数据块仍停留在缓冲块中,就可以直接用。b_uptodate 与 b_dirt,是为了解决缓冲块与数据块的数据正确性问题而存在的。b_uptodate 针对进程方向,如果 b_uptodate 为 1,说明缓冲块的数据已经是数据

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 教育教学资料库 > 参考答案

Copyright © 2018-2021 Wenke99.com All rights reserved

工信部备案号浙ICP备20026746号-2  

公安局备案号:浙公网安备33038302330469号

本站为C2C交文档易平台,即用户上传的文档直接卖给下载用户,本站只是网络服务中间平台,所有原创文档下载所得归上传人所有,若您发现上传作品侵犯了您的权利,请立刻联系网站客服并提供证据,平台将在3个工作日内予以改正。