程序人生-Hello’s P2P

程序人生-Hello’s P2P

计算机科学与技术学院

2022年5月

摘  要

本文结合hello程序的生成详细地讲述了预处理、编译、汇编、进程管理、存储管理、IO管理的概念、作用、命令等。展示了Linux操作系统下hello程序的生命周期,简单说明了linux系统进行进程管理,存储管理以及I/O管理等的实现机制和底层原理。

关键词:LINUX;预处理;编译;链接;进程管理;存储管理;IO管理                          

目  录

第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简介

根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。

P2P(Program to Process): 程序员编写出hello的原始C语言代码,保存为文本文件:hello.c,作为一个program。在操作系统中hello.c经过预处理器cpp的预处理生成hello.i文件、通过编译器ccl的编译生成hello.s文件、通过汇编器as的汇编生成hello.o文件、经过连接器ld的链接最终成为可执行目标程序hello。在shell中为其fork出进程并执行。

 

 

020(zero to zero): shellhello创建进程并加载hello的可执行文件,通过execve映射虚拟内存,进入程序入口,程序载入物理内存,main函数执行目标代码,hello通过IO管理来输入输出。最终hello进程结束,shell将进程回收

1.2环境与工具

  • 硬件环境X64 CPU1.8GHz8G RAM512GHD
  • 软件环境Windows11 64位;VirtualBox-6.1.32Ubuntu-20.04.4 64位;
  • 开发和调试工具 gcc,vim,edb,objdump,readelf,HexEdit,ld

1.3 中间结果

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

hello.c

C语言源程序

hello.i

hello.c预处理的文本文件

hello.s

hello.i编译后的汇编文件

hello.o

hello.s汇编后的可重定位文件

hello_oelf.txt

hello.o经过readelf分析得到的文本文件,分析汇编器和链接器行为

hello_oobj.txt

hello.o经过objdump反汇编后得到的文本文件,主要是为了分析hello.o

hello

链接后的可执行文件

hello_elf.txt

helloreadelf分析得到的文本文件,为了重定位过程分析

hello_obj.txt

hello经过objdump反汇编后得到的文本文件

1.4 本章小结

本章主要简要介绍了hello程序P2P,020的过程,列出了实验环境条件,列出了实验中生成的中间文件。

第2章 预处理

2.1 预处理的概念与作用

预处理是C语言的一个重要功能,它由预处理程序负责完成。当对一个源文件进行编译时,系统将自动引用预处理程序对源程序中的预处理部分作处理,处理完毕自动进入对源程序的编译。

大多数预处理器指令属于下面3种类型:

1、宏定义:#define 指令定义一个宏,#undef 指令删除一个宏定义。

2、文件包含:#include指令导致一个指定文件的内容被包含到程序中。

3、条件编译:#if,#ifdef,#ifndef,,#elif,#else 和#endif 指令

作用:

1、将源文件中以”include”格式包含的文件复制到编译的源文件中。

2、用实际值替换用“#define”定义的字符串。

3、根据“#if”后面的条件决定需要编译的代码。

2.2在Ubuntu下预处理的命令

命令:gcc -E hello.c -o hello.i

 

2.3 Hello的预处理结果解析

用文本编辑器打开hello.i,main函数的预处理解析结果如图。

 

 

在main函数前出现的是stdio.h unistd.h stdlib.h头文件。原本只有二十几行的文件已经被扩展到了三千多行,而多出的代码其实就是.c文件中包含的以#开头的头文件,他们的内容被cpp插入到了文件中

.i程序中是没有#define的,并使用了大量的#ifdef #ifndef的语句。

预处理指令会对条件值进行判断来决定是否执行包含其中的逻辑。

2.4 本章小结

本章主要介绍了预处理的概念与作用,执行预处理的命令,并结合hello.i文件,解析了hello的预处理结果

第3章 编译

3.1 编译的概念与作用

编译的概念:编译器(ccl)将文本文件hello.i翻译成文本文件hello.s,它包含一个汇编语言程序.该程序包含函数main的定义,这个过程称为编译

