程序人生-Hello’s P2P

程序人生-Hello’s P2P
第1章 概述
1.1
Hello简介
我是Hello,我是每一个程序猿的初恋(羞
羞……),我是第一个第一个玩 P2P和020的
From Program to Process:
当hello一行一行的键入.c文件,它的一生就此开始,经过编译预处理器(cpp)的编译预处理变成.i文件,经过ccl的编译变成.s文件,as的汇编变成可重定位目标文件.o,链接器(ld)的链接产生可执行目标文件hello,在shell中键入启动命令,shell为hello进行fork子进程,hello便真正实现了From
Program to Process
O2O:From Zero-0 to Zero -0:
之后shell进行execve,先删除当前虚拟地址的用户部分已存在的数据结构,为hello的代码、数据、bss和栈区域创建新的区域结构,然后映射共享区域,设置程序计数器,映射虚拟内存,然后加载物理内存,,然后进入 main函数执行目标代码,CPU为运行的hello分配时间片执行逻辑控制流。当程序运行结束后,shell父进程负责回收hello进程,内核删除相关数据结构,以上全部便是020的过程。
根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
1.2 环境与工具
硬件环境
X64 CPU;2GHz;2G RAM;256GHD Disk 以上
1.2.2 软件环境
Visual Studio 2010 64位以上;GDB/OBJDUMP;DDD/EDB等
1.2.3 开发工具
Windows7 64位以上;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64位/优麒麟 64位,vim, gcc , as , ld , edb , readelf , HexEdit.
1.3 中间结果
在这里插入图片描述

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

1.4 本章小结

本章作为大作业的开头,简述了Hello的P2P,020的整个过程,基本上概述了hello一生的经过几部分,这些部分解析所用到的工具和文件,对整个hello一生列出一个框架结构

(第1章0.5分)

第2章 预处理

2.1 预处理的概念与作用

概念:编译预处理器(cpp)根据以字符#开头命令,修改原始c程序。比如hello.c中第六行,#include<stdio.h>命令告诉预处理器读取系统头文件stdio.h的内容,并直接插入程序文本,就得到了另一个C程序,以.i为文件扩展名

作用:

1.将hello.c中stdio.h,stdlib.h,unistd.h等#为开头的头文件读取到新的.i文件C程序中比如hello.c中第六行,#include<stdio.h>命令告诉预处理器读取系统头文件stdio.h的内容,并直接插入程序文本,就得到了另一个C程序,以.i为文件扩展名

2.删除注释,比如将hello.c文件中1-4行注释删除

3.执行宏定义(宏展开),用实际值替代宏定义字符串

4.条件编译,当某些语句希望在条件满足之后才编译,例如格式:

(1)#ifdef 标识符,程序段1,#else,程序段2,#endif

当表达式1成立时,编译程序段1,当不成立时,编译程序段2。

使用条件编译可以使目标程序变小,运行时间变短。

预编译使问题或算法的解决方案增多,有助于我们选择合适的解决方案

2.2在Ubuntu下预处理的命令
在这里插入图片描述

2.3 Hello的预处理结果解析

打开hello.i文件,hello.i程序已经扩展成3188行,main函数在hello.i文件最下面,自3099行开始,在3099行之前为hello.c中stdio.h,stdlib.h,unistd.h等#为开头的头文件读取到的.i文件C程序,三个头文件依次展开,cpp打开/usr/include/stdio文件,进一步解析替换stdio.h文件中的#define定义的宏定义字符串,然后进一步解析条件判断语句,判断其中包含的逻辑。cpp递归展开使得所有宏定义,以及条件判断语句进行展开解析

2.4 本章小结

本章主要介绍里预处理的定义概念以及功能作用,并通过hello.c以及预处理后hello.i的文件对比;对hello预处理结果进行解析,深入的了解了预编译过程的具体实现,以及特征

预处理程序把这些C程序通过预处理的命令处理好、替换好然后交给编译程序,使下一步编译可以顺利进行,提高接下来一步的执行效率。就好现象做饭一样,预处理程序就好像是择菜洗菜一样,处理好C程序之后再进行下一步,编译(切菜)。

(第2章0.5分)

第3章 编译

3.1 编译的概念与作用

概念:编译器(ccl)将文本hello.i翻译成文本文件hello.s,它包含一个汇编语言程序,该程序包含main函数的定义,每一条定义都以文本格式描述的一条低级机器语言指令。

作用:

1.编译进程将C语言程序转换为汇编语言程序,汇编语言非常有用,他为不同高级语言的不同编译程序提供通用输出语言。

2.编译器处理流程:词法分析,语法分析,中间代码,目标代码,表格管理,出错处理,

3.编译的基本功能是把源程序(高级语言)翻译成目标程序。但是,作为一个具有实际应用价值的编译系统,除了基本功能之外,还应具备语法检查、调试措施、修改手段、覆盖处理、目标程序优化、不同语言合用以及人-机联系等重要功能。①语法检查:检查源程序是否合乎语法。如果不符合语法,编译程序要指出语法错误的部位、性质和有关信息。编译程序应使用户一次上机,能够尽可能多地查出错误。②调试措施:检查源程序是否合乎设计者的意图。为此,要求编译程序在编译出的目标程序中安改、程序执行时所经历的线路等。这些信息有助于用户核实和验证源程序是否表达了算法要求。③修改手段:为用户提供简便的修改源程序的手段。编译程序通常要提供批量修改手段(用于修改数量较大或临时不易修改的错误)和现场修改手段(用于运行时修改数量较少、临时易改的错误)。④覆盖处理:主要是为处理程序长、数据量大的大型问题程序而设置的。基本思想是让一些程序段和数据公用某些存储区,其中只存放当前要用的程序或数据;其余暂时不用的程序和数据,先存放在磁盘等辅助存储器中,待需要时动态地调入。⑤目标程序优化:提高目标程序的质量,即占用的存储空间少,程序的运行时间短。依据优化目标的不同,编译程序可选择实现表达式优化、循环优化或程序全局优化。目标程序优化有的在源程序级上进行,有的在目标程序级上进行。⑥不同语言合用:其功能有助于用户利用多种程序设计语言编写应用程序或套用已有的不同语言书写的程序模块。最为常见的是高级语言和汇编语言的合用。这不但可以弥补高级语言难于表达某些非数值加工操作或直接控制、访问外围设备和硬件寄存器之不足,而且还有利于用汇编语言编写核心部分程序,置一些输出指令以便在目标程序运行时能输出程序动态执行情况的信息,如变量值的更以提高运行效率。⑦人-机联系:确定编译程序实现方案时达到精心设计的功能。目的是便于用户在编译和运行阶段及时了解内部工作情况,有效地监督、控制系统的运行。

注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序

3.2 在Ubuntu下编译的命令

应截图,展示编译过程!

3.3 Hello的编译结果解析

3.3.1指令解析

  .file "hello.c"   //声明原文件hello.c

  .text  //代码段,程序入口

  .globl     sleepsecs 

