CSAPP大作业

  • Post author:
  • Post category:其他



CSAPP大作业


摘 要

通过对hello程序整个运行过程的分析,回顾本学期所学的内容,加深了印象。分析了从c文件转化为可执行文件过程中的预处理、编译、汇编、链接和可执行文件执行过程中的进程管理、存储空间管理和I/O管理的原理

关键词:预处理:编译:汇编:链接:hello进程管理:hello的存储管理:hello的IO管理;

(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)

目 录

第1章 概述 – 4 –

1.1 HELLO简介 – 4 –

1.2 环境与工具 – 4 –

1.3 中间结果 – 4 –

1.4 本章小结 – 4 –

第2章 预处理 – 5 –

2.1 预处理的概念与作用 – 5 –

2.2在UBUNTU下预处理的命令 – 5 –

2.3 HELLO的预处理结果解析 – 5 –

2.4 本章小结 – 5 –

第3章 编译 – 6 –

3.1 编译的概念与作用 – 6 –

3.2 在UBUNTU下编译的命令 – 6 –

3.3 HELLO的编译结果解析 – 6 –

3.4 本章小结 – 6 –

第4章 汇编 – 7 –

4.1 汇编的概念与作用 – 7 –

4.2 在UBUNTU下汇编的命令 – 7 –

4.3 可重定位目标ELF格式 – 7 –

4.4 HELLO.O的结果解析 – 7 –

4.5 本章小结 – 7 –

第5章 链接 – 8 –

5.1 链接的概念与作用 – 8 –

5.2 在UBUNTU下链接的命令 – 8 –

5.3 可执行目标文件HELLO的格式 – 8 –

5.4 HELLO的虚拟地址空间 – 8 –

5.5 链接的重定位过程分析 – 8 –

5.6 HELLO的执行流程 – 8 –

5.7 HELLO的动态链接分析 – 8 –

5.8 本章小结 – 9 –

第6章 HELLO进程管理 – 10 –

6.1 进程的概念与作用 – 10 –

6.2 简述壳SHELL-BASH的作用与处理流程 – 10 –

6.3 HELLO的FORK进程创建过程 – 10 –

6.4 HELLO的EXECVE过程 – 10 –

6.5 HELLO的进程执行 – 10 –

6.6 HELLO的异常与信号处理 – 10 –

6.7本章小结 – 10 –

第7章 HELLO的存储管理 – 11 –

7.1 HELLO的存储器地址空间 – 11 –

7.2 INTEL逻辑地址到线性地址的变换-段式管理 – 11 –

7.3 HELLO的线性地址到物理地址的变换-页式管理 – 11 –

7.4 TLB与四级页表支持下的VA到PA的变换 – 11 –

7.5 三级CACHE支持下的物理内存访问 – 11 –

7.6 HELLO进程FORK时的内存映射 – 11 –

7.7 HELLO进程EXECVE时的内存映射 – 11 –

7.8 缺页故障与缺页中断处理 – 11 –

7.9动态存储分配管理 – 11 –

7.10本章小结 – 12 –

第8章 HELLO的IO管理 – 13 –

8.1 LINUX的IO设备管理方法 – 13 –

8.2 简述UNIX IO接口及其函数 – 13 –

8.3 PRINTF的实现分析 – 13 –

8.4 GETCHAR的实现分析 – 13 –

8.5本章小结 – 13 –

结论 – 14 –

附件 – 15 –

参考文献 – 16 –

第1章 概述

1.1 Hello简介

hello.c经过预处理,编译,汇编,链接之后,作为目标程序执行,在shell中启动之后,shell会调用fork创建子进程,这是P2P过程

然后shell为execve映射虚拟内存,程序开始后加载物理内存然后进入main函数执行代码,结束之后shell父进程回收hello进程,释放其占用的内存,删除数据结构,这是020过程

1.2 环境与工具

X64 CPU;2GHz;2G RAM;256GHD Disk 以上

Windows7 64位以上;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64位/优麒麟 64位

Dev-C++5.11

1.3 中间结果

hello.i经过预处理得到

hello.s编译后得到的汇编代码

hello.o汇编后得到的可重定位目标执行文件

hello.out链接后得到的二进制文件

