计算机系统

大作业

题     目 程序人生-Hellos P2P

专       业 计算机类

学     号 120L021305

班   级 2003002

学       生 李一凡

指 导 教 师 史先俊

计算机科学与技术学院

2022年5月

摘  要

本文从编写开始,完整详细地深入了解hello.c程序从预处理、编译、汇编、连接一直到可执行文件诞生的全过程,分析其背后的软硬件知识如Bash与OS对hello的进程管理、存储管理等。通过hello程序的“人生”,对整个计算机系统有了更深入的理解。

关键词:计算机系统,hello.c,编译,汇编,进程,虚拟内存;

目  录

第1章 概述 - 4 -

1.1 Hello简介 - 4 -

1.2 环境与工具 - 4 -

1.3 中间结果 - 4 -

1.4 本章小结 - 5 -

第2章 预处理 - 6 -

2.1 预处理的概念与作用 - 6 -

2.2在Ubuntu下预处理的命令 - 6 -

2.3 Hello的预处理结果解析 - 7 -

2.4 本章小结 - 8 -

第3章 编译 - 9 -

3.1 编译的概念与作用 - 9 -

3.2 在Ubuntu下编译的命令 - 9 -

3.3 Hello的编译结果解析 - 9 -

3.4 本章小结 - 12 -

第4章 汇编 - 13 -

4.1 汇编的概念与作用 - 13 -

4.2 在Ubuntu下汇编的命令 - 13 -

4.3 可重定位目标elf格式 - 13 -

4.4 Hello.o的结果解析 - 17 -

4.5 本章小结 - 18 -

第5章 链接 - 19 -

5.1 链接的概念与作用 - 19 -

5.2 在Ubuntu下链接的命令 - 19 -

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

5.4 hello的虚拟地址空间 - 23 -

5.5 链接的重定位过程分析 - 24 -

5.6 hello的执行流程 - 26 -

5.7 Hello的动态链接分析 - 27 -

5.8 本章小结 - 28 -

第6章 hello进程管理 - 29 -

6.1 进程的概念与作用 - 29 -

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

6.3 Hello的fork进程创建过程 - 29 -

6.4 Hello的execve过程 - 29 -

6.5 Hello的进程执行 - 30 -

6.6 hello的异常与信号处理 - 31 -

6.7本章小结 - 36 -

第7章 hello的存储管理 - 37 -

7.1 hello的存储器地址空间 - 37 -

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

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

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

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

7.6 hello进程fork时的内存映射 - 40 -

7.7 hello进程execve时的内存映射 - 40 -

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

7.9动态存储分配管理 - 41 -

7.10本章小结 - 42 -

第8章 hello的IO管理 - 43 -

8.1 Linux的IO设备管理方法 - 43 -

8.2 简述Unix IO接口及其函数 - 43 -

8.3 printf的实现分析 - 44 -

8.4 getchar的实现分析 - 45 -

8.5本章小结 - 46 -

结论 - 47 -

附件 - 48 -

参考文献 - 49 -

第1章 概述

1.1 Hello简介

(1)P2P:From Program to Process的缩写,即从程序到进程的转换过程。该过程中hello.c文件首先经历来自cpp的预处理,实现文本替换功能产生hello.i文件。然后再经历ccl的编译,转换成汇编代码hello.s文件,此时已贴近计算机硬件。接着汇编代码经过as的翻译称为一定格式的机器码hello.o文件。最后经过ld的链接形成可执行文件hello。在shell中输入启动命令后,shell为其fork产生一个子进程,然后hello便从程序变为了进程。

(2)020:From Zero to Zero的缩写,即进程从初始到回收的过程。该过程中shell为hello进程execve、mmap,execve时hello会被分配一个虚拟内存,对应着一个物理内存,里面存放着程序运行需要的信息。CPU为运行的hello分配时间片执行逻辑控制流,I/O管理与信号处理软硬结合,然后hello程序开始运行,进行一个又一个process。最终hello程序返回,被shell回收,对应内存和虚拟内存随即释放,内核删除相关数据结构,结束。

1.2 环境与工具

列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。

硬件环境:X64 CPU;2GHz;2G RAM;256GHD Disk以上

软件环境:Windows10 64位以上;Vmware 11以上;Ubuntu 16.04 LTS 64位/优麒麟 64位以上;福昕高级pdf编辑器

开发工具:Visual Studio 2010 64位以上;CodeBlocks 64位;vi/vim/gedit+gcc;GDB/OBJDUMP;EDB等

1.3 中间结果

文件名

文件作用

hello.i

Hello.c经过预处理生成的文本文件

hello.s

Hello.i经过编译生成的汇编文件

hello.o

Hello.s经过汇编生成的可重定位目标文件

hello.txt

Hello.o反汇编生成的汇编语言文本文件

hello

链接生成的可执行文件

ams.s

Hello反汇编生成的汇编语言文本文件

1.4 本章小结

本章结合hello的自述,简述了hello的P2P和020过程,介绍了实验的环境以及实验过程中生成的中间结果文件与它们的作用。后文将深入研究过程中的每一个步骤与每一个中间文件的生成过程,深入理解计算机系统。

第2章 预处理

2.1 预处理的概念与作用

  1. 预处理的概念:

预处理一般指在程序源代码被翻译为目标代码的过程中,生成二进制代码之前的过程。对于C语言,预处理器CPP根据以字符#开头的命令,修改原始的C程序,之后会生成一个.i文件,为随后的编译等操作提供条件。这个过程不对程序的源代码进行解析,但它把源代码分割或处理成为特定的单位。

  1. 预处理的作用:

预处理的功能包括宏定义、文件包含、条件编译三部分,分别对应宏定义命令#define、条件包含命令#include、条件编译命令#if/#ifdef/#ifndef/#esle/#elif/#endif三部分实现。此外还有#line(行控制)#error(错误指令)#prgama(和实现相关的杂注)以及单独的#(空指令)等。预处理指令一般被用来使源代码再不同的执行环境中被方便的修改或编译。

