计算机系统
大作业
题 目 程序人生-Hello’s
P2P
专 业 计算机科学与技术
学 号 1180300814
班 级11803008
学 生 谢天霖
指 导 教 师 吴锐
计算机科学与技术学院
2019年12月
摘 要
本文通过对一个简单的程序hello.c的预处理,编译,汇编,链接,重定位等步骤,复习了程序从预处理开始直到运行结束过程中出现的种种问题,详细的解释了各个过程的原理与实现。
关键词:程序,进程,O2O,P2P
(摘要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简介
1)P2P简介
程序员用IDE等相关工具编写hello.c程序(文本文件);在Linux操作系统里,预处理器根据以字符#开始的命令修改hello.c得到另一个C程序hello.i(文本文件);编译器将hello.i翻译成文本文件hello.s(文本文件);汇编器翻译得到可重定位目标文件hello.o;经过链接(ld)生成hello(可执行目标程序)。程序员在Shell输入./hello执行此程序,hello最后变成了系统里的一个进程。
2)O2O 简介
在Shell处理Hello过程中,shell会fork一个子进程,并在这个子进程中调用execve加载hello。然后程序会跳转到_start地址,最终调用hello的main函数。打印完hello后程序结束。最后shell回收此进程。
1.2 环境与工具
硬件环境:Intel® Core™ i9-8950HK CPU @ 2.90GHz
8G RAM
1.0T SSD
软件环境:windows10家庭版(64位)
VMWare
Workstation 15 Player
Ubuntu
18.04.3 LTS
1.3 中间结果
hello.c源代码
hello.i预处理生成文件
hello.s编译生成的文件
hello.o打包成的可重定位目标文件
hello链接生成的可执行目标文件
helloo.elf可重定位目标文件的ELF格式
helloo_disas.txt可重定位目标文件的反汇编生成代码
hello.elf可执行目标文件的ELF格式
hello_disas.txt可执行目标文件的反汇编生成代码
1.4 本章小结
本章介绍了大作业的基本信息与概论。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
概念:预处理器(cpp)根据以字符#开始的 命令,修改原始的c程序。
C语言的预处理主要有三个方面的内容:
1.宏定义; 2.文件包含; 3.条件编译。
作用:预处理过程扫描源代码,对预处理命令进行转换,插人所有用#include命令指定的文件,得到新的文本代码。预处理过程还会删除程序中的注释和多余的空白字符。
2.2在Ubuntu下预处理的命令
gcc -E hello.c -o hello.i
2.3 Hello的预处理结果解析
根据插入各种内容,如printf的声明等
2.4 本章小结
本章介绍了预处理的有关内容,展示了预处理的命令与作用。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
概念:编译器将预处理得到的文件编译成汇编文件(文本文件)。
作用:编译的基本作用是将高级语言编写的程序翻译成汇编语言,但除此之外编译还具有还应具备语法检查、调试措施、修改手段、覆盖处理、目标程序优化的作用
3.2 在Ubuntu下编译的命令
gcc -S
hello.i -o hello.s
3.3 Hello的编译结果解析
3.3.1对数据的操作
1、全局变量
int sleepsecs
= 2.5;
hello.s里显示了sleepsecs被声明为全局变量,存放在.data段,占四个字节(int 型)。
2、字符串
1) “用法: Hello 学号 姓名!\n”,可以发现字符串被编码成UTF-8格式,一个汉字在utf-8编码中占三个字节。声明在.LC0段。.LC0保存在rodata中。
2) “Hello %s %s\n”。声明在.LC1段。.LC1也保存在rodata中。
3、局部变量
保存在栈里
int argv与int i.
3.3.2赋值操作
int sleepsecs =
2.5;直接赋初值,声明在数据段,int
i;未赋初值,直到被赋值为0时压入栈。
3.3.3类型转换
int sleepsecs =
2.5;int型变量赋值为2.5,隐式转化成整形2;
3.3.4计算
i++;由add操作实现
3.3.5控制转移与关系操作
需要两个判断
判断argc是否不等于3和i是否小于10,通过cmpl和条件跳转je、jle
实现
3.3.6函数操作
通过call调用函数,参数由寄存器%rdi传入,返回值保存在%rax里(未使用);
3.4 本章小结
本章显示简述了编译的概念和作用,具体分析了一个c程序是如何被编译器编译成一个汇编程序的过程,还详细分析了不同的c语句和翻译成汇编语句之后的表示方法。(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
汇编的概念是指的将汇编语言(xxx.s)翻译成机器指令,并将这些指令打包成一种叫做可重定位目标程序,并将这个结果保留在(xxx.o)中。这里的xxx.o是二进制文件。汇编过程的作用是将汇编指令转换成一条条机器可以直接读取分析的机器指令。
4.2 在Ubuntu下汇编的命令
as hello.s -o hello.o
4.3 可重定位目标elf格式
elf
头,用于总得描述文件,可以看到文件格式elf64,小端序,起始位置,节数目等信息。
section head描述各个节的信息,可以看到各个节的名称,类型,大小,地址和偏移等信息。
.rela.text:重定位节,这个节包含了.text(具体指令)节中需要进行重定位的信息。这些信息描述的位置,在由.o文件生成可执行文件的时候需要被修改(重定位)。
4.4 Hello.o的结果解析
与hello.s相比,hello.o的反汇编大致一致,但包含了指令的地址,重定位的信息等内容。
控制转移不是使用跳转到L1,L2的方式,而是直接跳转到某个地址(基于PC寻址或绝对寻址),调用函数也不仅仅时call一个函数名,而是一个地址
4.5 本章小结
本章简述了hello.s汇编指令被转换成hello.o机器指令的过程,通过readelf查看hello.o的ELF、反汇编的方式查看了hello.o反汇编的内容,比较其与hello.s之间的差别。学习了汇编指令映射到机器指令的具体方式。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
概念:将多个可重定位目标程序合并,生成可执行目标文件。
作用:处理可重定位目标程序中的重定位,生成最终的可被加载的文件。
5.2 在Ubuntu下链接的命令
ld -o hello -dynamic-linker
/lib64/ld-linux-x86-64.so.2 /usr/lib/x86_64-linux-gnu/crt1.o
/usr/lib/x86_64-linux-gnu/crti.o hello.o /usr/lib/x86_64-linux-gnu/libc.so
/usr/lib/x86_64-linux-gnu/crtn.o
5.3 可执行目标文件hello的格式
与hello.o的elf类似,可以看到elf head和section head,
5.4 hello的虚拟地址空间
5.5 链接的重定位过程分析
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。结合hello.o的重定位项目,分析hello中对其怎么重定位的。
1.hello与hello.o的不同:
链接器在链接可执行文件或动态库的过程中,它会把来自不同可重定位对象文件中的相同名称的 section 合并起来构成同名的 section,在这里因为链接的时候指定了/lib64/ld-linux-x86-64.so.2,crt1.o、crti.o、crtn.o,所以将会把这些.o文件的每个节与hello.o的节合并。在合并的过程中,会根据重定位信息对相应的地方进行重定位,比如hello.o中的.text节和.data节,接着,它又会把带有相同属性(比方都是只读并可加载的)的 section 都合并成所谓 segments(段)。segments 作为链接器的输出,常被称为输出section。所以说hello.o只是hello的一部分。通过将两者的反汇编代码进行比对也能看出这一点,hello的反汇编代码中也比hello.o的反汇编代码节要多:.init .plt .text
.fini,在这些节中多出了一些main函数运行必要的函数:_start,_init,__libc_csu_init,__libc_csu_fini,__libc_start_main,和在hello中调用的函数:printf、sleep、getchar、exit函数。
2.hello是如何进行重定位的:
在4.3节中我们举了一个具体的例子来说明重定位条目是如何来指导进行重定位的,这里我们继续这个例子来说明:
①首先计算需要被重定位的位置
refptr = .text + r.offset = 0x4005e7 + 0x1b = 0x400602
②然后链接器计算出运行时需要重定位的位置:
refaddr = ADDR(.text) +
r.offset = 0x4005e7+0x1b = 0x400602
③然后更新该位置
*refptr = (unsigned)
(ADDR(r.symbol) + r.addend-refaddr)
5.6 hello的执行流程
通过查看反汇编代码,以及使用gdb单步运行,找出在main函数之前运行的函数名称有:_start,__libc_csu_init,_dl_relocate_static_pie,在main之后运行的函数有:thread_create,thread_wait,thread1,thread2,__libc_csu_fini
5.7 Hello的动态链接分析
在edb调试之后我们发现原先0x00600a10开始的global_offset表是全0的状态,在执行过_dl_init之后被赋上了相应的偏移量的值。这说明dl_init操作是给程序赋上当前执行的内存地址偏移量,这是初始化hello程序的一步。
5.8 本章小结
本章介绍了链接的概念和作用,分析了hello的ELF格式,虚拟地址空间的分配,重定位和执行过程还有动态链接的过程。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
进程是计算机程序需要进行对数据集合进行操作所运行的一次活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
6.2 简述壳Shell-bash的作用与处理流程
作用:shell是一个交互型的应用级程序,它代表用户运行其他程序。
流程:
1)从终端获得命令行。
2)解析命令行中的命令、参数。
3)若为内核命令,则立即执行。
4)若为调用,则创建一个新的进程以执行。
6.3 Hello的fork进程创建过程
如图6.1,其中./hello要求shell创建新的进程以调用hello,其参数为1180300814和xtl。
子进程得到与父进程用户级虚拟地址空间相同但独立的一份副本,包括代码和数据段、堆、共享库以及用户栈。子进程还获得与父进程任何打开文件描述符相同的副本,这就意味着当父进程调用fork时,子进程还可以读写父进程中打开的任何文件。父进程和新创建的子进程之间最大的区别在于它们有不同的PID。
6.4 Hello的execve过程
在子进程被创建后,shell会调用execve函数以加载并运行hello程序。
当加载器运行时,它创建类似于下图所示的内存映像。在程序头部表的引导下,加载器将可执行文件的片复制到代码段和数据段。接下来,加载器跳转到程序的入口点,也就是_start函数的地址。这个函数是在系统目标文件ctrl.o中定义的,对所有的C程序都是一样的。_start函数调用系统启动函数__libc_start_main,该函数定义在libc.so中。它初始化执行环境,调用用户层的main函数,处理main函数的返回值,并且在需要的时候把控制返回给内核。
内存结构如图6.2所示。
6.5 Hello的进程执行
在调用sleep过程之前,进程会一直保持运行,直到被其他进程抢占,这时会发生上下文切换:内核中的调度器会保存该进程的上下文,恢复被调用进程的上下文,并将控制转交给被调用的进程。
此外,在调用sleep过程时,也会发生上下文切换:内核处理休眠导致的主动释放控制,同时定时器开始计时,当到达特定时间后,定时器会发送一个信号给内核,以恢复当前进程的执行。
而当调用getchar时,实际上进程遭遇了一个陷阱,此时调度器会调度执行其他进程,直到其捕获了键盘输入的一个信号,才会恢复该进程的执行。
6.6 hello的异常与信号处理
hello执行过程中可能出现以下四种异常:中断、陷阱、故障、终止
中断是异步发生的,是来自处理器外部的I/O设备的信号的结果。硬件中断的异常处理程序被称为中断处理程序。
陷阱是有意的异常,是执行一条指令的结果。陷阱处理程序将控制返回到下一条指令。
故障由错误情况引起,它可能能够被故障处理程序修正。当故障发生时,处理器将控制转移给故障处理程序。如果处理程序能够修正这个错误情况,它就将控制返回到引起故障的指令并重新执行,否则处理程序返回到内核中的abort例程,终止引起故障的程序。
终止是非故意、不可恢复的致命错误造成的,通常是一些硬件错误,如非法指令,奇偶校验错误,机器检查等。终止处理程序从不将控制返回给应用程序,直接将控制返回到内核中的abort例程,终止这个程序。
6.7本章小结
本章介绍了进程的概念和作用,描述了shell如何在用户和系统内核之间建起一个交互的桥梁。讲述了shell的基本操作以及各种内核信号和命令,还总结了shell是如何fork新建子进程、execve如何执行进程、hello进程如何在内核和前端中反复跳跃运行的。
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
物理地址:CPU通过地址总线的寻址,找到真实的物理内存对应地址。 CPU对内存的访问是通过连接着CPU和北桥芯片的前端总线来完成的。在前端总线上传输的内存地址都是物理内存地址。
逻辑地址:程序代码经过编译后出现在 汇编程序中地址。逻辑地址由选择符(在实模式下是描述符,在保护模式下是用来选择描述符的选择符)和偏移量(偏移部分)组成。
线性地址:逻辑地址经过段机制后转化为线性地址,为描述符:偏移量的组合形式。分页机制中线性地址作为输入。
至于虚拟地址,只关注CSAPP课本中提到的虚拟地址,实际上就是这里的线性地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
一个逻辑地址由两部分组成,段标识符和段内偏移量。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号。后面3位包含一些硬件细节。可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段。
7.3 Hello的线性地址到物理地址的变换-页式管理
虚拟内存被组织为一个由存放在磁盘上的N个连续的字节大小的单元组成的数组。每个字节都有一个唯一的虚拟地址,作为到数组的索引。磁盘上的数据被分割成块,这些块作为磁盘和主存之间的传输单元。虚拟页则是虚拟内存被分割为固定大小的块。物理内存被分割为物理页,大小与虚拟页大小相同。
地址翻译的过程:
页面命中:
1)处理器生成一个虚拟地址,并将其传送给MMU
2-3)MMU使用内存中的页表生成PTE地址,高速缓存/主存向MMU返回PTE
4)MMU将物理地址传送给高速缓存/主存
5)高速缓存/主存返回所请求的数据字给处理器
页不命中(缺页异常):
1)处理器将虚拟地址发送给MMU
2-3)MMU使用内存中的页表生成PTE地址
4)有效位为零, 因此MMU触发缺页异常
5)缺页处理程序确定物理内存中牺牲页(若页面被修改,则换出到磁盘)
6)缺页处理程序调入新的页面,并更新内存中的PTE
7)缺页处理程序返回到原来进程,再次执行导致缺页的指令
7.4 TLB与四级页表支持下的VA到PA的变换
7.5 三级Cache支持下的物理内存访问
CPU发出一个虚拟地址,在TLB里寻找。如果命中,那么将PTE发送给L1Cache,否则先在页表中更新PTE。然后再进行L1根据PTE寻找物理地址,检测是否命中。如此依次进行。
7.6 hello进程fork时的内存映射
系统调用fork时,内核为hello创建子进程,同时创建数据结构,给hello分配唯一的PID。为了给hello创建虚拟内存,内核创建当前进程的mm_struct、区域结构和样表的原样副本,并将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。
7.7 hello进程execve时的内存映射
execve函数调用驻留在内核区域的启动加载器代码,在当前进程中加载并运行包含在可执行目标文件hello中的程序,用hello程序有效地替代了当前程序。加载并运行hello需要以下几个步骤:
1.删除已存在的用户区域,删除当前进程虚拟地址的用户部分中的已存在的区域结构。
2.映射私有区域,为新程序的代码、数据、bss和栈区域创建新的区域结构,所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为hello文件中的.text和.data区,bss区域是请求二进制零的,映射到匿名文件,其大小包含在hello中,栈和堆地址也是请求二进制零的,初始长度为零。
3.映射共享区域, hello程序与共享对象libc.so链接,libc.so是动态链接到这个程序中的,然后再映射到用户虚拟地址空间中的共享区域内。
4.设置程序计数器(PC),execve做的最后一件事情就是设置当前进程上下文的程序计数器,使之指向代码区域的入口点。
7.8 缺页故障与缺页中断处理
缺页故障是一种常见的故障,当指令引用一个虚拟地址,在MMU中查找页表时发现与该地址相对应的物理地址不在内存中,因此必须从磁盘中取出的时候就会发生故障。
缺页中断处理:缺页处理程序是系统内核中的代码,选择一个牺牲页面,如果这个牺牲页面被修改过,那么就将它交换出去,换入新的页面并更新页表。当缺页处理程序返回时,CPU重新启动引起缺页的指令,这条指令再次发送VA到MMU,这次MMU就能正常翻译VA了。
7.9动态存储分配管理
动态存储可用内存空间被称为堆。
第一个字是一个双字节边界对齐的不使用的填充字。填充后面紧跟一个特殊的序言块,这是一个8字节的已分配块,只由一个头部和一个脚部组成。序言块是在初始化时创建的,并且永不释放。在序言块后紧跟的是零个或者多个由malloc或者free调用创建的普通块。堆总是以一个特殊的结尾块来结束,这个块是一个大小为零的普通块。堆总是以一个特殊的结尾块来结束,这个块是一个大小为零的已分配块,只由一个头部组成。序言块和结尾块是一种消除合并时条件的技巧。分配器使用一个单独的私有全局变量,它总是指向序言块。
在每个块的开头和结尾,分别添加头部和脚部,其记录了块大小和是否被分配。之后是有效载荷,其可以记录所需记录的数据。
为了保证各个块的字节对齐,块中会有填充空间。
在分配块时,一个应用通过调用mm_malloc函数来向内存请求大小为size字节的块。在检查完请求的真假后,分配器必须调整请求块的大小,从而为头部和脚部留有空间,并满足双字对齐的要求。
一旦分配器调整了请求的大小,它就会搜索空闲链表,寻找一个合适的空闲块。如果有合适的,那么分配器就放置这个请求块,并可选地分割出多余的部分,然后返回新分配块的地址。
7.10本章小结
计算机中的存储结构具有极强的层次性。
从上到下,存储空间逐渐扩大,读写速度逐渐减慢。
由于局部性的存在,我们可以在保证低花费的情况,有着较好的速度。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件
设备管理:unix io接口
一个Linux文件就是一个m字节的序列;
所有的I/O设备都被模型化为文件;
I/O操作可看作对相应文件的读或写
8.2 简述Unix IO接口及其函数
linux提供如下IO接口函数:
read和write:最简单的读写函数
readn和writen:原子性读写操作
recvfrom和sendto:增加了目标地址和地址结构长度的参数
recv和send:允许从进程到内核传递标志
readv和writev:允许指定往其中输入数据或从其中输出数据的缓冲区
recvmsg和sendmsg:结合了其他IO函数的所有特性,并具备接受和发送辅助数据的能力
8.3 printf的实现分析
使用vsprintf来格式化。它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出。
从vsprintf生成显示信息,到write系统函数,到陷阱系统调用 int 0x80或syscall。字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章节讲述了一下linux的I/O设备管理机制,了解了开、关、读、写、转移文件的接口及相关函数,简单分析了printf和getchar函数的实现方法以及操作过程
(第8章1分)
结论
Hello的P2P过程大抵如下。
1)编写:通过IDE将代码键入hello.c
2)预处理:所有外部的库展开合并到一个hello.i文件中
3)编译:将hello.i编译成为汇编文件hello.s
4)汇编:将hello.s汇编成为可重定位目标文件hello.o
5)链接:将hello.o与可重定位目标文件和动态链接库链接成为可执行目标程序hello
6)运行:在shell中输入./hello 1183710211 WDZR
7)创建子进程:shell进程调用fork为其创建子进程
8)运行程序:shell调用execve,在子进程中加载可执行目标文件,根据入口执行main函数。
9)执行指令:CPU为其分配时间片,在一个时间片中,hello享有CPU资源,顺序执行自己的控制逻辑流
10)访问内存:MMU将程序中使用的虚拟内存地址通过页表映射成物理地址。
11)动态申请内存:printf会调用malloc向动态内存分配器申请堆中的内存。
12)结束:shell父进程回收子进程,内核删除为这个进程创建的所有数据结构。
(结论0分,缺少 -1分,根据内容酌情加分)
附件
hello.c源代码
hello.i预处理生成文件
hello.s编译生成的文件
hello.o打包成的可重定位目标文件
hello链接生成的可执行目标文件
helloo.elf可重定位目标文件的ELF格式
helloo_disas.txt可重定位目标文件的反汇编生成代码
hello.elf可执行目标文件的ELF格式
hello_disas.txt可执行目标文件的反汇编生成代码
(附件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.
(参考文献0分,确实 -1分)