火影 发表于 2024-9-5 03:56:38

哈工大CSAPP大作业:程序人生


计算机系统

大作业


题     目  程序人生-Hello’s P2P 
专       业  2+X人工智能                     
学     号  2021111342                      
班   级  21WL021                      
学       生   刘文青               
指 导 教 师    史先俊                 

计算机科学与技能学院
2023年4月
摘  要
HelloWorld是每个程序员的出发点,本文通过跟踪一个名为Hello的程序的生命周期,详细描述了它从最初的C语言源代码开始,逐渐经历预处置惩罚、汇编、链接等一系列过程,终极发展为可实行文件。这也标志着Hello将迎来一个新的阶段。
在这个新的阶段中,Hello将与操作系统举行交互。操作系统就像Hello的伯乐一样,为它开辟进程,提供虚拟内存和独立的地点空间,为它分配时间片和控制流,以便让它在操作系统上自由运行。终极,随着进程的结束,Hello的辉煌生命周期也告一段落。
本文以一个名为hello.c的源代码为出发点,追踪Hello的发展历程,并详细阐述了每一步变革的过程和意义

关键词:预处置惩罚,编译,汇编,链接,进程管理,虚拟地点,存储管理,IO操作                            









目  录
第1章 概述
1.1 Hello简介
1.2 环境与工具
1.3 中心效果
1.4 本章小结
第2章 预处置惩罚
2.1 预处置惩罚的概念与作用
2.2在Ubuntu下预处置惩罚的命令
2.3 Hello的预处置惩罚效果分析
2.4 本章小结
第3章 编译
3.1 编译的概念与作用
3.2 在Ubuntu下编译的命令
3.3 Hello的编译效果分析
3.4 本章小结
第4章 汇编
4.1 汇编的概念与作用
4.2 在Ubuntu下汇编的命令
4.3 可重定位目的elf格式
4.4 Hello.o的效果分析
4.5 本章小结
第5章 链接
5.1 链接的概念与作用
5.2 在Ubuntu下链接的命令
5.3 可实行目的文件hello的格式
5.4 hello的虚拟地点空间
5.5 链接的重定位过程分析
5.6 hello的实行流程
5.7 Hello的动态链接分析
5.8 本章小结
第6章 hello进程管理
6.1 进程的概念与作用
6.2 简述壳Shell-bash的作用与处置惩罚流程
6.3 Hello的fork进程创建过程
6.4 Hello的execve过程
6.5 Hello的进程实行
6.6 hello的非常与信号处置惩罚
6.7本章小结
第7章 hello的存储管理
7.1 hello的存储器地点空间
7.2 Intel逻辑地点到线性地点的变换-段式管理
7.3 Hello的线性地点到物理地点的变换-页式管理
7.4 TLB与四级页表支持下的VA到PA的变换
7.5 三级Cache支持下的物理内存访问
7.6 hello进程fork时的内存映射
7.7 hello进程execve时的内存映射
7.8 缺页故障与缺页中断处置惩罚
7.9动态存储分配管理
7.10本章小结
第8章 hello的IO管理
8.1 Linux的IO装备管理方法
8.2 简述Unix IO接口及其函数
8.3 printf的实现分析
8.4 getchar的实现分析
8.5本章小结
结论
附件
参考文献


第1章 概述

1.1 Hello简介

HelloWorld想必是每一个程序员的发蒙小怪,当我们打开VSCode,输入一行行代码,按下编译运行后,我们便完成了第一次代码编写,向世界问好。这个看似非常简单的程序实则是早先第一个实现的P2P。
P2P:全称为From Program to Progress,从项程序到进程。Hello的P2P是指预处置惩罚(Preprocessing)、编译(Comcompling)和链接(Linking)的过程。详细来说,P2P的整个过程如下:
①预处置惩罚(Preprocessing):在这个阶段,Hello的源代码会被预处置惩罚器处置惩罚。预处置惩罚器会先处置惩罚预处置惩罚指令,例如#include和#define,然后将处置惩罚后的代码输出到一个.i文件中。这个.i文件是Hello的下一个阶段的输入文件。
②编译(Comcompling):在这个阶段,Hello的.i文件会被编译器处置惩罚。编译器会将.i文件中的代码翻译成汇编语言,生成一个.s汇编文件。这个.s汇编文件是下一个阶段的输入文件。
③汇编(Assembly)是编译器将预处置惩罚后的代码翻译成机器码的过程。详细来说,编译器会将.s汇编文件中的汇编代码翻译成机器指令,生成目的文件。目的文件包罗了可实行代码和数据,但还没有被链接器链接成终极的可实行文件。
④链接(Linking):在这个阶段,Hello的.s汇编文件会被链接器处置惩罚。链接器会将汇编代码转化为可实行文件,并将代码中使用的函数和库链接到可实行文件中。终极生成的可实行文件就是Hello的运行文件。
020:由shell加载hello。映射虚拟内存,进入程序,载入物理内存,实行main函数。程序结束后,shell回收hello进程,删除hello的进程占用。
1.2 环境与工具