2.2在Ubuntu下预处理的命令

预处理命令为:gcc -E hello.c -o hello.i。

如图可知预处理成功,我们得到了hello.i文件。

图2.1 预处理命令

2.3 Hello的预处理结果解析

打开hello.i文件,可以看到与hello.c相比,该文件明显增加了很多内容。结合预处理的概念可知,多出来的内容为预处理得到的头文件stdio.h、unistd.h、stdlib.h的具体内容。在文件最后可以看到,hello.c中的C语言代码并未发生变化,说明预处理并不对程序的源代码进行解析。另外可以看到注释被删除了。

图2.2 hello.i文件

2.4 本章小结

本章介绍了预处理的概念和作用,使用预处理命令在Ubuntu中对hello.c进行预处理得到了hello.i文件,并对其中内容进行分析,了解到预处理会解释头文件包含、宏定义等信息,删除注释,且不对程序的源代码进行解析。

第3章 编译

3.1 编译的概念与作用

(1)编译的概念:

编译是指编译器ccl将预处理文件(hello.i)翻译成汇编文件(hello.s)的过程,hello.s中包含一个汇编语言程序。

(2)编译的作用:

首先ccl将检查代码的正确性,即进行词法分析、语法分析等,确认无误后将源代码翻译为汇编代码完成目标代码的生成。

3.2 在Ubuntu下编译的命令

编译的命令为:gcc -S hello.i -o hello.s

如图可知编译成功,我们得到了hello.s文件。

图3.1 编译命令

3.3 Hello的编译结果解析

3.3.1汇编初始部分

1.汇编文件hello.s初始部分代码如下

2.分析:

其中.file声明源文件;.text声明代码段;.section .rodata声明只读数据段;.align8声明对指令或者数据的存放地址进行对齐的方式;.string声明一个字符串;.globl声明全局变量;.type声明函数、对象类型。

3.3.2数据

(1)字符串

程序中有两个字符串,都存储在只读数据段中,其定义代码如下图。

其中LC0是printf传入的输出格式化参数,LC1是输出格式化参数。

(2)整型数据

i)int i

i是局部变量,处理i的汇编代码如下图。

局部变量i被压入栈中,保存在地址%rbp-4,作为循环变量除了赋初值0外,还需要进行算数操作,即每次循环执行完毕后i加1,相应的代码出现在L4中。

ii)int argc

argc是main函数的第一个参数,先保存在%edi中,之后被存放在栈中地址%rbp-20,具体压栈存储代码如下图。

(3)数组

数组char *argv[]是main函数的第二个参数,先保存在%esi中,之后被存放在栈中地址%rbp-32,数组中的每一个元素都是一个指向字符类型的指针,在内存中被两次调用传给printf函数。具体压栈存储代码如下图。

3.3.3全局函数

hello.c声明了一个函数int main(int agrc,char *argv[]),在hello.s中其声明的汇编代码如下图。

3.3.4赋值操作

对于局部变量i,源程序中有赋值为0的操作,在hello.s文件中汇编代码通过mov指令来实现,其代码如下图。

3.3.5算术操作

hello.c中的算术操作主要有i++,在hello.s文件中汇编代码通过add指令来实现,其代码如下图。

3.3.6关系操作

hello.c中有两处关系操作,第一处是argc!=4用来判断用户输入的参数个数是否为4;第二处是i<8用来判断i的值是否小于8,从而决定是否继续循环,在hello.s文件中汇编代码通过cmp指令来实现,其代码如下图。

其中比较i和8的关系时,编译器并没有和8比较而是和7,结合程序功能,当i>=8时结束循环与当i<=7时继续循环是等价的。

3.3.7控制转移操作

hello.c中有两处控制转移操作,第一处是if语句实现的转移,第二处是for语句实现的转移。

第一处在hello.s文件中用je指令来实现,在判断argc等于4后跳转至L2执行相应的功能语句,其汇编代码如下图。

第二处在hello.s文件中用jle指令来实现,在判断i仍然小于8后,继续跳转至L4即循环开始处执行,其汇编代码如下图。

3.3.8函数操作

hello.c中有关函数的操作有main函数、printf函数、exit函数、sleep函数、getchar函数,以及main函数的返回return。在hello.s中对此的处理均是先完成参数传递(有入口参数的情况下),然后用call语句转到相应函数的入口处执行,其相关汇编代码如下:

首先,main函数是主函数,是程序执行的起点。

在main函数中进行了两次输出函数printf的调用,但第一次call的不是printf而是puts,因为该次调用输出函数并不需要传递参数,只需将字符输出;而第二次调用printf实在循环中,这次有参数argv[1]和argv[2]。

将1作为参数调用exit函数,表示异常退出。

将输入的值通过%edi传参给sleep,控制进程休眠时间。

调用getchar函数,吸收缓存区的所有字符。

3.3.9类型转换

hello.c中的atoi(argv[3])将字符串类型转换为整型。

3.4 本章小结

本章主要介绍了编译的概念和作用,对hello.i文件进行编译得到了hello.s文件,对其中数据、赋值操作、算术操作、关系操作、控制转移操作、函数操作、类型转换等进行了分析,见证了hello程序从用高级的C语言实现经过编译转换为用低阶的汇编代码实现的过程。

第4章 汇编

4.1 汇编的概念与作用

1.汇编的概念:

汇编是指从.s到.o即编译后的文件到生成机器语言二进制程序的过程,汇编器as将hello.s文件翻译成机器语言并将这些指令打包成可重定位目标程序的格式存放在hello.o目标文件中,它是一个二进制文件,包含程序的指令编码。

2.汇编的作用:

在汇编的过程中,输入用汇编语言书写的源程序,输出用机器语言表示的目标程序,即将汇编语言翻译成机器语言,使其在链接后能够被机器识别。

4.2 在Ubuntu下汇编的命令