//全局变量声明sleepsecs

  .data  //已初始化的全局变量和静态变量(数据段)

  .align 4  //声明数据和指令地址对齐的方式为4

  .type      sleepsecs, @object  //声明函数类型

  .size       sleepsecs, 4  //声明sleepsecs大小

sleepsecs:

  .long      2  //声明一个long类型

  .section .rodata  //.rodata,只读数据

  .align 8  //声明数据和指令地址对齐的方式为8

.LC0:

  .string    "Usage: Hello 1171430615

\347\216\213\345\256\266\347\220\252\357\274\201"
//声明一个string类型

.LC1:

  .string    "Hello %s %s\n"  //声明一个string类型

  .text

  .globl     main

  .type      main, @function

3.3.2数据类型

Hello.c文件中数据类型有int型,数组argv[],输出的字符串

Hello.s文件中处理的数据类型有整数,数组,字符串。

  1. 整数

(1)C程序中int sleepsecs=2.5;全局变量,编译器处理时将其存放在.data节中;Hello.s文件中首先定义为全局变量,存放在.data中,对齐方式为4,设置为long类型,值为2,

(2)C程序中int i:编译器将局部变量储存在寄存器或者栈中

cmpl $9,
-4(%rbp) //i存储在栈上空间-4(%rbp)中;i占据了栈中的4B,

(3)int argv[]:作为参数传入

(4)立即数:直接编码到汇编代码中;比如:subq $32, %rsp;cmpl $9, -4(%rbp);

2.字符串

Hello.s文件汇编程序中的字符串为

字符串都声明在.section .rodata //.rodata,只读数据节

printf(“Usage: Hello 1171430615 王家琪!\n”);//输出格式化参数

.string “Usage: Hello 1171430615
\347\216\213\345\256\266\347\220\252\357\274\201”

字符串被编码成UTF-8格式,一个汉字在utf-8编码中占三个字节,一个\代表一个字节

printf(“Hello %s
%s\n”,argv[1],argv[2]); //输出格式化参数

3.数组,char*大小为8字节,

C程序中int
main(int argc,char *argv[]),argv作为char类型指针的数组,指针指向存放着字符指针的连续空间,起始地址为argv,函数在访问数组时分为argv[1],argv[2],在hello.s文件中使用两次%rax分别为两个数组首地址

按照起始地址argv大小8字节计算数据地址取数据

3.3.3类型转换,赋值,算术和逻辑运算操作

(1)类型转换

C程序中int
sleepsecs=2.5;(全局变量),将浮点数类型转换为int型。

浮点型数据强转为int类型,程序改变数值和位模式的原则为向零舍入

sleepsecs: .long 2 //2.5舍为2

(2)赋值操作

  1. int sleepsecs=2.5;(全局变量)

sleepsecs: .long 2
//2.5舍为2;.data节中将sleepsecs声明为值2的long类型数

  1. int i; for(i=0;i<10;i++)

i=0; // movl $0,
-4(%rbp)

mov指令完成,根据数据的大小不同使用不同后缀

整数操作数 b : 1字节、w :2 字节、l :4 字节、q :8字节

浮点型操作数 s : 单精度浮点数、 l :双精度浮点数

(3)算术和逻辑运算操作

i++运算操作

addl $1,
-4(%rbp)

对计数器i自增,程序指令addl,后缀l代表操作数大小为4字节

subq $32,
%rsp

addq $16,
%rax

对栈针进行移动操作

leaq .LC1(%rip),
%rdi

加载有效地址指令leaq;并计算LC1的段地址%rip+.LC1;并将地址传递到%rdi

3.3.4条件分支(关系控制跳转,循环)

C程序中判断argv不等于3 ,if(argc!=3)

汇编指令为

cmpl $3,
-20(%rbp) //利用cmpl指令,设置argv-3为条件码

  je    .L2  //通过条件码判断条件码是否为零(相等),若argv= =3则跳转到L2,执行下一个循环,若不为零,则顺序执行if下面的语句

leaq .LC0(%rip),
%rdi

  call puts@PLT

  movl      $1, %edi

  call exit@PLT

.L2:

  movl      $0, -4(%rbp)

  jmp .L3  //无条件跳转,for循环开始

.L4: //循环体中操作,for循环中指令

  movq     -32(%rbp), %rax

  addq      $16, %rax

  movq     (%rax), %rdx

  movq     -32(%rbp), %rax

  addq      $8, %rax

  movq     (%rax), %rax

  movq     %rax, %rsi

  leaq .LC1(%rip), %rdi

  movl      $0, %eax

  call printf@PLT

  movl      sleepsecs(%rip), %eax

  movl      %eax, %edi

  call sleep@PLT

  addl $1, -4(%rbp)

.L3:

  cmpl      $9, -4(%rbp)  //i<10;汇编指令通过,使用cmpl指令,比较i和9的大小关系,设置条件码i-9

  jle   .L4  //通过jle指令,判断如果i<9,则跳转到L4继续执行循环中操作,否则顺序执行下一条语句,跳出for循环

  call getchar@PLT

  movl      $0, %eax

  leave

  .cfi_def_cfa

7, 8

  ret

  .cfi_endproc

3.3.5过程(函数调用,参数传递)

过程机制:

传递控制1.调用:转到过程代码的起始处2.结束:回到返回点

传递数据1.过程参数2.返回值

内存管理1.过程运行期间申请2.返回时解除分配

过程内容:

1.栈结构;2.调用约定 ▪ 传递控制 ▪ 传递数据 ▪ 管理局部数据;3.递归

传递控制:

1.栈结构支撑过程调用,返回;

  1. 过程调用 call func_label;返回地址入栈(Push);跳转到func_label (函数名字就是函数代码段的起始地址)

3.返回地址:紧随call指令的下一条指令的地址

  1. 过程返回 re;从栈中弹出返回地址(pop);跳转到返回地址

函数调用过程数据流:传递数据

管理局部数据

1.进入过程时申请空间1.生成代码,构建栈帧2.包括call指令产生的push操作

2.当返回时解除申请 ▪ 结束代码,清理栈帧 ▪ 包括ret指令产生的pop操作

Hello.s文件中函数过程操作:

main函数:

控制传递:main函数被系统启动函数__libc_start_main调用,call指令将下一条指令地址入栈,然后跳转到main函数执行

控制数据:调用过程向main函数传递参数argc和argv,分别使用%rdi和%rsi存储

movl %edi,
-20(%rbp)

movq %rsi,
-32(%rbp)

函数出口

movl $0, %eax

leave

.cfi_def_cfa 7, 8

ret //函数正常出口为return 0,将%eax设置0返回

管理局部数据

分配栈空间,释放栈空间

pushq %rbp // %rbp记录栈底指针

.cfi_def_cfa_offset 16

.cfi_offset 6, -16

movq %rsp,
%rbp

.cfi_def_cfa_register 6

subq $32,
%rsp //函数分配栈帧空间在%rbp之上

movl %edi,
-20(%rbp)

movq %rsi,
-32(%rbp)

函数调用结束

leave //恢复栈空间为调用之前的状态,然后ret返回

