计算机系统

大作业

题 目 程序人生-Hello’s P2P
专 业 计算机
学   号 119020XXX
班   级 1936XXX
学 生 XXX   
指 导 教 师 刘宏伟

计算机科学与技术学院
2021年6月
摘 要
本文以hello.c程序为例,阐述程序在Linux系统中的整个生命周期和操作系统在此过程中进行的各种管理,具体阐述了hello程序经过预处理、编译、汇编、链接生成可执行文件、产生进程直至进程终止回收的过程、操作系统的进程管理、存储管理和I/O管理。

关键词:Linux;计算机系统、程序、进程;

目 录

第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的P2P(From Program to Process)过程:hello程序的生命周期从高级C语言程序开始,为了在系统上运行hello.c 文件,首先要完成从源文件到可执行目标文件的过程,这包括四个阶段:预处理、编译、汇编、链接。在得到可执行目标文件hello之后,在shell中运行这个目标程序,shell将为其分配进程空间,加载目标程序并运行。这个过程就是P2P(From Program to Process)的过程。
Hello的020(From Zero to Zero)过程:程序运行前,shell调用execve函数将hello程序加载到相应的上下文中,将程序内容载入物理内存,并从main函数开始执行目标代码;程序结束后,父进程回收终止进程,内核清除相关痕迹。hello程序从不存在(0)到产生再到被回收(0)的过程,即是020的过程。
1.2 环境与工具
硬件环境:X64 CPU;2GHz;2G RAM;256GHD Disk 以上
软件环境:Windows7 64位以上;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64位/优麒麟 64位;
开发工具:Visual Studio 2010 64位以上;GDB/OBJDUMP;DDD/EDB;GCC;vim ; readelf;HexEdit
1.3 中间结果
hello.c : c语言编写的源文件,以ASCII码编码
hello.i : hello.c经预处理得到的文本文件
hello.s: hello.i经编译得到的文本文件,包含汇编语言程序
hello.o : hello.s 经汇编得到的可重定位目标文件
hello : 经链接得到的可执行目标文件
hello.out : hello 的反汇编文件

1.4 本章小结
本章简要介绍了hello文件的P2P和020过程,介绍了将要使用的环境与工具,列出了过程中产生的中间结果。

(第1章0.5分)

第2章 预处理
2.1 预处理的概念与作用
预处理器(cpp)根据以字符#开头的命令,修改原始的C程序。比如hello.c中第1行的#include <stdio.h>命令告诉预处理器读取头文件stdio.h的内容,并把它直接插入程序文本中。结果就得到了另一个C程序,通常是以.i作为文件拓展名。
2.2在Ubuntu下预处理的命令
命令:gcc -E hello.c -o hello.i

图2.1 对hello.c的预处理过程
2.3 Hello的预处理结果解析

图2.2 hello.c的预处理结果–hello.i 文件

打开经预处理得到的hello.i文件,可以看到文件的内容增多,所做的处理包括
1)处理宏定义指令,如对#define指令进行替换,对#undef取消对某个宏的定义
2)处理条件编译指令,如#ifdef、#ifndef、#else、#elif、#endif等,过滤不必要的代码
3)处理头文件包含指令,即#include,把头文件中的定义添加到文件中
4)处理特殊符号,对LINE、FILE等符号用何时的值进行替换
可以看到main函数在文件的最底部。

图2.3 最底部的main函数

2.4 本章小结
本章简要介绍了hello.c的预处理过程,分析了hello.i 文件,阐述了预处理过程中所进行的操作。

(第2章0.5分)

第3章 编译
3.1 编译的概念与作用
编译器(ccl)将文本文件hello.i翻译成文本文件hello.s,它包含一个汇编语言程序,以文本格式描述低级机器语言指令。汇编语言为不同高级语言的不同编译器提供了通用的输出语言。
3.2 在Ubuntu下编译的命令
命令:gcc -S hello.i -o hello.s

图3.1 hello.i 的编译过程
3.3 Hello的编译结果解析
3.3.1 汇编指令

图3.2 汇编指令

.file “源文件名”,声明源文件
.text 定义一个代码段,处理器开始执行代码的地方,代表后面是代码
.global 定义声明全局变量
.data 定义初始化数据段
.align 对齐方式
.type 指明一个符号的类型(对象类型/函数类型)
(type sleepsecs, @object 声明sleepsecs是对象类型
type main , @function 声明main是函数类型 )
.size 大小
.long 定义一个长整形,并为它分配空间,占4字节
.section .rodata 定义只读数据段
.string 定义一个字符串,并为它分配空间

综上,hello.s文件的第一部分,声明文件名是“hello.c”;数据以4字节方式对齐;声明了一个全局变量sleepsecs,是对象类型的,大小是4字节,类型是Long,数值是2;在只读数据段存放了两个字符串;声明了一个全局变量main,它是函数类型的。
3.3.2 数据
1.全局变量
1)程序中定义了一个全局变量sleepsecs,在hello.c文件中,它在main函数前被定义(如图3.3)为int类型,值是2.5

图3.3 sleepsecs在hello.c中的定义
在hello.s中,它在程序开头,通过.global关键字被定义,并在初始化数据段指明了它的类型、大小和值:对象类型,4字节大小,Long型,值为2

图3.4 speepsecs在hello.s中的定义
这里需要注意的是,由于整型不能带有小数部分,可以看到,实际上2.5被隐含的强制类型转换取整数值2 。
2)hello.c中还定义了一个全局变量main,它是函数类型的,被声明在代码段

图3.5 hello.s中声明的全局变量main
2.局部变量
程序中定义了一个局部变量i,初始化后被存储在栈中,地址为-4(%rbp)

图3.6 存储在栈中的变量i被赋值为0

3.字符串
在hello.c中一共有两个字符串,是作为printf的参数传入的,存储在只读数据段
.rodata中

图3.7 hello.c中的字符串1

图3. 8 hello.c中的字符串2

图3.9 存储在只读数据段的字符串

图3.10 作为printf参数传入的字符串1

图3.11 作为printf参数传入的字符串2
4.函数参数
main函数有两个参数,整型变量argc和指针数组argv,都存储在栈中,其中argv包含指向上述两个字符串的指针。

图3.12 main函数的两个参数

图3.13 存储在栈中的argc

图3.14 存储在栈中的两个字符指针
5.立即数
在汇编代码中,许多立即数出现在指令中

