计算机系统

大作业

题 目 程序人生-Hello’s P2P
专 业 计算机
学   号 1190301902
班   级 1903010
学 生 薛文    
指 导 教 师 史先俊

计算机科学与技术学院
2021年5月
摘 要
本文纵览简单程序hello.c的一生,主要讲述了它在编写完成后运行在linux中的生命历程,用Ubuntu的相关工具分析预处理、编译、汇编、链接等各个过程的步骤原理。与此同时,本文分析了这些过程对应的文件的信息和作用。并着重介绍了shell的内存管理、进程管理等相关知识。旨在深入了解虚拟内存、异常信号、进程切换等相关内容。本文完整地梳理了深入理解计算机系统这门课程的主要知识。

关键词:预处理;编译;汇编;链接;进程管理;虚拟内存;hello.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:在Linux下,Hello.c经过预处理、编译、汇编、链接生成可执行文件Hello,在shell中输入执行命令./hello后,shell通过OS进程管理为其fork产生子进程,此过程中,hello.c完成从程序(program)到进程(process)的实现。
020:在P2P之后,shell通过OS进程管理execve加载执行hello进程,映射虚拟内存,进入程序入口后程序载入物理内存,CPU为hello分配时间片,执行逻辑控制流,在程序结束后,shell回收hello进程,删除其相关存储,hello结束生命,整个过程From Zero to Zero,简称020。
1.2 环境与工具
(1)硬件环境:X64 CPU;2GHz;2G RAM;256GHD Disk 以上

(2)软件环境:Windows10 64位;Vmware 10;Ubuntu 16.04 LTS 64位

(3)开发工具:GDB,Codeblocks,vim,GCC,EDB,Objdump,readelf

1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。

hello.c 源程序
hello.i 预处理得到的文本文件
hello.s 编译得到的汇编文件
hello.o 汇编得到的可重定位目标文件
hello 链接之后得到的可执行文件
hello.txt 可执行文件hello的elf文本文件
hello.o.txt 可重定位目标文件hello.o的elf文本文件

1.4 本章小结
第一章主要是对hello的简介,包括P2P和020过程,以及对进行实验时的软硬件环境和开发与调试工具简单的介绍,最后是叙述在本篇论文中所生成的中间结果文件。
(第1章0.5分)

第2章 预处理
2.1 预处理的概念与作用
(1)概念:
预处理,在程序设计领域,一般是指程序源代码被翻译为目标代码的过程中,生成二进制代码之前的过程。典型地,由预处理器对程序源代码文本进行处理,得到的结果再由编译器核心进一步编译。这个过程并不对程序的源代码进行解析,但它把源代码分割或处理为特定的单位。最常见的预处理是C语言和C++语言。ISO C和ISO C++都规定程序由源代码被翻译分为若干有序的阶段(phase),通常前几个阶段由预处理器实现。
(2)作用:
预处理器(cpp)根据以字符#开头的命令,修改原始的C程序。比如hello.c中第一行的#include<stdio.h>命令告诉预处理器读取系统头文件stdio.h的内容,并把它直接插入程序文本中。预处理器还会替换程序起始位置的宏,结果就得到了另一个C程序,通常是以.i作为文件扩展名。

2.2在Ubuntu下预处理的命令
命令:gcc -E -o hello.i hello.c

hello.i文件内容部分截图如下:

2.3 Hello的预处理结果解析

源程序中hello.c的程序被放在了hello.i的文件末尾,同时,对原文件中的宏进行了宏展开,增加的内容其实就是三个头文件的源码,预处理器将头文件stdio.h、unistd.h、stdlib.h依次展开,若在展开该头文件的过程中仍然遇到了以#开头的define,预处理器会对此继续展开。
2.4 本章小结
第二章主要概括了预处理的概念和作用,并且详细说明了在ubuntu下的预处理命令,最后结合预处理文件hello.i进行了简单分析。

(第2章0.5分)

第3章 编译
3.1 编译的概念与作用
(1)概念:
编译器将文本文件hello.i翻译成文本文件hello.s,它包含一个汇编语言程序,该程序包含main函数的定义。编译的过程实质上就是对预处理文件进行操作,词法分析、语法分析、语义检查和中间代码生成、代码优化、和目标代码生成。需要注意的是,转换后的文件仍为ASCII文本文件。
(2)作用:
将输入的高级程序设计语言源程序翻译成以汇编语言或机器语言表示的目标程序作为输出。与此同时,编译后生成的.s汇编语言程序文本文件比预处理文件更容易让机器理解、比.o可重定位目标文件更容易让程序员理解。汇编语言程序为不同高级语言的不同编译器提供了通用的输出语言,为低级机器语言。

注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序
3.2 在Ubuntu下编译的命令
命令:gcc -S -o hello.s hello.i

hello.s文件内容部分截图如下:

3.3 Hello的编译结果解析
hello.c程序截图如下:

3.3.1 数据
(1)一个局部变量i,int型4个字节且存放在栈上-4(%rbp)中。
将int i与7进行比较。
(2)main的参数argc,存放在栈上-20(%rbp)

将argc与4作比较,完成第一个判断语句。
(3)其他整型数据都是立即数,作为常量,直接加上$符号表明这是个立即数。
(4)字符串,在函数中有两个字符串,且都是作为printf的参数,存放在.rodata中。且汉字并非直接出现,而是转化成了UTF-8编码

其对字符串的引用如下:

(5)argv数组,先将数组的地址赋值给寄存器,再加上偏移量得到数组每个元素的地址,然后访问数组得到这个值。

3.3.2开头部分
.file 指明.c源文件
.text 代码段
.align 对齐方式
.section .rodata 只读数据段
且在开头部分指明了main是一个全局函数,存放在.text节中