hello可执行程序

1.4 本章小结

简单介绍了hello程序,列出了环境与工具还有中间的结果

(第1章0.5分)

第2章 预处理

2.1 预处理的概念与作用

概念:

一般是指在程序源代码被翻译为目标代码的过程中,生成二进制代码之前的过程。典型地,由预处理器对程序源代码文本进行处理,得到的结果再由编译器核心进一步编译。

作用:

宏定义;文件包含;条件编译

2.2在Ubuntu下预处理的命令

gcc -E -o hello.i hello.c

在这里插入图片描述

2.3 Hello的预处理结果解析

#include <stdio.h> #include <stdlib.h> #include <unistd.h>三个头文件消失,替代的是一大段代码,描述的是运行库在计算机中的位置,方便下一步翻译成汇编语言

2.4 本章小结

理解了预处理的概念和作用,对hello.c进行了预处理,并分析得到的.i文件

(第2章0.5分)

第3章 编译

3.1 编译的概念与作用

概念:

把代码转化为汇编指令的过程

作用:

把用高级程序设计语言书写的源程序,翻译成等价的计算机汇编或机器语言书写的目标程序 编译程序以高级程序设计语言书写的源程序作为输入,而以汇编或机器语言表示的目标程序作为输出。

注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序

3.2 在Ubuntu下编译的命令

gcc -S hello.i -o hello.s

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

3.3 Hello的编译结果解析

1.字符串

“Usage: Hello \345\255\246\345\217\267 \345\247\223\345\220\215\357\274\201”

对应Usage: Hello 学号 姓名!\n

中文被编码为UTF-8格式

“Hello %s %s\n”

对应第二个printf中的格式化参数

2.变量和赋值

全局变量sleepsecs

先初始化为2,然后存放在.rodata节(只读数据节)

int i

cmpl $9, -4(%rbp),从这里可以看出存到了-4(%rbp)中

movl $0, -4(%rbp),在这里赋值为0

int argc

作为第一个参数被压入栈中,pushq %rbp

3.数组

movq -32(%rbp), %rax

addq $16, %rax

movq (%rax), %rdx

movq -32(%rbp), %rax

addq $8, %rax

movq (%rax), %rax

argv作为一个指针数组,每个地址是8位

4.算数操作

addl $1, -4(%rbp) i++

leaq .LC1(%rip), %rdi,计算格式串地址

5.关系操作

cmpl $9, -4(%rbp) i<10被优化为i<=9

cmpl $3, -20(%rbp) argc!=3

6.控制转移

if(argc!=3)

cmpl $3, -20(%rbp)

je .L2

先比较,如果为0,则直接跳转到L2否则执行接下来的代码(if里面的内容)

for(i=0;i<10;i++)

addl $1, -4(%rbp)

cmpl $9, -4(%rbp)

jle .L4

先把i++,然后与9比较,如果小于等于9,则跳转到L4继续执行循环里的内容,否则则退出循环

7.函数操作

main

call指令将下一条指令的地址压入栈中,然后跳转到main函数,main将%eax设置为0返回

exit

call exit@PLT

printf

第一次call puts@PLT

第二次 call printf@PLT

sleep

call sleep@PLT

getchar

call getchar@PLT

3.4 本章小结

理解编译的概念和作用,对hello.进行编译,然后分析了编译得到的文件,理解机器指令如何实现C语言代码中的操作

(第3章2分)

第4章 汇编

4.1 汇编的概念与作用

概念:

通过汇编器把汇编语言翻译成机器语言

作用:

通过汇编过程把汇编代码转换成机器代码

4.2 在Ubuntu下汇编的命令

gcc hello.s -s hello.o

在这里插入图片描述

4.3 可重定位目标elf格式

文件头

在这里插入图片描述

描述了生成该文件的系统的字的大小和字节顺序,还有帮助链接器语法分析和解释目标文件的信息

节头部表

在这里插入图片描述

包括节名称,类型,读写权限,长度

符号表

在这里插入图片描述

包含main定义和引用的符号的信息

4.4 Hello.o的结果解析

在这里插入图片描述

与hello.s的比较:

左边多了机器码,用于机器识别

分支跳转call有了具体的地址