1.2.1 硬件环境
处置惩罚器 11th Gen Intel(R) Core(TM) i5-11400H @ 2.70GHz   2.69 GHz机带 RAM 16.0 GB (15.8 GB 可用)
系统范例:64 位操作系统, 基于 x64 的处置惩罚器
GPU:NVIDIA GeForce RTX 3050 Laptop GPU
1.2.2 软件环境
软件环境:Windows 10、Vmware16上的Ubuntu(64位)虚拟机
1.2.3 开发与调试工具
开发工具:vim文本编辑器、Visual Studio、EDB、Vscode、Gcc编译器
调试工具:GDB、EDB
1.3 中心效果

文件名称
                作用
hello.c
储存hello程序源代码
hello.i
源代码经过预处置惩罚产生的文件(包罗头文件等工作)
hello.s
hello程序对应的汇编语言文件
hello.o
可重定位目的文件
hello_o_s.txt
hello.o的反汇编语言文件
hello_o_elf.txt
hello.o的ELF文件格式
hello
二进制可实行文件
hello_elf.txt
可实行文件(hello)的ELF文件格式
hello_s.txt
可实行文件(hello)的汇编语言文件
1.4 本章小结

本章扼要介绍了hello的p2p以及020的过程,并扼要的介绍了hello.c文本源文件到hello可实行目的文件的变革。并枚举了实行举行过程中的硬件环境,软件环境,开发工具,调试工具。
第2章 预处置惩罚

2.1 预处置惩罚的概念与作用

预处置惩罚概念:处置惩罚c源文件中的预处置惩罚程序和注释。
预处置惩罚作用:预处置惩罚器(cpp)根据以字符#开头的预处置惩罚命令处置惩罚c源文件,修改原始的c源文件,预处置惩罚过程中,预处置惩罚器会分析预处置惩罚指令,依据#include举行库文件的插入,依据#define举行字符串的更换等,并且同时去除c源代码中的注释
2.2在Ubuntu下预处置惩罚的命令

Linux中使用如下指令举行预处置惩罚操作

图1 预处置惩罚操作指令


图2 hello.i文件生成

2.3 Hello的预处置惩罚效果分析

在Linux中我们用文本编辑器打开.i文件,程序如下图所示:

图3 hello.i文件内容展示
    我们可以发现.i文件比源程序多了很多内容,也少了一些内容。多的内容包罗对源文件中定义的宏举行了展开,将头文件中的内容包罗到这个文件中,还定义了一些函数,以及一些布局体范例的声明;同时预处置惩罚操作去掉了原来的注释。
2.4 本章小结

本章实行了预处置惩罚指令,看了预处置惩罚后的文本文件的内容,看了预处置惩罚的方式与作用。

第3章 编译

3.1 编译的概念与作用

编译的概念:
编译是利用编译程序从源语言编写的源程序产生目的程序的过程,也是用编译程序产生目的程序的动作。也就是说编译器会将通过预处置惩罚产生的.i文件翻译成一个后缀为.s的汇编语言文件,编译就是从高级程序计划语言到机器能明确的汇编语言的转换过程。
编译的功能:
着实从概念中也可以直接提炼出,编译的功能就是产生汇编语言文件,并交给机器。除此之外,编译器还有一些其他功能,例如语法查抄等。
3.2 在Ubuntu下编译的命令

Linux中使用如下指令举行编译操作

图4 hello.s编译指令





3.3 Hello的编译效果分析


图5 hello.s文件
3.3.1 数据
    字符串:hello.c有两个字符串,字符串数据是以字符数组的情势存储在只读数据区的,如图7,这两个字符串在访问时都是通过其首地点访问的,如图6。string1中的汉字是以utf-8格式存的,超过了char表示的ASCII码范围,以十六进制的格式体现,因此是一堆大正数。

图6 字符串表示

图7 字符串访问指令
3.3.2 数组及操作
    hello.c⾥⾯有⼀个参数的char型指针数组,数组⾸地点放在栈里,指针数组
⾥的每个元素指针内里放的是每个参数字符串地点地点。


图8 数组
图9为栈中-32(%rbp)处的内容,放的是指针数组首地点。可以看出整个指针
数组也在栈里,


图9 数组指针首地点
每次引用数组元素时,采取首地点加偏移的⽅式,先从-32(%rbp)处得到指针数组首地点,再加上偏移找到对应的指针,再将指针里的参数字符串的地点转达给寄存器,调用puts或printf函数打印字符串。

图10 参数字符串地点转达
3.2.3 局部变量
    for循环里声明了⼀个局部变量i,编译器进⾏编译的时候将局部变量i放在栈中,即-4(%rbp)处。


