计算机系统

大作业

题     目 程序人生-Hello’s P2P

专       业 计算机类

学     号 120L021025

班     级 2003001

学       生  

指 导 教 师 史先俊

计算机科学与技术学院

2022年5月

摘  要

本文结合《深入理解计算机系统》以及本学期所学的知识,以hello.c程序为例分析了程序从高级语言程序经预处理、编译、汇编、链接到生成可执行文件的生命历程,以及程序执行过程中的进程管理、存储空间管理和I/O管理如何进行,并探讨系统如何通过硬件和软件的相互交织、共同协作以达到运行程序的最终目的,学以致用,将本学期所学知识融会贯通,真正做到“深入理解计算机系统”。

关键词:计算机系统;程序;生命历程;融会贯通

(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分

目  录

第1章 概述............................................................................................................. - 4 -

1.1 Hello简介...................................................................................................... - 4 -

1.2 环境与工具..................................................................................................... - 4 -

1.3 中间结果......................................................................................................... - 5 -

1.4 本章小结......................................................................................................... - 5 -

第2章 预处理......................................................................................................... - 7 -

2.1 预处理的概念与作用..................................................................................... - 7 -

2.2在Ubuntu下预处理的命令.......................................................................... - 7 -

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

2.4 本章小结......................................................................................................... - 8 -

第3章 编译............................................................................................................. - 9 -

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

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

3.3 Hello的编译结果解析................................................................................ - 10 -

3.4 本章小结....................................................................................................... - 13 -

第4章 汇编........................................................................................................... - 14 -

4.1 汇编的概念与作用....................................................................................... - 14-

4.2 在Ubuntu下汇编的命令........................................................................... - 14 -

4.3 可重定位目标elf格式............................................................................... - 14 -

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

4.5 本章小结....................................................................................................... - 19 -

第5章 链接........................................................................................................... - 20 -

5.1 链接的概念与作用....................................................................................... - 20 -

5.2 在Ubuntu下链接的命令........................................................................... - 20 -

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

5.4 hello的虚拟地址空间................................................................................ - 26 -

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

5.6 hello的执行流程........................................................................................ - 27 -

5.7 Hello的动态链接分析................................................................................ - 28 -

5.8 本章小结....................................................................................................... - 29 -

第6章 hello进程管理.................................................................................... - 30-

6.1 进程的概念与作用....................................................................................... -30 -

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

6.3 Hello的fork进程创建过程..................................................................... - 30 -

6.4 Hello的execve过程................................................................................. - 31 -

6.5 Hello的进程执行........................................................................................ - 31 -

6.6 hello的异常与信号处理............................................................................ - 32 -

6.7本章小结....................................................................................................... - 32 -

第7章 hello的存储管理............................................................................... - 33 -

7.1 hello的存储器地址空间............................................................................ - 33 -

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

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

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

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

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

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

7.8 缺页故障与缺页中断处理........................................................................... - 36 -

7.9动态存储分配管理....................................................................................... - 36 -

7.10本章小结..................................................................................................... - 37 -

第8章 hello的IO管理................................................................................. - 38 -

8.1 Linux的IO设备管理方法.......................................................................... - 38 -

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

8.3 printf的实现分析........................................................................................ - 39 -

8.4 getchar的实现分析.................................................................................... - 40 -

8.5本章小结....................................................................................................... - 40 -

结论.......................................................................................................................... - 41-

附件......................................................................................................................... - 42 -

参考文献................................................................................................................. - 43 -

第1章 概述

1.1 Hello简介

1.1.1 P2P,即Program to Process,是指程序从编辑器下的高级语言程序(如hello.c)生成可执行程序(如hello)、执行可执行程序产生进程的过程。P2P的具体过程如下:cpp预处理器将hello.c文件中的#开头的命令进行预处理,插入对应的系统头文件中的内容,得到hello.i文件;然后编译器ccl将其翻译为汇编语言文件hello.,此后汇编器as负责将汇编语言翻译为机器指令代码,得到hello.o文件;最后,在链接阶段,链接器ld将hello.o和其它用到的预编译好的目标文件合并到一起并且完成引用的重定位工作,就得到了可执行文件hello。在输入命令执行改文件时,shell程序会调用fork函数创建子进程,并且在子进程中加载该程序,由此,hello.c就由高级语言程序变成了一个进程。

1.1.2 020,即“From 0 to 0”由子进程开始执行(此前内存无hello相关内容)到子进程结束被回收(内存中仍无hello相关内容)的过程。020的具体过程如下:在子进程调用execve函数加载hello执行后,操作系统OS为其映射虚拟内存至物理内存,CPU也为运行此进程分配时间,通过Cache,TLB等机制加速访问时间;程序结束后,shell父进程通过相应的信号机制得到子进程结束的消息并且回收进程,同时内核将控制权转移回shell,子进程的相关数据被清除。

1.2 环境与工具

1.2.1硬件环境:

主机:X64 CPU(i7-10750H);2.6 GHz;2×8G RAM;1T SSD;

虚拟机:X64 CPU(i7-10750H);2.6 GHz;4.0G RAM;20G SSD。

1.2.2软件环境:

Windows:Windows 10家庭版;

Linux:VMware Workstation 15 Pro;Ubuntu 64位-20.04。

1.2.3调试工具:

Windows下:Visual Studio 2022,Dev-C++;CodeBlocks 20.03;

Linux下:CodeBlocks 20.03;gcc9.3.0/Linux。

1.3 中间结果

所有中间产物的名称与功能见下表:

文件名

功能

hello.c

源程序

hello.i

预处理后得到的文本文件

hello.s

编译后得到的汇编语言文件

hello.o

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

hello

链接后得到的可执行目标文件

hello.elf

用readelf读取hello.o得到的ELF格式信息

hello_elf.txt

用readelf读取hello得到的ELF格式信息

hello.asm

反汇编hello.o得到的反汇编文件

hello2.asm

反汇编hello可执行文件得到的反汇编文件

1.4 本章小结

本章简要介绍了程序的P2P和020过程,即高级语言程序经预处理、编译、汇编、链接生成可执行程序,从执行程序生成子进程至回收子进程的过程;介绍了为编写论文使用的计算机的软硬件环境和调试工具;展示了为编写本论文生成的中间结果文件。

(第1章0.5分)

第2章 预处理

2.1 预处理的概念与作用

2.1.1预处理的概念:预处理是指在编译之前进行的处理,由预处理器(cpp)根据以字符#开头的预处理命令,修改原始的C程序,读取系统头文件的内容,并把它们直接插入程序文本中,得到.i扩展名文件。C语言的预处理主要包括宏定义、文件包含、条件编译三个方面的内容。

2.1.2预处理的作用:预处理通过调用系统头文件,用实际内容替换用#define定义的字符串,简化了程序,使编译器编译程序时更加方便。

2.2在Ubuntu下预处理的命令

预处理命令为cpp hello.c > hello.i

图2.1 Ubuntu下的预处理命令

2.3 Hello的预处理结果解析

hello.c经预处理得到hello.i文件,hello.i共3060行,大小为64.7KB。

源文件最开始的stdio.h、stdlib.h、unistd.h三个头文件被系统头文件的实际展开内容替换,展开的具体流程如下(以stdio.h为例):cpp先删除指令#include <stdio.h>,并到Ubuntu系统的默认的环境变量中寻找 stdio.h,最终打开路径/usr/include/stdio.h下的stdio.h文件。若stdio.h文件中使用了#define语句,则按照上述流程继续递归地展开,直到所有#define语句都被解释替换掉为止。除此之外,cpp还会进行删除程序中的注释和多余的空白字符等操作,并对一些值进行替换。

此后是所调用函数的声明,文件的最后是hello.c中的main()函数,它位于第3047到第3060行。

图2.2 hello.i的解析

图2.3 头文件的展开内容

图2.4 所调用函数的声明

图 2.5 hello.i中的main函数

2.4 本章小结

本章介绍了预处理的概念、过程、作用、实现指令,并对预处理操作的结果进行了解析,展示了程序生命周期中的第一步。

(第2章0.5分)

第3章 编译

3.1 编译的概念与作用

3.1.1 编译的概念

编译是指利用编译程序从经预处理的源语言编写的源程序产生汇编指令的过程。编译过程中,C编译器ccl通过词法分析、语法分析、语义检查、中间代码生成、代码优化和目标代码生成,将合法指令翻译成等价汇编代码,并就语法错误给出错误分析。通过编译,编译器将文本文件 hello.i 翻译成汇编语言文件 hello.s,在hello.s中,以文本的形式描述了一条条低级机器语言指令。

3.1.2 编译的作用

编译将预处理后的文本文件转化为汇编语言文件,为把高级语言变成计算机可以识别的二进制语言的过程做准备。

3.2 在Ubuntu下编译的命令

Ubuntu下编译命令为gcc -m64 -Og -no-pie -fno-PIC -S hello.i -o hello.s。(或/usr/lib/gcc/x86_64-linux-gnu/9/cc1-m64 -Og -no-pie -fno-PIC hello.i -o hello.s)

图3.1 编译命令与结果

3.3 Hello的编译结果解析

经编译生成的hello.s共78行,大小1.3KB。

图3.2 经编译生成的hello.s

3.3.1 数据类型

A.整型(int

立即数1,4等直接转化为$1、$4,并直接使用,如第25行cmpl  $4, %edi。

int i 作为局部变量被存储在寄存器或者栈空间中。i作为函数内部的局部变量,并不占用文件实际节的空间,只在运行时存在于栈中。对于i的操作就是直接对寄存器或栈进行操作。

int argc是main函数的第一个参数,64位编译下,argc由寄存器%rdi传入,进而保存在堆栈中。

B.字符串(char*

字符串"用法: Hello 学号 姓名 秒数!\n"和"Hello %s %s\n"均被储存在.text数据段中,其中汉字采用UTF-8编码,每个汉字占3个字节。

图3.3 字符串在hello.s中的处理

C.数组(char*[]

char *argv[]是main函数的第二个参数。在hello.s中,其首地址保存在栈中。访问时,通过寄存器%rbx寻址的方式访问。图中%rsi储存argv[1],%rdi储存argv[2]。

图3.4 数组在hello.s中的存储

3.3.2操作

A.赋值操作

对局部变量的赋值使用movx指令完成。如将局部循环变量i的值保存在寄存器%ebp中。

3.5 对循环变量i的赋值操作

B.关系操作与控制转移操作

(不)等于判断及其跳转使用cmpx和jne指令实现。如if(argc!=4),若判断结果不相等,则CS:IP跳转到jne后地址的指令,若相等则继续顺序执行指令。

3.6 argc!=4的判断及跳转实现

小于(或大于)判断使用cmpx及jle(jge)指令实现。编译器会将i<8转换为i<=7来判断。同理for(i=0;i<8;i++),若i<8的判断结果为真,则跳转到L3继续执行循环体,若i<8不再成立,则继续顺序执行指令脱离循环。

图3.7 i<8的判断及跳转实现

       C.访问数组操作

通过将首地址保存在栈中,并使用寄存器%rbx通过相对寻址的方式访问。

D.算数操作

i++使用addq指令实现

图3.8 i++指令的实现

还有一些算数操作,如修改访问数组使用的寄存器等操作,同样通过addq和subq等指令实现。

图3.9 访问数组时使用的算数运算

E.函数操作

(1)调用函数采用callq指令实现,执行这条指令后程序计数器CS:IP会被设置为callq后的地址。

图3.10 exit(1)的实现

(2)函数参数传递采用movx指令通过寄存器实现。前6个参数传入的寄存器分别为%rdi、%rsi、%rdx、%rcx、%r8、%r9,第7个及以后的参数通过堆栈传递。以main函数为例,argc作为第一个参数,使用%edi传入,argv[]作为第二个参数,使用%rsi传入。

图3.11 main函数的argc参数传入的实现

(3)在函数执行开始时,可能需要为其局部变量分配空间,在返回前,又必须释放这些空间。

图3.12 函数为局部变量分配空间

3.4 本章小结

本章介绍了编译的概念与作用,编译是将文本文件翻译成汇编语言文件,为后续将其转化为二进制机器指令文件做准备的过程。同时,本章以hello.s文件为例,介绍了编译器如何处理各个数据类型以及各类操作,验证了大部分数据、操作在汇编代码中的实现。

(第32分)

第4章 汇编

4.1 汇编的概念与作用

4.1.1 概念:汇编是指汇编器(assembler)将以.s结尾的汇编程序翻译成机器语言指令,并把这些指令打包成可重定位目标程序格式,最终结果保存在.o 目标文件中的过程。

4.1.2 作用:将汇编语言翻译为机器指令,并将这些指令以可重定位目标程序格式保存在.o文件中。为后续的链接和执行过程奠基。

4.2 在Ubuntu下汇编的命令

Ubuntu下的汇编命令为gcc -m64 -no-pie -fno-PIC -c hello.s -o hello.o

图4.1 hello.o的生成指令与过程

4.3 可重定位目标elf格式

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

生成ELF文件的指令为readelf -a hello.o > hello.elf

图4.2 生成ELF文件的指令和过程

图4.3 生成的hello.elf

生成的ELF文件包括ELF头、节头、程序头(本文件无)、重定位节等内容。具体如下:

(1)ELF头:以 16字节的二进制序列 Magic 开始,其描述了生成该文件的系统的字的大小和字节顺序。此后是帮助链接器语法分析和解释目标文件的信息,其中包括 ELF 头大小、目标文件类型、机器类型、节头部表的文件偏移、节头部表中条目的大小和数量等以及类别、数据、ABI版本、类型等相关信息。

图4.4 hello.elf的ELF

(2)节头

包含了文件中出现的各个节的详细信息,包括节的标号、类型、大小和位置(地址与偏移量)等信息。

图4.5 hello.elf的节头

(3)重定位节

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

.rela.text 中的8 条重定位信息分别是对.L0(第一个 printf 中的字符串)、puts 函数、exit 函数、.L1(第二个 printf 中的字符串)、printf 函数、sleepsecs、sleep 函数、getchar 函数进行重定位声明。

.rela.eh_frame同理。

偏移量

代表需要进行重定向的代码在.text或.data节中的偏移位置

信息

包括symbol和type两部分,其中symbol占前半部分,type占后半部分,symbol代表重定位到的目标在.symtab中的偏移量,type代表重定位的类型

类型

重定位到的目标的类型

符号名称+加数

计算重定位位置的辅助信息

表4.1 重定位节中的信息

图4.6 hello.elf的两个重定位节

(4)符号表(Symbol table)

图4.7 hello.elf的符号表

符号表中保存着定位、重定位程序中符号定义和引用的信息,所有重定位需要引用的符号都在其中声明。

4.4 Hello.o的结果解析

生成hello.o的反汇编文件的指令为objdump -d -r hello.o

机器语言由二进制指令构成,其与汇编指令基本上一一对应。

对比两版本对hello.c源程序的转换,可以发现,反汇编文件的篇幅明显短于汇编文件,但对于具体的语句类型,汇编文件与反汇编文件几乎一致,不同点具体如下:

(1)格式上,hello.i前没有一串二进制数,即相应的机器码,而反汇编代码前面有与之对应的机器码。

(2)数据内容上,立即数在hello.i这一汇编语言文本文件中为十进制,而在反汇编代码中为十六进制。

(3)跳转方式不同。在汇编代码中,代码直接声明具体的段存储位置,通过助记符(如.L0、.L1等)存储在.rodata段中;而反汇编代码是依据地址跳转的。

(4)重定位条目不同。汇编代码仍然采用直接声明的方式,即通过助记符;而反汇编代码采用重定向的方式进行跳转,机器代码在此处留下一些地址以供链接时重定向。

图4.8 objdump生成的hello.o反汇编结果

4.5 本章小结

本章介绍了汇编的概念与作用,在Ubuntu下通过实际操作将hello.s文件翻译为hello.o文件,并生成hello.o的ELF格式文件hello.elf,研究了ELF格式文件的具体结构。通过解析hello.o文件并比较hello.o的反汇编文件hello.asm与hello.s有何区别,了解了汇编语言与机器语言的异同之处。

(第41分)

第5章 链接

5.1 链接的概念与作用

5.1.1链接的概念:

  链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可以被加载到内存并执行。链接可以执行于编译时,也就是在源代码被编译成机器代码时;也可以执行于加载时,也就是在程序被加载器加载到内存并执行时;甚至于运行时,也就是由应用程序来执行。链接执行符号解析、重定位过程。

5.1.2链接的作用:

  把可重定位目标文件和命令行参数作为输入,产生一个完全链接的,可以加载运行的可执行目标文件。使得分离编译成为可能。

5.2 在Ubuntu下链接的命令

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

用readelf查看hello的ELF格式的命令为readelf -a hello > hello_elf.txt

图5.2 用readelf查看hello的ELF格式的命令和结果

(1)ELF头:ELF头以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。ELF头剩下的部分包含帮助链接器语法分析和解释目标文件的信息。其中包括ELF头的大小、目标文件的类型、机器类型、节头部表的文件偏移,以及节头部表中条目的大小和数量。

图5.3 hello的ELF

(2)节头:节头描述了26个节的相关信息,其中一些段的作用如下:

interp段:动态链接器在操作系统中的位置不是由系统配置决定,也不是由环境参数指定,而是由ELF文件中的 .interp段指定。该段里保存的是一个字符串,这个字符串就是可执行文件所需要的动态链接器的位置,常位于/lib/ld-linux.so.2。(通常是软链接)

dynamic段:该段中保存了动态链接器所需要的基本信息,是一个结构数组,可以看做动态链接下 ELF文件的“文件头”。存储了动态链接会用到的各个表的位置等信息。

dynsym段:该段与 “.symtab”段类似,但只保存了与动态链接相关的符号,很多时候,ELF文件同时拥有 .symtab 与 .synsym段,其中 .symtab 将包含 .synsym 中的符号。该符号表中记录了动态链接符号在动态符号字符串表中的偏移,与.symtab中记录对应。

dynstr段:该段是 .dynsym 段的辅助段,.dynstr 与 .dynsym 的关系,类比与 .symtab 与 .strtab的关系 hash段:在动态链接下,需要在程序运行时查找符号,为了加快符号查找过程,增加了辅助的符号哈希表,功能与 .dynstr 类似

rel.dyn段:对数据引用的修正,其所修正的位置位于 “.got”以及数据段(类似重定位段 “rel.data”)

rel.plt段:对函数引用的修正,其所修正的位置位于 “.got.plt”。

图5.4 hello的节头

(3)程序头:如图

图5.5 hello的程序头

(4)Section to Segment mapping:如图。

图5.6

(5)Dynamic section:如图。

图5.7

(6)重定位节:.rela.dyn和.rela.plt,如图。

图5.8 重定位节

(7)符号表:.dynsym和.symtab等,存放程序中定义和引用的函数和全局变量的信息。hello程序的符号表包含编号Num、Value、Size、Type、Bind、Vis、Ndx、Name等字段。

图5.9 hello的符号表

(8)版本信息:如图。

图5.10 hello的版本信息

5.4 hello的虚拟地址空间

图5.11 使用edb加载hello的结果

edb的分析结果表明,虚拟地址空间从0x401000开始。

如.interp段地址从0x4002e0开始,偏移量为0x2e0,大小为0x1c,对齐要求为1。

5.5 链接的重定位过程分析

使用objdump查看hello汇编语言的指令为objdump -d -r hello

图5.12 使用objdump查看hello的汇编语言

hello与hello.o的区别如下:

(1)hello.o反汇编代码虚拟地址从0开始;而hello反汇编代码从0x400000开始。

(2)hello.o反汇编代码的第一个段是.text段,然后为main函数;而由于链接过程中重定位而添加进来了各种函数、数据,在hello反汇编的结果中开始的函数和调用的函数填充在main函数之前。

(3)call函数引用全局变量和跳转模块值时地址也有所变化。可执行文件跳转和应用就是相对或绝对的虚拟内存地址;而hello.o反汇编的跳转的就是只要hello数据时对应的位置。

5.6 hello的执行流程

程序名称和地址见表。

程序名称

程序地址

ld-2.27.so!_dl_start

0x7fce8cc38ea0

ld-2.27.so!_dl_init

0x7fce8cc47630

hello!_start

0x400500

libc-2.27.so!__libc_start_main

0x7fce8c867ab0

-libc-2.27.so!__cxa_atexit

0x7fce8c889430

-libc-2.27.so!__libc_csu_init

0x4005c0

hello!_init

0x400488

libc-2.27.so!_setjmp

0x7fce8c884c10

-libc-2.27.so!_sigsetjmp

0x7fce8c884b70

--libc-2.27.so!__sigjmp_save

0x7fce8c884bd0

hello!main

0x400532

hello!puts@plt

0x4004b0

hello!exit@plt

0x4004e0

*hello!printf@plt

--

*hello!sleep@plt

--

*hello!getchar@plt

--

ld-2.27.so!_dl_runtime_resolve_xsave

0x7fce8cc4e680

-ld-2.27.so!_dl_fixup

0x7fce8cc46df0

--ld-2.27.so!_dl_lookup_symbol_x

0x7fce8cc420b0

libc-2.27.so!exit

0x7fce8c889128

5.7 Hello的动态链接分析

动态链接项目中,查看dl_init前后项目变化。对于动态共享链接库中PIC函数,编译器加重定位记录,等待动态链接器处理,为避免运行时修改调用模块的代码段,链接器采用延迟绑定的策略,将过程地址的绑定推迟到第一次调用该过程。动态链接器使用过程链接表PLT+全局偏移量表GOT实现函数的动态链接,GOT中存放函数目标地址,PLT使用GOT中地址跳转到目标函数。

在dl_init调用之前,对于每一条PIC函数调用,调用的目标地址都实际指向PLT中的代码逻辑,初始时每个GOT条目都指向对应的PLT条目的第二条指令。

在dl_init调用之后,0x6008c0和0x6008c0处的两个8字节的数据分别发生改变。

和PLT联合使用时,GOT[0]和GOT[1]包含动态链接器在解析函数地址时会使用的信息。其中GOT[1]指向重定位表(依次为.plt节需要重定位的函数的运行时地址)用来确定调用的函数地址, GOT[2]是动态链接器ld-linux.so模块中的入口点。

在之后的函数调用时,首先跳转到PLT执行.plt中逻辑,第一次访问时,GOT地址为下一条指令,将函数序号压栈,然后跳转到PLT[0],在PLT[0]中将重定位表地址压栈,然后访问动态链接器,在动态链接器中使用函数序号和重定位表确定函数运行时地址,重写GOT,再将控制传递给目标函数。之后如果对同样函数调用,第一次访问跳转直接跳转到目标函数。

图5.13 调用前后的GOT变化

5.8 本章小结

本章介绍了链接的概念、作用。使用Ubuntu下的链接指令将hello.o转换为可了执行目标文件,并分析了hello的反汇编结果与hello.o的异同,其中用到了rodata中的重定位条目,并分析了程序如何实现的动态库链接。

(第51分)

第6章 hello进程管理

6.1 进程的概念与作用

6.1.1 进程的概念:

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

 6.1.2 进程的作用:

  进程为我们塑造一种假象,我们的程序好像是当前唯一运行的程序,我们的程序好像独占CPU和内存,我们程序的代码和数据好像是系统内存中唯一的内容。

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

在计算机科学中,Shell俗称壳(用来区别于核),是指“为使用者提供操作界面”的软件(命令解析器)。它类似于DOS下的command.com和后来的cmd.exe。它接收用户命令,然后调用相应的应用程序。同时它又是一种程序设计语言。作为命令语言,它交互式解释和执行用户输入的命令或者自动地解释和执行预先设定好的一连串的命令;作为程序设计语言,它定义了各种变量和参数,并提供了许多在高级语言中才具有的控制结构,包括循环和分支。

  Bash是一个命令处理器,通常运行于文本窗口中,并能执行用户直接输入的命令。Bash还能从文件中读取命令,这样的文件称为脚本。和其他Unix shell 一样,它支持文件名替换(通配符匹配)、管道、here文档、命令替换、变量,以及条件判断和循环遍历的结构控制语句。包括关键字、语法在内的基本特性全部是从sh借鉴过来的。其他特性,例如历史命令,是从csh和ksh借鉴而来。总的来说,Bash虽然是一个满足POSIX规范的shell,但有很多扩展。

  shell-bash的处理流程如下:

  (1)用户输入命令;

  (2)shell对用户输入命令进行解析,判断是否为内置命令;

  (3)若为内置命令,调用内置命令处理函数,否则调用execve函数创建一个子进程进行运行。

  (4)判断是否为前台运行程序,如果是,则调用等待函数等待前台作业结束;否则将程序转入后台,直接开始下一次用户输入命令。

    (5)shell应该接受键盘输入信号,并对这些信号进行相应处理。

6.3 Hello的fork进程创建过程

当在shell上输入./hello时,由于这个不是一个内置的shell命令,所以shell会认为hello是一个可执行目标文件,通过调用某个驻留在存储器中被称为加载器的操作系统代码来运行它。

  当shell运行一个程序时,父进程通过fork函数生成这个程序的进程。新创建的子进程几乎但不完全与父进程相同,包括代码、数据段、堆、共享库以及用户栈。父进程和新创建的子进程之间最大的区别在于他们有不同的PID。

6.4 Hello的execve过程

execve函数在当前进程的上下文中加载并运行一个新程序。

  execve函数加载并运行可执行文件filename(hello),且带参数列表argv和环境变量envp。只有当出现错误时,例如找不到filename,execve才会返回到调用程序。

  当加载器运行时,它创建一个类似与图 6-2 的内存映像。在程序头部表的引导下,加载器将可执行文件的片复制到代码段和数据段,接下来,加载器跳转到程序的入口,_start函数的地址,这个函数是在系统目标文件ctrl.o中定义的,对所有的c程序都一样。_start函数调用系统启动函数,_libc_start_main,该函数定义在libc.so里,初始化环境,调用用户层的main函数,处理main函数返回值,并且在需要的时候返回给内核。

6.5 Hello的进程执行

多个流并发地执行的一般现象被称为并发。一个进程和其他进轮流运行的概念称为多任务。一个进程执行它的控制流的一部分的每一时间段叫做时间片。因此,多任务也叫做时间分片。

  操作系统内核使用一种称为上下文切换的较高层形式的异常控制流来实现多任务。内核为每个进程维持一个上下文。上下文就是内核重启一个被抢占的进程所需得状态。

  在执行过程中,内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程,这个决策称为调度。

  hello程序与操作系统其他进程通过操作系统的调度,切换上下文,拥有各自的时间片从而实现并发运行。所以其实hello在sleep时就是这样的切换。

  程序在进行一些操作时会发生内核与用户状态的不断转换。这是为了保持在适当的时候有足够的权限和不容易出现安全问题。

  简单看hello sleep进程调度的过程:当调用sleep之前,如果hello程序不被抢占则顺序执行,假如发生被抢占的情况,则进行上下文切换,上下文切换是由内核中调度器完成的,当内核调度新的进程运行后,它就会抢占当前进程,并保存以前进程的上下文、恢复新恢复进程被保存的上下文、将控制传递给这个新恢复的进程,来完成上下文切换。

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

  当hello调用getchar的时候,实际落脚到执行输入流是stdin的系统调用read,hello之前运行在用户模式,在进行read调用之后陷入内核,内核中的陷阱处理程序请求来自键盘缓冲区的DMA传输,并且安排在完成从键盘缓冲区到内存的数据传输后,中断处理器。此时进入内核模式,内核执行上下文切换,切换到其他进程。当完成键盘缓冲区到内存的数据传输时,引发一个中断信号,此时内核从其他进程进行上下文切换回hello进程。

6.6 hello的异常与信号处理

hello执行过程中可能出现陷阱、中断、故障和终止。陷阱发生后进行内核调用,随后将控制转移给hello中的下一命令。故障发生后进行内核调用,运行故障修理程序,如果修理成功,控制返还给引起故障的命令,否则结束进程。中断发生后等待继续运行信号的输入。终止发生后进程等待被回收。

(1)不停乱按:按下普通的字符,包括字母、数字、回车、空格等等并不影响程序运行。按下的内容会被立即输出到屏幕。

(2)按下ctrl+z会暂停进程,此时按下ps会列出当前系统中进程,标明进程名称和进程号。

(3)按下jobs会列出当前shell环境中已启动的任务状态。

(4)按下pstree会以树状图显示进程间的关系。

(5)按下fg会将进程调到前台。

6.7本章小结

shell接收用户输入的命令行,执行对应的内置命令或代表用户执行对应的可执行程序。程序运行被抽象为进程,每个进程运行在独立的进程上下文中,进程之间切换涉及进程切换的具体原理、细节。

shell执行程序的方式为构造要传递给execve函数的字符串组,fork子进程,并设置进程组id,然后调用execve函数,根据传入的字符串组复制可执行程序到内存执行。

程序执行可分为前台和后台执行两种。键盘输入信号可影响程序的执行,或查看当前shell中运行的各个进程信息。

(第61分)

第7章 hello的存储管理

7.1 hello的存储器地址空间

7.1.1 逻辑地址的概念:

  在有地址变换功能的计算机中,访问指令给出的地址 (操作数) 叫逻辑地址,也叫相对地址。要经过寻址方式的计算或变换才得到内存储器中的物理地址。

  7.1.2 物理地址的概念:

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

  7.1.3 虚拟地址的概念:

  CPU启动保护模式后,程序运行在虚拟地址空间中。注意,并不是所有的“程序”都是运行在虚拟地址中。CPU在启动的时候是运行在实模式的,Bootloader以及内核在初始化页表之前并不使用虚拟地址,而是直接使用物理地址的。

  7.1.4 线性地址的概念:

  线性地址是逻辑地址到物理地址变换之间的中间层。在分段部件中逻辑地址是段中的偏移地址,然后加上基地址就是线性地址。

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

一个逻辑地址由两部分组成:段标识符和段内偏移量。段标识符是一个16位长的字段组成,称为段选择符,其中前13位是一个索引号。后面三位包含一些硬件细节。

索引号,是一个数组下标,它对应的“数组”就是段描述符表,段描述符具体描述了一个段地址,这样,很多段描述符就组成段描述符表。可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段。

这里面最重要的是Base字段,它描述了一个段的开始位置的线性地址。

Intel设计的本意是,一些全局的段描述符,就放在“全局段描述符表(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取出虚拟页号,通过页表基址寄存器(PTBR)来定位页表条目,在有效位为1时,从页表条目中取出信息物理页号(PPN),通过将物理页号与虚拟页偏移量(VPO)结合,得到由物理地址(PPN)和物理页偏移量(PPO)组合的物理地址。

虚拟地址到物理地址的转换

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

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

  TLB是一个小的、虚拟寻址的缓存,其中每一行都保存着一个由单一PTE组成的块。TLB通常有高的相联度,从虚拟地址中的页号提取出组选择和行匹配的索引和标记字段。

  因为所有的地址翻译都是在芯片上的MMU中进行的,因此非常快。

  多级页表:将虚拟地址的VPN划分为相等大小的不同的部分,每个部分用于寻找由上一级确定的页表基址对应的页表条目。如下图,VPN被分为k个部分,第一级VPN结合基址寄存器得到一个页表条目,其中存放下一级页表的基址,再结合VPN2,得到第三级页表基址,继续寻找,以此类推,直到最后确定对应的物理页号,与VPO结合,由图7.3,得到由PPN与PPO结合成的物理地址,用于物理地址寻址。

如果是二级页表,第一级页表的每个PTE负责一个4MB的块,每个块由1024个连续的页面组成。二级页表每一个PTE负责一个4KB的虚拟地址页面。这样的好处在于,如果一级页表中有一个PTE是空,那么二级页表就不会存在,这样会有巨大的潜在节约,因为4GB的地址空间大部分都是未分配的。

现在的64位计算机采用4级页表,36位的VPN被封为4个9位的片,每个片被用作一个页面的偏移,CR3寄存器包含L1页表的物理地址。VPN1提供到一个L1PET的偏移量,这个PTE包含L2页表的基地址,VPN2提供一个到L2PTE的偏移量,以此类推。

Core i7是四级页表进行的虚拟地址转物理地址。48位的虚拟地址的前36位被分为四级VPN区。结合存放在CR3的基址寄存器,由前面多级页表的知识,可以确定最终的PPN,与VPO结合得到物理地址。

多级页表的原理

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

对于一个虚拟地址请求,首先将去TLB寻找,看是否已经在TLB中缓存。如果命中的话就直接MMU获取,没有命中的话就先在结合多级页表,得到物理地址,去cache中找,到了L1里面以后,寻找物理地址又要检测是否命中,不命中则紧接着寻找下一级cache L2,接着L3。这里就是使用到CPU的高速缓存机制了,一级一级往下找,直到找到对应的内容。

7.6 hello进程fork时的内存映射

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

7.7 hello进程execve时的内存映射

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

(1)删除已存在的用户区域。删除当前进程(shell)虚拟地址的用户部分中的已存在的区域结构。

(2)映射私有区域。为hello的代码、数据、bss和栈区域创建新的区域结构。所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为hello 文件中的.text和.data区。bss 区域是请求二进制零的,映射到匿名文件,其大小包含在hello 中。栈和堆区域也是请求二进制零的,初始长度为零。

(3)映射共享区域。如果hello程序与共享对象(或目标)链接,比如标准C 库libc. so,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内。

(4)设置程序计数器(PC)。execve做的最后一件事情就是设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。

下一次调度hello进程时,它将从这个入口点开始执行。Linux将根据需要换入代码和数据页面。

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

虚拟内存中,DRAM缓存不命中称为缺页。如图7-13,CPU需要引用VP3中的一个字,通过读取PTE3,发现有效位为0,说明不在内存里,这时就发生了缺页异常。缺页异常发生时,通常会调用内核里的缺页异常处理程序,该程序会选择一个牺牲页,这里是存放在PP3的VP4,如果VP4已经被修改,内核就会将它复制回磁盘。无论哪种情况,内核都会修改VP4的页表条目,反映出VP4不再缓存在内存里。

接下来,内核从磁盘复制VP3到内存中的PP3,更新PTE3,随后返回。当异常处理程序返回时,它会重新启动导致缺页的指令。

缺页处理程序不是直接就替换,它会经过一系列的步骤:

(1)虚拟地址是合法的吗?如果不合法,它就会触发一个段错误。

(2)试图进行的内存访问是否合法?即进程是否有读写或者执行这个区域的权限。

经过上述判断,这时才能确定这是个合法的虚拟地址,然后才会执行上述的替换。

7.9动态存储分配管理

程序中可以进行动态内存分配的内存区域称为堆,堆的管理方法有隐式空闲链表、显式空闲链表、分离的空闲链表等等。

隐式空闲链表即在每个分配块和空闲块的头尾两个单字大小的区域写入块的大小信息和分配信息,这样,块之间就由块大小以及分配信息隐式的连接起来。分配块的时间和堆中所有块数量成正比。

隐示空闲链表

显式空闲链表即在空闲块中存入指向前后空闲块的指针。显式空闲链表的空闲块释放可以采用插入头部策略和根据地址顺序插入链表两种方式。

显示空闲链表

邻接的空闲块需要合并,为了提高程序效率可以采用分配时合并的方式。

如果堆中已经没有满足申请大小的空闲块,则扩展堆,然后再找符合条件的空闲块。

7.10本章小结

本章整理了有关内存管理的知识,介绍了在hello运行的64位系统中内存管理方法,虚拟内存和物理内存之间的关系,了解intel环境下的段式管理和页式管理、fork和execve的内存映射,介绍了缺页故障和缺页中断管理机制,了解了如何根据缓存或页表寻找物理内存。

(第7 2分)

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

设备的模型化:文件;

设备管理:unix io接口;

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

8.2 简述Unix IO接口及其函数

8.2.1 Unix I/O接口统一操作:

  设备可以通过Unix I/O接口被映射为文件,这使得所有的输入和输出都能以一种统一且一致的方式来执行:

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

  Linux shell创建的每个进程开始时都有三个打开的文件: 标准输入(描述符为0)、标准输出(描述符为1)和标准错误(描述符为2)。头文件<unistd.h>定义了常量STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO,它们可以用来代替显式的描述符值。

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

  读写文件。 一个读操作就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增加到k+n。给定一个大小为m字节的而文件,当k>=m时执行读操作会触发一个成为end-of-file(EOF)的条件,应用程序能检测到这个条件。在文件结尾处并没有明确的“EOF符号”。类似一个写操作就是从内存中复制n>0个字节到一个文件,从当前文件位置k开始,然后更新k。

  关闭文件。 当应用完成了对文件的访问之后,它就通知内核关闭这个文件。作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。无论一个进程因为何种原因终止时,内核都会关闭所有打开的文件并释放它们的内存资源。

  8.2.2 Unix I/O接口函数:

  (1)进程是通过调用open函数来打开一个存在的文件或者创建一个新文件的,函数声明如下:

  int open(char *filename, int flags, mode_t mode);

  open函数将filename转换为一个文件描述符,并且返回描述符数字。返回的描述符总是在进程中当前没有打开的最小描述符。flags参数指明了进程打算如何访问这个文件。mode参数指定了新文件的访问权限位。作为上下文的一部分,每个进程都有一个umask,它是通过调用umask函数来设置的。当进程通过带某个mode参数的open函数调用来创建一个新文件时,文件的访问权限位被设置成mode&~umask。

  (2)进程通过调用close函数关闭一个打开的文件。函数声明如下:

  int close(int fd);

  成功返回0错误返回EOF

  (3)应用程序是通过分别调用read和write函数来执行输入和输出的。函数声明如下:

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

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

  read函数从描述符为fd的当前文件位置赋值最多n个字节到内存位置buf。返回值-1表示一个错误,0表示EOF,否则返回值表示的是实际传送的字节数量。write函数从内存位置buf复制至多n个字节到描述符为fd的当前文件位置。

  (4)通过调用lseek函数,应用程序能够显式地修改当前文件的位置。函数声明如下:

  off_t lseek(int handle, off_t offset, int fromwhere);

8.3 printf的实现分析

形参列表中的…是可变形参的一种写法,当传递参数的个数不确定时,用这种方式来表示。

va_list的定义如下:typedef char *va_list,说明它是一个字符指针,其中 (char*)(&fmt) + 4) 即arg表示的是...中的第一个参数。

vsprintf程序按照格式fmt结合参数args生成格式化之后的字符串,并返回字串的长度。

在printf中调用系统函数write(buf,i)将长度为i的buf输出。write函数如下:

printf函数的功能为接受一个格式化命令,并按指定的匹配的参数格式化输出,故i = vsprintf(buf, fmt, arg)是得到打印出来的字符串长度,其后的write(buf, i)是将buf中的i个元素写到终端。

因此,vsprintf的作用为接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,进而产生格式化输出。

syscall将字符串中的字节从寄存器中通过总线复制到显卡的显存中,显存中存储的是字符的ASCII码,符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。

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;

}

8.4 getchar的实现分析

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

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

getchar调用系统函数read,发送一个中断信号,内核抢占这个进程,用户输入字符串,键入回车后(字符串和回车都保存在缓冲区内),再次发送信号,内核重新调度这个进程,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;

}

8.5本章小结

       本章主要介绍了linux的IO设备管理方法和及其接口和函数,并对printf函数和getchar函数的底层实现做了一定的分析。

(第81分)

结论

hello程序从一个高级语言程序hello.c变成可执行的目标文件hello(P2P)、由子进程开始执行到子进程结束被回收(020)的生命周期如下:

(1)预处理:

将hello.c中include的所有外部的头文件头文件内容直接插入程序文本中。完成字符串的替换,方便后续处理;

(2)编译:

通过词法分析和语法分析,将合法指令翻译成等价汇编代码。通过编译过程,编译器将hello.i 翻译成汇编语言文件 hello.s;

(3)汇编:

将hello.s汇编程序翻译成机器语言指令,并把这些指令打包成可重定位目标程序格式,最终结果保存在hello.o 目标文件中;

(4)链接:

通过链接器,将hello的程序编码与动态链接库等收集整理成为一个单一文件,生成完全链接的可执行的目标文件hello;

(5)加载运行:

打开shell,在其中键入 ./hello <学号> <姓名>,终端为其fork新建进程,并通过execve把代码和数据加载入虚拟内存空间,程序开始执行;

(6)执行指令:

在该进程被调度时,CPU为hello其分配时间片,在一个时间片中,hello享有CPU全部资源,PC寄存器一步一步地更新,CPU不断地取指,顺序执行自己的控制逻辑流;

(7)访存:

内存管理单元MMU将逻辑地址,一步步映射成物理地址,进而通过三级高速缓存系统访问物理内存/磁盘中的数据;

(8)动态申请内存:

printf 会调用malloc 向动态内存分配器申请堆中的内存;

(9)信号处理:

进程时刻等待着信号,如果运行途中键入ctr-c ctr-z 则调用shell 的信号处理函数分别进行停止、挂起等操作,对于其他信号也有相应的操作;

(10)终止并被回收;

shell父进程等待并回收hello子进程,内核删除为hello进程创建的所有数据结构。

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

附件

所有中间产物的名称与功能见下表:

文件名

功能

hello.c

源程序

hello.i

预处理后得到的文本文件

hello.s

编译后得到的汇编语言文件

hello.o

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

hello

链接后得到的可执行目标文件

hello.elf

用readelf读取hello.o得到的ELF格式信息

hello_elf.txt

用readelf读取hello得到的ELF格式信息

hello.asm

反汇编hello.o得到的反汇编文件

hello2.asm

反汇编hello可执行文件得到的反汇编文件

(附件0分,缺失 -1分)

参考文献

[1]  Randal E. Bryant,David O'Hallaron,《深入理解计算机系统》[M](原书第三版),北京:机械工业出版社,2021:10-1.

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

哈工大2021~2022春季学期《计算机系统》大作业相关推荐

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

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

  2. 2022年春季学期计算机系统大作业——程序人生心得体会

    计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 计算学部 学    号 120L021801 班    级 2003006 学       生 郑卓 指 导 ...

  3. 哈尔滨工业大学2022春季学期CSAPP大作业

    此大作业涵盖CSAPP一大本书的内容,可以很好地帮助复习和理解其中的内容. 计算机系统 大作业 题 目 程序人生-Hello's P2P 专 业 计算学部 学 号 120L022308 班 级 200 ...

  4. 2021春深入理解计算机系统大作业---hello的一生

    计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 计算学部 学    号 120L021725 班    级 2003006 学       生 杨楠 指 导 ...

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

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

  6. 2022春季学期哈工大计算机系统大作业

    计算机系统 计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 计算学部 学   号 120L022013 班   级 2003007 学       生 王炜栋 ...

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

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

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

    计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 人工智能(未来技术) 学   号 120L020301 班   级 2036011 学       生 张思远 ...

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

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

最新文章

  1. 毫末智行 Fluid 实践:云原生 AI 让汽车变得“更聪明”
  2. 【2】thinkphp 3.2.3简单介绍
  3. 学成在线--11.RabbitMQ快速入门
  4. 世界上没有一模一样的东西_免费是世界上最昂贵的东西
  5. 艾孜尔江用JS/TS生成随机姓名
  6. Python用户画像词云图生成并集成到django网站
  7. 上海宝付知识普及:个人信用到底多重要?
  8. SpringMVC引入静态org.webjars中资源404
  9. DevOps八荣八耻了解下,哈哈~
  10. java学习就去这56个网站论坛(附各种java学习公众号)
  11. backdrop-filter filter
  12. php文件是不是死链,怎么判断网站的链接是不是死链接? 百度搜索标准死链官方文档...
  13. Process finished with exit code 1 解决方法
  14. BZOJ - 3687
  15. 2509-Druid监控功能的深入使用与配置-基于SpringBoot-完全使用 .properties配置文件
  16. dumpsys activity 查看系统运行了哪些任务?
  17. 自然数学-对数性质证明
  18. 悬垂指针(Dangling pointer)和野指针(Wild pointer)
  19. Android hook方式抓包
  20. 性能测试跑分软件,AE Benchmark(AE性能测试跑分工具) V1.0 免费版

热门文章

  1. 常用标点符号的中英对照
  2. 目前中国顶级优秀的程序员都有谁?
  3. drupal 7 启用 签名设置 filtered html,php – 如何在Drupal 7中为节点设置自定义字段值?...
  4. 物联网服务器通信协议选择,物联网都有哪些通信协议,该如何去选择
  5. 深读 |《人人都是产品经理2.0》上篇
  6. 键盘win键没反应?估计是锁了
  7. 长三角是指哪几个城市
  8. 5G基站外市电改造建设方案 (ppt可编辑)
  9. Blink内存分配器PartitionAlloc
  10. 马云+牛根生+史玉柱 经典语录