大作业

题     目 程序人生-Hello’s P2P

专       业 计算机科学与技术

学     号 2021111719

班     级 2103101

学       生 杨济荣   

指 导 教 师 刘宏伟

计算机科学与技术学院

2022年5月

摘  要

本文主要通过观察hello.c程序在Linux系统下的生命周期,探讨hello.c源程序的预处理、编译、汇编、链接、生成可执行文件并运行的主要过程。同时结合课本中所学知识详细说明系统是如何实现对hello程序的进程管理,存储管理和I/O管理。通过对hello.c程序的生命周期的探索,让我们对可执行文件的生成和执行以及其它相关的计算机系统的知识有更深的理解。
关键词:预处理,编译,汇编,链接,加载,进程管理,存储管理,I/O管理

目  录

第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简介

(p2p)Hello程序最初以hello.c的文件存放在磁盘中,之后通过我们编译器驱动器(gcc)的编译下,首先进行预处理,生成hello.i文件,主要是展开头文件并将定义的宏赋值。然后经过编译步骤,生成汇编代码,也就是hello.s文件。之后通过汇编器(AS)对文件进行进一步转化,生成hello.o文件(可重定位目标文件)之后再通过链接器(LD)链接,生成可执行文件。 在shell中进入对应目录并输入./hello运行hello程序,shell会为这个程序fork子进程,此时由Program转变成了Process。

(020)文件的运行,首先在终端(Terminal)下,输入命令gcc hello.c,完成对上述文件的生成(中间文件不保留),得到hello.out文件,而后输入命令./hello.out,这时shell会创建一个子进程(fork()),将程序hello.out加载之内存中该子进程的上下文中(execve()),通过流水线化的CPU来处理该程序。在程序执行结束之后,盖子进程会变为一个僵死进程,发送一个信号(SIGCHLD)给shell,shell会来回收该子进程(将其从内存中删除),操作系统内核删除其相关信息,释放它所占用的内存空间。

1.2 环境与工具

X64 CPU;3.2GHz;27.9G RAM;954GHD Disk 以

Windows10 64位; Vmware 11;Ubuntu 20.04 LTS 64位

1.3 中间结果

hello.c

源程序

hello.i

预处理后的文本文件

hello.s

编译后的汇编文件

hello.o

汇编后的可重定位目标文件

hello.asm

hello.o得到的elf文件

hellooo.asm

hello得到的elf文件

hello.asm

hello.o得到的反汇编文件

hellooo.asm

hello得到的反汇编文件

hello

链接后的可执行目标文件

1.4 本章小结

本章我们对hello的一生做了分解和剖析,翻译了p2p和020在程序中的意思:从源程序到执行的P2P过程,再从执行到消亡的020大致过程。介绍了此作业的实验环境。

第2章 预处理

2.1 预处理的概念与作用

2.1.1概念:预处理是C语言的一个重要功能,它由预处理程序负责完成。当对一个源文件进行编译时,系统将自动引用预处理程序对源程序中的预处理部分作处理,处理完毕自动进入对源程序的编译。

预处理器(cpp)根据以字符#开头的命令,修改原始的c程序,比如hello.c第一行的#include<stdio.h> 命令告诉预处理器读取系统头文件stdio.h的内容,并把它直接插入程序文本中,结果就得到了另一个c程序,以.i作为文件扩展名

C语言提供多种预处理功能,主要处理#开始的预编译指令,如宏定义(#define)、文件包含(#include)、条件编译(#ifdef)等。

2.1.2作用:合理使用预处理功能编写的程序便于阅读、修改、移植和调试,也有利于模块化程序设计。