3.3.3操作
(1)赋值操作,通过mov语句完成。且最后一个字符表明处理的字节数。b:一个字节,w:两个字节(一个字),l:四个字节(双字),q:八个字节(四字)。由于i是int型,所以四个字节选择l
例如:
(2)算术操作,对i++的操作通过addl语句完成

(3)类型转换,由于sleep函数的参数为int值,而argv为字符串数组,在hello.c中用atoi将字符串转化成int型,在hello.s中用call语句调用atoi函数强制处理该类型转换。

(4)控制转移,两处控制转移,第一处是if语句,第二处是for语句。在这里面先要进行关系操作,即通过cmp指令对两个操作数做减法,然后设置条件码,在通过判断语句je,jle来判断是否跳转。je是判断是否相等,是根据ZF条件码来判断,jle是判断是否不相等。用这两个语句来实现if判断和for循环。

(5)函数操作和调用,使用call来调用函数,对于不需要参数直接调用的函数(如getchar)属于直接调用。
而对于需要参数的函数(例如本例中的main函数),其参数通过寄存器%edi和%rsi传入。在调用前需将参数压入堆栈。需要传递参数的函数有main,exit,printf,atoi,sleep。

最后在进行函数返回ret操作。函数返回时会根据函数原型,返回值存放在%rax中,函数执行完毕后会返回调用前的位置。

此部分是重点,说明编译器是怎么处理C语言的各个数据类型以及各类操作的。应分3.3.1~ 3.3.x等按照类型和操作进行分析,只要hello.s中出现的属于大作业PPT中P4给出的参考C数据与操作,都应解析。
3.4 本章小结
本章主要介绍了编译的概念和作用,展示了将.i文件编译为.s文件的过程。紧接着对编译后的.s文件进行了分析。数据与赋值部分说明了整形常量与变量、字符串转化的对比。关系操作与控制转移中介绍了汇编语句如何实现条件判断和跳转;函数操作部分介绍了汇编代码是如何完成参数传递、函数调用和函数返回的。
(第3章2分)

第4章 汇编
4.1 汇编的概念与作用
(1)概念:
汇编过程指的是从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程,将.s汇编程序翻译成机器语言并将这些指令打包成可重定目标程序的格式存放在.o中。需要注意的是,这里的.o文件是一个二进制文件,包含着程序的指令编码。
(2)作用:
汇编过程将汇编代码转换为机器指令(二进制机器代码),使其在链接后能被机器识别并执行。

注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。
4.2 在Ubuntu下汇编的命令
命令:gcc -c hello.s -o hello.o

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

用readelf命令查看hello.o的elf格式,并将结果保存在hello.o.txt文本文件中。
命令:readelf -a hello.o >hello.o.txt

ELF头:描述生成该文件的系统的字的大小和字节顺序
段头部表:将连续的文件映射到运行时的内存段
.init:程序初始化代码会调用其中的_init函数
  .text:程序的机器代码
.rodata:只读数据,如跳转表
.data:已初始化的全局和静态C变量
.bss:未初始化的全局和静态C变量,以及初始化为0的全局和静态变量
.symtab:符号表,存放在程序中定义及引用的函数和全局变量的信息
.debug:调试符号表,含有原始C源文件等。
.line:原始C源程序的行号和.text节中机器指令之间的映射
.strtab:一个字符串表,其内容包括 .symtab 和 .debug节中的符号表,以及节头部中的节名字
节头部表:描述目标文件的节

4.3.1 ELF头

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

从上表可知小端存储,可重定位目标文件。

4.3.2节头部表
概念:节头部表描述了目标文件中每一个节的位置和大小,每个节都有一个固定的条目,包括名称、类型、地址及偏移量等。

4.3.3重定位节
.rela.text,保存的是.text节中需要被修正的信息;任何调用外部函数或者引用全局变量的指令都需要被修正;调用外部函数的指令需要重定位;引用全局变量的指令需要重定位; 调用局部函数的指令不需要重定位;在可执行目标文件中不存在重定位信息。本程序需要被重定位的是printf、puts、exit、sleepsecs、getchar、sleep和.rodata中的.L0和.L1。
.rela.eh_frame节是.eh_frame节重定位信息。

4.3.4符号表
.symtab,一个符号表,它存放在程序中定义和引用的函数和全局变量的信息,一些程序员错误地认为必须通过-g选项来编译一个程序,才能得到符号表信息。实际上每个可重定位目标文件在.symtab中都有一张符号表。然而,和编译器中的符号表不同,.symtab符号表不包含局部变量的条目。

Num:表项的编号,Value相对起始位置的偏移量,size所占字节大小,Type表项类型,Bind标识是全局变量还是本地,Ndx表项在节头部表的索引,Name表项名

4.4 Hello.o的结果解析
命令:objdump -d -r hello.o

分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。
主要差异如下:
机器语言的构成,与汇编语言的映射关系:
(1)hello.s中call语句后紧跟着函数名,而在hello.o反汇编中call语句后跟着的是相对地址,显示相应的重定位条目,因为未链接无法确定绝对地址。

(2)hello.s中跳转语句后紧跟着的是.L2.L3这样的标签,而在hello.o反汇编中跳转语句后跟着的是相对地址。

(3)hello.s中的操作数是十进制,而在hello.o反汇编中操作数是十六进制。
(4)每一条汇编语句都已经有了相对应的机器码表示。

4.5 本章小结
第四章主要概括了汇编的概念和作用,分析了ELF文件的内容,另外比较了重定位前汇编程序和重定位后反汇编的差别,了解从汇编语言翻译成机器语言的转换处理和机器语言和汇编语言的映射关系。
(第4章1分)