图3.15 出现在汇编代码中的立即数(1)

图3.16 出现在汇编代码中的立即数(2)
3.3.3 赋值
hello.c中有两个赋值操作,分别是给全局变量sleepsecs赋值为2.5,和给局部变量i赋初值为0。

图3.17 给全局变量sleepsecs赋值

图3.18 给局部变量i赋初值0
在汇编文件hello.s中,给sleepsecs赋值体现在初始化数据段.data中,给i赋值则通过汇编指令movl完成,这里的后缀l表示传送四个字节。

图3.19 给sleepsecs赋值

图3.20 给局部变量i赋值
MOV是一类数据传送指令,按照传送的数据大小和进行的拓展有不同的后缀,具体如下表

图3.21 简单的数据传送指令

图3.22 零拓展数据传送指令

图3.23 符号拓展数据传送指令
3.3.4 类型转换
程序中包含一个隐式类型转换,即给全局变量sleepsecs的赋值,由于sleepsecs被声明的类型是int型,而hello.c程序试图将一个浮点值2.5赋值给它,所以程序进行了隐式类型转换,将2.5的整数值2赋给了sleepsecs,在hello.s汇编文件中可以看出。

图3.24 不正确的赋值

图3.25 汇编代码给sleepsecs赋值为2

3.3.5 算数操作
hello.c中只包含一个局部变量i的自加一的算数操作,由汇编指令add实现。

图3.26 add指令实现自加一
其他的整数算数操作汇编指令如下表:

图3.27 整数算数操作

3.3.6 关系操作
程序中包含两个关系操作:argc!=3 和i<10,均由cmpl指令实现,这两个指令同时设置条件码,用于条件跳转。

图3.28 两个关系操作

图3.29 两个比较指令
3.3.7 数组/指针/结构操作
main函数的参数argv是一个指针数组,argv[0]指向可执行文件的文件名,argv[1]、argv[2]分别指向开始的两个字符串,使argv[1]、argv[2]指向字符串的操作是通过leaq指令将存放字符串的地址放置到指针所指的寄存器来实现的。

图3.30 使指针指向寄存器

图3.31 将存放字符串的地址存到寄存器里
3.3.8 控制转移
函数中包含两个控制转移:
1)if(argc!=3)
2)for(i=0;i<10;i++)
这两个控制转移根据关系操作中设置的条件码进行条件跳转:
1)如果argc=3,跳过if括号里的操作(跳转到L2)。
2)如果i<=9,进入for循环(跳转到L4)
在汇编代码中的实现如下:

图3.32跳转到L2

图3.33跳转到L4
3.3.9 函数操作
程序中涉及多个函数的操作:
1)main函数:参数argc、argv
2)printf函数
3)exit函数:参数1
4)sleep函数:参数sleepsecs
5)getchar函数
以hello.c中调用的第一个printf为例:
首先第一个字符串被存储在%rdi寄存器里,作为第一个参数,之后通过call指令调用puts函数,第一个参数被传递进来,如果要使用返回值,返回值应当被存储在%rax寄存器中。
需要强调的是,在函数调用时,程序会在栈上给被调用者分配空间,保存一些寄存器的值,当函数调用结束时,栈又会恢复到调用之前的状态,并恢复需要恢复的寄存器值。
3.4 本章小结
本章详细的叙述了程序编译过程中编译器所进行的各种操作,并将c语言的数据与操作对应到汇编代码。
(第3章2分)

第4章 汇编
4.1 汇编的概念与作用
汇编器(as)将hello.s翻译成机器语言指令,把这些指令打包成可重定位目标文件的格式,并将结果保存在hello.o中。hello.o是一个二进制文件,包含main函数的指令编码。
4.2 在Ubuntu下汇编的命令
命令:gcc -c hello.s -o hello.o

4.3 可重定位目标elf格式
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
4.3.0典型的ELF可重定位目标文件的格式(如图4.1):

图4.1 典型的ELF格式
ELF头:以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序,ELF头剩下的部分描述包含帮助链接器语法分析和解释目标文件的信息:ELF头的大小、目标文件的类型、机器类型、节头目表的文件偏移,节头目表中条目的大小和数量。
.text :已编译程序的机器代码
.rodata:只读数据
.data:已初始化的全局和静态C变量
.bss:未初始化的全局和静态C变量
.symtab:一个符号表,存放在程序中定义和引用的函数和全局变量的信息
.rel.text:一个.taxt节中位置的列表,当链接器把这个目标文件和其他的文件组合时,需要修改这些位置。
.rel.data:被模块引用或定义的所有全局变量的重定位信息
.debug:一个调试符号表,其条目是程序中定义的局部变量和类型定义,程序中引用的全局变量,以及原始的C文件
.line:原始C源程序中的行号和.text节中机器指令之间的映射
.strtab:一个字符串表,其内容包括.symtab 和.debug节中的符号表,以及节头部中的节名字
节头部表:描述目标文件的节
4.3.1 ELF头(如图4.2)
命令:readelf -h hello.o
由图4.2可以看出,这是一个64位ELF文件;数据以2的补码、用小端法表示;是可重定位文件;机器类型X86-64;入口点地址是0;程序头开始地址是0;节头部开始地址是1152字节;ELF头的大小是64字节;节头部的大小是64字节;
共有13个节头,节头部的字符串表索引是12。

图4.2 用readelf查看的ELF头信息

4.3.2 节头部表(图4.3)
命令:readelf -S hello.o
可重定位目标文件的每个节都从0开始,当在文件头中得到节头部的信息后,将使用节头部表中的偏移量和大小,确定各节在文件中的起始位置及大小。
图4.3显式了各个节的信息,包括名字、类型、地址、偏移量、大小、对齐方式、读写权限等。可以看到,代码段不可写,可执行;.data和.bss段可读可写,不可执行;只读数据段不可写、不可执行。

图4.3 用readelf查看的节头部表信息
4.3.3 符号表(如图4.4)
命令:readelf -s hello.o
Name是字符串表中的字节偏移,指向符号的以null结尾的字符串名字;value是符号的地址,对于可重定位的模块来说,value是句定义目标的节的起始位置的偏移,对于可执行目标文件来说,该值是一个绝对运行时地址;size是目标的大小(字节);type指明是数据、函数、对象等;bingding指明是符号是全局的还是本地的;Ndx指示所在节索引数,UND为未定义,ABS为无需重定位。