编译的作用:基本功能是把用高级程序设计语言书写的源程序,翻译成等价的计算机汇编语言书写的目标程序。并且具有语法检查、调试措施、修改手段、覆盖处理、目标程序优化、不同语言合用以及人-机联系等重要功能。

       

3.2 在Ubuntu下编译的命令

gcc -S hello.i -o hello.s

 

 

3.3 Hello的编译结果解析

3.3.1编译文件指令

 

.file:源文件名

.globl:全局变量

.data:数据段

.align:对齐方式

.type:指定是对象类型或是函数类型

.size:大小

.long:长整型

.section  .rodata:下面是.rodata节

.string:字符串

.text:代码段

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

3.3.2数据

1.字符串

 

程序中有两个字符串,由上图可知,

2.局部变量i

main函数声明了一个局部变量i,编译器进行编译的时候将局部变量i会放在堆栈中。如图所示,局部变量i放在栈上-4%rbp)的位置。

 

3.传入main的参数

main函数接收符号数argc和字符型数组指针argv作为参数,这两个参数分别通过%edi%esi传递。由汇编码可以看出,argc存放于%rsp-20处,argv[]是元素皆为字符指针的数组,存放在%rsp-32处,具体存放的汇编码如图

 

3.3.3赋值操作

程序中的赋值操作主要有:i=0这条赋值操作在汇编代码主要使用mov指令来实现

 

3.3.4算数操作

原代码中的算术操作主要是对局部变量i进行加法操作:i++,加法操作一般使用add相关命令来实现

 

3.3.5关系操作和控制转移

关系操作一般使用命令cmp来实现,一般后续还会跟随条件判断得到结果之后伴随的控制转移(跳转语句)。原代码中涉及到关系相关的操作包括:

argc=4:作为条件判断的关系操作,比较argc4,相等则会跳转到L2。命令如下:

 

 i<8:作为原程序中循环的判定条件,判定条件后如果i<=7会跳转到L4。命令如下:

 

3.3.6数组/指针操作:

原代码中的数组为传递入main函数的字符串数组argv[],在printf和后面的sleep函数中都涉及到取出argv[]中的数组元素的操作,具体如下:

 

 

上图为printfargv[]元素的过程,先将数组的首地址赋给rax,之后rax的值+16,即取出argv[2],之后这里的值被赋给rdx,同样的操作在后三行argv[1]的值被赋给rax。之后把他们作为参数,调用函数print

3.3.7 函数操作:

汇编码中的函数操作一般使用call命令来使用,原代码中涉及到不少函数操作,部分列举如下

 

 

 

这些函数的返回值都保存在rax寄存器中。

3.4 本章小结

本章节主要叙述了主要介绍了编译概念和作用,Linux系统下的编译命令,编译器是如何处理c语言程序的,结合c语言中的各种数据类型,各类运算,各类函数操作,逐个分析编译器的具体行为

第4章 汇编

4.1 汇编的概念与作用

定义:汇编器(as)将.s汇编程序翻译成机器语言,把这些指令打包成可重定位目标程序的格式,并将结果保存在.o 目标文件(一个包含程序指令编码的二进制文件)中。

作用:将汇编代码转换为能够直接被CPU执行的机器码的形式,使其在链接后能被机器识别并执行。

4.2 在Ubuntu下汇编的命令

gcc hello.s -c -o hello.o

 

4.3 可重定位目标elf格式

使用命令:readelf -a hello.o > hello_elf.txt,将elf格式的内容重定向至文件hello_oelf.txt中查看

4.3.1ELF header

 

      ELF头包括一个16字节的序列、ELF头的大小、目标文件的类型(如可重定位、可执行或共享的)、机器类型(如x86-64)、节头部表(section header table)的文件偏移,以及节头部表中条目的大小和数量。

4.3.2Section Header:

 

 

 

节头部表包含了文件中出现的各个节的语义,每个节都从0开始,用于重定位。在文件头中得到节头表的信息后,可以使用节头表中的字节偏移信息得到各节在文件中的起始位置,以及所占空间的大小。从节头部表中我们得知,hello.o一共有13个节。