第5章 链接
5.1 链接的概念与作用
(1)概念:
链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可以被加载到内存并执行。链接可以执行于编译时,也就是在源代码被编译成机器代码时;也可以执行于加载时,也就是在程序被加载器加载到内存并执行时;甚至于运行时,也就是由应用程序来执行。链接执行符号解析、重定位过程。
(2)作用:
为了构造可执行文件,链接器必须完成两个主要任务:符号解析和重定位。符号解析目的是将每个符号引用正好和一个符号定义关联起来。重定位将每个符号定义与一个内存位置关联起来,修改所有对这些符号的引用,使得他们指向这个内存位置。
注意:这儿的链接是指从 hello.o 到hello生成过程。
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

使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件
5.3 可执行目标文件hello的格式
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
命令:readelf -a hello>hello.txt

典型的ELF可执行目标文件如下:

可执行目标文件hello的格式类似于可重定位目标文件hello.o的格式。ELF头描述文件的总体格式。它还包括程序的入口点,也就是当程序运行时要执行的第一条指令的地址。.text、.rodata和.data节与可重定位目标文件的节是相似的,除了这些节已经被重定位到它们最终的运行时内存外。.init节定义了一个小函数,叫_init,程序的初始化会调用它。因为可执行文件hello是完全链接的,所以不再需要.rel节。
5.3.1 ELF头

文件类型变成了可执行文件EXEC

5.3.2节头部表

5.3.3符号表

5.3.4程序头部表:描述了可执行文件中连续的片与内存中连续的段的映射关系。

Type指明描述的内存段的类型,或者如何解析该程序头的信息。
Offset指明该段内容的起始位置相对于文件开头的偏移量。
VirtAddr指明该段中内容的起始位置在进程地址空间中的虚拟地址。
PhysAddr指明该段中内容的其实位置在进程地址空间中的物理地址。对于目前大多数操作系统而言,这个字段大多数情况下不起作用。(由于无法预知物理地址)
FileSiz 目标文件的段大小
MemSiz 内存中段大小
Flags运行时的访问权限
Align 对齐要求

5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。

hello的虚拟地址空间从0x401000开始。根据5.3节中的节头部表,可以知道每个节的虚拟地址起始位置和大小。
例如.text的偏移量为0x0010f0,加上起始地址后得到0x4010f0大小为0x145

5.5 链接的重定位过程分析
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
不同:
(1)链接后增加了很多库函数,如exit、printf、sleep、getchar等函数。
(2)hello相比hello.o增加了.init和.plt和.final节

(3)链接后hello不再需要重定位条目,并且跳转和函数调用的地址都变成了虚拟内存地址。同理,hello中对地址的访问也是根据虚拟内存地址而不是hello.o中的相对偏移地址。hello的反汇编代码有确定的运行时地址,说明已经完成了重定位,而hello.o反汇编代码中涉及到运行时地址的地方均标记为0.

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

5.6 hello的执行流程
程序地址 子程序名
401000 _init
401090 puts@plt
4010a0 printf@plt
4010b0 getchar@plt
4010c0 atoi@plt
4010d0 exit@plt
4010e0 sleep@plt
4010f0 _start
401120 _dl_relocate_static_pie
401125 main
4011c0 __libc_csu_init
401230 __libc_csu_fini
401238 _fini
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
5.7 Hello的动态链接分析
假设程序调用了共享库里的函数,编译器无法其运行时地址,为了能使得代码段里对数据及函数的引用与具体地址无关,链接器采用延迟绑定的策略。延迟绑定是通过全局偏移量表(GOT)和过程链接表(PLT)之间的交行实现的。如果一个目标模块调用定义在共享库中的任何函数,那么它就有自己的GOT和PLT。GOT是数据段的一部分,PLT是代码段的一部分。

对于变量而言,可以利用代码段和数据段的相对位置不变的原则计算正确地址。在函数调用时,首先跳转到PLT执行.plt中操作,第一次访问跳转时GOT地址为下一条指令,将函数序号入栈,然后跳转到PLT[0],之后将重定位表地址入栈,访问动态链接器,在动态链接器中使用在栈里保存的函数序号和重定位表计算函数运行时的地址,重写GOT,返回调用函数.之后如果还有对该函数的访问,就不用执行第二次跳转,直接参看GOT信息。
Got起始地址0x403ff0,前后变化截图如下:

分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
5.8 本章小结
本章主要介绍了链接的概念和作用,并对生成的hello可执行文件的ELF格式、虚拟地址空间、重定位过程、执行流程、动态链接等方面进行了详细分析。比较了和可重定位目标文件反汇编代码得不同之处。
(第5章1分)

