程序人生-hello-csapp大作业

 

第1章 概述

1.1 Hello简介

P2P: From Program to Process

        在编译器的处理下,hello.c文件经历预处理、编译、汇编、链接,四个步骤,变为可执行文件(program)然后由shell为其创建一个新的进程(process)并运行它。

020: From Zero To Zero

        在它还没有被执行的时候(Zero),shell先为其映射出虚拟内存,然后在开始运行进程时写入其中,进程结束后由shell回收。

1.2 环境与工具

硬件环境:

 

      X64CPU;     8GHz;   8GRAM;      1TB HD

 

软件环境:

 

      Windows10 64位;VMware14.12;     Ubuntu 16.04 LTS 64位

 

使用工具:

 

      codeblocks,objdump,gdb,edb,hexedit

1.3 中间结果

列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。

1.4 本章小结

    (hello的故事就这样开始了)

 

 

 

第2章 预处理

2.1 预处理的概念与作用

预处理器根据以字符#开头的命令,修改原始的c程序。预处理器会读取头文件的内容并直接插入程序文本中,替换宏定义,清理注释,并以.i作为新的到的文件的扩展名。

作用:使其规格至能被汇编器正确编译为汇编文件

2.2在Ubuntu下预处理的命令

linux 下命令为 gcc -E xxx.c -o xxx.i

意思为将xxx.c 预处理并将结果放在xxx.i

编译结果

图2.2.1编译结果

2.3 Hello的预处理结果解析

预处理结束后文件大了很多

图2.3.1hello.i文件属性

打开文件发现主要有以下几种文本结构

描述动态库位置

图2.3.2hello.i文件部分截图

声明变量类型

图2.3.3 hello.i中声明部分

函数声明

图2.3.4 函数声明部分

以及修改过的hello.c部分

图2.3.5 源码部分

2.4 本章小结

预处理是对c文件文本上的处理,简单但是十分重要,它补全了编译所需信息。

(于是hello拿走了全村所有的[头文]剑,出发了,但他好像…只带了声明。)

 

第3章 编译

3.1 编译的概念与作用

编译是指.i文件在编译器的作用下生成相应的汇编文件(.s)的过程。

这个过程将较偏向自然语言的c文件,转换为偏机器语言的汇编文件,为下一步的汇编生成机器码创造了条件,同时也保持了一定的可读性,和微弱的可移植性。

3.2 在Ubuntu下编译的命令

gcc -S xxx.i -o xxx.s

图3.2.1编译结果

3.3 Hello的编译结果解析

 

此部分是重点,说明编译器是怎么处理C语言的各个数据类型以及各类操作的。应分3.3.1~ 3.3.x等按照类型和操作进行分析,只要hello.s中出现的属于大作业PPT中P4给出的参考C数据与操作,都应解析

3.3.1 全局变量 sleepsecs:

     汇编中

     c文件中

     全局变量在汇编文件中直接被标在.globl段,并在之后data声明其值,由于其为int类型所以给其分配的值为2

(师傅[编译器]拿出小刀,并推荐hello练辟邪剑法)

3.3.2 字符串

   字符串被放在rodata节中如下图所示

3.3.3 常数

    被表示为立即数

3.3.4 关系表达式

    利用比较操作,并根据比较得来的条件码完成对算式的求解

    1  argc != 3

     

    2  i < 10

3.3.5 if

    采用条件控制跳转的方式来实现

  

3.3.6 for

    采用条件控制跳转的方式来实现,并每次进行i++

   

3.3.7  ++

    采用add $1完成该运算

   

3.3.8 函数操作

利用call,完成对函数的调用,并在这之前完成对参数的处理

 

利用ret实现return

3.3.9 局部变量

    int I 被放在寄存器中

3.4 本章小结

本章主要阐述了编译器是如何处理C语言的各个数据类型以及各类操作的

(hello:我变强了,也变长了)

 

第4章 汇编

4.1 汇编的概念与作用

汇编器(as)将.s汇编程序翻译成机器语言指令,把这些指令打包成可重定位目标程序的格式,并将结果保存在.o目标文件中,.o文件是一个二进制文件,它包含程序的指令编码。这个过程称为汇编,亦即汇编的作用。

4.2 在Ubuntu下汇编的命令

  

图 4.2.1汇编结果

4.3 可重定位目标elf格式

分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。

ELF格式如下图所示:

图4.3.1 ELF文件格式

 

 

 

使用readelf -S得到各节信息如下图所示

图4.3.2 hello.o中的各段信息

重定位分析:
        用readelf -r获取hello.o的重定位信息其结果如下

1 .rela.text:

2. rela.en_frame:

图 4.3.3 4.3.4 hello.o中重定位信息

info 标注了重定位类型和重定位信息位置(符号表序号), offset标注了需要重定位信息的地方

4.4 Hello.o的结果解析

以下格式自行编排,编辑时删除

objdump -d -r hello.o  分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。

机器语言由 指令码+操作数 构成

图4.4.1比较结果

机器语言在很大程度上保留汇编文件的逻辑,但在以下方面仍有区别

1 分支转移 .o中已经为函数跳转预留了空间,而在.s中这些被保留为段名称

2 函数调用:在.s文件中,函数调用之后直接跟着函数名称,而在反汇编程序中,call的目标地址是当前下一条指令。因为hello.c中调用的函数都是共享库中的函数,需要通过动态链接器才能确定函数的运行时执行地址,在汇编的时候,将call指令后的相对地址设置为全0(目标地址正是下一条指令),然后在.rela.text节中为其添加重定位条目,等待静态链接的进一步确定。    

3全局变量访问:在.s文件中,访问rodata(printf中的字符串),使用段名称+%rip,在反汇编代码中0+%rip,因为rodata中数据地址也是在运行时确定,故访问也需要重定位。所以在汇编成为机器语言时,将操作数设置为全0并添加重定位条目。

4.5 本章小结

本章介绍了hello从hello.s到hello.o的汇编过程,介绍了elf文件的结构,比较了hello.o反汇编代码与hello的差异

(hello:我现在都不知道我要说啥[rodata还没被ld重定位])

 

 

第5章 链接

5.1 链接的概念与作用

链接(linking)是将各种代码和数据片段收集并组合成一个单一文件的过程,这个文件可被加载到内存并执行。链接可以执行于编译时(compile time),也就是在源代码被编译成机器代码时;也可以执行于加载时(load time),也就是在程序被加载器(loader)加载到内存并执行时;甚至于运行时(run time),也就是由应用程序来执行。链接是由叫做链接器(linker)的程序执行的,链接器使得分离(separate compilation)编译成为可能。

5.2 在Ubuntu下链接的命令

图5.2.1 链接命令

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

 

图5.3.1 可执行目标hello的各段信息

可以看出,比起前边hello.o中的elf文件这里的条目明显变多了,但格式仍未有太大变化

5.4 hello的虚拟地址空间

使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。  

打开后可以发现进程被装在以0x400000开始的位置如下图所示:

图5.4.1 hello开始位置

之后我们看其他段的位置:

图5.4.2 其他段信息

其中:

PHDR保存程序头表。

      INTERP指定在程序已经从可执行文件映射到内存之后,必须调用的解释器(如动态链接器)。

      LOAD表示一个需要从二进制文件映射到虚拟地址空间的段。其中保存了常量数据(如字符串)、程序的目标代码等。

      DYNAMIC保存了由动态链接器使用的信息。

      NOTE保存辅助信息。

      GNU_STACK:权限标志,标志栈是否是可执行的。

      GNU_RELRO:指定在重定位结束之后那些内存区域是需要设置只读

可知其分别在0x40000和0x60000存放内容,这与5.3中的偏移量有一点冲突.但实际上原位置还是有一份副本的.

 

5.5 链接的重定位过程分析

以下格式自行编排,编辑时删除

反编译hello

观察发下多了以下章节:

  .init    程序初始化执行的代码

.plt      静态连接的连接表            

.plt.got  保存函数引用的地址             

.fini    程序正常终止时执行的代码

其他的区别: 如下图所示

图5,5,1 反编译文件比较

其具体过程如下: 在使用ld命令链接的时候,指定了动态链接器为64的/lib64/ld-linux-x86-64.so.2,crt1.o、crti.o、crtn.o中主要定义了程序入口_start、初始化函数_init,_start程序调用hello.c中的main函数,libc.so是动态链接共享库,其中定义了hello.c中用到的printf、sleep、getchar、exit函数和_start中调用的__libc_csu_init,__libc_csu_fini,__libc_start_main。链接器将上述函数加入。之后,解析重定条目时发现对外部函数调用的类型为R_X86_64_PLT32的重定位,此时动态链接库中的函数已经加入到了PLT中,.text与.plt节相对距离已经确定,链接器计算相对距离,将对动态链接库中函数的调用值改为PLT中相应函数与下条指令的相对地址,指向对应函数。对于此类重定位链接器为其构造.plt与.got.plt。之后,链接器解析重定条目时发现两个类型为R_X86_64_PC32的对.rodata的重定位(printf中的两个字符串),.rodata与.text节之间的相对距离确定,于是链接器直接修改call之后的值为目标地址与下一条指令的地址之差,使其指向相应的字符串。