图11 局部变量压栈
3.3.4 常量
    ⽤来做比较运算的常量4和5(比较时用的是4)等以立即数的情势存在。


图12 立即数用于跳转指令
3.3.5 赋值语句
    在for循环中对i赋初值,直接用数据传输指令将立即数0传送给栈中-4(%rbp)
处,如图11所示。

3.3.6 范例转换语句
    atoi(argv),显式地将输⼊的最后一个参数由字符串范例转换为整数范例,
作为sleep的参数。

图13 范例转换语句
3.3.7 算术操作
i++涉及到加法运算,⽤addl命令实现。

图14 加法操作指令
3.3.8 关系操作及控制转移
控制转移往往是和关系操作结合在一起的,关系操作在比较中设置了条件码,控制转移根据条件码判断是否要举行跳转。



图15 关系操作及跳转指令
这里的if语句用来判断argc与4是不是相称,如果相称则跳转到.L2实行,如果不相称则顺序实行下一条指令。
雷同的还有for循环,也被翻译成比较加跳转。


图16 for跳转指令源程序和汇编指令对应
3.3.9 循环语句

图17 循环语句
如3.3.8中所示,for循环语句也被翻译成比较加跳转,与if等跳转不同的是,条件满足时总是向之前的命令跳转,重复实行。
3.3.10 函数操作
主函数main,调用的函数有printf,exit,sleeep,getchar,atoi,全部都是共享库中的函数,命令只有直接call,如图18示例所示,返回值放在%rax内里。


图18 函数调用语句
3.4 本章小结

本章介绍了编译阶段编译器是如何将c程序翻译成汇编语⾔程序的,并举行了编译命令的演示,以及编译器是怎么处置惩罚C语言的各个数据范例以及各类操作的。


第4章 汇编

4.1 汇编的概念与作用

汇编的概念:汇编器将.s文本文件翻译成机器语言。

汇编的作用:汇编器将.s中的汇编语言翻译成计算性能处置惩罚的机器指令语言,并输出可重定位文件(二进制文件)。

4.2 在Ubuntu下汇编的命令


图19 hello.o文件
4.3 可重定位目的elf格式

4.3.1 ELF头
    .o文件为目的文件,相称于Windows中的.obj后缀文件,因此直接使用Vim大概其他文本编辑器查看会出现一大堆乱码。那么我们选择查看可重定位目的文件的elf情势,使用命令readelf -h hello.o查看ELF头,效果如图20所示。
ELF头以一个16字节的序列(Magic,魔数)开始,这个序列描述了生成文件的系统的字的巨细和字节顺序。ELF头剩下部分的信息包罗资助毗连器语法分析和表明目的文件的信息。其中包罗ELF头的巨细、目的文件的范例和机器范例等。例如上图中,Data表示了系统采取小端法,文件范例Type为REL(可重定位文件),节头数量Number of section headers为13个等信息。

图20 hello.o的ELF文件
4.3.2 Section头
使用命令readelf -S hello.o查看节头,效果如图21。

图21 Section头信息
夹在ELF头和节头部表之间的都为节,包罗了文件中出现的各个节的语义,包罗节的范例、位置和巨细等信息。各部分寄义如下:
名称
包罗内容寄义
.text
已编译程序的机器代码
.rodata
只读数据
.data
已初始化的全局和静态C变量
.bss
未初始化的全局和静态C变量,以及全部被初始化为0的全局或静态变量
.symtab
一个符号表,存放一些在程序中定义和引用的函数和全局变量的信息
.rel.text
一个.text节中位置的列表
.rel.data
被模块引用或定义的全部全局变量的重定位信息
.debug
一个调试符号表
.line
原始C源程序中的行号和.text节中机器指令之间的映射
.strtab
一个字符串表(包罗.symtab和.debug节中的符号表)
4.3.3 符号表
使用命令readelf -s hello.o查看节头,效果如图22

图22 符号表
这其中,Num为某个符号的编号,Name是符号的名称。Size表示他是一个位于.text节中偏移量为0处的146字节函数。Bind表示这个符号是当地的还是全局的,由上图可知ABS hello.c函数名称这个符号变量是当地的。
4.3.4 可重定位段信息
使用readelf -r hello.o查看可重定位段信息,效果如下

图23 可重定位节
offset是需要被修改的引用的节偏移,Sym.(符号)标识被修改引用应该指向的符号。Type告知毗连器如何修改新的引用,Addend(加数)是一个有符号常数,一些范例的重定位要使用它对被修改的引用的值做偏移调解。
4.4 Hello.o的效果分析

使用objdump -d -r hello.o命令对hello.o可重定位文件举行反汇编,得到的反汇编效果如下图