(1)宏定义(如 #define a b)

将a用b替换,#undef 后取消该定义,使其不再被替换

(2)文件包含

#include,将相应的头文件内容插入C语言代码中

(3)条件编译

定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件,将那些不必要的代码过滤掉。

2.2在Ubuntu下预处理的命令

图2.1 预处理命令                      图2.2 预处理结果

gcc hello.c -E -o hello.i

执行上述命令后,如图2.1可以看到,在文件夹中生成了hello.i文件。

2.3 Hello的预处理结果解析

图2.3文件结尾

图2.4更改变量命名              图2.5 头文件中函数

如图2.2,经过预处理后得到hello.i,源文件中的注释被删除,原本18行的hello.c代码扩展到了3060行。内容的增加包括引入了头文件中的函数结构体等内容、使用了具体的常量来替换宏定义,更改各种变量命名等等。

2.4 本章小结

本章介绍了预处理的概念以及作用(包括头文件的展开、宏替换、去掉注释、条件编译等),在ubuntu上输入命令进行测试,然后分析了hello.c到hello.i的过程和代码变化。

学习到了预处理的使用方法及好处:合理的使用预处理功能编写的程序便于阅读、修改、移植和调试,也有利于模块化程序设计。

第3章 编译

3.1 编译的概念与作用

3.1.1概念:c语言是对汇编语言的抽象,汇编语言是对硬件的抽象,编译过程是整个程序构建的核心部分,会将源代码由文本形式转换成机器语言,编译过程就是把预处理完的文件进行一系列词法分析、语法分析、语义分析以及优化后生成相应的.s汇编代码文件,文件内是汇编语言。

3.1.2作用:把源程序(高级语言)翻译成汇编语言。其包括以下几个步骤:

1、词法分析:词法分析是编译器的第一个步骤,它也被称为扫描。词法分析器通过读取源程序的字符流对其进行扫描,并且把它们组成有意义的词素(lexeme)序列,并产生词法单元(token) 作为输出,传递给语法分析。

词素是 token 的实例,词法分析器的主要任务就是从源程序中读取字符并产生 token。token 的结构为<token-name, attribute-value>,token-name代表分析的抽象符号,attribute-value指向符号表中该条目的位置。

符号表能够记录源程序中使用变量的名称,并收集和每个名称相关的属性信息,例如变量的存储分配、类型、作用域,对于过程方法还有参数类型、传递方法、返回类型。符号表为每个变量创建一个记录条目,编译器可以迅速查找到记录并对其进行存取。

2.语法分析:语法分析又称为 解析。语法分析器使用词法单元的第一个分量来创建树形的中间表示–语法树。树中的每个非叶节点都表示一个运算,其左右子节点表示运算的分量。编译器的后续步骤都会使用这个语法结构来帮助分析源程序,并声称目标程序。

3、中间代码:源程序的一种内部表示,或称中间语言。中间代码的作用是可使编译程序的结构在逻辑上更为简单明确,特别是可使目标代码的优化比较容易实现中间代码。

4、目标代码生成与优化:代码生成器将中间代码转成机器代码,这个过程是依赖于目标机器的,因为不同的机器有着不同的字长、寄存器、数据类型等。最后目标代码优化器对目标代码进行优化,比如选择合适的寻址方式、使用位移来代替乘除法、删除出多余的指令等。

图3.1 编译过程

3.2 在Ubuntu下编译的命令

图3.2 编译命令及生成文件

gcc -S hello.i -o hello.s

3.3 Hello的编译结果解析

3.3.1 声明部分

.file:声明源文件名称
.text:代码节
.rodata:只读代码段
.align:数据或者指令的地址对齐方式
.string:声明一个字符串

.global:声明全局变量
.type:声明一个符号是数据类型还是函数类型

3.3.2 字符串

作为print函数的参数,保存在只读数据段中

3.3.3 常量

4、0、9都是立即数,且0作为赋值,4、9用于比较,下面查看汇编语言

若等于则跳转,否则向下执行printf和exit

将rpb下移4字节,赋值int i等于0

与8比较,小于等于则进入循环。

3.3.4 变量

全局变量:储存在.data或.bss节中。初始化的全局变量储存在.data节,它的初始化不需要汇编语句,而是直接完成的。但本代码中没有全局变量。

局部变量:在main函数中定义了一个局部变量i作为计数器,上文已叙述过i的定义,i在每次循环后会加一,在下文的运算部分叙述。

还有传入main函数的两个参数argv数组和数组长度argc变量,通过寻找存储函数变量的寄存器rdi,rsi即可确定其在栈中的位置:

edi是第一个参数,即argc,rsi是argv*[]

3.3.4 赋值

      在汇编代码中主要为mov操作,根据需要传递的数据类型的不同选择 movq,movl,movb,movw等,分别对应8、4、2、1个字节。也有leaq操作可以实现赋值运算。

立即数赋值存到栈上,如i=0,寄存器赋值存到栈上,如参数的传递,这两部分上文都已经提及,下面再找一个不同类型的赋值进行叙述。

将在栈上的值赋值到寄存器上,不难推测这是一段地址,在下一段(%rax)寻址后将内存中的值赋值给%rdx。

3.3.5 运算

i作为循环计数器每次加1,之后存在原位置(第二个操作数是目的操作数)

rsp-32,用于开辟栈的空间

3.3.6 类型转换

C语言的atoi(argv[3]),将argv[3]字符串转换为整形数,在汇编语言中需要调用函数

3.3.7 关系操作

argc!=4和i< 8需要用到关系操作,即cmp指令

若等于则跳转,否则向下执行printf和exit

与8比较,小于等于则进入循环。

3.3.8 控制转移

控制转移与关系操作密切相连,往往紧接在关系操作之后。控制转移由cmpl和条件码来实现。Cmp操作的本质是S2-S1,根据得到的数设置条件码。如上文的两次比较,je代表在相等时执行跳转;jle,代表在小于等于时执行跳转。

还有一种特殊情况是jmp,直接进行跳转

3.3.9 数组

在这里定义了数组argv[]的首地址为-32(%rbp),将地址传递给寄存器%rax后通过增加%rax的值来实现对数组元素的访问:首先加16,指向argv[2],寻址取值后存入rdx,再恢复首地址后加8,指向argv[1],寻址取值后存入rsi。

3.3.10函数

调用函数时有以下操作:传递控制:进行过程Q的时候,程序计数器必须设置为Q的代码的起始地址,然后在返回时,要把程序计数器设置为P中调用Q后面那条指令的地址;传递数据:P能向Q参数,Q能向P中返回一个值;分配和释放内存:在开始时,Q可能需要为局部变量分配空间,而在返回前,又必须释放这些空间。

      (0)main函数:传入的argc存于%edi中,argv存储于%rsi中

图3.3 (1)(2)函数

  1. 将argv[3] 所指向的字符串地址存入rdi作为第一个参数,通过atoi函数转为数字。

(2)通过eax(rax)返回,并传给edi作为第一个参数,送入sleep函数。

图3.4 (3)(4)函数

(3)puts取出.lc0中存储的字符串直接进行打印

(4)将第一个参数设为1,调用exit终结程序。

(5)getchar没有参数,直接调用

(6)在这种情况为printf设置参数,即为两个%s字符串和格式

3.4 本章小结

本章主要讲述了编译阶段中编译器如何处理各种数据和操作,以及c语言中各种类型和操作所对应的汇编代码。通过理解这些编译器编译的机制,我们可以更容易的进行汇编语言和c语言的相互转换。

第4章 汇编

4.1 汇编的概念与作用

4.1.1概念:汇编实际上指把汇编语言代码翻译成目标机器指令的过程。生成可重定位目标程序。

4.1.2作用:用汇编语言编写的程序,机器不能直接识别,而汇编器(as)将hello.s 汇编程序翻译成机器语言指令,把这些指令打包成可重定位目标程序的格式,并将结果保存在hello.o目标文件中,hello.o文件是一个二进制文件,它包含程序的指令编码,机器就可以进行识别执行。

4.2 在Ubuntu下汇编的命令

图4.2 执行汇编的命令

gcc -c hello.s -o hello.o

4.3 可重定位目标elf格式

readelf hello.o -a 将信息打印

4.3.1 ELF Header

ELF头以一个16字节大小的Magic序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序,让操作系统和编译器辨别此文件。ELF头剩下的部分包含帮助链接器语法分析和解释目标文件的信息。其中包括 ELF 头的大小、目标文件的类型(如可重定位、可执行或者共享的,本示例中REL,即为可重定位)、机器类型(如 x86-64)、字节头部表的文件偏移,以及节头部表中条目的大小和数量等信息,可得知该文件有14个节。

图4.3  ELF头的信息

4.3.2 Section Headers

图4.4 section header信息

Section header描述不同节的位置和大小,包括节名称,节的类型,节的属性(读写权限),节在ELF文件中所占的长度,节的对齐方式以及节的偏移量。节信息无须复制到虚拟地址空间中为可执行文件创建的最终的进程映像。但该信息在二进制文件中总是存在的。

从上面可以看见该节头表包含14个节。每个节都指定了一个类型,定义了节数据的语义。如PROGBITS是程序必须解释的信息,例如二进制代码、SYMTAB是符号表;RELA是重定位信息。STRTAB用于存储于ELF格式有关的字符串,例如节的符号名称。

各节都指定了大小和在二进制文件内部的偏移量,address字段可用于指定节加载到虚拟地址空间中的位置。flag代表如何访问或处理。可以看到,代码段是可读可执行的,数据段和只读数据段都可读,但只读数据段不可写。

图4.5 flag各种标志的含义

4.3.3 Relocation Headers

图4.6 Relocation Headers的信息

重定位节.rela.text:一个.text节中位置的列表,保存.text节中需要修正的信息。一般来说,调用外部函数或者引用全局变量的指令都需要修改,如printf、exit、getchar等函数和rodata中的字符串。

重定位节.rela.data:被模块引用或定义的所有全局变量的重定位信息。

Offset:需要被修改的引用节的偏移信息,包括symbol和type两个部分,symbol为前面四个字节,type为后面四个字节。

Info:包含symbol和type两个信息。

Symbol:标识被修改引用应该指向的符号。

type:重定位的类型(相对还是绝对)告知链接器应该如何修改新的引用。

addend:一个有符号常数,一些重定位要使用它对被修改引用的值做偏移调整。

Sym.Name:重定向到的目标的名称。

4.3.3 .symtab: 存放在程序中定义和引用的函数和全局变量的信息,但它不包含局部变量的条目。

图4.7 符号表信息

name是符号名称,value是符号地址。对于可重定位目标模块,value是符号相对于目标节的起始位置偏移,对于可执行目标文件,该值是一个绝对运行时的地址。Size是目标的大小(以字节为单位),Type要么是数据、要么是函数。Bind字段表明符号是本地的还是全局的。

4.4 Hello.o的结果解析

hello.o的反汇编相较于hello.s在语法、语义上并没有太大差别,但是反汇编除了汇编语言外,还会显示机器代码。

机器指令由操作码和操作数构成,每一条汇编语言操作都可以用机器二进制数据来表示,所以可以将所有的汇编语言和二进制机器语言建立一一映射的关系。

操作数:hello.s中的操作数时十进制,hello.o反汇编代码中的操作数是十六进制。

分支转移:跳转语句之后,hello.s中是.L2和.LC1等段名称,而反汇编代码中跳转指令之后是相对偏移的地址,也即间接地址。而段名称只是在汇编语言中编写的助记符,不够直观体现程序执行时在内存中变化。

函数调用:在.s 文件中,函数调用之后直接跟着函数名称,而在反汇编程序中,call的目标地址是当前指令的下一条指令。因为函数只有在链接之后才能确定运行执行的地址,因此在.rela.text节中为其添加了重定位条目。等待静态链接的进一步确定。该代码函数前用PLT/PC32标记,说明是相对地址,则重定位后通过PC当前地址与相对地址引用计算有效地址。

图4.8 反汇编结果

4.5 本章小结

本章介绍了汇编的概念与作用。as汇编器将汇编语言转换为机器语言并得到了hello.o可重定位目标文件,通过readelf命令,我们剖析了hello.o中各个节存储的内容及其作用,并对比了反汇编后内容与.s文件区别,同时阅读机器指令和原汇编文件更加清晰了解汇编语言是如何翻译成机器代码的。

第5章 链接

5.1 链接的概念与作用

5.1.1概念:将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载(复制)到内存并执行。
5.1.2作用:链接使得分离编译成为可能。我们可以将一个大型应用程序分解为更小更好管理的模块,可以独立地修改和编译这些模块,节省了大量的工作空间。

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的格式

图5.2 得到hello的elf文件

5.3.1 ELF HeaderType(可执行文件)与节数(27)不同,偏移量也发生了变化,并且由于链接过程已经完成,因此这时程序入口已经确定:0X4010f0,并且比hello.o程序多了一个段表头,即.init节。节头表起始位置也变为14208B。

图5.3 ELF头

5.3.2 Sections Header节头表包含的节数有所增加,变为27个,而且我们发现,每一个节中有了实际地址,而不是像.o文件那样全为0

图5.4  节头部表

5.3.3 符号表:

   符号表包含编号Num、Value、Size、Type、Bind、Vis、Ndx、Name字段。依旧没有变,只不过value的值发生了改变。

图5.5  符号表

5.3.4 段头部表:

  

图 5.5 段头部表

段头表描述了可执行目标文件的连续的片与连续的虚拟内存段之间的映射关系。从段头表中可以看到根据可执行目标文件的内容初始化为两个内存段,分别为只读内存段(代码段)和读写代码段(数据段)。

5.3.5 重定位节

图 5.6 重定位

Offset:需要被修改的引用节的偏移信息,包括symbol和type两个部分,symbol为前面四个字节,type为后面四个字节。

Info:包含symbol和type两个信息。

Symbol:标识被修改引用应该指向的符号。

type:重定位的类型(相对还是绝对)告知链接器应该如何修改新的引用。

addend:一个有符号常数,一些重定位要使用它对被修改引用的值做偏移调整。

Sym.Name:重定向到的目标的名称。

5.4 hello的虚拟地址空间

图5.7 edb

我们已经知道在ELF头部表中,程序入口地址为0X004010f0

图5.8 程序入口

我们还可以看到任意一个节中地址所包含的信息,如.data(0X404048)节中:

图5.9 程序入口

5.5 链接的重定位过程分析

利用命令objdump -d -r hello > hello.asm得到了反汇编程序

图5.10 生成反汇编文件

5.5.1 不同:

比较二者的反汇编发现以下不同:

  1. hello.o的起始是0X00000000,而hello.out的起始地址为0X00401000
  2. hello中加入了许多其它函数的汇编代码,比如_init等(由节头部表知,.init节是初始化代码时执行的函数,而.plt则是动态链接的过程链接表)

图5.11 入口及init函数

  1. 关于调用函数,在hello.o中跳转指令的地址均为0X00000000,而在hello中,却有了真实的地址

5.5.2 重定位过程:

在汇编器生成一个模块时,汇编器并不能确定该模块在可执行文件中最终的位置,因为链接可能会打乱这些地址,因此,它会先将未知的地址全赋值为0,并生成一个重定位条目。

其中,offset是需要被修改的引用的节偏移。symbol标识被修改引用应该指向的符号。type告知链接器如何修改新的引用。addend是一个有符号常数,一些类型的重定位要使用它对被修改引用的值做偏移调整。
    ELF中定义了32中不同的重定位类型。其中比较常用的是R_X86_64_PC32和R_X86_64_32。前者重定位一个使用32位PC相对地址的引用,后者重定位一个使用32位绝对地址的引用。

通过查找资料,我们得到PC相对寻址代码如下:

图5.12 PC相对寻址

我们在反汇编代码中,看到了一个利用相对地址重定位类型:

它的类型是只读数据,重定位类型为R_X86_64_PC32,因此我们在rela.text节中找到它的重定位条目:

代入我们的公式进行计算:

*refptr=(unsigned)(ADDR(.rodata)+Addend-ADDR(main)-offset)

=(unsigned)(0x402008-4-0x401125-1c)

=0xec3

图5.13 验证计算结果

5.6 hello的执行流程

ld-2.31.so!_dl_start

0x7f8e7cc34ed0

ld-2.31.so!_dl_init

0x7f8e7cc486a0

hello!_start

0x4010f0

libc-2.31.so!_libc_start_main

0x7ff 825425fc0

libc-2.31.so!_cxa_atexit

0x7ff 825448f60

hello!_libc_csu_int

0x4011c0d0

libc-2.31.so!_setjmp

0x7ff 82fdb2e00

libc-2.27.so!exit

0x7ff 82fdc3bd0

表1 调用与跳转的各个子程序名和程序地址

5.7 Hello的动态链接分析

对于动态共享链接库中PIC函数,编译器没有办法预测函数的运行时地址,所以需要添加重定位记录,等待动态链接器处理,为避免运行时修改调用模块的代码段,链接器采用延迟绑定的策略。

上述延迟绑定是通过GOT和PLT实现。动态链接器使用过程链接表PLT+全局偏移量表GOT实现函数的动态链接,GOT中存放函数目标地址,PLT使用GOT中地址跳转到目标函数。

具体用法如下:GOT是数据段的一个数组,其中每个条目是8字节地址。和PLT联合使用时,GOT[O]和GOT[1]包含动态链接器在解析函数地址时会使用的信息。GOT[2]是动态链接器在1d-linux.so模块中的入口点。其余的每个条目对应于一个被调用的函数,其地址需要在运行时被解析。每个条目都有一个相匹配的PLT条目;而PLT是代码段的一个数组,其中每个条目是16字节代码。PLT[0]是一个特殊条目,它跳转到动态链接器中。每个被可执行程序调用的库函数都有它自己的PLT条目。每个条目都负责调用一个具体的函数。

用hello进行分析:首先找到GOT的起始地址为0x403ff0,然后在EDB中查看其值,发现在dl_init之前0x601008后的16个字节都是0。

调用前:

调用后:

图5.14 动态链接

从图中可以看出,在dl_init调用之后,该处的两个8字节的数据都发生了改变。
    和PLT联合使用时,GOT[0]和GOT[1]包含动态链接器在解析函数地址时会使用的信息。其中GOT[1]指向重定位表(依次为.plt节需要重定位的函数的运行时地址)用来确定调用的函数地址, GOT[2]是动态链接器ld-linux.so模块中的入口点。

5.8 本章小结

本章主要介绍了链接的概念和作用,详细介绍了hello.o是如何链接生成一个可执行文件的。并将可执行文件与.o文件反汇编内容进行了对比,同时展示了可执行文件中不同节的内容。最后分析了程序是如何实现的动态链接的。

第6章 hello进程管理

6.1 进程的概念与作用

6.1.1概念:进程的经典定义是一个执行中程序的实例,系统的每个程序都运行在某个进程的上下文。上下文是由程序正确运行所需的状态组成的,这个状态包括存放在内存里的程序的代码和数据,它的栈和通用目的寄存器的内容,程序计数器,环境变量以及打开文件描述符的集合。

6.1.2作用:向用户提供了一种假象:我们的程序好像是系统中当前运行的唯一程序,我们的程序好像是独占的使用处理器和内存。处理器好像是无间断的执行我们程序中的指令,我们程序中的代码和数据好像是系统内存中唯一的对象。

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

Linux系统中,Shell是一个交互型应用级程序,代表用户运行其他程序(是命令行解释器,以用户态方式运行的终端进程)。

其基本功能是解释并运行用户的指令,重复如下处理过程:

(1)终端进程读取用户由键盘输入的命令行。

(2)分析命令行字符串,获取命令行参数,并构造传递给execve的argv向量

(3)检查第一个(首个、第0个)命令行参数是否是一个内置的shell命令

(4)如果不是内部命令,调用fork( )创建新进程/子进程

(5)在子进程中,用步骤2获取的参数,调用execve( )执行指定程序。

(6)如果用户没要求后台运行(命令末尾没有&号)否则shell使用waitpid(或wait)等待作业终止后返回。

(7)如果用户要求后台运行(如果命令末尾有&号),则shell返回。

6.3 Hello的fork进程创建过程

当在shell上输入./hello命令时,命令行会首先判断该命令是否为内置命令,如果是内置命令则立即对其进行解释。否则将其看成一个可执行目标文件,再调用fork创建一个新进程并在其中执行。

当shell运行一个程序时,父进程通过fork函数生成这个程序的进程。新创建的子进程几乎但不完全与父进程相同,包括代码、数据段、堆、共享库以及用户栈。父进程和新创建的子进程之间最大的区别在于他们有不同的PID。父进程与子进程是并发运行的独立进程,内核能够以任意方式交替执行它们的逻辑控制流的指令。

6.4 Hello的execve过程

execve函数加载并运行可执行目标文件filename,且带参数列表argv和环境变量列表envp。只有当出现错误时,例如找不到filename,execve才会返回到调用程序。加载并运行需要以下几个步骤:

  1. 删除已存在的用户区域。删除当前进程虚拟地址的用户部分中已存在的区域结构。
  2. 映射私有区域。为新程序的代码、数据、bss和栈区域创建新的区域结构。所有这些区域结构都是私有的,写时复制的。虚拟地址空间的代码和数据区域被映射为hello文件的.txt和.data区。bss区域是请求二进制零的,映射匿名文件,其大小包含在hello文件中。栈和堆区域也是请求二进制零的,初始长度为零。
  3. 映射共享区域。如果hello程序与共享对象链接,比如标准C库libc.so,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域。
  4. 设置程序计数器(PC)。exceve做的最后一件事就是设置当前进程的上下文中的程序计数器,使之指向代码区域的入口点。下一次调用这个进程时,它将从这个入口点开始执行。Linux将根据需要换入代码和数据页面。

图6.1内存映像

6.5 Hello的进程执行

6.5.1上下文信息

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

一个进程执行它的控制流的一部分的每一时间段叫做时间片。
6.5.3用户态和内核态

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

当一个进程正在执行时,内核调度了另一个新的进程运行后,它就抢占当前进程,并使用一种称为上下文切换的机制来将控制转移到新的进程。在进行上下文切换时,需要保存以前进程的上下文,恢复新恢复进程被保存的上下文,将控制传递给这个新恢复的进程来完成上下文切换。

6.5.5 hello的进程执行过程如下:
    hello初始运行在用户模式,在hello进程调用sleep之后陷入内核模式,内核处理休眠请求主动释放当前进程以加载新的进程进行执行。同时将hello进程从运行队列中移入到等待队列,定时器开始计时,内核进行上下文切换将当前进程的控制权交给其他进程并执行,当定时器到时时发送一个中断信号,此时进入内核状态执行中断处理,将hello进程从等待队列中移出重新加入到运行队列,成为就绪状态,hello进程就可以继续进行自己的控制逻辑流了。

图6.2 进程切换

6.6 hello的异常与信号处理

异常分为如下四类:

图6.3 异常分类

在hello程序执行过程中这几类异常都可能出现。当出现异常时,操作系统会根据异常表进行一个间接过程调用,找到异常对应的异常处理程序。当异常处理程序完成处理后,根据引起异常的事件的类型,会发生以下3种情况中的一种:
    1.处理程序将控制返回给当前指令,即当事件发生时正在执行的指令。
    2.处理程序将控制返回给当前指令的下一条指令,即如果没有异常发生将会执行的下一条指令。
    3.处理程序终止被中断的程序。

首先我们输入一些./hello,在输入回车之前输入的所有字符,都会被当作argv[]来存储,最终在输入回车后输出:

图6.4 正常执行

图6.5 不停乱按

6.6.1不停乱按可以发现,乱按只是将屏幕的输入缓存起来,等待执行完毕后,一个’\n’结尾的字符串就作为一次输入然后继续执行。

6.6.2 Ctrl-C

可以看到,Ctrl-C后直接终止运行。进程收到信号SIGINT。

6.6.3 Ctrl-Z会发送一个SIGSTP信号,使得进程停止/挂起

图6.6 Ctrl-Z效果及在此状态下使用jobs、ps、fg命令

图6.7 pstree效果图

图6.8 kill终止进程后再次使用ps和fg

按下jobs进行观察,发现有一个暂停的进程,调用ps指令查看后台进程,fg可以将hello调到前台。

pstree以树状图查看进程间关系。

Kill命令终止进程后再次查看ps发现hello已经消失,fg也无法将hello调到前台。

6.7本章小结

本章介绍了进程的概念和作用,同时介绍了Shell的一般处理过程和作用。分析了fork和execve函数的功能,叙述了hello进程的执行以及hello的异常和信号处理的过程并进行测试。

第7章 hello的存储管理

7.1 hello的存储器地址空间

逻辑地址:程序经过编译后出现在汇编代码中的地址。逻辑地址用来指定一个操作数或者是一条指令的地址。是由一个段标识符加上一个指定段内相对地址的偏移量,表示为 [段标识符:段内偏移量]。
   线性地址:也叫虚拟地址,和逻辑地址类似,也是一个不真实的地址,如果逻辑地址是对应的硬件平台段式管理转换前地址的话,那么线性地址则对应了硬件  也是内存的转换前地址。
   虚拟地址:也就是线性地址。
   物理地址:用于内存芯片级的单元寻址,与处理器和CPU链接的地址总线相对应。可以直接把物理地址理解成插在机器上那根内存本身,把内存看成一个从0字节一直到最大空量逐字节的编号的大数组,然后把这个数组叫做物理地址。

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

一个逻辑地址由两部分组成,段标识符,段内偏移量。段标识符是一个16位长的字段组成,称为段选择符,其中前13位是一个索引号。后面三位包含一些硬件细节。
    索引号就是段描述符的索引。段描述符具体描述了一个段地址,这样,很多段描述符就组成段描述符表。可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符。
    Base字段,表示的是包含段的首字节的线性地址,也就是一个段的开始位置的线性地址。
    一些全局的段描述符,就放在“全局段描述符表(GDT)”中,一些局部的,例如每个进程自己的,就放在所谓的“局部段描述符表(LDT)”中。GDT在内存中的地址和大小存放在CPU的gdtr控制寄存器中,而LDT则在ldtr寄存器中。
   首先,给定一个完整的逻辑地址[段选择符:段内偏移地址],看段选择符的T1=0还是1,知道当前要转换是GDT中的段,还是LDT中的段,再根据相应寄存器,得到其地址和大小。我们就有了一个数组了。
   拿出段选择符中前13位,可以在这个数组中,查找到对应的段描述符,这样,它了Base,即基地址就知道了。把Base + offset,就是要转换的线性地址了。

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

计算机利用页表,通过MMU来完成从虚拟地址到物理地址的转换。线性地址即虚拟地址,用VA来表示。VA被分为虚拟页号(VPN)与虚拟页偏移量(VPO),CPU取出虚拟页号,通过页表基址寄存器来定位页表条目,在有效位为1时,从页表条目中取出信息物理页号(PPN),通过将物理页号与虚拟页偏移量(VPO)结合,得到由物理地址(PPN)和物理页偏移量(PPO)组合的物理地址。

具体过程如下图:

图7.1 使用页表的地址翻译

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

每次CPU产生一个虚拟地址,MMU(内存管理单元)就必须查阅一个PTE(页表条目),以便将虚拟地址翻译为物理地址。在最糟糕的情况下,这会从内存多取一次数据,代价是几十到几百个周期。如果PTE碰巧缓存在L1中,那么开销就会下降1或2个周期。然而,许多系统都试图消除即使是这样的开销,它们在MMU中包括了一个关于PTE的小的缓存,称为翻译后备缓存器(TLB)。TLB是一个小的、虚拟寻址的缓存,其中每一行都保存着一个由单一PTE组成的块。TLB通常有高的相联度。

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

如果采用多级页表策略,例如四级页表层次结构,每个四级页表进程都有他自己私有的页表层次结构,这种设计方法从两个基本方面就是减少了对内存的需求,如果一级页表的PTE全部为空,那么二级页表就不会继续存在,从而为进程节省了大量的内存,而且也只有一级页表才会有需要总是在一个内存中。四级页表的层次结构操作流程如下:36位虚拟地址被寄存器划分组成四个9位的片,每个片被寄存器用作一个页表中偏移量。CR3寄存器内储存了一个L1页表的一个物理起始基地址,指向第一级页表的一个起始和最终位置,这个地址是页表上下文的一部分信息。VPN1提供了到一个L1PTE的偏移量,这个PTE寄存器包含一个L2页表的起始基地址。VPN2则提供了到一个L2PTE的偏移量,一共四级,逐级以此层次类推。

7.6 hello进程fork时的内存映射

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

7.7 hello进程execve时的内存映射

1.删除已存在的用户区域。删除当前进程虚拟地址的用户部分中已存在的区域结构。
    2.映射私有区域。为新程序的代码、数据、bss和栈区域创建新的区域结构。所有这些区域结构都是私有的,写时复制的。虚拟地址空间的代码和数据区域被映射为hello文件的.txt和.data区。bss区域是请求二进制零的,映射匿名文件,其大小包含在hello文件中。栈和堆区域也是请求二进制零的,初始长度为零。
    3.映射共享区域。如果hello程序与共享对象链接,比如标准C库libc.so,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域。
    4.设置程序计数器(PC)。exceve做的最后一件事就是设置当前进程的上下文中的程序计数器,使之指向代码区域的入口点。下一次调用这个进程时,它将从这个入口点开始执行。Linux将根据需要换入代码和数据页面。

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

缺页故障是指用户想要写内存地址(虚拟地址)时该地址对应的物理页不在内存,而是在磁盘中的一种可恢复的故障。这时,系统调用缺页处理程序,选择牺牲页面。具体步骤为:

1)CPU为MMU生成并传送一个虚拟地址。

