Hello程序是每个程序员起步的第一个程序。本文重要围绕hello的一生介绍hello从普通文字转变为可执行程序的过程,包括预处理,编译,汇编,链接等待。本文还将介绍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即 From Program to Process,即文本文件hello. C(源程序,文本)文件通过预处理器cpp预处理变成hello.i文件(修改了的源程序,文本),然后通过编译器ccl编译成为hello.s文件(汇编程序,文本),而后通过汇编器as汇编得到hello.o文件(可重定位目标程序,二进制),最后通过链接器ld变成hello(可执行程序,二进制)。
hello在shell中输入“./hello”运行hello程序,shell为其fork,产生子进程, 此时hello.c从Program(程序)变为Process(进程)。
然后, 调用 execve 函数在新的子进程中加载并运行 hello,调用函数,之后转移到hello的mian函数,CPU 为运行的 hello 分配时间片执行逻辑控制流。当程序运行结束时回收进程,释放内存,删除与执行程序相关的数据结构,便为hello的O2O。

1.2 环境与工具
硬件:X64 Intel core i5-8250 @1.60GHz 1.80GHz; 8G RAM; 256G SSD;
软件:Windows10,Ubuntu 18.04
工具:gcc,as,ld,edb,readelf,HexEdit,gedit,vim;
1.3 中间结果
hello.i 预处理产生文件
hello.s 编译产生文件
hello.o 汇编产生文件
hello 链接产生可执行文件
hello.elf hello.o的elf文件
hello.objdump hello反汇编生成文件
1.4 本章小结
介绍hello,陈述hello的中间文件,交代运行环境