图24 hello.o反汇编程序
看到hello.o的反汇编文件我们很是熟悉,它使用的汇编代码和hello.s汇编文件的汇编代码是一样的,但是在这反汇编文件的字里行间中,也稠浊着一些我们相对陌生的面貌,也就是机器代码。
这些机器代码是二进制机器指令的聚集,每一条机器代码都对应一条机器指令,到这儿才是机器真正能识别的语言。每一条汇编语言都可以用机器二进制数据来表示,汇编语言中的操作码和操作数以一种相称于映射的方式和机器语言举行对应,从而让机器能够真正明确代码的寄义并且实行相应的功能。机器代码与汇编代码不同的地方在于:
(1)hello.s中的操作数和偏移均为10进制,反汇编所用均为16进制。
(2)hello.s中特有.cfi指令。
(3)反汇编中有二进制机器语言指令,hello.s内里只有汇编语言指令。
(4)hello.s 中跳转地点为.Lx,反汇编中为⽤偏移量表示的未重定位的内存地点。
(5)hello.s 中调⽤函数直接用函数名,⽽hello.o中用重定位条目来占位。
机器语言是二进制语言,是cpu可以识别的语言,由操作码和操作数构成。汇编语言是为了便于⼈们阅读机器语言而产生的一种文本表示。汇编语言可直接翻译成机器语言,和机器语言是⼀⼀对应的。
4.5 本章小结

    本章介绍了汇编阶段的汇编器的作用,展示了汇编指令和生成的.o文件中ELF格式信息,举行了汇编语言程序.s⽂件和反汇编文件中内容的比较。


第5章 链接

5.1 链接的概念与作用

概念:链接(Linking)分为静态链接和动态链接,是指将多个目的文件或库文件组合成一个可实行文件或共享库的过程。
作用:链接的作用是将多个目的文件或库文件归并成一个可实行文件或共享库。通过链接,可以使得程序具有可实行性,并且可以使得不同程序间共享同一份库文件,进步系统资源利用率。链接还可以举行符号分析、地点重定向等操作,以确保程序在运行时能够正常访问所需的函数和变量。
5.2 在Ubuntu下链接的命令

Linux系统hello.c使用如图所示指令举行链接

图25 链接的指令和hello可实行文件的产生


5.3 可实行目的文件hello的格式

5.3.1 ELF头
使用命令readelf -h hello查看hello的ELF头

图26 ELF头
可以看到文件的Type发生了变革,从REL变成了EXEC(Executable file可实行文件),节头部数量也发生了变革,变为了27个。
5.3.2 Section头
使用命令readelf -S hello查看节头部表信息

图27 Section头信息
节头部表对hello中全部信息举行了声明,包罗了巨细(Size)、偏移量(Offset)、起始地点(Address)以及数据对齐方式(Align)等信息。根据始地点和巨细,我们就可以计算节头部表中的每个节地点的区域。
5.3.3 符号表
使用命令readelf -s hello查看符号表信息

图28 符号表信息
与hello.o的符号表相比较,hello的符号数量剧增。这是因为经过毗连之后引入了很多其他库函数的符号,一并参加到了符号表中。
5.4 hello的虚拟地点空间

在edb中打开可实行文件hello,可以看到hello虚拟地点空间的起始地点为0x401000,结束地点为0x401fff。


图29 EDB查看虚拟地点空间
根据5.3.2节内里的Section头部表,我们可以找到对应的节的着实空间对应位置,例如.interp节,offset为0,起始位置地点为0x401000在edb中有其对应位置,如图29所示。

图30 interp节与其对应
5.5 链接的重定位过程分析

使用命令objdump -d -r hello查看hello可实行文件的反汇编条目

图31 hello反汇编文件(截取)
分析 hello 与 hello.o 不同:

[*]hello.out反汇编有更多的节。hello.out从init部分开始反汇编,有很多节,如.text,.bss等,而hello.o中只有main函数部分。
[*]hello.out新增了很多加载时链接的共享库中的函数代码,如exit、printf、sleep、getchar等函数,是因为在加载时举行了符号分析加重定位再反汇编,所以会有这些函数代码。
[*]hello反汇编的代码有确定的虚拟地点,也就是说已经完成了重定位,main函数地点为 0x400637 开始。而hello.o反汇编代码中main函数地点为0,未完成可重定位的过程。
[*]在调用函数和访问字符串⽅⾯,hello.out的反汇编有确定的call地点和字符串首地点,但是hello.o反汇编出来的这两个部分只有重定位条目占位,还没有进⾏全局符号和外部符号的分析和重定位(重定位条目和指令实际放在不同节,OBJDUMP工具将他们放在一起)。

图32 hello的反汇编函数调用

图33 hello.o的反汇编函数调用
5.6 hello的实行流程