2)地址管理单元生成PTE,高速缓存或主存向MMU传送PTE。

3)PTE的有效位0触发一次MMU异常,传递CPU的控制信号到操作系统内核中执行缺页异常处理程序。

4)处理程序决定物理内存中的牺牲页,如果这个页是非空的就把它换到磁盘。

5)处理程序调入新页面并更新主存中的PTE。

6)处理程序返回,程序继续执行触发缺页异常的那条指令。

7.9动态存储分配管理

动态内存分配器维护者一个进程的虚拟内存区域,称为堆。(如图7.9.1所示),分配器将堆视为一组不同的大小的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。分配器有两种基本风格。两种风格都是要求显示的释放分配块。
   (1) 显式分配器:要求应用显示的释放任何已分配的块。例如C标准库提供一个叫做malloc程序包的显示分配器。
   (2) 隐式分配器:要求分配器检测一个已分配块何时不再被程序使用,那么就释放这个块。隐式分配器也叫垃圾收集器。

7.10本章小结

本章主要介绍了有关内存管理的知识。详细阐述了hello程序是如何存储,如何经过地址翻译得到最终的物理地址。介绍了hello的四级页表的虚拟地址空间到物理地址的转换。阐述了三级cache的物理内存访问、进程 fork 时的内存映射、execve 时的内存映射、缺页故障与缺页中断处理、动态存储分配管理。

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

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

