CSAPP大作业hello程序的一生

计算机系统

大作业

题 目 程序人生-Hello’s P2P
专 业 计算机
学   号 1190201501
班   级 1903009
学 生 孙宇帅    
指 导 教 师 吴锐

计算机科学与技术学院
2021年5月
摘 要
本文介绍了hello程序的一生,包括在linux系统下的预处理,编译,汇编,链接,进程管理等阶段。运用相应的工具,进行了具体的分析与总结,目的是回顾计算机系统所学内容,加深印象,增进对程序运行过程和计算机内部结构的了解。

关键词:hello程序,linux,链接,编译,汇编语言,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的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
P2P:Program to Process
Program:通过键盘键入,得到hello.c程序
Process:源程序hello.c在预处理器(cpp)处理下,得到hello.i,通过编译器(ccl),得到汇编程序hello.s,再通过汇编器(as),得到可重定位的目标程序hello.o,最后通过链接器(ld)得到可执行的目标程序hello。在shell中键入运行命令后,shell调用fork函数为其创建子进程。
020:zero to zero
shell为hello进程execve,映射虚拟内存,进入程序入口后程序开始载入物理内存。进入 main 函数执行目标代码,CPU为执行文件hello分配时间周期,执行逻辑控制流,每条指令在流水线上取值、译码、执行、访存、写回、更新PC。当程序运行结束后, shell 父进程负责回收 hello 进程,内核删除相关数据结构。Hello程序从无到有再到无的过程就是020

1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
硬件环境
X64CPU;
2GHz;
16G RAM;
512GHD Disk
软件环境
Windows10 64位;Virtual Box;Ubuntu 20.04LTS 64位/
开发工具
GCC visual studio codes CodeBlocks gdb

1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
文件名 文件作用
hello.c Hello程序源文件
hello.i 经过预处理之后的文件
hello.s Hello.i经过编译之后的文件
hello.o Hello.s汇编生成的可重定位二进制文件
hello 经过链接之后的可执行文件
elf.txt Hello.o的elf文档
helloelf.txt Hello的elf文档

1.4 本章小结
本章对helloc程序的生命周期进行了粗略的介绍,简述了p2p,020的过程,介绍了实验所用的软硬件环境及开发工具,以及阐述了生成中间结果文件的名称及作用。
(第1章0.5分)

第2章 预处理
2.1 预处理的概念与作用
概念
预处理一般是指在程序源代码被翻译为目标代码的过程中,生成二进制代码之前的过程即,即在编译之前进行的处理。预处理中会展开以#起始的行,试图解释为预处理指令,预处理指令一般被用来使源代码在不同的执行环境中被方便的修改或者编译。
作用
1.将源文件中用#include 形式声明的文件包含到新的程序中。
2.用实际值替换用#define 定义的宏定义
3.根据#if 后面的条件决定需要编译的代码

2.2在Ubuntu下预处理的命令

2.3 Hello的预处理结果解析
整个程序拓展到3060行,主体部分是stdio.h unistd.h stdlib.h 的依次展开,原来hello.c的程序出现末尾

2.4 本章小结
本章主要介绍了预处理的概念和作用,了解到了预处理的宏定义,文件包含,条件编译等方面的内容,并且对hello.c经过预处理得到的hello.i文件进行了解析。
(第2章0.5分)

第3章 编译
3.1 编译的概念与作用
注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序
概念
利用编译程序,通过词法分析和语法分析,将符合语法规则的指令都翻译成汇编代码。
作用
将源代码转换为汇编代码
编译包括以下基本流程:
1.语法分析:编译程序的语法分析器以单词符号作为输入,分析单词符号串是否形成符合语法规则的语法单位,方法分为两种:自上而下分析法和自下而上分析法。
2.中间代码:源程序的一种内部表示,或称中间语言。中间代码的作用是可使编译程序的结构在逻辑上更为简单明确,特别是可使目标代码的优化比较容易实现中间代码。
3.代码优化:指对程序进行多种等价变换,使得从变换后的程序出发,能生成更有效的目标代码。
4.目标代码:生成是编译的最后一个阶段。目标代码生成器把语法分析后或优化后的中间代码变换成目标代码。此处指汇编语言代码,须经过汇编程序汇编后,成为可执行的机器语言代码

3.2 在Ubuntu下编译的命令

3.3 Hello的编译结果解析
Hello.c源程序

3.3.1 数据
1)常量

常量以立即数的形式出现,值保存的位置在.text中,作为指令的一部分
2)变量
初始化的全局变量储存在.data节,它的初始化不需要汇编语句,而是直接完成的。
局部变量存储在寄存器或栈中。
程序中定义了局部变量int i,
i的初始化

循环迭代

3.3.2 赋值
对变量i的赋值

3.3.3 类型转换

Argv[3]是字符串格式,使用了atoi函数,将其转换为int型

3.3.4算术操作
循环中对i进行了自加操作

3.3.5关系操作
判断argc是否等于4

汇编代码为

Je为等于跳转

For循环条件i<8

汇编代码