ret

相当于pop,mov指令

printf函数:

leaq .LC0(%rip),
%rdi

call puts@PLT

第一次printf将%rdi设置为“Usage: Hello 1171430615 王家琪\n”字符串的首地址

只有一个字符串参数,所以call puts@PLT

leaq .LC1(%rip),
%rdi

movl $0, %eax

call printf@PLT

第二次printf设置%rdi为“Hello %s %s\n”的首地址,设置%rsi为argv[1],%rdx为argv[2];第二次printf使用call printf@PLT

exit函数:

movl $1, %edi //传递数据:将%edi设置为1

call exit@PLT //控制传递

sleep函数:

movl sleepsecs(%rip),
%eax //传递数据:将%eax设置为sleepsecs

movl %eax,
%edi //传递数据:将%eax中值传给%edi;

call sleep@PLT //控制传递

getchar函数:

call getchar@PLT //控制传递

此部分是重点,说明编译器是怎么处理C语言的各个数据类型以及各类操作的。应分3.3.1~ 3.3.x等按照类型和操作进行分析,只要hello.s中出现的属于大作业PPT中P4给出的参考C数据与操作,都应解析。

3.4 本章小结

本章小结,编译器(ccl)将文本hello.i翻译成文本文件hello.s,它包含一个汇编语言程序,我们通过hello.s文件中汇编指令,我们深入了解了汇编器是如何高效的将C语言中各个数据类型以及各类操作转换为汇编语言指令,汇编器将经过编译预处理的.i文件编译成.s文件,C语言程序经过编译处理,成功的降解难度级数,成为低级机器语言(汇编语言)

Hello.c文件经过洗菜处理(编译预处理),终于在编译阶段进行了“切菜处理”

(第3章2分)

第4章 汇编

4.1 汇编的概念与作用

概念:

汇编器(as)将hello.s翻译成机器语言指令,把这些指令打包成一种叫做可重定向目标程序(relocatable object program)的格式,并将结果保存在目标文件hello.o中。Hello.o文件是一个二进制文件,它包含的17个字节是main函数指令编码,在文本编辑器中打开hello.o文件为一堆乱码;

作用:

汇编器也是编译器,只是它和我们熟知的编译器的有略微的差别。汇编器处理的“高级语言”是汇编语言,输出的是机器语言二进制形式。因此,对于汇编器的构造,实质上和编译器大同小异,也都需要进行词法分析、语法分析、语义处理、符号表管理和代码生成(机器代码)等阶段。

汇编器结构

注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。

4.2 在Ubuntu下汇编的命令

或者

应截图,展示汇编过程!

4.3 可重定位目标elf格式

获得hello.o文件的ELF格式

ELF头:在文件的开始,保存了路线图,描述了该文件的组织情况。以一个16字节序列Magic(ELF文件魔数)开始,Magic描述了生成该文件的系统字节大小和字节序列;ELF头剩下部分包含帮助链接器语法分析和解释目标文件信息;包括ELF头的大小,目标文件的类型(如可重定位,可执行,共享),机器类型(如x86-64),节头部表的文件偏移,以及节头部表中的条目的大小和数量;

  1. 节头部表(Section header table)(节头表):

记录每个节的节名、偏移和大小

不同节的位置和大小由节头部表描述,其中目标文件每一个节都有一个固定大小的条目;

可重定位目标文件中,每个可装入节的起始地址总是0

.text节:编译后的代码部分

.rodata节:只读数据,如printf 格式串、switch 跳转表等

.data节:已初始化的全局变量

.bss节:未初始化全局变量,仅是占位符,不占据任何实际磁盘空间。区分初始化和非初始化是为了空间效率

.symtab节:存放函数和全局变量(符号表)信息,它不包括局部变量

重定位节

.rel.text节的重定位信息,用于重新修改代码段的指令中的地址信息

当链接器把这个可重定向目标文件与其他文件链接时,需要修改这些位置的地址信息,一般而言,任何调用外部函数或者应用全局变量的指令都需要进行重定位修改;

.rel.data节的重定位信息,用于对被模块使用或定义的全局变量进行重定位的信息,一般而言,任何已经初始化的全局变量,如果它的初始值为一个全局变量地址或者外部定义函数的地址,都需要被修改;

Hello.o文件中的8条重定位信息分别对应的是:.L0(第一个printf中的字符串),puts函数,.L1(第二个printf中的字符串),
printf函数, sleepsecs(全局变量),sleep函数、getchar函数

偏移量:需要重定位代码在.text .data.节中的偏移位置,8字节

信息:symbol和type两部分,其中symbol占前4个字节,type占后4个字节,symbol代表重定位到的目标在.symtab中的偏移量,type代表重定位的类型

类型:重定位到目标类型

R X86_ 64 PC32。 重定位一个使用32位PC相对地址的引用。一个PC相对地址就是距程序计数器(PC)的当前运行时值的偏移量。当CPU执行一条使用PC相对寻址的指令时,它就将在指令中编码的32位值加上PC的当前运行时值,得到有效地址(如call指令的目标),PC值通常是下一条指令在内存中的地址。

R X86_ 64 _32。重定位一个使用32位绝对地址的引用。通过绝对寻址,CPU直接使用在指令中编码的32位值作为有效地址,不需要进一步修改

符号名称+加数:重定向到的目标的名称+计算重定位位置的辅助信息,共占8个字节(Addend)

重定位过程将由链接器进行,接下来进行简要的介绍:

将多个代码段与数据段分别合并为一个单独的代码段和数据段

计算每个定义的符号在虚拟地址空间中的绝对地址

将可执行文件中符号引用处的地址修改为重定位后的地址信息

链接器根据info信息向.symtab节中查询链接目标的符号,例如由info.symbol=0x05,可以发现重定位目标链接到.rodata的.L1,重定位条目为r

然后类型为R X86_ 64 PC32。,重定位一个使用32位PC相对地址的引用,一个PC相对地址就是距程序计数器(PC)的当前运行时值的偏移量

r.offset=0x18,
r.symbol=.rodata, r.type=R_X86_64_PC32, r.addend=-4

需要重定位的.text节中的位置为S1,设重定位的目的位置S2

指向S1的指针= s +r.offset

S1的运行时地址= ADDR(s) + r.offset

运行时值*refptr =(unsigned) (ADDR(r.symbol) + r.addend-refaddr)

ADDR(r.symbol)计算dst的运行时地址,在本例中,ADDR(r.symbol)获得的是dst的运行时地址,因为需要设置的是绝对地址,即dst与下一条指令之间的地址之差,所以需要加上r.addend=-4

.debug节:调试用符号表(gcc -g)

.strtab节:包含symtab和debug节中符号及节名

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

4.4 Hello.o的结果解析

objdump获得hello.o文件反汇编代码;

对比hello.s文件中汇编代码,

Hello.s文件中主函数

1.条件分支变化:反汇编产生的跳转指令,不再是跳转到段名称.L2 .L3…而是确定的相对偏移地址(链接器重定位), 段名称只是在汇编语言中便于编写的助记符,所以在汇编成机器语言之后显然不存在