4.3.3 Relocation Section

 

重定位节记录了各段引用的符号的相关信息。在链接时,可以通过重定位节对这些位置的地址进行重定位。即在重定位的过程中,链接器结合重定位节中的偏移量和其他信息来计算正确的地址。

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

4.3.4Symbol table

 

符号表用来存放在程序中定义和引用的函数和全局变量的信息。

name是符号名称,对于可重定位目标模块,value是符号相对于目标节的起始位置偏移,对于可执行目标文件,该值是一个绝对运行的地址。size是目标的大小,type说明是数据或函数。Bind字段表明符号是本地的还是全局的。

4.4 Hello.o的结果解析

使用命令:objdump -d -r hello.o > hello_o.txt, 得到hello.o的反汇编,重定向至hello_oobj.txt文件。文件内容截图如下

 

通过与第三章.s文件比较可以发现:

  1. hello_oobj.txt记录了文件格式和.text代码段:而hello.s中除了记录了文件格式和.text代码段还包括.type .size .align以及.rodata
  2. hello_oobj.txt跳转中地址为已确定的实际指令地址;
  3. hello.s跳转中地址为助记符如.L2,通过使用例如.L2等的助记符进行跳转。
  4. hello_oobj.txt中,call的目标地址是指令,如callq 21 <main+0x21>

hello.s文件中,call的地址是函数名称,如puts@PLT

4.5 本章小结

本章介绍了汇编的概念与作用,在linux下进行汇编的指令,可重定位目标文件elf的格式,将hello.o的结果解析与hello.s进行对照分析,分析了机器语言的构成以及与汇编语言的映射关系。

第5章 链接

5.1 链接的概念与作用

概念:链接是指将各种代码和数据片段收集并组合为一个单一的可执行文件的过程,这个文件可被加载到内存并执行。

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

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

 

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

命令:readelf -a hello > hello_elf.txt

 

 

 

 

 

 

5.4 hello的虚拟地址空间

   

 

edb查看hello的虚拟地址空间的各段信息  

5.5 链接的重定位过程分析

objdump –d –r hello > hello_obj.txt得到反汇编文件hello_obj.txt             

我们可以得到hello的反汇编代码相较于hello.o的反汇编代码主要有以下区别:

  1. 相比hello.o反汇编代码一开始即为<main>,hello的反汇编代码增加了新的节,如.init, .plt,如下图所示:

 

  1. hello中链接加入了在hello.c中用到的库函数,如exitprintfsleepgetchar等函数
  2. hello中无hello.o中的重定位条目,并且跳转和函数调用的地址在hello中都变成了虚拟内存地址。

链接过程:链接就是链接器(ld)将各个目标文件(各种.o文件)组装在一起,文件中的各个函数段按照一定规则累积在一起。

重定位过程:在重定位过程中,链接器进行符号解析,关联每个符号引用和符号定义,进行重定位时,使用汇编器产生的重定位条目,把符号定义和一个内存位置关联起来,使每条指令和全局变量拥有唯一的运行时地址。最终,就得到了一个可执行目标文件。

5.6 hello的执行流程

子程序名

地址

ld-2.31.so!_dl_start

0x7f8586bea770

ld-2.31.so!_dl_init

0x7f8586bea9a0

hello!_start

0x4010f0

libc-2.31.so!_libc_start_main

0x7f634a96ce60

hello!printf@plt

0x401040

hello!sleep@plt

0x401080

hello!getchar@plt

0x401050

libc-2.31.so!exit

0x7f634a7540d0

5.7 Hello的动态链接分析

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

动态链接采用了延迟加载的策略,即在调用函数时才进行符号的映射。使用偏移量表GOT和过程链接表PLT的协同工作实现函数的动态链接。GOT中存放函数目标地址,PLT使用GOT中的地址跳转到目标函数。

 

dl_init调用前

 

dl_init调用后, GOT条目初始时指向其PLT条目的第二条指令的地址