5.6 hello的执行流程

_dl_start        地址:0x7ff806de3ea0

_dl_init        地址:0x7f75c903e630

_start             地址:0x400500

_libc_start_main      地址:0x7fce59403ab0

_cxa_atexit            地址:0x7f38b81b9430

_libc_csu_init  地址:0x4005c0

_setjmp                    地址:0x7f38b81b4c10

_sigsetjmp              地址:0x7efd8eb79b70

_sigjmp_save         地址:0x7efd8eb79bd0

main               地址:0x400532    (puts 地址:0x4004b0  exit 地址:0x4004e0)

print                   地址:0x4004c0

sleep                   地址:0x4004f0 (以上两个在循环体中执行10次)

getchar             地址:0x4004d0

_dl_runtime_resolve_xsave 地址:0x7f5852241680

_dl_fixup          地址:0x7f5852239df0

_uflow             地址:0x7f593a9a10d0

exit               地址:0x7f889f672120

5.7 Hello的动态链接分析

dl_init之前

图5.7.1 dl_init之前

之后:

图 5.7.2 dl_init之后

说明调用动态连接器之后got被写入正确地址之后在访问就可以访问到动态连接的函数了

5.8 本章小结

本章主要介绍连接的过程以及重定位具体实现,比较了链接前后文件有哪些不同.

(hello:我终于神功大成,可以出去闯荡一番了)

(第51分)

 

 

第6章 hello进程管理

6.1 进程的概念与作用

进程是一个执行中的程序的实例,它好像是独占地使用处理器和内存。处理器就好像无间断地一条条执行我们程序中的命令。最后我们程序中的代码和数据好像是系统内存中唯一的对象。

这种假象便是进程这种抽象带来的。