第6章 hello进程管理
6.1 进程的概念与作用
(1)概念:
进程的经典定义就是一个执行中程序的实例。系统中的每个程序都运行在进程的上下文中。上下文是由程序正确运行所需的状态组成的。这个状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。
进程提供给应用程序的关键抽象:
一个独立的逻辑控制流,它提供一个假象,好像我们的程序独占地使用处理器。
一个私有的地址空间,它提供一个假象,好像我们的程序独占地使用内存系统。
(2)作用:
系统中每个程序都运行在某个进程的上下文中。通过进程,我们会得到一种假象,好像我们的程序是当前唯一运行的程序,我们的程序独占处理器和内存,我们程序的代码和数据好像是系统内存中唯一的对象。每次用户向shell输入一个可执行目标文件的名字,运行程序时,shell就会创建一个新的进程,然后在这个新进程的上下文中运行这个可执行目标文件。应用程序也能创建新进程,并且在这个新进程的上下文中运行它们自己的代码和、或其他应用程序。
6.2 简述壳Shell-bash的作用与处理流程
(1)作用:
是一个交互级的应用程序,提供了一个界面,解释并执行用户打入的各种命令,实现用户与Linux核心的接口。它接收用户命令,然后调用相应的应用程序。同时它又是一种程序设计语言。作为命令语言,它交互式解释和执行用户输入的命令或者自动地解释和执行预先设定好的一连串的命令;作为程序设计语言,它定义了各种变量和参数,并提供了许多在高级语言中才具有的控制结构,包括循环和分支。
(2)处理流程:
第一步:用户输入命令。
第二步:shell对用户输入命令进行解析,判断是否为内置命令。
第三步:若为内置命令,调用内置命令处理函数,否则调用execve函数创建一个子进程进行运行。
第四步:判断是否为前台运行程序,如果是,则调用等待函数等待前台作业结束;如果不是,则将程序转入后台,直接开始下一次用户输入命令。
第五步:shell应该接受键盘输入信号,并对这些信号进行相应处理。
6.3 Hello的fork进程创建过程
父进程可以通过fork函数创建一个新的运行的子进程。
其函数声明为:pid_t fork(void)
子进程享有与父进程相同但各自独立的上下文,包括代码、堆、数据段、共享库以及用户栈。在父进程中,fork函数返回子进程的PID,在子进程中,fork函数返回0,父进程与子进程最大的区别在于它有着不同的pid。当我们在终端中输入./hello时,shell会先判断这不是shell内置的命令,于是就把这条命令当作一个可执行程序的名字。接下来shell会调用执行fork函数为hello创建新的进程。
6.4 Hello的EXECVE过程
execve函数在当前进程的上下文中加载并运行一个新程序。
其函数声明为:int execve(const char *filename,const char *argv[],const char *envp[]),
execve函数加载并运行可执行目标文件filename,且带参数列表argc和环境变量列表envp。只有当出现错误时,例如找不到filename,execve才会返回到调用程序。所以,与fork函数一次调用返回两次不同,execve调用一次从不返回。
在execve加载了hello之后,它会调用系统提供的启动代码,启动代码设置栈,系统会用execve构建的数据结构覆盖其上下文,替换成hello的上下文,然后将控制传递给新程序的主函数main。
当main开始执行是,用户栈的组织结构:从栈底(高地址)往栈顶(低地址)看,首先是参数和环境字符串。栈网上紧随其后的是以null结尾的指针数组,其中每个指针都指向栈中的一个环境变量字符串。全局变量environ指向这些指针中的第一个envp[0]。紧随环境变量数组之后的是以null结尾的argv[],其中每个元素都指向栈中的一个参数字符串。在栈的顶部是系统启动函数lib_start_main的栈帧。
当加载器运行时,它创建一个内存映像。在程序头部表的引导下,加载器将可执行文件的片复制到代码段和数据段,接下来,加载器跳转到程序的入口,_start函数的地址,这个函数是在系统目标文件ctrl.o中定义的,对所有的c程序都一样。_start函数调用系统启动函数,_libc_start_main,该函数定义在libc.so里,初始化环境,调用用户层的main函数,处理main函数返回值,并且在需要的时候返回给内核。

6.5 Hello的进程执行
进程时间片:多个流并发地执行的一般现象被称为并发。一个进程和其他进轮流运行的概念称为多任务。一个进程执行它的控制流的一部分的每一时间段叫做时间片。因此,多任务也叫做时间分片。
进程为每个程序提供一种假象,好像它在独占地使用处理器。事实上,CPU为每个进程分配时间片,通过逻辑控制流不停的切换当前进程。每个进程执行它的流的一部分,然后被抢占(暂时挂起),然后轮到其它进程。
上下文切换:操作系统内核使用上下文切换的较高层次的异常控制流来实现多任务。上下文切换机制建立在较低层异常机制之上。内核为每个进程维护一个上下文,上下文就是内核重新启动一个被抢占的进程所需的状态。上下文由一些对象的值组成,通常包括通用目的寄存器、浮点寄存器、程序计数器、用户栈和各种内核数据结构。
在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的新的进程,这种决策就叫做调度,是由内核中调度器的代码处理的。
在内核调度了一个新的进程运行后,它就抢占当前进程,并使用一整称为上下文切换的机制来将控制转移到新的进程,1)保存当前进程的上下文,2)恢复某个先前被抢占的进程被保存的上下文,3)将控制传递给这个新恢复的进程。

调度的过程,用户态与内核态的转换:
hello一开始运行在用户模式,在调用sleep之后陷入内核模式,内核处理休眠请求主动释放当前进程,并将hello进程从运行队列中移出加入等待队列,定时器开始计时,内核进行上下文切换将当前进程的控制权交给其他进程,当定时器到你输入的时间后,发送一个中断信号,此时进入内核状态执行中断处理,运行中断异常处理子程序,将hello进程从等待队列中移出重新加入到运行队列,此时hello进程就可以继续进行自己的控制逻辑流了。
当hello调用getchar的时候,在进行read调用之后陷入内核模式,内核中的陷阱处理程序会安排在完成从键盘缓冲区到内存的数据传输后,中断处理器。此时进入内核模式,内核执行上下文切换,切换到其他进程。当完成键盘缓冲区到内存的数据传输时,会引发一个中断信号,此时内核从其他进程进行上下文切换回hello进程。
进程上下文切换的剖析如下:

结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。
6.6 hello的异常与信号处理
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。

执行过程可能出现的异常一共有四种:中断、陷阱、故障、终止。
中断:来自I/O设备的信号,异步发生,总是返回到下一条指令。
陷阱:有意的异常,同步发生,总是返回到下一条指令。
故障:潜在可恢复的错误,同步发生,能被故障处理程序修正,可能返回到当前指令或终止。
终止:不可恢复的错误,通常是一些硬件错误,同步发生,不会返回。
(1)运行过程中不停乱按,包括回车,程序正常运行,无关输入被缓存到stdin,并输出到结果上。