hello程序先调用_init初始化,之后到_start,再到main,main实行之后实行_printf、_exit、_atoi、_sleep、_getchar,最后退出。
程序名称
程序地点
_init
0x401000
main
0x401125
_start
0x4010f0
puts@plt
0x401090
printf@plt
0x4010a0
getchar@plt
0x4010b0
atoi@plt
0x4010c0
exit@plt
0x4010d0
sleep@plt
0x4010e0

5.7 Hello的动态链接分析

   链接器在形成可实行程序时,发现引用了一个外部的函数,此时会查抄动态链接库,发现这个函数名是一个动态链接符号,此时可实行程序就不对这个符号举行重定位,而把这个过程留到加载时再举行。在调用共享库函数时,编译器没有办法猜测这个函数的运行时地点,因为定义它的共享模块在运行时可以加载到任意位置。正常的方法是为该引用生成一条重定位记载,然后动态链接器在程序加载的时候再分析它。GNU 编译系统使用延迟绑定(lazybinding),将过程地点的绑定推迟到第一次调用该过程时。延迟绑定是通过GOT和PLT实现的。GOT是数据段的一部分,⽽PLT是代码段的一部分,由之前图27可知GOT.PLT地点为0x404000


图34 在调用_init之前

图35 在调用_init之后
可见其中个别字节发生了改变,是由于动态链接产生的效果。
5.8 本章小结

概括了链接的概念。分析了hello程序运行时的虚拟地点空间,hello的重定位过程和实行过程中地点的变革。阐述了动态链接的过程。


第6章 hello进程管理

6.1 进程的概念与作用

概念:进程是⼀个实行中的程序的实例,系统中的每个程序都运⾏在某个进程的上下⽂中。
作⽤:提供两个抽象
(1)一个独立的逻辑控制流,它提供一个假象,好像我们的程序独占的使用处置惩罚器。
(2)⼀个私有的地点空间,它提供一个假象,好像我们的程序独占的使用内存系统。
6.2 简述壳Shell-bash的作用与处置惩罚流程

作用:shell是一个为用户提供界面访问操作系统的内核服务。它是用户使用linux的方式。
处置惩罚流程:

[*]从终端输入程序
[*]切分字符串得到参数
[*]如果内置命令则立刻实行,否则调用相应的程序为其分配子进程并运行
6.3 Hello的fork进程创建过程

在Linux系统中,用户可以通过./指令来实行一个可实行目的文件。在程序运行时,Shell就会创建一个新的进程,并且新创建的进程更新上下文,在这个新建进程的上下文中便可以运行这个可实行目的文件。fork()函数拥有一个int型的返回值。子进程中fork返回0,在父进程中fork返回子进程的Pid。新创建的进程与父进程几乎雷同但有细微的差别。子进程得到与父进程虚拟地点空间雷同的(但是独立的)一份副本(代码、数据段、堆、共享库以及用户栈),并且紫禁城拥有与父进程不同的Pid。
6.4 Hello的execve过程

(1) execve函数调⽤loader加载器函数,loader删除子进程现有的虚拟内存段和映射表,清空和物理内存,硬盘上可实行文件的映射,栈、堆初始化为0。

[*]将虚拟地点空间中的页映射到磁盘上所哀求的可实行文件的页,运行时由缺页中断驱动可实行文件的页加载到物理内存,建李虚拟地点空间与物理地点空间的映射。
[*]将IP设置为程序起始地点,
6.5 Hello的进程实行

1.逻辑控制流:程序计数器(PC)值的序列叫做逻辑控制流,简称为逻辑流。
2.上下文切换:上下文是内核重新启动一个被抢占进程所需的状态。内核可以决定抢占当前的进程,并重新开始一个先前被抢占的进程,这种决策成为调度,由调度器实行。在内核中调度一个新的程序运行,它会抢占当前的进程,该过程中使用的机制成为上下文切换,是一种较高条理的一场控制流来实现的。
3.时间片:进程实行它的控制流的一部分的每一个时间段称为时间片。
Shell调用fork为hello创建子进程,使用execve举行运行,开始hello运行在用户模式,收到信号后进入内核模式,运行信号的处置惩罚程序,之后回到用户模式。此时上下文切换,切分成时间片,并且也会切入到其他进程,形成多个程序共用处置惩罚器,但给人以独占的感觉。
6.6 hello的非常与信号处置惩罚

6.6.1 可能出现的非常:
① 中断:处置惩罚器外I/O装备信号的效果,异步非常,总是返回到下一条指令
② 陷阱:一种有意的非常,使指令实行的效果,其最重要的用途是在用户程序和内核之间提供一个像过程一样的接口,称为系统调用。一样平常返回下一条指令。
③ 由错误的环境引起,非有意。如果被故障处置惩罚程序修正,则重新实行;若不能被修正,则停止程序。
④ 不可恢复的致命性错误。停止处置惩罚程序,并且不会控制返回给应用程序。
6.6.2 信号

图36 可能出现的信号及处置惩罚方法
6.6.3 hello的详细信号处置惩罚
① Ctrl+C效果:停止