Jle为小于等于跳转
3.3.6 数组操作
Main函数参数中有指针数组char argv[]
argv地址放在了寄存器RSI中,使用时放在栈中
Char
占8个字节

3.3.7 函数操作
main函数:传入参数argc和argv[],分别用寄存器%rdi和%rsi存储,设置%eax为0并且返回

Printf函数
共有两处
第一处

call puts时只传入了字符串参数首地址

第二处

for循环中call  printf时传入了 argv[1]和argc[2]的地址。

Exit函数:传入的参数为1,再执行退出命令
汇编代码

Atoi函数:传入参数argv[3],返回值保存在%eax中

Sleep函数:传入参数atoi(argv[3]),%eax为atoi函数的返回值,存入%edi中作为参数传入sleep函数

Getchar函数

3.4 本章小结
本章介绍了编译的概念及作用,解析了hello.i编译成hello.s后的常量,变量,相关运算,关系比较,数组操作,函数调用等汇编语句,从更底层的层面理解了C语言的数据与操作。
(第3章2分)

第4章 汇编
4.1 汇编的概念与作用
概念
驱动程序运行汇编器as,将汇编语言(hello.s)翻译成机器语言(hello.o)的过程称为汇编,hello.o是一个二进制文件,包含着程序的指令编码。
作用
将高级语言转化为机器可直接识别执行的二进制机器代码,这个二进制机器代码是程序在本机器上的机器语言的表示。
4.2 在Ubuntu下汇编的命令

4.3 可重定位目标elf格式
4.3.1使用readelf命令查看hello.o的ELF格式

4.3.2ELF 头
包含了系统信息,编码方式,ELF头大小,节的大小和数量等等一系列信息。Elf头内容如下:

Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
类别: ELF64
数据: 2 补码,小端序 (little endian)
Version: 1 (current)
OS/ABI: UNIX - System V
ABI 版本: 0
类型: REL (可重定位文件)
系统架构: Advanced Micro Devices X86-64
版本: 0x1
入口点地址: 0x0
程序头起点: 0 (bytes into file)
Start of section headers: 1240 (bytes into file)
标志: 0x0
Size of this header: 64 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 64 (bytes)
Number of section headers: 14
Section header string table index: 13
4.3.3 节头目表
描述了.o文件中出现的各个节的类型、位置、所占空间大小等信息。
[号] 名称 类型 地址 偏移量
大小 全体大小 旗标 链接 信息 对齐
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
0000000000000092 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 00000388
00000000000000c0 0000000000000018 I 11 1 8
[ 3] .data PROGBITS 0000000000000000 000000d2
0000000000000000 0000000000000000 WA 0 0 1
[ 4] .bss NOBITS 0000000000000000 000000d2
0000000000000000 0000000000000000 WA 0 0 1
[ 5] .rodata PROGBITS 0000000000000000 000000d8
0000000000000033 0000000000000000 A 0 0 8
[ 6] .comment PROGBITS 0000000000000000 0000010b
000000000000002b 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 00000136
0000000000000000 0000000000000000 0 0 1
[ 8] .note.gnu.propert NOTE 0000000000000000 00000138
0000000000000020 0000000000000000 A 0 0 8
[ 9] .eh_frame PROGBITS 0000000000000000 00000158
0000000000000038 0000000000000000 A 0 0 8
[10] .rela.eh_frame RELA 0000000000000000 00000448
0000000000000018 0000000000000018 I 11 9 8
[11] .symtab SYMTAB 0000000000000000 00000190
00000000000001b0 0000000000000018 12 10 8
[12] .strtab STRTAB 0000000000000000 00000340
0000000000000048 0000000000000000 0 0 1
[13] .shstrtab STRTAB 0000000000000000 00000460
0000000000000074 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)

4.3.4 重定位节
表述了各个段引用的外部符号等,在链接时,需要通过重定位节对这些位置的地址进行修改。链接器会通过重定位条目的类型判断该使用什么养的方法计算正确的地址值,通过偏移量等信息计算出正确的地址。

重定位节 ‘.rela.text’ at offset 0x388 contains 8 entries:
偏移量 信息 类型 符号值 符号名称 + 加数
00000000001c 000500000002 R_X86_64_PC32 0000000000000000 .rodata - 4
000000000021 000c00000004 R_X86_64_PLT32 0000000000000000 puts - 4
00000000002b 000d00000004 R_X86_64_PLT32 0000000000000000 exit - 4
000000000054 000500000002 R_X86_64_PC32 0000000000000000 .rodata + 22
00000000005e 000e00000004 R_X86_64_PLT32 0000000000000000 printf - 4
000000000071 000f00000004 R_X86_64_PLT32 0000000000000000 atoi - 4
000000000078 001000000004 R_X86_64_PLT32 0000000000000000 sleep - 4
000000000087 001100000004 R_X86_64_PLT32 0000000000000000 getchar - 4

重定位节 ‘.rela.eh_frame’ at offset 0x448 contains 1 entry:
偏移量 信息 类型 符号值 符号名称 + 加数
000000000020 000200000002 R_X86_64_PC32 0000000000000000 .text + 0
4.4 Hello.o的结果解析
objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。
获取hello.o的反汇编