在.s文件中,call调用函数会直接加上函数名,反汇编中,call后是下一条指令的地址,许多函数要从链接的库中调用,call将他们的相对地址全都设置为0,在.rela.text中设置重定位条目

.s中访问rodata使用段名称%rip,在反汇编代码中是0+%rip,因为rodata中数据地址也是在运行时确定,访问也需要重定位

4.5 本章小结

了解汇编的概念和作用,汇编得到.o文件,然后分析了可重定位目标elf格式,然后使用objdump进行反汇编并与.s文件进行比较,理解机器语言与汇编语言的关系

(第4章1分)

第5章 链接

5.1 链接的概念与作用

概念:

链接是将各种代码和数据片段收集并组合成一个单一文件的过程

作用:

解析未定义的符号引用,将目标文件的占位符替换为符号的地址

5.2 在Ubuntu下链接的命令

在这里插入图片描述

5.3 可执行目标文件hello的格式

在这里插入图片描述

节头部表:

在这里插入图片描述

符号表信息:

在这里插入图片描述

5.4 hello的虚拟地址空间

在这里插入图片描述

代码段偏移为0,被映射到虚拟地址0x0x0000000000400040,位于hello开头的代码被加载到虚拟地址为0x400000的地方,同理PFDR段位于偏移0x40处,被加载到了0x400040处

5.5 链接的重定位过程分析

在这里插入图片描述

各指令对应的地址为重定位之后的虚拟内存的地址,从0x400000开始,而hello.o是从0000000开始

此外在开头hello还多了.init,.fini,.plt,.plt.got节,分别是程序初始化执行的代码,程序终止时需要执行的代码,动态链接中的过程连接表,动态链接中的全局偏移表

5.6 hello的执行流程

在这里插入图片描述

函数 地址

ld-2.27.so!_dl_start 0x7f96ed2e1ea0

ld-2.27.so!_dl_init 0x7f96ed2f0630

hello!_start 0x400500

libc-2.27.so!__libc_start_main 0x7fbdf0cccab0

hello!puts@plt 0x400410

hello!exit@plt 0x400440

hello!printf@plt 0x4004c0

hello!sleep@plt 0x4004e0

hello!getchar@plt 0x4004f0

libc-2.27.so!exit 0x7236b7b9e120

5.7 Hello的动态链接分析

在这里插入图片描述

找到GOT地址0x601000

在这里插入图片描述

在这里插入图片描述

一开始这个地址开始的字节都为0,调用_dl_init函数之后GOT内容产生变化,存储的地址是动态连接器在Id-linux.so的入口点

5.8 本章小结

理解链接的作用,对hello.o进行链接得到hello文件,并对hello进行分析,与hello.o文件进行比较

(第5章1分)

第6章 hello进程管理

6.1 进程的概念与作用

概念:

进程是一个执行中的程序的实例

作用

通过进程,我们会得到一种假象,好像我们的程序是当前唯一运行的程序,我们的程序独占处理器和内存,我们程序的代码和数据好像是系统内存中唯一的对象。

6.2 简述壳Shell-bash的作用与处理流程

Shell是一个命令解释器,它解释由用户输入的命令并且把它们送到内核。不仅如此,Shell有自己的编程语言用于对命令的编辑,它允许用户编写由shell命令组成的程序。Shell编程语言具有普通编程语言的很多特点,比如它也有循环结构和分支控制结构等

Shell处理流程:

1.打印提示信息

2.等待用户输入

3.接受命令

4.解释命令

5.找到该命令,执行命令,如果命令含有参数,输入的命令解释它

6.执行完成,返回第一步

6.3 Hello的fork进程创建过程

shell先判断出不是内置命令,于是加载可执行文件hello,通过fork创建一个子进程,子进程得到与父进程用户级虚拟地址空间相同的一份副本。子进程还获得与父进程任何打开文件描述符相同的副本。子进程与父进程有不同的pid。fork被调用一次,返回两次。在父进程中fork返回子进程的pid,在子进程中fork返回0.父进程与子进程是并发运行的独立进程。

6.4 Hello的execve过程

