HITICS程序人生-Hello’s P2P From Program to Process
摘 要
俺是Hello,额是每一个程序猿¤的初恋(羞羞……)
却在短短几分钟后惨遭每个菜鸟的无情抛弃(呜呜……),他们很快喜欢上sum、sort、matrix、PR、AI、IOT、BD、MIS……,从不回头。
只有我自己知道,我的出身有多么高贵,我的一生多么坎坷!
多年以后,那些真正懂我的大佬(也是曾经的菜鸟一枚),才恍然感悟我的伟大!
……………………想当年:俺才是第一个玩 P2P的: From Program to Process
懵懵懂懂的你笨笨磕磕的将我一字一键敲进电脑存成hello.c(Program),无意识中将我预处理、编译、汇编、链接,历经艰辛-神秘-高贵-欣喜,我-Hello一个完美的生命诞生了。
你造吗?在壳(Bash)里,伟大的OS(进程管理)为我fork(Process),为我execve,为我mmap,分我时间片,让我得以在Hardware(CPU/RAM/IO)上驰骋(取指译码执行/流水线等);
你造吗?OS(存储管理)与MMU为VA到PA操碎了心;TLB、4级页表、3级Cache,Pagefile等等各显神通为我加速;IO管理与信号处理使尽了浑身解数,软硬结合,才使我能在键盘、主板、显卡、屏幕间游刃有余, 虽然我在台上的表演只是一瞬间、演技看起来很Low、效果很惨白。
感谢 OS!感谢 Bash!在我完美谢幕后为我收尸。 我赤条条来去无牵挂!
我朝 CS(计算机系统-Editor+Cpp+Compiler+AS+LD + OS + CPU/RAM/IO等)挥一挥手,不带走一片云彩! 想想俺也是 O2O: From Zero-0 to Zero-0。
历史长河中一个个菜鸟与我擦肩而过,只有CS知道我的生、我的死,我的坎坷,“只有 CS 知道……我曾经……来…………过……”
关键词:关键词1;关键词2;……;
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
第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:
在Editor中键入代码得到 hello.c
linux,cpp预处理、ccl编译、as汇编、ld链接>>可执行目标程序hello,在shell中键入启动命令,fork产生子进程,变为Process
020:
用shell ,execve,映射虚拟内存,进入程序入口后程序开始载入物理内 存,进入main函数执行目标代码,CPU为运行的hello分配时间片执行逻辑控制流。当程序运行结束后,shell父进程负责回收hello进程,内核删除相关数据结构
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
软件环境 Windows7 64 位以上;VirtualBox/Vmware 11 以上;Ubuntu 16.04 LTS 64 位/ 优麒麟 64 位
硬件环境 X64 CPU;2GHz;2G RAM;256GHD Disk 以上
开发与调试工具 vim,gcc,as,ld,edb,readelf,HexEdit
1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
hello.i 预处理之后文本文件
hello.s 编译之后的汇编文件
hello.o 汇编之后的可重定位目标执行
Hello 链接之后的可执行目标文件
hello2.c 测试程序代码
hello2 测试程序
Helloo.objdmp Hello.o的反汇编代码
helloo.elf Hello.o的ELF格式
hello.objdmp Hello的反汇编代码
hello.elf Hello的ELF格式
tmp.txt 存放临时数据
1.4 本章小结
Hello的P2P,020,实验环境,中间结果
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
程序设计语言的预处理的概念:在编译之前进行的处理。
C语言的预处理主要有三个方面的内容:
1.宏定义;
2.文件包含;
3.条件编译。 预处理命令以符号“#”开头。
预处理阶段:预处理器(cpp)根据字符#开头的命令,修改原始的c程序。得到另 一个c程序,通常以.i 作为文件扩展名
概念:预处理器 cpp 根据以字符#开头的命令(宏定义、条件编译),修改原始的C程序,将引用的所有库展开合并成为一个完整的文本文件。
主要功能如下:
1、将源文件中用#include形式声明的文件复制到新的程序中。比如hello.c第6-8行中的#include等命令告诉预处理器读取系统头文件 stdio.h unistd.h stdlib.h 的内容,并把它直接插入到程序文本中。
2、用实际值替换用#define 定义的字符串
3、根据#if 后面的条件决定需要编译的代码
2.2在Ubuntu下预处理的命令
2.3 Hello的预处理结果解析
先是stdio.h unistd.h stdlib.h的依次展开
3099行开始是hello.c
2.4 本章小结
预处理的定义与作用、并结合预处理之后的程序对预处理结果进行了解析。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
编译器(ccl)将文本文件hello.i翻译成文本文件hello.s,它包含一个汇编语言程序。
编译器将文本文件hello.i翻译成文本文件hello.s,它包含一个汇编语言程序。
这个过程称为编译,同时也是编译的作用。
编译器的构建流程主要分为3个步骤:
1.词法分析器,用于将字符串转化成内部的表示结构。
2.语法分析器,将词法分析得到的标记流(token)生成一棵语法树。
目标代码的生成,将语法树转化成目标代码。
3.2 在Ubuntu下编译的命令
3.3 Hello的编译结果解析
3.3.1汇编指令
.file 声明源文件
.text 以下是代码段
.section .rodata 以下是rodata节
.globl 声明一个全局变量
.type 用来指定是函数类型或是对象类型
.size 声明大小
.long.string 声明一个long、string类型
.align 声明对指令或者数据的存放地址进行对齐的方式
字符串
1.“Usage: Hello 学号 姓名!\n”,第一个printf传入的输出格式化参数,字符串被编码成UTF-8格式,一个汉字在utf-8编码中占三个字节,一个\代表一个字节。
2.“Hello %s %s\n”,第二个printf传入的输出格式化参数,其中后两个字符串都声明在了.rodata只读数据节。
3.3.1整数
int i:编译器将局部变量存储在寄存器或者栈空间中,在hello.s中编译器将i存储在栈上空间-4(%rbp)中,可以看出i占据了栈中的4B
int argc:作为第一个参数传入
立即数:其他整形数据的出现都是以立即数的形式出现的,直接硬编码在汇编代码中
数组
char argv[]:main函数执行时输入的命令行,argv作为存放char指针的数组同时是第二个参数传入。argv 单个元素char大小为 8B,argv 指针指向已经分配好的、一片存放着字符指针的连续空间,起始地址为argv,main函数中访问数组元素argv[1],argv[2]时,按照起始地址argv大小8B计算数据地址取数据,在hello.s中,使用两次(%rax)(两次rax分别为argv[1]和argv[2]的地址)取出其值。
3.3.3赋值
i=0:整型数据的赋值使用 mov 指令完成
3.3.4算数
i++,对计数器i自增,使用程序指令addl,后缀1代表操作数是一个4B大小的数据
leaq .LC0(%rip),%rdi,使用了加载有效地址指令leaq计算LC0的段地址%rip+.LC0 并传递给%rdi。
3.3.5compare
argc!=4:判断argc不等于3。hello.s中使用cmpl $4,-20(%rbp),计算argc-4然后设置条件码,为下一步je利用条件码进行跳转作准备。
i<10:判断i小于8。hello.s 中使用cmpl $7,-4(%rbp),计算i-7然后设置条件码,为下一步jle利用条件码进行跳转做准备。
3.3.6控制
if (argv!=4):当argv不等于4的时候执行程序段中的代码。if 判断编译器使用跳转指令实现,首先cmpl比较argv和4,使用je判断标志位,如果为0,说明argv==4, 则不执行if中的代码直接跳转到.L2,否则顺序执行下一条语句,即执行if中的代码
for(i=0;i<10;i++):使用计数变量i循环8次。先无条件跳转到位于循环体.L4之后的比较代码,使用cmpl比较,如果 i<=7,则跳入.L4 for 循环体执行,否则循环结束,顺序执行for之后的逻辑。
3.4 本章小结
hello.c和hello.s的映射关系,将C语言结构为低级汇编语言
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
汇编器(as)将.s汇编程序翻译成机器语言指令,把这些指令打包成可重定位目标程序的格式,并将结果保存在.o目标文件中,.o文件是一个二进制文件,它包含程序的指令编码。
4.2 在Ubuntu下汇编的命令
4.3 可重定位目标elf格式
1.ELF Header
以16B的序列Magic开始,Magic描述了生成该文件的系统的字的大小和字节顺序,ELF头剩下的部分包含帮助链接器语法分析和解释目标文件的信息,其中包括ELF头的大小、目标文件的类型、机器类型、字节头部表的文件偏移,以及节头部表中条目的大小和数量等信息。
2.Section Headers
节头部表,包含文件中出现的各个节的语义,包括节的类型、位置和大小等信息。
3.重定位节.rela.text ,一个.text节中位置的列表,包含.text节中需要进行重定位的信息,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。8条重定位信息分别是对.L0、puts函数、exit函数、.L1、 printf 函数、atoi、sleep函数、getchar 函数进行重定位声明。
4.4 Hello.o的结果解析
objdump和.s区别
1.分支转移:反汇编代码跳转指令的操作数使用的不是段名称而是地址,因为段名称只是在汇编语言中便于编写的助记符,所以在汇编成机器语言之后不存在,而是确定的地址。
2.函数调用:在.s文件中,函数调用之后是函数名称,而反汇编程序中,call的目标地址是当前下一条指令。因为hello.c中调用的函数都是共享库中的函数,最终需要通过动态链接器才能确定函数的运行时执行地址
3.全局变量访问:在.s文件中,访问rodata使用段名称+%rip,在反汇编代码中 0+%rip,因为rodata中数据地址在运行时确定,所以访问也需要重定位。所以在汇编成为机器语言时,将操作数设置为全0并添加重定位条目。
4.5 本章小结
从 hello.s到hello.o汇编,查看hello.o的elf格式,使用 objdump得到反汇编代码,与hello.s进行比较
(第4章1分)
第5章 链接
5.1 链接的概念与作用
链接是将各种代码和数据片段收集并组合成一个单一文件的过程,这个文件可被加载到内存并执行。链接可以执行于编译时,也就是在源代码被编译成机器代码时;也可以执行于加载时,也就是在程序被加载器加载到内存并执行时;甚至于运行时,也就是由应用程序来执行。链接是由叫做链接器的程序执行的。链接器使得分离编译成为可能。
5.2 在Ubuntu下链接的命令
5.3 可执行目标文件hello的格式
5.4 hello的虚拟地址空间
在0x400000~0x401000段中,程序被载入,自虚拟地址0x400000开始,自0x400fff 结束,这之间每个节的排列即开始结束同Section Headers的Address中声明。
查看 ELF 格式文件中的Program Headers,程序头表在执行的时候被使用,告诉链接器运行时加载的内容并提供动态链接的信息,提供各段在虚拟地址空间和物理地址空间的大小、位置、标志、访问权限和对齐方面的信息。
1.PHDR保存程序头表。
2.INTERP指定在程序已经从可执行文件映射到内存之后,必须调用的解释器(如动态链接器)。
3.LOAD表示一个需要从二进制文件映射到虚拟地址空间的段。其中保存了常量数据、程序的目标代码等。
4.DYNAMIC保存由动态链接器使用的信息。
5.NOTE保存辅助信息。
6.GNU_STACK权限标志,标志栈是否是可执行的。
7.GNU_RELRO指定在重定位结束之后那些内存区域是需要设置只读。
5.5 链接的重定位过程分析
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
5.6 hello的执行流程
ld-2.27.so!_dl_start
ld-2.27.so!_dl_init
hello!_start
libc-2.27.so!__libc_start_main
-libc-2.27.so!__cxa_atexit
-libc-2.27.so!__libc_csu_ini
hello!_init
libc-2.27.so!_setjmp
-libc-2.27.so!_sigsetjmp
–libc-2.27.so!__sigjmp_save
hello!main
[email protected]
[email protected]
*[email protected]
*[email protected]
*[email protected]
ld-2.27.so!_dl_runtime_resolve_xsave
-ld-2.27.so!_dl_fixup
–ld-2.27.so!_dl_lookup_symbol_x
libc-2.27.so!exit
5.7 Hello的动态链接分析
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
5.8 本章小结
(以下格式自行编排,编辑时删除)
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
进程是一个执行中的程序的实例,每一个进程都有它自己的地址空间,一般 情况下,包括文本区域、数据区域和堆栈。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储区着活动 过程调用的指令和本地变量。进程为用户提供了以下假象:我们的程序好像是系统中当前运行的唯一程序一样,我们的程序好像是独占的使用处理器和内存,处理器好像是无间断的执行我们程序中的指令,我们程序中的代码和数据好像是系统内存中唯一的对象。
6.2 简述壳Shell-bash的作用与处理流程
Shell 的作用:Shell 是一个用 C 语言编写的程序,他是用户使用 Linux 的 桥梁。 Shell 是指一种应用程序,Shell 应用程序提供了一个界面,用户通过这个 界面访问 操作系统内核的服务。
1.从终端读入输入的命令。
2.将输入字符串切分获得所有的参数
3.如果是内置命令则立即执行
4.否则调用相应的程序为其分配子进程并运行
5.shell 应该接受键盘输入信号,并对这些信号进行相应处理
6.3 Hello的fork进程创建过程
运行的终端程序对输入的命令行进行解析,因为hello不是一个内置的shell命令,解析之后终端程序判断./hello的语义为执行当前目录下的可执行目标文件 hello,之后终端程序首先会调用fork函数创建一个新的运行的子进程,新创建的子进程几乎但不完全与父进程相同,子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份副本,这就意味着,当父进程调用fork时,子进程可以读写父进程中打开的任何文件。父进程与子进程之间最大的区别在于它们拥有不同的PID。父进程与子进程是并发运行的独立进程,内核能够以任意方式交替执行它们的逻辑控制流的指令。在子进程执行期间,父进程默认选项是显示等待子进程的完成。
6.4 Hello的execve过程
fork之后,子进程调用execve函数(传入命令行参数)在当前进程的上下文中加载并运行一个新程序即hello程序,execve调用驻留在内存中的被称为启动加载器的操作系统代码来执行hello程序,加载器删除子进程现有的虚拟内存段,并创建一组新的代码、数据、堆和栈段。新的栈和堆段被初始化为零,通过将虚拟地址空间中的页映射到可执行文件的页大小的片,新的代码和数据段被初始化 为可执行文件中的内容。最后加载器设置PC指向_start地址,_start最终调用hello中的main函数。除了一些头部信息,在加载过程中没有任何从磁盘到内存的数据 复制。直到CPU引用一个被映射的虚拟页时才会进行复制,这时,操作系统利用 它的页面调度机制自动将页面从磁盘传送到内存。
6.5 Hello的进程执行
上下文信息:内核重新启动一个被抢占的进程所需要的状态,它由通用寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构等对象的值构成。
时间片:一个进程执行它的控制流的一部分的每一时间段叫做时间片。用户模式和内核模式:处理器通常使用一个寄存器提供两种模式的区分,该寄存器描述了进程当前享有的特权,当没有设置模式位时,进程就处于用户模式中,用户模式的进程不允许执行特权指令,也不允许直接引用地址空间中内核区 内的 代码和数据;设置模式位时,进程处于内核模式,该进程可以执行指令集中的任何命令,并且可以访问系统中的任何内存位置。
hello初始运行在用户模式,在hello进程调用sleep之后内核模式,内核处理休眠请求主动释放当前进程,并将hello进程从运行队列中移出加入等待队列,定时器开始计时,内核进行上下文切换将当前进程的控制权交给其他进程,当定时器到时时发送一个中断信号,此时进入内核状态执行中断处理,将hello进程从等待队列中移出重新加入到运行队列,成为就绪状态,hello进程继续进行自己的控制逻辑流。循环8次。当hello调用getchar,实际落脚到执行输入流是stdin的系统调用read,hello之前运行在用户模式,在进行read调用之后陷入内核,内核中的陷阱处理程序请求来自键盘缓冲区的DMA传输,并且安排在完成从键盘缓冲区到内存的数据传输后,中断处理器。此时进入内核模式,内核执行上下文切换,切换到其他进程。当完成键盘缓冲区到内存的数据传输时,引发一个中断信号,此时内核从其他进程进行上下文切换回hello进程。。
6.6 hello的异常与信号处理
(以下格式自行编排,编辑时删除)
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
6.7本章小结
(以下格式自行编排,编辑时删除)
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:程序代码经过编译后出现在汇编程序中地址,由选择符(在实模式下是描述符,在保护模式下是用来选择描述符的选择符)和偏移量(偏移部分)组成。
线性地址:逻辑地址经过段机制后转化为线性地址,为描述符:偏移量的组合 形式。分页机制中线性地址作为输入。
虚拟地址:线性地址。
物理地址:CPU 通过地址总线的寻址,找到真实的物理内存对应地址。CPU 对内存的访问是通过连接着CPU和北桥芯片的前端总线来完成的。在前端总线上 传输的内存地址都是物理内存地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
逻辑地址=段选择符+偏移量
每个段首地址存放在自己的段描述符中,所有段描述符存放在一个描述附表中(GDT和LDT),想找到某个段的描述符必须经过段选择符。段选择符由3个部分组成,从右向左依次是RPL、TI、index。当TI=0时,表示段描述符在GDT中,当TI=1时表示段描述符在LDT中。将描述符表看成是一个数组,每个元素都存放一个段描述符,那index就表示某个段描述符在数组中的索引。假设有一个段的段选择符TI=0,index=8。我们可以知道这个段的描述符是在GDT数组中,并且他的在数组中的索引是8。假设GDT的起始位置是0x00020000,而一个段描述符的大小是8个字节,可以计算出段描述符所在的地址:0x00020000+8index,从而我们就可以找到我们想要的段描述符,从而获取某个段的首地址,然后再将从段描述符中获取到的首地址与逻辑地址的偏移量相加就得到了线性地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
由逻辑地址得到的线性地址一共 32 位。前 10 位是页目录索引,中间 10 位是页表索引,最后 12 位是业内偏移量
由 CR3 寄存器得到「页目录基地址」,再得到「页目录项」
由「页目录项」得到「页表基地址」
由「页表基地址」得到「页表项」,最后得到物理地址。
7.4 TLB与四级页表支持下的VA到PA的变换
在Intel Core i7环境下研究VA到PA的地址翻译问题。
前提如下:虚拟地址空间48位,物理地址空间52位,页表大小4KB,4级页表。TLB4路16组相联。CR3 指向第一级页表的起始位置。
解析前提条件:由一个页表大小 4KB,一个PTE条目8B,共512个条目,使用9位二进制索引,一共4个页表共使用36位二进制索引,所以v*n共36位,因为 VA 48 位,所以VPO 12位;因为TLB共16组,所以TLBI需4位,因为v*n 36 位,所以TLBT 32位。CPU 产生虚拟地址VA,VA传送给MMU,MMU使用前 36位v*n作为TLBT(前32位)+TLBI(后 4 位)向TLB中匹配,如果命中,则 得到PPN(40bit)与VPO(12bit)组合成 PA(52bit)。
如果TLB中没有命中,MMU向页表中查询,CR3确定第一级页表的起始地址,v*n1(9bit)确定在第一级页表中的偏移量,查询出 PTE,如果在物理内存中且权限符合,确定第二级页表的起始地址,以此类推,最终在第四级页表中查询到PPN,与VPO 组合成 PA,并且向TLB中添加条目。
如果查询PTE的时候发现不在物理内存中,则引发缺页故障。如果发现权限不够,则引发段错误。
7.5 三级Cache支持下的物理内存访问
L1 Cache 8路64组相联,块大小64B。解析前提条件:因为共64组,所以需要6bit CI进行组寻址,共有8 路,块大小为64B所以需要6bit CO表示数据偏移位置。因为VA共 2bit,所以CT共40bit。 使用CI进行组索引,每组8路,对8路的块分别匹配CT(前40位)如果匹配成功 且块的valid 标志位为1,则命中(hit),根据数据偏移量 CO(后六位)取出数据返回。如果没有匹配成功或者匹配成功但是标志位是1,则不命中,向下一级缓存中查询数据(L2 Cache->L3 Cache->主存)。查询到数据之后,一种简单的放置策略如下:如果映射到的组内有空闲块,则直接放置,否则组内都是有效块, 产生冲突,则采用最近最少使用策略 LFU 进行替换。
7.6 hello进程fork时的内存映射
当fork函数被shell进程调用,内核为新进程创建各种数据结构,并分配给它一个唯一的 PID,为了给这个新进程创建虚拟内存,它创建了当前进程的mm_struct、区域结构和页表的原样副本。它将这两个进程的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。
7.7 hello进程execve时的内存映射
1.删除已存在的用户区域,删除当前进程虚拟地址的用户部分中的已存 在 的区域结构。
2.映射私有区域,为新程序的代码、数据、bss 和栈区域创建新的区域结 构, 所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射 hello 文件中 的.text 和.data 区,bss 区域是请求二进制零的,映射到匿名 文件,其大小包含 在 hello 中,栈和堆地址也是请求二进制零的,初始长度为零。
3.映射共享区域, hello 程序与共享对象 libc.so 链接,libc.so 是动态链 接 到这个程序中的,然后再映射到用户虚拟地址空间中的共享区域内。
4.设置程序计数器(PC),execve 做的最后一件事情就是设置当前进程 上 下文的程序计数器,使之指向代码区域的入口点。
7.8 缺页故障与缺页中断处理
缺页故障是一种常见的故障,当指令引用一个虚拟地址,在MMU中查找页表时发现与该地址相对应的物理地址不在内存中,因此必须从磁盘中取出的时候 就 会发生故障。
缺页中断处理:缺页处理程序是系统内核中的代码,选择一个牺牲页面,如果这个牺牲页面被修改过,那么就将它交换出去,换入新的页面并更新页表。当缺页处理程序返回时,CPU重新启动引起缺页的指令,这条指令再次发送VA到MMU,这次MMU就能正常翻译VA 了。
7.9动态存储分配管理
(以下格式自行编排,编辑时删除)
Printf会调用malloc,请简述动态内存管理的基本方法与策略。
7.10本章小结
hello的存储器地址空间、intel的段式管理、hello的页式管理,以intel Core7在指定环境下介绍了VA到PA的变换、物理内存访问,hello进程fork时的内存映射、execve时的内存映射、缺页故障与缺页中处理、动态存储分配管理。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:所有的IO设备都被模型化为文件,而所有的输入和输出都被当做对相应文件的读和写来执行,这种将设备优雅地映射为文件的方式,允许Linux内核引出一个简单低级的应用接口,称为Unix I/O。
8.2 简述Unix IO接口及其函数
Unix I/O 接口统一操作:
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)关闭文件,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中去。
Unix I/O 函数:
1)int open(char filename,int flags,mode_t mode),进程通过调用open函数来打开一个存在的文件或是创建一个新文件。open函数将filename转换为一个文件描述符,并且返回描述符数字,返回的描述符总是在进程中当前没有打开的最小描述符,flags参数指明了进程打算如何访问这个文件,mode 参数指定了新文件的访问权限位。
2)int close(fd),fd是需要关闭的文件的描述符,close返回操作结果。
3)ssize_t read(int fd,void *buf,size_t n),read函数从描述符为fd的当前文件位置赋值最多n个字节到内存位置buf。返回值-1表示一个错误,0表 示EOF,否则返回值表示的是实际传送的字节数量。
4)ssize_t wirte(int fd,const void *buf,size_t n),write函数从内存位置buf 复制至多n个字节到描述符为fd的当前文件位置。
8.3 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;
}
指针va_list
函数va_start()、va_arg()、va_end();
va_list args; //声明args,用于存储了所有的val1,val2,……
va_start(args, fmt);//得到第一个可变参数的地址
va_end(args);//清空args的指针
https://www.cnblogs.com/pianist/p/3315801.html
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
异步异常-键盘中断的处理:当用户按键时,键盘接口会得到一个代表该按键 的键盘扫描码,同时产生一个中断请求,中断请求抢占当前进程运行键盘中断子 程序,键盘中断子程序先从键盘接口取得该按键的扫描码,然后将该按键扫描码 转换成ASCII码,保存到系统的键盘缓冲区之中。getchar函数落实到底层调用了系统函数read,通过系统调用read读取存储在键盘缓冲区中的ASCII码直到读到回车符然后返回整个字串,getchar进行封装,大体逻辑是读取字符串的第一个字符然后返回。
8.5本章小结
IO 设备管理方法、Unix IO 接口及其函数、printf 函数和 getchar 函数。
(第8章1分)
结论
1.编写 建立hello.c
2.预处理 hello.c预处理为hello.i
3.编译 将hello.i编译成为汇编文件hello.s
4.汇编 将hello.s汇编为可重定位目标文件hello.o
5.链接 将hello.o与可重定位目标文件和动态链接库链接成为可执行目标程序 hello
6.运行 命令./hello 1180300328 zhangyifei
7.创建子进程 shell进程调用fork为其创建子进程
8.运行程序 shell调用execve,execve调用启动加载器,加映射虚拟内存,进入程序入口后程序载入物理内存,然后进入main函数。
9.执行指令 CPU为其分配时间片,在一个时间片中,hello享有CPU资源,顺序执行自己的控制逻辑流
10.访问内存 MMU将程序中使用的虚拟内存地址通过页表映射成物理地址。
11.动态申请内存 printf调用malloc向动态内存分配器申请堆中的内存。
12.信号 如果运行途中键入ctr-c ctr-z则调用shell的信号处理函数分别停止、挂起。
13.结束 shell父进程回收子进程,内核删除为这个进程创建的所有数据结构。
用计算机系统的语言,逐条总结hello所经历的过程。
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
(结论0分,缺失 -1分,根据内容酌情加分)
附件
hello 链接之后的可执行目标文件
helloo.elf Hello.o的ELF格式
hello.i 预处理之后文本文件
hello.o 汇编之后的可重定位目标执行
hello.objdmp Hello的反汇编代码
hello.s 编译之后的汇编文件
hello.elf Hello的ELF格式
helloo.objdmp Hello.o的反汇编代码
(附件0分,缺失 -1分)
参考文献
为完成本次大作业你翻阅的书籍与网站等
[1]ELF构造 https://www.cs.stevens.edu/~jschauma/631/elf.html
[2]虚拟地址、逻辑地址、线性地址、物理地址
https://blog.csdn.net/rabbit_in_android/article/details/49976101
[3]printf源代码的分析 https://blog.csdn.net/smallfish0315/article/details/46812081
[4]进程的睡眠、挂起和阻塞:https://www.zhihu.com/question/42962803
(参考文献0分,缺失 -1分)