设备的模型化:文件

设备管理:unix io接口

8.2 简述Unix IO接口及其函数

8.2.1 Unix io 接口:

1.打开文件。一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个 I/O 设备,内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件,内核记录有关这个打开文件的所有信息。

2.Shell 创建的每个进程都有三个打开的文件:标准输入,标准输出,标准错误。

3.改变当前的文件位置:对于每个打开的文件,内核保持着一个文件位置 k,初始为 0,这个文件位置是从文件开头起始的字节偏移量,应用程序能够通过执行 seek,显式地将改变当前文件位置 k。

4.读写文件:一个读操作就是从文件复制 n>0 个字节到内存,从当前文件位置 k 开始,然后将 k 增加到 k+n,给定一个大小为 m 字节的而文件,当 k>=m 时,触发EOF。类似一个写操作就是从内存中复制 n>0 个字节到一个文件,从当前文件位置 k 开始,然后更新 k。

5.关闭文件,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中去。

8.2.2 Unix io 函数:

1.int open(char* filename,int flags,mode_t mode) ,进程通过调用 open 函 数来打开一个存在的文件或是创建一个新文件的。open函数将filename 转换为一个文件描述符,并且返回描述符数字,返回的描述符总是在进程中当前没有打开的最小描述符,flags 参数指明了进程打算如何访问这个文件,mode参数指定了新文件的访问权限位。
    2.int close(fd),fd 是需要关闭的文件的描述符,close 返回操作结果。
    3.ssize_t read(int fd,void *buf,size_t n),read 函数从描述符为fd的当前文件位置赋值最多n个字节到内存位置 buf。返回值-1 表示一个错误,0表示EOF,否则返回值表示的是实际传送的字节数量。
    4.ssize_t wirte(int fd,const void *buf,size_t n),write 函数从内存位置buf复制至多n个字节到描述符为fd的当前文件位置。

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;

}