在shell创建的子进程中将会调用execve函数,来调用加载器,加载器将可执行目标文件中的代码和数据从磁盘复制到内存中,然后通过跳转到程序的第一条指令或入口点来运行该程序。这个将程序复制到内存并运行的过程叫做加载。。在程序头部表的引导下,加载器将可执行文件的片复制到代码段和数据段。接下来,加载器跳转到程序的入口点,也就是_start函数的地址。这个函数是在系统目标文件ctrl.o中定义的,对所有C程序都是一样的。

start函数调用系统启动函数

_libc_start_main,该函数定义在libc.so中,它初始化执行环境,调用用户层的main函数,处理main函数的返回值,并且在需要的时候把控制返回给内核。

6.5 Hello的进程执行

系统中的每个程序都运行在某个进程的上下文中。上下文由程序正确运行所需的状态组成,这个状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。

内核调度hello的进程开始进行,输出Hello与之前输入的内容,然后执行sleep函数,这个函数是系统调用,它显示地请求让调用进程休眠。内核转而执行其他进程,这时就会发生一个上下文转换。2s后,又会发生一次进程转换,恢复hello进程的上下文,继续执行hello进程。重复9次这个过程。

循环结束后,后面执行到getchar函数,这时读取数据一般需要很长的时间,所以将会发生一个上下文切换转而执行其他进程,当数据已经被读取到缓存区中,将会发生一个中断,使内核发生上下文切换,重新执行hello进程。

6.6 hello的异常与信号处理

回车

在这里插入图片描述

进程被回收

Ctrl-Z

在这里插入图片描述
在这里插入图片描述

会发送一个信号,停止前台作业

Ctrl-C

在这里插入图片描述

会向进程传递中断信号,使程序被终止

乱按

在这里插入图片描述

乱按的内容会被保存在缓冲区,按下回车会在程序停止后执行这些内容

6.7本章小结

了解了进程的概念和作用以及shell的作用,分析了hello的执行过程,以及fork和execve的作用

(第6章1分)

第7章 hello的存储管理

7.1 hello的存储器地址空间

物理地址:

CPU通过地址总线的寻址,找到真实的物理内存对应地址。 CPU对内存的访问是通过连接着CPU和北桥芯片的前端总线来完成的。在前端总线上传输的内存地址都是物理内存地址。

逻辑地址:

程序代码经过编译后出现在 汇编程序中地址。逻辑地址由选择符(在实模式下是描述符,在保护模式下是用来选择描述符的选择符)和偏移量(偏移部分)组成。

线性地址:

逻辑地址经过段机制后转化为线性地址,为描述符:偏移量的组合形式。分页机制中线性地址作为输入。

7.2 Intel逻辑地址到线性地址的变换-段式管理

段式内存管理方式就是直接将逻辑地址转换成物理地址,也就是CPU不支持分页机制。其地址的基本组成方式是段号+段内偏移地址。

在x86保护模式下,段的信息即段描述符占8个字节,段信息无法直接存放在段寄存器中。Intel的设计是段描述符集中存放在GDT或LDT中,而段寄存器存放的是段描述符在GDT或LDT内的索引值。

首先给定一个完整的逻辑地址

1.看段选择描述符中的T1字段是0还是1,可以知道当前要转换的是GDT中的段,还是LDT中的段,再根据指定的相应的寄存器,得到其地址和大小,我们就有了一个数组了。

2.拿出段选择符中的前13位,可以在这个数组中查找到对应的段描述符,这样就有了Base,即基地址就知道了。

3.把基地址Base+Offset,就是要转换的下一个阶段的地址。

7.3 Hello的线性地址到物理地址的变换-页式管理

首先先将线性地址分为VPN+VPO的形式,然后再将VPN拆分成TLBT+TLBI,然后去TLB缓存里找所对应的PPN,如果发生缺页情况则直接查找对应的PPN,找到PPN之后,将其与VPO组合变为PPN+VPO就是生成的物理地址了。

7.4 TLB与四级页表支持下的VA到PA的变换

36位的虚拟地址被分割成4个9位的片。CR3寄存器包含L1页表的物理地址。VPN1有一个到L1 PTE的偏移量,找到这个PTE以后又会包含到L2页表的基础地址;VPN2包含一个到L2PTE的偏移量,找到这个PTE以后又会包含到L3页表的基础地址;VPN3包含一个到L3PTE的偏移量,找到这个PTE以后又会包含到L4页表的基础地址;VPN4包含一个到L4PTE的偏移量,找到这个PTE以后就是相应的PPN