汇编命令为:gcc -c hello.s -o hello.o

如图可知汇编成功,我们得到了hello.o文件。

图4.1 汇编命令

4.3 可重定位目标elf格式

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

1.使用指令readelf -h hello.o查看ELF头ELF Header,结果如下:

图4.2 ELF Header

在ELF Header中可以看到ELF的类型、版本、大小端等信息。根据头文件的信息可知该文件是可重定位目标文件,有14个节。

2.使用指令readelf -S hello.o查看节头表Section Headers,结果如下:

图4.3 Section Headers

在Section Headers中可以看到,目标文件中的每个节都有一个固定的条目体现在这个表中,指明了各个节的信息,包括名称、类型、起始地址和偏移量等。由于是可重定位目标文件,所有每个节都从0开始,用于重定位。同时从信息中可以观察到,代码段可执行,但是不可写;数据段和只读数据段都不可执行,只读数据段不可写等。

3.使用指令readelf -r hello.o查看重定位节.rela.text,结果如下:

图4.4 .rela.text

重定位节中保存的是可重定位文件必须包含的说明如何修改其节内容的信息。当汇编器生成一个目标模块时,它并不知道数据和代码最终存放在内存中的什么位置,也不知道这个模块引用的任何外部定义的函数和局部变量的位置。而通过重定位节中保存的修改信息,链接器就能知道在将目标文件链接成可执行文件时如何修改这些未知引用。

4.使用指令readelf -s hello.o查看符号表.symtab,结果如下:

图4.5 .symtab

符号表里存放着程序中定义和引用的函数与全局变量的信息。

Name:符号名称;

Value:符号相对于目标节的起始位置偏移,对于可执行目标文件,该值是一个绝对运行的地址;

Size:目标的大小;

Type:声明时数据类型还是函数类型;

Bind:声明符号是举报的还是全局的。

4.4 Hello.o的结果解析

1.使用指令objdump -d -r hello.o > hello.txt对hello.o进行反汇编,并将结果保存在hello.txt文件中。

图4.6 反汇编过程

2.分析:

将反汇编生成的hello.txt文件与hello.s进行对照分析,可知hello.txt中的汇编代码部分与hello.s在功能上基本相同而形式上略有区别,例如:

(1)在.s文件中的立即数为十进制,而在反汇编文件中为十六进制:

hello.s

hello.txt

(2)在.s文件中跳转时使用段名称,而在反汇编文件中使用确定地址:

hello.s

hello.txt

(3)在.s文件中调用函数时使用的是具体的名称,而在反汇编文件中为直接加偏移量:

hello.s

hello.txt

除此之外,两文件最大的不同是反汇编代码中包括了机器语言代码。

机器语言程序是二进制机器指令的集合,是纯粹的二进制数据表示的语言,是计算机可以真正识别的语言。机器指令由操作码和操作数构成。而汇编语言是用人们比较熟悉的词句直接表述CPU动作形成的语言。每一条汇编指令都可以用机器二进制数据来表示,因此可以将所有的汇编语言和机器语言建立一一对应的映射关系,进而将汇编语言翻译为机器语言。

4.5 本章小结

本章主要介绍了汇编的概念和作用,对hello.s进行汇编得到了hello.o文件,并分析了可重定位文件的ELF头、节头表、可重定位节和符号表,还对hello.s进行反汇编得到了hello.txt文件,通过比较hello.s和hello.txt发现了汇编与反汇编代码的细微不同,介绍了机器语言和汇编语言一一对应的映射关系。

5 链接

5.1 链接的概念与作用

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

如图可知链接成功,我们得到了hello文件。

图5.1 链接命令

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

1.查看hello的ELF头:

图5.2 链接后的ELF头

从ELF头中可以知道,hello的类型是可执行文件,有27个节。

2.查看链接后的节头表:

图5.3 链接后的节头表

节头表对hello中所有的节信息进行了声明,包括大小、类型、地址、偏移量等,其中地址是指程序被载入到虚拟地址的起始地址。根据节头表中的信息我们可以用HexEdit定位各个节所占的区间。

3.查看链接后的重定位节:

图5.4 链接后的重定位节

4.查看链接后的符号表:

图5.5 链接后的符号表

5.4 hello的虚拟地址空间

通过edb查看hello的虚拟地址空间各段的信息:

图5.6 查看hello的虚拟地址空间各段

结合5.3中的节头表中各节的起始地址信息,可以通过edb的Memory Regions找到各段的信息。例如.text节,起始地址为0x4010f0,大小为0x145,在EDB中查看如下:

图5.7 查看.text

其他段的信息查看方式与.text节相同。

5.5 链接的重定位过程分析

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

objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。

结合hello.o的重定位项目,分析hello中对其怎么重定位的。

1.使用命令objdump -d -r hello > asm.txt获得hello的反汇编代码并存入asm.txt文件中,如图可知反汇编成功,生成了asm.txt文件。

图5.8 反汇编hello

2.分析hello与hello.o的不同:

(1)相比hello.o的反汇编代码,hello反汇编代码中多了很多的节以及其函数的汇编代码,如.init节、.plt节等,因为这些函数定义在共享库中,在链接时完成了符号解析和重定位。具体如下图:

图5.9 hello中多出的节与函数

(2)hello.o的反汇编代码中call、jmp指令后紧跟着的是相对地址,hello反汇编的代码则有确定的虚拟地址,因为链接器完成了重定位的过程,可以确定运行时的地址。具体如下图:

(hello.o)hello.txt:

(hello)asm.txt:

3.链接与重定位过程:

(1)链接过程:

hello.o文件定义和引用了符号,每个符号对应着一个函数、一个全局变量、一个静态变量等。符号解析就是给每个符号引用分配一个精确的符号定义。

链接器通过解析符号引用完成链接,具体方法是将每个引用与它输入的可重定位目标文件的符号表中的一个确定的符号定义关联起来。对于那些引用定义在相同模块中的局部符号的引用,符号解析是非常简单明了的。