图4.4 用readelf查看的符号表信息
4.3.4 rel.data(如图4.5)
命令:readelf -r hello.o
重定位条目的格式:
Offset:需要被修改的引用的节偏移
Info:包含symbol(前4字节)和type(后4字节)的信息
Addend:一个有符号常数,一些类型的重定位要用它对被修改引用的值做偏移调整
Symbol:标识被修改引用应该指向的符号
Type:重定位类型,告知链接器如何修改新的引用,包括两种最基本的重定位类型:
R_X86_64_PC32:重定位一个使用32位PC相对地址的引用。一个PC相对地址就是距程序计数器的当前运行时值的偏移量。当CPU执行一条使用PC相对寻址的指令时,它就在指令中编码的32位值加上PC当前运行时值,得到有效地址,PC值通常是下一条指令在内存中的地址。
R_X86_64_32:重定位一个使用32位绝对地址的引用,通过绝对寻址,CPU直接使用在指令中编码的32位值作为有效地址,不需要进一步修改。

图4.5 用readelf查看的rel.data信息

4.4 Hello.o的结果解析
hello.o的反汇编结果:

图4.6 hello.o的反汇编结果

图4.7 hello.s
反汇编的过程是反汇编器根据机器代码生成一种类似于汇编代码的格式,在反汇编文件中,左侧列出机器代码,右侧列出与它等价的汇编语言。机器代码与它的反汇编有一些特性:
1)X86_64的指令长度从1到15个字节不等,常用的指令以及操作数较少的指令所需的字节数少,而那些不太常用的或者操作数较多的指令所需字节数较多。
2)设计指令格式的方式是,从某个给定位值开始,可以将字节唯一地解码成机器指令。
3)反汇编器指示基于机器代码文件中的字节序列来确定汇编代码,他不需要访问该程序的源代码或汇编代码。
4)反汇编器使用的指令命名规则与GCC生成的汇编代码使用的有些细微差别,它省略了很多指令结尾的大小指示符,但给call和ret添加了q后缀。
在分支转移中,反汇编使用的不是段名称,而是确定的地址。
在函数调用中,反汇编的call的目标不是函数名称,而是下一条指令。
4.5 本章小结
本章分析了hello.s 汇编后得到的可重定位目标文件hello.o,并对可重定位目标文件的各部分进行了分析比较了机器语言与汇编语言的区别。

(第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.1 链接生成可执行目标文件的过程
5.3 可执行目标文件hello的格式
典型的可执行目标文件ELF格式如图5.2

图5.2 典型的ELF可执行目标文件
可执行目标文件的格式类似于可冲定位目标文件的格式。.text 、.rodata 、。data节与可重定位目标文件中的节是相似的,除了这些节已经被重定位到它们最终运行的内存地址以外。.init 节定义了一个小函数,叫做_init,程序的初始化代码会用调用它,因为可执行文件是完全链接的(已重定位的),所以它不再需要.rel 节

5.3.1 ELF头(如图5.3)
命令:readelf -h hello
由图4.2可以看出,这是一个64位ELF文件;数据以2的补码、用小端法表示;是可执行文件;机器类型X86-64;入口点地址是0x400500;程序头开始地址是64;节头部开始地址是5928字节(指明程序运行时要执行的第一条指令的地址);ELF头的大小是64字节;节头部的大小是64字节;
共有25个节头,节头部的字符串表索引是24。

图5.3 用readelf查看的ELF头信息

5.3.2 节头部表(图5.4)
命令:readelf -S hello
图4.3显示了各个节的信息,包括名字、类型、地址、偏移量、大小、对齐方式、读写权限等,根据节头部中的信息可以确定每个节的起始位置和大小。

图5.4 用readelf查看的节头部表信息

5.3.3符号表(如图5.5)
命令:readelf -s hello

图5.5 用readelf查看的符号表信息
5.4 hello的虚拟地址空间
hello的虚拟地址为0x400000~0x400ff0

图5.6 hello虚拟空间的开始地址

图5.7 hello虚拟空间的结束地址
利用节头部表中的信息:

图5.8 节头部表中信息
.init 段开始与 0x400488,大小为0x017

图5.9 .init段信息

.text 段开始与 0x400500,大小为0x132

图5.10 .text 段信息
.rodata 段开始与 0x400640,大小为0x008

图5.11 .rodata 段信息

5.5 链接的重定位过程分析

图5.12 hello的反汇编
hello.out 与hello.o 的不同:
1)hello.out中增加的许多节和在hello.c中用到的函数的汇编代码
2)因为可执行文件是完全链接的(已重定位的),所以它不再需要.rel 节
3)hello.o中用相对偏移表示的地址在hello.out中替换成了虚拟内存地址。

图5.13 hello.out
重定位过程:
1)重定位节和符号定义:在这一步中,链接器将所有相同类型的节合并为同一个类型的新的聚合节,然后,链接器将运行时内存地址赋给新的聚合节,赋给输出模块定义的每个节,体积赋给输出模块定义的每个符号。当这一步完成时,程序中的每条指令和全局变量都有唯一的运行时内存地址了。
2)重定位节中的符号引用:在这一步中,链接器修改代码节和数据节中对每个符号的引用,使得他们指向正确的地址,要执行这一步,链接器依赖于可冲定位目标模块中称为重定位条目的数据结构。

图5.14 重定位条目
ELF定义了32种不同的重定位类型,其中最基本的两种:
R_X86_64_PC32:重定位一个使用32位PC相对地址的引用。一个PC相对地址就是距程序计数器的当前运行时值的偏移量。当CPU执行一条使用PC相对寻址的指令时,它就在指令中编码的32位值加上PC当前运行时值,得到有效地址,PC值通常是下一条指令在内存中的地址。
R_X86_64_32:重定位一个使用32位绝对地址的引用,通过绝对寻址,CPU直接使用在指令中编码的32位值作为有效地址,不需要进一步修改。

图5.15 重定位算法

5.6 hello的执行流程
ld-2.27.so!_dl_start 0x7f8c 4587893
ld-2.27.so!_dl_init 0x7f8c 458070c5
hello!_start 0x400500
libc-2.27.so!_libc_start_main 0x7f8c 45206534
-libc-2.27.so!_cxa_atexit 0x7f8c 452488647
-libc-2.27.so!_libc_csu_init 0x4005c0
libc-2.27.so!_setjmp 0x7f8c 45248672
libc-2.27.so!exit 0x7f8c 45246425
5.7 Hello的动态链接分析
动态链接项目:global_offset表,全局偏移表
通过hello信息可以看到,GOT起始表的位置是0x601000