2.函数调用

在.s文件中,函数调用之后直接跟着函数名称,而在反汇编程序中,call的目标地址是当前下一条指令。这是因为hello.c中调用的函数都是共享库中的函数,最终需要通过动态链接器才能确定函数的运行时执行地址,在汇编成为机器语言的时候,对于这些不确定地址的函数调用,将其call指令后的相对地址设置为全0(目标地址正是下一条指令),然后在.rela.text节中为其添加重定位条目,等待静态链接的进一步确定。

3.数据访问:

全局变量访问,在反汇编中对.rodata中printf的格式串的访问需要通过链接时重定位的绝对引用确定地址,在汇编代码相应位置仍为占位符表示,对.data中已初始化的全局变量sleepsecs为0x0+%rip的方式访问hello.s中访问方式为sleepsecs+%rip

在hello.s文件中,访问rodata(printf中的字符串),使用段名称+%rip,在反汇编代码中0+%rip,因为rodata中数据地址也是在运行时确定,故访问也需要重定位。所以在汇编成为机器语言时,将操作数设置为全0并添加重定位条目

objdump -d
-r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。

说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。

4.5 本章小结

本章通过查看hello.o文件ELF格式,对重定位项目分析和使用Objdump反汇编代码与hello.s文件汇编语言进行对比,深入了解了hello.s到hello.o的汇编过程

Hello.c文件记过编译预处理,编译,汇编,经过多次优化装换为低级语言,终于要进行链接过程;生成可执行文件操作了;

(第4章1分)

第5章 链接

5.1 链接的概念与作用

概念:链接是将各种代码和数据片段收集并组合称为一个单一文件的过程,这个文件可被加载(复制)到内存并执行,这个文件可被加载到内存并执行。链接可以执行于编译时,也就是在源代码被编译成机器代码时;也可以执行于加载时,也就是在程序被加载器加载到内存并执行时;甚至于运行时,也就是由应用程序来执行,链接是由链接器的程序自动执行

作用:链接器使分离编译成为可能;一个大型应用程序不再需要组织巨大的源文件,而是可以分解为更小,更好管理的模块,可以独立修改和编译这些模块,当我们改变这些模块中的一个时,只需简单编译,重新链接使用。

注意:这儿的链接是指从 hello.o 到hello生成过程。

5.2 在Ubuntu下链接的命令

链接命令:

ld
-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

/usr/lib/gcc/x86_64-linux-gnu/7/crtbegin.o

/usr/lib/gcc/x86_64-linux-gnu/7/crtend.o

/usr/lib/x86_64-linux-gnu/crtn.o

hello.o -lc -z
relro -o hello

使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件

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

通过readelf获得hello的ELF格式文件

ELF 文件包括三个索引表:ELF header,Program header
table,Section header table;

1)ELF
header:在文件的开始,保存了路线图,描述了该文件的组织情况。

2)Program
header table:告诉系统如何创建进程映像。用来构造进程映像的目标文件必须具有程序头部表,可重定位文件不需要这个表。

3)Section header able:包含了描述文件节区的信息,每个节区在表中都有一项,每一项给出诸如节区名称、节区大小这类信息。用于链接的目标文件必须包含节区头部表,其他目标文件可以有,也可以没有这个表

4)hello的ELF格式文件无两个.rel节(无需重定位)

1.ELF头部表

ELF头中字段e_entry给出执 行程序时第一条指令的地址, 而在可重定位文件中,此字段为0(与可重定位ELF文件不同的地方)

2.节头部表:描述目标文件的节

Section Headers对hello中所有的节信息进行了声明,包括大小Size以及在程序中的偏移量Offset,大小、全体大小、旗标、链接、信息、对齐等信息,根据Section Headers中的信息可以定位各个节所占的区间。其中地址一般是程序被载入到虚拟地址的起始地址

3.程序头表(段头表)(segment header table)

是一个结构数组;将连续的文件节映 射到运行时内存段

  1. .init节:

用于定义_init函数,该函数用来进行可执行目标文件开始执行时的初始化工作

(可重定位ELF文件没有.init节)

分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。

5.4 hello的虚拟地址空间

用edb打开hello可执行程序

在Data Dump窗口观察hello程序被加载到0x00400000——0x00400ff0段中

程序头表在执行的时候被使用,它告诉链接器运行时加载的内容并提供动态链接的信息。每一个表项提供了各段在虚拟地址空间和物理地址空间的大小、位置、标志、访问权限和对齐方面的信息。在下面可以看出,程序包含8个段:

PHDR:保存程序头表。起始地址0x400000偏移0x40字节处、大小为0x1c0字节

INTERP:指定在程序已经从可执行文件映射到内存之后,必须调用的解释器(如动态链接器)。位于内存起始位置0x400000偏移0x200字节处,大小为0x1c个字节,记录了程序所用ELF解析器(动态链接器)的位置位于: /lib64/ld-linux-x86-64.so.2

LOAD:表示一个需要从二进制文件映射到虚拟地址空间的段。其中保存了常量数据(如字符串)、程序的目标代码等。

第一段:(包括ELF头、程序头表、.init、 .text和.rodata节),映射到虚拟地址0x00400000开始长度为0x82c字节的区域 ,按0x20000=800KB对齐,具有只读/执行权限(Flg=RE),是只读代码段

第二段:.data节,映射到虚拟地址 0x00600e00开始长度为0x254字节的存储区域,在596B存储区中,前588B用.data节内容初始化,后面8B对应.bss节,初始化为0 ,按0x200000=800KB对齐,具有可读可写权限(Flg=RW),是可读写数据段

DYNAMIC:保存了动态链接器使用的信息。

NOTE:保存辅助信息。此处NOTE表示该段位于内存起始位置0x400000偏移0x21c字节处,大小为0x20个字节,该段是以‘\0’结尾的字符串,包含一些附加信息

GNU_STACK:权限标志,标志栈是否是可执行的。

GNU_RELRO:表示这段在重定位结束之后那些内存区域是需要设置只读

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

5.5 链接的重定位过程分析

使用Objdump指令获得hello反汇编文件

与hello.o反汇编文件相比,hello反汇编相比,hello反汇编地址为虚拟内存地址,并且多了许多节和子程序:

Section to Segment mapping:

段节…

00

01 .interp //保存ld.so的路径

02
.interp .note.ABI-tag .hash .gnu.hash .dynsym .dynstr .gnu.version
.gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame

03 .init_array .fini_array .dynamic .got

.got.plt //动态链接中过程链接表PLT和全局偏移量表GOT的间接调转

.data .bss

04 .dynamic //存放被ld.so使用的动态链接信息

05
.note.ABI-tag //Linux下特有的section

06

07 .init_array .fini_array .dynamic .got

Disassembly
of section .init //程序初始化需要执行的代码

0000000000400488 <_init>

Disassembly
of section .plt: //动态链接-过程链接表

00000000004004a0
<.plt>

00000000004004b0
puts@plt