7.5 三级Cache支持下的物理内存访问

先将虚拟地址转换为物理地址,再对物理地址进行分析,物理地址一般由CT、CI、CO组成,用CI位进行索引,如果匹配成功且valid值为1,则称为命中,根据偏移量在L1cache中取数,如果不命中,在分别到L2、L3和主存中重复上述过程

7.6 hello进程fork时的内存映射

在用fork创建虚拟内存的时候,要经历以下步骤:

1.创建当前进程的mm_struct,vm_area_struct和页表的原样副本

2.两个进程的每个页面都被标记为只读页面

3两个进程的每个vm_area_struct都被标记为私有

7.7 hello进程execve时的内存映射

exceve函数加载和执行程序Hello,需要以下几个步骤:

1.删除已存在的用户区域。

2.映射私有区域。为Hello的代码、数据、bss和栈区域创建新的区域结构,所有这些区域都是私有的、写时复制的。

3.映射共享区域。比如Hello程序与标准C库libc.so链接,这些对象都是动态链接到Hello的,然后再用户虚拟地址空间中的共享区域内。

4.设置程序计数器(PC)。exceve做的最后一件事就是设置当前进程的上下文中

7.8 缺页故障与缺页中断处理

在虚拟内存的习惯说法中,DRAM缓存不命中称为缺页。例如:CPU引用了VP3中的一个字,VP3并未缓存在DRAM中。地址翻译硬件从内存中读取PTE3,从有效位推断出VP3未被缓存,并且触发一个缺页异常。缺页异常调用内核中的缺页异常处理程序,该程序会选择一个牺牲页,在此例中就是存放在PP3中的VP4。如果VP4已经被修改了,那么内核就会将它复制回磁盘。无论哪种情况,内核都会修改VP4的页表条目,反映出VP4不再缓存在主存中这一事实。

接下来,内核从磁盘复制VP3到内存中的PP3,更新PTE3,随后返回。当异常处理程序返回时,它会重新启动导致缺页的指令,该指令会把导致缺页的虚拟地址重发送到地址翻译硬件。但是现在VP3已经缓存在主存中了,那么也命中也能由地址翻译硬件正常处理了。

7.9动态存储分配管理

基本方法:维护一个虚拟内存区域“堆”,将堆视为一组不同大小的块的集合来维护,每个块要么是已分配的,要么是空闲的,需要时选择一个合适的内存块进行分配。

1 记录空闲块,可以选择隐式空闲链表,显示空闲链表,分离的空闲链表和按块大小排序建立平衡树

2 放置策略,可以选择首次适配,下一次适配,最佳适配

3 合并策略,可以选择立即合并,延迟合并

4 需要考虑分割空闲块的时机,对内部碎片的忍耐阈值.

7.10本章小结

回顾了存储的一些概念,进程fork和execve内存映射的内容,还有缺页问题和动态存储分配管理的问题

(第7章 2分)

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

设备的模型化:文件

文件的类型:

普通文件:包含任意数据的文件。

目录:包含一组链接的文件,每个链接都将一个文件名映射到一个文件(他还有另一个名字叫做“文件夹”)。

套接字:用来与另一个进程进行跨网络通信的文件

命名通道

符号链接

字符和块设备

设备管理:unix io接口

打开和关闭文件

读取和写入文件

改变当前文件的位置

8.2 简述Unix IO接口及其函数

1.Unix IO接口:

打开文件:一个应用程序通过要求内核打开相应的文件,宣告它想要访问一个I/O设备。内核返回一个小的非负整数,叫做描述符。它在后续对此文件的所有操作中标识这个文件。内核记录有关这个打开文件的所有信息。应用程序只需记住这个描述符。

shell创建的每个进程开始时都有三个打开的文件:标准输入、标准输出和标准错误。

改变当前的文件位置:对于每个打开的文件,内核保持着一个文件的位置k,初始为0,这个文件的位置是从文件开头起始的字符偏移量。应用程序能够通过执行seek操作,将文件的当前位置设置为k。