(hello:我当年可是住着8GB的大房子。[hello妄想中]

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

shell是一个交互型的应用级程序,它代表用户运行其他程序

处理流程如下:

      1读取终端输入命令

   2解析命令,并直接执行内部命令

   3 分配新进程运行外部命令

      4 程序运行期间要接受信号,并产生相应的反应

   5 回收已结束的进程

6.3 Hello的fork进程创建过程

hello在运行过程中shell调用fork(),内核会创建一个新的进程,给与其新的PID,并为其分配与其父进程相同的上下文,然后在两个进程中fork()返回不同的值,至此为创建的hello的fork()进程创建成功。

6.4 Hello的execve过程

在6.3中的fork()得到新进程后shell使用execve(“hello”,argv,envp)调用驻留在内存中被称为启动加载器的操作系统代码来执行hello程序

以下为其执行步骤:1 删除已存在的用户区域

                               2 映射私有区域

为新程序的代码、数据、bss、和栈区创建新的区域结构,这些区域都是请求二进制零的

                  3 映射共享区域

                   4 设置PC

处理后的虚拟内存如下图所示

图 6.5.1 hello的虚拟内存分布

然后在内核的调度下,hello便开始被执行。

6.5 Hello的进程执行

在讲述进程进行过程中,我们不妨先来回忆下一些定义:

逻辑控制流:一系列程序计数器PC的值的序列叫做逻辑控制流,进程是轮流使用处理器的,在同一个处理器核心中,每个进程执行它的流的一部分后被抢占(暂时挂起),然后轮到其他进程

时间片:一个进程执行它的控制流的一部分的每一时间段叫做时间片。

用户模式和内核模式:处理器通常使用一个寄存器提供两种模式的区分,该寄存器描述了进程当前享有的特权,当没有设置模式位时,进程就处于用户模式中,用户模式的进程不允许执行特权指令,也不允许直接引用地址空间中内核区内的代码和数据;设置模式位时,进程处于内核模式,该进程可以执行指令集中的任何命令,并且可以访问系统中的任何内存位置。

上下文信息:上下文就是内核重新启动一个被抢占的进程所需要的状态,它由通用寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构等对象的值构成

hello进程中有如下片段

其中调用sleep时会引发上下文切换(其余的时候也会发生上下文切换,但是由于不可控就不在这里叙述了)

其过程上下文与状态转换如下图所示。

图6.5.1 上下文切换

于是hello执行过程为 判断参数是否合法->反复输出等待(上下文切换发生)->等待用户输入一个字符->结束->被shell回收

6.6 hello的异常与信号处理

 1 程序正常结束 shell收到SIGCHILD信号然后将其回收

图 6.6.1 测试1
   可以看出shell的确会回收正常结束的进程

 

 

 

 

 

2 不停乱按

图 6.6.1 测试2

不停乱按是没有效果的,shell对等待的过程的这种信号应该是默认屏蔽

3 回车 同 2

4 Ctrl+c

图 6.6.1 测试3

hello进程当场去世。对于这种信号,shell会结束前台进程。

(hello:为什么?为什么?!我明明经历了这么多,一个Ctrl+z就把沃….不要啊)

5 Ctrl+z +ps/jobs/pstree/fg/kill

图 6.6.1 测试4 图 6.6.1 测试5

图 6.6.1 测试6

 shell对于这种信号行为为挂起前台。

(hello:你们竟然把沃抓起来,还各种调戏,还不如一个CTRL+C杀了我算了)

6.7本章小结

本章介绍了进程的概念和作用,描述了shell如何在用户和系统内核之间建起一个交互的桥梁,以及hello是怎么被运行和回收的。

(hello的作为程序一生到这里就结束了,接下来,让我们跟随考古学家去挖一挖hello的坟吧[笑])

    

 

第7章 hello的存储管理

7.1 hello的存储器地址空间

以下格式自行编排,编辑时删除

逻辑地址:逻辑地址指由程序产生的与段相关的偏移地址部分。在有地址变换功能的计算机中,访内指令给出的地址 (操作数) 叫逻辑地址,也叫相对地址。要经过寻址方式的计算或变换才得到内存储器中的实际有效地址,即物理地址。hello中hello.o\hello.s中的左侧标记地址便是一种逻辑地址

线性地址:线性地址是逻辑地址到物理地址变换之间的中间层。在分段部件中逻辑地址是段中的偏移地址,其偏移量加上基地址就是线性地址。hello的反汇编文件中看到的地址(即逻辑地址)中的偏移量,加上对应段的基地址,便得到了hello中内容对应的线性地址。

虚拟地址:使用虚拟寻址时,CPU通过生成一个虚拟地址来访问主存,这个虚拟地址在被送至内存前先转换成适当的物理地址。它数值上等于先行地址

物理地址:计算机系统的主存被组织成一个M个连续字节大小的单元组成的数组,每字节都有一个独立的物理地址。hello中在内存的数据每字节对应一个地址

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

以下格式自行编排,编辑时删除

逻辑地址由段选择符和偏移量组成,线性地址为段首地址与逻辑地址中的偏移量组成。其中,段首地址存放在段描述符中。而段描述符存放在描述符表中,也就是GDT(全局描述符表)或LDT(局部描述符表)中。

段选择符如下:

图7.2.1 段描述符的格式

其过程如下图所示:

图7.2.2 变换过程

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

以下格式自行编排,编辑时删除

将hello的线性地址看成VPN 和VPO两部分,物理地址看成PPN和PPO

图7.3.1 两种地址之间关系

其中VPO和PPO是相同的可以直接转换.

而从VPN到PPN则先要通过VPN和页表基址寄存器(PTBR)访问相应常驻内存的页表得到物理页号PPN.

图7.3.2 访问模式

若访问失败,则引发缺页异常,之后会选择一个牺牲页,将其写回磁盘然后加载我们访问的目标进入磁盘.若无法加载则引发段错误.

图7.3.2缺页处理

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

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

在知道虚拟地址之后首先回去访问TLB,查询目标是否在TLB中,查询信息如下,以TLBI作为组索引,TLBT作为tag访问,若命中则直接获取PPN

在TLB不命中的情况下,对于36位VPN来说MMU将其分为4份9位偏移量,第一份和CR3控制寄存器合成一级页表中的目标PTE地址,这个PTE指向你需要的二级页表首地址加上第二份偏移量获得第二个页表中目标PTE的地址,以此类推,并在第四级页表得到PPN,之后将该信息写入TLB

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

如图所示,MMU将物理地址发给L1缓存,缓存从物理地址中取出缓存偏移CO、缓存组索引CI以及缓存标记CT。若缓存中CI所指示的组有标记与CT匹配的条目且有效位为1,则检测到一个命中,读出在偏移量CO处的数据字节,并把它返回给MMU,随后MMU将它传递给CPU。若不命中,则需到低一级Cache(若L3 cache中找不到则到主存)中取出相应的块将其放入当前cache中,重新执行对应指令,访问要找的数据。

图7.5.1 cache

7.6 hello进程fork时的内存映射

当fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给他一个唯一的pid。为了给这个新进程创建虚拟内存,他创建了当前进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有写时复制。当fork从新进程返回,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,也就为每个进程保持了私有地址空间的抽象概念。

7.7 hello进程execve时的内存映射

execve函数在当前进程中加载并运行包含在可执行目标文件hello中的程序,用hello程序有效地替代了当前程序。加载并运行hello需要:1)删除已存在的用户区域:删除当前进程虚拟地址的用户部分中的已存在的区域结构。2)映射私有区域:为新程序hello的代码、数据、bss 和栈区域创建新的区域结构。所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为hello文件中的.text和.data 区。bss 区域是请求二进制零的,映射到匿名文件,其大小包含在hello 中。栈和堆区域也是请求二进制零的,初始长度为零。3) 映射共享区域:如果hello程序与共享对象(或目标)链接,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内。4) 设置程序计数器(PC) :设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。