00000000004004c0 printf@plt

00000000004004d0 getchar@plt

00000000004004e0 exit@plt

00000000004004f0 sleep@plt

调用的子函数

Disassembly
of section .text: //hello主函数代码段

0000000000400500 <_start>

0000000000400530 <_dl_relocate_static_pie>

0000000000400530 <_dl_relocate_static_pie>

0000000000400540 <deregister_tm_clones>

  …….

00000000004005e7

0000000000400670 <__libc_csu_init>

00000000004006e0 <__libc_csu_fini>

Disassembly
of section .fini: //当程序正常终止时需要执行的代码

00000000004006e4 <_fini>

在使用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

函数调用过程中;链接器解析重定条目时发现对外部函数调用的类型为R_X86_64_PLT32的重定位,此时动态链接库中的函数已经加入到了PLT中,.text与.plt节相对距离已经确定,链接器计算相对距离,将对动态链接库中函数的调用值改为PLT中相应函数与下条指令的相对地址,指向对应函数。对于此类重定位链接器为其构造.plt与.got.plt;

.rodata引用:链接器解析重定条目时发现两个类型为R_X86_64_PC32的对.rodata的重定位(printf中的两个字符串),.rodata与.text节之间的相对距离确定,因此链接器直接修改call之后的值为目标地址与下一条指令的地址之差,指向相应的字符串

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

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

5.6 hello的执行流程

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

5.7 Hello的动态链接分析

在调用共享库函数时,编译器没有办法预测这个函数的运行时地址,因为定义它的共享模块在运行时可以加载到任意位置。正常的方法是为该引用生成一条重定位记录,然后动态链接器在程序加载的时候再解析它;为避免运行时修改调用模块的代码段,链接器采用延迟绑定的策略。动态链接器使用过程链接表PLT+全局偏移量表GOT实现函数的动态链接,GOT中存放函数目标地址,PLT使用got中地址跳转到目标函数

延迟绑定是通过GOT和PLT实现的。GOT是数据段的一部分,而PLT是代码段的一部分。两表内容分别为:

PLT:PLT是一个数组,其中每个条目是16字节代码。PLT [0]是一个特殊条目,它跳转到动态链接器中。每个被可执行程序调用的库函数都有它自己的PLT条目。每个条目都负责调用一个具体的函数。

GOT:GOT是一个数组,其中每个条目是8字节地址。和PLT联合使用时,GOT [0]和GOT [1]包含动态链接器在解析函数地址时会使用的信息。GOT [2]是动态链接器在1d-linux.so模块中的入口点。其余的每个条目对应于一个被调用的函数,其地址需要在运行时被解析。每个条目都有一个相匹配的PLT条目

Hello 的ELF格式文件中查找到,.got.plt起始位置为0x00601000

在调用dl_start之前0x601008后的16个字节均为0

调用_start之后发生改变,0x601008后的两个8个字节分别变为:0x7fbe330ea170、0x7fbe328680,其中GOT [0](对应0x600e28)和GOT [1](对应0x7fbe330ea170)包含动态链接器在解析函数地址时会使用的信息。GOT [2](对应0x7fbe328680)是动态链接器在1d-linux.so模块中的入口点。其余的每个条目对应于一个被调用的函数;

0x7fbe330ea170指向重定位表:

0x7fbe328680指向重定位表:

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

分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。

5.8 本章小结

本章通过解释链接的概念及作用,和分析hello的ELF格式,以及hello的虚拟导致空间,重定位过程,执行流程,和动态连接过程,深入学习了hello.o 可重定位文件到hello可执行文件的流程,和链接的各个过程

懵懵懂懂的我笨笨磕磕的将hello world一字一键敲进电脑存成hello.c(Program),经过预处理、编译、汇编、链接,历经艰辛,Hello一个完美的生命诞生了。

然后在壳(Bash)里,伟大的OS(进程管理)为hello进程fork(Process),为hello进程execve, mmap,分时间片,让hello得以在Hardware(CPU/RAM/IO)上驰骋(取指译码执行/流水线等)

(第5章1分)

第6章 hello进程管理

6.1 进程的概念与作用

概念:进程是一个执行中的程序的实例,系统中每一个程序都运行在某个进程的上下文中,每一个进程都有它自己的地址空间,一般情况下,包括文本区域、数据区域、和堆栈。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储区着活动过程调用的指令和本地变量。

作用:

  1. 一个独立的逻辑控制流,它提供一种假象,好像我们运行的每个程序独占的使用处理器;
    
  2. 一个私有的地址空间,它提供一种假象,含香我们的程序独立的使用内存系统;
    

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

Shell的作用:

1.shell是计算机用来解释你输入的命令然后决定进行何种处理的程序。Shell 是指一种应用程序,Shell应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务

2.可交互,和非交互的使用shell。在交互式模式,shell从键盘接收输入;在非交互式模式,shell从文件中获取输入。

3.shell提供了少量的内置命令,以便自身功能更加完备和高效。

4.shell除了执行命令,还提供了变量,流程控制,引用和函数等,类似高级语言一样,能编写功能丰富的程序。

5.shell强大的的交互性除了可编程,还体现在作业控制,命令行编辑,历史命令,和别名等方面。

Shell处理流程:

由输入设备读取命令(键盘或者文件)

将输入字符串转为计算机可以了解的机械码(分割命令字符串),然后执行它。

  1. Shell程序先识别输入的指令的序列,解析这个这个命令,看是否是内置命令,是的话直接执行该命令,子进程中加载并执行这个文件。否则调用相应的程序为其分配子进程并运行

6.3 Hello的fork进程创建过程

fork进程:父进程通过调用fork函数创建一个新的运行的子进程。新创建的子进程几乎但不完全与父进程相同。子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份副本,包括代码和数据段、堆、共享库以及用户栈。子进程还获得与父进程任何打开文件描述符相同的副本,这就意味着当父进程调用fork时。子进程可以读写父进程中打开的任何文件。父进程和新创建的子进程最大的区别在于他们有不同的PID

1.调用一次,返回两次;在父进程中fork会返回子进程的PID(总为非零值),在子进程中fork会返回0 ;2.并行执行:父进程与子进程是并发运行的独立进程。内核能够以任何方式交替执行他们逻辑控制流中的指令;3.相同但是独立地址空间;4.共享文件

Shell程序fork简单进程图

6.4 Hello的execve过程

fork函数子进程之后,子进程调用execve函数,在当前进程上下文加载的上下文中加载并运行一个新的hello程序,

execve调用驻留在内存中的被称为启动加载器的操作系统代码来执行hello程序,加载器删除子进程现有的虚拟内存段,并创建一组新的代码、数据、堆和栈段。新的栈和堆段被初始化为零,通过将虚拟地址空间中的页映射到可执行文件的页大小的片,新的代码和数据段被初始化为可执行文件中的内容。

加载器虚拟内存映像为:

最后加载器设置PC指向_start地址,_start最终调用hello中的main函数。除了一些头部信息,在加载过程中没有任何从磁盘到内存的数据复制。直到CPU引用一个被映射的虚拟页时才会进行复制,这时,操作系统利用它的页面调度机制自动将页面从磁盘传送到内存