读写文件:读操作就是从文件中复制n>0个字节到内存中,从当前文件位置k开始,然后将k增加到k+n。而写操作就是从内存复制字节到文件中。

关闭文件:当应用完成了对文件的访问后,它就通知内核关闭这个文件。作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。无论一个进程因为何种原因终止时,内核都会关闭所有打开的文件并释放它们的内存资源。

2.Unix IO函数:

open 函数:调要open函数可以打开或创建一个文件。

create函数:创建一个文件,也可通过以特定参数使用open来实现。

close函数:读文件进行关闭。

Iseek函数:为一个打开的文件设置其偏移量。

read函数:从打开的文件中读数据到buf。

write函数:写入文件。

pread,prwrite函数:主要用于解决文件共享问题。

dup函数

syns函数:用于解决延迟写问题,保证磁盘上实际文件系统和缓冲区高速缓存中内容的一致性。

8.3 printf的实现分析

int printf(const char

fmt,…){


int i;

char buf[256];

va_list arg = (va_list)((char

)(&fmt) + 4);

i = vsprintf(buf,fmt,arg);

write(buf,i);

return i;

}

调用了两个函数vsprintf,write

vsprintf函数的作用是将所有的参数内容格式化之后存入buf,然后返回格式化数组的长度。

write函数是将buf中的i个元素写到终端的函数。

从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.

字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。

显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。

8.4 getchar的实现分析

int getchar(void){

static char buf[BUFSIZ];

static char *bb = buf;

static int n = 0;

if(n == 0)

{


n = read(0, buf, BUFSIZ);

bb = buf;

}

return(–n >= 0)?(unsigned char) *bb++ : EOF;

}

getchar函数调用read函数,将整个缓冲区都读到buf里,并将缓冲区的长度赋值给n。返回时返回buf的第一个元素,除非n<0。

异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。

getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。

8.5本章小结

了解了hello的IO管理,以及读写的两个函数的实现

(第8章1分)

结论

用计算机系统的语言,逐条总结hello所经历的过程。

你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。

先用C语言编写hello.c

预处理hello.c,将其与所有外部的库展开合并到hello.i中

编译hello.i成为汇编文件hello.s

汇编hello.s,将其变为可重定位目标文件hello.o

将hello.o与可重定位目标文件和动态链接库链接成为可执行程序hello

运行hello

shell调用fork创建子进程

shell调用execve,映射虚拟内存

进入程序,载入物理内存,进入main函数

CPU分配时间片,hello在一个时间片中顺序执行

正常结束或者ctr-c ctr-z终止或挂起进程

shell父进程回收子进程,hello的一生结束

(结论0分,缺失 -1分,根据内容酌情加分)

附件

列出所有的中间产物的文件名,并予以说明起作用。

hello.c 用C语言编写的hello程序源代码

hello.i 经预处理处理得到的文本文件

hello.s 编译后得到的汇编代码

hello.o 汇编操作得到的可重定位目标执行文件

hello.out 链接后得到的二进制文件

hello 可执行程序

(附件0分,缺失 -1分)

参考文献

为完成本次大作业你翻阅的书籍与网站等

[1] 林来兴. 空间控制技术[M]. 北京:中国宇航出版社,1992:25-42.

[2] 辛希孟. 信息技术与信息服务国际研讨会论文集:A集[C]. 北京:中国科学出版社,1999.

[3] 赵耀东. 新时代的工业工程师[M/OL]. 台北:天下文化出版社,1998 [1998-09-26]. http://www.ie.nthu.edu.tw/info/ie.newie.htm(Big5).

[4] 谌颖. 空间交会控制理论与方法研究[D]. 哈尔滨:哈尔滨工业大学,1992:8-13.

[5] KANAMORI H. Shaking Without Quaking[J]. Science,1998,279(5359):2063-2064.

[6] CHRISTINE M. Plant Physiology: Plant Biology in the Genome Era[J/OL]. Science,1998,281:331-332[1998-09-23]. http://www.sciencemag.org/cgi/ collection/anatmorp.

[7] Computer Systems:A Programmer’s Perspective, Bryant,R.E., 2016

(参考文献0分,缺失 -1分)



版权声明:本文为j1yhao原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。