计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 计算机
学 号 1190202219
班 级 1903003
学 生 何星阳
指 导 教 师 郑贵滨
计算机科学与技术学院
2021年6月
摘 要
摘要是论文内容的高度概括,应具有独立性和自含性,即不阅读论文的全文,就能获得必要的信息。摘要应包括本论文的目的、主要内容、方法、成果及其理论与实际意义。摘要中不宜使用公式、结构式、图表和非公知公用的符号与术语,不标注引用文献编号,同时避免将摘要写成目录式的内容介绍。
关键词:关键词1;关键词2;……;
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
本次大作业主要讲解一个hello.c程序如何从程序员编写完成后编译成为汇编文件,之后汇编成为可重定位目标文件,再与其他的库链接形成可执行文件的过程,以及改可执行文件在在机器中的存储,以及在运行hello的时候shell如何加载运行程序以及程序运行中以及结束时的处理
关键词1:编译,汇编,链接
关键词2:内存
关键词3:进程,信号
目 录
第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 -
P2P的: From Program to Process,O2O: From Zero-0 to Zero-0
首先hello.c程序员编写好之后,经过预处理生成hello.i,之后再经过编译生成hello.s汇编文件,之后汇编生成hello.o可重定位目标文件,hello.o与其他相关的可重定位目标文件以及库等链接生成可执行文件hello,
在linux终端运行hello时,shell会先fork一个子程序,用execve加载hello,并为该程序在内存中分配各种空间,首次运行hello会发生缺页,将其载入内存,之后的几次调用不会发生缺页,之后父程序等待hello运行结束即回收hello
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
软件环境:Ubuntu, VMware,Windows
硬件环境:AMD64 Family 23 Model 24 Stepping 1 AuthenticAMD ~2300 Mhz
开发与调试工具:gcc,objdump,readelf
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
hello.i 预处理后产生的文件
hello.s 编译后产生的文件
hello.o 汇编后产生的可重定位文件
hello 链接后产生的可执行文件
hello1 hello.o反汇编的可读文件
hello2 hello.o readelf产生的文件
hello3 hello readelf产生的文件
本章为hello.c到执行的过程,先预编译后编译然后汇编然后链接生成可执行文件,执行时父进程fork子程序execve加载该程序,结束之后被父程序或内核程序回收
(第1章0.5分)
概念: 在程序进行编译以前对hello.c程序进行的处理
作用:将源文件中以”include”格式包含的文件复制到编译的源文件中。用实际值替换用“#define”定义的字符串。根据“#if”后面的条件决定需要编译的代码。
gcc -E hello.c -o hello.i
应截图,展示预处理过程!
图-1
图-2
图-3
从大小可以看出hello.i比原来的hello.c大得多,再看hello.i里面的内容
图-4
一直到末尾这里才是原本hello.c的源代码,前面全是其他的函数,而且原来的
#include
#include
#include 已经被展开,hello.i里面已经没有了,前面全都是头文件里面的函数
程序到可执行文件第一步预处理时,寻找程序的#开头的语句进行展开处理,并将头文件里面的函数都展开到hello.i中,
(第2章0.5分)
注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序
概念:编译是将预处理后的文件进一步生成汇编语言,由编译器和编译程序完成
作用: 把用高级程序设计语言书写的源程序,翻译成等价的计算机汇编语言或机器语言书写的目标程序 编译程序以高级程序设计语言书写的源程序作为输入,而以汇编语言或机器语言表示的目标程序作为输出。并可以检查出程序是否错误
gcc -S hello.i -o hello.s
应截图,展示编译过程!
图-5
此部分是重点,说明编译器是怎么处理C语言的各个数据类型以及各类操作的。应分3.3.1~ 3.3.x等按照类型和操作进行分析,只要hello.s中出现的属于大作业PPT中P4给出的参考C数据与操作,都应解析。
3.31常量类型:
图-6
hello.c里的常量类型为字符串常量"Usage: Hello 学号 姓名!\n""Hello %s %s\n",一般保存在readonly数据段里
3.32变量类型:
全局:该文件里面对于全局变量sleepsecs,是存在数据段中的,
图-7
全局变量的信息存在开头,global表示全局的,.data表示存在.data段里面,size表示大小为4个字节,只要是赋初值(不为0)的全局变量都放在.data段里面,long表示将其赋值2(int)类型
局部:
图-8
该文件里面的局部变量为i,.l2里,将i初始化为0,之后跳到l3,即为循环判断,而i是存在-4(%rbp)中的,即存在栈面
函数参数:该文件函数参数为int argc,char *argv[],即命令行参数,
cmpl $3, -20(%rbp)
je .L2
这两句语句对应argc条件的判断,可知argc参数存在栈中
图-9
字符串数组char *argv[]的传递:
图中调用print之前,传入了三个参数,即rdx,rsi,rdi,可以rdi为输出函数的第一个参数,即输出格式,rdx为-32(%rbp)+16里的值,即第二个参数argv【2】,rsi对应的为-32(%rbp)+8值,即命令行第一个参数argv【1】。
3.33赋值操作:
该文件除了对全局变量sleepsecs赋值外,还给未初始化的局部变量i赋值
movl $0, -4(%rbp)
利用movl给i赋值为0,movl将原操作数的值送给目的操作数
3.3.3类型转换
int sleepsecs=2.5;为隐式类型转换,只有float和double类型才能接受2.5,int类型会舍去小数,赋给它2.
图 13
3.3.4算术操作
算术操作为i++,每次对其加一,当每次
cmpl $9, -4(%rbp)
jle .L4,
条件不满足时继续循环,并将i+1.
图-10
3.3.5关系操作:
图-11
!关系:第一个参数argc即存入在edi,赋给-20(%rbp)后,中比较argc和3的值,如果相等,跳到l2,即不进入if语句,如果不相等,则满足条件,进入if语句,输出提示信息
<关系:
cmpl $9, -4(%rbp)
jle .L4
同样比较i和9的值,小于等于则跳到l4继续循环,否则结束循环,可以看出,汇编语言判断两个数的关系,是采用cmp指令操作完成的。
3.3.6数组/指针操作
图-12
数组即为指针数组,char *argc[],指针即为其中的每个元素
改指针指向命令行参数,即字符串,argv[0]为执行文件路径,从argv[1]开始才为命令行参数,即argv[1]指向学号,argv[1]指向姓名
数组的访问通过首地址加上偏移量来完成,采用将地址加上立即数的方式进行访问数组,该立即数就是偏移量字节大小。由于数组元素为指针(8字节),访问argc[1]即首地址加上8,访问argv[2]即加上16
3.3.7控制转移
改文件中有两个控制转移
if(argc!=3)和for(i=0;i<10;i++)处
比较for(i=0;i<10;i++)时,
cmpl $9, -4(%rbp)
jle .L4
如果小于等于9则转移到l4,
图-13
可知l4为循环体,则条件满足跳入循环体,否则跳出循环
图-14
同样,比较(argc!=3),如果不相等继续执行下一条指令即为if语句的内容,否则跳到l2,l2为给i赋值为0,之后跳到L3,
cmpl $9, -4(%rbp)
jle .L4
即比较i和9的大小,若小于则继续循环,否则跳出循环
图-15
3.3.8函数操作
1.参数传递
函数的参数传递时通常是存在寄存器中,main函数有两个参数,就分别存在%edi和%esi中,printf三个参数分别存在rdi,rsi,rdx中
2.函数调用
采用call指令,call指令后面跟着函数的地址
比如call printf@PLT
Call指令会将返回地址即当前rip的值压入栈中,并将rip的值修改为call后面的地址
3.函数返回
图-16
函数返回时返回值存在eax中,汇编调用ret指令,将当前rsp指向的值即返回地址送给rip返回
编译阶段编译器将.i文件编译成.s文件。将高级语言编译成汇编语言,让不同高级语言变成统一的汇编语言,操作也变得更加简洁明了
(第3章2分)
注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。
概念:将汇编语言翻译成机器代码,由hello.s生成hello.o可重定位目标文件
作用:翻译成机器可直接识别的机器代码,便于后面的链接过程
gcc -no-pie -fno-PIC -c hello.c -o hello.o
应截图,展示汇编过程!
图-17
图-18
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
Elf头
图-19
首先是elf头,elf头包含了文件的各种信息,包括程序入口,节头大小,节头数量等
节头部表:
图-20
节头部表包含各个节的名称,大小,地址,偏移等
重定位信息:
图-21
重定位信息表里面存了所有需要重定位的符号名称及偏移量,其中R_X86_64_PC32表示相对寻址,R_X86_64_PLT32表示调用库函数
符号表:
图-22
符号表包含定义和引用的符号的信息,可分为三种不同符号:
由本模块定义并能被其他模块引用的全局符号,对应c语言非静态函数和全局变量
由其他模块定义并本模块引用,这些符号成为外部符号,对应其他模块定义的c语言非静态函数和全局变量
只被本模块定义和引用的局部符号,对应带static属性的c函数和全局变量,不能被其他模块调用
objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
图-23
图-24
对照:
1.汇编中mov等指令后都有表示操作数大小的后缀,比如q、l等,反汇编得到的代码中没有。
2.汇编代码中有很多以“.”开头的伪指令,反汇编得到的代码中没有。
3.对全局变量的访问,在汇编代码中是通过段名称+%rip,而在反汇编代码中是通过mov 0x0(%rip),%eax ,此处的0代表占位符,在链接时需要重定位。
4.汇编代码中调用函数是用“call 函数名”来表示,而反汇编代码中用“call 数字”表示,且相对应的机器代码中有PC相对引用的占位符,有对可重定位的标识
汇编代码中跳转“jmp”+伪指令的形式,在反汇编代码中直接为“jmp”+函数地址的形式。
机器语言的构成:机器代码是由01组成,一条语句通过操作码+操作数来完成,
一条汇编指令对应一条机器代码,汇编指令的mov,call等操作在机器码中有对应的操作码
分支跳转:汇编代码中有很多以“.”开头的伪指令,反汇编得到的代码中没有,反汇编得到的代码中跳转会以相对寻址的方式跳转到之前或之后的语句
函数调用:汇编代码中调用函数是用“call 函数名”来表示,而反汇编代码中用“call 数字”表示,且相对应的机器代码中有PC相对引用的占位符,有对可重定位的标识
汇编将hello.s文件生成hello.o文件,文件中包含了各种信息,包括为链接做准备的一些信息,文件格式为elf可重定位文件,文件包含elf头,节头部表,重定位信息以及符号表
(第4章1分)
注意:这儿的链接是指从 hello.o 到hello生成过程。
概念:链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载(复制)到内存并执行
作用:链接使得分离编译成为可能,不用将一个大型的应用程序组织成为一个巨大的源文件,而是可以把它分解成更小,更好管理的模块,可以独立地修改和编译这些模块,改变模块时,只需重新编译它,并重新链接应用,不必重新编译其他文件
使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件
ld -o hello hello.o -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/x86_64-linux-gnu/libc.so /usr/lib/x86_64-linux-gnu/crtn.o
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
Hello的elf格式由elf头,节头部表,程序头,重定位信息,以及符号表组成,
图-25
图-26
节头部表中列出了各段的信息,包括大小,地址,偏移量等
图-27
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
从edb的datadump可以看到各段的地址空间,比如elf文件中存的。.text代码段的起始地址为0x400500,相对于hello文件的起始地址0x400000偏移量为0x500,与elf文件中记录的偏移量和地址一致
图-28
链接是将不同的文件连接在一起,由于hello生成的过程不仅连接了hello.o,还链接了/lib64/ld-linux-x86-64.so.2,crt1.o、crti.o、crtn.o,所以将会把这些.o文件的每个节与hello.o的节合并。合并后,链接器会通过合并后的.text段和.data段等来重定位对应的重定位条目。可以看出hello.o只是生成hello的一部分
图-29
重定位时,已知put输出函数的地址后,通过hello.o定义重定位条目偏移量
00000000001d 000c00000004 R_X86_64_PLT32 0000000000000000 puts – 4
找到对应的需要重定位的位置,之后通过相对寻址计算出call后面的数值,之后填入00处。
(以下格式自行编排,编辑时删除)
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
通过edb查看调用与跳转的子程序名:
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_init
hello!_init
libc-2.27.so!_setjmp
-libc-2.27.so!_sigsetjmp
–libc-2.27.so!__sigjmp_save
hello!main
hello!puts@plt
hello!exit@plt
–ld-2.27.so!_dl_lookup_symbol_x
libc-2.27.so!exit
(以下格式自行编排,编辑时删除)
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
动态链接库函数时,是通过GOT表来链接的,并且GOT[2]才是真正入口
由readelf查看hello3查看
] .got.plt PROGBITS 0000000000601000 00001000
0000000000000040 0000000000000008 WA 0 0 8
改节即为GOT表的位置,为0x601000,调用dl_init前查看其内容
图-30
可直GOT[2]处为0,也就是还没有将真正地址写入,
调用dl_init后,查看GOT[2]的内容,为0x7fa06f99a680
图-31
查看真正的put函数地址,也是0x7fa06f99a680
,与GOT表一致,可直执行dl_init后GOT表的动态链接函数入口改写成真正入口
图-32
而第一次运行puts函数时,第一条语句是调用GOT[3]跳到下一条语句
图-33
Puts函数的plt第二条语句正好为0x4004b600
链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,链接时,通过链接器将各种目标文件以及库连接起来并填写重定位条目,在连接时,为了减少重复,利用动态链接,运行时加载改库函数,找到库函数地址并载入执行
(以下格式自行编排,编辑时删除)
第6章 hello进程管理
概念:进程就是一个执行中程序的实列
作用:1.一个独立的逻辑控制流,它提供一个假象,好像我们程序独占地使用处理器。
2.一个私有的地址空间,它提供一个假象,好像我们的程序独占地使用内存系统。
shell是一个交互型应用程序,代表用户运行其他程序其基本功能是解释并运行用户的指令,处理过程:
1.终端进程读取用户由键盘输入的命令行。
2.分析命令行字符串,获取命令行参数,并构造传递给execve的argv向量。
3.检查第一个(首个,第0个)命令行参数是否是一个内置的shell命令。
4.如果不是内部命令,调用fork()创建新进程/子进程。
5.在子进程中,用步骤2获取的参数,调用execve()执行指定程序。
6.如果用户没要求后台运行(命令末尾没有&号)否则shell使用waitpid(或wait…)等待作业终止后返回
7.如果用户要求后台运行(命令末尾有&号),则shell返回。
用户输入./hello 学号 姓名时,shell会判断命令,由于不是内置命令,shell会认为一个程序,在相应目录中寻找,找到后fork一个子进程,fork函数有两个返回值,子进程返回值为0,父进程返回子进程的id,子进程拥有与父进程相同的副本,子进程得到与父进程用户级虚拟地址空间相同(但是独立的)一份副本,包括代码和数据段、堆、共享库以及用户栈。子进程还获得与父进程任何打开文件描述符相同的副本,这就意味着当父进程调用fork时,子进程可以读写父进程打开的任何文件。只是子进程的pid和父进程的pid不同,而且子进程和父进程是独立的
execve函数加载并运行可执行目标文件hello(filename),且带参数列表argv和环境变量列表envp。只有当出现错误时,例如找不到hello,execve才会返回到调用程序。所以,与fork一次调用返回两次不同,execve调用一次并从不返回。
(以下格式自行编排,编辑时删除)
结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。
上下文信息:操作系统内核使用一种称为上下文切换的较高层形式的一场控制流来实现多任务。上下文就是内核重新启动一个被抢占的进程所需的状态。它由一些对象的值组成,这些对象包括通用目的寄存器,浮点寄存器,程序计数器,用户栈,状态寄存器,内核栈和各种内核数据结构。
调度:在进程执行的某些时刻,内核可以决定当前进程,并重新开始一个先前被抢占了的进程,这种决策叫做调度。
进程时间片:一个进程执行它的控制流的一部分的每一时间段叫做时间片
图-34
假如进程a初始运行在用户模式中,a执行某些函数,比如从磁盘读取数据,由于这个过程比较长,系统不会一直等待其读取完,而是会系统调用内核模式执行指令切换到进程b并运行在用户模式,当磁盘读取完成发出一个信号后,进程b已经运行足够长时间,系统调用内核模式之后切换到a继续执行
(以下格式自行编排,编辑时删除)
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
异常可以分为四类:中断,陷阱,故障和终止
中断 来自I/O设备的信号 异步 总是返回到下一条指令
陷阱 有意的异常 同步 总是返回到下一条指令
故障 潜在可恢复的错误 同步 可能返回到当前指令
终止 不可恢复的错误 同步 不会返回
图-35
不会有影响,只是会输出在屏幕上
图-36
程序会接收到终止信号,直接退出
3,
Ctrl-z,程序会接收到停止信号停止
图-37
图-38
图-39
按ctrl-z程序停止后,ps会列出进程,jobs命令显示已启动的作业,pstree用于查看进程树之间的关系,即哪个进程是父进程,哪个是子进程,可以清楚的看出来是谁创建了谁
图-40
Fg将程序调到前台继续运行,kill将程序杀死
一个系统要能够有效的运行,它必须建立一个简单但有效的模型。计算机系统为了解决这个问题,提供了两个抽象:进程让每个程序都以为只有它自己在运行,虚拟内存让每个程序都以为它自己在独占整个内存空间。这两个抽象使得计算机系统能够对每个程序都够以一致的方式去管理。多任务就通过进程之间快速的切换来实现,程序之间的影响就通过进程之间的通信——信号来实现。
(第6章1分)
结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。
逻辑地址 :是指由程式产生的和段相关的偏移地址部分,段地址
线性地址 :是逻辑地址到物理地址变换之间的中间层,线性地址即就是书中所说的虚拟地址。
物理地址 :是指出目前CPU外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果地址。
虚拟地址 :N=2^n地址的地址空间,是虚拟的
(以下格式自行编排,编辑时删除)
段选择符:
程序最开始有一个逻辑地址,使用其段标识符(也即段选择符)的Index字段去索引段描述符表,若TI=0,索引全局段描述符表,TI=1,索引局部段描述符表,表的地址在相应的寄存器中。通过Index字段和段描述符表的位置能找到某项具体的段描述符。将段描述符中描述的段的地址和逻辑地址中的偏移字段合并即得到了线性地址。
(以下格式自行编排,编辑时删除)
页式管理是指每个进程都有自己的页表,寄存器cr3存有页表的地址,当有一个虚拟地址时,mmu利用寄存器cr3存的页表地址找到页表,虚拟地址分为VPN虚拟页号,和vpo虚拟偏移地址,mmu利用vpn在页表找到对应的条目取出物理页号ppn,第一次取的时候发生缺页并将其从磁盘载入到内存,取出ppn后与vpo拼出物理地址
图-41
首先TLB是,MMU中一个小的具有高相联度的集合 ,实现虚拟页号向物理页号的映射 ,页数很少的页表可以完全放在TLB中
访问TLB时0,与访问cashe类似,如果TLB组数S=2^s,vpn前s位为组索引,其他位均为标记位,若TLB命中,则可以直接送个mmu,若不命中,需要到相应的物理内存中的页表取出条目,若物理内存也没有,则缺页,从磁盘载入内存
图-42
多级页表:多级页表有多级,一级页表只有一个,地址存在寄存器cr3中,一级页表每个条目存的不是物理页号,而是下一级页表对应的物理地址,这样依次向下,级数越高,页表数量越多,多级页表访问时,将vpn分为多段,假如一级页表有2^k个条目,则最高的k位用来对应一级页表的条目访问二级页表,这样依次到最后一级页表存的为物理页号,找到物理页号后与vpo拼接得到物理地址
图-43
图-44
若E等于1,为直接映射,S=1,为全映射,对应的物理地址取来后,若S=2^s,E=2^e,B=2^b,则低b位为块偏移,之后s位为组索引,其他高位的部分为tag位,当mmu算出物理地址后,发给L1,通过组索引找到组,之后匹配tag,若成功而且有效位为1,则cahse命中,否则,不命中,需要到下一级cashe中取数据,依次进行,取得数据后,向上级传送,则需要相应的替换驱逐策略,
当fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID,为了给这个新进程创建虚拟内存,他创建了当前进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制,当fork函数在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,因此,也就为每个进程保持了私有地址空间的概念。
图-45
上图为进程的地址空间,当执行int execve(char *filename, char *argv[], char *envp[])时,覆盖当前进程的代码、数据、栈 ,保留有相同的PID,继承已打开的文件描述符和信号上下文,并将数据段,代码段,堆设置为私有的,都是写时私有复制的,对于共享库函数是共享的,这些函数都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内。
图-46
缺页:引用虚拟内存中的字,不在物理内 存中 (DRAM 缓存不命中),当页表条目的有效位为0时,表示缺页
处理:当物理内存可存的也数未满时,直接从磁盘载入内存,若满了,选择一个牺牲页替换,载入后,将有效位置为1,重新执行一遍刚才的导致缺页程序。
Printf会调用malloc,请简述动态内存管理的基本方法与策略。
动态内存分配器维护着一个进程的虚拟内存区域,称为堆:
分配器将堆视为一组不同大小的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应程序使用。空闲块可用来分配。
分配器有两种基本风格。两种风格都要求应用显式地分配块。它们的不同之处在于由哪个实体来负责释放已分配的块。
1.显式分配器,要求应用显式地释放任何已分配的块。
2. 隐式分配器:要求分配器检测一个已分配块何时不再被程序所使用,那么就释放这个块。隐式分配器也叫做垃圾收集器。
显示分配:
隐式分配链表:
图-47
如果块是对齐的,那么块大小的一些低位值总是0 ,不存储这些0位,将它作为已分配/未分配的标志 , 读大小字段时,必须将其屏蔽掉,由于分配给用户的地址是8或16的倍数,则块大小都是8(或16)的倍数
分配方式:搜索一个合适的空闲块分配,分为首次适配,即找到第一个满足条件的空闲块,下一次适配,即从上次适配的位置开始搜索,最佳适配,即检查每个空闲块,找到最小的满足条件的空闲块
分割空闲块:一旦分配器找到一个匹配的空闲块,它就必须做另一个策略决定,那就是分配这个空闲块中多少空间。一个选择是用整个空闲块。虽然这种方式简单而快捷,但是主要的缺点就是会产生内部碎片。
释放空闲块:释放时,需要合并当前块前后的空闲块,对于边界标记的合并,通过上一块的脚部和下一块的头部可以判断是否被分配,若未被分配,则将其合并
显示分配链表:一种更好的方式是将空闲块组织为某种形式的显式数据结构。因为根据定义,程序不需要一个空闲块的主体,所以实现这个数据结构的指针可以存放在这些空闲块的主体里面。例如,堆可以组织成一个双向空闲链表,在每个空闲块中,都包含一个pred和succ指针
维护:可以通过后进先出将释放的空闲块放到头部,或地址顺序维护将释放的空闲块按地址大小循序插入
隐式分配:
在释放块是,使用隐式回收,带有垃圾收集器,即找到所有已经分配但不可达的块,将其回收
虚拟内存式对主存的一个抽象,支持虚拟内存的处理器通过使用一种叫做虚拟寻址的简介形式来引用主存,地址翻译的过程必须和系统中所有的硬件缓存的操作集成在一起,大多数页表条目位于L1高速缓存中,但是TLB会消除开销。大多数程序依赖于动态内存分配器,分配器分为显示分配器和隐式分配器
(第7章 2分)
设备的模型化:文件
设备管理:unix io接口
一个Linux文件就是一个m个字节的序列:
B0,B1,B2…Bk…Bm-1
所有的I/O设备都被模型化为文件,而所有的输入和输出都被当作相应文件的读和写来执行。这种将设备优雅地映射为文件的方式,允许Linux内核引出一个简单,低级的应用接口,称为Unix I/O,这使得所有的输入和输出都能以一种统一且一致的方式来执行
打开文件:通知内核,你准备好访问该文件
int open(char *filename, int flags, mode_t mode);
关闭文件通知内核不再访问文件
int close(int fd) ;
读文件从当前文件位置复制字节到内存位置,然后更新文件位置
ssize_t read(int fd, void *buf, size_t n);
返回值表示的是实际传送的字节数量
写文件从内存复制字节到当前文件位置,然后更新文件位置
ssize_t write(int fd, void *buf, size_t n);
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和write:
1,
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。用格式字符串对个数变化的参数进行格式化,产生格式化输出
2,
对于write函数:
write:
mov eax, _NR_write
mov ebx, [esp + 4]
mov ecx, [esp + 8]
int INT_VECTOR_SYS_CALL
这里是给几个寄存器传递了几个参数
对于最后一句种的sys_call:
sys_call:
;ecx中是要打印出的元素个数
;ebx中的是要打印的buf字符数组中的第一个元素
;这个函数的功能就是不断的打印出字符,直到遇到:'\0'
;[gs:edi]对应的是0x80000h:0采用直接写显存的方法显示字符串
xor si,si
mov ah,0Fh
mov al,[ebx+si]
cmp al,'\0'
je .end
mov [gs:edi],ax
inc si
loop:
sys_call
.end:
ret
sys_call即通过传过来的参数显示格式化了的字符串
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
程序调用getchar(),时,程序等待用户输入,输入的字符会被放在缓冲区,当按下回车键后,getchar()调用read函数读取字符
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
Linux提供了少量的基于unixI/O模型的系统级函数,它们允许应用程序打开,关闭,读和写文件,提取文件的元数据,,以及执行I/O的重定向
(第8章1分)
用计算机系统的语言,逐条总结hello所经历的过程。
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
(结论0分,缺失 -1分,根据内容酌情加分)
编写源程序:
程序员通过编辑器编写hello.c源文件,改文件为ASCII文件
预处理: cpp为预处理器,通过展开带#的库含文件,并加入到文件中,解析宏定义,生成hello.i,该文件也为ASCII 文件
编译: ccl为编译器,编译器通过编译将高级程序语言翻译成汇编语言,处理常量表达式等,生成hello.s文件,该文件也为ASCII文件
汇编: As为汇编器,生成可重定位目标文件hello.i,该文件为二级制文件
链接: Hello.o与其他可重定位文件以及动态库链接,重定位,符号解析,生成可执行文件hello,该文件也为二进制文件
加载运行:在终端shell中输入./hello 1173710105 曾钰城。shell 调用 execve, execve通过调用某个驻留在存储器中称为加载器的操作系统代码来运行hello,加载器将可执行目标文件中的代码和数据从磁盘中复制到内存中,然后通过跳转到程序的第一条指令或者入口点来运行该程序。
可执行文件生成后,在shell中输入./hello,运行程序,shell解析命令并fork子进程,execve加载并运行hello,运行中通过mmu翻译虚拟地址到物理地址,第一次运行前,程序被加载到磁盘文件中,第一次运行时,发送缺页并调用处理程序载入内存,重新执行
结束:hello运行结束,从return返回,父进程收到信号并回收,删除hello进程内存区域
列出所有的中间产物的文件名,并予以说明起作用。
(附件0分,缺失 -1分)
hello.i 预处理后产生的文件
hello.s 编译后产生的文件
hello.o 汇编后产生的可重定位文件
hello 链接后产生的可执行文件
hello1 hello.o反汇编的可读文件
hello2 hello.o readelf产生的文件
hello3 hello readelf产生的文件
为完成本次大作业你翻阅的书籍与网站等
[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.
(参考文献0分,缺失 -1分)