区别:
1.数据表示:Hello.s中对立即数的操作是十进制的,hello.o的反汇编中是十六进制的
2.分支转移:跳转语句之后,hello.s中是.L2,.L3等段名称,而hello.o的反汇编中跳转指令之后是相对偏移的地址
3.函数调用:hello.s中,call指令使用的是函数名称,而反汇编代码中call指令使用的是main函数的相对偏移地址
4.5 本章小结
本章通过介绍汇编代码与反汇编代码,对比了二者的区别,了解了汇编、反汇编这两种不相同的程序表现形式,更深刻地理解了汇编语言到机器语言实现地转变。
(第4章1分)

第5章 链接
5.1 链接的概念与作用
概念
链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,链接执行符号解析、重定位过程。
作用
把可重定位目标文件和命令行参数作为输入,生成可以正常工作的可执行文件。这令分离编译成为可能,节省了工作空间。
5.2 在Ubuntu下链接的命令

5.3 可执行目标文件hello的格式
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
5.3.1使用readelf命令查看hello的ELF格式

5.3.2 ELF头
ELF 头:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
类别: ELF64
数据: 2 补码,小端序 (little endian)
Version: 1 (current)
OS/ABI: UNIX - System V
ABI 版本: 0
类型: EXEC (可执行文件)
系统架构: Advanced Micro Devices X86-64
版本: 0x1
入口点地址: 0x4010f0
程序头起点: 64 (bytes into file)
Start of section headers: 14208 (bytes into file)
标志: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 12
Size of section headers: 64 (bytes)
Number of section headers: 27
Section header string table index: 26
5.3.3 节头部表
描述了各个节的大小、偏移量和其他属性。链接器链接时,会将各个文件的相同段合并成一个大段,并且根据这个大段的大小以及偏移量重新设置各个符号的地址。

