2018-CSAPP大作业

摘 要
本文介绍了程序hello.c在linux中,通过预处理形成.i,通过编译形成.s,通过汇编形成不可见的.o,通过链接之后形成最终的可执行文件hello。此时的hello可以通过直接执行文件。之后shell即可为其fork的过程。在壳中,shell为hello生成子进程,fork,execve等过程,最后生成虚拟地址和物理地址,最后被杀死之后内存被释放的过程

关键词:P2P,O2O,CSAPP,程序结构与执行;

(摘要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简介
在VIM中输入每个程序员都熟悉的——hello的代码,保存后更改后缀为hello.c
在linux中,打开终端,通过预处理形成.i,通过编译形成.s,通过汇编形成不可见的.o,通过链接之后形成最终的可执行文件hello。此时的hello可以通过直接执行文件。之后shell即可为其fork,这就是P2P的过程。
最后形成的可执行文件,还可以通过反编译,形成.o和.s文件。通过这个文件,我们依旧可以窥视hello的风采。

1.2 环境与工具
硬件环境:Intel® Core™ i7-7500 CPU @2.70GHz 2.90GHz
软件环境:VMware Ubuntu18.04.1 LTS
开发与调试工具:VIM,Code Blocks,EDB,as,objdump,

1.3 中间结果
文件名称 文件作用
hello.c 编写的hello文件
hello.i hello.c经过预处理之后的文件
hello.s hello.c编译之后的文件
hello.o hello.c汇编之后的文件
hello 最后生成的可执行文件
ams.txt hello反编译生成的文件

1.4 本章小结
本章介绍了hello在linux系统下生成可执行文件的过程,介绍了p2p和p0p的过程
(第1章0.5分)

第2章 预处理
2.1 预处理的概念与作用
预处理是指在cpp中,根据宏定义和条件编译,修改原始的C程序,将引用的库和程序合并成一个完整的文本文件。即:hello.c变成hello.i的过程
功能有如下三条:
1、将原文件中define定义替换为定义之后的结果,用实际值替换define定义的字符串
2、将原文件中用#include定义的库加入到文件中,如系统头文件stdio。h等
3、根据if后面的条件决定需要编译的代码

2.2在Ubuntu下预处理的命令

cpp hello.c > hello.i

2.3 Hello的预处理结果解析

将hello.i在codeblocks中打开之后,发现最后的内容不变,而前面则添加了3099行代码。这些代码就是cpp在头文件#include <stdio.h>中生成的。前面包含了大量的#define以及#ifdef和#typedef内容,而最后的程序中是不包含define的内容的。

2.4 本章小结

本章介绍了与处理的方法、内容和作用。把hello.c中的头文件,正是转变成了代码的内容,就好比你的“初恋”hello第一次的化妆一样,然而这并改变不了hello很快就要被抛弃的命运……
(第2章0.5分)

第3章 编译
3.1 编译的概念与作用

编译是指文本文件hello.i 通过编译器后,转变成一个由汇编语言组成的hello.s。编译的流程主要分为以下三步:
1、处理器:作用是通过代入预定义等程序段将源程序补充完整。
2、前端:前端主要负责解析输入的源代码,有语法分析器和语义分析器写通工作。比如a = b + c ;前端语法分析器将他们组装成b+c,在组装成a = b+c。前端还负责语义的检查最终形成一个抽象的语法树。
3、后端:编译器的后端主要负责分析,优化中间代码以及生成机器代码,也就是我们最终看到的hello.s

3.2 在Ubuntu下编译的命令

unix>gcc -S hello.i -o hello.s

3.3 Hello的编译结果解析
3.3.1 数据
数据的类型有常量、变量、表达式、类型、宏复制、都好操作符、赋初值/不赋初值。在hello.s:整形,字符串和数组
首先出现的,是long型常量。如图所示,sleepsecs的值被设置为2。返回hello.c中查看源代码,int sleepsecs = 2.5;是一个全局变量,而因为是int型,所以2.5的小数位自动被忽略了,而long和int都是4位,故最后sleepsecs被设置成long型

然后,是字符串型数据,如图所示,内容为”hello 1173710216万佳元”的ASCⅡ码形,如下所示:

由图片可以知道,每个汉字对应3个字节。
然后,在hello.s中出现的是数组变量,即:argv作为char[]指针的数组,在hello.s中,反别调用了argv[0]和argv[1]。至于内容为什么为argv[0]和argv[1],将在下一部分中进行解析。

3.3.2赋值

在hello.c中,赋值语句为int i;i =0;这句。观察hello.s

movl $0,%eax 这句话的作用,是将%eax中储存的值设置为0,其中l是指4个字节
3.3.3 类型转换

在程序中,sleepsecs为long型,而赋值号右侧的值为2.5,为浮点型,因此,发生了隐式转换。隐式转换会将浮点数小数点之后的位数消除,也就是只剩2。观察hello.s上图内容,最后sleepsecs被赋值为2
3.3.4 Sizeof

在这里,出现了sleepsecs的size为4。在linux里,long和int同样为4个字节,float4字节,double8字节,因此,最后sleepsecs最后被申请为long常量。

3.3.5算数操作符

在L4中,addq $16, %rax则指将rax的值增加16,addl $1, -4(%rbx)则是指将rbp-4指针内存储的值增加1,相当于i++

3.3.6 关系操作
在hello.s中,一共涉及了两个关系操作。观察hello.c,其中一共进行了两次比较:
1、比较argv和3的大小
2、比较i和10的大小
观察hello.s,可以得到如下汇编代码:

常用的关系操作有以下几种:

3.3.7控制转移
在3.3.6中,我们使用的关系操作,可用在控制转移当中。比如下图:

先比较9和-4(%rbp),也就是i的大小,如果9 >=i,则进入L4的循环中。同样,L4中也有类似的代码,保证了循环次数的时间。
除此之外,在比较argv和3的大小中,也使用了同样的je。
3.3.8函数操作
函数是一种封装代码的过程,然后用一组指定的参数和可选的返回值实现某种功能。在hello.s中,包含了以下几种函数
1、main函数
main函数在被系统启动函数调用后,call将下一条指令的地址压栈,然后转到main函数中。然后外部调用过程向main函数传递argc和argv,最后分配和释放内存
2、print函数
print函数的传入值为字符串数组的首地址,第一次将%rdi设置成”Usage:Hello
1173710216 万佳元”的首地址,第二次设置成”Hello %s%s”的首地址
3、exit函数
exit的传入值为1,然后call exit@PLT,达到退出程序的目的
4、sleep函数
函数的传入值为sleepsecs,目的是让系统休眠sleepsecs毫秒,然后call sleep@PLT
5、getchar函数
call getchar@PLT
3.4 本章小结
本章讲述了hello.i变成hello.s,形成汇编代码的过程,以及汇编语言的原理以及c语言和汇编语言的关系。
我们的hello.c已经渐渐没有了最初的样子,变成了计算机更喜欢的样子,然而接下来才是最绝望的变化……
(第3章2分)

第4章 汇编
4.1 汇编的概念与作用
汇编指hello.s文件,在汇编器的翻译下,变成二进制文件hello.o,将汇编指令变成二进制指令,这个过程就称之为汇编。
4.2 在Ubuntu下汇编的命令
as hello.s -o hello.o

4.3 可重定位目标elf格式
readelf -a hello.o > helloo.elf

4.4 Hello.o的结果解析
objdump -d -r hello.o 获得反汇编代码

经观察,反汇编获得的hello.objdump和hello.s有着极高的相似度,主要区别有以下三点:
1、反汇编跳转的指令不再是L1L2L3,因为反汇编之后函数有了明确的地址,所有跳转的指令指向了确定的地址。
2、再返回编程序中,call的地址是当前下一条指令。
3、全局变量的设定,在.s文件中,使用了段名称,而在反汇编代码中,也需要重新定位
4.5 本章小结

本章介绍了hello.s变成hello.o的汇编过程,了解汇编语言和机器语言之间的关联。
终于hello变成了我们看不到的样子,接下来hello又会变成什么样呢
(第4章1分)

第5章 链接
5.1 链接的概念与作用
链接是指将多个或者单个.o文件通过链接器进行拼接,最后形成一个可执行文件的过程。链接可以执行于多个阶段,甚至可以在运行的时候进行链接。连接器使得分离编译成为了可能。
5.2 在Ubuntu下链接的命令

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

5.4 hello的虚拟地址空间
使用edb加载hello,查看hello的虚拟地址:
表头的VirtAddr时虚拟地址在0x400000中,到0x400fff结束,程序在0x400000中被载入。查看hello.elf的表头,Offset行提供了各个函数的偏移量。在程序中一共分成了八个段。
同理,在VirtAddr的0x600000中,同样储存了相同的程序。直到0x602000程序结束。

5.5 链接的重定位过程分析

objdump -d -r hello

获得反汇编代码后,观察获得的hello.odjdump,发现其中多处很多函数。这些函数就是crt1.o,crti.o,crtn.o提供的。这些函数提供了main的程序入口,使hello函数可以被执行,同时定义了exit出口,使hello可以结束。同时,在libc.so中,包括了hello函数中的printf,sleep等函数的原型,使这些函数在最终链接形成的文件中可以被调用。最后,由连接器将这些函数统一形成可执行文件hello。
5.6 hello的执行流程
同5.5,使用edb加载hello,观察函数执行流程,发现按照执行顺序,系统一共调用了以下函数:
0000000000400488 <_init>:
00000000004004a0 <.plt>:
00000000004004b0 puts@plt:
00000000004004c0 printf@plt:
00000000004004d0 getchar@plt:
00000000004004e0 exit@plt:
00000000004004f0 sleep@plt:
0000000000400500 <_start>:
0000000000400530 <_dl_relocate_static_pie>
0000000000400532 :
00000000004005c0 <__libc_csu_init>:
0000000000400630 <__libc_csu_fini>:
0000000000400634 <_fini>:

5.7 Hello的动态链接分析

在dl_init执行之前,调用的目标地址都指向代码逻辑,GOT中存放的是下一条指令地址;
在dl_init执行之后,GOT[1]指向重定位表,根据重定位表,来重新定位调用的函数地址,在之后的函数调用中,首先跳转GOT地址下一条指令,然后将函数压栈,跳转到PLT,然后访问动态连接器,再传递给目标函数。
5.8 本章小结

本章介绍了链接的作用和原理,介绍了虚拟地址空间和物理空间的转化,重定位和的连接过程。
(第5章1分)

第6章 hello进程管理
6.1 进程的概念与作用

进程是计算机中关于数据集的程序的运行活动,是系统资源分配和调度的基本单元,是操作系统结构的基础。在早期的过程设计计算机结构中,过程是程序的基本执行实体。在现代面向线程的计算机体系结构中,进程是线程的容器。程序是指令、数据及其组织的描述。进程是程序的实体。

6.2 简述壳Shell-bash的作用与处理流程
我们的普通用户不能直接使用核心,而是通过shell应用程序。这是外壳,与Windows不同,图形界面是外壳。shell的简单定义是命令行解释器,它的功能是将用户的命令转换为核心处理,并将核心处理的结果转换为用户。您可以看到,shell主要解析我们对Linux内核的指令。反馈结果通过内核运行,并通过shell解析给用户。

6.3 Hello的fork进程创建过程
在使用命令./hello的时候,shell解析hello,然后中断程序调用fork创建一个新的运行的子进程。新创建的子进程和父进程是由同样的函数形成的,而且子进程和父进程拥有同样的虚拟地址空间。
子进程与父进程有着不同的pid,两者可以并发的运行,同时,当子进程和父进程同时结束的时候,waitpid函数结束。
6.4 Hello的execve过程
execve是指execve(执行文件)在父进程中fork一个子进程,在子进程中调用exec函数启动新的程序。exec函数一共有六个,其中execve为内核级系统调用,其他(execl,execle,execlp,execv,execvp)都是调用execve的库函数。
execve()用来执行参数filename字符串所代表的文件路径,第二个参数是利用指针数组来传递给执行文件,并且需要以空指针(NULL)结束,最后一个参数则为传递给执行文件的新环境变量数组。
6.5 Hello的进程执行
在hello进程执行的时候,正常执行hello的结果如图:

或者输入超过三个字符串:

在hello调用sleep前,如果hello未被抢占则正常执行,否则进行上下文切换。再调用sleep之后,hello进程被休眠,然后开始计时,2描绘,将hello重新加入队列,然后继续执行程序。
6.6 hello的异常与信号处理
当执行进程发现上面描述的事件之一无法继续执行时,它通过调用阻塞原语block()来阻塞自己。可以看出,过程阻塞是过程本身的一种主动行为。进入block进程后,由于该进程此时仍在执行,应立即停止执行,将process control block中的当前状态从执行改为block,并将PCB插入block队列中。如果在系统中设置了多个被不同事件阻塞的阻塞队列,则应该将进程插入具有相同事件的阻塞(等待)队列中。最后,调度器重新调度,将处理器分配给另一个就绪进程,并进行切换,即保留阻塞进程(在PCB中)的处理器状态,并根据新进程PCB中的处理器状态设置CPU环境。

当一个阻塞的进程期望一个事件时,例如I/O完成或者期望的数据已经到达,相关的进程(例如,使用并释放I/O设备的进程)将调用来唤醒等待事件的进程。唤醒原语执行的过程是首先从等待事件的阻塞队列中删除阻塞进程,将其PCB的当前状态从阻塞更改为就绪,然后将PCB插入就绪队列。

在执行过程中按下ctrl+c之后,shell收到信号,然后将hello终止,并回收进程。
在过程中连续按回车或是乱按,并不影响hello的运行。在运行结束后,过程中乱按的结果,被顺延输入,如果是命令的则执行,如果不是命令的的返回相应结果
6.7本章小结
本章中介绍了进程,shell,fork三者如何创建一个新的进程,最后hello的执行过程和过程中出现的异常的处理。
(第6章1分)

第7章 hello的存储管理
7.1 hello的存储器地址空间
计算机系统的主存被组织成一个一个有M个连续字节大小的单元组成的数组。每一个字节都有一个唯一的物理地址。第一个字节的地址为0,下一个为1,在下一个为2,这招方式就叫物理寻址。
虚拟寻址,CPU通过生成一个虚拟地址,来访问主存,这个虚拟地址在被送到内存之前先转化成物理地址。
如果地址空间的证书是连续的,拿着一个地址空间就叫做线性地址空间。在一个带虚拟内存的系统中,CPU从一个有N = 2^n个地址的地址空间中声称虚拟地址,这个地址空间成为虚拟地址空间。
7.2 Intel逻辑地址到线性地址的变换-段式管理

7.3 Hello的线性地址到物理地址的变换-页式管理
形式上来说,地址翻译是一个N元素的虚拟地址空间中的元素和一个M元素的物理空间地址的映射
CPU中的一个控制寄存器,页表地址寄存器指向当前页表,n位的虚拟地址包含两个部分,一个p位的虚拟页面便宜和一个n-p位的虚拟页号。当页面命中时,CPU硬件执行的步骤为:
第一步:布里奇生成一个虚拟地址,并把它传递给MMU。
第二步:MMU生成PTE地址,并从高速缓存/主存请求得到它
第三步:高速缓存/主存向MMU返回PTE
第四步:MMU搞糟物理地址,并将它传递给高速缓存/主存
第五步:高速缓存/主存返回所请求的数据字给处理器
7.4 TLB与四级页表支持下的VA到PA的变换
首先CPU产生虚拟地址,然如果VPN中的TLBT和TLBI匹配,则命中,将缓存的PPN返回MMU
如果不命中,则MMU从贮存中取出相应的PTE。
比如,虚拟地址VA为0x03d4,则VA为00001111010100,其中TLB索引为0x3,TLB标记为0x3,此时匹配,然后将来自PTE的PPN和来自虚拟地址的VPO连接起来,形成了物理地址PA。
接下来,MMU发送物理地址给缓存,缓存从物理地址中抽取出缓存便宜,缓存组索引和缓存标记。如果标记和CT相匹配,则独处在偏移量CO出的数据字节,并将他返回给MMU,随后MMU将他返回传递给CPU。
7.5 三级Cache支持下的物理内存访问
逻辑地址空间是指一个源程序在编译或链接装配后指令和数据所用的所有相对地址的空间。它是作业进入内存,其程序、数据在内存中定位的参数。
段式管理是指把一个程序分成若干段进行存储,每个段都是一个逻辑实体。段式存储都是通过表进行的。
7.6 hello进程fork时的内存映射
在hello中,当fork函数被当前进程调用时,内核微信进程创建各种数据结构,并分配给他一个唯一的PID。为了给这个新进程船舰虚拟内存,他创建了当前进程的mm_struct区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中每个区域结构都标记为私有的写时复制。
当fork在新进程中返回时,新进程现在的虚拟内存调用fork时存在的虚拟内存相同。当这两个进程中的任意一个后来进行写操作时,写时复制机制就会创建新页面,因此,也就为每个进程保持了私有地址空间的抽象概念,
7.7 hello进程execve时的内存映射
虚拟内存和内存映射在将程序加载到内存的过程中,也扮演着关键的角色。在hello中,执行了如下的execve调用:
execve(helllo.out,NULL,NULL);
当加载hello.out时,进行了以下几个步骤
1、删除已存在的用户区域
2、映射私有区域
3、映射共享区域
4、设置程序计数器
7.8 缺页故障与缺页中断处理
当引用一个地址的时候,如果其VPN对应的PPN不存在,则会发生缺页故障。放发生缺页故障的时候,程序终止
处理缺页中断则需要重新更新页表,CPU重新启动,分配新的虚拟地址,然后新的虚拟地址发动到MMU,此时才能正常翻译VA
7.9动态存储分配管理
malloc函数返回一个指针,指向一个大小至少为size字节的内存块,这个块会为可能包含在这个快内的任何数据对象类型做对齐。在32位中,malloc返回的块的地址总是8的倍数,在64位中,总为16的倍数。
动态内存分配器维护着要给进程对额虚拟内存区域,称为堆。分配器将堆视为一种不同大小的块的集合。已分配的块显式的保留为供应应用程序使用。分配器有两种基本风格,两种风格都要求应用显式的分配块。
1、显示分配器:要求应用显式的释放任何已分配的块。
2、隐式分配器:要求分配器检测一个已分配的块何时不再被程序所使用,那么就释放这个块。
7.10本章小结
本章介绍了hello的虚拟内存,物理地址和线性地址的转化,内存的映射和动态内存分配,缺页中断与缺页中断处理。
(第7章 2分)

第8章 hello的IO管理
8.1 Linux的IO设备管理方法
所有的I/O设备都被模型化为文件,而所有的输入和输出都被当作相应文件的读和写来完成,这种将设备优雅的映射为文件的方式,允许Linux内核引出一个简单的,低级的应用接口,成为Unix I/O,这使得所有的输入和输出都能以一种统一且一致的方式来执行
8.2 简述Unix IO接口及其函数
在Unix I/O中,所有的输入和输出都已一种统一且一致的方式来执行:
1、打开文件。一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个I/O设备,内核返回一个小的非负整数,叫做描述符。它在后续对此文件的所有做做中标识这个文件。内核记录有关这个打开文件的所有信息。应用程序只需记住这个描述符。
2、Linux shell创建的每个进程开始时都有三个打开的文件:标准输入,标准输出,和标准错误。头文件<unistd.h>中定义了常量STDIN_FILENO,STDOUT_FILENO和STDERR_FILENO,它们可以用来替代显示的描述符值
3、改变当前文件位置。对于每个打开的文件,内核保持着一个文件的位置k,初始为0,这个文件的位置是从文件开头起始的字符偏移量。应用程序能够通过执行seek操作,显示地设置文件的当前位置为k。
4、读写文件。一个读操作就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增加到k+n。当k>=m时执行读操作会出发一个成为end-of-file的条件,应用程序能检测到这个条件
5、关闭文件。当应用完成了对文件的访问后,他就通知内核关闭这个文件。作为夏国英,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中,无论一个进程因为何种原因终止时,内核都会关闭所有打开的文件并释放它们的内存资源。
8.3 printf的实现分析

从首先,printf和vsprintf的代码如下:

在printf中,首先定义一个大小256的字符串数组,然后通过获得一个参数,进入vsprintf函数
在vsprintf中,调用系统函数write,然后将buf输出,最后,将字符串输出。
8.4 getchar的实现分析
getchar原理是调用了系统函数read,通过read读取键盘缓冲区的内容,然后通过如下代码
getchar有一个int型的返回值.当程序调用getchar时.程序就等着用户按键.用户输入的字符被存放在键盘缓冲区中.直到用户按回车为止(回车字符也放在缓冲区中).当用户键入回车之后,getchar才开始从stdio流中每次读入一个字符.getchar函数的返回值是用户输入的第一个字符的ASCII码,如出错返回-1,且将用户输入的字符回显到屏幕.如用户在按回车之前输入了不止一个字符,其他字符会保留在键盘缓存区中,等待后续getchar调用读取.也就是说,后续的getchar调用不会等待用户按键,而直接读取缓冲区中的字符,直到缓冲区中的字符读完为后,才等待用户按键.
8.5本章小结
本章介绍了Linux的IO的原理方法,以及IO的几个函数,打开文件和关闭文件的方法,以及分析了使用IO的两个函数,printf和getchar。
(第8章1分)
结论
一个hello程序,从编写到结束进程,一共分为以下几个步骤:
1、在VIM中编写,生成hello.c
2、在cpp中,通过预处理,hello.c变为hello.i
3、在gcc中,hello.i通过汇编,生成了hello.s
4、在as中,hello.s变为了二进制文件hello.o
5、通过链接,hello.o和其头文件链接,生成了可执行文件hello
6、通过shell,hello通过输入的指令,开始运行
7、shell调用fork,为hello创建子进程
8、MMU将hello中的虚拟地址转化成物理地址
9、最后,shell回收子进程,释放hello所占用的内存
(结论0分,缺少 -1分,根据内容酌情加分)

附件
列出所有的中间产物的文件名,并予以说明起作用。
(附件0分,缺失 -1分)

参考文献
为完成本次大作业你翻阅的书籍与网站等
[1] 兰德尔 E.布莱恩特. 深入理解计算机系统. 龚奕利 译.

[2] 库函数getchar()详解https://blog.csdn.net/hulifangjiayou/article/details/40480467
[3] printf 函数实现的深入剖析https://www.cnblogs.com/pianist/p/3315801.html
[4] Linux进程虚拟地址空间https://www.cnblogs.com/xelatex/p/3491305.html
[5] 常用汇编算术运算指https://blog.csdn.net/qq_35524916/article/details/61421411
[6] linux下edb调试器https://blog.csdn.net/zcc1414/article/details/24439485

(参考文献0分,确实 -1分)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值