5.8 本章小结

本章介绍了链接的概念作用,linux下链接的命令,可执行文件hello的格式。分析helloELF格式和虚拟地址空间,通过实例分析了hello的动态链接、执行流程、重定位过程、加载以及运行时函数调用顺序,深入理解链接和重定位的过程。

第6章 hello进程管理

6.1 进程的概念与作用

1.进程的概念:

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

2.进程的作用:

进程提供两个假象,程序独占地使用处理器和程序在独占地使用系统内存。处理器看上去像是不间断地一条接着一套地执行程序当中地指令,即该程序地代码和数据是系统内存当中地唯一的对象。

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

1. 壳Shell-bash的作用:

shell应用程序提供了一个界面让用户能通过访问这个界面访问操作系统内核的服务。

2.处理流程:

1)从终端读入输入的命令。

2)将输入字符串切分获得所有的参数。

3)如果是内置命令则立即执行。

4)否则调用相应的程序执行。

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

6.3 Hello的fork进程创建过程

普通的系统调用,调用一次就返回一次,而fork()调用一次,会返回两次,一次是父进程,另一个是子进程,互不干扰,调用的先后顺序由操作系统的调度算法决定。子进程永远返回0,父进程则返回子进程的ID

6.4 Hello的execve过程

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

6.5 Hello的进程执行

1.上下文信息:

进程的上下文信息是指程序正确运行所需的状态,这个状态包括存放在内存中的程序的代码和数据,它的栈,通用目的寄存器的内容,程序计数器,环境变量以及打开文件描述符的集合。

2.用户模式和内核模式转换:

内核模式和用户模式是一个进程的不同模式,由模式位来控制。当设置了模式位时,进程就运行在内核模式中,这时候这个进程就可以执行指令集中的任何指令,并且可以访问系统中的任何内存位置。没有设置模式位时,进程就运行在用户模式中。用户模式中的进程不允许执行特权指令,反之,用户程序必须通过系统调用接口间接地访问内核代码和数据。运行程序代码的进程一开始是处于用户模式,只有当发生中断、故障或者陷入系统调用这样的异常时,转而去执行异常处理程序,这时进程才会变为内核模式。当它返回到应用程序代码时,处理器就把模式从内核模式改为用户模式。

3.上下文切换:

系统通过处理器调度让处理器轮流执行多个进程,实现不同进程中指令交替执行的机制称为进程的上下文切换。

过程:1)保存当前进程的上下文,2)回复某个先前被抢占的进程被保存的上下文,3)将控制传递到这个新恢复的进程。

4. hello程序进程执行图示如下:

 

6.6 hello的异常与信号处理

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

1)乱按和回车:

 

2ctrl-z

3ctrl-c

4ctrl+z后续jobs

 

5ctrl+z后续pstree(部分):

6ctrl+z后续fg

7ctrl+z后续ps

8ctrl+zfgps

 9kill以及后续ps

6.7本章小结

本章首先介绍了进程的概念与作用,并简述壳Shell-bash的作用与处理流程,讲解了Hello的fork进程创建过程和execve过程,以及Hello的进程是如何执行的,如何处理hello的异常与产生的信号. 从中我们可以看出,进程执行时独占cpu和整个内存空间只是假象,实际上通过操作系统的进程调度机制该进程是在与其他进程并发进行。

第7章 hello的存储管理

7.1 hello的存储器地址空间

逻辑地址:包含在机器语言中用来指定一个操作数或一条指令的地址。每一个逻辑地址都由一个段(segment)和偏移量(offset)组成,偏移量指明了从段开始的地方到实际地址之间的距离。就是hello.o里相对偏移地址。

线性地址:逻辑地址到物理地址变换之间的中间层。程序代码会产生逻辑地址,或者说是段中的偏移地址,加上相应段的基地址就生成了一个线性地址。是hello中的虚拟内存地址。

虚拟地址:一个带虚拟内存的系统中,CPU从一个有N=2^n个地址空间中生成虚拟地址。虚拟地址其实就是线性地址。