图37 Ctrl+C信号
② 乱摁和回车:正常运行

图37 乱摁和回车
③ Ctrl+Z:停止

图38 Ctrl+Z
④ ps:体现进程

图39 ps
⑤ jobs:体现任务

图40 jobs
⑥ pstree:体现进程

图41 pstree
⑦ fg:后台停止程序变为前台运行程序

图42 fg
⑧ Kill:发送杀死信号:停止程序变为停止程序

图43 Kill
⑨ 发送SIGCONT:停止程序继承运行

图44 SIGCONT
6.7本章小结

本章概括了进程的概念,shell的作用,fork和execve的运行过程以及hello的非常和信号处置惩罚。

第7章 hello的存储管理

7.1 hello的存储器地点空间

在CPU中当然不是仅仅只有hello一个进程,而是很多进程共享CPU和主存资源。那么为了使内存管理更加高效,操作系统提出了一种非常重要的抽象,即虚拟内存。它有几处有点:

[*]可以有效使用主存;
[*]可以简化内存管理;
[*]提供独立的地点空间。
下面介绍几个概念
① 逻辑地点:由程序产生的和段相关的偏移地点,格式为:段+偏移。
② 线性地点:逻辑地点加上相应段的基地点就生成了线性地点。
③ 虚拟地点:虚拟地点是一个抽象的地点空间,虚拟地点对应虚拟页,虚拟页会映射磁盘空间的一页,如果要使用该页上的数据,则会将该页载入内存,虚拟地点就对应了物理地点。
④ 物理地点:CPU外部地点总线上的物理内存的地点,可以将内存看成一个从0字节开始的数组,每个字节拥有单独的物理地点。
7.2 Intel逻辑地点到线性地点的变换-段式管理

用段选择符去GDT(全局描述符表)里得到段基址(段基址)然后加上段内偏移,这就得到了线性地点。我们把这个过程称作段式内存管理。

图45 逻辑地点与线性地点映射
对于linux系统,为了保证可移植性,其简化了分段机制,仅使用IA-32的分页机制,把全部的段描述符的基址设为0,此时逻辑地点和线性地点根本没什么区别。
7.3 Hello的线性地点到物理地点的变换-页式管理

首先cpu的PC产生的是虚拟地点,也就是线性地点,需要经过MMU翻译成物理地点,终极cpu芯片发出的地点信号是物理地点。转换时需要用到一个重要的工具,就是存放在物理内存中叫做页表的数据布局,页表将虚拟页映射到物理页。每次地点翻译硬件将一个虚拟地点转换为物理地点时,都会读取页表。页表就是一个页表条目的数组,虚拟地点空间的每一个页在页表中一个固定的偏移量处都有一个PTE,每个PTE是由一个有效位和一个n位的地点字段构成,如果设置了有效位,那么地点字段就表示DRAM中相应的物理页的起始位置。CPU想要读取虚拟内存中的一个字时,地点翻译硬件将虚拟地点作为一个索引从页表中找到其地点物理页的起始位置,再构造这个字的物理地点传给地点总线。

图46 页式管理地点映射
7.4 TLB与四级页表支持下的VA到PA的变换

TLB:每次CPU产生一个虚拟地点,MMU(内存管理单元)就必须查阅一个PTE(页表条目),以便将虚拟地点翻译为物理地点。在最糟糕的环境下,这会从内存多取一次数据,代价是几十到几百个周期。如果PTE碰巧缓存在L1中,那么开销就会下降1或2个周期。然⽽,很多系统都试图消除即使是如许的开销,它们在MMU中包罗了一个关于PTE的小的缓存,称为快表(TLB)。TLB是一个⼩的,虚拟寻址的缓存,其中每一行都保存着一个由单个PTE构成的块。地点翻译硬件每次想要读取页表时,先去访问TLB,若TLB中有想访问的PTE(即命中),翻译速度会很快。
多级页表:多级页表是一种压缩页表的方法,思想很简单,就是将虚拟地点划分为k个VPN和⼀个VPO,每个VPNi都是一个到第i级页表的索引,直到找到最后一级页表中的物理页起始地点,和VPO一起构造物理地点。

图47 多级页表
7.5 三级Cache支持下的物理内存访问

首先通过物理地点中的组索引位得到组号。先在L1高速缓存对比有效位和标志位,匹配乐成则读取,失败则进入二级高速缓存中再次匹配,以此类推,并且举行缓存的更改,在k-1层存储中缓存该数据。

图48 三级Cache内存访问
7.6 hello进程fork时的内存映射

为新进程创建虚拟内存,创建当进步程的的mm_struct, vm_area_struct和页表的原样副本.,两个进程中的每个页面都标记为只读,两个进程中的每个区域布局都标记为私有的写时复制。在新进程中返回时,新进程拥有与调用fork进程雷同的虚拟内存,随后的写操作通过写时复制机制创建新页面。
7.7 hello进程execve时的内存映射