(2)按下Crtl-Z暂停,向进程发送SIGTSTP信号,将hello进程挂起,但并没有被回收。

输入ps,我们发现hello进程还没有被回收。
使用fg运行前台进程。

输入jobs查看进程,输入pstree查看所有进程之间的关系。

输入kill -9 进程号杀死进程

(3)按下Crtl-C终止,向进程发送终止信号SIGINT。

通过ps命令,知道hello进程已经被回收了。
6.7本章小结
第六章概括介绍了进程的概念和作用以及shell-bash的处理过程与作用。与此同时,还介绍了fork和execve进程以及hello进程的信号异常处理过程和上下文切换。本章详细地描述了shell如何在用户与系统内核中建立起一个交互的桥梁。
(第6章1分)

第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:在有地址变换功能的计算机中,访存指令给出的地址 (操作数) 叫逻辑地址,也叫相对地址。要经过寻址方式的计算或变换才得到内存中的实际有效地址,即物理地址。一个逻辑地址由两部份组成,段标识符: 段内偏移量。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是个索引号,后面3位包含一些硬件细节。hello中给出的额相对地址就是逻辑地址。
线性地址:线性地址是逻辑地址到物理地址变换之间的中间层。程序代码会产生逻辑地址,或者说是段中的偏移地址,加上相应段的基地址就生成了一个线性地址。
虚拟地址:即线性地址。概念上,虚拟地址被组织成一个存放在磁盘上的N个连续的字节大小的单元组成的数组。hello中函数指令的地址就是虚拟地址。
物理地址:物理地址用于内存芯片级的单元寻址,CPU对内存的访问是通过连接着CPU和北桥芯片的前端总线来完成的。在前端总线上传输的内存地址都是物理内存地址。
结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。
7.2 Intel逻辑地址到线性地址的变换-段式管理
一个逻辑地址由两部份组成,段标识符: 段内偏移量。段标识符是由一个16位长的字段组成,其中前13位是个索引号,后面3位包含一些硬件细节。索引号是段描述符的索引,很多的段描述符组成段描述表。可以通过段标识符的前13位在段描述符表种找到一个具体的段描述符,这个描述符就描述了一个段。当给定一个完整的逻辑地址–段选择符:段内偏移地址
(1)先看段选择符的T1是1还是0,知道当前要转换是全局段描述符表(GDT)中的段,还是局部段描述符表(LDT)中的段,再根据相应寄存器,得到其地址和大小。就可以得到一个数组。
(2)拿出段选择符中前13位,可以在这个数组中,查找到对应的段描述符,这样,有了基地址字段。
(3)基地址加上偏移量,就是要转换的线性地址
7.3 Hello的线性地址到物理地址的变换-页式管理
线性地址,即虚拟地址,虚拟内存被分割成称为多个虚拟页(VP),类似地,物理内存被分割为物理页(PP)。系统使用页表将虚拟页映射到物理页,每一个页表条目由有效位和一个n位的地址字段组成。如果设置有效位说明该页已缓存到物理内存,否则未缓存。有效位为0且地址字段不为空时指向一个虚拟页在磁盘上的起始地址。CPU通过MMU将虚拟地址翻译成物理地址,通过VPN找到对应的页表条目,如果已缓存则命中,否则不命中,发生缺页故障,需要操作系统内核与硬件合作处理。此时MMU会选择一个牺牲页,用将产生缺页的虚拟页替换牺牲页,并更新页表,然后重新执行地址翻译。

7.4 TLB与四级页表支持下的VA到PA的变换
TLB:每次CPU产生一个虚拟地址,MMU(内存管理单元)就必须查阅一个PTE,以便将虚拟地址翻译为物理地址。为了降低不命中带来的巨大时间开销,在MMU中包括了一个关于PTE的小的缓存,称为翻译后备缓存器(TLB)。TLB是一个小的、虚拟寻址的缓存,其中每一行都保存着一个由单一PTE组成的块。TLB通常有高的相连度,从虚拟地址中的页号提取出组选择和行匹配的索引和标记字段。四级页表:VPN被解释成从低位到高位的4段,从高地址开始,第一段VPN作为第一级页表的索引,用以确定第二级页表的基址;第二段VPN作为第二级页表的索引,用以确定第三级页表的基址;第三段VPN作为第三级页表的索引,用以确定第四级页表的基址;第四段VPN作为第四级页表的索引,若该位置的有效位为1,则该表项存储的是PPN。在上述过程中,只要有一级页表条目的有效位为0,下一级页表就不存在,对子页表的访问将产生缺页故障,需要从磁盘载入内存。

7.5 三级Cache支持下的物理内存访问
L1 Cache是8路64 组相联。块大小为 64字节。因为共64组,所以需要 6位 CI作为组索引,因为块大小为64字节所以需要 6位CO表示数据块内偏移,因为PA 共52位,所以 CT 共40位。我们已经将VA转换为PA,,使用CI进行组索引,将CT与组内的8个块的标记分别进行比较,如果匹配成功且块的有效位为1,则命中,根据数据块偏移CO取出数据返回给CPU。如果不命中,就去下一级缓存中查询数据,以此类推,直到主存。 采取替换策略驱逐一个块,将所需的内存块写入。