物理地址:用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应。地址翻译会将hello的一个虚拟地址转化为物理地址。

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

在段式存储管理中,将程序的地址空间划分为若干个段,在段式存储管理系统中,为每个段分配一个连续的分区,而进程中的各个段可以不连续地存放在内存的不同分区中。程序加载时,操作系统为所有段分配其所需内存,物理内存的管理采用动态分区的管理方法。在为某个段分配物理内存时,可以采用首先适配法、下次适配法、最佳适配法等方法。在回收某个段所占用的空间时,要注意将收回的空间与其相邻的空间合并。段式存储管理也需要硬件支持,实现逻辑地址到物理地址的映射。

每个段的首地址都存放在自己的段描述符中,而所有的段描述符都存放在一个描述符表中(描述符表分为全局描述符表GDT和局部描述符表LDT).而要想找到某个段的描述符必须通过段选择符才能找到.

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

各进程的虚拟地址空间会被划分为若干页,内存空间按页的大小建立页表。然后把页式虚拟地址与内存地址建立一一对应页表,并用相应的硬件地址变换机构,来解决离散地址变换问题。页式管理采用请求调页或预调页技术实现了内外存存储器的统一管理。

页式存储管理的逻辑地址由两部分组成:页号+偏移量

对于一个线性地址,会通过它的虚拟页号在页表里查询对应的页表条目,得到物理页号以及虚拟页偏移量,组合形成物理地址。之后CPU会通过这个物理地址来访问物理内存。

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

1.TLB:转译后备缓冲器,是用于缩短虚拟寻址时间的小缓存,其每一行都保存着PTE块,这就使得如果请求的虚拟地址在TLB中存在,它将很快地返回匹配结果。

2.多级页表:多级页表是用于压缩页表的层次结构。从两个方面减少内存要求:1.一级页表中如果有PTE为空,二级页表就不存在。2.一级页表需要总是在主存中,虚拟内存系统在需要时创建,页面调出或调入二级页表,减少主存压力。

3.VAPA的变换:首先将CPU内核发送过来的32VA[31:0]分成三段,前两段VA[31:20]VA[19:12]作为两次查表的索引,第三段VA[11:0]作为页内的偏移,查表的步骤如下:⑴从协处理器CP15的寄存器2(TTB寄存器,translation table base register)中取出保存在其中的第一级页表(translation table)的基地址,这个基地址指的是PA,也就是说页表是直接按照这个地址保存在物理内存中的。⑵以TTB中的内容为基地址,以VA[31:20]为索引值在一级页表中查找出一项(2^12=4096),这个页表项(也称为一个描述符,descriptor)保存着第二级页表(coarse page table)的基地址,这同样是物理地址,也就是说第二级页表也是直接按这个地址存储在物理内存中的。⑶以VA[19:12]为索引值在第二级页表中查出一项(2^8=256),这个表项中就保存着物理页面的基地址,我们知道虚拟内存管理是以页为单位的,一个虚拟内存的页映射到一个物理内存的页框,从这里就可以得到印证,因为查表是以页为单位来查的。⑷有了物理页面的基地址之后,加上VA[11:0]这个偏移量(2^12=4KB)就可以取出相应地址上的数据了。

 

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

CPU对一个物理地址进行访问的时候,根据PAL1高速缓存的组数和块大小确定高速缓存块偏移(CO),组索引(CI)和高速缓存标记(CT),利用CI进行组索引,对每组的8行分别匹配CT,若匹配成功且块的valid标志位为1,则命中,根据CO取出数据后返回。

若未找到匹配的行或有效位为0,则L1不命中,查找下一级缓存L2 Cache,不命中则查找L3,若三级Cache里都没有对应内存块,那么CPU将会直接访问物理内存。当至少有一级高速缓存未命中时,需要在得到数据后更新未命中的Cache。判断其中是否有有效位为0的块,如果有则可以直接将数据写入,否则则需要根据LRU,LFU等替换策略驱逐一个块后再写入。

7.6 hello进程fork时的内存映射

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