而对于全局符号的引用,编译器会假设该符号是在其他模块中定义的,生成一个链接器符号表条目并把它交给链接器处理。如果链接器在它的任何输入模块中都找不到这个被引用符号的定义,就输出一条错误信息并终止。

(2)重定位过程:

当汇编器生成一个目标模块时,它既不知道数据和代码最终将放在内存中的什么位置,也不知道这个模块引用的任何外部定义的函数或者全局变量的位置。所以无论何时汇编器遇到对最终位置未知的目标引用,它都会生成一个重定位条目,告诉链接器在将目标文件合成可执行文件时如何修改这个引用。

链接器依赖于可重定位模块中的重定位条目,修改代码节和数据节对每个符号的引用,使得它们执行正确的运行时地址,并将所有相同类型的节合并为同一类型的新的聚合节,从而实现重定位。

代码的重定位条目放在rel.text中,已初始化数据的重定位条目放在rel.data中。

5.6 hello的执行流程

子程序名

程序地址

hello!init

0x401000

hello!puts@plt

0x401030

hello!printf@plt

0x401040

hello!getchar@plt

0x401050

hello!atioi@plt

0x401060

hello!exit@plt

0x401070

hello!sleep@plt

0x401080

hello!.plt+0x70

0x401090

hello!_start

0x4010f0

hello!_dl_relocate_static_pie

0x401120

hello!main

0x401125

hello!__libc_csu_init

0x4011c0

hello!__libc_csu_fini

0x401230

exit

0x401fff

5.7 Hello的动态链接分析

1.动态链接分析:

动态链接指在可执行文件装载或运行时,由操作系统的装载程序加载库。动态链接库中的函数在程序执行时才会确定地址,所以编译器无法确定其地址。为避免运行时修改调用模块的代码段,链接器采用延迟绑定的策略。延迟绑定通过两个数据结构之间简洁而又有些复杂的交互来实现,即过程链接表(PLT)和全局偏移量(GOT)。

2.dl_init前后项目内容的变化:

由节头表可得.got.plt的地址为0x404000,大小为0x40:

蓝色选中区域即为.got.plt:

调用dl_init前:

调用dl_init后:

对比信息,可见动态链接器解析函数的地址加入了信息中。

5.8 本章小结

本章主要介绍了链接的概念与作用,并且详细阐述了hello.o经过链接成为一个可执行文件hello的过程,介绍了hello的ELF格式和某些节的含义,分析了hello的虚拟地址空间、重定位过程、执行流程、动态链接等。

6 hello进程管理

6.1 进程的概念与作用

(1)进程的概念:

进程是计算机科学中最深刻、最成功的概念之一,其经典定义为一个执行中程序的实例。

(2)进程的作用:

进程给应用程序提供了两个关键抽象:一是一个独立的逻辑控制流,它提供一个假象,好像每个程序独占地使用处理器;二是一个私有的地址空间,它提供一个假象,好像每个程序独占地使用内存系统。

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

(1)Shell-bash的作用:

shell是一个交互型应用级程序,其基本功能是解释并执行用户输入的各种命令,实现用户与Linux核心的交互。此外shell还能定义各种变量和参数,并提供许多在高级语言中才具有的控制结构,包括循环和分支。

(2)shell的处理流程:

①从终端读入输入的命令;

②将输入字符串切分获得所有的参数;

③如果是内置命令则立即执行,否则调用相应的程序执行;

④shell应该接收键盘输入信号,并对这些信号进行相应处理。

6.3 Hello的fork进程创建过程

当输入的命令不是内置shell命令时,父进程会调用pid_t fork(void)函数创建一个新的子进程,子进程具有与父进程完全相同(但是独立)的上下文,包括代码段、数据段、堆、共享库以及用户栈等。子进程还获得与父进程任何打开文件描述符相同的副本,因此当父进程调用fork时,产生的子进程可以读写父进程中打开的任何文件。唯一的不同是父进程和子进程具有不同的PID。

关于hello的fork进程创建过程:例如当在终端运行hello时,输入./hello 120L021305 李一凡 2时,首先shell会先判断发现这个参数不是shell内置的命令,于是把这条命令当作一个可执行程序的名字,调用fork函数为shell创建进程。

6.4 Hello的execve过程

成功创建hello子进程之后,将调用execve函数在当前子进程的上下文加载并运行一个新的程序即hello程序。

当执行成功时,execve并不会返回,调用execve的程序的代码段、数据段等上下文都将被execve加载且运行的新程序重写并覆盖,然后将控制传递给新程序的主函数main;当调用execve失败时,则会返回-1,errno也被当即更新。

6.5 Hello的进程执行

1.进程执行的相关概念:

(1)两个假象:

逻辑控制流:好像每个程序独占地使用处理器。

私有地址空间:好像每个程序独占地使用内存系统。

(2)并发与时间片:

一个逻辑流的执行在时间上与另一个流重叠,称为并发流。

多个流并发地执行的现象,称为并发。

一个进程执行它的控制流的一部分的每一时间段,称为时间片。

(3)用户模式与核心模式:

处理器通常用某个控制寄存器中的一个模式位来限制一个应用可以执行的指令以及它可以访问的地址空间范围,该寄存器描述了进程当前享有的特权。

当设置了模式位时,进程就运行在内核模式中,一个运行在内核模式的进程可以执行指令集中的任何指令,可以访问系统中的任何内存位置;没有设置模式位时,进程就运行在用户模式中,一个运行在用户模式的进程不允许执行特权指令,不允许直接引用地址空间中内核区的代码和数据。

运行应用程序代码的进程起初在用户模式中运行,进程从用户模式变为内核模式的唯一方法就是通过异常。当异常发生时,控制传递到异常处理程序,处理器将模式从用户模式变为内核模式。等它处理完异常,再返回到应用程序代码时,处理器就把模式从内核模式变为用户模式。

(4)上下文切换:

内核为每个进程维持一个上下文,它是内核重新启动一个被抢占的进程所需的状态,由一些对象的值组成。这些对象包括通用目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构,比如包含当前进程信息的进程表、包含进程已打开文件信息的文件表。

在进程执行的某些时刻,内核可以抢占当前进程并重新启动一个先前被抢占的进程,这种决策称为调度,由内核中称为调度器的代码处理。

在内核调度了一个新的进程运行后,它就抢占了当前进程,并使用一种称为上下文切换的机制来将控制转移到新的进程。

上下文切换的过程是:保存当前进程的上下文;恢复某个先前被抢占的进程被保存的上下文;将控制传递给这个新恢复的进程。

2.hello程序的进程执行:

进程调用execve函数后,便为helllo程序分配了私有地址空间。当自定义参数有三个(即我的学号与姓名)时,起初hello在用户模式中运行,输出”Hello 120L021305 李一凡”。然后hello调用sleep函数,进程转为内核模式,内核不会等待sleep函数调用结束,而是休眠并释放hello进程,并将hello进程从当前运行队列中移至待运行队列,转回用户模式。接着定时器开始计时,内核进行上下文切换将当前进程的控制权交给其他进程,2秒后休眠结束,定时器发送一个中断信号,进入内核模式处理终端,并将hello进程从待运行队列重新移回运行队列并继续运行hello进程。再次输出”Hello 120L021305 李一凡”,重复以上过程直到运行8次。而当自定义参数数量不为3时,则要简单的多,因为不会调用sleep也就没有模式转换和额外的上下文切换,hello输出”用法: Hello 学号 姓名 秒数!”,然后结束。

6.6 hello的异常与信号处理

1.hello执行过程中会出现的异常和信号:

①中断:在hello程序执行的过程中可能会出现外部I/O设备引起的异常,造成中断;

②陷阱:在hello程序调用并执行sleep函数的时候会出现陷阱;

③故障:执行hello程序的时候,可能会发生缺页故障;

④终止:hello程序执行过程中可能出现DRAM或SRAM位损坏的奇偶错误,引起终止;

⑤常见信号:

图6.1 hello执行过程中可能出现的信号

2.程序运行过程中的键盘输入:

①随便乱按:

图6.2 随便乱按

可以发现随便输入只是将屏幕的输入缓存,当打印结束执行getchar()时,会读入一个回车结尾的字符作为依次输入,而其它字符串会在程序结束后当作shell命令行输入呈现。

②Ctrl-Z后分别运行ps jobs pstree fg kill等命令:

图6.3 Ctrl-z后分别运行ps jobs pstree fg kill等命令

Ctrl-Z后运行ps可以看出,hello并未被回收,而是被挂在了后台。jobs显示hello进程已停止。pstree命令会显示出更详细的进程信息。输入fg命令会发现hello进程又来到了前台继续运行。再次Ctrl-Z,然后运行kill命令向hello进程发送SIGKILL信号杀死程序,再想用fg将hello调回前台运行时发现进程已被杀死。

③Ctrl-C:

图6.4 Ctrl-c

输入Ctrl-C会使内核发送SIGINT信号到前台进程组的每个进程,默认情况是终止前台作业,如图用ps查看发现前台进程组中已没有hello进程的信息,说明hello进程已被终止并回收。

6.7本章小结

本章主要介绍了进程的概念与作用,shell的作用与处理流程,分析了hello的fork进程创建过程、execve过程以及hello的进程执行过程。本章还介绍了hello运行过程中可能出现的异常和信号,并分析了hello程序对程序运行过程中一些键盘输入的处理。

7 hello的存储管理

7.1 hello的存储器地址空间

1.逻辑地址:

在有地址变换功能的计算机中,访问指令给出的地址(操作数)称为逻辑地址,也叫相对地址。一个逻辑地址由一个段标识符加上一个指定段内相对地址的偏移量构成。

2.线性地址:

线性地址是逻辑地址到物理地址变换之间的中间层。在分段部件中逻辑地址是段中的偏移地址,然后加上基地址就是线性地址。若没有启用分页机制,那么线性地址直接就是物理地址。

3.虚拟地址:

程序保护模式下,程序访问存储器所使用的逻辑地址称为虚拟地址,例如hello里面的虚拟内存地址。与实地址模式下的分段地址类似,虚拟地址也可以写为“段:偏移量”的形式,这里的段是指段选择器。当CPU生成一个虚拟地址来访问主存,这个虚拟地址可以通过地址翻译转换成对应的物理地址。

4.物理地址:

在存储器里以字节为单位存储信息,为正确地存放或取得信息,每一个字节单元给以一个唯一的存储器地址,称为物理地址,又叫实际地址或绝对地址。

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

一个逻辑地址由一个段标识符加上一个指定段内相对地址的偏移量构成。

段式管理的特点:

  1. 以段为单位分配内存,每段分配一个连续的内存区;
  2. 由于各段长度不等,所以这些存储区的大小不一;
  3. 同一进程包含的各段之间不要求连续;
  4. 段式管理的内存分配与释放在作业或进程的执行过程中动态进行。

对于一个给定的完整的逻辑地址[段选择符:段内偏移地址],将其转换为线性地址的一般步骤:

  1. 看段选择符的T1为0还是1,进而判断当前要转换的是GDT中的段还是LDT中的段,再根据相应寄存器求其地址和大小,得到一个数组;
  2. 拿出段选择符中的前13位,可以在这个数组中,查找到相应的段描述符,得到基地址base;
  3. 基地址base+偏移量就是要转换得到的线性地址。

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

分页机制把下线性地址空间和物理地址空间划分为大小相同的块,这样的块称为页。通过在线性地址空间的页与物理地址空间的页之间建立的映射,分页机制实现线性地址到物理地址的变换。这种映射关系通过一种称为页表的数据结构实现。页表是一个页表条目(PTE)的数组,其基本的组织结构如下:

图7.1 页表基本组织结构