Hello栈结构:

6.5 Hello的进程执行

6.5.1逻辑控制流:一系列程序计数器PC的值的序列叫做逻辑控制流,这些值唯一地对应于包含在程序的可执行目标文件中的指令,或是包含在运行时动态链接到程序的共享对象中的指令,进程是轮流使用处理器的,在同一个处理器核心中,每个进程执行它的流的一部分后被抢占(暂时挂起),然后轮到其他进程

6.5.2并发流:一个逻辑流的执行在时间上与另一个流重叠,称为并发流,这两个流被称为并发地执行。多个流并发地执行的一般现象被称为并发。一个进程和其他进程轮流运行的概念被称为多任务。一个进程执行它的控制流的一部分的每一时间段叫做时间片。因此,多任务也叫时间分片

6.5.3私有地址空间:提供一种假象,含香我们的程序独立的使用内存系统;一个程序的空间中某个地址相关联的内存字节是不能被其他进程读或者是写的;

6.5.4用户模式和内核模式:处理器通过某个控制寄存器中的一个模式位来提供限制一个应用可以执行的指令以及它可以访问的地址空间范围的功能。该寄存器描述了当前进程享有的特权。当设置了模式位时,进程就运行在内核模式中。没有设置模式位时,进程就运行在用户模式中。一个运行在内核模式的进程可以执行指令集中的任何指令,并且可以访问系统中的任何内存;用户模式的进程不允许和执行特权指令、也不允许用户模式中的进程直接引用地址空间中内核区内的代码和数据

6.5.5上下文切换:较高形式的异常控制流来实现都任务;内核为每个进程维持一个上下文,上下文就是内核重新启动的一个被强占的进程所需的状态。由包括通用目的寄存器、浮点寄存器、程序计数器、用户站、状态寄存器、内核栈和各种内核数据结构

在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程,这个决策就叫做调度,是由内核中称为调度器的代码处理的。在内和调度了一个新的进程运行后,它就抢占当前进程,并使用上文所述的上下文切换的机制将控制转移到新的进程,上下文切换机制:1.保存当前上下文;2.回复某个先前被抢占的进程的上下文;3.将控制传递给新进程

Hello上下文进程切换:

结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。

6.6 hello的异常与信号处理

hello执行过程中出现的异常种类可能会有:中断、陷阱、故障、终止。

中断是异步发生的,是来自处理器外部的I/O设备的信号的结果。硬件中断的异常处理程序被称为中断处理程序。

陷阱是有意的异常,是执行一条指令的结果。就像中断处理程序一样,陷阱处理程序将控制返回到下一条指令。陷阱最重要的用途是在用户程序和内核之间提供一个像过程一样的接口,叫做系统调用。

故障由错误情况引起,它可能能够被故障处理程序修正。当故障发生时,处理器将控制转移给故障处理程序。如果处理程序能够修正这个错误情况,它就将控制返回到引起故障的指令,从而重新执行它。否则处理程序返回到内核中的abort例程,abort例程会终止引起故障的应用程序。

终止是不可恢复的致命错误造成的结果,通常是一些硬件错误,比如DRAM或者SRAM位被损坏时发生的奇偶错误。终止处理程序从不将控制返回给应用程序。处理程序将控制返回给一个abort例程,该例程会终止这个应用程序

信号允许进程和内核中断其他进程。每种信号都对应于某种系统事件。低层的硬件异常是由内核异常处理程序处理的,正常情况下,对用户进程是不可见的。信号提供一种机制,通知用户进程发生了这些异常。例如在hello运行过程中键入回车,Ctrl+Z,Ctrl+C,乱码等

运行过程中键入Ctrl+Z,内核发生SIGTSTP信号给hello,同时也发生给hello父进程,键入ps查询当前进程数量和内容,被暂停的hello进程PID为1663,然后键入jobs,显示当前被暂停进程

键入Ctrl+Z后,键入fg,恢复前台作业hello;

键入kill和hello进程的PID:杀死hello进程

键入Ctrl+C:终止hello进程

键入回车键

键入乱码

hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。

程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs
pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。

6.7本章小结

本章从进程角度了解hello子进程fork和execve进程,并了解了Hello的进程执行过程中上下文切换以及逻辑控制流,以及Shell-bash处理流程:hello执行过程中可能引发的异常和信号处理

(第6章1分)

第7章 hello的存储管理

7.1 hello的存储器地址空间

物理地址:CPU通过地址总线的寻址,找到真实的物理内存对应地址。CPU对内存的访问是通过连接着CPU和北桥芯片的前端总线来完成的。在前端总线上传输的内存地址都是物理内存地址,M =
2m 个物理地址的集合 {0, 1, 2, 3, …,
M-1}

逻辑地址:程序代码经过编译后出现在汇编程序中地址。逻辑地址由选择符(在实模式下是描述符,在保护模式下是用来选择描述符的选择符)和偏移量(偏移部分)组成

线性地址:逻辑地址经过段机制后转化为线性地址,为描述符:偏移量的组合形式。分页机制中线性地址作为输入,地址空间中的整数是连续的

虚拟地址:虚拟内存被组织为一个由存放在磁盘上的N 个连续的字节大小的单元组成的数组,N = 2n 个虚拟地址的集合 {0, 1, 2, 3, …, N-1}

Intel采用段页式存储管理(通过MMU)实现:

·段式管理:逻辑地址—>线性地址==虚拟地址;

·页式管理:虚拟地址—>物理地址。

以hello中的puts调用为例:mov $0x400714,%edi callq
4004a0,$0x400714为puts输出字符串逻辑地址中的偏移地址,需要经过段地址到线性地址的转换变为虚拟地址,然后通过MMU转换为物理地址,才能找到真正对应物理内存

三种地址之间的关系

结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。

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

段式寄存器:用于存放段选择符:CS(代码段)是指程序代码所在段;SS(栈段)是指栈区所在段;DS(数据段)是指全局静态数据所在段;其他三个段寄存器ES、GS和FS可指向任意数据段;最初8086处理器的寄存器是16位的,为了能够访问更多的地址空间但不改变寄存器和指令的位宽,所以引入段寄存器,8086共设计了20位宽的地址总线,通过将段寄存器左移4位加上偏移地址得到20位地址,这个地址就是逻辑地址。将内存分为不同的段,段有段寄存器对应,段寄存器有一个栈、一个代码、两个数据寄存器

分段功能分两种情况:

  1. 实模式:逻辑地址=线性地址=实际的物理地址。段寄存器存放真实段基址,同时给出32位地址偏移量,则可以访问真实物理内存

  2. 保护模式:线性地址还需要经过分页机制才能够得到物理地址,线性地址也需要逻辑地址通过段机制来得到。段寄存器无法放下32位段基址,所以它们被称作选择符,用于引用段描述符表中的表项来获得描述符。描述符表中的一个条目描述一个段