printf程序按照格式fmt结合参数args生成格式化之后的字符串,并返回字串的长度。printf用了两个外部函数,一个是vsprintf,还有一个是write。
vsprintf函数作用是接受确定输出格式的格式字符串fmt(输入)。用格式字符串对个数变化的参数进行格式化,产生格式化输出。
write函数将buf中的i个元素写到终端。
    从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本章小结

本章主要介绍了Linux的IO设备管理方法、Unix IO接口及其函数,分析了printf和getchar函数的实现。

结论:

一、用计算机系统的语言,逐条总结hello所经历的过程。

1.首先编写hello.c的源程序,从此hello.c诞生。

2.对hello.c进行预处理(gcc -E),hello.c变成了hello.i。为文本文件。
3.对hello.i进行编译处理(gcc -S),hello.i变成了hello.s。为汇编语言
4.对hello.s进行汇编处理(gcc -c),hello.s变成了hello.o。此时hello变成了二进制文件。
5.对hello.o进行链接处理,将其与其它可重定位目标文件以及动态链接库进行链接生成可执行目标文件hello。此时hello程序就可以在计算机上执行。
6.在shell命令行上输入./hello 2021111719 杨济荣 5来运行hello程序。
7.shell首先判断输入命令是否为内置命令。经过检查后发现其不是内置命令,则shell将其当作程序执行。
8.shell调用fork函数创建一个子进程。
9.shell调用execve函数,execve函数会将新创建的子进程的区域结构删除,然后将其映射到hello程序的虚拟内存,然后设置当前进程上下文中的程序计数器,使其指向hello程序的入口点。
10.运行hello时,内存管理单元MMU、翻译后备缓冲器TLB、多级页表机制、三级cache协同工作,完成对地址的翻译和请求。
11.execve函数并未将hello程序实际加载到内存中。当CPU开始执行hello程序对其进行取指时,发现其对应的页面不在内存中。此时会出现缺页故障这一异常,操作系统通过异常表对缺页处理程序进行间接调用,缺页处理程序实现虚拟内存和物理内存的映射,将缺失的页面加载到内存中。此时CPU重新执行引起故障的指令,指令可以正常执行,程序从而继续向下执行。
12.当hello程序调用sleep函数后进程休眠进入停止状态。而CPU不会等待hello程序休眠结束,而是通过内核进行上下文切换将当前进程的控制权转移到其它进程。当sleep函数调用完成后,内核再次进行上下文切换重新执行hello进程。
13.当hello程序执行printf函数时,会调用malloc函数从堆中申请内存。
14.在hello进程执行时,当在命令行中输入Ctrl-C时,shell会向前台作业发送SIGINT信号,该信号会终止前台作业,即hello程序终止执行。当输入Ctrl-Z时,shell会向前台作业发送SIGTSTP信号,该信号会挂起当前进程,即hello程序停止执行,之后再向其发送SIGCONT信号时,hello程序会继续执行。
15.当hello进程执行完成后,父进程会对子进程进行回收。内核删除为这个进程创建的所有数据结构。

