摘  要

本文通过分析一个给定的hello.c程序的完整的生命周期(即从该程序最开始的预处理,到被编译器(gcc)编译,再到进入汇编器进行汇编,再到链接器执行链接,以及在进程中运行等过程),以及概括hello.c程序的内存管理、进程管理以及I/O管理等深层次内容,展示了Linux系统对c程序进行处理的一套完整的过程,即一个c程序在Linux操作系统下的完整的生命周期。

关键词:Linux操作系统;c程序;生命周期;

目  录

第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:这里的P2P是From Program to Process的简写,即从源文件到目标文件的转化以及最终的运行的过程。转化过程是由编译器驱动程序完成的, 即在这一过程中GCC编译器驱动程序读取实现用文本编辑工具或者C语言编辑器编写好的源文件hello.c,并把它编译成一个可执行目标文件hello。这里的编译可大体分为以下四个阶段完成。①首先,预处理器(cpp)根据以字符#开头的命令(编译预处理指令),对编写好的C程序进行处理,实现了将hello.c文件转化为hello.i文件的过程;②编译器(ccl)将文本文件hello.i内部的C程序翻译成为汇编程序并输出到文本文件hello.s中;③汇编器(as)将hello.s翻译成机器语言指令,把这些指令以可重定位目标程序的格式存储在目标文件hello.o中;④通常的c程序会调用一些库函数(比如这里的hello程序调用了printf函数,即C标准库内的一个函数),因此要将对应的目标代码或库文件通过链接器进行链接(这里是printf函数对应的printf.o文件用链接器(ld)合并到我们hello.o程序中,结果得到hello文件,即可执行文件)。运行过程主要是Linux系统中通过内置命令行解释器shell加载运行hello程序,为hello程序创建fork进程,并在进程中进行程序执行。

图1-1 C程序转化过程

020:020是From Zero to Zero的简写。在开始时程序不占用系统资源,随着虚拟内存的访问程序开始载入物理内存,shell通过execve在fork产生的子进程中加载并执行hello,先删除当前虚拟地址的用户部分已存在的数据结构,虚拟内存机制通过mmap为hello的代码段、数据、bss以及栈区域规划空间,而后在执行过程中CPU不断从.text和.data节中读取代码和数据,且这一过程中调度器为hello分配时间片执行逻辑控制流。hello通过Unix I/O管理来控制输出。如果程序执行出现异常会触发异常处理子程序。hello执行完成后shell父进程会回收hello进程,并且内核会从系统中删除hello所有痕迹。总结这一过程hello从开始不占用系统资源到被清除痕迹——即表达为From Zero to Zero。

1.2 环境与工具

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

软件环境:Windows7 64位以上;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64位

开发与调试工具:Visual Studio 2010 64位以上;GDB/OBJDUMP;DDD/EDB等

1.3 中间结果

hello.i 经预处理器产生的文本文件
hello.s 编译器产生的汇编文件
hello.o 汇编器产生的可重定位目标文件
hello 链接器产生的的可执行目标文件
hello-objdump.txt Hello.o 的反汇编代码
hello-elf.txt Hello.o 的 ELF 文件

1.4 本章小结

本章分析了C程序的P2P与020的特点,列出进行实验时的软硬件环境及开发与调试工具,以及在本文中生成的中间结果文件。

第2章 预处理

2.1 预处理的概念与作用

2.1.1概念:

预处理器(cpp)根据以字符#开头的命令,修改原始的C程序。(出自深入理解计算机系统-原书第3版,龚奕利,贺莲译)

图2-1:hello.c中待预处理的内容

2.1.2作用:

C语言的编译预处理主要包括:文件包括(#include)、宏定义(#define)以及条件编译。

编译预处理是C语言编译程序的组成部分,用于解释处理C语言源程序中的各种预处理指令,形式上都以#开头,这些语句不属于C语言中真正的语句,但增强了C语言的编程功能,提高了编程效率,并在一定程度上可以增强代码的可读性。

由于#define等编译预处理指令不是语句,不能被编译程序翻译,需要在真正编译之前做一个预处理,解释完成编译预处理指令,从而把预处理指令转换成相应的C程序段,最终称为由纯粹C语句构成的程序,经编译后得到目标代码。

所有编译预处理指令都在编译预处理步骤中起作用,与程序真正运行过程无关。

2.2在Ubuntu下预处理的命令

在Ubuntu下,通过gcc指令“gcc -E hello.c -o hello.i”生成预处理后的hello.i文件。

图2-2:gcc生成hello.i

2.3 Hello的预处理结果解析

打开gcc通过编译预处理生成的hello.i文件,可以看到预处理器将预处理指令展开,声称在hello.i文件的首部,尾部是hello.c中的main函数部分,可以看到,因为还未经编译,这部分仍是C代码。

图2-3:hello.i文件的首部分和尾部分

2.4 本章小结

本章简述了在编译前进行的预处理过程(即hello.c文件经过预处理生成hello.i的过程),展示了hello.i文件中对宏定义、文件包含、条件编译进行的处理效果。

第3章 编译

3.1 编译的概念与作用

3.1.1概念:

编译器(ccl)将文本文件翻译成包含一个汇编语言程序的文本文件的过程。(出自深入理解计算机系统-原书第3版,龚奕利,贺莲译)

3.1.2作用:

编译阶段的作用大致在以下几个阶段中体现出来:

  • 词法分析:编译过程的第一个阶段是词法分析阶段。在这一阶段编译器会从左到右逐个字符地读入待编译的C程序,即对构成程序的字符流进行扫描然后根据构词规则识别单词(也称单词符号或符号)。
  • 语法分析:编译过程的一个逻辑阶段被称作语法分析阶段。语法分析的任务是在上述的词法分析阶段的基础上将读入的单词序列组合成各类语法短语。语法分析程序判断源程序在结构上是否正确,即C程序在结构上的错误可在这一阶段被检查出来。在这一阶段,C程序的结构由上下文无关文法(CFG)描述。
  • 语义分析:语义分析是编译过程的另一个逻辑阶段. 语义分析的任务是对结构上正确的源程序进行上下文有关性质的审查, 进行类型审查,这一阶段往往可以对源程序在语义上进行静待检查。
  • 优化与代码生成:对C程序做一定程度的优化后生成相应的汇编代码,输出到.s文件中,以待后续进入汇编器进行汇编。

总结下来,编译阶段的主要作用首先是前三个阶段对源程序进行静态检查,然后是会对代码进行一定程度的保守的优化,最后将源代码转化为汇编代码,以待后续操作的进行。

图3-1:编译的四个阶段

3.2 在Ubuntu下编译的命令

使用gcc指令“gcc -S hello.i -o hello.s”进行编译

图3-2:gcc编译生成hello.s

3.3 Hello的编译结果解析

3.3.1汇编指示(AD)

在hello.s文件的开头,可以看到以“.”开头的诸多指令,称作汇编指示(或伪指令)。

图3-3:汇编代码中的伪指令

3.3.2数据定义

①全局变量sleepsecs

如图3-4,在hello.c中,可以看到全局int变量sleepsecs的定义,对应图3-3中,使用汇编指示“.globl”定义,“.data”注明将之存放在.data段中,“.align 4”表明双字对齐,“.size”赋变量大小4字节(对应int类型的大小),“.long”将sleepsecs赋值为2(对应C语言赋值2.5由于是int类型,编译器自动做了隐式转换)。

图3-4:hello.c中的全局变量

②局部变量i

如图3-5,在hello.c中可以看到局部变量i未赋初值的定义方式,对应于图3-7所示的汇编代码中,直至for循环开始赋初值才将0传到-4(%rbp),也能从此看出局部变量i被存储在栈中。

图3-5:hello.c中的局部变量i。

图3-6:for循环中的局部变量i。

图3-7:局部变量i在for循环中赋初值

③常量

在图3-5中可看到for循环中存在常量10,对应编译产生的汇编代码是作为立即数9在cmpl中进行使用的(如图3-8)。

图3-8 hello.s中的for循环

  • 字符串

如图3-9所示,在hello.c中存在两个printf,设计到两个字符串常量,对应在图3-3所示的汇编指示中,通过“.string”进行定义与赋值。

图3-9 hello.c中的printf

3.3.3赋值操作

hello.c中涉及到的赋值操作即图3-6,3-7所示的for循环中对i赋初值的操作,使用movl指令将立即数0传到-4(%rbp)。

3.3.4类型转换

hello.c中涉及到的类型转换只有将浮点值2.5赋值给int类型变量sleepsecs时进行的隐式类型转换,在3.3.2节以提到,编译器在编译阶段直接进行了类型转换,导致生成的汇编代码中直接将转换后的值2赋值给对应的变量sleepsecs。

3.3.5算术操作

如图3-10,hello.s中的算术操作主要是for循环中的i++操作(对应addl指令)以及调用printf计算格式串地址时的地址计算(对应leaq指令)。

图3-10:hello.s中涉及到算术运算的部分

3.3.6关系操作

hello.c中涉及到的关系操作主要在如图3-11所示的16行if判断“argc!=3”,以及图3-9所示的for循环中的“i<10”。对应的hello.s中的汇编代码分别如图3-12和3-8所示,通过相应的条件跳转实现(这一点在后文的控制转移还会提到)。

图3-11:hello.c中的if语句

图3-12:hello.s中“argc!=3”对应的汇编代码

3.3.7数组操作

hello.c中涉及到的数组操作主要在于printf的格式串中两个占位符“%s”对应于传入的数组argv[]的第2和第3个元素(如图3-9所示),对应的汇编代码如图3-13所示。根据函数调用的参数传递规则(在后文函数部分亦有所提及)可知:寄存器rsi中存储第一个占位符对应的值argv[1],寄存器rdx存储的是argv[2],观察汇编代码可知,-32(%rbp)存放的是argv[ ]的首地址,而argv[ ]的元素(存储字符串)为字符指针类型,所以其元素大小为8字节,所以argv[1]的地址对应的偏移量为8,argv[2]偏移为16。

图3-13:hello.s中对应数组的操作

3.3.8控制转移

hello.c中涉及到的控制转移主要是图3-11和图3-9所示的for循环和if语句中,对应的汇编代码如图3-8,3-12所示,使用了cmpl和je指令实现条件转移,以达成所需的条件判断和循环跳转。

3.3.9函数操作

hello.c中涉及的函数共有5种,即主函数main,函数printf,exit,sleep以及函数getchar。

  • 参数传递

涉及到参数传递的函数有主函数main(argc,argv[]),printf(“格式串”,参数2,参数3,…)以及exit(1)和sleep,其中main函数的参数传递在进入main函数前就完成了,后三个函数的参数传递对应的汇编代码如图3-14,3-15所示。

图3-14:printf和exit函数的参数传递

图3-15:sleep的参数传递

从图3-14和可以看出,exit的参数由movl将立即数1传入寄存器edi中进行实现,printf的3个参数(即格式串+两个占位符对应的变量),结合上文3.3.7的分析,可以看到三个参数分别传入了rdi,rsi,rdx寄存器。

  • 函数调用

由图3-14,3-15也表明了编译器在识别出源程序中函数调用时,对应于汇编代码中使用call指令进行函数的调用

  • 函数返回

如图3-16所示,函数返回时,若有返回值,则先将返回值传递到寄存器rax中,再使用leave和ret指令返回,其中leave指令用以平衡堆栈,相当于mov ebp,esp  和pop ebp,ret相 当于pop rip,即将%rip设置为下一条执行语句。

图3-16:main函数返回0

3.4 本章小结

本章首先简述了在预处理之后编译器进行编译的过程,通过.i文件生成.s文件,通过分析hello.s文件中存储的汇编代码,对hello.c的编译结果进行了较为详细的系统化的解析。

第4章 汇编

4.1 汇编的概念与作用

4.1.1概念:

汇编器(as)将.s文件翻译成机器语言指令,把这些指令打包成可重定位目标程序的格式,并将结果保存在目标文件.o的过程。(出自深入理解计算机系统-原书第3版,龚奕利,贺莲译)

4.1.2作用:

将汇编语言翻译成机器语言指令,以便链接后的程序运行。

4.2 在Ubuntu下汇编的命令

如图4-1所示,按要求输入gcc指令“gcc -no-pie -fno-PIC -c hello.c -o hello.o”进行汇编。

图4-1:ubuntu下进行汇编

4.3 可重定位目标elf格式

首先如图4-2所示,使用readelf将elf信息输出到helloelf.txt中

图4-2:使用readelf生成elf文件

图4-3:ELF头

4.3.1 ELF头

如图4-3所示,生成的helloelf.txt的开头为ELF头,包含的信息如下:

  • Magic:前四个字节 7f ,45,4c,46分别对应ascii码中的Del、“E”、“L”、“F”,操作系统在加载可执行文件时会确认这四个字节是否正确,如果不正确则拒绝加载。 第五个字节标识ELF文件是32位(01)还是64位(02)的。 第六个字节标识该ELF文件字节序是小端(01)还是大端(02)的。 第七个字节指示ELF文件的版本号,一般是01。 后九个字节ELF标准未做定义。一般为00。
  • 操作系统/ABI:Unix-System V
  • 文件类型:REL(可重定位文件)
  • 系统架构:x86-64
  • 入口地址:程序在虚拟地址的何处开始运行(这里是0x0)
  • ELF头大小:64字节
  • 节头部表的大小:64字节
  • 节的个数:14
  • 节头部表索引:13

4.3.2节头部表

如图4-4所示,ELF头下面的部分是节头部表,写明了各个节(这里包含.text,.rela.text,.data,.bss…等14个节)的大小,类型,地址以及偏移量的信息。

图4-4 节头部表

4.3.3剩余部分

如图4-5所示,helloelf.txt中剩余的部分是.rela.text节和.rela.eh_frame节中的信息。其中.rela.text节中包含.text节中需要重定位的信息,每一行由一条重定位条目组成,在后续链接的过程中,这些行所对应的符号名称部分代表需要进行重定位的位置。而.rela.eh_frame节在本门课程中未有详细介绍。

图4-5 ELF文件的剩余部分

4.4 Hello.o的结果解析

对汇编生成的hello.o进行反汇编,生成的内容如图4-6所示。

图4-6 hello.o的反汇编

4.4.1对照分析:

对反汇编hello.o得到的汇编代码和第三章通过编译hello.i得到的汇编代码(hello.s)进行对照分析,可得到以下结论:

①在内容上,首先后者在代码前无对应的机器代码,而前者具有,由此得到汇编代码与机器代码存在一一对应的映射关系;其次前者中有很多以“.”开头的汇编指示(伪指令),用来指导汇编器和链接器对代码进行操作,而后者中没有。

②在操作数上,后者在代码中不涉及操作数大小(如q、l)的后缀,而前者往往具有;

③在分支转移上,前者使用跳转指令时,是直接指令+确定地址而后者是设置标签,以指令+标签的形式构建代码。

④在函数调用上,前者用call指令+数字表示,查看机器代码发现有PC相对引用的占位符(等待链接为其重定位具体的地址),而后者是指令+函数名的形式。

4.4.2机器语言的构成:

机器语言由二进制的机器指令序列集合构成,机器指令由操作码和操作数组成。

4.4.3机器语言与汇编语言的映射关系:

由4.4.1节的分析,机器语言与汇编语言具有一条汇编语言对应一条机器指令的映射关系。

4.5 本章小结

本章主要探究了汇编阶段(从hello.s到hello.o)这一过程,分析了可重定位目标文件的ELF文件的相关信息,并比较了hello.o对应的反汇编代码和hello.s代码之间的差异。

第5章 链接

5.1 链接的概念与作用

5.1.1概念:

链接是将各种代码和数据片段收集并组合成为一个单一文件的过程。(出自深入理解计算机系统-原书第3版,龚奕利,贺莲译,5.1.2同)

5.1.2作用:

链接使得分离编译成为可能,能够将一个大型的应用程序分解成为更小、更好管理的模块,可以独立地修改和编译这些模块。当我们改变这些模块中的一个时,只需简单地重新编译它,并重新链接应用,而不必重新编译其他文件。

5.2 在Ubuntu下链接的命令

输入ld的链接命令“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-1:ld链接命令

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

使用指令“readelf -a hello > helloelf.txt”将可执行目标文件hello的ELF信息输出到helloelf.txt中。

5.3.1 elf头

Elf头如图5-2所示,与hello.o的ELF格式相对比,发现可执行文件hello的ELF头中类型信息变为EXEC(可执行文件),同时入口点地址和程序头起点被确定,节头数量、字符串表索引节头也发生了变化。

图5-2:helloelf.txt中的elf头

5.3.2节头部表

如图5-3所示,和先前所示的hello.o的elf信息中类似,hello的elf信息中位于ELF头下方的节头部表包含了各节的基本信息。(位置,大小,程序中偏移量等)

图5-3:helloelf.txt中各节的基本信息

5.3.3程序头部表

如图5-4所示,helloelf.txt之后的内容是包含各个段基本信息的程序头部表(Program Header Table)以及段与节的对应表(Section to Segment mapping,显示每个段中包含了哪些节),这一部分在hello.o的ELF信息中所不具有。

图5-4:helloelf.txt中各段的基本信息及段与节的对应表

5.3.4符号表

最后是符号表,如图5-5所示,保存了程序实现或使用的所有(全局)变量和函数,这一部分也是链接前的hello.o的ELF信息中所不具有的。

图5-5 helloelf.txt中的符号表

5.4 hello的虚拟地址空间

使用edb加载hello,查看本进程的虚拟地址空间各段信息(通过 edb 的 Data Dump 窗口查看虚拟地址空间),由5.3的分析可知,代码段对应于第一个LOAD段,对应的偏移量为0,因此使用edb找到对应位置,如图5-6所示。(同理.init,.test,.rodata段等也在这部分中)

图5-6 代码段所在虚拟地址空间

5.5 链接的重定位过程分析

使用objdump对hello进行反汇编,将得到的汇编代码输出到helloobjdump.text中,如图5-7所示。

图5-7 objdump生成的hello的反汇编代码

5.5.1 与hello.o反汇编得到的汇编代码的区别

区别大体在于以下两点:

  • hello的反汇编代码相比于hello.o的反汇编代码多了.init节(在程序的初始化时调用)、.plt节(动态链接时用到)、.fini节(程序终止时调用),以及在hello中调用的函数(printf、sleep、getchar、exit等函数)。
  • hello相比于hello.o,反汇编代码中的可重定位条目都替换成了确定的地址。

5.5.2 hello进行重定位的过程

在链接器完成符号解析后,就把代码中的每个符号引用和正好一个符号定义关联起来。此时,链接器就知道它的输入目标模块中的代码节和数据节的确切大小。此时开始进行重定位,由以下两步组成:

①重定位节和符号定义:在这一步中,链接器将所有相同类型的节合并为同一类型的新的聚合节。例如,来自所有输入模块的.data节被全部合并成一个节,这个节成为输出的可执行目标文件的.data节,然后,链接器将运行时内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号。当这一步完成时,程序中的每条指令和全局变量都有唯一的运行时内存地址了。

②重定位节中的符号引用:在这一步中,链接器修改代码节和数据节中对每个符号的引用,使得他们指向正确的运行时地址。要执行这一步,链接器依赖于可重定位目标模块中称为重定位条目的数据结构。

而第二步中计算地址由ELF定义了32种不同的重定位类型,在本门课程中主要考虑两种:PC相对地址的引用以及32位绝对地址的引用。其中两种对应的算法分别为:

*refptr=(ADDR(r.symbol)+r.addend-refaddr)以及

*refptr= (ADDR(r.symbol)+r.addend)。

对应于hello中的重定位,如图5-8调用getchar(),这里通过计算*refptr= 0xffffff21,发现正好对应于图中的指令。

图5-8:hello中对getchar函数的调用

5.6 hello的执行流程

如下所示,执行流程中的函数(左)以及对应的地址(又)

ld-2.27.so!_dl_start 0x7f96ed2e1ea0

ld-2.27.so!_dl_init 0x7f96ed2f0630

hello!_start 0x400500

libc-2.27.so!__libc_start_main 0x7fbdf0cccab0

hello!puts@plt 0x4004b0

hello!exit@plt 0x4004e0

hello!printf@plt 0x4004c0

hello!sleep@plt 0x4004f0

hello!getchar@plt 0x4004d0

libc-2.27.so!exit 0x7f66b7b9e120

5.7 Hello的动态链接分析

由5.6节所示hello的执行流程中涉及到调用共享库函数(puts,exit,printf,sleep,getchar等),由图5-9所示,此处调用puts函数是hello中第一次对共享库函数的调用。

图5-9:hello中首次调用共享库函数

本课程的参考书《深入理解计算机系统》对动态链接过程的阐述如下:

1.PIC全局数据引用:

编译器在数据段开始的地方创建了一个表,叫做全局偏移量表(GOT),在GOT中,每个被这个目标模块引用的全局数据目标(过程或全局变量)都有一个8字节条目。编译器还为GOT中每个条目生成一个重定位记录,在加载时,动态链接器会重定位GOT中的每个条目,使得它包含目标正确的绝对地址。每个引用全局目标的目标模块都有自己的GOT

2.PIC函数调用:

GNU系统使用一种被称为延迟绑定的技术来动态链接共享库中的函数。这个技术通过两个数据结构之间简洁但又有些复杂的交互来实现,即GOT和过程链接表(PLT),如果一个目标模块调用定义在共享库中的任何函数,GOT是数据段的一部分,PLT是代码段中的一部分。

在程序运行前到开始运行,分别使用edb观察GOT的内容,如图5-11和图5-12,发现在dl_start执行后,GOT表的数据发生改变。

当程序调用动态链接库内定义的函数时(例如此处的puts),控制流会跳转到puts函数对应的PLT表中,PLT会通过GOT间接地把动态链接器的一个参数(即待调用函数puts的序号)压入栈中,并调用动态链接器。动态链接器进行重定位,将GOT先前用以跳转到PLT的地址,替换为函数地址(即0x4004b0),再把控制传递回调用函数,控制传递到PLT,再次通过GOT间接跳转,此时便跳转到了puts的地址,即完成了动态链接的过程。

图5-10运行前GOT内容

图5-11运行后GOT内容

5.8 本章小结

本章主要探究了在链接过程中可执行目标文件的ELF文件的相关信息,hello的执行流程以及链接过程中重定位的过程,并对动态连接过程进行了分析。

第6章 hello进程管理

6.1 进程的概念与作用

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

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

Shell作为一个命令行解释器,为用户提供了一个向Linux内核发送请求以便运行程序的界面系统级程序,用户可以用Shell来启动、挂起、停止甚至是编写一些程序。而bash是shell的一种,是大多数linux发行版默认的shell。

处理流程:

图6-1:处理流程图

6.3 Hello的fork进程创建过程

6.3.1 fork函数特点

fork函数有以下特点:

  • 调用一次,返回两次。
  • 父进程与子进程并发执行。
  • 相同但是彼此独立的地址空间 。
  • 共享文件。

6.3.2进程创建过程

在终端输入./hello时shell进行对命令行的解释,按照如6.2所示的处理流程,因为第一个命令行参数不是内置shell命令,因此调用fork创建一个新的运行子进程,执行可执行程序hello。子进程得到与父进程用户级虚拟地址空间相同的但独立一份副本。

6.4 Hello的execve过程

6.4.1 execve函数

首先execve函数具有如下的函数原型:int main(int argc , char **argv , char *envp)。该函数在当前进程中载入并运行程序,其中:

filename:可执行文件,目标文件或脚本(用#!指明解释器,如 #!/bin/bash)

argv:参数列表,惯例:argv[0]==filename。

envp:环境变量列表。

然后execve函数满足如下特点:

  • 覆盖当前进程的代码、数据、栈。
  • 保留:有相同的PID,继承已打开的文件描述符和信号上下文 。

③ 调用一次并从不返回,除非有错误,如:指定的文件不存在。

6.4.2 hello的execve过程

Hello程序在如6.3所示使用fork函数创建了子进程之后,继续如6.2所示的处理流程,即在子进程中分析命令行字符串,获取命令行参数,并构造传递给execve的argv向量,调用execve函数执行指定程序在当前进程的上下文中加载并运行hello 程序。该过程可概括如下:

  • 首先调用被称为启动加载器的相关操作系统代码来执行hello程序。
  • 由加载器删除子进程现有的各个虚拟内存段,并创建新的段。(即覆盖当前进程的代码、数据、栈与堆段)
  • 其中新的栈和堆段被初始化为零,而新的代码和数据段通过将虚拟地址空间中的页映射到可执行文件的页大小的片,被初始化为可执行文件中的内容。
  • 最后由加载器设置当前进程的上下文中的程序计数器,使之指向代码区域的入口点。

该过程的栈结构如下图所示:

图6-2:调用execve()时的栈结构

6.5 Hello的进程执行

6.5.1进程上下文与上下文切换

进程上下文实际上是进程执行活动全过程的静态描述。我们把已执行过的进程指令和数据在相关寄存器与堆栈中的内容称为上文,把正在执行的指令和数据在寄存器和堆栈中的内容称为正文,把待执行的指令和数据在寄存器与堆栈中的内容称为下文。

上下文切换的基本原理是当发生任务切换时, 保存当前任务的寄存器到内存中, 将下一个即将要切换过来的任务的寄存器状态恢复到当前CPU寄存器中, 使其继续执行, 同一时刻只允许一个任务独享寄存器。在任务切换的过程中是涉及任务上下文的保存和恢复操作, 而任务上下文切换操作的性能是衡量操作系统性能的一个重要指标。任务上下文切换指标可以反映出操作系统在多任务环境下的处理能力。

上下文切换的过程:

  • 保存当前进程的上下文
  • 恢复现在调度进程的上下文
  • 将控制传给新恢复进程

图6-3:上下文切换

6.5.2进程时间片

时间片(timeslice)又称为“量子(quantum)”或“处理器片(processor slice)”是分时操作系统分配给每个正在运行的进程微观上的一段CPU时间(在抢占内核中是:从进程开始运行直到被抢占的时间)。现代操作系统(如:Windows、Linux、Mac OS X等)允许同时运行多个进程,而同一个CPU无法真正地同时运行多个任务。因此在只考虑一个CPU的情况下运行,这些进程通过轮番穿插地运行,是指看似是同时进行的,由于时间片通常很短(在Linux上为5ms-800ms),进程的切换不会被用户所觉察。

6.5.3用户态与核心态

用户态指非特权状态。在此状态下,执行的代码被硬件限定,不能进行某些操作,比如写入其他进程的存储空间,以防止给操作系统带来安全隐患。而核心态则是操作系统内核所运行的模式,运行在该模式的代码,可以无限制地对系统存储、外部设备进行访问。用户程序调用系统 API 函数称为系统调用(System Call);发生系统调用时会暂停用户程序,转而执行内核代码(内核也是程序),访问内核空间,这称为内核模式(Kernel Mode)。而用户空间保存的是应用程序的代码和数据,是程序私有的,其他程序一般无法访问。当执行应用程序自己的代码时,称为用户模式(User Mode)。

6.5.4 hello中的进程执行

在hello中涉及到的上下文切换大致有两处:

  • sleep函数的调用。起初hello进程运行在用户模式(如6.5.3所述),当 hello 进程调用 sleep函数后进入内核模式,内核开始处理休眠状态。内核首先挂起当前进程,开始sleep函数计时,并进行上下文切换将当前进程的控制权交给其他进程。计时至sleep函数的参数值时,中断信号被发送,此时在内核状态执行中断处理,取消hello进程的挂起,hello进程就可以继续进行自己的控制逻辑流了。
  • getchar函数的调用。getchar调用了系统函数来实现字符的读取。调用该类系统函数时,同上文所述,会进入内核模式。内核执行上下文切换,切换到其他进程。当完成键盘缓冲区到内存的数据传输时,中断信号被发送,此时内核从其他进程进行上下文切换回 hello进程。

6.6 hello的异常与信号处理

6.6.1运行结果

①正常运行

如图6-4,程序输出8次hello 学号+姓名

图6-4正常运行

  • 乱按键盘

如图6-4所示,会将所按键位输出,直至第一次按enter键,将之后的输入作为shell的命令行输入。

图6-5 乱按键盘

  • CTRL+C

图6-6 CTRL+C

  • CTRL+Z

图6-7 CTRL+Z

  • 各种命令

图6-8各种命令

6.6.2 hello的异常与信号处理

hello执行过程中可能出现四类异常:中断、陷阱、故障和终止。如这里的键盘输入CTRL+C,使内核发送一个SIGINT信号到前台进程组中的每个进程,终止前台作业,CTRL+Z。使内核发送一个SIGSTP信号到前台进程组中的每个进程,前台作业被挂起等。

6.7本章小结

本章介绍了程序在shell执行及进程的相关概念,尤其是hello在运行过程中,调用fork函数及execve创建新的进程并执行程序这一系列过程的介绍,并在执行过程中在终端执行各种命令,并对产生的结果进行观察分析。

第7章 hello的存储管理

7.1 hello的存储器地址空间

7.1.1逻辑地址:逻辑地址是指在计算机体系结构中是指应用程序角度看到的内存单元(memory cell)、存储单元(storage element)、网络主机(network host)的地址。 逻辑地址往往不同于物理地址(physical address),通过地址翻译器(address translator)或映射函数可以把逻辑地址转化为物理地址。

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

7.1.3物理地址:在存储器里以字节为单位存储信息,为正确地存放或取得信息,每一个字节单元给以一个唯一的存储器地址,称为物理地址(Physical Address),又叫实际地址或绝对地址。对于hello程序而言,MMU将虚拟内存地址映射到内存中的地址就是hello的物理地址。

7.1.4虚拟地址: CPU在寻址的时候,是按照虚拟地址来寻址。CPU通过生成一个虚拟地址(VA)来访问主存,这个虚拟地址被送到内存之前先转换为适当的物理地址。实际上通过反汇编查看到的地址就是hello的虚拟地址。

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

7.2.1实地址模式:在实地址模式下,处理器使用20位的地址总线,可以访问1MB(0~FFFFF)内存。而8086的模式,只有16位的地址线,不能直接表示20位的地址,采用内存分段的解决方法。段地址存放于16位的段寄存器中(CS、DS、ES或SS)

7.2.2保护模式:在保护模式下,段寄存器存放段描述符在段描述符表中的索引值,称为段选择器,此时CS存放代码段描述符的索引值,DS存放数据段描述符的索引值,SS存放堆栈段描述符的索引值

7.2.3描述符表:

①全局描述符表GDT(Global Descriptor Table)

整个系统只有一个,包含:

▪ 操作系统使用的代码段、数据段、堆栈段的描述符

▪ 各任务/程序的LDT(局部描述符表)段

②局部描述符表LDT(Local Descriptor Table)

每个任务/程序有一个独立的LDT(intel 80386),包含:

▪ 对应任务/程序私有的代码段、数据段、堆栈段的描述符

▪ 对应任务/程序使用的门描述符:任务门、调用门等。

7.2.4段式管理:段式管理(segmentation),是指把一个程序分成若干个段(segment)进行存储,每个段都是一个逻辑实体(logical entity),程序员需要知道并使用它。它的产生是与程序的模块化直接有关的。段式管理是通过段表进行的,它包括段号或段名、段起点、装入位、段的长度等。此外还需要主存占用区域表、主存可用区域表。

①平坦分段模式:在这一模式下,所有段被映射到32位物理地址空间;(程序至少两个段:代码段和数据段)且由全局描述符表GDT 保存所有的段描述符。

②多段模式(Multi-Segment):在这一模式下每个任务/程序有自己的局部段描述符表(LDT) ,每个任务/程序有代码段、数据段、堆栈段且每个段有独立的地址空间。

图7-1:分段过程

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

7.3.1分页:将内存分割成4KB大小的页(Pages) ,同时将程序段的地址空间按内存页的大小进行划分。

7.3.2分页模式的基本思想:当任务运行时,当前活跃的执行代码保留在内存中,而程序中当前未使用的部分,将继续保存在磁盘上。当CPU需要执行的当前代码存储在磁盘上时,产生一个缺页错误,引起所需页面的换进(从磁盘载入内存)。通过分页以及页面的换进、换出,一台内存有限的计算机上可以同时运行多个大程序,让人感觉这台机器的内存无限大,因此称为虚拟内存。

7.3.3页表:页表是一个页表条目(Page Table Entry, PTE)的数组,将虚拟页地址映射到物理页地址。由页表这一数据结构实现了虚拟地址到物理地址的映射。

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

7.4.1翻译后备缓冲器:TLB(翻译后备缓冲器)是一个位于MMU中的小的虚拟地址的具有较高相联度的缓存,其每一行都是一组由数个PTE组成的块(对于页面数很少的页表可以完全包含在TLB中),即实现了虚拟页号向物理页号的映射且极大地减小了CPU访问PTE的开销。

7.4.2多级页表:如图7-2所示,每个PTE指向1个下一级级页表的页面的常驻内存,可以实现多级页表的存储结构,图7-2形象地展示了多级页表下VA到PA的转换过程。

7.4.3 Linux下的多级页表:Linux采用的方案是4级页表,分别是:

  • PGD:page Global directory(47-39), 页全局目录
  • PUD:Page Upper Directory(38-30),页上级目录
  • PMD:page middle directory(29-21),页中间目录

④ PTE:page table entry(20-12),页表项

图7-2使用多级页表的地址翻译

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

7.5.1 Cache:高速缓存(Cache): 将一种更小、速度更快的存储设备,作为更大、更慢存储设备的缓存区。分为直接映射高速缓存,组相联高速缓存以及全相联高速缓存三类。

7.5.2三级Cache:本节中的三级Cache指的是图7-3所示的7级存储器层次结构中的L1-L3的三级高速缓存,即被寄存器保存着且保存着从L2缓存中取出的缓存行的L1高速缓存,以及同理被上级存储器结构保存且存储着下级存储器结构的L2和L3高速缓存。

图7-3:七级存储器层次结构

7.5.3物理内存访问

由此参照图7-3所示的存储器层次结构,当对物理内存进行访问时,先访问L1高速缓存,若访问不命中则访问L2,再次不命中则访问L3,同理继续访问主存和硬盘。

7.6 hello进程fork时的内存映射

(出自深入理解计算机系统-原书第3版,龚奕利,贺莲译)

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

当fork在新进程中返回时,新进程现在的虚拟存储器刚好和调用fork时存在的虚拟存储器相同(就是父进程 运行到fork时的虚拟存储器,所以子进程运行的地方是从fork返回时开始的,并不是从头开始)。当这两个进程的任一个后来进行写操作时,写时拷贝机制就会创建新的物理页面。

7.7 hello进程execve时的内存映射

(出自深入理解计算机系统-原书第3版,龚奕利,贺莲译)

调用execve加载并运行hello大体分为以下数个步骤:

  • 删除已存在的用户区域,删除当前进程虚拟地址的用户部分中的已存在的区域

结构。

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

③映射共享区域, hello程序与共享对象libc.so链接,libc.so是动态链接到这个程序中的,然后再映射到用户虚拟地址空间中的共享区域内。

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

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

7.8.1缺页:Page fault缺页是指引用虚拟内存中的字,不在物理内存中 (DRAM 缓存不命中),此时会触发缺页故障,如图7-4所示。

图7-4:缺页故障

7.8.2缺页中断处理:当发生缺页故障时,如图7-5所示,内核调用缺页异常处理程序选择一个牺牲页,更新PTE时牺牲页的位置对应待访问的物理地址。然后控制返回给引起缺页故障的指令。当指令再次执行时,相应的物理页面已经驻留在内存中。

图7-5:牺牲页处理缺页

7.9动态存储分配管理

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

而分配器有两种风格——显式分配器和隐式分配器。

①显示分配器:要求应用显式地释放任何已分配的块,例如,C语言中的 malloc 和 free。

②隐式分配器: 应用检测到已分配块不再被程序所使用,就释放这个块,比如Java,ML和Lisp等高级语言中的垃圾收集 (garbage collection)。

7.10本章小结

本章介绍了虚拟地址、物理地址、线性地址、逻辑地址,地址管理中的段式管理与页式管理,进程执行fork与execve的内存映射的内容,存储器层次结构中的三级高速缓存以及描述了系统应对缺页异常的方法和malloc的内存分配管理机制。

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

一个linux文件就是一个m个字节的序列:

B0,B1,B2Bn

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

8.2 简述Unix IO接口及其函数

大多数文件的操作需要如下四个函数open、read、write和close函数。

① open函数:

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

open函数将filename转换为一个文件描述符,并且返回描述符数字,返回的描述符总是在进程中当前没有打开的最小描述符,若文件打开失败返回-1,打开失败原因可以通过errno或者strerror(errno)查,flags参数指明了进程打算如何访问这个文件,而mode参数指定了文件访问权限。

  • read函数:

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

其中fd为要读取文件的文件描述符。buf为读取文件数据缓冲区,nbytes为期待读取的字节数,通常为sizeof(buf)。

  • write函数:

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

其中fd为要读取文件的文件描述符。buf为读取文件数据缓冲区,nbytes为期待读取的字节数,通常为sizeof(buf)。若读取成功,读到文件末尾返回0,未读到文件末尾返回当前读的字节数;若读取失败则返回-1。

  • close函数

int close(int fd);

其中fd为要关闭文件的文件描述符,关闭成功返回0,失败则返回-1。

  1. printf的实现分析

printf函数是变长参数的函数,函数定义如下:

int printf(const char fmt, …)

具体实现步骤大体可概括如下:

  • 从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.
  • 字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
  • 显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。

8.4 getchar的实现分析

当程序调用getchar时.程序等待用户通过按键输入字符。输入的字符被存放在键盘缓冲区中.直到接收到回车字符为止。当用户键入回车之后,getchar函数从stdin流中每次读入一个字符。函数返回值是用户输入的第一个字符的ASCII码,如出错返回-1。

异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。

8.5本章小结

本章介绍了 Linux 中 I/O 设备的管理方法,Unix I/O 接口和函数,以及printf 和 getchar 函数的Unix I/O 实现分析。

结论

如上便完成了hello的完整的生命周期——

  • 首先用C语言编辑器生成了hello.c文件——hello的出生!
  • 预处理器cpp根据编译预处理指令将hello.c文件经过预处理生成hello.i。
  • 编译器ccl将文件hello.i翻译成汇编语言程序,输出到hello.s文件中。
  • 汇编器ac将hello.s翻译成机器语言指令,并把这些指令打包成可重定位目标文件并输出到hello.o。
  • 链接器ld将hello.o链接成为一个可执行文件。——hello的成年!
  • shell使用fork函数创建一个子进程,并在子进程中调用 execve函数加载运行 hello,CPU为进程分配时间片,加载器将计数器预置在程序入口点, hello可以顺序执行自己的逻辑控制流——hello开始工作!
  • CPU上的内存管理单元MMU根据页表将CPU生成的虚拟地址翻译成物理地址,将相应的页面调度。——hello立业
  • Unix I/O 帮助 hello 实现了与外接显示设备以及输入设备的连接,即实现了输出到屏幕和从键盘输入的功能。——hello成家
  • 执行完成后父进程回收子进程,内核删除为该进程创建的数据结构——雁过留声

附件

hello.i 经预处理器产生的文本文件

hello.s 编译器产生的汇编文件

hello.o 汇编器产生的可重定位目标文件

hello 链接器产生的的可执行目标文件

hello-objdump.txt Hello.o 的反汇编代码

hello-elf.txt Hello.o 的 ELF 文件

参考文献

[1]  深入理解计算机系统-原书第3版,龚奕利,贺莲译

[2]  https://blog.csdn.net/zhaojianyin/article/details/60141613

[3]  https://www.cnblogs.com/GOOGnine/p/12370596.html

[4] 张东,梅涛.一种高效的浮点上下文切换算法[J].科技风.中国知网

[5] https://blog.csdn.net/sinat_38104725/article/details/98474760

[6] https://blog.csdn.net/qq_41056224/article/details/104619596

[7] https://blog.csdn.net/qq_38877888/article/details/103261175

[8] https://www.cnblogs.com/pianist/p/3315801.html

程序人生——CSAPP大作业相关推荐

  1. HIT计算机系统CSAPP大作业

    HIT计算机系统CSAPP大作业 摘 要 一.第1章 概述 1.1 Hello简介 ·P2P过程 ·020过程 1.2 环境与工具 1.2.1 硬件环境 1.2.2 软件环境 1.2.3 开发工具 1 ...

  2. 用JAVA写一个画图小程序(JAVA 大作业)

    第一次写博客 且是稍微大点的程序 看看就行 重新写的在这,更加清晰明了:点击进入:用JAVA写一个画图小程序(JAVA 大作业)重排版本 设计思路 首先我直接去了Windows自带画图程序去实践模拟, ...

  3. 基于JavaSwing开发简单的画图板程序 课程设计 大作业源码

    基于JavaSwing开发简单的画图板程序:  (大作业) 开发环境: Windows操作系统 开发工具: MyEclipse+Jdk 运行效果图: 基于JavaSwing开发简单的画图板程序:  ( ...

  4. 基于avaSwing开发普通计算器程序 课程设计 大作业源码

    基于avaSwing开发普通计算器程序:   (大作业) 开发环境: Windows操作系统 开发工具: MyEclipse+Jdk 运行效果图: 基于avaSwing开发普通计算器程序:   (大作 ...

  5. 基于JavaSwing开发3D立体字制作程序 课程设计 大作业

    基于JavaSwing开发3D立体字制作程序:   (大作业) 开发环境: Windows操作系统 开发工具: Eclipse+Jdk 运行效果图: 基于JavaSwing开发3D立体字制作程序:  ...

  6. Web程序设计基础期末大作业——模仿QQ飞车手游S联赛官网编写的网页

    QQ飞车手游是我非常喜欢的游戏,也是我现在为数不多的常在玩的游戏,刚好我Web程序设计基础的大作业是要做一套网站,我就借此机会模仿飞车S联赛官网的页面自己做了一个网页,又加了一些自己的元素,由于我做这 ...

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

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

  8. CSAPP大作业程序人生

    计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 计算学部 学   号 班   级 学       生 指 导 教 师 吴锐 计算机科学与技术学院 2022年5 ...

  9. hit csapp大作业 程序人生-Hello’s P2P

    计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 计算机科学与技术 学   号 2021112810 班   级 2103103 学       生 肖芩芩 指 ...

最新文章

  1. 面向对象程序设计案例
  2. c 语言cad 二次开发,c#对AutoCAD二次开发
  3. [转] 关于ASP.NET中由于无法创建应用程序域,因此未能执行请求解决方案
  4. 浙江省计算机二级c语言分数构成,计算机二级C语言题型和评分标准
  5. 计算机一级综合第九套试题及答案,2012年计算机一级B第九套选择题精选及参考答案...
  6. Android:强制EditText删除焦点? [重复]
  7. 安装双系统:Win7/Win10 + Ubuntu(亲测可用)
  8. 分布式事务 - 两阶段提交与三阶段提交
  9. mysql 5.7 临时表_MySQL 5.7内部临时表使用
  10. MySQL实战系列3:视图、存储过程、函数、索引全解析
  11. java面试问题与心得
  12. fastJson与String、对象、集合之间相互转换
  13. java的round函数加点差_【JAVA】Math.Round()函数常见问题“四舍5入”
  14. 电脑电池出场容量与目前最大容量查询查询
  15. DFP算法_python
  16. App Inventor 四字节转单精度浮点数
  17. 鲍尔默:我可能说过Linux是“恶性肿瘤” 但现在我爱它
  18. 虎年起点:两个100万
  19. Python黑马头条推荐系统第四天 TensorFlow框架介绍和深度学习
  20. The package jxl is not accessible解决

热门文章

  1. matlab等高图填色,[转载]matlab学习——area填色图
  2. PHP版]个人精美网站系统
  3. 上传csv文件后出现乱码
  4. ABAP编程语言-概览
  5. 【无标题】一款功能非常强大的免费串口示波器串口助手,支持绘图,logo保存数据保存,历史数据加载与对比。
  6. C#对接条码电子秤拉取和下发数据
  7. [PAT A1046]Shortest Distance
  8. 进程间通信之---消息队列
  9. 微信小程序中使用第三方ui框架方法
  10. Python实现自动抠图(下篇)