【无标题】 程序人生-Hello’s P2P

计算机系统

大作业

题     目  程序人生-Hello’s P2P 

专       业      计算学部           

学     号      120L022205         

班     级      2003007            

学       生      李子龙             

指 导 教 师       吴锐               

计算机科学与技术学院

2021年5月

摘  要

本文描述了c程序经过多步处理变为可执行文件,再由可执行文件加载入内存中形成运行的进程,最终从内存中删除。本文将以底层实现视角逐步分析操作系统的原理与实现过程,带领读者漫游计算机系统。

关键词:预处理;编译;链接;进程;异常与信号;虚拟内存;地址翻译;I/O                           

(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分

目  录

第1章 概述............................................................................................................. - 4 -

1.1 Hello简介...................................................................................................... - 4 -

1.2 环境与工具..................................................................................................... - 4 -

1.3 中间结果......................................................................................................... - 4 -

1.4 本章小结......................................................................................................... - 5 -

第2章 预处理......................................................................................................... - 6 -

2.1 预处理的概念与作用..................................................................................... - 6 -

2.2在Ubuntu下预处理的命令.......................................................................... - 6 -

2.3 Hello的预处理结果解析.............................................................................. - 7 -

2.4 本章小结......................................................................................................... - 8 -

第3章 编译............................................................................................................. - 9 -

3.1 编译的概念与作用......................................................................................... - 9 -

3.2 在Ubuntu下编译的命令.............................................................................. - 9 -

3.3 Hello的编译结果解析.................................................................................. - 9 -

3.4 本章小结....................................................................................................... - 13 -

第4章 汇编........................................................................................................... - 14 -

4.1 汇编的概念与作用....................................................................................... - 14 -

4.2 在Ubuntu下汇编的命令............................................................................ - 14 -

4.3 可重定位目标elf格式................................................................................ - 14 -

4.4 Hello.o的结果解析..................................................................................... - 18 -

4.5 本章小结....................................................................................................... - 20 -

第5章 链接........................................................................................................... - 21 -

5.1 链接的概念与作用....................................................................................... - 21 -

5.2 在Ubuntu下链接的命令............................................................................ - 21 -

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

5.4 hello的虚拟地址空间................................................................................. - 26 -

5.5 链接的重定位过程分析............................................................................... - 27 -

5.6 hello的执行流程......................................................................................... - 29 -

5.7 Hello的动态链接分析................................................................................ - 30 -

5.8 本章小结....................................................................................................... - 30 -

第6章 hello进程管理.................................................................................... - 31 -

6.1 进程的概念与作用....................................................................................... - 31 -

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

6.3 Hello的fork进程创建过程...................................................................... - 31 -

6.4 Hello的execve过程.................................................................................. - 32 -

6.5 Hello的进程执行........................................................................................ - 33 -

6.6 hello的异常与信号处理............................................................................. - 34 -

6.7本章小结........................................................................................................ - 34 -

第7章 hello的存储管理................................................................................ - 35 -

7.1 hello的存储器地址空间............................................................................. - 35 -

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

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

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

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

7.6 hello进程fork时的内存映射................................................................... - 37 -

7.7 hello进程execve时的内存映射............................................................... - 37 -

7.8 缺页故障与缺页中断处理........................................................................... - 37 -

7.9动态存储分配管理........................................................................................ - 37 -

7.10本章小结...................................................................................................... - 39 -

第8章 hello的IO管理.................................................................................. - 40 -

8.1 Linux的IO设备管理方法........................................................................... - 40 -

8.2 简述Unix IO接口及其函数........................................................................ - 40 -

8.3 printf的实现分析........................................................................................ - 40 -

8.4 getchar的实现分析.................................................................................... - 41 -

8.5本章小结........................................................................................................ - 41 -

结论......................................................................................................................... - 41 -

附件......................................................................................................................... - 43 -

参考文献................................................................................................................. - 44 -

第1章 概述

1.1 Hello简介

P2P(Program to Process):

hello程序的生命是从一个高级c语言程序开始的,因为这种形式能被人读懂.但是,计算机系统是读不懂高级语言的.为了在系统上运行hello.c 程序,每条 C 语句都必须要被其他程序转化为一系列的低级机器语言指令。,于是要对hello.c进行cpp的预处理得到hello.i、ccl的编译得到hello.s、as的汇编得到hello.o、ld的链接得到可执行文件hello。之后用户通过shell键入./hello命令开始执行程序,shell通过fork函数创建一个子进程,再由子进程执行execve函数加载hello。

020(Zero-0 to Zero-0):

      020过程指的是在execve执行hello程序后,内核为hello进程映射虚拟内存。在hello进入程序入口后,hello相关的数据就被内核加载到物理内存中,hello程序开始正式被执行。为了让hello正常执行,内核还需要为hello分配时间片、逻辑控制流。最后,当hello运行结束,终止成为僵尸进程后,由shell负责回收hello进程,删除与hello有关的数据内容。

1.2 环境与工具

硬件环境:AMD R7-4800H、8G RAM、512G SSD +375G SSD

软件环境:Windows10 64位、Vmware、Ubuntu 20.04

开发与调试工具:Codeblocks、gcc、Objdump、edb、Hexedit

1.3 中间结果

Hello.c  程序源码

Hello.i  hello.c文预处理后的文本文件

Hello.s  hello.i文件编译后的汇编文件

Hello.o  hello.s汇编后的可重定位目标文件

Hello    hello.s链接后的可执行文件

1.4 本章小结

本章作为整个文章的概述,介绍了一个hello程序从源代码到可执行文件再到运行中的进程,最后再到运行结束被清除数据的P2P和020过程,为下文开篇做铺垫。

(第1章0.5分)

第2章 预处理

2.1 预处理的概念与作用

概念:

预处理(pre-treatment),是指在进行最后加工完善以前进行的准备过程,具体应用在不同的行业或领域,会有不同的解释。在一些程序设计语言中,预处理是preprocessing的翻译。

最常见的预处理是C语言和C++语言。ISO C和ISO C++都规定程序由源代码被翻译分为若干有序的阶段(phase),通常前几个阶段由预处理器实现。预处理中会展开以#起始的行,试图解释为预处理指令(preprocessing directive) ,其中ISO C/C++要求支持的包括#if/#ifdef/#ifndef/#else/#elif/#endif(条件编译)、#define(宏定义)、#include(源文件包含)、#line(行控制)、#error(错误指令)、#pragma(和实现相关的杂注)以及单独的#(空指令)。预处理指令一般被用来使源代码在不同的执行环境中被方便的修改或者编译。

预处理器在UNIX传统中通常缩写为PP,在自动构建脚本中C预处理器被缩写为CPP的宏指代。为了不造成歧义,C++(cee-plus-plus) 经常并不是缩写为CPP,而改成CXX。

注意预处理常被错误地当作预编译(precompiling) ,事实上这是两个不同的概念。预处理尽管并不是ISO C/C++要求的单独阶段,但“预处理”这个术语正式地出现并参与构成其它术语,如C的预处理翻译单元(preprocessing translation unit)  以及C/C++词法规则中预处理记号(prerprocessing-token) 这个语法分类。预编译是一些编译器支持的特性,不是C/C++语言的特性或实现必须要求遵循的规则涉及到的内容,没有在ISO C/C++全文中出现。

作用:

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

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

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

2.2在Ubuntu下预处理的命令

使用预处理命令:gcc hello.c -E -o hello.i 对hello.c进行预处理

2.3 Hello的预处理结果解析

首先可以看到在打开的目录下新增了hello.i文件

打开hello.i发现内容比hello.c大得多,这是因为.c头文件中的内容被复制到了.i文件中。

并且原hello.c中的注释不会出现在hello.i中:

2.4 本章小结

本章主要通过预处理命令对hello进行了预处理,了解了预处理的概念与作用,并根据预处理结果对预处理过程进行了分析。

(第2章0.5分)

第3章 编译

3.1 编译的概念与作用

概念:

编译(compilation , compile) 1、利用编译程序从源语言编写的源程序产生目标程序的过程。 2、用编译程序产生目标程序的动作。 编译就是把高级语言变成计算机可以识别的2进制语言,计算机只认识1和0,编译程序把人们熟悉的语言换成2进制的。 编译程序把一个源程序翻译成目标程序的工作过程分为五个阶段:词法分析;语法分析;语义检查和中间代码生成;代码优化;目标代码生成。主要是进行词法分析和语法分析,又称为源程序分析,分析过程中发现有语法错误,给出提示信息。

作用:

       编译将高级语言转化为汇编语言能够检测代码的正确性,并为接下来将汇编语言生成机器可识别的机器码做准备。

3.2 在Ubuntu下编译的命令

在ubuntu下对.i文件进行编译的指令是:gcc -S -o hello.s hello.i

3.3 Hello的编译结果解析

3.3.1汇编代码:

3.3.2数据:

字符串:如图,字符串被放在只读数据段中。

局部变量i:如图,局部变量i被放在栈中,通过rbp的相对偏移来访问。

主函数传递参数argc、argv:如图,这两个参数初始时分别被放在edi和rsi上,并在主函数中将其压栈,同样通过rbp的偏移量来访问。之所以这么判断的根据是64位系统函数间传递参数时使用寄存器的顺序。

3.3.3赋值:

在原本c程序中的赋值操作只有i = 0这个赋值语句。在汇编中是通过mov立即数的方式体现的,如图:

处理c语言中翻译过来的赋值操作,在汇编代码中还有通过以leaq(地址传递)来赋值的方式,如图:

算数操作:c代码中的算术操作有i++,在汇编代码中是通过add来实现的:

除了c代码中的算数操作,在汇编代码中还有通过sub(减操作):

3.3.4 类型转化:

c程序中设计到的类型转化只有atoi,在汇编代码中同样是调用atoi函数进行:

3.3.5 关系操作:

在c代码中有的关系操作时argc != 4以及i < 8在汇编代码中是通过cmp比较来完成的,如图:

3.3.6 数组:

c代码中的数组访问有argv[1]、argv[2]、argv[3],在汇编代码中访问这三个量是通过数组首地址加偏移量的方式,如图:

argv[2]被放在rdx中,

argv[1]被放在rsi中,

argv[3]被放在rdi中。

3.3.7 控制转移:

在c源程序中的控制转移有if语句和for循环,在汇编代码中这两者都是通过条件跳转指令来完成的。如图:

3.3.8 函数操作:

在64位系统中,参数的传递首先是通过寄存器,顺序是rdi、rsi、rdx、rcx、r8、r9,其余参数压栈。

如图,我们的汇编代码在调用printf函数之前就先把格式串和输出的内容分别压入rdi、rsi、rdx中。

函数的调用使用的是call语句,如果用立即数驱动call语句就必须要计算函数所在位置和rip的相对偏移量。

3.4 本章小结

本章通过编译器对上一章生成的.i文件编译成.s文件,并对数据类型,赋值,类型转换,算数操作,关系操作,位移控制,函数进行了分析,将编译过程进行了全面的分析,并对一些常用的汇编指令进行了阐述。

(第32分)

第4章 汇编

4.1 汇编的概念与作用

概念:

汇编程序是指把汇编语言书写的程序翻译成与之等价的机器语言程序的翻译程序。汇编程序输入的是用汇编语言书写的源程序,输出的是用机器语言表示的目标程序。汇编语言是为特定计算机或计算机系列设计的一种面向机器的语言,由汇编执行指令和汇编伪指令组成。采用汇编语言编写程序虽不如高级程序设计语言简便、直观,但是汇编出的目标程序占用内存较少、运行效率较高,且能直接引用计算机的各种设备资源。它通常用于编写系统的核心部分程序,或编写需要耗费大量运行时间和实时性要求较高的程序段。

作用:

       将.s文件生成机器码,使其能够被链接器ld链接生成可执行文件。

4.2 在Ubuntu下汇编的命令

gcc -c -o hello.o hello.s

4.3 可重定位目标elf格式

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

4.3.1 elf格式:

ELF 全称 “Executable and Linkable Format”,即可执行可链接文件格式,目前常见的Linux、 Android可执行文件、共享库(.so)、目标文件( .o)以及Core 文件(吐核)均为此格式。ELF文件主要有三种类型,可以通过ELF Header中的e_type成员进行区分:可重定位文件、可执行文件、共享目标文件。

4.3.2 ELF内容:    

使用readelf -a hello.o > helloELF.txt获得hello.o的ELF格式文件

其内容如下:

ELF头告诉了我们文件的基本信息。

节头表告诉了我们每个节的大小、名称、类型、读、写、执行权限以及对其方式。

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

重定位节包含了.text文件中需要重定位的信息,在链接器将目标文件与其它文件进行链接时需要修改这些信息,可执行文件中不包含重定位节。

4.4 Hello.o的结果解析

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

使用objdump -d hello.o > helloas.txt获得反汇编代码

分支转移:反汇编代码中的跳转指令不再使用段名称如L0、L1等,而是采用跳转到指定地址的方式进行调用采用确定的段名称进行跳转。

函数调用:在上一章生成的.s文件中函数调用后时函数的名称,而通过hello.o生成的反汇编代码中确实下一条指令的地址,这是因为hello中调用的都是共享库中的函数如printf、getchar,这些都需要在下一张=章链接中通过链接器的链接才能完成得到运行时的执行地址,现在的地址时不确定的,对于这些不确定的地址call指令都会先设置偏移地址为0并在.rela,text中添加其重定位条目,最终在下一章中链接过程中完成。

4.5 本章小结

本章介绍了程序的汇编过程,将hello.s进行了汇编处理生成了可重定位的目标.o文件为下一步链接做准备,分析了一个可重定位目标文件中的内容,剖析了这些内容在接下来的链接过程中能起什么作用,并对比了汇编与反汇编代码有何异同。

(第41分)

5章 链接

5.1 链接的概念与作用

概念:

      链接是指在电子计算机程序的各模块之间传递参数和控制命令,并把它们组成一个可执行的整体的过程。链接也称超级链接,是指从一个网页指向一个目标的连接关系,所指向的目标可以是另一个网页,也可以是相同网页上的不同位置,还可以是图片、电子邮件地址、文件、甚至是应用程序。

作用:

      链接器在软件开发中扮演一个关键的角色,它使得分离编译成为可能。在我们开发一个大型程序的时候,通用的做法是将它分解为更小、更好管理的模块,独立地修改和编译这些模块,当改变其中的一个模块时只需简单地重新编译它,并重新链接应用而无须重新编译其他文件。

5.2 在Ubuntu下链接的命令

在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 > helloelf.txt得到ELF格式的文件

ELF头:可执行文件的ELF头与可重定位目标文件的ELF头文件的类型不同,可执行文件的类型不再是REL而是EXEC;程序的入口点不一样,因为连接上了库文件,使得main函数不再是从0x0开始。同理节头的开始位置也发生了变化;节头的数量产了变化。

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

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

重定位节:节的重定位信息。

符号表:用来存放程序中定义和引用的函数和全局变量的信息。重定位需要引用的符号都在其中声明。

5.4 hello的虚拟地址空间

使用edb加载hello:

程序被载入的虚拟地址为0x4010f0并且与ELF文件的入口点地址相同。

程序头表在执行时告诉链接器器运行时加载的内容并提供动态链接的信息,每一表项。每一个表项提供了各段在虚拟地址空间和物理地址空间的大小、位置、标志、访问权限和对齐方面的信息,如:LOAD 指定可装入段,通过 filesz 和 memsz 进行描述。文件中的字节会映射到内存段的起始位置。如果段的内存大小(memsz)大于文件大小(filesz),则将多余字节的值定义为0这些字节跟在段的已初始化区域后面。文件大小不能大于内存大小。程序头表中的可装入段的各项按升序显示,并基于vaddr 成员进行排列;DYNAMIC 指定动态链接信息;NOTE 指定辅助信息的位置和大小;GNU_STACK:权限标志,标志栈是否是可执行的。

5.5 链接的重定位过程分析

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

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

hello.o的main函数首地址为0而经过连接后的hello的main函数首地址为0x400532,这是main函数在运行时相对.text段的偏移位置。

在链接过程中动态链接器为函数定义了程序入口_start、初 始化函数_init,_start 程序调用main函数并链接进了printf,getchar等库函数,链接器将这些函数链接到一起使程序能够最终执行。

在可重定位的hello.o的返汇编代码中字符串常量或者变量(已初始化)仅仅是使用0x0(%rip)来暂时表示其位置(占位),跳转地址的偏移量也都为0,这些并不是最终的地址,链接时,链接器会根据可重定位目标文件的重定位节给每个符号分配了最终的地址。

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

5.6 hello的执行流程

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

进入edb后第一个调用的程序是hello!_start:

地址是0x401129

下一个进入的程序是hello!_init:

执行过程有以下主要函数:

hello!_start

hello!_init

hello!main

dl_init

hello!puts@plt

hello!exit@plt

5.7 Hello的动态链接分析

使用动态链接生成可执行文件,在启动时首先执行动态链接器,。动态链接器使用过程链接表 PLT+全局偏移量 表 GOT 实现函数的动态链接,GOT 中存放函数的目标地址,PLT 中存放跳转到 GOT 中目标地址的代码逻辑。

调用dl_init之前,每一条PIC函数调用的目标地址实际指向都是PLT 中的代码逻辑,GOT 存放的是 PLT 中函数调用指令的下一条指令地址。

调用dl_init之后,观察截图与调用前对比发现标记出的部分发生了改变,再次通过Data dump查看。

动态链接器将可执行文件和链接器本身的符号表合并到全局符号表,之后链接器寻找执行文件所依赖的共享对象,通过. Dynamic中的入口DT_NEEDED, 链接器可以列出可执行文件所需要的所有共享对象,并放入到集合中,链接器由集合中的名字找到相应的文件读取相应的ELF文件头和“.dynamic”段, 然后将它相应的代码段和数据段映射到进程空间中,如果ELF共享对象还依赖于其他共享对象,那么将所依赖的共享对象的名字放到装载集合中,并不断循环直到全部加载。

5.8 本章小结

本章对.o文件进行了最后的链接操作,使之成为了一个可执行程序并可最后执行输出结果。介绍了程序链接已经加载动态库的过程。这是程序生成可执行文件的最后一步,也是将大型程序项目分解成小模块的关键所在。

(第51分)

6章 hello进程管理

6.1 进程的概念与作用

概念:

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

作用:

      计算机处理的所有用户的任务由进程来完成。它提供一个假象,好像我们的程序独占地使用处理器和系统内存。

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

Shell 是一种命令行解释器, 其读取用户输入的字符串命令, 解释并且执行命令。它是一种特殊的应用程序, 介于系统调用/库与应用程序之间, 其提供了运行其他程序的的接口.它可以是交互式的, 即读取用户输入的字符串;也可以是非交互式的, 即读取脚本文件并解释执行, 直至文件结束. 无论是在类 UNIX, Linux 系统, 还是 Windows, 有很多不同种类的 Shell: 如类 UNIX, Linux 系统上的 Bash, Zsh 等; Windows 系统上的 cmd, PowerShell 等。

Bash 是 Bourne Again SHell 的缩写, 是 GNU 计划中的 Shell, 也是一些类 UNIX 系统与多数 Linux 发行版的默认 Shell. 本文对 Shell 进行简介, 并对 Bash 的功能与解释过程进行了介绍分析.

处理流程:

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

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

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

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

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

6.3 Hello的fork进程创建过程

检查子进程是否允许被跟踪,复制进程描述符,返回的是新的进程描述符的地址,初始化完成量,唤醒新进程,等待子进程完成,在父进程返回用户空间时,其返回子进程的ID,子进程返回用户空间时,其返回值为0。

6.4 Hello的execve过程

1.使用execve就是一次系统调用,首先要做的将新的可执行文件的绝对路径从调用者(用户空间)拷贝到系统空间中。

2.在得到可执行文件路径后,就找到可执行文件打开,由于操作系统已经为可执行文件设置了一个数据结构,就初始化这个数据结构,保存一个可执行文件必要的信息。

3.可执行文件不是真正上能够自己运行的,需要有代理人来代理。在系统内核中有一个formats队列,循环遍历这个队列,看看现在被初始化的这个数据结构是哪个代理人可以代理的。如果没有就继续查看数据结构中的信息。按照系统配置了是否可以动态加载模块,加载一次模块,再循环遍历看是否有代理人前来认领。

4.找到正确的代理人后,代理人首先要做的就是放弃以前从父进程继承来的资源(虽然代理程序各不相同,但这是一个共性的操作)。主要是对信号处理表,用户空间和文件3大资源的处理。

  a.信号处理表:将父进程的信号处理表复制过来(可能已经复制了,也可能没有复制,通过检查信号处理表的共享参数就可以知道)。信号处理分3种情况:一对该信号不理睬,二对信号采取默认动作,三对信号采取指定动作。前面2种可以直接复制,最后一种,所谓的默认动作就是用户指定了程序处理,这段程序的代码必然是父进程自己拥有的,他的位置就存在用户空间中,下面我们会放弃继承到的父进程用户空间,对第3种情况的处理就是将其改成默认处理。所以,在新建的子进程中,对信号如果子进程没有采取指定处理,那么一律都会是默认处理,当然如果父进程对某个信号采取了不理睬,子进程也会不理睬,除非子进程后来又做了修改。

  b.用户空间,放弃原来的用户空间(子进程可能有自己的页面,或者就是通过指针共享了父进程的页面)这些一律放弃,将进程控制块task_struct中对用户空间的描述的数据结构mm_struct的下属结构vma全部置0.简而言之就是现在子进程的用户空间是个空架子,一个页面也没有,父进程空间被放弃。

   c.进程控制块task_struct中有file的指针记录了进程打开的文件信息,子进程对继承到的文件采取关闭应当关闭的信息。file的数据结构中有位图记录了应当关闭的文件,子进程放弃这些文件。一般来说,执行的效果是除了标准输入文件,标准输出文件,标准错误输出文件。其它的文件都不会被子进程继承。(标准输入一般就是键盘,标准输出就是显示器。因此如果子进程有打印语句的话,那么他的打印出来的字符会打印到父进程打印的地方,前面写文章有点错误,我已经改掉了)。

5.至此我们已经做了的实际动作就是信号处理表,用户空间和文件。但用户空间是个空架子,真正的程序代码没载入,数据段也没载入,堆栈没有开辟,执行参数和环境变量也没有被印射。但可以知道,每个可执行文件的载入是不同的,比如linux下shell文件和a.out文件2个有很明显的不同,你可以对他们采用同样的载入办法吗。下面就是各个代理人自己开始为自己的代理方申请空间,准备用户内存。最后调用 start_thread 开始启动进程。

6.5 Hello的进程执行

1> 逻辑控制流:每个进程似乎独占地使用CPU,通过OS内核的

上下文切换 机制提供,每个进程拥有一个独立的逻辑控制流,使得程序员以为自己的程序在执行过程中独占处理器

2> 私有地址空间:每个进程似乎独占地使用内存系统,OS内核的虚拟内存 机制提供,每个进程拥有一个私有的虚拟地址空间,使得程序员以为自己的程序在执行过程中独占使用存储器

3> 时间片:每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间

4> 上下文切换:进程由常驻内存的操作系统代码块(称为内核)管理,内核不是一个单独的进程,而是作为现有进程的一部分运行;控制流通过上下文切换

从一个进程传递到另一个进程。

5> 用户模式与内核模式:进程的运行一般有两个模式,用户模式和内核模式,内核模式的权限更高,当处于内核态时,可以执行任何指令并访问系统空间。处于用户态执行时,进程所能访问的内存空间和对象受到限制,其所处于占有的处理机是可被抢占的 ;而处于核心态执行中的进程,则能访问所有的内存空间和对象,且所占有的处理机是不允许被抢占的。

hello的执行过程:hello初始运行为用户模式,当运行到sleep时将会陷入到内核模式,内核处理休眠请求主动释放当前进程,hello由运行状态转为等待状态,进行上下文切换将控制权交给sleep进程,当收到中断信号后,处于内核态的hello能够接受处理信号,将hello进程重新进入运行状态,之后再次运行到getchar时,实际落实到执行输入流是 stdin 的系统调用 read,hello再次陷入到内核态并进行数据传输接受用户的输入命令,时磁盘发出一个中断信号,此时内核从其他进程进行上下文切换回 hello 进程。

6.6 hello的异常与信号处理

异常可以分为如下四类:中断、陷阱、故障和终止。

乱按:只是在屏幕上显示出来并未对程序的输出造成影响。

按回车:输入回车后程序的行为和乱按几乎是一致的,只是回车没有被识别为无效指令而是被无视。

Crtl+z:向内核发送信号SINGINT,陷入到内核态,处理信号,该命令为将进程挂起并显示处理结果,通过ps看出hello并未被回收,再次fg调用,程序继续执行。

Crtl+c:向内核发送信号SINGINT,陷入到内核态,处理信号,终止程序。

                 

6.7本章小结

本章介绍了进程。了解了进程的概念和作用,以及fork以及execve的过程。了解进程的创建与异常流控制。叙述了一个进程是怎么在计算机中被创建的,一个程序是怎么通过子进程被执行的,这是P2P中的最后一步process。还介绍了异常与信号,并实际对hello用各种信号进行测试,来了解常用信号的用途。

(第61分)

7章 hello的存储管理

7.1 hello的存储器地址空间

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

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

虚拟地址是Windows程序时运行在386保护模式下,这样程序访问存储器所使用的逻辑地址称为虚拟地址,与实地址模式下的分段地址类似,虚拟地址也可以写为“段:偏移量”的形式,这里的段是指段选择器。

在存储器里以字节为单位存储信息,为正确地存放或取得信息,每一个字节单元给以一个唯一的存储器地址,称为物理地址(Physical Address),又叫实际地址或绝对地址。

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

从8086CPU开始,为了让程序在内存中能自由浮动而又不影响它的正常执行,CPU将内存划分成逻辑上的段来给程序使用。

x86继续沿用了这一模式,但是用保护模式将其管理起来,进行保护。而段式管理正是用来对段进行管理的。

在保护模式下,会将每个段的信息先进行登记。

和段有关的信息需要8 个字节来描述,所以称为段描述符 (Segment Descriptor),每个段都需要一个描述符。为了存放这些段描述符,就在内存中开辟了一段空间,在这段空间中所有的段描述符以数组的形式存放在一起,这就构成了一个段描述表(Descriptor Table)。最主要的描述符表是全局描述符表(Global Descriptor Table GDT),这个全局段描述符表在任何时刻都可以使用,在进入保护模式前必须要先定义全局描述表的内容才行。

然而段描述符由八个字节来组成,用起来太大了而且不方便,于是保护模式又有了段选择子(Segment Selector),段选择子作为一种结构体,通过对段选择子的解析可以得到段描述符的地址,然后就可以方便的进行读取了。

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

页式管理是一种内存空间存储管理的技术,页式管理分为静态页式管理和动态页式管理。

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

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

虚地址(Virtual Address, VA)实地址(Physical Address, PA)转化,虚实地址转换主要分为两种:

由于整个系统的进程数不定,每个进程所需要的内存不定,以及进程切换的不确定性,因此,虚实地址转化不能简单的将某个连续大内存块映射到某个进程(Coarse-grained),必须采取更细粒度(Final-grained)的映射,即将一些可能不连续的小内存块(比如4K大小)一起映射到进程,形成一块连续的虚拟地址。为了记录这些映射信息,需要页表(Page)。但是页表的导入引入了新的问题,那就是每次访存变成了两次,一次查询页表,得到物理地址,第二次通过物理地址取数(事实上有办法把这两个过程部分并行起来,详见Cache的那篇)。为了提高查询页表的速度,现在的处理器都为页表做了一个小Cache,叫做旁路转换缓冲(Translation lookasidebuffer, TLB)。

直接映射,比如直接将64位的虚拟地址高位抹去,得到物理地址。这主要用于操作系统启动时的那块内存区域。主要是由于系统刚启动时,第1种转化所需要的页表,TLB没有初始化(页表,TLB其实都是操作系统管理的,倘若还用第一种,就陷入了鸡生蛋,蛋生鸡的死循环了),只能用这种最简单粗暴的办法。

由于第二种比较简单,在这里主要讲一下第1种虚实地址转化,即通过页表和TLB进行虚实地址转化。

用固定大小的页(Page)来描述逻辑地址空间,用相同大小的页框(Frame)来描述物理内存空间,由操作系统实现从逻辑页到物理页框的页面映射,同时负责对所有页的管理和进程运行的控制。

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

MMU将物理地址发给L1缓存,缓存从物理地址中取出缓存偏移CO、缓存组索引CI以及缓存标记CT。若缓存中CI所指示的组有标记与CT匹配的条目且有效位为1,则检测到一个命中,读出在偏移量CO处的数据字节,并把它返回给MMU,随后MMU将它传递给CPU。若不命中,则需到低一级Cache(若L3 cache中找不到则到主存)中取出相应的块将其放入当前cache中,重新执行对应指令,访问要找的数据。

7.6 hello进程fork时的内存映射

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

7.7 hello进程execve时的内存映射

删除已存在的用户区域。创建新的私有区域(.malloc,.data,.bss,.text)。创建新的共享区域(libc.so.data,libc.so.text)。设置程序计数器PC,指向代码的入口点。

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

缺页故障:当指令引用一个虚拟地址,而与该地址相对于的物理页面不在内存中,因此必须从磁盘中取出时,就会发送缺页故障。

缺页中断处理:假设MMU在试图翻译某个虚拟地址A时,触发了一个缺页。这个异常当值控制转移到内核的缺页处理程序,处理程序随后指向下面的步骤:

1、保护CPU现场

2、分析中断原因

3、转入缺页中断处理程序进行处理

4、恢复CPU现场,继续执行

7.9动态存储分配管理

动态存储分配,即指在目标程序或操作系统运行阶段动态地为源程序中的量分配存储空间,动态存储分配包括栈式或堆两种分配方式。需要注要的是,采用动态存储分配进行处理的量,并非所有的工作全部放在运行时刻做,编译程序在编译阶段要为其设计好运行阶段存储组织形式,并为每一个数据项安排好它在数据区中的相对位置。

方法:

首次适应算法(first fit)

我们以空闲分区链为例来说明采用 首次适应算法(first fit)时的分配情况。FF 算法要求空闲分区链以地址递增的次序链接。在分配内存时,从链首开始顺序查找,直至找到一个大小能满足要求的空闲分区为止;然后再按照作业的大小,从该分区中划出一块内存空间分配给请求者,余下的空闲分区仍留在空闲链中。若从链首直至链尾都不能找到一个能满足要求的分区,则此次内存分配失败,返回。该算法倾向于优先利用内存中低址部分的空闲分区,从而保留了高址部分的大空闲区。这给为以后到达的大作业分配大的内存空间创造了条件。其缺点是低址部分不断被划分,会留下许多难以利用的、很小的空闲分区,而每次查找又都是从低址部分开始,这无疑会增加查找可用空闲分区时的开销。

循环首次适应算法(next fit)

该算法是由首次适应算法演变而成的。在为进程分配内存空间时,不再是每次都从链首开始查找,而是从上次找到的空闲分区的下一个空闲分区开始查找,直至找到一个能满足要求的空闲分区,从中划出一块与请求大小相等的内存空间分配给作业。为实现该算法,应设置一起始查寻指针,用于指示下一次起始查寻的空闲分区,并采用循环查找方式,即如果最后一个(链尾)空闲分区的大小仍不能满足要求,则应返回到第一个空闲分区,比较其大小是否满足要求。找到后,应调整起始查寻指针。该算法能使内存中的空闲分区分布得更均匀,从而减少了查找空闲分区时的开销,但这样会缺乏大的空闲分区。

最佳适应算法(Best Fit)

算法:将空闲分区链中的空闲分区按照空闲分区由小到大的顺序排序,从而形成空闲分区链。每次从链首进行查找合适的空闲分区为作业分配内存,这样每次找到的空闲分区是和作业大小最接近的,所谓“最佳”。

优点:第一次找到的空闲分区是大小最接近待分配内存作业大小的;

缺点:产生大量难以利用的外部碎片。

最坏适应算法(Worst Fit)

算法:与最佳适应算法刚好相反,将空闲分区链的分区按照从大到小的顺序排序形成空闲分区链,每次查找时只要看第一个空闲分区是否满足即可。

优点:效率高,分区查找方便;

缺点:当小作业把大空闲分区分小了,那么,大作业就找不到合适的空闲分区。

快速适应算法(quick fit)

该算法又称为分类搜索法,是将空闲分区根据其容量大小进行分类,对于每一类具有相同容量的所有空闲分区,单独设立一个空闲分区链表,这样,系统中存在多个空闲分区链表,同时在内存中设立一张管理索引表,该表的每一个表项对应了一种空闲分区类型,并记录了该类型空闲分区链表表头的指针。空闲分区的分类是根据进程常用的空间大小进行划分,如 2 KB、4 KB、8 KB 等,对于其它大小的分区,如 7 KB 这样的空闲区,既可以放在 8 KB 的链表中,也可以放在一个特殊的空闲区链表中。该算法的优点是查找效率高,仅需要根据进程的长度,寻找到能容纳它的最小空闲区链表,并取下第一块进行分配即可。另外该算法在进行空闲分区分配时,不会对任何分区产生分割,所以能保留大的分区,满足对大空间的需求,也不会产生内存碎片。该算法的缺点是在分区归还主存时算法复杂,系统开销较大。此外,该算法在分配空闲分区时是以进程为单位,一个分区只属于一个进程,因此在为进程所分配的一个分区中,或多或少地存在一定的浪费。空闲分区划分越细,浪费则越严重,整体上会造成可观的存储空间浪费,这是典型的以空间换时间的作法。

7.10本章小结

本章介绍了hello 的存储器地址空间、 intel 的段式管理、 hello 的页式管理,在指定环境下介绍了 VA 到 PA 的变换,以及进程fork和execve内存映射的内容,还有缺页问题和动态存储分配管理的问题。

(第7 2分)

8章 hello的IO管理

8.1 Linux的IO设备管理方法

设备的模型化:文件

      在Linux中,所有的IO设备(网络、磁盘、终端等)都被模型化为文件,所有的输入和输出都被当作对相应文件的读和写来执行。

设备管理:unix io接口

      这种将设备映射为文件的方式,允许linux内核引出一个简单的低级的应用接口,称为Unix I/O,这使得所有的输入输出都能以一种统一且一致的方式来执行。

8.2 简述Unix IO接口及其函数

打开文件:打开一个已经存在的文件,若文件不存在则创建一个新的文件。

open()函数

关闭文件:通知内核关闭这个文件,内核会释放文件打开时创建的数据结构,将描述符恢复到描述符池中。

close()函数

读取文件:从当前文件位置复制字节到内存位置,如果返回值<0则说明出现错误。

read()函数

写入文件:从内存复制字节到当前文件位置,如果返回值<0则说明出现错误。

write()函数

改变文件位置:文件开始位置为文件偏移量,应用程序通过seek操作,可设置文件的当前位置为k。

lseek()函数

8.3 printf的实现分析

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

从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.

字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。

显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。

syscall将字符串中的字节从寄存器中通过总线复制到显卡的显存中,显存中存储的是字符的ASCII码。字符显示驱动子程序将通过ASCII码在字模库中找到点阵信息将点阵信息存储到vram中。

显示芯片会按照一定的刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。然后打印。

8.4 getchar的实现分析

异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。

getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。

getchar的底层实现是通过系统函数read实现的。getchar通过read函数从缓冲区中读入一行,并返回读入的第一个字符,若读入失败则返回EOF。

8.5本章小结

本章hello的IO管理的学习了linux的IO设备管理方法,Unix IO接口以及五个主要函数open(打开)、close(关闭)、read(读)、write(写)、lseek(定位)的功能以及函数的结构,并分析了printf与getchar的实现。

(第81分)

结论

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

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

过程如下:

编写:通过键盘鼠标等IO设备敲入代码

预处理:对敲入的hello.c文件进行预处理生成hello.i

编译:编译器将hello.i编译生成hello.文件

汇编:汇编器对hello.s进行汇编处理生成可重定位的hello.o文件

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

运行:在shell中输入./hello

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

运行程序:调用execve将程序加载进去

创建新的内存区域,并创建一组新的代码、数据、堆和栈段。并安排好内容

对异常,信号,命令进行处理

进程的回收,杀死进程

感悟与创行理念与实现方法:

       本次大作业十分抽象,而计算机系统本身就是抽象的,其包含的内容大多也如此,一个hello简单的代码小程序背后的执行过程竟是如此复杂抽象,没想到一个习以为常的hello程序竟然能够挖掘出如此庞大的知识体系,可谓学无止境,但是要学好计算机更要从最底层开始理解,不知道这辈子对计算机的理解能到达什么程度。

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

附件

列出所有的中间产物的文件名,并予以说明起作用。

hello.c: hello原程序代码.

hello.i: hello.c经过预处理后的文件。

hello.s: hello.i经过编译得到的文件。

hello.o: hello.s经过汇编得到的文件。

hello: hello.o经过链接后得到的可执行文件。

helloas.txt: hello.o的反汇编。

helloELF1.txt: hello.o的ELF格式。

helloelf.txt: hello的ELF格式。

hello.txt: hello的反汇编。

(附件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.

[7]  https://baike.baidu.com/item/%E5%8A%A8%E6%80%81%E5%AD%98%E5%82%A8%E5%88%86%E9%85%8D/21306011?fr=aladdin#3

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值