节头:
[号] 名称 类型 地址 偏移量
大小 全体大小 旗标 链接 信息 对齐
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 00000000004002e0 000002e0
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.gnu.propert NOTE 0000000000400300 00000300
0000000000000020 0000000000000000 A 0 0 8
[ 3] .note.ABI-tag NOTE 0000000000400320 00000320
0000000000000020 0000000000000000 A 0 0 4
[ 4] .hash HASH 0000000000400340 00000340
0000000000000038 0000000000000004 A 6 0 8
[ 5] .gnu.hash GNU_HASH 0000000000400378 00000378
000000000000001c 0000000000000000 A 6 0 8
[ 6] .dynsym DYNSYM 0000000000400398 00000398
00000000000000d8 0000000000000018 A 7 1 8
[ 7] .dynstr STRTAB 0000000000400470 00000470
000000000000005c 0000000000000000 A 0 0 1
[ 8] .gnu.version VERSYM 00000000004004cc 000004cc
0000000000000012 0000000000000002 A 6 0 2
[ 9] .gnu.version_r VERNEED 00000000004004e0 000004e0
0000000000000020 0000000000000000 A 7 1 8
[10] .rela.dyn RELA 0000000000400500 00000500
0000000000000030 0000000000000018 A 6 0 8
[11] .rela.plt RELA 0000000000400530 00000530
0000000000000090 0000000000000018 AI 6 21 8
[12] .init PROGBITS 0000000000401000 00001000
000000000000001b 0000000000000000 AX 0 0 4
[13] .plt PROGBITS 0000000000401020 00001020
0000000000000070 0000000000000010 AX 0 0 16
[14] .plt.sec PROGBITS 0000000000401090 00001090
0000000000000060 0000000000000010 AX 0 0 16
[15] .text PROGBITS 00000000004010f0 000010f0
0000000000000145 0000000000000000 AX 0 0 16
[16] .fini PROGBITS 0000000000401238 00001238
000000000000000d 0000000000000000 AX 0 0 4
[17] .rodata PROGBITS 0000000000402000 00002000
000000000000003b 0000000000000000 A 0 0 8
[18] .eh_frame PROGBITS 0000000000402040 00002040
00000000000000fc 0000000000000000 A 0 0 8
[19] .dynamic DYNAMIC 0000000000403e50 00002e50
00000000000001a0 0000000000000010 WA 7 0 8
[20] .got PROGBITS 0000000000403ff0 00002ff0
0000000000000010 0000000000000008 WA 0 0 8
[21] .got.plt PROGBITS 0000000000404000 00003000
0000000000000048 0000000000000008 WA 0 0 8
[22] .data PROGBITS 0000000000404048 00003048
0000000000000004 0000000000000000 WA 0 0 1
[23] .comment PROGBITS 0000000000000000 0000304c
000000000000002a 0000000000000001 MS 0 0 1
[24] .symtab SYMTAB 0000000000000000 00003078
00000000000004c8 0000000000000018 25 30 8
[25] .strtab STRTAB 0000000000000000 00003540
0000000000000158 0000000000000000 0 0 1
[26] .shstrtab STRTAB 0000000000000000 00003698
00000000000000e1 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)
5.3.4 程序头表
程序头:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
0x00000000000002a0 0x00000000000002a0 R 0x8
INTERP 0x00000000000002e0 0x00000000004002e0 0x00000000004002e0
0x000000000000001c 0x000000000000001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x00000000000005c0 0x00000000000005c0 R 0x1000
LOAD 0x0000000000001000 0x0000000000401000 0x0000000000401000
0x0000000000000245 0x0000000000000245 R E 0x1000
LOAD 0x0000000000002000 0x0000000000402000 0x0000000000402000
0x000000000000013c 0x000000000000013c R 0x1000
LOAD 0x0000000000002e50 0x0000000000403e50 0x0000000000403e50
0x00000000000001fc 0x00000000000001fc RW 0x1000
DYNAMIC 0x0000000000002e50 0x0000000000403e50 0x0000000000403e50
0x00000000000001a0 0x00000000000001a0 RW 0x8
NOTE 0x0000000000000300 0x0000000000400300 0x0000000000400300
0x0000000000000020 0x0000000000000020 R 0x8
NOTE 0x0000000000000320 0x0000000000400320 0x0000000000400320
0x0000000000000020 0x0000000000000020 R 0x4
GNU_PROPERTY 0x0000000000000300 0x0000000000400300 0x0000000000400300
0x0000000000000020 0x0000000000000020 R 0x8
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 0x10
GNU_RELRO 0x0000000000002e50 0x0000000000403e50 0x0000000000403e50
0x00000000000001b0 0x00000000000001b0 R 0x1
5.3.5 Section to Segment mapping
段节…
00
01 .interp
02 .interp .note.gnu.property .note.ABI-tag .hash .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt
03 .init .plt .plt.sec .text .fini
04 .rodata .eh_frame
05 .dynamic .got .got.plt .data
06 .dynamic
07 .note.gnu.property
08 .note.ABI-tag
09 .note.gnu.property
10
11 .dynamic .got
5.3.6 Dynamic section
标记 类型 名称/值
0x0000000000000001 (NEEDED) 共享库:[libc.so.6]
0x000000000000000c (INIT) 0x401000
0x000000000000000d (FINI) 0x401238
0x0000000000000004 (HASH) 0x400340
0x000000006ffffef5 (GNU_HASH) 0x400378
0x0000000000000005 (STRTAB) 0x400470
0x0000000000000006 (SYMTAB) 0x400398
0x000000000000000a (STRSZ) 92 (bytes)
0x000000000000000b (SYMENT) 24 (bytes)
0x0000000000000015 (DEBUG) 0x0
0x0000000000000003 (PLTGOT) 0x404000
0x0000000000000002 (PLTRELSZ) 144 (bytes)
0x0000000000000014 (PLTREL) RELA
0x0000000000000017 (JMPREL) 0x400530
0x0000000000000007 (RELA) 0x400500
0x0000000000000008 (RELASZ) 48 (bytes)
0x0000000000000009 (RELAENT) 24 (bytes)
0x000000006ffffffe (VERNEED) 0x4004e0
0x000000006fffffff (VERNEEDNUM) 1
0x000000006ffffff0 (VERSYM) 0x4004cc
0x0000000000000000 (NULL) 0x0
5.3.7 重定位节
重定位节 ‘.rela.dyn’ at offset 0x500 contains 2 entries:
偏移量 信息 类型 符号值 符号名称 + 加数
000000403ff0 000300000006 R_X86_64_GLOB_DAT 0000000000000000 __libc_start_main@GLIBC_2.2.5 + 0
000000403ff8 000500000006 R_X86_64_GLOB_DAT 0000000000000000 gmon_start + 0

重定位节 ‘.rela.plt’ at offset 0x530 contains 6 entries:
偏移量 信息 类型 符号值 符号名称 + 加数
000000404018 000100000007 R_X86_64_JUMP_SLO 0000000000000000 puts@GLIBC_2.2.5 + 0
000000404020 000200000007 R_X86_64_JUMP_SLO 0000000000000000 printf@GLIBC_2.2.5 + 0
000000404028 000400000007 R_X86_64_JUMP_SLO 0000000000000000 getchar@GLIBC_2.2.5 + 0
000000404030 000600000007 R_X86_64_JUMP_SLO 0000000000000000 atoi@GLIBC_2.2.5 + 0
000000404038 000700000007 R_X86_64_JUMP_SLO 0000000000000000 exit@GLIBC_2.2.5 + 0
000000404040 000800000007 R_X86_64_JUMP_SLO 0000000000000000 sleep@GLIBC_2.2.5 + 0

5.3.8 符号表
Symbol table ‘.dynsym’ contains 9 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (2)
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2)
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND getchar@GLIBC_2.2.5 (2)
5: 0000000000000000 0 NOTYPE WEAK DEFAULT UND gmon_start
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND atoi@GLIBC_2.2.5 (2)
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND exit@GLIBC_2.2.5 (2)
8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND sleep@GLIBC_2.2.5 (2)