dl_init 前:

dl_init后:

表中数据变成了相应的偏移量。
5.8 本章小结
本章针对于链接过程,分析了ELF格式的可重定位目标文件、可执行目标文件和动态链接,分析了程序的执行流程。
(第5章1分)

第6章 hello进程管理
6.1 进程的概念与作用
进程是一个执行中程序的实例,是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。进程给应用程序提供两个关键的抽象:一个独立的逻辑控制流,它提供一个假象,好像我们的程序独占地使用处理器;一个私有的地址空间,他提供一个假象,好像我们的程序独占地使用内存系统。
6.2 简述壳Shell-bash的作用与处理流程
1)shell:
Shell是一个连接了用户和 Linux 内核的应用程序,是一个命令解释器,它通过接受用户输入的Shell命令来启动、暂停、停止程序的运行或对计算机进行控制。
2)shell的功能:
Shell 除了能解释用户输入的命令,将它传递给内核,还可以调用其他程序,给其他程序传递数据或参数,并获取程序的处理结果;在多个程序之间传递数据,把一个程序的输出作为另一个程序的输入,Shell 本身也可以被其他程序调用。
因为shell可以调用其他程序,所以shell完全能够胜任 Linux 的日常管理工作,如文本或字符串检索、文件的查找或创建、大规模软件的自动部署、更改系统设置、监控服务器性能、发送报警邮件、抓取网页内容、压缩文件等。
我们还可以在shell中编程。
2)处理命令行的流程:
1.Shell首先从命令行中找出特殊字符(元字符),在将元字符翻译成间隔符号。元字符将命令行划分成小块tokens。Shell中的元字符如下所示:
SPACE , TAB , NEWLINE , & , ; , ( , ) ,< , > , |
2. 程序块tokens被处理,检查看他们是否是shell中所引用到的关键字。
3. 当程序块tokens被确定以后,shell根据aliases文件中的列表来检查命令的第一个单词。如果这个单词出现在aliases表中,执行替换操作并且处理过程回到第一步重新分割程序块tokens。
4.Shell对~符号进行替换。
5.Shell对所有前面带有符号的变量进行替换。6.Shell将命令行中的内嵌命令表达式替换成命令;他们一般都采用符号的变量进行替换。 6.Shell将命令行中的内嵌命令表达式替换成命令;他们一般都采用符号的变量进行替换。6.Shell将命令行中的内嵌命令表达式替换成命令;他们一般都采用(command)标记法。
7.Shell计算采用$(expression)标记的算术表达式。
8.Shell将命令字符串重新划分为新的块tokens。这次划分的依据是栏位分割符号,称为IFS。缺省的IFS变量包含有:SPACE , TAB 和换行符号。
9.Shell执行通配符* ? [ ]的替换。
10.shell把所有从处理的结果中用到的注释删除,并且按照下面的顺序实行命令的检查:
A. 内建的命令
B. shell函数(由用户自己定义的)
C. 可执行的脚本文件(需要寻找文件和PATH路径)
11.在执行前的最后一步是初始化所有的输入输出重定向。
12.最后,执行命令。

6.3 Hello的fork进程创建过程
父进程通过调用fork()函数创建一个新的运行的子进程。新创建的子进程几乎但并不完全与父进程相同。子进程得到与父进程用户级虚拟地址空间相同(但是独立的)一份副本包括代码和数据段、堆、共享库以及用户栈。子进程还获得与父进程任何打开文件描述符相同的副本,这就意味着当父进程调用fork是,子进程可以读写父进程中代开的任何文件。父进程和新创建的子进程之间最大的区别在于它们有不同的PID。
当我们输入执行这个hello程序的命令时,shel解析命令,并调用fork创建一个子进程。
6.4 Hello的execve过程
execve函数在当前进程的上下文中加载并运行hello程序,加载并运行程序需要以下几个步骤:
1)删除已存在的用户区域。
删除当前进程虚拟地址的用户部分中的已存在的区域结构。
2)映射私有区域。为新程序的代码、数据、bss和栈区域创建新的区域结构。所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为a.out文件中的.text和.data区。bss区域是请求二进制零的,映射到匿名文件,其大小包含在a.out中。栈和堆区域也是请求二进制零的,初始长度为零。图6.1概括了私有区域的不同映射。
3)映射共享区域。如果a.out程序与共享对象(或目标)链接,比如标准C库 libc.so,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内。
4)设置程序计数器(PC)。execve做的最后一件事情就是设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。

图6.1 加载器是如何映射用户地址空间的区域的
6.5 Hello的进程执行
结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。
概念:
1)进程上下文信息:上下文是由程序正常运行所需的状态组成的,这个状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。
2)进程时间片:一个进程控制它的控制流的一部分的每一时间段 。
3)用户模式与内核模式:处理器通过控制某个寄存器的一个模式位来切换用户模式与内核模式;执行在内核模式下的进程,可以执行指令集中的任何指令,访问系统中的任何内存模式;用户程序则必须通过调用系统接口间接的访问内核代码和数据。
4)调度:在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个之前被抢占了的进程所需的状态,这种决策就叫调度。
hello程序开始时运行在用户模式,当它调用sleep函数后,进程进入内核模式。内核处理休眠,将hello进程从运行队列移入等待队列,切换上下文,移交控制给其他进程。hello休眠的计时器到达时间后发送信号给内核,内核执行中断,将hello重新添加进运行队列,继续执行。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
hello调用getchar时,程序也从用户模式切换成内核模式,接受数据传输,内核移交控制给其他进程,数据传输结束后,内核接受中断信号,切换上下文,移交控制给hello。
6.6 hello的异常与信号处理
可能出现的异常:

图6.2 可能出现的异常
我们可以通过键盘输入发送中断信号SIGTSTP信号和终止信号SIGINT给进程。
1)Ctrl+z 发送一个SIGTSTP中断信号给进程,进程挂起但不结束,使用ps仍能看到hello程序,调用fg,将hello调度到前台,hello继续运行,完成后续步骤。

图6.3 ctrl+Z的处理
2)Ctrl+c 发送一个SIGINT信号给进程,默认行为是终止进程,调用ps不再看到hello。