为hello程序删除已存在的用户区域,创建新的区域布局,代码和初始化数据映射到.text和.data区(目的文件提供),bss和栈堆映射到匿名文件 ,栈堆的初始长度0,共享对象由动态链接映射到本进程共享区域,设置PC,指向代码区域的入口点。
7.8 缺页故障与缺页中断处置惩罚

缺页处置惩罚程序的重要步调为:
1.查抄虚拟地点是否合法,若不合法,则程序停止。
2.查抄进程是否有读写或实行该区域权限,若不具有,则触发非常保护机制,程序停止。
3.内核选择一个捐躯页面,写入磁盘,重新更换新的页面。
4.控制权转移给hello进程,实行触发缺页的指令。
7.9动态存储分配管理

动态内存管理的根本方法;使用动态内存分配器。动态内存分配器维护着一个进程的虚拟内存区域,称为堆。它紧接在未初始化的数据区域反面开始,并向下生长。对于每个进程,内核维护着一个变量brk,它指向堆的顶部。分配器将堆视为一组不同的巨细的块的聚集来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配的状态,直到它被释放,这种释放要么是应用程序显式实行的,要么是内存分配器自身隐式实行的,取决于分配器的不同风格。
策略:动态内存管理重要有两种策略:

[*]用隐式空闲链表组织堆
首先了解几个概念:
初次适配(First fit):从头开始搜刮空闲链表,选择第一个合适的空闲块:搜刮时间与总块数(包罗已分配和空闲块)成线性关系。在靠近链表起始处留下小空闲块的“碎片”。
下一次适配 (Next fit):和初次适配相似,只是从链表中上一次查询结束的地方开始。比初次适应更快:避免重复扫描那些无用块。一些研究表明,下一次适配的内存利用率要比初次适配低得多。
最佳适配 (Best fit):查询链表,选择一个最好的空闲块;适配,剩余最少空闲空间。保证碎片最小——进步内存利用率,通常运行速度会慢于初次适配。
在隐式空闲链表工作时,如果分配块比空闲块小,可以把空闲块分为两部分,一部分用来承装分配块,如许可以减少空闲部分无法使用而造成的浪费。隐式链表采取边界标记的方法举行双向归并。脚部与头部是雷同的,均为4个字节,用来存储块的巨细,以及表明这个块是已分配还是空闲块。同时定位头部和尾部,是为了能够以常数时间来举行块的归并。无论是与下一块还是与上一块归并,都可以通过他们的头部或尾部得知块巨细,从而定位整个块,避免了从头遍历链表。但与此同时也显著的增加了额外的内存开销。他会根据每一个内存块的脚部边界标记来选择归并方式,如下图:

图49 隐式空闲链表中的块归并

[*]用显式空闲链表组织堆
显式空闲链表只记载空闲块,而不是来记载全部块。它的思路是维护多个空闲链表,每个链表中的块有大抵相称的巨细,分配器维护着一个空闲链表数组,每个巨细类一个空闲链表,当需要分配块时只需要在对应的空闲链表中搜刮。
7.10本章小结

本章阐述了计算机中虚拟内存管理,物理地点,线性地点,逻辑地点以及他们的变换模式,段式,页式管理,在内存映射的基础上重新熟悉了fork和excave。
(第7章 2分)

第8章 hello的IO管理

8.1 Linux的IO装备管理方法

装备的模子化:文件
装备管理:unix io接口
全部的I/O装备(例如网络、磁盘和终端)都被模子化为文件,而全部的输入和输出都被当做对相应文件的读和写来实行,这种将装备优雅地映射为文件的方式,允许Linux内核引出一个简单低级的应用接口,称为Unix I/O。
8.2 简述Unix IO接口及其函数

Unix IO 接口定义如 8.1 所述,下面简述其函数。
1. 打开⽂件,open 函数
int open(char* filename,int flags,mode_t mode);
进程通过调用open函数来打开一个存在的文件或是创建一个新文件。open函数将filename转换为一个文件描述符,并且返回描述符数字,返回的描述符总是在进程中当前没有打开的最小描述符。flags参数指明了进程筹划如何访问这个文件,也可以是⼀个大概更多位掩码的,为写提供⼀些额外的指示。mode参数指定了新文件的访问权限位。
2. 关闭文件,close函数
int close(int fd);
进程通过调用close函数关闭⼀个打开的文件,关闭一个已关闭的文件会出错。fd是要关闭的进程描述符,返回操作效果。
3. 读⽂件,read函数
ssize_t read(int fd,void *buf,size_t n)
进程调用read函数实行输入,read函数从描述符为fd的当前文件位置赋值最多n个字节到内存位置buf。返回值-1表示一个错误,0表示EOF,否则返回值表示的是实际传送的字节数量。