段选择符构造:其中TI表示描述符表,TI=0则为全局描述符表;TI=1则为局部描述符表。RPL表示环保护,为第0级,位于最高级的内核态,RPL=11,为第3级,位于最低级的用户态,中间环留给中间软件使用。高13位表示用来确定当前使用的段描述符在描述符表中的位置

逻辑地址向线性地址转换的过程中被选中的描述符先被送至描述符cache,每次从描述符cache中取32位段基址,与32位段内偏移量(有效地址)相加得到线性地址,过程如下图:

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

7.3.1线性地址(虚拟地址VA):虚拟内存是一种对主存的抽象概念,是硬件异常、硬件地址翻译、主存、磁盘文件和内存文件的完美交互。为每个进程提供了一个大的、一致的和私有的地址空间

虚拟内存被组织为一个由存放在磁盘上的N 个连续的字节大小的单元组成的数组

7.3.2页表:非常重要的数据结构;是一个页表条目 (Page Table Entry,
PTE)的数组,将虚拟 页地址映射到物理页地址

虚拟页和物理页之间的关系:如下图

页命中:虚拟内存中的一个字存在于物理内存中

缺页::引用虚拟内存中的字,不在物理内存 中 (DRAM 缓存不命中)

7.3.3地址翻译:

基本参数:

基于页表的翻译流程图:

MMU地址翻译流程:

地址翻译是一个N元素的虚拟地址空间(VAS)中的元素和一个M元素的物理地址空间(PAS)中元素的映射。CPU中存在一个控制寄存器为页表基址寄存器指向当前页表。n位的虚拟地址包含着p位的虚拟页面偏移和(n-p)位的虚拟页号。MMU利用VPN来选择适当的PTE,将页表条目中的物理页号和虚拟地址中的VPO串联起来就得到相对应的物理地址

1.处理器生成一个虚拟地址,并将其传送给MMU;2.MMU生成PTE地址(PTEA),并从高速缓存/主存请求得到PTE;3.高速缓存/主存向MMU返回PTE;4. MMU 将物理地址传送给高速缓存/主存;5. 高速缓存/主存返回所请求的数据字给处理器

缺页异常:

(1)处理器生成一个虚拟地址,并将其传送给MMU (2) MMU生成PTE地址(PTEA),并从高速缓存/主存请求得到PTE(3) 高速缓存/主存向MMU返回PTE(4) PTE的有效位为零, 因此 MMU
触发缺页异常(5)缺页处理程序确定物理内存中的牺牲页 (若页面被修改,则换出到磁盘)(6)缺页处理程序调入新的页面,并更新内存中的PTE (7) 缺页处理程序返回到原来进程,再次执行导致缺页的指令

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

7.4.1 TLB(翻译后备缓冲器):

MMU中一个小的具有高相联度的集合 ▪ 实现虚拟页码向物理页码的映射 ;对于页码数很少的页表可以完全包含在TLB中

MMU 使用虚拟地址的 VPN 部分来访问TLB

TLB命中:命中减少内存访问

TLB不命中:不命中引发了额外的内存访问

7.4.2四级页表:

地址翻译流程:

1—3级页表条目格式:

每个条目引用一个4KB子页表:

P: 子页表在物理内存中(1)不在(0)

R/W: 对于所有可访问页,只读或者读写访问权限

U/S: 对于所有可访问页,用户或超级用户(内核)模式访问权限

WT: 子页表的直写或写回缓存策略

A: 引用位(由MMU 在读或写时设置,由软件清除)

PS: 页大小为4 KB 或4 MB (只对第一层PTE定义)

Page table physical base address: 子页表的物理基地址的最高40位(强制页表 4KB 对齐)

XD:能/不能从这个PTE可访问的所有页中取指令

第4级页表条目格式:

D: 修改位(由MMU 在读和写时设置,由软件清除)

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

7.5.1高速缓存(三级Cache):高速缓存存储器是小型的、快速的基于SRAM的存储器是在 硬件中自动管理的

缓存不命中的种类:

  1. 冷不命中(或强制性不命中) ▪ 当缓存为空时,对任何数据的请求都会不命中
    
  2. 冲突不命中 ▪ 大部分缓存将第k+1层的某个块限制在第k层块的一个 子集里(有时只是一个块) ▪ 例如,第k+1层的块i必须放置在第k层的块 (i mod 4) 中 ▪ 当缓存足够大,但是被引用的对象都映射到同一缓存块中,
    

此种不命中称为冲突不命中

  1. 容量不命中 ▪ 当工作集(working set)的大小超过缓存的大小时,会发 生容量不命中
    

7.5.2高速缓存层次结构(三级Cache)

7.5.3通用的高速缓存存储器组织结构(S, E, B)

7.5.4高速缓存读或写

读策略:

1.定位组;2.检查组中的任何行是否有匹配的标记;3.是 + 行有效 :命中;4.定位从偏移开始的数据

写策略:

写命中:1.直写 (Write-through,立即写入存储器);2.写回 (Write-back,推迟到缓存行要替换时才写入内存),需要一个修改位 (标识缓存行与内存是否相同/有修改)

写不命中:1.写分配
(Write-allocate加载到缓存,更新这个缓存行),如后续有较多向该位置的写,优势明显;2.非写分配(No-write-allocate直接写到主存中,不加载到缓存中)

7.6 hello进程fork时的内存映射

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

共享对象及写时复制:两个进程都映射了私有的写时复制对象;区域结构被标记为私有的写时复制;私有区域的页表条目都被标记为只读;

写私有页的指令触发保护故障;故障处理程序创建 这个页面的一个新 副本;故障处理程序返回时重新执行写指令;尽可能地延迟拷贝(创建副本)

7.7 hello进程execve时的内存映射

execve函数在当前进程中加载并运行新程序a.out的 步骤:1.删除已存在的用户区域 2. 创建新的区域结构 ▪ 代码和初始化数据 映射到.text和.data区 (目标文件提供) ▪ .bss和栈映射到匿名文件3. 设置PC,指向代码区域的入口点 ▪ Linux根据需要换入代码和数据页面

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

Page fault缺页 :引用虚拟内存中的字,不在物理内存中,DRAM 缓存不命中

缺页中断处理:

  1. 页面不命中导致缺页(缺页异常)
    
  2. 缺页异常处理程序选择一个牺牲页
    
  3. 缺页处理程序调入新的页面,并更新内存中的PTE
    
  4. 缺页处理程序返回到原来进程,再次执行导致缺页的指令
    

7.9动态存储分配管理

printf函数调用malloc动态分配内存

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

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

显式分配器:要求应用显式地释放任何已分配的块。

一种是先进后出,另一种是地址顺序。

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

标记边界隐式空闲块:

放置空闲块的策略有三种,分别是首次适配、下一次适配、最佳适配。

首次适配从头开始搜索空闲链表,选择第一个合适的空闲块。下一次适配和首次适配很相似,只不过不是从链表的起始处开始每次搜索,而是从上一.次查询结束的地方开始。最佳适配检查每个空闲块,选择适合所需请求大小的最小空闲块