图6.4 ctrl+C的处理
3)不停乱按:正常运行时不停乱按,输入的字符只是进入缓冲区,非命令的输入都不会得到处理。

图6.5 乱按的处理

6.7本章小结
本章介绍了进程创建、执行到回收的过程,以hello为例,分析了程序对常见信号的处理。
(第6章1分)

第7章 hello的存储管理
7.1 hello的存储器地址空间
1)逻辑地址:CPU所生成的地址。逻辑地址是内部和编程使用的、并不唯一。逻辑地址指的是机器语言指令中,用来指定一个操作数或者是一条指令的地址。对逻辑地址要求,“一个逻辑地址,是由一个段标识符加上一个指定段内相对地址的偏移量,表示为 [段标识符:段内偏移量]。对hello程序来说,它是出现在汇编代码中的地址。
2)线性地址(虚拟地址):跟逻辑地址类似,它也是一个不真实的地址,线性地址对应了硬件页式内存的转换前地址
3)物理地址:用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应。加载到内存地址寄存器中的地址,内存单元的真正地址。在前端总线上传输的内存地址都是物理内存地址,编号从0开始一直到可用物理内存的最高端。这些数字被Nortbridge chip映射到实际的内存条上。物理地址是明确的、最终用在总线上的编号,不必转换,不必分页,也没有特权级检查
7.2 Intel逻辑地址到线性地址的变换-段式管理
逻辑地址由48位组成的,前16位为“段选择符”(段标识符),后32位是段内偏移量。
1)段选择符的格式如图:

图7.1 段选择符
前13位是在(全局/局部)描述符表中的索引;
之后一位是TI,如果TI是0,那么前面的索引是在全局描述符表GDT中的索引;如果TI是1,那么前面的索引是在局部描述符表中的索引;
最后两位是RPL,它指明段的级别。为 00,位于最高级别的内核态。为 11,位于最低级别的用户态。
2)全局描述符 GDT:只有一个,用来存放系统内用来存放系统内每个任务共用的描述符,例如,内核代码段、内核数据段、用户代码段、用户数据段以及 TSS(任务状态段)等都属于 GDT 中描述的段。
局部描述符表 LDT:存放某任务(即用户进程)专用的描述符
3)描述符表存放这段描述符,每个段描述符是8字节:
BASE(32位):段首地址的线性地址。

G:为0代表此段长度以字节为单位,为1代表此段长度以4K为单位。

LIMIT(20位):此最后一个地址的偏移量,也相当于长度,G=0,段大小在11MB,G=1,段大小为4KB4GB。

S:为0表示是系统段,否则为代码段或数据段。

Type:描述段的类型和存取权限。

DPL:描述符特权级,表示访问这个段CPU要求的最小优先级(保存在cs寄存器的CPL特权级),当DPL为0时,只有CPL为0才能访问,DPL为3时,CPL为0为3都可以访问这个段。

P:表示此段是否被交换到磁盘,总是置为1,因为linux不会把一个段都交换到磁盘中。

D或B:如果段的LIMIT是32位长,则置1,如果是16位长,置0。(详见intel手册)

AVL:A = 1 已被访问过,A = 0 未被访问过。(通常 A 包含在 TYPE 字段中)
4)下面来进行转换:
①首先,由段选择符列TI确定在全局还是局部描述符表里查找
②然后根据段选择符中的索引确定要选择的表项(段描述符)
③应用段选择符的RPL确定的权限
④把段描述符中取到的段基地址加到汇编语言中的地址(偏移量)上,形成线性地址
7.3 Hello的线性地址到物理地址的变换-页式管理
概念:
1)虚拟页面:虚拟内存被组织为一个有存放在磁盘上的N个连续的字节大小的单元组成的数组,每字节都有一个唯一的虚拟地址,作为到数组的索引。VM系统将虚拟内存分割为称为虚拟页(VP)的固定大小的块。类似的,物理内存也被分割为屋里也(PP)。虚拟页和物理页的大小是相同的。
在任意时刻,虚拟页面的集合都分为三个不相交的子集:
①未分配的:VM系统还未分配(或者创建)的页。未分配的块没有任何数据和它们相关联,因此也就不占用任何磁盘空间。
②缓存的:当前已缓存在物理内存中的已分配页。
③未缓存的:未缓存在物理内存中的已分配页。
2)DRAM缓存作为虚拟内存系统的缓存,在主存中缓存虚拟页。
3)页表是一个页表条目(PTE)的数组(如图7.2),虚拟地址空间中的每个页在页表中一个固定的偏移量处都有一个PTE。

图7.2 页表
每个PTE有一个有效位和一个n位地址字段组成。有效位表明了该虚拟页当前是否被缓存在DRAM中。
①如果设置了有效位,那么地址字段就表示 DRAM中相应的物理页的起始位置,这个物理页中缓存了该虚拟页。(缓存的)
②如果没有设置有效位,那么一个空地址表示这个虚拟页还未被分配。(未分配的)
③否则,这个地址就指向该虚拟页在磁盘上的起始位置。(未缓存的)
4)缺页异常:
访问某个虚拟页时,它的PTE的有效位为0,说明它没有被缓存到物理页,但是物理页已经都有存储的信息了,那么缺页处理程序从物理页中选择一个牺牲页,把现在要访问的这个页存到这个物理页里,如果这个物理页之前被修改过,就把物理页的内容写回内存,否则直接覆盖。
下面叙述线性地址翻译成物理地址的过程:
一个n位的虚拟地址包含两部分:一个p位的虚拟页面偏移(VPO)和一个(n-p)位的虚拟页号(VPN)。
处理器生成一个虚拟地址,把它传送给MMU,
MMU选择VPN对应的PTE,如果有效位是1,那么取出后续的物理页的起始位置,把它和VPO拼接在一起,构成一个物理地址。
如果有效位是0,触发缺页异常,处理异常后,重新访问PTE,取出后续物理页的起始地址,与VPO组成物理地址。
即以VPO作为偏移量,以物理页起始地址为基址,进行。
7.4 TLB与四级页表支持下的VA到PA的变换