当fork在hello进程中返回时,hello进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,因此,也就为每个进程保持了私有地址空间的抽象概念。

7.7 hello进程execve时的内存映射

execve函数加载和运行hello需要以下几个步骤:

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

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

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

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

 

 

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

(1)缺页中断及处理:在主存中查找页表时相应页表条目有效位为0且物理页号为NULL,则该页表条目处于未分配,属于缺页中断。缺页中断的异常处理程序为终止

(2)缺页故障及处理:在主存中查找页表时相应页表条目有效位为0但是物理页号指向磁盘,属于缺页故障,缺页故障的异常处理程序是从磁盘装入相应页到内存并更新页表,再返回到故障指令开始执行。

7.9动态存储分配管理

(1)实现动态内存分配要考虑空闲块组织、放置、分割和合并。

(2)分配器分为两种:显式分配器、隐式分配器。显式分配器:要求应用显式地释放任何已分配的块。隐式分配器:要求分配器检测一个已分配块何时不再使用,那么就释放这个块,自动释放未使用的已经分配的块的过程叫做垃圾收集。隐式空闲链表的优点是简单,缺点是任何操作的开销,例如放置分配的块,要求空闲链表的搜索与堆中已分配块和空闲块的总数呈线性关系。

(3)当接收到一个内存分配请求时,从头开始遍历堆,找到一个空闲的满足大小要求的块,若有剩余,将剩余部分变成一个新的空闲块,更新相关块的控制信息。调整起始位置,返回给用户。释放内存时,仅需把使用情况标记为空闲即可。

(4)搜索可以满足请求的空闲块时,策略有以下几种:首次适应法、最佳适应法、最坏适应法和循环首次适应法

7.10本章小结

本章简单介绍了hello的存储器地址空间,段式管理和页式管理,TLB与四级页表支持下VA到PA的变换,三级cache支持下物理内存访问,hello进程fork和execve时的内存映射,缺页故障与缺页中断处理以及动态存储分配管理等内容。表现出了现代计算机应用了许多机制和技术来进行性能的提升。

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

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

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

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

4)读写文件:一个读操作就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增加到k+n,给定一个大小为m字节的而文件,当k>=m时,触发EOF。类似一个写操作就是从内存中复制n>0个字节到一个文件,从当前文件位置k开始,然后更新k。

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

8.2 简述Unix IO接口及其函数

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

功能:函数打开一个文件,将filename转换为一个文件描述符,若成功则返回描述符数字,出错返回-1。flags参数指明进程如何访问文件,mode参数指定新文件的访问权限位。

2.int close (int fd);

功能:函数关闭一个打开的文件,fd 是需要关闭的文件的描述符,close 返回操作结果,成功为0,失败为-1。

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

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

4.ssize_t wirte (int fd, const void *buf, size_t n);

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

8.3 printf的实现分析

1.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;

}

printf函数调用vsprintf函数,按照格式fmt结合参数arg生成格式化后的字符串,最后通过系统调用函数write输出,并返回字符串的长度。

2.printf调用的vsprintf函数:

int vsprintf(char *buf, const char *fmt, va_list args)

   {

    char* p;

    char tmp[256];

    va_list p_next_arg = args;

  

    for (p=buf;*fmt;fmt++) {

    if (*fmt != '%') {

    *p++ = *fmt;

    continue;

    }

    fmt++;

    switch (*fmt) {

    case 'x':

    itoa(tmp, *((int*)p_next_arg));

    strcpy(p, tmp);

    p_next_arg += 4;

    p += strlen(tmp);

    break;

    case 's':

    break;

    default:

    break;

    }

    }

    return (p - buf);

   }

vsprintf函数的主要作用为格式化,它接受一个格式字符串fmt来限制格式,对参数进行格式化,将产生的输出写入buf中。该函数返回要打印的字符串的长度。

3.write系统调用:

write:

     mov eax, _NR_write

     mov ebx, [esp + 4]

     mov ecx, [esp + 8]

     int INT_VECTOR_SYS_CALL