Symbol table ‘.symtab’ contains 51 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000004002e0 0 SECTION LOCAL DEFAULT 1
2: 0000000000400300 0 SECTION LOCAL DEFAULT 2
3: 0000000000400320 0 SECTION LOCAL DEFAULT 3
4: 0000000000400340 0 SECTION LOCAL DEFAULT 4
5: 0000000000400378 0 SECTION LOCAL DEFAULT 5
6: 0000000000400398 0 SECTION LOCAL DEFAULT 6
7: 0000000000400470 0 SECTION LOCAL DEFAULT 7
8: 00000000004004cc 0 SECTION LOCAL DEFAULT 8
9: 00000000004004e0 0 SECTION LOCAL DEFAULT 9
10: 0000000000400500 0 SECTION LOCAL DEFAULT 10
11: 0000000000400530 0 SECTION LOCAL DEFAULT 11
12: 0000000000401000 0 SECTION LOCAL DEFAULT 12
13: 0000000000401020 0 SECTION LOCAL DEFAULT 13
14: 0000000000401090 0 SECTION LOCAL DEFAULT 14
15: 00000000004010f0 0 SECTION LOCAL DEFAULT 15
16: 0000000000401238 0 SECTION LOCAL DEFAULT 16
17: 0000000000402000 0 SECTION LOCAL DEFAULT 17
18: 0000000000402040 0 SECTION LOCAL DEFAULT 18
19: 0000000000403e50 0 SECTION LOCAL DEFAULT 19
20: 0000000000403ff0 0 SECTION LOCAL DEFAULT 20
21: 0000000000404000 0 SECTION LOCAL DEFAULT 21
22: 0000000000404048 0 SECTION LOCAL DEFAULT 22
23: 0000000000000000 0 SECTION LOCAL DEFAULT 23
24: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello.c
25: 0000000000000000 0 FILE LOCAL DEFAULT ABS
26: 0000000000403e50 0 NOTYPE LOCAL DEFAULT 19 __init_array_end
27: 0000000000403e50 0 OBJECT LOCAL DEFAULT 19 _DYNAMIC
28: 0000000000403e50 0 NOTYPE LOCAL DEFAULT 19 __init_array_start
29: 0000000000404000 0 OBJECT LOCAL DEFAULT 21 GLOBAL_OFFSET_TABLE
30: 0000000000401230 5 FUNC GLOBAL DEFAULT 15 __libc_csu_fini
31: 0000000000404048 0 NOTYPE WEAK DEFAULT 22 data_start
32: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@@GLIBC_2.2.5
33: 000000000040404c 0 NOTYPE GLOBAL DEFAULT 22 _edata
34: 0000000000401238 0 FUNC GLOBAL HIDDEN 16 _fini
35: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@@GLIBC_2.2.5
36: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _libc_start_main@@GLIBC
37: 0000000000404048 0 NOTYPE GLOBAL DEFAULT 22 __data_start
38: 0000000000000000 0 FUNC GLOBAL DEFAULT UND getchar@@GLIBC_2.2.5
39: 0000000000000000 0 NOTYPE WEAK DEFAULT UND gmon_start
40: 0000000000402000 4 OBJECT GLOBAL DEFAULT 17 _IO_stdin_used
41: 00000000004011c0 101 FUNC GLOBAL DEFAULT 15 __libc_csu_init
42: 0000000000404050 0 NOTYPE GLOBAL DEFAULT 22 _end
43: 0000000000401120 5 FUNC GLOBAL HIDDEN 15 _dl_relocate_static_pie
44: 00000000004010f0 47 FUNC GLOBAL DEFAULT 15 _start
45: 000000000040404c 0 NOTYPE GLOBAL DEFAULT 22 __bss_start
46: 0000000000401125 146 FUNC GLOBAL DEFAULT 15 main
47: 0000000000000000 0 FUNC GLOBAL DEFAULT UND atoi@@GLIBC_2.2.5
48: 0000000000000000 0 FUNC GLOBAL DEFAULT UND exit@@GLIBC_2.2.5
49: 0000000000000000 0 FUNC GLOBAL DEFAULT UND sleep@@GLIBC_2.2.5
50: 0000000000401000 0 FUNC GLOBAL HIDDEN 12 _init
5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
Data Dump 窗口可以查看加载到虚拟地址中的 hello 程序

.text段地址从4010f0开始

.
5.5 链接的重定位过程分析
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
使用

获得反汇编代码

Hello.o的反汇编与hello的反汇编的区别
1.在hello中链接器加入了hello.c所用的库函数exit、printf、atoi、sleep、getchar,而hello.o的反汇编中代码只是.text段和main函数
2.Hello.o的反汇编地址从0开始,hello的反汇编地址从0x400000开始
3.hello中没有hello.o中的重定位条目,并且跳转和函数调用的地址在hello中都变成了虚拟内存地址。对于hello.o的反汇编代码,函数只有在链接之后才能确定运行执行的地址,因此在.rela.text节中为其添加了重定位条目
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
链接过程
链接器将各个目标文件链接到一起,进行符号解析和重定位,将各个目标文件组装在一起,文件中的各个函数段按照重定位条目累计在一起
5.6 hello的执行流程
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
程序的执行流程
401000 <_init>
401020 <.plt>
401030 puts@plt
401040 printf@plt
401050 getchar@plt
401060 atoi@plt
401070 exit@plt
401080 sleep@plt
401090 <_start>
4010c0 <_dl_relocate_static_pie>
4010c1 
401150 <__libc_csu_init>
4011b0 <__libc_csu_fini>
4011b4 <_fini>

