计算机系统

大作业

题 目 程序人生-Hello’s P2P
专 业 计算机
学   号 ***********
班   级 *******
学 生 ***    
指 导 教 师 郑贵滨

计算机科学与技术学院
2021年6月
摘 要
每一段程序都像是一段人生,有开始,有过程也有最终的结束,我们经历了纷繁复杂的事物,与千变万化的考验最终绽放出自己的价值,一个程序也是如此,e今天我们从最简单的一个helloworld!开始探究程序的人生。通过一个最简单的hello.c源程序,一步步编译执行,走完一个程序完整的一生,感悟程序人生的魅力。
关键词:计算机系统、编译、链接、加载、运行、异常、进程。
(摘要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的P2P实际含义为,Hello的From Program to process,直译过来为hello这个程序的实现过程,其实就是从文本文件到可执行文件并最终运行出结果的一个过程。
hello.c源程序经过预处理器(cpp)生成hello.i文件(修改了的源程序)再经过一步编译器(cc1)的编译生成hello.s汇编程序,汇编程序经过汇编器(as)生成hello.o(可重定位目标程序),最后经过链接器(ld)链接生成hello可执行目标文件,可以被加载到内存中,由系统执行。
具体流程如下图:

图1.1
接下来,在shell中输入./hello,执行该文件,此时shell会调用一个fork()函数为该条指令开启一个子进程,然后调用execve函数,将hello加载入内存中进行执行。。。。。(未完待续)
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
计算机性能如下图:

                         图1.2

Windows10 64位操作系统;Vmware16 Ubuntu 20.04.2.2.0-amd64.iso
gcc; gdb; readelf; objdump;vim;valgrind; CodeBlocks 17.12

1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
文件名 作用
hello.i 预处理后产生的文件(修改后的源程序)
hello.s 通过编译器产生的的汇编程序,以文本的格式记录着实现高级语言的机器语言指令。
hello.o 通过汇编器产生的可重定位目标程序,为一个二进制文件。
hello.d 对.o文件进行objdump指令会产生反汇编文件,经常使用它来理解程序的内部结构。
hello 由链接器生成的可执行目标文件
hello.elf 由hello.o文件经过readelf指令生成,可以观察hello.o文件的各个节的信息

1.4 本章小结
本章主要介绍了Hello的P2P的概念内涵,列出了本次大作业的运行环境与工具。初步探究了一个hello.c的程序从文本文件到一个可执行文件并且完成执行的一系列步骤,给出了各个步骤需要的器件和生成的中间文件,以及各类文件的功能和特点。

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

           图2.1

2.处理条件编译
#if,#ifndef,#ifdef,#endif,#undef等也是比较常见的预处理,主要是进行编译时进行有选择的挑选,注释掉一些指定的代码,以达到版本控制、防止对文件重复包含的功能。
3.处理宏替换
#define,这是最常见的用法,它可以定义符号常量、函数功能、重新命名、字符串的拼接等各种功能。在处理宏替换时,需要将程序中所有宏名全部替换为被定义的字符串,这个操作被称为宏展开。
2.2在Ubuntu下预处理的命令
Ubuntu下的预处理指令为 gcc -E hello.c -o hello.i

图2.2
2.3 Hello的预处理结果解析
打开hello.i的文本文件,我们寻找编写的源代码,在3047行(文本文件的最后)找到了源代码,前面3046行都被预处理产生的信息所填充

                 图2.3

回到最开始进行观察

                图2.4

发现首先引用了/usr/include/stdc-predef.h文件,经过查询得知这个头文件是为了确定gcc的版本,在每个.i文件开始都会出现。

图2.5
接下来又对每一个源文件的引用。
我们观察第14行的# 27 “/usr/include/stdio.h” 3 4 探究这一行的数字和文件名称,我们通过文件路径找到stdio.h文件

         图2.6

进入stdio.h文件中查找第27行发现:

                     图2.7

第27行的内容正好是.i文件中下一行的内容。# 1 “/usr/include/x86_64-linux-gnu/bits/libc-header-start.h” 1 3 4,接着试验下面几行结果也是如此,由此我们可以得出.i文件每一行开头的数字,代表下一行在这一行所写文件的第几行,一个文件一个文件嵌套式的寻找,构成一个递归的解析过程。
2.4 本章小结
在这一章中,我们讨论了预编译的概念,目的,具体内容和实现结果。了解到预编译到底做了什么。文件包含,条件编译和宏替换。
(第2章0.5分)

第3章 编译
3.1 编译的概念与作用
编译器(cc1)将文本文件hello.i翻译成文本文件hello.s。
主要作用:

  1. 扫描(词法分析)将源代码输入扫描器,将源代码的字符序列分割成一系列记号。
  2. 语法分析,基于词法分析,得到一系列记号生成语法树
  3. 语义分析,由语义分析器完成,指示判断是否合法.并不报错,分为静态语义和动态语义(在运行时才能确定)两种.
  4. 源代码优化(中间语言生成),中间代码使得编译器分为前端和后端,前端产生与机器(或环境)无关的中间代码,编译器的后端将中间代码转换为目标机器代码.
  5. 代码生成,目标代码优化,编译器后端主要包括,代码生成器,依赖于目标机器的不同字长,寄存器,数据类型.
    3.2 在Ubuntu下编译的命令
    gcc -S hello.i -o hello.s

图3.1
3.3 Hello的编译结果解析
编译将hello.i文件生成hello.s文件,及汇编文件,主要有机器指令组成。
数据段 功能
.text 已编译程序的机器代码
.rodata 只读数据段
.data 已初始化的全局变量
.bss 未初始化和初始化为0的全局变量,
.symtab 符号表,存放程序中定义和引用的函数和全局变量的信息
.rel.text 一个.text节中位置的列表
.rel.data 被模块引用或定义的任何全局变量的重定位信息
.debug 一个调试符号标。-g命令后会得到这张表
.line C源程序的行号和.text节中机器指令之间的映射。同样-g后得到
.strtab 一个字符串表,内容包括.symtab和.debug节中的符号表

3.3.1数据
在hello.c文件中main函数之前定义了一个全局变量sleepecs = 2.5(如图3.2)

     图3.2

在图3.3中我们可以看到hello.s文件的数据段中存储着sleepecs,存放在.data节,按4字节对齐,类型为sleepsecs,大小占4个字节,,而接着sleepsecs被赋值为了long类型的2

             图3.3
如图3.4所示,下一部分是两个需要打印输出的字符串。

这两个字符串被存放在只读数据段,第一个字符串.LC0包含汉字,每个汉字在utf-8编码中被编码为三个字节

图3.5
除此之外,还有argc和i两个整形参数,其中int i是循环中用来计数的局部变量,argc是从终端输入的参数的个数,也是main函数的第一个参数。编译器一般会将局部变量存储在寄存器或者程序栈中.
参数argc保存在寄存器edi中,初始值为1,当不等于3时继续循环。

             图3.6
参数i保存在-4(%rbp)中,初始值为0,当i小于10时进行循环图3.7

hello.c中的数组是main()函数的第二个参数char *argv[],argv是字符串指针的数组,每个元素是一个指向一个字符串首地址的指针,作为函数的第二个参数,argv[]开始被保存在寄存器%rsi中,然后又被保存到栈中-32(%rbp)的位置。

                 图3.8

在循环体(.L4)内,数组的首地址又被赋给了%rax,然后main函数分两次读取了%rax+16和%rax+8的地址的内容,分别放入了%rdx(printf的第三个参数)和%rsi(printf的第二个参数),然后把.LC1放入%edi(printf的第一个参数),然后调用printf函数。这样就实现了printf的格式化输出。

                 图3.9

3.32赋值
Hello.c的赋值有两处,第一处是给sleepecs赋值为2.5,第二处是将i赋值为初始值0,然后一步一步递增。由图3.3可以看出,全局变量sleepsecs的赋值在.data节中就已经完成,直接将sleepsecs赋值为long型的2。局部变量i的赋值是利用了mov指令,如图3.7中所展示的,movl $0,-4(%rbp),其中movl的l表示i的大小是四字节, 0 的 0的 0的符表示0是一个立即数。
3.33类型转换
再将sleepsecs赋值为2.5时,因为sleepsecs的类型为int,所以编译器在赋值的同时会将2.5自动转化为2.所以最终sleepsecs得到的值为2.
3.34算术操作
根据课本上汇编语言的算术操作:

图3.10
hello.c中的算术操作是每次循环将i加一。通过addl $1,-4(%rbp)指令来完成。
3.35关系运算
C语言中的关系操作有==、!=、<、>、<=、>=等,这在汇编语言中主要由cmp指令和test指令实现。cmp用来将前后两个数进行比较,实际上的实现与sub相同(通过两个数的差设置条件码,但不改变目的值),test的内部实现方式与and相同(通过两个数的与设置条件码,但不改变目的值)。

              图3.11

如图3.11所示,从hello.c文件中看到,需要比较argc与3是否相等,以及比较i和10的大小关系。在对应的hello.s文件中如图3.12和3.13通过cmpl这一指令完成对两数的比较

                     图3.12

图3.13
3.35控制转移
教材上的汇编指令:

                         图3.14
主要是由一系列的jmp加上条件判断组成跳转控制。

对于hello.c中的if(argc!=3),编译器处理为了先判断argc和3是否相等,若相等,则跳转至.L2,否则继续执行之后的内容。而对于for循环中的i<10,编译器则会判断i是否小于等于9,若是,则继续执行循环体内的内容,否则继续执行下面的内容。
3.36函数操作
1.main函数

     图3.15

Main函数被保存在.text节中,在hello.c开始运行时将由系统启动调用,main函数有两个变量分别是argc和argv其功能在之前已经介绍过。从命令行输入,由寄存器%rdi和%rsi保存。
2.pirntf函数

         图3.16

hello.c中一共由两个printf函数,第一个printf的参数只有一个,所以在hello.s中被优化为puts函数(如图3.16)。在判断得知argc!=3之后,将.LC0字符串赋值给%rdi,作为puts的第一个参数,然后调用puts函数。对于第二个printf函数,共有三个参数,分别是.LC1,argv[1]和argv[2],编译器先用movq指令将对应的参数从内存中取出赋值给%rdi,%rsi 和%rdx,然后执行call printf调用printf函数。
3.exit函数
在hello.c文件中调用的是exit(1),参数为1,如图3.17所示,在调用exit函数之前,先将立即数1放入%edi中,作为参数传入函数exit中。

图3.17
4.sleep函数
有了之前几个函数调用的经验,我们知道在调用sleep之前需要事先将需要的参数传入相应的寄存器中。与之前的传参稍微有所不同的是,在这里先将参数传递给%eax,经过%eax才传递到%edi中,最终调用sleep函数(如图3.18)。

     图3.18

3.4 本章小结
本章主要介绍了编译器是如何处理C语言的各种数据类型和各种操作的,主要结合hello.c中的C语言代码与hello.s中的汇编语言代码进行研究和分析。在编译的过程中,编译器将高级语言编译生成了汇编语言(低级语言),作为机器语言01串的一个过渡。慢慢地将程序员可识别的语言转化为机器可识别的语言(还需要第三步汇编)。虽然是机器可识别,但是在计算机系统这门课程中,汇编语言是相当的重要,对于我们理解编程的底层实现、代码优化、代码效率等等方面都有一定的帮助。
(第3章2分)

第4章 汇编
4.1 汇编的概念与作用
汇编的概念:汇编器(as)将.s文件翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序的格式,并将结果保存在目标文件(这里是hello.o)中。
汇编的作用:将编译器产生的汇编语言进一步翻译为计算机可以理解的二进制机器语言,并产生.o文件。
4.2 在Ubuntu下汇编的命令
gcc -c hello.s -o hello.o

图4.1v
4.3可重定位目标elf格式
可重定位目标文件的格式:

     图4.1

分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
Readelf指令及运行结果如下:

图4.2
ELF头中的内容如下:

图4.3
以一个十六字节的序列Magic开始,Magic描述了生成该文件的系统的字节大小和字节顺序。ELF头剩下的部分包含帮助连接器语法分析和解释目标文件的信息,包括ELF头的大小、目标文件的类型(如可重定位、可执行或共享的)、机器类型、节头部表和文件偏移,以及节头部表中条目的大小和数量。不同节的位置和大小是由节头部表描述的,其中目标文件中每个节都有一个固定大小的条目。
节头的信息如下:
节头记录了每个节的特定信息,如节的大小、地址、偏移量。从.text节一直到.shstrtab节。

图4.4
重定位节:
一个.text 节中位置的列表,包含.text 节中需要进行重定位的信息,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。

图4.5
值得注意的是在记录信息的八个字节中,前四个字节记录了符号,后四个字节记录了重定位的类型(R_X86_64_PC32和R_X86_64_PLT32)。加数是一个有符号常数,一些重定位要使用它对被修改引用的值做偏移调整。
在接下来是符号表:

图4.6
符号表用来存放程序中定义和引用的函数和全局变量的信息。重定位需要引用的符号都在其中声明。name是字符串表中的字节偏移,指向符号的以null结尾的字符串名字,value是符号的地址,对于可重定位的模块来说,value是距定义目标的节的起始位置的偏移。对于可执行目标文件来说,该值是一个绝对运行时地址,size是目标的大小,type通常要么是数据,要么是函数,binding表示符号是本地的还是全局的。ABS代表不该被重定位的符号,UNDEF代表未定义的符号,也就是在本目标模块中引用,但是却在其他地方定义的符号,COMMON表示还未被分配位置的未初始化的数据目标。
4.4 Hello.o的结果解析
objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
指令及结果:

图4.7

说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。
通过反汇编的代码和hello.s进行比较,发现汇编语言的指令并没有什么不同的地方,只是反汇编代码所显示的不仅仅是汇编代码,还有机器代码,机器语言程序的是二进制机器指令的集合,是纯粹的二进制数据表示的语言,是电脑可以真正识别的语言。机器指令由操作码和操作数构成,汇编语言是人们比较熟悉的词句直接表述CPU动作形成的语言,是最接近CPU运行原理的语言。每一条汇编语言操作码都可以用机器二进制数据来表示,进而可以将所有的汇编语言(操作码和操作数)和二进制机器语言建立一一映射的关系,因此可以将汇编语言转化为机器语言,通过对机器代码的分析可以看出一下不同的地方。

(1)分支转移:反汇编的跳转指令用的不是段名称比如.L3,二是用的确定的地址,因为,因为段名称只是在汇编语言中便于编写的助记符,所以在汇编成机器语言之后显然不存在,而是确定的地址。

(2)函数调用:在.s 文件中,函数调用之后直接跟着函数名称,而在反汇编程 序中,call的目标地址是当前下一条指令。这是因为 hello.c 中调用的函数 都是共享库中的函数,最终需要通过动态链接器才能确定函数的运行时执 行地址,在汇编成为机器语言的时候,对于这些不确定地址的函数调用,将其call指令后的相对地址设置为全0(目标地址正是下一条指令),然后在.rela.text 节中为其添加重定位条目,等待静态链接的进一步确定。
4.5 本章小结
本章主要介绍了汇编的详细过程,从汇编的概念及作用开始,分析了ELF可重定位文件的结构内容和功能,将每一节的截图展示出来,并进行了相应的分析。最后对hello.o文件的内容进行了返汇编,并对其生成的hello.d文件进行着重的分析。
(第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文件头:hello的文件头和hello.o文件头的不同之处如下图标记所示,类型为EXEC表明hello是一个可执行目标文件,有12个节

节头部表Section Headers:Section Headers 对 hello中所有的节信息进行了声明,其 中包括大小 Size 以及在程序中的偏移量 Offset,因此根据 Section Headers 中的信息我们就可以用 HexEdit 定位各个节所占的区间(起始位置,大小)。其中 Address 是程序被载入到虚拟地址的起始地址。
图5.3

                         图5.4

5.4 hello的虚拟地址空间
打开edb查看ELF头的信息:

                         图5.5
之后我们可以找到.text节,位于虚拟内存的0x400550处。

图5.6
5.5 链接的重定位过程分析
先用objdump -d -r hello > hello1.d生成hello的反汇编文件。

图5.7
打开hello1.d文件内容如下:

                      图5.8

在反汇编生成的汇编代码方面,hello和hello.o完全相同,hello与hello.o两者的反汇编文件的唯一不同之处在于:地址由相对偏移变为了可以由CPU直接访问的虚拟地址,链接器把hello.o中的偏移量加上程序在虚拟内存中的起始地址0x0040000和.text节的偏移量就得到了hello1.d中的一个个地址。函数内的控制转移即jmp指令后的地址由偏移量变为了偏移量+函数的起始地址(虚拟地址);call后的地址由链接器执行重定位后计算出实际地址。
首先回顾之前的内容:汇编器识别出了main函数引用了一个全局符号,因此产生了一个重定位条目,来让链接器能够将该全局符号正确地重定位。hello.o中我们看到的16、18、21、32行就是四个重定位条目,它包含了对应符号的重定位类型以及addend的值。文件交给链接器后,链接器先进行符号解析,就是把代码中的每个符号引用和正好一个符号定义关联起来,然后就可以开始重定位了。重定位由两步组成:

  1. 重定位节和符号定义。在这一步中,链接器将所有相同类型的节合并为同一类型的新的聚合节。例如,来自所有输入模块的.data节被全部合并成一个节,这个节成为输出的而可执行目标文件的.data节。然后链接器将运行时内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号。当这一步完成时,程序中的每条指令和全局变量都由唯一的运行时内存地址了。
  2. 重定位节中的符号引用。在这一步中,链接器修改代码节和数据节中对每个符号的引用,使得它们指向正确的运行时地址。要执行这一步,链接器依赖于汇编器产生的可重定位条目。

5.6 hello的执行流程
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。

图5.9
5.7 Hello的动态链接分析
动态链接,在可执行文件装载时或运行时,由操作系统的装载程序加载库。大多数操作系统将解析外部引用(比如库)作为加载过程的一部分。在这些系统上,可执行文件包含一个叫做import directory的表,该表的每一项包含一个库的名字。根据表中记录的名字,装载程序在硬盘上搜索需要的库,然后将其加载到内存中预先不确定的位置,之后根据加载库后确定的库的地址更新可执行程序。可执行程序根据更新后的库信息调用库中的函数或引用库中的数据。这种类型的动态加载成为装载时加载 ,被包括Windows和Linux的大多数系统采用。
优点:
①更加节省内存并减少页面交换;
② DLL文件与EXE文件独立,只要输出接口不变(即名称、参数、返回值类型和调用约定不变),更换DLL文件不会对EXE文件造成任何影响,因而极大地提高了可维护性和可扩展性;
③不同编程语言编写的程序只要按照函数调用约定就可以调用同一个DLL函数;
④适用于大规模的软件开发,使开发过程独立、耦合度小,便于不同开发者和开发组织之间进行开发和测试。
缺点:
使用动态链接库的应用程序不是自完备的,它依赖的DLL模块也要存在,如果使用载入时动态链接,程序启动时发现DLL不存在,系统将终止程序并给出错误信息。而使用运行时动态链接,系统不会终止,但由于DLL中的导出函数不可用,程序会加载失败;速度比静态链接慢。当某个模块更新后,如果新模块与旧的模块不兼容,那么那些需要该模块才能运行的软件,统统撕掉。
接下来观察dl_init前后动态链接项目的变化。由图5.2可以得知.got.plt节的起始地址是0x601000,在DataDump中找到该位置。
dl_init前的.got.plt节:

图5.10
dl_init后的.got.plt节:

图5.11
5.8 本章小结
在本章中主要介绍了链接的概念与作用,并且详细阐述了hello.o是怎么链接成为一个可执行目标文件的过程,详细介绍了hello.o的ELF格式和各个节的含义,并且分析了hello的虚拟地址空间、重定位过程、执行流程、动态链接过程。经历了这一切后hello可执行程序正式生成。
(第5章1分)

第6章 hello进程管理
6.1 进程的概念与作用
进程是一个执行中的程序的实例,每一个进程都有它自己的地址空间,一般情况下,包括文本区域、数据区域、和堆栈。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储区着活动过程调用的指令和本地变量。
作用:进程为用户提供了以下假象:
(1) 我们的程序好像是系统中当前运行的唯一程序一样,我们的程序好像是独占的使用处理器和内存。
(2) 处理器好像是无间断的执行我们程序中的指令,我们程序中的代码和数据好像是系统内存中唯一的对象。
6.2 简述壳Shell-bash的作用与处理流程
shell俗称壳,是一种指"为使用者提供操作界面"的嵌入式软件(也被称为命令解析器)。软件提供了一种允许用户与其他操作系统之间进行通讯的一种方式。这种简单的通讯方式可以以交互方式(从键盘输入,并且用户可以立即地得到命令响应),或者以交互方式shellscript(非交互)的方式允许用户执行。shell(即壳)它是一个简单的命令解释器,它允许系统接收到一个用户的命令,然后自动调用相应的命令执行应用程序。
Shell 的处理流程:shell读取用户从终端使用外部设备输入(通常是键盘输入)的指令。解析所读取的指令,如果这个指令是一个内部指令则立即执行,否则,加载调用一个应用程序为申请的程序创建新的子进程,在子进程的上下文中运行。同时shell还允许接收从键盘读入的外部信号,(如:kill)并根据不同信号的功能进行对应的处理。
6.3 Hello的fork进程创建过程
(以下格式自行编排,编辑时删除)
用户在终端输入对应的指令(./hello),这时shell就会读取输入的命令,并开始进行以下操作:
第一步:判断hello不是一个内置的shell指令,所以调用应用程序,找到当前所在目录下的可执行文件hello,准备执行。
Shell会自动的调用fork()函数为父进程创建一个新的子进程,子进程就会因此得到与父进程(即shell)虚拟地址空间相同的一段各种的数据结构的副本(包括代码和数据段,堆,共享库和用户栈)。父进程与子进程最大的不同在于他们分别拥有不同的PID,父进程与子进程分别是两个并发的进程,在子进程中程序运行的这个过程中,父进程在原位置等待着程序的运行完毕。
6.4 Hello的execve过程
execve函数加载并运行可执行目标文件hello,且且包含相对应的一个带参数的列表argv和环境变量的列表exenvp,,只有当出现错误时,例如找不到hello文件时,execve才会返回-1到调用程序,execve调用成功则不会产生返回。
在shell调用fork函数之后,execve调用驻留在内存中的被称为启动加载器的操作系统代码来执行程序,使用启动加载器,子进程调用execve函数,在当前进程即子进程的上下文中加载新程序hello,这个程序覆盖当前正在执行的进程的所有的地址空间,但是这样的操作并没有创建一个新的进程,新的进程有和原先进程相同的PID,并且它还继承了打开hello调用execve函数之前所有已经打开的文件描述符。新的栈和堆段都会被初始化为零,新的代码和数据段被初始化为可执行文件中的内容。只有一些调用程序头部的信息才可能会在加载的过程中被从可执行磁盘复制到对应的可执行区域的内存。
6.5 Hello的进程执行
操作系统内核使用一中称为上下文切换 的较高层形式的异常控制流来实现多任务:内核为每个进程维持一个上下文,上下文就是内核重新启动一个被抢占的进程所需的状态,它由一些对象的值组成,这些对象包括通用目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构,比如描述地址空间的页表、包含有关当前进程信息的进程表,以及包含进程一打开文件的信息的文件表。 上下文切换的流程是: 1.保存当前进程的上下文。 2.恢复某个先前被抢占的进程被保存的上下文。 3.将控制传递给这个新恢复的进程 。

                 图6.1
为了使操作系统内核提供一个无懈可击的进程抽象,处理器必须提供一种机制,限制一个应用可以执行的指令以及它可以访问的地址空间范围。 处理器通常使用某个控制 寄存器的 一个 模式位提供两种模式的区分,该寄存器描述了进程当前享有的特权,当没有设置模式位时,进程就处于用户模式中,用户模式的进程不允许执行特权指令,也不允许直接引用地址空间中内核区内的代码和数据;设置模式位时,进程处于内核模式,该进程可以执行指令集中的任何命令,并且可以访问系统中的任何内存位置。

接下来分析 hello的进程 调度, hello在刚开始运行时内核为其保存一个上下文,进程在用户状态下运行。如果没有异常或中断信号的产生, hello将继续正常地执行。 如果有异常或系统中断,那么内核将启用调度器休眠当前进程,并在内核模式中完成上下文切换 ,将控制传递给其他进程。当 hello运行到 sleep(sleepsecs)时,hello显式地请求休眠,并发生上下文切换,控制转移给另一个进程,此时计时器开始计时,当计时器到达 2s时,它会产生一个中断信号,中断当前正在进行的进程,进行上下文切换,恢复 hello在休眠前的上下文信息,控制权回到 hello继续执行 。 当循环结束后, hello调用 getchar函数, 之前 hello运行在用户模式下,在调用 getchar时进入内核模式,内核中的 陷阱处理程序请求来自键盘缓冲区的 DMA传输 并执行上下文切换,并把控制转移给其他进程。当完成键盘缓冲区到内存的数据传输后,引发一个中断信号,此时内核从其他进程切换回 hello进程,然后 hello执行 return,进程终止。
6.6 hello的异常与信号处理
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
hello执行过程中可能出现四类异常:中断、陷阱、故障和终止。

  1. 中断是来自 I/O设备的信号,异步发生,中断处理程序对其进行处 理,返
    回后继续执行调用前待执行的下一条代码,就像没有发生过中断。
  2. 陷阱是有意的异常,是执行一条指令的结果,调用后也会返回到下一条指
    令,用来调用内核的服务进行操作。帮助程序从用户模式切换到内核模式。
  3. 故障是由错误情况引起的,它可能能够被故障处理程序修正。如果修正成
    功,则将控制返回到引起故障的指令,否则将终止程序。
  4. 终止是不可恢复的致命错误造成的结果,通常是一些硬件的错误,处理程
    序会将控制返回给一个 abort例程,该例程会终止这个应用程序。
    接下来对不同的输入和信号进行分析:

图6.2
可以看到在 hello运行过程中乱按键盘会在屏幕上显示出按的内容,但不会影响 hello的输出,在 hello的循环结束后调用 getchar函数,读入了一行输入,而之后的输入每一行都会被shell当作一个命令 ,可见在运行 hello的过程中从键盘的输入被缓存到了输入缓存区 。

图6.3
可以看到按下Ctrl+C可以将进程终止。

图6.4
在hello程序运行后按下Ctrl+Z,hello程序被挂起,可以通ps指令进行查看pid,通过jobs可以看到程序被挂起。

图6.5
用fg 1指令可以将后台的程序重新放到前台运行。

图6.6
用ps指令可以看到hello的PID是2750,再用kill -9指令可以杀死该进程。
6.7本章小结
本章引入了进程这一抽象概念,主要介绍了shell的运行,包含fork和execve函数的详细功能,以及hello程序执行的上下文切换、异常信号处理,以及一些可以发送信号的方法。
(第6章1分)

第7章 hello的存储管理
7.1 hello的存储器地址空间
结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。
逻辑地址:程序经过编译后出现在汇编代码中的地址。逻辑地址用来指定一个操作数或者是一条指令的地址。是由一个段标识符加上一个指定段内相对地址的偏移量,表示为 [段标识符:段内偏移量]。
线性地址:也叫虚拟地址,和逻辑地址类似,也是一个不真实的地址,如果逻辑地址是对应的硬件平台段式管理转换前地址的话,那么线性地址则对应了硬件也是内存的转换前地址。
虚拟地址:也就是线性地址。
物理地址:用于内存芯片级的单元寻址,与处理器和CPU链接的地址总线相对应。可以直接把物理地址理解成插在机器上那根内存本身,把内存看成一个从0字节一直到最大空量逐字节的编号的大数组,然后把这个数组叫做物理地址,但是事实上,这只是一个硬件提供给软件的抽像,内存的寻址方式并不是这样。所以,说它是“与地址总线相对应”,是更贴切一些,不过抛开对物理内存寻址方式的考虑,直接把物理地址与物理的内存一一对应,也是可以接受的。也许错误的理解更利于形而上的抽像。
7.2 Intel逻辑地址到线性地址的变换-段式管理
逻辑地址空间表示:段地址:偏移地址
段式管理有两种模式:
实模式:逻辑地址CS:EA=CS*16+EA物理地址
保护模式下:以段描述符作为小标,到对应的描述符表中得到段地址,此时的段基址+偏移地址=线性地址

基本概念
段寄存器(16位):用于存放段选择符
CS(代码段):程序代码所在段
SS(栈段):栈区所在段
DS(数据段):全局静态数据区所在段
其他三个段寄存器ES、GS和FS可指向任意数据段

段描述符:一种数据结构,等价于段表项,分为两类
用户的代码段和数据端描述符
系统控制段描述符

描述符表:实际上为段表,由段描述符(段表项构成)分为三种类型:
全局描述符表GDT:只有一个,用来存放系统内每个任务都可能访问的描述符,例如,内核代码段、内核数据段、用户代码段、用户数据段以及TSS(任务状态段)等都属于GDT中描述的段
局部描述符表LDT:存放某任务(即用户进程)专用的描述符
中断描述符表IDT:包含256个中断门、陷阱门和任务门描述符
段选择符中字段的含义:

图7.1
其中CS寄存器中的RPL字段表示CPU的当前特权级
TI=0,选择全局描述符表(GDT),TI=1,选择局部描述符表(LDT)
RPL=00为第0级,位于最高级的内核态;RPL=11为第3级,位于最低级的用户态
高13位-8K个索引用来确定当前使用的段描述符在描述符表中的位置
逻辑地址线性地址的转变过程:

图7.2
48位的逻辑地址分为32位的段内偏移量和16位的段选择符
首先根据段选择符的TI部分判断需要用到的段选择符表是全局描述符表还是局部描述符表
随后根据段选择符的高13位的索引(描述符表偏移)到对应的描述符表中找到对应的偏移量的段描述符,从中取出32位的段基址地址
将32位的段基址地址与32位的段内偏移量相加得到32位的线性地址
7.3 Hello的线性地址到物理地址的变换-页式管理
(以下格式自行编排,编辑时删除)
概念上而言,虚拟内存被组织为一个由存放在磁盘上的 N个连续的字节大小的单元组成的数组。每字节都有一个唯一的虚拟地址,作为到数组的索引。磁盘上数组的内容被缓存在主存中。和存储 器层次结构中其他缓存一样,磁盘(较低层)上的数据被分割成块,这些块作为磁盘和主存(较高层)之间的传输单元。 VM系统通过将虚拟内存分割位称为虚拟页的大小固定的块来处理这个问题。每个虚拟页的大小位 P = 2p字节。类似地,物理内存被分割为物理页,大小也为 P字节。虚拟页面地集合被分为三个不相交的子集已缓存、未缓存和未分配 。

图7.3

图7.4
虚拟地址分为两部分: 前一部分为虚拟页号,可以索引到当前进程的的物理页表地址,后一部分为虚拟页偏移量,将来可以直接作为物理页偏移量 ,页表是一个存放在物理内存中的数据结构,页表将虚拟页映射到物理页。每次地址翻译硬件将一个虚拟地址转换为物理地址时,都会读取页表
7.4 TLB与四级页表支持下的VA到PA的变换
如下图是二级页表的基本结构:

图7.5

图7.6
以Core i7 系统进行讨论
在Corei7 中48位虚拟地址分为36位的虚拟页号以及12位的页内偏移。
四级页表中包含了一个地址字段,它里面保存了40位的物理页号(PPN),这就要求物理页的大小要向4kb对齐

四级页表每个表中均含有512个条目,故计算四级页表对应区域如下:
第四级页表:每个条目对应4kb区域,共512个条目
第三级页表:每个条目对应4kb512=2MB区域,共512个条目
第二级页表:每个条目对应2MB
512 = 1GB区域,共512个条目
第一级页表:每个页表对应1GB*512 = 512GB区域,共512个条目
每个页表项PTE为64位8b大小,共512个条目,故每个页表的大小均为4kb,这样就做到了向4kb对齐的要求

如下是K级页表的地址翻译(当k为4时对应四级页表):

图7.7
7.5 三级Cache支持下的物理内存访问
在现代计算机中,存储器被组织成层次结构,因为这样可以最大程度地平衡访存时间和存储器成本。所以在CPU在访存时并不是直接访问内存,而是访问内存之前的三级cache。已知Core i7的三级cache是物理寻址的,块大小为64字节。LI和L2是8路组相联的,而L3是16路组相联的。Core i7实现支持48位虚拟地址空间和52位物理地址空间

图7.8
CO:缓冲块内的字节偏移量 CI:Cache索引 CT:Cache标记
从物理地址中取出对应的CO、CI、CT并根据CI找到cache对应的组,若有效位为1则命中,否则不命中。若命中则再判断CT是否一致,最后根据CO取出对应的内容。否则从依次从下一级缓存L2,L3或主存中取出对应的内容。找到内容后需要进行缓存块的替换,有很多种替换策略,这里不再赘述。
7.6 hello进程fork时的内存映射
Fork函数:
·为新进程创建虚拟内存
·创建当前进程的mm——struct,vm_area_struct和页表的原样副本
·两个进程中的每个页面都标记为只读
·两个进程中的每个区域结构(vm_area_struct)都标记为私有的写时复制
·在新进程中返回时,新进程拥有与调用fork进程相同的虚拟内存
·在随后若进行了写操作,则通过写时复制基址创建新页面,构造新的映射

调用Fork前:

             图7.9

调用Fork后:

              图7.10

私有的写shi 复制:

图7.11
7.7 hello进程execve时的内存映射
(以下格式自行编排,编辑时删除)
步骤:
·删除已存在的用户区域
·创建新的区域结构
·私有的,具有写时复制的特性
·代码和初始化的数据映射到.text和.data段
·.bss和堆栈映射到匿名文件,堆栈的初始长度为0
·共享对象由动态链接映射到本进程共享区域
·设置PC,指向代码区域的入口点

图7.12

7.8 缺页故障与缺页中断处理
(以下格式自行编排,编辑时删除)
缺页概念:
DRAM缓存不命中称为缺页,即虚拟内存中的字不在物理内存中CPU引用了虚拟页的一个字,地址翻译硬件从内存中读取了该虚拟页对应的页表条目,从有效位推断出该页未被缓存,这样就触发了 一个缺页异常,缺页异常调用内核中的缺页异常处理程序 ,该程序会选择一个牺牲页 ,把要缓存的页缓存到牺牲页的位置。 如果这个牺牲页被修改过,就把它交换出去 。 当缺页处理程序返回时,CPU 重新启动引起缺页的指令,这条指令再次发送 VA到 MMU,这次 MMU 就能正常翻译 VA了。

图7.13
7.9动态存储分配管理
动态内存分配器维护着一个进程的虚拟内存区域,称为堆。分配器将堆视为 一组不同大小的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是已 分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。
分配器分为两种基本风格:显式分配器、隐式分配器。
显式分配器:要求应用显式地释放任何已分配的块。
隐式分配器:要求分配器检测一个已分配块何时不再使用,那么就释放这个块,自动释放未使用的已经分配的块的过程叫做垃圾收集。
使用边界标签(头部、脚部)的堆块的格式:

每个块增加四字节的头部和四字节的脚部保存块大小和是否分配信息,可以在常数时间访问到每个块的下一个和前一个块,使空闲块的合并也变为常数时间,而且可以遍历整个链表。隐式空闲链表即为,利用边界标签区分已分配块和未分配块,根据不同的分配策略(首次适配、下一次适配、最佳适配),遍历整个链表,一旦找到符合要求的空闲块,就把它的已分配位设置为1,返回这个块的指针。隐式空闲链表并不是真正的链表,而是"隐式"地把空闲块连接了起来(中间夹杂着已分配块)。

     图7.14

7.10本章小结
本章知识点较为细碎,知识范围很广,包括hello的存储器地址空间、intel 的段式管理、hello 的页式管理,以intel Core7在指定环境下介绍了VA到PA的变换、物理内存访问,还介绍了hello进程fork时的内存映射、execve时的内存映射、缺页故障与缺页中断处理、动态存储分配管理等等,通过对这一章系统综合的学习对虚拟内存和内存管理系统的认知逐渐加深。
(第7章 2分)

第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件
文件类型:
·普通文件:包含任何数据,分两类
·文本文件:只含有ASCII码或Unicode字符的文件
·二进制文件:所有其他文件
·目录:包含一组链接的文件。每个链接都将一个文件名映射到一个文件
·套接字:用于与另一个进程进行跨网络通信的文件
·其余类型命名通道、符号链接等不予讨论
Linux将所有文件都组织为目录层次结构来进行管理,诶个位置均可以用相对路径或绝对路径来描述。

设备管理:unix io接口
所有的输入和输出都被当作对应文件的读和写来执行,利用Unix IO接口,这些输入输出都能用一种统一的方法来执行:
·打开文件:程序要求内核打开文件,内核返回一个小的非负整数(描述符),用于标识这个文件。程序在只要记录这个描述符便能记录打开文件的所有信息
·shell在进程的开始为其打开三个文件:标准输入、标准输出、标准错误
·改变当前文件的位置:程序可以通过接口显式改变文件位置(相对于文件开头字节的偏移量)
·读写文件:将n字节文件从文件位置k处开始复制,k=k+n;读文件时用k判断是否读完,读完触发EOF条件
·关闭文件:内核释放打开文件时创建的数据结构以及占用的内存资源,并将描述符恢复到可用的描述符池
8.2 简述Unix IO接口及其函数
打开文件:open函数,打开一个已存在的文件或者创建一个新文件
int open(char *filename,int flags,mode_t mode);
关闭文件:close函数,关闭一个已打开的文件
int close(int fd);
读:read函数,执行输入
写:write函数,执行输出
ssize_t read(inf fd,void *buf,size_t n );
ssize_t write(inf fd,const void *buf,size_t n );

8.3 printf的实现分析
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;
}
分析:
typedef char va_list ;
在C语言中函数参数是从右向左依次压入栈的,而fmt为一个字符指针,指向
fmt中的第一个参数,故可以得到arg指向的是输入…中的第一个参数
int vsprintf(char *buf, const char fmt, va_list args)
{
char
p;
char tmp[256];
va_list p_next_arg = args;
for (p=buf;*fmt;fmt++) {
if (*fmt != ‘%’) {
*p++ = *fmt;
continue;
}
fmt++;
switch (*fmt) {
case ‘x’:
itoa(tmp, ((int)p_next_arg));
strcpy(p, tmp);
p_next_arg += 4;
p += strlen(tmp);
break;
case ‘s’:
break;
default:
break;
}
}

return (p - buf);

}
功能:vsprintf的作用就是格式化。它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出。
剩下的write是Unix I/O接口函数,它用到了sys_call函数,用于进行系统函数调用,我们来看看sys_call的结构:
sys_call:
call save
push dword [p_proc_ready]
sti
push ecx
push ebx
call [sys_call_table + eax * 4]
add esp, 4 * 3
mov [esi + EAXREG - P_STACKBASE], eax
cli
ret
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。由此write函数显示一个已格式化的字符串
总体的过程就是定义一个256字节的缓冲区,printf读入用户参数并将arg指向第一个参数,随后从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall。最后利用调用系统级函数将buf缓冲区中的内容输出到终端上完成打印。

8.4 getchar的实现分析
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
当用户按键时,键盘接口会得到一个代表该按键的键盘扫描码,同时产生一个中断请求,中断请求抢占当前进程运行键盘中断子程序,键盘中断子程序先从键盘接口取得该按键的扫描码,然后将该按键扫描码转换成ASCII码,保存到系统的键盘缓冲区之中
8.5本章小结
本章主要介绍了各类IO,最主要的时Unix IO和标准IO,仔细探讨的过程中会发现,即使是小小的输入输出也有很多学问。
(第8章1分)
结论
做完整个大作业,毫无疑问即使是简简单单的hello程序的人生也是丰富多彩的。
1.经过预处理器cpp预处理,将#开头的行进行预处理得到hello.i
2.编译器cc1将hello.i编译生成hello.s文件
3.汇编器as又将得到的hello.s翻译成机器语言指令得到可重定位目标文件。
4.链接器ld将hello.o与动态链接库链接生成可执行文件hello
5.在shell中输入./hello 1190500106 周艺耕,为hello fork一个子进程,并调用execve函数来加载运行hello.
6.cpu为hello分配内存空间,hello从磁盘被加载到内存。
7.将虚拟地址翻译成为物理地址,通过L1 L2 L3三级cache访问内存
8.程序运行过程中任意时间段,若产生异常会发送信号,由异常处理信号进行处理。
9.标准IO帮助hello实现了输入输出
10.return,hello程序结束了自己灿烂的一生。
我的感悟:
学习计算机系统的经历无疑是刻骨铭心的,小到一个hello.c程序的一生,大到整个操作系统的安全运行,其中的道理真的不少,我只能通过一点点感知和学习来探寻这个框架,体会其中玄妙。系统的设计者竭尽所能的提升效率较低成本,将“抠门”的设计理念运用到极致,也为我们创造出了很多杰出的计算机系统设计模式和参考。
在做本次大作业的过程中,我也进行了很多探索。每一章节所有要求的操作都立完成并且截图上传在文档中,熟悉并掌握了各种Linux指令和工具,gdb、edb、objdump、readelf等等。尤其是在第二章预处理中,我探索了hello.i头文件目录的规律,一步一步追根溯源,在第五章链接的也将链接的全过程跟踪实现了一遍,理解深度大大加深。
(结论0分,缺失 -1分,根据内容酌情加分)

附件
列出所有的中间产物的文件名,并予以说明起作用。
(附件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分)

计算机系统大作业————程序人生相关推荐

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

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

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

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

  3. 2022计算机系统大作业——程序人生-Hello’s P2P

    计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 计算机 学    号 120L021716 班    级 2003005 学       生 蔡泽栋 指 导 ...

  4. 哈工大2022计算机系统大作业---程序人生

    计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 计算机类 学    号 120L021923 班    级 2003006 学       生 甄自镜 指 导 ...

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

    计算机系统 大作业 题          目程序人生-Hello's P2P 专          业 计算机科学与技术 学       号120L022401 班          级 200300 ...

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

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

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

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

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

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

  9. HIT计算机系统大作业-程序人生-Hello’s P2P

    计算机系统大作业 ** 由于采用静态部署,需要看图片详细分析的小伙伴请移步个人博客网站:** 个人博客 题目:程序人生-Hello's P2P 学号: 姓名:熊峰 摘要: hello程序作为最简单的. ...

  10. 哈工大 2021春 计算机系统 大作业程序人生

    计算机系统 大作业 题 目 程序人生-Hello's P2P 专 业 计算机 学 号 1190200828 班 级 1936601 学 生 赵英帅 指 导 教 师 刘宏伟 计算机科学与技术学院 202 ...

最新文章

  1. 4000个“不会数学”的程序员出现大反转!居然能学AI,玩算法,搞逻辑!背后原因首次曝光...
  2. android canvas 保存图片,ionic3 html5 video 抓图保存到手机相册,解决Android手机不支持Canvas drawImage问题...
  3. 学python用ubuntu还是win10_Windows 10上使用Ubuntu的优点
  4. 天地与我并存/万物与我为一 2
  5. printf的格式控制的完整格式
  6. python qt 按钮_PyQt(Python+Qt)学习随笔:toolButton的popupMode属性
  7. 安卓学习笔记:使用PopupWindow创建简单菜单
  8. CSS3质感分析——表面线性渐变
  9. 数据科学入门与实战:玩转pandas之六时间序列
  10. RestTemplate异常no suitable HttpMessageConverter found for request type [java.lang.Integer]
  11. 微型计算机是一种将CPU,Bwnhlq计算机一级考试选择题题库之微型计算机基础题及答案(XX年最新版)1.doc...
  12. Security log is full,only administrator can log on to fix the problem(安全日志满了)
  13. 8uftp目标计算机拒接,8UFTP连接FTP时空白的解决办法
  14. Django文档地址
  15. unity ps4 下碰到的坑
  16. 微信小程序--订单查询页面
  17. Redis教程–事务机制基本介绍
  18. Git与远程仓库进行关联设置
  19. 开发网页需要学什么?
  20. 高通平台SEE架构-sensor学习

热门文章

  1. 解释路径名:绝对,相对,UNC,URL
  2. 抖音拍摄技巧 甩布-2018如何做好视频伪原创
  3. 如何设置html字体
  4. 2021年塔式起重机司机找解析及塔式起重机司机模拟试题
  5. python中数组(numpy.array)的基本操作【转载】
  6. 逻辑回归实例:从疝气病预测病马的死亡率
  7. 响应式网页设计工具:Bootstrap Studio for Mac
  8. Adaboost介绍与使用(实战)
  9. 微信小程序SDK使用实例
  10. 算法题:150盏亮着的电灯,各有一个拉线开关控制,编号为1~150 将编号为1~150倍数的灯,依次拉一下 问:拉完后亮着的灯数为几盏? 哪盏灯被拉的最多?