第2章 预处理
2.1 预处理的概念与作用
预处理是指在程序源代码被翻译为目标代码的过程中,生成二进制代码之前的过程。由预处理器对程序源代码文本进行处理,得到的结果再由编译器核心进一步编译。这个过程并不对程序的源代码进行解析,但它把源代码分割或处理成为特定的单位。
预处理的作用:
1.宏定义(#define);
2.文件包含(#include);
3.条件编译(#ifdef/#ifndef/#if/#else/#elseif/#endif)。

2.2在Ubuntu下预处理的命令

gcc -E -o hello.i hello.c

2.3 Hello的预处理结果解析

预处理将头文件stdio.h,unistd.h,stdlib.h的内容添加到hello.i中,并且删除所有注释
Hello.i代码有3000行

文件打开后如下:

2.4 本章小结

本章介绍了hello.c程序预处理变成hello.i文件的过程。

(第2章0.5分)

第3章 编译
3.1 编译的概念与作用
编译:利用编译程序从源语言编写的源程序产生目标程序的过程。
编译就是把高级语言变成计算机可以识别的2进制语言,计算机只认识1和0,编译程序把人们熟悉的语言换成2进制的。 编译程序把一个源程序翻译成目标程序的工作过程分为五个阶段:词法分析;语法分析;语义检查和中间代码生成;代码优化;目标代码生成。主要是进行词法分析和语法分析,又称为源程序分析,分析过程中发现有语法错误,给出提示信息。
作用:将高级语言转化为机器语言,使得机器能够理解和执行。

3.2 在Ubuntu下编译的命令
gcc -S hello.i -o hello.s

3.3 Hello的编译结果解析

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

此部分是重点,说明编译器是怎么处理C语言的各个数据类型以及各类操作的。应分3.3.1~ 3.3.x等按照类型和操作进行分析,只要hello.s中出现的属于大作业PPT中P4给出的参考C数据与操作,都应解析。
3.3.1文件声明解析
声明 含义
.file 源文件
.text 代码段
.data 数据段(存储已初始化的全局和静态C变量)
.align 对齐格式
.type 符号类型
.size 数据空间大小
.section.rodata 只读代码段
.global 全局变量
.string 字符串类型数据
.long 长整型数据
3.3.2数据
1.int sleepsecs=2.5
sleepsecs在程序中被声明为全局变量,并且赋值,编译器处理时在.data段声明该变量,.data段存放已经初始化的全局和静态变量。

大小是4个字节,且为4字节对齐格式,类型为对象类型。
2.int i
在程序中声明,使用-4(%rbp)代表的栈空间来保存i

局部变量i:直接在使用该变量时用mov指令将其赋值为0

3.int argc
arg使用-0x20(%rbp)代表的栈空间来保存argc。

4.立即数
exit(1)

5.字符串
字符串被保存到了rodata段中

6.数组
源程序中的数组的是:char *argv[],是函数执行时输入的命令行,argv作为存放char指针的数组同时是第二个参数传入。
出现在栈帧中

3.3.3运算与操作
1.赋值操作
hello.c 的赋值操作有两处,一是将全局变量 sleepsecs 赋值为 2.5,二是在循 环开始时将 i 赋值为 0。源程序对i赋值为零的操作使用mov语句实现的。

2.比较操作
比较操作有2处

3.数组操作
argv[1]:首先从-32(%rbp)读取argv地址存入rax,接下来rax增加8个字节,此时rax中存放的是&(argv[1]),读取此地址指向的argv[1]放入rax,最后存入rsi

argv[2]:首先从-32(%rbp)读取argv地址存入rax,接下来rax增加16个字节,此时rax中存放的是&(argv[2]),读取此地址指向的argv[2]放入rdx。

3.3.4类型转换
int sleepsecs=2.5;
将浮点数类型的2.5转换为int类型。
但是发现,由于sleepsecs为全局变量,在.data段声明,且直接初始化为.long 2,是隐式转换。double强制转化为int类型。遵从向零舍入的原则,将2.5舍入为2

3.3.5算数操作

程序中使用的算数操作:
i++,对计数器i自增,使用程序指令addl,后缀l代表操作数是一个4B大小的数据。
3.3.6控制转移
控制转移往往与关系操作配合进行,如果满足某个条件,则跳转至某个位置。
Hello.s:

Hello.c:

if(argc!=3),编译器处理为了先判断 argc 和3是否相等,若相等,则跳转至.L2,否则继续执行之后的内容。而对于 for 循环中的 i<10,编译器则会判断i是否小于等于9,若是,则继续执行循环体内的内容,否则继续执行之后的内容。
3.3.7函数调用
1.main函数
main 函数开始时被存在.text 节,标记类型为函数,程序运行时,将由系统 启动函数调用,因此 main 函数是 hello.c 的起点。main 函数的两个参数分 别为 argc 和 argv[],由命令行输入,存储在%rdi 和%rsi 中。

2.printf函数
传递数据:第一次printf将%rdi设置为“Usage: Hello 学号 姓名!\n”字符串的首地址。第二次printf设置%rdi为“Hello %s %s\n”的首地址,设置%rsi为argv[1],%rdx为argv[2]。
控制传递:第一次printf因为只有一个字符串参数,所以call puts@PLT;第二次printf使用call printf@PLT。

3.exit函数
将%edi设置为1。call exit@PLT。

4.sleep函数:

将%edi设置为sleepsecs。call sleep@PLT。

5.getchar函数:
call gethcar@PLT

3.4 本章小结
本章介绍了hello.i到hello.s的转变,即编译过程。
分析了其汇编代码

第4章 汇编
4.1 汇编的概念与作用

概念:把生成的汇编指令逐条翻译成机器可以识别的形式,即机器码。
作用:汇编器(as)将hello.s翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序的格式,并把结果保存在目标文件hello.o当中。hello.o文件是一个二进制文件.
4.2 在Ubuntu下汇编的命令
as hello.s -o hello.o

4.3 可重定位目标elf格式
readelf -a hello.o获得hello.o文件的ELF格式
1.ELF头

2.节头
包含了文件中出现的各个节的语义,包括节的类型、位置和大小等信息

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

  2. 符号表

4.4 Hello.o的结果解析
objdump -d -r hello.o生成反汇编代码。

机器语言反汇编后得到的代码与 hello.s 中的汇编代码大致相同, 只有一些地方存在差异:
分支转移:反汇编代码跳转指令的操作数使用的不是段名称如.L3,因为段名称只是在汇编语言中便于编写的助记符,所以在汇编成机器语言之后显然不存在,而是确定的地址。
函数调用:在.s 文件中,函数调用之后直接跟着函数名称,而在反汇编程 序中,call 的目标地址是当前下一条指令。这是因为 hello.c 中调用的函数 都是共享库中的函数,最终需要通过动态链接器才能确定函数的运行时执 行地址,在汇编成为机器语言的时候,对于这些不确定地址的函数调用, 将其 call 指令后的相对地址设置为全 0(目标地址正是下一条指令),然 后在.rela.text 节中为其添加重定位条目,等待静态链接的进一步确定。
全局变量访问:在.s 文件中,访问 rodata(printf 中的字符串),使用段名称+%rip,在反汇编代码中 0+%rip,因为 rodata 中数据地址也是在运行时 确定,故访问也需要重定位。所以在汇编成为机器语言时,将操作数设置为全0并添加重定位条目。

4.5 本章小结
本章节记录了从hello.s到机器指令hello.o的过程,通过反汇编手段,分析了机器指令和汇编指令的不同,同时展示了elf表中信息

第5章 链接
5.1 链接的概念与作用
概念:链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载(复制)到内存中执行。链接可以执行于编译时,也就是源代码被翻译成机器代码时;链接也可以执行于加载时,也就是程序被加载器加载到内存中被执行时;甚至被执行于运行时,也就是由应用程序来执行。
作用:链接器(ld)将hllo.o文件与标准C库里面的用到的函数如printf函数所在的预编译好了的单独的文件printf.o合并成一个文件,合并成的文件则为hello可执行文件.
5.2 在Ubuntu下链接的命令
(以下格式自行编排,编辑时删除)
使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件
命令: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.3 可执行目标文件hello的格式
readelf -a hello > hello.elf 命令生成 hello 程序的 ELF 格式文件。
1.ELF头

2.Section Headers

5.4 hello的虚拟地址空间
使用edb加载hello后,在Data Dump中查看虚拟地址信息

5.5 链接的重定位过程分析
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
objdump -d -r hello > hello.objdump生成反汇编文件

Hello与hello.o的不同之处:
1.hello.o中的相对偏移地址到了hello中变成了虚拟内存地址
2.hello中将需要用到的一些库函数和外部函数写入进来。
3.Hello中使用的跳转地址和函数调用地址均为虚拟内存地址。
4.Hello中添加了许多节如.init,.plt等,而hello.o中只有.text节
2.链接的过程:在使用ld命令链接的时候,指定了动态链接器为64的/lib64/ld-linux-x86-64.so.2,crt1.o、crti.o、crtn.o中主要定义了程序入口_start、初始化函数_init,_start程序调用hello.c中的main函数,libc.so是动态链接共享库,其中定义了hello.c中用到的printf、sleep、getchar、exit函数和_start中调用的__libc_csu_init,__libc_csu_fini,__libc_start_main。链接器将上述函数加入。
3.重定位:链接器完成符号解析后,将代码中每个符号引用和一个符号定义(即它的一个输入目标模块中的一个符号表条目)关联起来,此时,链接器就知道了它的输入目标模块中的代码节和数据节的确切大小。重定位首先应进行重定位节和符号定义,在这一步骤中,链接器将所有相同类型的节合并为同一类型的新的聚合节,然后程序将运行时内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号,由此程序中的每一条指令和全局变量都有唯一的运行时内存地址。其次,进行重定位节中的符号引用,这一步中,链接器修改代码节和数据节中对每个符号的引用,使得它们指向正确的运行时地址。
通过书中概念我又总结了重定位地址的计算方法,更易理解,过程如下:
重定位地址的计算:首先获取各节的大小,然后直接通过对应的节与程序头之间的相对距离大小确定节头的地址,节中对应的指令地址则由与节头之间的相对地址计算,各指令的具体地址均可由此获知。call指令后接的函数的绝对地址则应通过相对地址+PC(也就是下一条指令的地址)的方式进行计算

5.6 hello的执行流程
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。

使用edb单步调试执行hello注意call的调用
载入:
_dl_start
_dl_init
开始执行:
__stat
_cax_atexit
_new_exitfn
_libc_start_main
_libc_csu_init
运行:
_main
_printf
_exit
_sleep
_getchar
_dl_runtime_resolve_xsave
_dl_fixup
_dl_lookup_symbol_x
退出:
Exit

5.7 Hello的动态链接分析
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
全局偏移量表(GOT):在GOT中,每个被这个目标模块引用的全局数据目标(过程或全局变量)都有一个8字节条目。编译器还为GOT中每个条目生成 一个重定位记录。在加载时,动态链接器会重定位GOT中的每个条目,使得它包含目标的正确的绝对地址。

dl_init函数地址:

变成了

在dl_init调用之后,0x6008b8和0x6008c0处的两个8B数据分别发生改变为00007f1ee350f168和00007f1ee32ff870,其中GOT[1]指向重定位表(依次为.plt节需要重定位的函数的运行时地址)用来确定调用的函数地址,GOT[2]指向动态链接器ld-linux.so运行时地址。
5.8 本章小结
本章将hello.o链接成了hello,分析了hello的ELF格式,虚拟地址空间的分配,重定位和执行过程还有动态链接的过程。

第6章 hello进程管理
6.1 进程的概念与作用
概念:一个执行中程序的实例,一个程序关于某个数据集合的一次运行活动。
作用:提供给应有程序一个独立的逻辑控制流和一个私有的地址空间,好像程序在独占的使用处理器和内存系统
6.2 简述壳Shell-bash的作用与处理流程
shell是一个应用程序,他在操作系统中提供了一个用户与系统内核进行交互的界面,用户通过这个界面访问操作系统内核的服务。
处理流程:
1.从终端读入输入的命令。
2.将输入字符串切分获得所有的参数
3.如果是内置命令则立即执行
4.否则调用相应的程序为其分配子进程并运行
5.shell应该接受键盘输入信号,并对这些信号进行相应处理
6.3 Hello的fork进程创建过程
在shell中输入命令./hello ,shell 会对输入的命令进行解析,发现不是内置命令则调用当前目录下的可执行文件, shell通过调用fork函数创建一个新的运行的子进程,新创建的hello进程几乎但不完全与父进程相同:hello进程得到与父进程虚拟地址空间相同的(但是独立的)一份副本。hello进程获得与父进程任何打开文件描述符相同的副本。最大区别:hello进程有不同于父进程的PID
6.4 Hello的execve过程
在新创建的子进程中,execve函数加载并运行hello,且带参数列表argv和环境变量envp。在execve加载了hello之后,它调用_start,_start设置栈,并将控制传递给新程序的主函数。
6.5 Hello的进程执行
(以下格式自行编排,编辑时删除)
结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。
在hello运行时,也有一些其它进程在并行地运行,这些进程的逻辑流的执行时间与hello的逻辑流重叠,称为并发流。而一个进程和其它进程轮流运行的概念叫作多任务,一个进程执行它的控制流的一部分的每一时间段叫做时间片。因此,多任务也叫做时间分片。

上下文信息:上下文就是内核重新启动一个被抢占的进程所需要的状态,它由通用寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构等对象的值构成。
hello调用printf与sleep,这两个函数引发系统调用,系统调用使得进程从用户模式变为内核模式,处理程序运行在内核模式中,当它返回到应用程序代码时,处理器就把模式从内核模式改回到用户模式,而执行sleep系统调用时,内核可能会执行上下文切换而非将控制返回给hello进程。在切换的第一部分中,内核代表hello在内核模式下执行指令,然后在某一时刻,它开始代表另一个进程在内核模式下执行指令,在切换之后,内核代表那个进程在用户模式下执行指令。
而这个切换过程可以分为三个步骤:
1.保存当前进程的上下文。
2.恢复某个先前被抢 占的进程被保存的上下文。
3.将控制传递给这个新恢复的进程。
示意图:

6.6 hello的异常与信号处理
1.正常运行

2.Ctrl-Z
这个操作向进程发送了一个sigtstp信号,让进程暂时挂起,输入ps命令符可以发现hello进程还没有被关闭。

3.Ctrl-C
这个操作向进程发送了一个sigint信号,让进程直接结束,输入ps命令可以发现当前hello进程已经被终止了。
先杀死了前面Crtl-Z挂起的进程。

4.随意乱按

程序直接忽视其他按键,正常运行后再出现执行随意输入的命令
6.7本章小结
(以下格式自行编排,编辑时删除)、
本章介绍了Shell的一般处理流程,调用fork创建新进程,调用execve执行hello,hello的进程执行,hello的异常,信号处理。

第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址: 是指由程式产生的和段相关的偏移地址部分。
线性地址: 是逻辑地址到物理地址变换之间的中间层。程式代码会产生逻辑地址,或说是段中的偏移地址,加上相应段的基地址就生成了一个线性地址。如果启用了分页机制,那么线性地址能再经变换以产生一个物理地址。若没有启用分页机制,那么线性地址直接就是物理地址。
物理地址: 是指出目前CPU外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果地址。如果启用了分页机制,那么线性地址会使用页目录和页表中的项变换成物理地址。如果没有启用分页机制,那么线性地址就直接成为物理地址了。
虚拟内存:是指计算机呈现出要比实际拥有的内存大得多的内存量。因此他允许程式员编制并运行比实际系统拥有的内存大得多的程式。这使得许多大型项目也能够在具有有限内存资源的系统上实现。
7.2 Intel逻辑地址到线性地址的变换-段式管理
在段式存储管理中,将程序的地址空间划分为若干个段(segment),这样每个进程有一个二维的地址空间。在前面所介绍的动态分区分配方式中,系统为整个进程分配一个 连续的内存空间。而在段式存储管理系统中,则为每个段分配一个连续的分区,而进程中的各个段可以不连续地存放在内存的不同分区中。
程序加载时,操作系统为 所有段分配其所需内存,这些段不必连续,物理内存的管理采用动态分区的管理方法。在为某个段分配物理内存时,可以采用首先适配法、下次适配法、最佳适配法 等方法。
在回收某个段所占用的空间时,要注意将收回的空间与其相邻的空间合并。段式存储管理也需要硬件支持,实现逻辑地址到物理地址的映射。
程序通过分段 划分为多个模块,如代码段、数据段、共享段。这样做的优点是:可以分别编写和编译源程序的一个文件,并且可以针对不同类型的段采取不同的保护,也可以按段为单位来进行共享。总的来说,段式存储管理的优点是:没有内碎片,外碎片可以通过内存紧缩来消除;便于实现内存共享。缺点与页式存储管理的缺点相同,进程必须全部装入内存。
7.3 Hello的线性地址到物理地址的变换-页式管理
CPU的页式内存管理单元,负责把一个线性地址,转换为物理地址。Linux将虚拟内存组织成一些段的集合,段之外的虚拟内存不存在因此不需要记录。内核为hello进程维护一个段的任务结构即图中的task_struct,其中条目mm指向一个mm_struct,它描述了虚拟内存的当前状态,pgd指向第一级页表的基地址(结合一个进程一串页表),mmap指向一个vm_area_struct的链表,一个链表条目对应一个段,所以链表相连指出了hello进程虚拟内存中的所有段。
7.4 TLB与四级页表支持下的VA到PA的变换
首先将VPN分成三段,对于TLBT和TLBI来说,如果可以在TLB中找到对应的PPN的话那肯定是最好不过的了,但是还有可能出现缺页的情况,这时候就需要到页表中去找。此时,VPN被分成了更多段(这里是4段)CR3是对应的L1PT的物理地址,然后一步步递进往下寻址,越往下一层每个条目对应的区域越小,寻址越细致,在经过4层寻址之后找到相应的PPN让你和和VPO拼接起来。
7.5 三级Cache支持下的物理内存访问
首先CPU发出一个虚拟地址,在TLB里面寻找。如果命中,那么将PTE发送给L1Cache,否则先在页表中更新PTE。然后再进行L1根据PTE寻找物理地址,检测是否命中的工作。这样就能完成Cache和TLB的配合工作
7.6 hello进程fork时的内存映射
虚拟内存和内存映射解释了fork函数如何为每个新进程提供私有的虚拟地址空间。Fork函数为新进程创建虚拟内存。创建当前进程的的mm_struct, vm_area_struct和页表的原样副本,两个进程中的每个页面都标记为只读,两个进程中的每个区域结构(vm_area_struct)都标记为私有的写时复制(COW)。在新进程中返回时,新进程拥有与调用fork进程相同的虚拟内存,随后的写操作通过写时复制机制创建新页面。
7.7 hello进程execve时的内存映射
Execve函数在当前进程中,加载并运行包含在可执行目标文件hello中的程序,用hello程序有效地替代了当前程序。加载并运行hello需要以下几个步骤:
1.删除已存在的用户区域。删除shell虚拟地址的用户部分中的已存在的区域结构。
2.映射私有区域。为hello的代码、数据、bss 和栈区域创建新的区域结构。所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为hello 文件中的.text和.data 区。bss 区域是请求二进制零的,映射到匿名文件,其大小包含在hello 中。栈和堆区域也是请求二进制零的,初始长度为零。下图概括了私有区域的不同映射。
3.映射共享区域。如果hello程序与共享对象(或目标)链接,比如标准C 库libc. so, 那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内。
4.设置程序计数器(PC) execve 做的最后一件事情就是设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。
7.8 缺页故障与缺页中断处理
情况1:段错误:首先,先判断这个缺页的虚拟地址是否合法,那么遍历所有的合法区域结构,如果这个虚拟地址对所有的区域结构都无法匹配,那么就返回一个段错误(segment fault)。
情况2:非法访问:接着查看这个地址的权限,判断一下进程是否有读写改这个地址的权限。
情况3:如果不是上面两种情况那就是正常缺页,那就选择一个页面牺牲然后换入新的页面并更新到页表。
7.9动态存储分配管理
动态内存分配器简介:
动态内存分配器维护着一个进程的虚拟内存区域,称为堆(heap) 。系统之间细节不同,但是不失通用性,假设堆是一个请求二进制零的区域,它紧接在未初始化的数据区域后开始,并向上生长(向更高的地址) 。对于每个进程,内核维护着一个变量brk, 它指向堆的顶部。
分配器将堆视为一组不同大小的块(block) 的集合来维护。每个块就是一个连续的虚拟内存片(chunk),要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。
基本方法:
1.首次适配从头开始搜索空闲链表,选择第一个合适的空闲块,优点是趋向于将大的空闲块保留在链表的后面,缺点是会在链表的起始处留下许多小空闲块的碎片。
2.显示空闲链表。在空闲块中使用指针连接空闲块。
3.分离的空闲链表.维护多个空闲链表,每个链表的块有大致相等的大小
放置策略:
1.隐式空闲链表是空闲块通过头部中的大小字段隐含着连接的,分配器可以通过遍历堆中的所有块简介遍历整个空闲块集合,优点是简单,缺点是比较浪费时间。
2.下次适配。下次适配和首次适配的方式很相似,只不过是从上一次查询结束的地方开始,而不是从链表的起始处开始搜索。
3.最佳适配。检查每个空闲块,选择适合所需请求大小的空闲块。

7.10本章小结
本章介绍了储存器的地址空间,讲述了虚拟地址、物理地址、线性地址、逻辑地址的概念,还有进程fork和execve时的内存映射的内容。描述了malloc的内存分配管理机制。本章还描述了系统是如何应对缺页故障现象的,讲述了动态存储分配的多种管理方式。

第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:所有的 IO 设备都被模型化为文件,而所有的输入和输出都被 当做对相应文件的读和写来执行,这种将设备优雅地映射为文件的方式,允许 Linux 内核引出一个简单低级的应用接口,称为 Unix I/O
8.2 简述Unix IO接口及其函数
Unix I/O 接口的几种操作:

  1. 打开文件:程序要求内核打开文件,内核返回一个小的非负整数(描 述符),用于标识这个文件。程序在只要记录这个描述符便能记录打 开文件的所有信息。 2. shell 在进程的开始为其打开三个文件:标准输入、标准输出和标准错 误。
  2. 改变当前文件的位置:对于每个打开的文件,内核保存着一个文件位 置 k,初始为 0。这个文件位置是从文件开头起始的字节偏移量。应用 程序能够通过执行 seek 操作显式地设置文件的当前位置为 k。
  3. 读写文件:一个读操作就是从文件复制 n>0 个字节到内存,从当前文 件位置 k 开始,然后将 k 增加到 k+n。给定一个大小为 m 字节的文件, 当 k>=m 时执行读操作会出发一个称为 EOF 的条件,应用程序能检测 到这个条件,在文件结尾处并没有明确的 EOF 符号。
  4. 关闭文件:内核释放打开文件时创建的数据结构以及占用的内存资源, 并将描述符恢复到可用的描述符池中。无论一个进程因为何种原因终 止时,内核都会关闭所有打开的文件并释放它们的内存资源。
    Unix I/O函数:
    1.进程是通过调用open 函数来打开一个已存在的文件或者创建一个新文件的:
    int open(char *filename, int flags, mode_t mode);
    open 函数将filename 转换为一个文件描述符,并且返回描述符数字。返回的描述符总是在进程中当前没有打开的最小描述符。flags 参数指明了进程打算如何访问这个文件,mode 参数指定了新文件的访问权限位。
    返回:若成功则为新文件描述符,若出错为-1。
  5. 进程通过调用close 函数关闭一个打开的文件。
    int close(int fd);
    返回:若成功则为0, 若出错则为-1。
  6. 应用程序是通过分别调用read write 函数来执行输入和输出的。
    ssize_t read(int fd, void *buf, size_t n);
    read 函数从描述符为fd 的当前文件位置复制最多n 个字节到内存位置buf 。返回值-1表示一个错误,而返回值0 表示EOF。否则,返回值表示的是实际传送的字节数量。
    返回:若成功则为读的字节数,若EOF 则为0, 若出错为-1。
    ssize_t write(int fd, const void *buf, size_t n);
    write 函数从内存位置buf 复制至多n 个字节到描述符fd 的当前文件位置。
    返回:若成功则为写的字节数,若出错则为-1。
    8.3 printf的实现分析
    https://www.cnblogs.com/pianist/p/3315801.html
    Printf代码:

Printf源代码:

由printf源代码得知arg表示的是输入的第一个参数,由vsprintf源代码得知vsprintf返回的是一个长度,即要打印出来的字符串的长度。再看printf中后面的一句:write(buf, i),它的功能为将buf中的第i个字符写到终端。
write函数的实现过程write函数的实现过程:

sys_call的源代码:

本质上该函数的功能就是不断的打印出字符,直到遇到’\0’

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

8.4 getchar的实现分析
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
getchar调用了一个read函数,这个read函数是将整个缓冲区都读到了buf里面,然后将返回值是缓冲区的长度。我们可以发现,如果buf长度为0,getchar才会调用read函数,否则是直接将保存的buf中的最前面的元素返回。
8.5本章小结
本章节讲述了I/O设备管理机制,接口及相关函数,简单分析了printf和getchar函数的实现方法以及操作过程。

结论
hello所经历的过程
1.用户从键盘输入,得到hello.c源文件。
2.编译器和汇编器对hello.c进行预处理,然后对其进行编译和汇编,得到可重定位目标文件hello.o。
3.链接器对hello.o进行链接,并得到可执行目标文件hello,此时hello已经可以被操作系统加载和执行。
4.bash执行hello,首先bash会fork一个进程,然后在这个新的进程中execve hello,execve会清空当前进程的数据并加载hello,然后把rip指向hello的程序入口,把控制权交给hello。
5.hello与许多进程并行执行,执行过程中由于系统调用或者计时器中断,会导致上下文切换,内核会选择另一个进程进行调度,并抢占当前的hello进程。
6.hello执行的过程中可能收到来自键盘或者其它进程的信号,当收到信号时hello会调用信号处理程序来进行处理,可能出现的行为有停止终止忽略等。
7.hello输出信息时需要调用printf和getchar,而printf和getchar的实现需要调用Unix I/O中的write和read函数,而它们的实现需要借助系统调用。
8.hello中的访存操作,需要经历逻辑地址到线性地址最后到物理地址的变换,而访问物理地址的数据可能已被缓存至高速缓冲区,也可能位于主存中,也可能位于磁盘中等待被交换到主存。
9.hello结束进程后,bash作为hello的父进程会回收hello进程

附件
hello.i 预处理产生文件
hello.s 编译产生文件
hello.o 汇编产生文件
hello 链接产生可执行文件
hello.elf hello.o的elf文件
hello.objdump hello反汇编生成文件

参考文献
[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.
[7]内存管理之一 段式与页式管理https://www.cnblogs.com/xavierlee/p/6400230.html
[8] 机器语言到汇编 https://wenku.baidu.com/view/0d3ee0a0453610661ed9f4ae.html
[9] Pianistx----printf函数实现的深入剖析https://www.cnblogs.com/pianist/p/3315801.html

csapp 大作业 hello的自白相关推荐

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

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

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

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

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

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

  4. HIT CSAPP大作业论文

    计算机系统 大作业 题 目 程序人生-Hello's P2P 专 业 计算机类 学 号 1190201816 班 级 1903012 学 生 樊红雨 指 导 教 师 史先俊 计算机科学与技术学院 20 ...

  5. 哈工大csapp大作业报告

    计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 计算学部 学    号 120L022422 班    级 2003008 学       生 曹瑞麒 指 导 ...

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

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

  7. 哈工大 csapp大作业 120L021917

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

  8. CSAPP大作业论文 程序人生

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

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

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

  10. 2023春哈工大CSAPP大作业

    计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 网络空间安全 学   号 2021112406 班   级 2103901 学       生 刘晓赟 指 导 ...

最新文章

  1. 基于Redis实现分布式应用限流--转
  2. 5、MySQL事务隔离级别详解
  3. [BUUCTF-pwn]——jarvisoj_level1
  4. 【转】xargs命令详解,xargs与管道的区别
  5. 流量计算机标准,流量计算机
  6. NET Core微服务之路:SkyWalking+SkyApm-dotnet分布式链路追踪系统的分享
  7. 虚拟打印的实现-安装
  8. 基于SkyEye的覆盖率测试
  9. 为 Joomla 而生的 Kunena 论坛安装手册
  10. Linux下安装PHP扩展
  11. 信息技术(计算机基础知识精华版)
  12. C语言编程怎么搜答案,C语言编程题及答案
  13. 两台redhat7虚拟机配置ssh免密访问
  14. html插入activex,在HTML网页中插入ActiveX控件
  15. oracle 查询入职年限,计算入职年限员工人数占总人数的百分比
  16. 【图形图像处理】之栅格化介绍
  17. 北京周末去哪儿 —— 香山
  18. 线程停止(stop/intrrupt)
  19. 【Java】线程池、Lambda表达式
  20. windows 10 时间同步,时间显示不准自动校准。

热门文章

  1. 广东英语高考怎么计算机,2019广东高考英语听说考试大纲出炉!附三大题型得分套路!...
  2. 基于Vue3和Nestjs的网上匿名聊天网站的实现
  3. 游戏自动化协议测试工具的开发个人思路
  4. otdr全部曲线图带解说_【赛事主播】你最期望哪个电竞选手组合来解说比赛?...
  5. ASPNET 5 和 dnx commands
  6. i技术会 | 爱奇艺品牌广告算法探索和实践
  7. pimple学习:源码解读
  8. 如何软著办理,软著申请步骤,软著办理流程
  9. minigui输入法汉字的获取
  10. 《近代名老中医经验集》出版