分割空闲块:申请空间比空闲块小——可以把空闲块分割成两部分

合并空闲块:合并相邻的空闲块,边界标记检查前后是否有空闲块

Printf会调用malloc,请简述动态内存管理的基本方法与策略。

7.10本章小结

本章通过介绍hello的储存器地址空间,段式管理,和页表管理,和在TLB与四级页表支持下的VA到PA的变换;三级Cache支持下的物理内存访问;hello进程fork时的内存映射;hello进程execve时的内存映射;缺页故障与缺页中断处理;动态存储分配管理;printf函数调用malloc动态分配内存,深入了解了计算机系统存储的各种各样有序的管理。

(第7章 2分)

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

  1. 设备的模型化:所有的IO设备都被模型化为文件,个 Linux 文件 就是一个 m 字节的序列:B0 , B1
    

, … , Bk , … , Bm-1。所有的I/O设备都被模型化为文件:/dev/sda2(用户磁盘分区)/dev/tty2(终端);甚至内核也被映射为文件:/boot/vmlinuz-3.13.0-55-generic(内核映像);/proc(内核数据结构);而所有的输入和输出都被当做对相应文件的读和写来执行,这种将设备优雅地映射为文件的方式,允许Linux内核引出一个简单低级的应用接口,称为Unix I/O

  1. 虚拟设备技术:利用增加抽象层再一次完美解决独占设备影响进程速度的问题
    
  2. 设备分配使用方式 :独占设备不存在死锁问题;安全分配方式
    

设备的模型化:文件

设备管理:unix io接口

8.2 简述Unix
IO接口及其函数

Shell创建的每个进程都有三个打开的文件:标准输入,标准输出,标准错误。

UnixI/O接口及其函数:

打开和关闭文件open()and
close():

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

open函数将file那么转换为一个文件描述符并且返回描述符数字。返回的描述符总是在进程中当前没有打开的最小描述符

int close(int fd)

关闭文件,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中去

读写文件read() and
write()

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

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

ssize_t write(int fd, const void

  • buf, size_t n)

write函数从内存位置buf复制至多n个字节到描述符fd的当前文件位置

改变当前的文件位置
(seek)

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

指示文件要读写位置的偏移量lseek()

8.3 printf的实现分析

printf函数分析:
在这里插入图片描述

/sys_call的实现/

/*sys_call的功能显示格式化了的字符串

ecx中是要打印出的元素个数

ebx中的是要打印的buf字符数组中的第一个元素

这个函数的功能就是不断的打印出字符,直到遇到:’\0’*/

sys_call:

 call save 

 push dword [p_proc_ready] 

 sti 

 push ecx 

 push ebx 

 call [sys_call_table + eax * 4] 

 add esp, 4 * 3 

 mov [esi + EAXREG - P_STACKBASE], eax 

 cli

 ret

https://www.cnblogs.com/pianist/p/3315801.html

从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)
8.4 getchar的实现分析
getchar函数源代码及解析,实现分析:
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
在这里插入图片描述
8.5本章小结

本章通过深入了解Linux I/O设备管理方法,Unix I/O接口及其接口函数,学习到了系统级I/O底层实现机制,再通过对printf的实现分析和getchar的实现分析,发现printf ,getchar都是Unix I/O的封装,而真正调用的是write和read这样的系统调用函数,而它们又都是由内核完成的,我们从代码级深入解析了Unix
I/O的异常中断实现

(第8章1分)

结论

只有CS知道我的生、我的死,我的坎坷,“只有 CS 知道……我曾经……来…过……”——未来一首关于Hello的歌曲绕梁千日不绝

Hello终于走完自己的一生,

P2P的: From Program to Process

编译预处理:将hello.c调用的函数库加入到hello.i中,并条件编译,宏展开;

编译:将hello.i编译成为汇编文件hello.s

汇编:将hello.s会变成为可重定位目标文件hello.o

链接:将hello.o与可重定位目标文件和动态链接库链接成为可执行目标程序hello

O2O: From Zero-0
to Zero-0

运行:在shell中输入./hello 1171430615 王家琪

创建子进程:shell进程调用fork为其创建子进程

运行程序:shell调用execve,execve调用启动加载器,加映射虚拟内存,进入程序入口后程序开始载入物理内存,然后进入
main函数。

执行指令:CPU为其分配时间片,在一个时间片中,hello享有CPU资源,顺序执行自己的控制逻辑流

访问物理内存:MMU将程序中使用的虚拟内存地址通过页表映射成物理地址

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

信号:如果运行途中键入ctr-c ctr-z则调用shell的信号处理函数分别停止、挂起

回收子进程:shell父进程回收子进程,内核删除为这个进程创建的所有数据结构;悄悄的我走了,正如我悄悄的来;我挥一挥衣袖,不带走一片云彩

通过学习了hello的一生,感觉自己更加深入了解到了计算机底层的各种各样的运行机制,计算机系统中有着人类智慧的结晶,各个软硬件完美连接,协同工作,各种各样完美的机制,无论是P2P还是O2O,无数程序员尽折腰,计算机之美才刚刚拉开帷幕,计算机之门才刚刚缓缓打开

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

你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。

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

附件

列出所有的中间产物的文件名,并予以说明起作用。
在这里插入图片描述

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

参考文献

为完成本次大作业你翻阅的书籍与网站等

[1] 兰德尔E.布莱恩特 大卫R.奥哈拉伦. 深入理解计算机系统(第3版).机械工业出版社. 2018.4

[2] 博客园:printf函数实现深度剖析https://www.cnblogs.com/pianist/p/3315801.html

[3] hello的一生:https://blog.csdn.net/lin1746/article/details/23292473

[4] ELF文件-段和程序头:https://blog.csdn.net/u011210147/article/details/54092405

[5] 林来兴.
空间控制技术[M].
北京:中国宇航出版社,1992:25-42.

[6] 辛希孟. 信息技术与信息服务国际研讨会论文集:A集[C].
北京:中国科学出版社,1999.

[7] 赵耀东.
新时代的工业工程师[M/OL].
台北:天下文化出版社,1998
[1998-09-26]. http://www.ie.nthu.edu.tw/info/ie.newie.htm(Big5).

[8] 谌颖.
空间交会控制理论与方法研究[D].
哈尔滨:哈尔滨工业大学,1992:8-13.

[9] KANAMORI H.
Shaking Without Quaking[J]. Science,1998,279(5359):2063-2064.

[10] 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.

[11] Linux内核:IO设备的抽象管理方式:

https://blog.csdn.net/roger_ranger/article/details/78473886

[12] ELF可重定位目标文件的格式

https://blog.csdn.net/Xindolia_Ring/article/details/80961363

[13] getchar()函数详解:http://www.aspku.com/kaifa/c/332134.html

[14] 内存地址转换与分段 https://blog.csdn.net/drshenlei/article/details/4261909

[15] 南京大学袁春风MOOC课程及课件

    https://www.icourse163.org/course/NJU-1001625001

    https://www.icourse163.org/course/NJU-1002532004

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

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值