通过edb记录下call命令进入的函数
5.7 Hello的动态链接分析
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
在elf文件中找到

通过edb查看

执行_init前

执行_init后

对于库函数而言,需要plt、got合作,plt初始存的是一批代码,它们跳转到got所指示的位置,然后调用链接器,在加载时,动态链接器会重定位GOT中的条目,来获得正确的绝对地址。
5.8 本章小结
本章介绍了链接器的概念和作用,分析了可执行文件hello的elf格式,虚拟空间的地址分配,介绍了链接过程中的重定位和动态链接的执行过程。
(第5章1分)

第6章 hello进程管理
6.1 进程的概念与作用
概念
进程是操作系统对一个正在运行的程序的一种抽象
作用
进程提供给应用程序的关键抽象:一个独立的逻辑控制流,如同程序独占处理器;一个私有的地址空间,如同程序独占内存系统
6.2 简述壳Shell-bash的作用与处理流程
作用
解释命令,连接用户和操作系统以及内核
处理流程
1.用户键入命令
2.shell对用户输入命令进行解析,判断是否为内置命令
3.若为内置命令,调用内置命令处理函数,否则调用execve函数创建一个子进程进行运行
4.判断是否为前台运行程序,如果是,则调用等待函数等待前台作业结束;否则将程序转入后台,直接开始下一次用户输入命令
5.shell应该接受键盘输入信号,并对这些信号进行相应处理
6.3 Hello的fork进程创建过程
执行hello后,父进程进行判断,如果不是内部指令,即会调用fork函数创建子进程。子进程与父进程近似,并得到一份与父进程用户级虚拟空间相同且独立的副本——包括数据段、代码、共享库、堆和用户栈。二者间的PID不相同,fork函数会返回两次,在父进程中,返回子进程的PID,在子进程中,返回0。
6.4 Hello的execve过程
execve函数加载并运行可执行文件hello,且带有参数列表argv和环境变量envp,只有当出现错误时,例如找不到hello时,execve才会返回到调用程序。在execve加载了可执行程序之后,它调用启动代码。启动代码设置栈,并将控制传递给新程序的主函数,即可执行程序的main函数,进入main函数后便开始逐步运行程序。

6.5 Hello的进程执行
结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。
逻辑控制流
系统在运行进程时维护着一系列程序计数器(PC)的值,这些值唯一地对应于包含在程序的可执行目标文件中的指令,或是包含在运行时动态链接到程序的共享对象中的指令,这个PC的值序列就叫做逻辑控制流。
时间片
是分时操作系统分配给每个正在运行的进程微观上的一段CPU时间时间片由操作系统内核的调度程序分配给每个进程。首先,内核会给每个进程分配相等的初始时间片,然后每个进程轮番地执行相应的时间,当所有进程都处于时间片耗尽的状态时,内核会重新为每个进程计算并分配时间片,如此往复。
用户模式和内核模式
处理器提供一种机制,限制一个应用可以执行的指令以及它可以访问的地址空间范围。处理器通常是用某个控制寄存器中的一个模式位来提供给这种功能的。当设置了模式位时,进程就运行在内核模式当中,可以执行指令集中的任何指令,并且可以访问系统中的任何内存位置。没有设置模式位时,进程就运行在用户模式种,不允许执行特权指令,或者发起一个IO请求,也不允许用户模式种的进程直接引用地址空间中内核区内的代码和数据,任何这样的尝试都会导致知名的保护故障,反之,用户程序必须通过系统调用接口间接的访问内核代码和数据。
上下文切换与程序调度的过程
内核为每个进程维持一个上下文。上下文就是内核重新启动一个被抢占的进程所需要的状态。在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程,这种决策就叫做调度,是由内核中称为调度器的代码处理的。当内核选择了一个新的进程运行时,我们说内核调度了这个进程。在内核调度了一个新的进程运行后,它就抢占当前进程,并使用一种称为上下文切换的机制来控制转移到新的进程。
用户态与核心态的转换
进程只有故障、中断或陷入系统调用时才会得到内核访问权限,其他情况下始终处于用户权限之中,保证了系统的安全性。
6.6 hello的异常与信号处理
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
正常运行状态

产生的异常类型
类别 原因 异步/同步 返回行为
中断 来自I/O设备的信号 异步 总是返回到下一条指令
陷阱 有意的异常 同步 总是返回到下一条指令
故障 潜在可恢复的错误 同步 可能返回到当前指令
终止 不可恢复的错误 同步 不会返回
处理方式
中断:
陷阱:
故障:
终止:

按下CTRL+Z:会发送一个SIGTSTP信号给前台进程组的每个进程,结果是停止前台作业,hello 进程挂起。用ps查看,进程的PID不为0.用jobs查看后台程序,job号为1,用fg命令将其调回前台,会输出剩下的printf指令