图7.3
如图7.3,虚拟地址是48位的,物理地址是52位的,每个页表的大小是4KB,共四级页表,每级页表以VPN的9位为索引,LI TLB是4路16组相联的,L1高速缓存的块大小是64字节。
CPU产生一个48位的虚拟地址,MMU将36位VPN的钱32位作为TLBT,后四位作为TLBI,如果在TLB中命中,直接得到PTE,得到四十位物理页起始地址,与12位VPO组合成物理地址;如果TLB不命中,以VPN前9位为第一季页表索引,确定二级页表基址,以此类推,在四级页表中得到四十位PPN,与12为VPO组合得到物理地址。
上述过程中,如果PTE有效位为0,引发缺页异常,处理后重新访问PTE。
7.5 三级Cache支持下的物理内存访问
以图7.3的L1 d-cache为例,组数是64=26,所以组索引位数是6,每组8行,块大小是64=26字节,所以偏移量也是6位,所以标记位是52-6-6=40位。
当有一个52位的物理地址
1)首先根据组索引确定LI缓存的组数
2)然后把标记位与组里每个行的标记位比较,如果有标记位一致且有效位为1的行,则根据偏移量选择出需要的块。
3)如果没有符合条件的行,从下一级存储结构里取出这个块,放到刚才的组里,如果组里有空行,直接放置在空行上;如果组已满,那么采用一种驱逐策略,驱逐一个行,把这个块存进去。
7.6 hello进程fork时的内存映射
当shell为hello程序调用fork函数时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID。为了给这个新进程创建虚拟内存,它创建了当前进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。
当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,因此,也就为每个进程保持了私有地址空间的抽象概念。
7.7 hello进程execve时的内存映射
execve函数加载并运行hello程序需要以下几个步骤:
1)删除已存在的用户区域。
删除当前进程虚拟地址的用户部分中的已存在的区域结构。
2)映射私有区域。为新程序的代码、数据、bss和栈区域创建新的区域结构。所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为a.out文件中的.text和.data区。bss区域是请求二进制零的,映射到匿名文件,其大小包含在a.out中。栈和堆区域也是请求二进制零的,初始长度为零。图6.1概括了私有区域的不同映射。
3)映射共享区域。如果a.out程序与共享对象(或目标)链接,比如标准C库 libc.so,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内。
4)设置程序计数器(PC)。execve做的最后一件事情就是设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。

7.8 缺页故障与缺页中断处理
在虚拟内存的习惯说法中,DRAM缓存不命中称为缺页(page fault)。图7.4展示了在缺页之前我们的示例页表的状态。CPU引用了 VP 3中的一个字,VP 3并未缓存在DRAM中。地址翻译硬件从内存中读取PTE 3,从有效位推断出VP 3未被缓存,并且触发一个缺页异常。缺页异常调用内核中的缺页异常处理程序,该程序会选择一个牺牲页,在此例中就是存放在PP 3中的VP 4。如果VP4已经被修改了,那么内核就会将它复制回磁盘。无论哪种情况,内核都会修改VP4的页表条目,反映出VP4不再缓存在主存中这一事实。

图7.4 缺页前
接下来,内核从磁盘复制VP3到内存中的PP 3,更新PTE3,随后返回。当异常处理程序返回时,它会重新启动导致缺页的指令,该指令会把导致缺页的虚拟地址重发送到地址翻译硬件。但是现在,VP 3已经缓存在主存中了,那么页命中也能由地址翻译硬件正常处理了。图9-7展示了在缺页之后我们的示例页表的状态。

图7.5 缺页后
7.9动态存储分配管理
1)动态内存分配器维护着一个进程的虚拟内存区域,称为堆。假设堆是一个请求二进制零的区域,它紧接在未初始化的数据区域后开始,并向上生长(向更高的地址)。对于每个进城,内核维护着一个变量brk,它指向堆的顶部。
分配器将堆视为一组不同大小的块(block)的集合来维护。每个块就是一个连续的虚拟内存片(chunk),要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可用来分配。空闲块保持空闲,知道它显式地被应用所分配。一个已分配的块保持已分配状态,知道它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。
内存分配器有两种基本风格:
显式分配器要求应用显式地释放任何已分配的块;
隐式分配器,要求分配器检测一个已分配的块何时不再被程序所使用,那么就释放这个块。
2)带边界标签的隐式空闲链表:
分配器将用来区分块边界、区分已分配块和空闲块的数据结构嵌入块本身,在这种情况下,一个块是由一个字的头部、有效载荷,以及一些可能的额外的填充组成的。头部编码了这个块的大小(包括头部和所有的填充),以及这个块是已分配的还是空闲的。如果我们强加一个双字的对齐条件,那么块的大小就总是8的倍数,且块的最低3位总是零。因此,我们只需要内存大小的29个高位,释放剩余的3位来编码其他信息。在这种情况中,我们用其中的最低位(已分配为)来指明这个块是已分配的还是空闲的。
头部后面就是应用调用malloc时请求的有效载荷。有效载荷后面是一片不使用的填充块,其大小可以是任意的。需要填充有很多原因。比如,填充可能是分配器策略的一部分,用来对付外部碎片。或者也需要用它来满足对齐要求。
我们将组堆织为一个连续的已分配块和空闲块的序列,称为隐式空闲链表。分配器可以通过遍历堆中所有的块,从而间接地遍历整个空闲块的集合。此时需要某种特殊标记的结束块。
Knuth提出了一种边界标记技术(如图7.6),允许在常数时间内进行对前面块的合并。这种思想是在每个块的结尾处添加一个脚部,其中脚部就是头部的一个副本。如果每个块包括这样一个脚部,那么分配器就可以通过检查它的脚部,判断前面一个块的起始位置和状态,这个脚部总是在距当前块开始位置一个字的距离。

图7.6 使用边界标记的堆块的格式
将空闲链表组织成一个隐式空闲链表,有如图7.7的恒定格式

图7.7 隐式空闲链表的恒定格式
第一个字是一个双字边界对齐的不使用的填充字。填充后面紧跟着一个特殊的序言块(prologue block),这是一个8字节的已分配块,只由一个头部和一个脚部组成。序言块是在初始化时创建的,并且永不释放。在序言块后紧跟的是零个或者多个由malloc或者free调用创建的普通块。堆总是以一个特殊的结尾块(epilogue block)来结束,这个块是一个大小为零的已分配块,只由一个头部组成。序言块和结尾块是一种消除合并时边界条件的技巧。分配器使用一个单独的私有(static)全局变量(heap_listp),它总是指向序言块。(作为一个小优化,我们可以让它指向下一个块,而不是这个序言块。)
7.10本章小结
这一章首先叙述了不同的地址概念和他们之间的转换,由此引出了存储结构、和访存的相关知识,最后又重温了内存映射,复习了动态内存分配的方法策略。
(第7章 2分)