CPU中的一个控制寄存器——页表基址寄存器指向当前页表,n位的虚拟地址(VA)由虚拟页面偏移(VPO,p位)和虚拟页号(VPN,n-p位)组成。MMU利用VPN来选择适当的页表条目(PTE),将页表条目中的物理页号(PPN)与虚拟地址的页面偏移量(VPO)串联起来,就得到相应的物理地址。

页面命中时,CPU硬件执行流程如下:

  1. 处理器生成一个虚拟地址并传送给MMU;
  2. MMU生成PTE地址,并从高速缓存/内存中请求得到它;
  3. 高速缓存/内存向MMU返回PTE即MMU使用呃逆村中的页表生成页表条目;
  4. MMU构造物理地址并传送给高速缓存/主存;
  5. 高速缓存/主存返回所请求的数据字给处理器。

页面不命中时,CPU硬件执行流程如下:

  1. 处理器生成一个虚拟地址并传送给MMU;
  2. MMU生成PTE地址,并从高速缓存/内存中请求得到它;
  3. 高速缓存/内存向MMU返回PTE即MMU使用呃逆村中的页表生成页表条目;
  4. PTE中的有效位为0,MMU触发缺页异常;
  5. 缺页处理程序确定物理内存中牺牲页(若页面被修改则换出到磁盘);
  6. 却也处理程序调入新的页面,并更新内存中的PTE;
  7. 缺页处理程序返回到原来进程,再次执行导致缺页的指令。

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

1.Core i7地址翻译的概况图:

图7.2 Core i7地址翻译的概况图

2.由上图可知该虚拟地址共48位,前36位为VPN,后12位为VPO,TLB为16组级联,共64个条目,所以TLBI应为4位,TLBT为36-4=32位。由于是四级页表,VPN可以分为VPN1~4,对应一到四级页表,其中一级页表指向二级页表,二级页表指向三级页表,以此类推。

3.地址翻译:MMU对虚拟地址VA做处理,取出VPN1、VPN2、VPN3、VPN4(VPNi为指向第i级页表的索引)以及虚拟地址偏移量(VPO)。接着用VPN1在一级页表中匹配,若匹配PTE不为空,则用一级页表PTE的内容到二级页表中继续匹配;若匹配为空则代表该页未分配,产生缺页异常,需要跳转至缺页处理程序处理。在访问完4个页表之后,获得物理页面的PPN,再配合PPO(与VPO相等),就可以获得物理地址VP。

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

MMU根据VP解析出相应的PP后,需要用PP再Cache中寻找相应的数据。

根据第一级Cache的相关参数解析PP的索引位,具体说来,根据B大小将PP的低b位作为块内偏移,根据S大小将紧跟着的s位作为组索引,剩下的位全部作为tag标记位。

然后根据这些索引位在Cache中匹配,若命中,即该组中有一行的标记位与物理地址的标记位相同且改行的有效位为1,则通过块偏移找到返回的缓存字节;若未命中,则需要从存储层次结构中的下一层取出被请求的块,让后将新的块存储在组索引位所指示的组中的一行中。

7.6 hello进程fork时的内存映射

当fork函数被当前进程调用时,内核为hello进程创建各种数据结构,并分配给它一个唯一的PID。为了给hello进程创建虚拟内存,它创建了当前进程的mn_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。

当fork在hello进程中返回时,hello现在的虚拟内存刚好和调用fork时存在的虚拟内存相同,当这个两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,因此也就为每个进程保持了私有地址空间的抽象概念。

7.7 hello进程execve时的内存映射

execve函数在当前进程中加载并运行包含在可执行目标文件hello中的程序,用hello程序替代了当前程序。需要经过以下几个步骤:

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

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

1.某指令引用的某字未缓存在DRAM中;

2.地址翻译硬件从内存中读取该字,并且触发一个缺页异常;

3.缺页异常调用内核中的缺页异常处理程序,该程序会选择一个牺牲页;

4.如果牺牲页已被修改过,那么内核会将它复制回磁盘。无论哪种情况,内核都会修改牺牲页的页表条目,表明该页不再缓存在主存中;

5.内核从磁盘复制带缓存的页到内存中牺牲页的物理地址,更新PTE,随后返回;

6.重新启动导致缺页的命令,该命令会把导致缺页的虚拟地址重新发送到地址翻译硬件;

7.当指令再次执行时,相应的物理页面已经驻留在内存中,页命中,指令可以可以没有故障地运行下去。

7.9动态存储分配管理

1.动态内存管理的基本方法:

程序员使用动态内存分配器获得虚拟内存并进行动态内存管理,动态内存分配器维护着一个进程的虚拟内存区域,称为堆。分配器将堆视为一组不同大小的块的集合来维护,每个块是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留共应用程序使用;空闲块保持空闲直到它显式地被应用所分配。一个已分配的块保持已分配状态直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。

分配器有显式和隐式两种风格,这两种风格都要求应用显式地分配块,不同之处在于由哪个实体来负责释放已分配的块。

(1)显式分配器:

要求应用显式地释放已分配的块。显示分配器的设计有以下几个约束条件:处理任意请求序列;立即响应请求;只使用堆;对齐块;不修改已分配的块。此外一个好的显式分配器需要达到最大化吞吐率和最大化内存利用率,然而这两个要求相互牵制,要找到适当的平衡通常需要考虑空闲块组织、放置、分割、合并等问题。

(2)隐式分配器:

要求分配器检测一个已分配块何时不在被应用程序所用,那么就释放这个块。隐式分配器又叫垃圾收集器,而自动释放未使用的已分配的块的过程叫做垃圾收集。

2.动态内存管理的策略:

对于空闲块的组织通常有不带脚部的隐式空闲链表、带脚部的隐式空闲链表、显式空闲链表三种数据结构。

对于放置方面通常有三种策略:

(1)首次适配:从头开始搜索空闲链表,选择第一个合适的块。