[*]写文件,write 函数
ssize_t wirte(int fd,const void *buf,size_t n)
进程调用write函数实行输出,write函数从内存位置buf复制至多n个字节到描述符为fd的当前文件位置。

[*]体现地修改当前⽂件的位置,lseek函数
8.3 printf的实现分析

printf函数是C语言定义的标准I/O库中的函数,为程序员提供了UnixI/O的较高级别的替代,将write函数包装好供程序员方便地使用。
首先看printf函数的内容

图50 printf函数
printf程序按照格式fmt结合参数args生成格式化之后的字符串,并返回字串的长度。printf用了两个外部函数,一个是vsprintf,还有一个是write。
vsprintf函数作用是接受确定输特别式的格式字符串fmt。用格式字符串对个数变革的参数举行格式化,产生格式化输出。write函数将buf中的i个元素写到终端。从vsprintf生成体现信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.字符体现驱动子程序:从ASCII到字模库到体现vram。体现芯片按照刷新频率逐行读取vram,并通过信号线向液晶体现器传输每一个点。

8.4 getchar的实现分析







图51 getchar函数
当程序调用getchar时,程序等待用户按键,当用户按键时,键盘接口会得到一个代表该按键的键盘扫描码,同时产生一个中断哀求,中断哀求抢占当进步程运行键盘中断子程序,键盘中断子程序先从键盘接口取得该按键的扫描码,然后将该按键扫描码转换成ASCII码,保存到系统的键盘缓冲区之中。当用户键入回车之后(回车也在缓冲区当中),getchar调用read系统函数,从缓冲区中每次读入一个字符,直到接受到回车键才结束。getchar函数的返回值是用户输⼊的第⼀个字符的ascii码,如出错返回-1,且将用户输⼊的字符回显到屏幕。如用户在按回车之前输⼊了不止一个字符,其他字符会保留在键盘缓存区中,等待后续getchar调用读取。也就是说,后续的getchar调用不会等待用户按键,而直接读取缓冲区中的字符,直到缓冲区中的字符读完为后,才等待用户按键。
异步非常-键盘中断的处置惩罚:键盘中断处置惩罚子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调⽤read系统函数,通过系统调用读取按键ascii码,直到接受到回⻋键才返回
8.5本章小结

本章介绍了Linux的I/O装备的根本概念和管理方法,展示了Unix I/O接口及其函数,最后分析了printf函数和getchar函数的工作过程。
结论

Hello的历程:


[*]hello.cC语言文本文件的编写
[*]预处置惩罚过程:hello.c预处置惩罚为hello.i
[*]编译:编译器(ccl)将文本文件hello.i(C语言)翻译成文本文件hello.s(汇编语言)。
[*]汇编:汇编器将hello.s中的汇编语言翻译成计算性能处置惩罚的机器指令语言,并输出可重定位文件hello.o(二进制文件)
[*]链接:将hello中使用的库函数文件与hello.o文件举行毗连构成可实行目的文件。
[*]Shell为hello创建进程
[*]shell调用execve函数,execve函数会将新创建的子进程的区域布局删除,然后将其映射到hello程序的虚拟内存,然后设置当进步程上下文中的程序计数器,使其指向hello程序的入口点。
[*]运行hello时,内存管理单元、TLB、多级页表机制、三级cache协同工作,完成对地点的翻译和哀求。
[*]当Hello运行到printf这一步时,操作系统会调用malloc函数从堆中申请内存。
[*]当Hello实行时,可以通过IO输入等操作向进程发送信号。例如我们从键盘输入Ctrl-c,就会发送一个SIGINT信号,使当前前台进程的作业中断;同样哦们可以使用命令jobs来查看被抢占的进程,使用命令fg%<pid>来恢复对应ID的进程。
[*]结束运行,shell回收hello进程。
感悟:
1. 实行一个程序真的太复杂了,计算机系统真的博大精深!
2. 在系统地分析hello程序的整个过程时,我越来越体会到计算机严密的逻辑和层出不穷的创新,这种严谨而积极的态度值得我们学习!
附件

文件名称
                作用
hello.c
储存hello程序源代码
hello.i
源代码经过预处置惩罚产生的文件(包罗头文件等工作)
hello.s
hello程序对应的汇编语言文件
hello.o
可重定位目的文件
hello_o_s.txt
hello.o的反汇编语言文件
hello_o_elf.txt
hello.o的ELF文件格式
hello
二进制可实行文件
hello_elf.txt
可实行文件(hello)的ELF文件格式
hello_s.txt
可实行文件(hello)的汇编语言文件


参考文献


[*]Randal E.Bryant.深⼊明确计算机系统.北京:机器⼯业出书社,2016.7

https://www.cnblogs.com/diaohaiwei/p/5094959.html

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 哈工大CSAPP大作业:程序人生