按下CTRL+C:内核发送一个SIGINT信号给到前台进程组中的每个进程,结果是终止前台进程,使用ps查看,发现hello程序已经终止

中途乱按:键入的内容会被送入缓冲区,当作命令执行

运行kill命令:将进程终止,ps无法查询到PID

6.7本章小结
本章介绍了进程的概念和作用,并对shell的概念和作用进行介绍,总结了fork函数如何创建子进程和execve函数的运行方式。并且以hello进程为例,介绍了进程的创建,运行,终止,和在运行过程中键盘的输入所带来的影响。
(第6章1分)

第7章 hello的存储管理
7.1 hello的存储器地址空间
结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。
逻辑地址
又称相对地址,是指由程序hello产生的与段相关的偏移地址部分,一个逻辑地址,是由一个段标识符加上一个指定段内相对地址的偏移量
线性地址
是逻辑地址到物理地址变换之间的中间层,程序hello的代码会产生逻辑地址,它加上相应段的基地址就生成了一个线性地址,是对程序运行区块的一个抽象映射
虚拟地址
即逻辑地址,是指由程序产生的与段相关的偏移地址部分
物理地址
是指出现在CPU外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果地址。如果启用了分页机制,那么hello的线性地址会使用页目录和页表中的项变换成hello的物理地址。如果没有启用分页机制,那么线性地址就直接成为物理地址了
7.2 Intel逻辑地址到线性地址的变换-段式管理
在x86保护模式下,段的信息(段基线性地址、长度、权限等)即段描述符占8个字节,段信息无法直接存放在段寄存器中(段寄存器只有2字节)。Intel的设计是段描述符集中存放在GDT或LDT中,而段寄存器存放的是段描述符在GDT或LDT内的索引值(index).
一个逻辑地址由两部分组成,段选择符,段内偏移地址。首先给定一个完整的逻辑地址[段选择符:段内偏移地址]
1.首先判断段选择描述符中的T1字段是0还是1,可以知道当前要转换的是GDT中的段,还是LDT中的段,再根据指定的相应的寄存器,得到其地址和大小,就能够得到一个数组
2.拿出段选择符中的前13位,在这个数组中查找到对应的段描述符,这样就能够得到Base,即基地址
3.把基地址Base+Offset,就是要转换的下一个阶段的地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
将各进程的虚拟空间划分成若干个长度相等的页,页式管理把内存空间按照页的大小划分成片或者页面,然后把页式虚拟地址与内存地址建立一一对应页表,并用相应的硬件地址变换机构,来解决离散地址变换问题。虚拟内存系统中MMU负责地址翻译,MMU使用存放在物理内存中的被称为页表的数据结构将虚拟页到物理页的映射,即虚拟地址到物理地址的映射。
7.4 TLB与四级页表支持下的VA到PA的变换
CPU产生一个虚拟地址,MMU查阅一个PTE,将虚拟地址翻译为物理地址
如果查询PTE的时候发现不在物理内存中,则引发缺页故障,这时候就需要到页表中去找。将虚拟地址的VPN划分为相等大小的不同的部分,每个部分用于寻找由上一级确定的页表基址对应的页表条目,CR3是对应的L1PT的物理地址,然后一步步递进往下寻址,越往下一层每个条目对应的区域越小,寻址越细致,在经过4层寻址之后找到相应的PPN和VPO拼接起来组合获得PA。
7.5 三级Cache支持下的物理内存访问
CPU发送一条虚拟地址,随后MMU按照上述操作获得了物理地址PA,根据组号寻找到正确的组,比较每一个cacheline是否标记位有效以及标记位是否相等。如果命中就直接返回想要的数据,如果不命中,就依次去L2,L3,主存判断是否命中,当命中时,将数据传给CPU同时更新各级cache的cacheline,如果主存缺页则访问硬盘。

7.6 hello进程fork时的内存映射
当hello进程调用fork函数时,内核为新进程创建当前进程的mm_struct,vm_area_struct和页表的原始样本,将两个进程中的每个页面都标记位只读,并将两个进程中的每个区域结构都标记为私有的写时复制,并分配给它一个唯一的PID,同时为这个新进程创建虚拟内存。
当fork在新进程中返回时,新进程拥有与调用fork进程相同的虚拟内存,当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面。因此,也就为每个进程保持了私有空间地址的抽象概念。

7.7 hello进程execve时的内存映射
execve函数在当前进程中加载并运行包含在可执行文件hello中的程序
加载并运行hello的几个步骤:
1.删除已存在的用户区域
2.创建新的区域结构,映射私有区域和共享区域,代码映射到.text区域,初始化的区域映射到.data区,.bss和栈映射到匿名文件。
3.设置程序计数器PC,指向代码区域的入口点。而下一次调度这个进程时,他将从这个入口点开始执行。
7.8 缺页故障与缺页中断处理