7.6 hello进程fork时的内存映射
当fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID,同时为这个新进程创建虚拟内存。为了给这个新进程创建虚拟内存,它创建了当前进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记位只读,并将两个进程中的每个区域结构都标记为私有的写时复制。区域的页表条目都被标记为只读,并且区域结构被标记为私有的写时复制。只要没有进程试图写它自己的私有区域,它们就可以继续共享物理内存中对象的一个单独副本。然而,只要有一个进程试图写私有区域内的某个页面,那么这个写操作就会触发一个保护故障。
7.7 hello进程execve时的内存映射
execve函数在当前进程中加载并运行包含在可执行目标文件hello中的程序,用hello有效地替代当前程序。加载并运行hello的步骤如下:
(1)删除已存在的用户区域,删除当前进程虚拟地址的用户部分中的已存在的区域结构。
(2)映射私有区域,为新程序的代码、数据、bss和栈区域创建新的区域结构,所有这些新的区域都是私有的、写时复制的.代码和数据区域被映射为hello文件中的.text和.data区,bss区域是请求二进制零的,映射到匿名文件,其大小包含在hello中,栈和堆地址也是请求二进制零的,初始长度为零.
(3)映射共享区域, hello程序与共享对象libc.so链接,libc.so是动态链接到这个程序中的,然后再映射到用户虚拟地址空间中的共享区域内.
(4)设置程序计数器(PC),execve做的最后一件事情就是设置当前进程上下文的程序计数器,使之指向代码区域的入口点。下一次调度这个进程时,它将从这个入口点开始执行。Linux将根据需要换入代码和数据页面。

7.8 缺页故障与缺页中断处理
(1)第一步,判断虚拟地址A是否在某个区域结构定义的区域内(合法)。缺页处理程序搜索区域结构的链表,把A和每个区域结构中的vm_start和vm_end做比较。如果这个指令是不合法的,那么缺页处理程序就触发一个段错误,从而终止这个进程。
(2)第二步确认访问权限是不是正确的。如果这一页是只读页,但是却要做出写这个动作,那明显是不行的。如果做出了这类动作,那么处理程序就会触发一个保护异常,默认行为是结束这个进程
(3)第三步,当内核知道这个缺页是由于对合法的虚拟地址进行合法的操作造成的后,它是这样来处理这个缺页的:选择一个牺牲页面,如果这个牺牲页面被修改过,那么就将它交换出去,换入新的页面并更新页表。当缺页处理程序返回时,CPU重新启动引起缺页的指令,这条指令将再次发送A到 MMU。这次,MMU就能正常地翻译A,而不会再产生缺页中断了。

7.9动态存储分配管理
动态储存分配管理使用动态内存分配器来进行。动态内存分配器维护着一个进程的虚拟内存区域,称为堆。分配器将堆视为一组不同大小的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可以用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配的状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。分配器有两种风格。显式分配器要求应用显式地释放任何已分配的块。隐式分配器要求分配器检测一个已分配块何时不再被程序所使用,那么就释放这个块.隐式分配器也叫做垃圾收集器(garbage collector),而自动释放未使用的已分配的块的过程叫做垃圾收集。
程序通过malloc函数来从堆中分配块,malloc函数返回一个指针,指向大小为至少size字节的内存块,这个块会为可能包含在这个块内的任何数据对象类型做对齐。实际中,对齐依赖于编译代码在32位模式(gcc -m32)还是64位模式(默认的)中运行。在32位模式中,malloc返回的块的地址总是8的倍数。在64位模式中,该地址总是16的倍数。如果malloc遇到问题(例如,程序要求的内存块比可用的虚拟内存还要大),那么它就返回NULL,并设置errno。malloc不初始化它返回的内存。
(1)隐式空闲链表
带边界标记的隐式空闲链表的每个块由一个字的头部、有效载荷、可能的额外填充以及脚部组成。头部编码了块的大小,以及这个块是已分配的还是空闲的。脚部是头部的副本。在隐式空闲链表中,因为空闲块是通过头部中的大小字段隐含地连接着的。分配器可以通过遍历堆中所有的块,从而间接地遍历整个空闲块的集合。其中,一个设置了已分配的位而大小为零的终止头部将作为特殊标记的结束块。分配器可以通过检查它的脚部,判断前面一个块的起始位置和状态,从而完成空闲块的合并。最小的块为16字节。
当一个应用请求一个k字节的块时,分配器搜索空闲链表,查找一个足够大的可以放置所请求块的空闲块。分配器有三种放置策略:首次适配、下一次适配合最佳适配。分配完后可以分割空闲块减少内部碎片。同时分配器在面对释放一个已分配块时,可以合并空闲块,其中便利用隐式空闲链表的边界标记来进行合并。

(2)显式空闲链表
显式空闲链表将空闲块组织为显式数据结构。例如,堆可以组织成一个双向链表,在每个空闲块中,都包含一个前驱与一个后继指针。在显式空闲链表中,一种方法是用后进先出的顺序维护链表,将新释放的块放置在链表的开始处。使用后进先出的顺序和首次适配的放置策略,分配器会最先检查最近使用过的块。在这种情况下,释放一个块可以在常数时间内完成。如果使用了边界标记,那么合并也可以在常数时间内完成。另一种方法是按照地址顺序来维护链表,其中链表中每个块的地址都小于它后继的地址。在这种情况下,释放一个块需要线性时间的搜索来定位合适的前驱。

(3)分离空闲链表:
分配器维护一个空闲链表数组,每个空闲链表和一个大小类关联,按照大小的升序排列。当分配器需要一个大小为n的块时,它就搜索相应的空闲链表。如果不能找到合适的块与之匹配,就搜索下一个链表。且链表是显式或隐式的。
例如可以根据2的幂来划分块大小{1}{2}{3,4}{5-8}…{1025-2048}…