第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件
设备管理:unix io接口
一个Linux文件就是一个m个字节的序列:
B0,B1,…,Bk,…,Bm-1
所有的I/О设备(例如网络、磁盘和终端)都被模型化为文件,而所有的输入和输出都被当作对相应文件的读和写来执行。这种将设备优雅地映射为文件的方式,允许Linux内核引出一个简单、低级的应用接口,称为Unix I/O,这使得所有的输入和输出都能以一种统一且一致的方式来执行。
8.2 简述Unix IO接口及其函数
Unix I/O 接口;
1)打开文件。一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个I/O设备。内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件。内核记录有关这个打开文件的所有信息。应用程序只需记住这个描述符。
2)Linux shell 创建的每个进程开始时都有三个打开的文件:标准输入(描述符为0)、标准输出(描述符为1)和标准错误(描述符为2)。头文件< unistd.h>定义了常量sTDINFILENO、STDOUT_FILENO和STDERR_FILENO,它们可用来代替显式的描述符值。
3)改变当前的文件位置。对于每个打开的文件,内核保持着一个文件位置k,初始为0。这个文件位置是从文件开头起始的字节偏移量。应用程序能够通过执行seek操作,显式地设置文件的当前位置为k。
4)读写文件。一个读操作就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增加到k十n。给定一个大小为m字节的文件,当k≥m时执行读操作会触发一个称为end-of-file(EOF)的条件,应用程序能检测到这个条件。在文件结尾处并没有明确的“EOF符号”。类似地,写操作就是从内存复制n>0个字节到一个文件,从当前文件位置é开始,然后更新é。
5)关闭文件。当应用完成了对文件的访问之后,它就通知内核关闭这个文件。作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。无论一个进程因为何种原因终止时,内核都会关闭所有打开的文件并释放它们的内存资源。

Unix I/O 函数:
1)open函数:
int open(char *filename, int flags, mode_t mode)
返回:若成功则为新文件描述符,若出错则为-1
Open函数将filename转换为一个文件描述符,并且返回描述符数字。返回的描述符总是在进程中当前没有打开的最小描述符。Flags参数指明了进程打算如何访问这个文件,mode参数指明了新文件的访问权限位。
2)close函数:
int close(int fd)
返回:若成功则为0,出错则为-1.
进程通过调用close函数关闭一个打开的文件。
3)read函数
ssize_t read (int fd, void *buf, size_t n)
read 函数从描述符为fd的当前文件位置复制最多n个字节到内存位置buf。返回值一1表示一个错误,而返回值0表示 EOF。否则,返回值表示的是实际传送的字节数量。
4)write函数
ssize_t write(int fd, const void *buf, size_t n)
write函数从内存位置buf复制至多n个字节到描述符fd的当前文件位置。
5)lseek函数
通过调用了lseek函数,应用程序能够显式地修改当前文件的位置。
8.3 printf的实现分析
Printf函数原型:

图8.1 printf函数原型
printf函数调用vsprintf函数,这个函数的作用是格式化。它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出,它返回要打印的字符串长度
接着printf函数调用Unix I/O 函数write(buf,i),打印出长度为i的buf。

图8.2 write函数
int INT_VECTOR_SYS_CALL表示要通过系统来调用sys_call这个函数,sys_call函数显示格式化字符串。
https://www.cnblogs.com/pianist/p/3315801.html
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
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;
}
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章主要介绍了Unix I/O 的概念和相关函数,并以printf和getchar函数为例,展现了Unix I/O 函数的应用。
(第8章1分)
结论
用计算机系统的语言,逐条总结hello所经历的过程。
1.hello.c : c语言编写的源文件,以ASCII码编码
2.hello.i : hello.c经预处理得到的文本文件
3.hello.s: hello.i经编译得到的文本文件,包含汇编语言程序
4.hello.o : hello.s 经汇编得到的可重定位目标文件
5.hello : 经链接得到的可执行目标文件
6.运行:在shell中键入运行命令,
1)shell调用fork为hello创建子进程
2)shell调用execve加载并执行hello
3)hello调用函数,或shell接受信号,shell进行上下文切换,传递控制
4)shell为hello动态分配内存
5)hello运行结束或接收信号而终止,shell安排父进程回收hello进程,清除相关痕迹。
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
计算机系统设计是一个复杂而庞大的工程,系统中一个小程序的运行也是诸多部分共同工作的结果。计算机的设计首先体现了准确性:各种操作清晰明确,每一种操作在当时的情况下都是确定的;其次体现了全面性,计算机系统的设计必须考虑所有可能的情况,避免错误的发生;最后体现了平衡性,计算机系统协调的设计,追求时间、空间与经济上的平衡。

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

附件
(附件0分,缺失 -1分)
hello.c : c语言编写的源文件,以ASCII码编码
hello.i : hello.c经预处理得到的文本文件
hello.s: hello.i经编译得到的文本文件,包含汇编语言程序
hello.o : hello.s 经汇编得到的可重定位目标文件
hello : 经链接得到的可执行目标文件
hello.out : hello 的反汇编文件

参考文献
为完成本次大作业你翻阅的书籍与网站等
[1] 预处理阶段所要做的工作_yuer的博客-CSDN博客_预处理阶段有哪些操作
https://blog.csdn.net/u010141928/article/details/74200737?ops_request_misc=&request_id=&biz_id=102&utm_term=%E9%A2%84%E5%A4%84%E7%90%86%E8%BF%9B%E8%A1%8C%E4%BA%86%E4%BB%80%E4%B9%88%E6%93%8D%E4%BD%9C&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-1-.first_rank_v2_pc_rank_v29&spm=1018.2226.3001.4187[2]