二、你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
        计算机系统是由硬件和系统软件组成的,它们共同工作来运行应用程序。抽象是计算机系统设计与实现的重要基础:文件是对I/O设备的抽象,虚拟内存是对程序存储器的抽象,进程是对一个正在运行的程序的抽象,虚拟机是对整个计算机的抽象。
        计算机的工作效率是评价其好坏的一个重要的方面。为了提高计算机的工作效率,解决不同设备之间速度差异大的问题,利用缓存的机制设计了高速缓存来作为更底层的设备的缓存,如cache就是设计为主存的缓存,快表设计为虚拟内存页表的缓存等等。

附件

hello.c

源程序

hello.i

预处理后的文本文件

hello.s

编译后的汇编文件

hello.o

汇编后的可重定位目标文件

hello.asm

hello.o得到的elf文件

hellooo.asm

hello得到的elf文件

hello.asm

hello.o得到的反汇编文件

hellooo.asm

hello得到的反汇编文件

hello

链接后的可执行目标文件

参考文献

[1]  深入理解计算机系统

哈工大csapp大作业程序人生相关推荐

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

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

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

    哈工大计算机系统大作业 摘要 第1章 概述 1.1 Hello简介 1.2 环境与工具 1.3 中间结果 1.4 本章小结 第2章 预处理 2.1 预处理的概念与作用 2.2 在Ubuntu下预处理的 ...

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

    2022哈工大计算机系统大作业 目录 摘 要 第1章 概述 1.1 Hello简介 1.2 环境与工具 1.3 中间结果 1.4 本章小结 第2章 预处理 2.1 预处理的概念与作用 2.2在Ubun ...

  4. 哈工大2022春CSAPP大作业-程序人生(Hello‘s P2P)

    摘  要 本论文研究了hello.c这一简单c语言文件在Linux系统下的整个生命周期,以其原始程序开始,依次深入研究了编译.链接.加载.运行.终止.回收的过程,从而了解hello.c文件的" ...

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

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

  6. 2022春哈工大ICS大作业——程序人生-hello‘sP2P

    计算机系统(初发布于2022/5/15,修改于2022/5/20) 大作业 题     目 程序人生-Hello's P2P 专       业 学    号 班    级 学       生 沈业力 ...

  7. 哈工大 计算机系统大作业 程序人生-Hello’s P2P From Program to Process

    计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 计算学部 学    号 120L020512 班    级 2003004 学       生 黄鹏程 指 导 ...

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

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

  9. 哈工大计算机系统大作业: 程序人生-Hello’s P2P/ hello 的一生

    计算机系统 大作业 题 目 程序人生-Hello's P2P 专 业 计算学部 学 号 1190201801 班 级 1903012 学 生 耿健 指 导 教 师 史先俊 计算机科学与技术学院 202 ...