1.处理器生成虚拟地址,传送给MMU
2.MMU翻译出PTE地址,并在高速缓存中查找PTE
3.高速缓存将PTE返还给MMU
4.PTE中有效位是0,引发缺页异常,调用缺页异常处理程序
5.该程序选择一个牺牲页把它换到磁盘
6.缺页处理程序页面调入新的页面,并更新内存中的PTE
7.缺页处理程序返回到原来的进程,再次执行导致缺页的命令。
7.9动态存储分配管理
Printf会调用malloc,请简述动态内存管理的基本方法与策略。
动态内存分配器维护着一个进程的虚拟内存区域,称为堆。假设堆是一个请求二进制零的区域,它紧接在未初始化的数据区域后开始,并向上生长。对于每个进程,内核维护着一个变量brk,它指向堆的顶部。
分配器将堆视为一组大小不同的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是以分配的,要么是空闲的。已分配的块显式的保留为供应用程序使用。空闲块可用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。
分配器有两种基本风格,两种风格都要求应用显式的分配快。它们的不同之处在于由哪个实体来负责释放已分配的快。
显式分配器,要求应用显式地释放任何已分配的块。例如C标准库提供一种叫malloc程序包的显式分配器。C程序通过调用malloc函数来分配一个块,并通过调用free函数来释放一个块。C++中的new和delete操作和C中的malloc和free相当。
隐式分配器,另一方面,要求分配器检测一个已分配块何时不再被程序所使用,那么就释放这个块。隐式分配器也叫垃圾收集器,而自动释放未使用的已分配块的过程叫做垃圾收集。
7.10本章小结
本章介绍了hello程序的存储器地址空间,包括各地址空间的转换,页式管理等,介绍了VA到PA的转换,物理内存访问,调用fork函数和execve函数时的内存映射,发生缺页故障后的处理方法,以及动态储存的分配管理方法。
(第7章 2分)

第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件
所有的IO设备都被模型化为文件,所有的输入和输出都被当做对相应文件的读和写来执行。
设备管理:unix io接口
这种将设备模型化为文件的方式,允许Linux内核引出一个简单、低级的应用接口,称为Unix I/O。
8.2 简述Unix IO接口及其函数
unix io接口:
打开文件。一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个I/O设备。
改变当前文件的位置。对于每个打开的文件,内核保持着一个文件位置k、初始为0。这个文件位置是从文件开头起始的字节偏移量。应用程序能够通过执行seek操作,显示地设置文件的当前位置为k。
读写文件。一个读操作就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增加到k+n。写操作就是从内存复制n>0个字节到一个文件,从当前文件位置k开始,然后更新k。
关闭文件。当应用完成了对文件的访问之后,它就通知内核关闭这个文件。
操作函数:
1.Open()
打开一个已经存在的文件或是创建一个新文件
函数原型:int open(const char *pathname,int flags,int perms)
返回值:成功:返回文件描述符;失败:返回-1

2.Read()
从文件读取数据,执行输出
函数原型:ssize_t read(int fd, void *buf, size_t count);
返回值:返回所读取的字节数;0(读到EOF);-1(出错)。

3.Write()
向文件写入数据
函数原型:ssize_t write(int fd, void *buf, size_t count);
返回值:写入文件的字节数(成功);-1(出错)

4.Close()
关闭一个被打开的的文件
函数原型:int close(int fd)
函数返回值:0成功,-1出错

5.Lseek()
用于在指定的文件描述符中将文件指针定位到相应位置
函数原型:off_t lseek(int fd, off_t offset,int whence);
返回值:成功:返回当前位移;失败:返回-1

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函数将所有的参数内容格式化之后存入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才开始从stdio流中每次读入一个字符。getchar函数的返回值是用户输入的第一个字符的ascii码,如出错返回-1,且将用户输入的字符回显到屏幕。如用户在按回车之前输入了不止一个字符,其他字符会保留在键盘缓存区中,等待后续getchar调用读取。也就是说,后续的getchar调用不会等待用户按键,而直接读取缓冲区中的字符,直到缓冲区中的字符读完为后,才等待用户按键。
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章介绍了 Linux 的 I/O 设备的基本概念和管理方法,以及Unix I/O 接口及其相关函数,并且分析了printf 函数和 getchar 函数的工作过程
(第8章1分)
结论
用计算机系统的语言,逐条总结hello所经历的过程。
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
丰富多彩的hello的一生:
hello.c经过预处理,得到hello.i文本文件
hello.i经过编译,得到汇编代码hello.s
hello.s经过汇编,得到二进制可重定位目标文件hello.o
hello.o经过链接,得到可执行程序hello
进程调用fork函数,生成子进程
execve函数加载运行当新程序hello
hello运行会调用printf函数,与IO设备密切相关
最终,hello被父进程回收,内核会回收为其创建的所有信息

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

附件
列出所有的中间产物的文件名,并予以说明起作用。
文件名 文件作用
hello.c Hello程序源文件
hello.i 经过预处理之后的文件
hello.s Hello.i经过编译之后的文件
hello.o Hello.s汇编生成的可重定位二进制文件
hello 经过链接之后的可执行文件
elf.txt Hello.o的elf文档
helloelf.txt Hello的elf文档

(附件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分)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值