[2]https://blog.csdn.net/qq_38769551/article/details/100901907?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162441678416780261963856%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=162441678416780261963856&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allbaidu_landing_v2~default-1-100901907.first_rank_v2_pc_rank_v29&utm_term=%E6%B1%87%E7%BC%96%E6%96%87%E4%BB%B6%E5%BC%80%E5%A4%B4&spm=1018.2226.3001.4187
[3] https://blog.csdn.net/sphone89/article/details/5962699?ops_request_misc=&request_id=&biz_id=102&utm_term=%E9%80%BB%E8%BE%91%E5%9C%B0%E5%9D%80%E5%92%8C%E7%89%A9%E7%90%86%E5%9C%B0%E5%9D%80%E5%A6%82%E4%BD%95%E8%BD%AC%E6%8D%A2&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-4-.nonecase&spm=1018.2226.3001.4187
[4] https://blog.csdn.net/genghaihua/article/details/89450057?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162452282716780261991449%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=162452282716780261991449&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_positive~default-1-89450057.first_rank_v2_pc_rank_v29&utm_term=%E9%80%BB%E8%BE%91%E5%9C%B0%E5%9D%80&spm=1018.2226.3001.4187
[5]
https://blog.csdn.net/Pipcie/article/details/105670156?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162452327216780264020679%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=162452327216780264020679&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allbaidu_landing_v2~default-1-105670156.first_rank_v2_pc_rank_v29&utm_term=%E9%80%BB%E8%BE%91%E5%9C%B0%E5%9D%80%E5%88%B0%E7%BA%BF%E6%80%A7%E5%9C%B0%E5%9D%80%E7%9A%84%E5%8F%98%E6%8D%A2&spm=1018.2226.3001.4187
[6]https://blog.csdn.net/asdfsadfasdfsa/article/details/98223811?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162452327216780262512185%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=162452327216780262512185&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_v2~rank_v29-4-98223811.first_rank_v2_pc_rank_v29&utm_term=%E9%80%BB%E8%BE%91%E5%9C%B0%E5%9D%80%E5%88%B0%E7%BA%BF%E6%80%A7%E5%9C%B0%E5%9D%80%E7%9A%84%E5%8F%98%E6%8D%A2&spm=1018.2226.3001.4187

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

哈尔滨工业大学-计算机系统大作业-程序人生相关推荐

  1. 哈尔滨工业大学计算机系统大作业--程序人生

    计算机系统   大作业 题     目  程序人生-Hello's P2P      专       业   计算机科学与技术        学    号        2021110xxx      ...

  2. 哈尔滨工业大学计算机系统大作业 程序人生-Hello’s P2P

    计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 计算学部 计算机科学与技术学院 2021年5月 摘  要 本文以hello程序从hello.c到进程各个阶段所 ...

  3. 哈尔滨工业大学计算机系统大作业——程序人生-Hello’s P2P

    目录 摘要 第1章 概述 1.1 Hello简介 1.1.1 P2P:From Program to Process 1.1.2 020:From Zero-0 to Zero-0 1.2 环境与工具 ...

  4. 哈尔滨工业大学计算机系统大作业-程序人生 Hello‘s P2P

    文章目录 第1章 概述 1.1 Hello简介 1.2 环境与工具 1.3 中间结果 1.4 本章小结 第2章 预处理 2.1 预处理的作用及概念 2.2 在Ubuntu下预处理的命令 2.3 Hel ...

  5. 哈尔滨工业大学CSAPP大作业程序人生

    计算机系统 大作业 题 目 程序人生-Hello's P2P 专 业 计算学部 学 号 1190202126 班 级 1936602 学 生 李映泽 指 导 教 师 刘宏伟 计算机科学与技术学院 20 ...

  6. HIT 深入理解计算机系统 大作业 程序人生-Hello’s P2P

    HIT 深入理解计算机系统 大作业 程序人生-Hello's P2P 本论文旨在研究 hello 在 linux 系统下的整个生命周期.结合 CSAPP 课本, 通过 gcc 等工具进行实验,从而将课 ...

  7. 【2022】哈工大计算机系统大作业——程序人生Hello’s P2P

    2022哈工大计算机系统大作业--程序人生Hello's P2P 摘要 第1章 概述 1.1 Hello简介 1.2 环境与工具 1.3 中间结果 1.4 本章小结 第2章 预处理 2.1 预处理的概 ...

  8. 哈尔滨工业大学 计算机系统大作业

    计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 计算机类 学   号 120L022010 班   级 2003007 学       生 窦馨宇 指 导 教 ...

  9. 哈工大2022年春季学期计算机系统大作业——程序人生

    计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 人工智能(未来技术) 学   号 7203610716 班   级 20WJ102 学       生 孙铭蔚 ...

  10. 2021春深入理解计算机系统大作业——程序人生

    计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 计算学部 学    号 1190200608 班    级 1903004 学       生 琚晓龙 指 导 ...

最新文章

  1. 三维动画属于计算机专业吗,三维动画设计专业属于文科还是理科
  2. hdu 3622 Bomb Game【二分+2-SAT+tarjan】
  3. java开发变化_十年编程语言变化,大众程序员的路在哪里?
  4. ITK:观察过滤器Watch A Filter
  5. matlab 常用m文件,MATLAB基础课程 第三章 M文件知识(4)
  6. zabbix监控MHA及自动启动
  7. Bootstrap3 带表格的面板
  8. WINDOWS对文件签名,算法如何由sha1改为sha256/sha512
  9. 如何登录新浪微博html5,新浪微博怎么用登录名登录
  10. 使用Nmap扫描目标主机
  11. s2 安恒 漏洞验证工具_Struts2漏洞检查工具2018版本V2.1.exe
  12. spotlight ios_如何禁用iOS 10的Spotlight搜索历史记录
  13. 游戏程序员的2013年终总结
  14. Primefaces使用小结
  15. vue|JavaScript let语法
  16. 2月3日模拟赛分析及总结
  17. 代码随想录Day01:数组理论基础、二分查找、移除元素
  18. 常用Date日期方法
  19. java模拟atm_Java第一课——模拟ATM机
  20. Quartus ii 软件仿真基本流程(使用VHDL)

热门文章

  1. 网页版pdf转换方法
  2. 简单记录 03.21
  3. 如何巧妙将流量转化为销量?
  4. 重新学习FPGA(一):EDA技术
  5. EDA技术与VHDL设计
  6. 正逻辑与负逻辑的关系
  7. 西蔵高考成绩查询2021,2021西藏高考个人成绩排名查询,西藏高考成绩排名查询方式...
  8. hyperscan cmake .. 报错
  9. ppt convert to html,powerpoint(ppt)
  10. 虚拟机体验NAS私人云(第四篇):虚拟机安装群晖DSM7.01系统(附赠新版DS918+和DS3615xs启动映像)