(2)下一次适配:从上一次查询结束的地方开始,选择第一个合适的块。

(3)最佳适配:检测每个空闲块,西安则适合所需请求大小的最小空闲块。

对于合并空闲块方面通常有两种策略:

(1)立即合并:每次在一个块释放时,就合并所有相邻块。

(2)推迟合并:直到某个分配请求失败,扫描整个堆,合并所有的空闲块。

对于动态内存分配方面通常有三种策略:

(1)最先匹配:分配n个字节,使用第一个可用空间比n大的空闲区。

原理:空闲分区列表按照地址顺序排序;分配过程中,搜索第一个适合的分区;释放分区时,检查是否可与临近的空闲分区合并。

优点:简单;在高地址空间有大块的空闲分区。

缺点:外部碎片;分配大块时较慢。

(2)最佳匹配:分配n个字节,查找并使用不小于n的最小空闲分区。

原理:空闲分区列表按照大小排序;分配过程中,查找一个合适的分区;释放时,查找并合并临近的空闲分区。

优点:避免大的空闲分区被拆分;可减小外部碎片的大小。

缺点:外部碎片;释放分区较慢;容易产生很多无用的小碎片。

(3)最差匹配:分配n个字节,查找并使用不小于n的最大空闲分区。

原理:空闲分区列表按照大小排序;分配时,选最大的分区;释放时,检查是否可与临近的空闲分区合并,进行可能的合并,并调整空闲分区列表顺序。

优点:中等大小的分配较多,分配效果最好;避免出现太多的小碎片;

缺点:外部碎片;释放分区较慢;容易破坏大的空闲分区,因此后续难以分到大的分区。

7.10本章小结

本章主要介绍了hello的存储器地址空间,介绍了四种地址空间的概念和地址的相互转换。介绍了hello的四级页表支持下虚拟地址到物理地址的转换和三级Cache支持下物理地址的访问。分析了hello进程fork时和execve时的内存映射,缺页故障与缺页中断处理,动态存储分配管理等相关知识。

8 hello的IO管理

8.1 Linux的IO设备管理方法

设备的模型化:文件

设备管理:unix io接口

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

8.2 简述Unix IO接口及其函数

Unix I/O接口的统一输入输出方式:

  1. 打开文件:一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个I/O设备。内核返回一个小的非负整数,称为描述符,它在后续对此文件的所有操作中标识这个文件。内核记录有关这个打开文件的所有信息。应用程序只需记住描述符;
  2. Linux shell创建的每个进程开始时都有三个打开的文件:标准输入(描述符为0)、标准输出(描述符为1)和标准错误(描述符为2)。头文件<unistd.h>定义了常量STDIN_FILENO、STOOUT_FILENO和STDERR_FILENO,可以用来代替显式的描述符值;
  3. 改变当前的文件位置:对于每个打开的文件,内核保持着一个文件位置k,初始为0。这个文件位置是从文件开头其实的字节偏移量。应用程序能够通过执行seek操作显式地设置文件的当前位置为k。
  4. 读写文件:一个读操作就是从文件复制n(n>0)个字节到内存,从当前文件位置k开始,然后将k增加到k+n。给定一个大小为m字节的文件,当k~m时执行读操作会触发一个称为end_of_file(EOF)的条件,应用程序能检测到这个条件。在文件结尾处并没有明确的“EOF符号”。类似地,写操作就是从内存复制n>0个字节到一个文件,从当前文件位置k开始,然后更新k;
  5. 关闭文件:当应用完成了对文件的访问之后,它就通知内核关闭这个文件。作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。无论一个进程因为何种原因终止时,内核都会关闭所有打开的文件并释放它们的内存资源。

Unix I/O接口函数:

1.int opne(char *filename,int flags,mode_t mode);

函数功能:打开文件。将filename转换为一个文件描述符并返回,返回的描述符总是在进程中当前没有打开的最小描述符。

函数参数:

filename:要求打开的文件名。

flags:指明进程将如何访问这个文件。

mode:指定新文件的访问权限位。

2.int close(int fd);

函数功能:关闭文件。返回操作结果。

函数参数:

fd:要关闭的文件的描述符。

3.ssize_t read(int fd,void *buf,size_t n);

函数功能:读文件。从描述符为fd的当前文件复制最多n个字节到内存位置buf,返回-1表示错误,0表示EOF,其他表示实际传送的字节数量。

函数参数:

fd:要读的文件的描述符。

buf:要读入到的内存位置。

n:复制字节上限。

4.ssize_t write(int fd,const void *buf,size_t n);

函数功能:写文件。从内存位置buf复制至多n个字节到描述符为fd的当前文件。

函数参数:

fd:要写入的文件的描述符。

buf:要写的内存位置。

n:复制字节上限。

8.3 printf的实现分析

1.printf函数体:

图8.1 printf函数体

2.printf函数内容分析:

首先将第一个参数入栈,然后vsprintf函数,该函数可以格式化输出字符串并返回要打印的字符串长度i。再调用write函数,将i位字符串写入终端,将栈中参数放入寄存器。int INT_VECTOR_SYS_CALLA代表通过系统调用syscall,该函数功能是不断打印出字符。字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。于是字符串被打印到了屏幕上。

注:vsprint函数体:

图8.2 vsprint函数体

8.4 getchar的实现分析

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

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

1.异步异常-键盘中断的处理:

当用户按键时,键盘接口会得到一个代表该按键的键盘扫描码,同时产生一个中断请求,中断请求抢占当前进程运行键盘中断子程序,键盘中断子程序先从键盘接口取得该按键的扫描码,然后将该按键扫描码转换成 ASCII 码,保存到系统的键盘缓冲区之中。

2.getchar函数返回原理:

当程序调用getchar函数时,程序等待玉壶按键。用户输入的字符被存放在键盘缓冲区中直到用户按回车为止(回车字符也放在缓冲区中)。当用户键入回车之后,getchar函数才开始调用read系统函数,从输入流中每次读入一个字符。getchar函数的返回值时用户输入的第一个字符的ASCII码,如出错返回EOF。若用户在按回车之前输入了不止一个字符,则其他字符会保留在键盘缓存区中,等待后续getchar函数读取。也就是说,后续再调用getchar函数不会等待用户按键,而会直接读取缓冲区中的字符直到缓冲区中的字符读完,才继续等待用户按键。

3.getchar函数作用:

从标准输入流中只读取一个字符(包括空格、回车、Tab),督导回车符时退出。键盘输入的字符都存在缓冲区内,键入回车后getchar就进入缓冲区读取字符,每次返回第一个字符作为getchar函数的值。如果有循环或足够多的getchar语句,就会一次读出缓冲区内的所有字符直到’\n’。

8.5本章小结

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


结论

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

  1. 程序员用C语言编写出hello.c文件;
  2. hello.c文件经过cpp的预处理,生成hello.i文件;
  3. hello.i文件经过ccl的编译,生成汇编代码hello.s文件;
  4. hello.s文件经过as的翻译,生成一定格式的机器码hello.o文件;
  5. hello.o文件经过ld的链接,生成可执行文件hello;
  6. 在shell中输入命令./hello 120L021305 李一凡 2;
  7. 因为输入的不是shell的内置命令,所以fork产生一个子进程,然后hello便从程序变为了进程;
  8. 调用execve函数加载运行hello,映射虚拟内存,载入物理内存,进入main函数;
  9. 当hello程序执行printf函数时,会调用malloc向动态内存配分期申请堆中的内存;
  10. 打印完成后hello调用sleep函数,进程陷入内核模式,内核进行上下文切换将当前进程的控制权交给其他进程,2秒后休眠结束,内核进行上下文切换将控制传递给hello进程;
  11. 若无信号干扰,9,10重复执行8次;若输入Ctrl-Z,内核会发送SIGTSTP信号给hello进程,使之停止前台作业并挂起,后续输入fg会使之再次成为前台程序并继续运行;若输入Ctrl-C,内核会发送SIGINT信号给hello进程,使之终止;输入为其他字符时,第一次回车后在输入的字符将在hello进程运行结束后被当作命令行输入解释;
  12. 当hello进程执行完成后,内核安排父进程回收hello进程,并将hello进程的退出状态传递给父进程。

感悟:我对一个程序由编写到运行到结束的过程有了更全面且更深刻的理解,对计算机系统中进程、信号、存储等机制有了更深切的感受。一个简单的hello.c文件,它一生中的每一步都凝聚着计算机人的智慧,让人震撼,受益匪浅。

附件

文件名

文件作用

hello.i

Hello.c经过预处理生成的文本文件

hello.s

Hello.i经过编译生成的汇编文件

hello.o

Hello.s经过汇编生成的可重定位目标文件

hello.txt

Hello.o反汇编生成的汇编语言文本文件

hello

链接生成的可执行文件

ams.s

Hello反汇编生成的汇编语言文本文件

参考文献

[1]  Bryant,R.E.. 深入理解计算机系统[M]. 北京:机械工业出版社,2016

哈工大2022春计算机系统大作业:程序人生-Hello‘s P2P相关推荐

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

    计算机系统 大作业 题 目 程序人生-Hello's P2P 专 业 计算学部 学 号 班 级 学 生 指 导 教 师 计算机科学与技术学院 2022年5月 摘 要 为深入理解计算机系统,本文以hel ...

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

          计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 计算机类 学     号 1190200613 班     级 1903004 学       生 ...

  3. 哈工大2022秋计算机系统大作业——程序人生

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

  4. 哈工大2022秋计算机系统大作业-程序人生(Hello‘s P2P)

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

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

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

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

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

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

    摘要 本文以一个简单的hello.c程序为例,介绍了程序在Linux系统上的一生.通过使用Linux上的各种工具,详细揭露了hello.c文件从变为到可执行文件程序存到硬盘开始,之后再从载入内存进程, ...

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

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

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

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

最新文章

  1. python下三角代码分析_空间分析:2-3。用Python生成Delaunay三角形,23Python,德劳内
  2. GoogleClosureLibrary 中的Component 模型纪要
  3. 解决eclipse中Findbugs检查不生效的问题
  4. 程序设计中的数学思维函数总结(代码以C#为例)
  5. 微信 html关闭当前页
  6. Scala 学习之面向对象(3)
  7. Android 学习思维导图
  8. 广州科源980tc数控系统说明书_广州数控GSK980TC3车床数控系统 操作轴名
  9. fractal 分形维数 盒子维 纹理特征
  10. dota2服务器位置设置在哪里,《DOTA2》自走棋国服怎么进入 自走棋国服服务器进入方法...
  11. RxJava2.0的生动形象的分析
  12. 华米科技:庇佑之下,黄汪难设温柔乡
  13. fastlane 上传到蒲公英和Fir 配置
  14. SpringCloud全家桶主要组件及通信协议剖析
  15. Markdown与LATEX
  16. 到底什么是BI?BI能为企业带来什么?
  17. 用python做一个简单GUI小软件
  18. 三角形外接球万能公式_外接球半径常见的求法
  19. 使用describe()查看catering_sale.xls数据的基本情况,通过箱式图检测出catering_sale.xls中的异常值
  20. 什么样的离职原因适合你

热门文章

  1. 网吧网管软件守护神 v3.0 官方
  2. 外汇交易策略算法_算法交易策略
  3. itpub坛友问题--基于普通表或分区表创建索引,会占用临时表空间及何时回收临时表空间...
  4. 联想如何助80后公益创业?
  5. 数字化转型导师坚鹏:企业数字化营销能力提升
  6. python的包package
  7. 网站中木马病毒黑帽非法信息处理
  8. Your configuration does not allow connection to http://
  9. 相似图搜索网站(以图找图)
  10. 全民java竞争有多激烈,全民Kotlin:你没有玩过的全新玩法