最新文章

  1. python2与python3转换记录
  2. 手机QQ重构移动社交市场
  3. 理解原理的重要性 - 论PostgreSQL merge join 成本评估陷阱 含case
  4. waf可以检测哪个端口的流量_锐速云:CC防御过程中,WAF的主要特点有哪些?
  5. 【求助】windows server 2003 64位下面网卡IP总是默认为动态分派IP
  6. 用gson得JSON,数值变为double类型 ,去掉double值中的小数位(3.0改为3)
  7. Linux Shell命令能力倾向问题和解答
  8. list删除重复元素
  9. ios 视图切换翻页效果
  10. C语言杨辉三角形代码
  11. 蓝牙耳机买什么好?2021值得入手的蓝牙耳机推荐
  12. 华为qq邮箱服务器密码忘了,华为手机qq邮箱无法登录电子邮件鉴权失败的解决办法...
  13. EAccessViolation型错误
  14. 影响到网站收录有哪些方面呢
  15. exit code非0什么意思C语言,c++程序出现exit code -1073740940 (0xC0000374)
  16. 谷歌chrome浏览器设置成深色(护眼)模式
  17. linux 原子类型变量,linux 原子变量 - osc_m4jd02jn的个人空间 - OSCHINA - 中文开源技术交流社区...
  18. 7 Workflow
  19. 阿里“无影” | 未来每个人在云上都会有自己的主机
  20. 超高频RFID涉密资产智能档案柜应用

热门文章

  1. 42所双一流高校,都是如何处理学术不端行为的?
  2. 克服神经网络中的灾难性遗忘(EWC):Overcoming catastrophic forgetting inneural networks
  3. 工信部于佳宁:区块链要服务实体经济
  4. 推广网店的12个秘诀
  5. 【UML建模】(5) UML建模之活动图
  6. javaweb指导书
  7. [整站源码]thinkphp家纺针织床上用品类网站模板+前后端源码
  8. java对象实例化内存布局与访问定位
  9. gateway网关调用报 reactor.netty.http.client.HttpClient.chunkedTransfer(Z)Lreactor/netty/http/client/HttpC
  10. 2021-05-24 昨日三省吾身