7.10本章小结
本章首先讨论了逻辑地址、线性地址、虚拟地址、物理地址的概念以及彼此间的联系与区别,接着介绍了从逻辑地址到线性地址、从线性地址到物理地址的变换过程和原理。介绍了利用TLB和页表实现VA到PA的变换,以及利用三级Cache完成物理内存访问的过程。分析了hello在fork和execve时的内存映射。概括了缺页故障和缺页中断的处理流程。最后介绍了隐式空闲链表和显示空闲链表还有分离空闲链表这三种动态存储分配管理的方法。
(第7章 2分)

第8章 hello的IO管理
8.1 Linux的IO设备管理方法
所有的I/O设备(例如网络、磁盘和终端)都被模型化为文件,而所有的输入和输出都被当做对相应文件的读和写来执行,这种将设备优雅地映射为文件的方式,允许Linux内核引出一个简单低级的应用接口,称为Unix I/O,这使得所有的输入和输出都能以一种统一且一致的方式来执行。
设备的模型化:文件
设备管理:unix io接口
8.2 简述Unix IO接口及其函数
Unix I/O使得所有的输入输出都能以一种统一且一致的方式来执行:
(1)打开文件。一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个I/O设备,内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件,内核记录有关这个打开文件的所有信息。应用程序只需记住这个描述符。
(2)Linux shell创建的每个进程都有三个打开的文件:标准输入(描述符为0),标准输出(描述符为1),标准错误(描述符为2)。头文件< unistd.h >定义了常量STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO,它们可用来代替显式的描述符值。
(3)改变当前的文件位置。对于每个打开的文件,内核保持着一个文件位置k,初始为0,这个文件位置是从文件开头起始的字节偏移量,应用程序能够通过执行seek,显式地将改变当前文件位置k。
(4)读写文件。一个读操作就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增加到k+n,给定一个大小为m字节的而文件,当k>=m时,执行读操作会触发EOF的条件,应用程序能检测到这个条件。类似地,写操作就是从内存中复制n>0个字节到一个文件,从当前文件位置k开始,然后更新k。
(5)关闭文件。当应用完成了对文件的访问后,它就通知内核关闭这个文件。作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中去。无论一个进程因为何种原因终止时,内核都会关闭所有打开的文件并释放它们的内存资源。

函数:
(1)进程是通过调用open函数来打开一个存在的文件或者创建一个新文件的。
函数声明如下:
int open(char *filename, int flags, mode_t mode);
open函数将filename转换为一个文件描述符,并且返回描述符数字。返回的描述符总是在进程中当前没有打开的最小描述符。flags参数指明了进程打算如何访问这个文件。O_RDONLY:只读 O_WRONLY:只写 O_RDWR:可读可写
mode参数指定了新文件的访问权限位。作为上下文的一部分,每个进程都有一个umask,它是通过调用umask函数来设置的。当进程通过带某个mode参数的open函数调用来创建一个新文件时,文件的访问权限位被设置成mode&~umask。
成功返回新文件描述符,若出错返回-1。
(2)进程通过调用close函数关闭一个打开的文件。
函数声明如下:
int close(int fd);
  成功返回0错误,若出错返回-1
(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的实现分析
首先来看看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; 
    } 
Vsprintf的函数如下:

printf需要做的事情是:接受一个fmt的格式,且传递参数的个数不确定。然后将匹配到的参数按照fmt格式输出。printf用了两个外部函数,一个是vsprintf,还有一个是write。
vsprintf函数作用是接受确定输出格式的格式字符串fmt(输入)。用格式字符串对个数变化的参数进行格式化,产生格式化输出,返回要打印字符串的长度。
系统级IO函数:write函数将buf中的i个元素写到终端。
write会给寄存器传递几个参数,初始化执行环境,然后执行sys call指令,这条指令的作用是产生陷阱异常。陷阱是有意的异常,用户程序执行了系统调用的命令之后,就导致了一个到异常处理程序的陷阱,这个处理程序解析参数,并调用适当的内核程序。
https://www.cnblogs.com/pianist/p/3315801.html
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
当程序调用getchar时,用户输入的字符被存放在键盘缓冲区中,直到用户按回车为止,需要注意的是,回车字符也放在缓冲区中。当用户键入回车之后,getchar才开始从stdin流中每次读入一个字符。getchar函数的返回值是用户输入的第一个字符的ASCII 码,如出错返回-1,且将用户输入的字符回显到屏幕。如用户在按回车之前输入了不止一个字符,其他字符会保留在键盘缓存区中,等待后续的getchar调用读取。也就是说,后续的getchar调用不会等待用户按键,而直接读取缓冲区中的字符,直到缓冲区中的字符读完为后,才等待用户按键。
进入getchar之后,进程会进入阻塞状态,等待外界的输入。系统开始检测键盘的输入。此时如果按下一个键,就会产生一个异步中断,这个中断会使系统回到当前的getchar进程,然后根据按下的按键,转化成对应的ascii码,保存到系统的键盘缓冲区。接下来,getchar调用了read函数。read函数会产生一个陷阱,通过系统调用,将键盘缓冲区中存储的刚刚按下的按键信息读到回车符,然后返回整个字符串。接下来getchar会对这个字符串进行处理,只取其中第一个字符,将其余输入简单的丢弃,然后将字符作为返回值,并结束。
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
第八章简单介绍了Linux系统下I/O的机制、Unix IO接口及其函数。简述了有关打开、关闭与读写文件的操作,并且分析了printf和getchar两个函数的实现过程。
(第8章1分)

结论