下一次调度这个进程时,它将从这个入口点开始执行。Linux将根据需要换入代码和数据页面。

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

7.9动态存储分配管理

   动态内存分配器维护着一个进程的虚拟内存区域,称为堆。系统之间的细节不同,但是不失通用性,假设堆是一个请求二进制零的区域,它紧接在未初始化的数据区域后开始,并向上生长(向更高的地址)。对于每个进程,内核维护着一个变量brk,它指向堆的顶部。
分配器将堆视为一组不同大小的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可以用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被释放。这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。

   为了分配这些块需要一些特殊结构,而有时为了满足一些特殊的分配方式还需要一些其他的数据结构,例如 隐式\显式空闲链表,头部脚部标签等等.而为了更好的满足吞吐量和内存利用率的需求还需要一些算法辅助,比如基于红黑树的适配

7.10本章小结

本章主要介绍了存储器地址空间、逻辑地址到线性地址的变换-段式管理机制、线性地址到物理地址的变换-页式管理机制、TLB与四级页表支持下的VA到PA的变换的具体过程、三级Cache支持下的物理内存访问、fork时的内存映射、execve时内存映射、缺页故障与缺页中断处理机制 和 动态存储分配管理机制。

(第7 2分)

 

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

设备的模型化:文件

设备管理:unix io接口

     

8.2 简述Unix IO接口及其函数

所有的IO设备都被模型化为文件,而所有的输入和输出都被当做对相应的文件的度和写来执行。这种将设备优雅地映射为文件的方式,允许Linux内核引出一个简单的、低级的应用接口,称为Unix I/O。

以下为被统一的操作:1打开文件

2进程的三个初始打开文件

3改变文件位置

4 读写文件

5 关闭文件

其函数分别为

1 打开文件

2 关闭文件

3 读和写文件

4 读取文件元数据

5 I/O重定向

8.3 printf的实现分析

printf代码如下:

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

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

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

8.4 getchar的实现分析

getchar源码如下:

图8.4.1 getchar源码

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

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

8.5本章小结

本章节描述了linux的I/O机制,分析了printf和getchar函数的实现。

即了解了hello是怎么向你打招呼的

(第81分)

结论

hello的一生是被安排的明明白白的一生

      1 通过文本编辑器写完代码,形成c文件

      2 被预处理器预处理为hello.i

      3 被编译器处理成汇编文件

      4 与其他可重定位文件和动态链接库链接成为可执行文件

      5 在shell中被分配一个进程

   6 fork() + execve()让内核给他分配好了空间,3级cache让其可以高速访问内存,优秀的动态存储分配体系让其运行无阻

   6.5 执行时函数调用机制,和中断机制让字符输在屏幕上

7 执行过程中被内核安排进程切换以及信号处理

      8 执行结束后被shell回收

hello虽然简单,但从代码到屏幕上的一句问候,确是我们一代代人的心血

 我们从未看轻过hello,从未忘记他,一代又一代的新鲜血液,不都是从helloworld学起的么

 

 

附件

hello.c                     实验源文件

hello_1_i.i                              预处理文件

hello_1_s.s                              汇编文件

hello_1_o.o                             由hello_1_s.s编译得到的可重定位二进制文件

hello_objdump_s.s         由hello_1_o.o反编译得到的汇编文件

hello_2_objdump.s                 由hello反编译得到的汇编文件

hello                                        由hello_1_o.o链接的到的可执行文件


参考文献

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

[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]  https://www.cnblogs.com/pianist/p/3315801.html

 

[8]  https://www.cnblogs.com/wangcp-2014/p/5146343.html

 

[9]  https://blog.csdn.net/tuxedolinux/article/details/80317419

 

[10]  https://www.cnblogs.com/zengkefu/p/5452792.html

 

[11]  https://blog.csdn.net/gdj0001/article/details/80135196\

 

[12]  https://blog.csdn.net/weixin_41506416/article/details/85328814

你可能感兴趣的:(程序人生-hello-csapp大作业)