write () 函数对应的指令序列中,有用于系统调用的陷阱指令system_callwrite通过执行syscall指令调用系统服务,执行打印操作。内核会通过字符显示子程序,根据传入的ASCII码到字模库读取字符对应的点阵,然后通过vram对字符串进行输出。显示芯片将按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点,最终在终端输出字符串。

8.4 getchar的实现分析

一种getchar的代码实现如下:

int getchar(void)

{

    static char buf[BUFSIZ];

    static char* bb=buf;

    static int n=0;

    if(n==0)

    {

        n=read(0,buf,BUFSIZ);

        bb=buf;

    }

    return(--n>=0)?(unsigned char)*bb++:EOF;

}

getchar由宏实现:#define getchar() getc(stdin),从标准输入里读取下一个字符,返回类型为int型,为用户输入的ASCII码或EOF

可以看到n=read(0,buf,BUFSIZ);语句调用了read函数。read函数可以通过sys_call中断来调用内核中的系统函数。键盘中断处理子程序会接受按键扫描码并将其转换为ASCII码后保存在缓冲区,然后对缓冲区ASCII码进行读取直到接受回车键返回。

8.5本章小结

本章简单介绍了Linux系统的IO管理,简单介绍了unixIO接口及函数,分析了printfgetchar的实现,并通过这些分析展示了程序是如何接收用户键入的内容并在屏幕上打印信息的,展现了IO管理的强大功能。

结论

  1. hello程序实现p2p的过程:

1.预处理器处理hello.c,将所有调用的外部库展开,合并,生成hello.i文件。

2.编译器将hello.i转换为汇编语言文件hello.s

3.汇编器将hello.s文件转换为二进制形式的可重定位目标文件hello.o

4.链接器将hello.o与其它库进行链接并进行重定位,生成可执行文件hello

5.用户在shell中输入执行hello的命令后,shell通过fork创建进程。

6.shell调用execve,在上下文中加载可执行程序hello,开始执行hello

7.异常处理hello程序运行,在此过程中,可能产生异常与信号,例如用户键盘输入ctrl+zctrl+c等,需要调用信号处理程序进行处理。在执行程序的过程中,hello还将利用各种复杂的机制进行内存访问。

8.hello程序结束运行或收到信号后终止,父进程结束并回收hello子进程,内核删除内存中为hello创建的相关数据。

2.感悟:

Hello程序只是计算机学习中的一个最基础的部分,理解该程序的运行有助于我们今后的学习,完成报告的过程中我们又温故而知新,学习了很多新东西。依靠计算机,我们可以进行以往的人类不敢去想的天文数字级别的运算,而这些运算带来的不仅是物质生活的提高,更是让我们有能力去一探更深处的奥秘。

附件

hello.c

C语言源程序

hello.i

hello.c预处理的文本文件

hello.s

hello.i编译后的汇编文件

hello.o

hello.s汇编后的可重定位文件

hello_oelf.txt

hello.o经过readelf分析得到的文本文件,分析汇编器和链接器行为

hello_oobj.txt

hello.o经过objdump反汇编后得到的文本文件,主要是为了分析hello.o

hello

链接后的可执行文件

hello_elf.txt

helloreadelf分析得到的文本文件,为了重定位过程分析

hello_obj.txt

hello经过objdump反汇编后得到的文本文件

参考文献

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

[2] printf 函数实现的深入剖析[转]printf 函数实现的深入剖析 - Pianistx - 博客园

[3] Linux 操作系统原理—内存—页式管理、段式管理与段页式管理.代码天地. https://www.codetd.com/article/11354349

[4] 李洛, 黄达峰. Linux教程[M]. 清华大学出版社, 2005

[5] yjbjingcha. 进程控制在进程管理中的作用. 进程控制在进程管理中的作用 - yjbjingcha - 博客园.

[6] madao756. 段页式访存——线性地址到物理地址的转换. 段页式访存——线性地址到物理地址的转换 - 简书.

[7] 关于unix系统接口 普通文件io的小结关于unix系统接口 普通文件io的小结 - 依旧如此 - 博客园

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值