最简单的hello.c程序也要经历万般历练。(1)首先经过预处理,翻译成为hello.i文件(2)接着被编译为汇编语句文本文件hello.s(3)再之后被汇编器转化为可重定位目标文件hello.o(4)最后链接,和库函数们一起被链接为hello可执行文件。
至此生成了可执行文件,接下来就是运行的过程。
在shell中输入./hello,由bash新建进程,fork一个子进程并由execve函数加载运行hello,映射虚拟内存,载入物理内存。在函数中,hello调用printf函数和getchar函数,进一步利用write和read函数打印出字符串。且hello与其他进程并发运行。最后运行到exit函数,hello被shell父进程回收,内核释放删除掉它的全部数据所占的内存空间。
至此,hello的一生走到尽头。

用计算机系统的语言,逐条总结hello所经历的过程。
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
(结论0分,缺失 -1分,根据内容酌情加分)

附件
源程序:hello.c
预处理后的文件:hello.i
编译后的文件:hello.s
汇编后的文件:hello.o
可执行文件:hello
hello.o的ELF格式:hello.o.txt
hello的ELF格式:hello.txt

列出所有的中间产物的文件名,并予以说明起作用。
(附件0分,缺失 -1分)

参考文献
为完成本次大作业你翻阅的书籍与网站等
[1] 林来兴. 空间控制技术[M]. 北京:中国宇航出版社,1992:25-42.
[2] 辛希孟. 信息技术与信息服务国际研讨会论文集:A集[C]. 北京:中国科学出版社,1999.
[3] 赵耀东. 新时代的工业工程师[M/OL]. 台北:天下文化出版社,1998 [1998-09-26]. http://www.ie.nthu.edu.tw/info/ie.newie.htm(Big5).
[4] 谌颖. 空间交会控制理论与方法研究[D]. 哈尔滨:哈尔滨工业大学,1992:8-13.
[5] KANAMORI H. Shaking Without Quaking[J]. Science,1998,279(5359):2063-2064.
[6] CHRISTINE M. Plant Physiology: Plant Biology in the Genome Era[J/OL]. Science,1998,281:331-332[1998-09-23]. http://www.sciencemag.org/cgi/ collection/anatmorp.
(参考文献0分,缺失 -1分)

HITCSAPP大作业 2021 春相关推荐

  1. 哈工大 计算机系统 大作业 2021春

    计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 计算机系 学    号 1190500812 班    级 1903005 学       生 吴宇辰 指 导 ...

  2. 哈尔滨工业大学计算机系统大作业2022春

    计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 计算机 学   号 120L02**** 班   级 2003005 学       生 无敌飞龙 指 导 教 ...

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

    计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 计算学部 学    号 7203610401 班    级 2036012 学       生 王元辰 指 导 ...

  4. HITCSAPP大作业——程序人生

      摘  要 本文基于linux系统,利用gcc,edb,objdump等工具,研究hello.c源程序是如何一步步变为可执行程序hello的.结合课内外知识.将计算机系统软硬件方面的知识与实际的程序 ...

  5. 哈尔滨工业大学CSAPP大作业-2022春

    计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 人工智能(未来技术) 学    号 7203610202 班    级 2036015 学       生 熊 ...

  6. 哈尔滨工业大学 计算机系统 大作业 22春

    计算机系统大作业 计算机科学与技术学院 2022年5月 摘 要 摘要是论文内容的高度概括,应具有独立性和自含性,即不阅读论文的全文,就能获得必要的信息.摘要应包括本论文的目的.主要内容.方法.成果及其 ...

  7. 哈工大计算机系统大作业2022春

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

  8. HIT-CSAPP大作业:程序人生-Hello’s P2P

    摘  要 本文从一个简单的C语言程序hello出发,按照一个程序执行的顺序,分别对hello执行过程中预处理.编译.汇编.链接.进程管理.储存管理.I/O管理步骤进行了详细的讨论和研究,从程序和程序员 ...

  9. HIT-CSAPP大作业程序人生

最新文章

  1. Android 开发利用wifi调试
  2. CSS基础(part14)--定位
  3. PHP高并发高负载系统架构
  4. java 不加锁_在java中,在高并发的时候,不加锁的时候。
  5. Centos7使用Yum安装高版本的LNMP
  6. 关于并发数与在线数的概念
  7. 深度学习的半精度浮点数的运用
  8. 用python实现中文词云完整流程(wordcloud、jieba)
  9. 读掘金小册组件精讲总结2
  10. CentOS7.0+phpwind9.0.1环境搭建
  11. php tp5生成条形码,TP5条形码
  12. 云计算概论-大数据与云计算
  13. 经典网页设计:25个精美的全屏背景网站设计作品
  14. 无线基础知识学习(一)
  15. 手机维修刷机专业论坛:天目通移动维修论坛
  16. 【学习打卡】GradCAM可解释性分析
  17. 用c语言编写心里测试,写了个心理测试的c程序(*^__^*) 嘻嘻……
  18. python矩阵运算法则_导数与梯度、矩阵运算性质、科学计算库numpy
  19. Toolbar控件:32位真彩色大图标
  20. 线性支持向量机的随机梯度下降

热门文章

  1. 永擎服务器主板稳定性,支持AMD 64核撕裂者、17块硬盘扩展,双万兆:永擎发布TRX40D8-2N2T高端服务器工作站主板...
  2. java 图片合成_java 将两张相片合成一张,开发实用类
  3. 纯CSS教你实现磨砂玻璃背景效果(附代码)
  4. 用了十年竟然都不对,Java、Rust、Go主流编程语言的哈希表比较
  5. 为你的 Linux 桌面设置一张实时的地球照片
  6. 2022年最新版Spring专项面试突击
  7. 华为手机显示切换服务器,手机切换云服务器
  8. 设计模式——结构型模式
  9. webdav服务器文件大小限制,WebDAV服务器
  10. 物联网是大家都看好的创业方向