王海鱼 发表于 2024-6-22 01:45:45

程序人生-Hello’s P2P




https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=file%3A%2F%2F%2FC%3A%5CUsers%5C86188%5CAppData%5CLocal%5CTemp%5Cksohtml5984%5Cwps1.png&pos_id=tEZXSuxZ

计算机体系

大作业


题     目  程序人生-Hello’s P2P 
专       业   工科试验班(医类1)  
学     号   2022112276            
班   级   2252001               
学       生   蒋一冉               
指 导 教 师   史先俊                 






计算机科学与技能学院
2024年5月
摘  要
本文紧张介绍hello程序在linux下是如何从一个.c文件在经历了预处理、编译、汇编、链接一步步变成可实行文件的。对于在运行的过程中的进程、信号、非常处理、存储处理、IO管理等操作举行探究的探索。用以概括程序从书写完成到编译到实行的全过程,结合课表里内容举行完备的叙述

关键词:计算机体系;预处理;编译;汇编;链接;进程;存储管理;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简介

P2P过程:对hello.c文本(program)举行预处理(gcc -E)生成hello.i文件,编译(gcc -S)生成汇编代码hello.s文件,再通过汇编操作(gcc -c)生成目标文件hello.o文件,最后链接操作生成二进制可实行文件。然后shell将程序fork,产生子进程,即为process。

020过程:然后execve,映射虚拟内存,载入物理内存,实行目标代码,CUP分配时间片实行逻辑控制流。运行结束之后,父进程回首子进程,内核实行删除操作,最后实现Zeor->Zero。

1.2 情况与工具

硬件情况:
装备名称 LAPTOP-LR4FDSS7
处理器 Intel® Core™ i7-9750H CPU @ 2.60GHz 2.59 GHz
机带 RAM 16.0 GB
体系类型 64 位操作体系, 基于 x64 的处理器
软件情况
版本 Windows 10 家庭中文版
VMware Workstation 15 Pro
Ubuntu 20.04
1.3 中心结果

文件名称
文件作用
hello.c
源代码
hello.i
预处理之后的文件
hello.s
编译后的汇编文件
hello.o
汇编之后的目标文件
hello
链接之后的可实行文件
objdump.txt
对hello.o的反汇编文件
Objdump2.txt
对hello的反汇编文件
ELF.elf
hello.o的elf文件
ELF2.elf
hello的elf文件
1.4 本章小结

这部分紧张介绍了hello的P2P、020过程,同时给出了实验所必要的硬件、软件情况,以及必要用到的工具,给出了实验中产生的中心文件 。



第2章 预处理

2.1 预处理的概念与作用

概念:
预处理是 C 语言程序从源代码变成可实行程序的第一步,紧张是 C 语言编译器对各种预处理命令举行处理,包括头文件的包含、宏界说的扩展、条件编译的选择等。
作用:
预处理的作用紧张是让编译器在随后的文本举行编译的过程中更方便,便于编译器实行操作,由于以下会影响编译器的操作都在预处理阶段完成了。
1. 头文件展开:将#include包含的文件插入到该指令位置
2. 宏展开:展开全部的宏界说,并删除#define
3. 条件编译:处理全部的条件预编译指令: #if、 #ifdef、 #else
4. 删除解释行的内容
5. 添加行号和文件名标识,同时在编译调试的时候表现行号信息
6. 保存#pragma指令
简朴分析:
文件包含:根据以字符#开头的命令,修改原始的C程序.比如hello.c中第1行的#include <stdio.h>命令告诉预处理器读取体系头文件stdio.h的内容,并把它直接插入程序文本中, 该过程递归举行,及被包含的文件大概还包含其他文件.
宏界说:将宏名替换为文本,即在对相干命令或语句的含义和功能作详细分析之前就要换,比方
#define IMAX 100,把程序中全部的标识符IMAX换成某个详细数值100
条件编译:有些语句希望在条件满意时才编译.比方
#ifdef
标识符
程序段1
#else
程序段2
#endif
当标识符已经界说时,程序段1才参加编译.
2.2在Ubuntu下预处理的命令


https://img-blog.csdnimg.cn/direct/84764c4d48454f318e0dd8985a77b69e.png
图2.2.1 预处理截图及预处理文件
原文件大小为592字节,预处理为64756字节。
2.3 Hello的预处理结果剖析

检察预处理结果文件,发现预处理只是对#命令举行处理,将用到的库的地址及库中的函数加入其中,同时去掉解释部分的内容,见图2.3.1,但是对原代码界说的函数或变量并未举行修改,见图2.3.2。
https://img-blog.csdnimg.cn/direct/e316fe078b3048eeaddc056dc205b480.png
图2.3.1 预处理对头文件、宏解释的展开以及对解释的删除
https://img-blog.csdnimg.cn/direct/c35b0765b62646b8a73dff138b9ee79f.png
图2.3.2 预处理对源代码中函数保持稳定
2.4 本章小结

本章介绍了预处理的概念和功能,并在Ubuntu下的预处理指令将hello.c转化为hello.i文件,并对预处理生成的文件和源代码举行了对比分析。
预处理过程是计算机对程序操作的起始过程,在这个过程中预处理器对hello.c文件举行初步的解释,对头文件、宏解释举行展开扩展到程序之中,同时删除对程序没有作用的解释部分,对函数不举行更改,最后将处理完的文本生存到hello.i文件中。

第3章 编译

3.1 编译的概念与作用

概念:编译是在预处理之后的阶段,对源代码举行预处理后获得了hello.i文件,编译则是使用编译器(ccl)对hello.i文件举行处理的过程。此阶段编译器会完成对代码的语法和语义的分析,生成汇编代码,并将代码生存在hello.s文件中。汇编结果是语言无关的。
作用:
1. 对代码举行语法查抄,出现错误则反馈,编译失败。
2. 生成汇编码
3. 覆盖处理
4. 目标程序优化
3.2 在Ubuntu下编译的命令

编译操作命令:gcc -S hello.i -o hello.s
https://img-blog.csdnimg.cn/direct/6654cfc022dd49dcbd30c7e6c1f23501.png
图3.2.1 编译命令及结果
3.3 Hello的编译结果剖析

一些底子的汇编指令含义
指令
含义
.file
声明源文件
.text
以下是代码段
.section.rodata
以下是rodata节
.globl
声明一个全局变量
.type
指定函数类型和对象类型
.size
声明大小
.string
声明一个string类型
.align
声明对指令大概数据的存放地址举行对齐的方式
3.3.1 数据

1. 字符串常量
源代码中界说的字符串在.rodata 段中界说生存,使用时候直接使用。
https://img-blog.csdnimg.cn/direct/5408e1d18240401585250d8ea95ae88a.png
图3.3.1 字符串常量界说
https://img-blog.csdnimg.cn/direct/0c714fb526ef4c12bf78d3c7d0327f73.png
图3.3.2 字符串常量的使用
2. 局部变量
局部变量基本都存放在栈中。
可以发现,将argc存放在-20(%rbp)的栈位置,将*arg[]存放在-32(%rbp)的栈位置,而int i则存放在-4(%rbp)的栈位置。
无论是int还是int[],局部变量在都存储在栈中,区别在于必要在栈中分配的大小时不同的。数据的大小必要根据数组
中元素个数举行确定。
https://img-blog.csdnimg.cn/direct/df78d1a063d04df5a8ee59b1b338e77e.png
图3.3.3 栈中局部变量赋值
3.3.1 操作

1. 赋值
赋值操作使用movb、movw、movl、movq举行,详细区分必要分别传送1、2、4、8字节大小的数据,如图3.3.3所示。
2.关系操作
第一处关系操作:比较argc是否等于5使用的cmpl指令,更新条件码
https://img-blog.csdnimg.cn/direct/83d949a76f8843b787c1d994e55cecaa.png
图3.3.4 argc是否为5操作
第二处关系操作:比较i是否小于10,这段在.L3中,被优化为和9比较。
https://img-blog.csdnimg.cn/direct/c327beca7ab643db9f5f868ff8a86363.png
图3.3.5 i是否小于10

[*]控制转移
之前每一个cmpl的操作之后,都紧跟着一个j的操作,这个j的含义就是jmp,起到控制函数跳转的作用,j后面跟的参数,就对应了在满意什么条件的时候举行跳转。除了cmpl后的控制转移外,还有直接使用jmp的控制转移。
源代码编译后有多次控制转移,下面将共同图片举行说明。
https://img-blog.csdnimg.cn/direct/0acc69b648fe4adda5362444ec2eadc2.png
图3.3.6 此处是判断argc是否为5,为5跳转到.L2
https://img-blog.csdnimg.cn/direct/9c4f95c2fa76431dbaf15f41eece5e22.png

图3.3.7 此处无条件跳转到.L3
https://img-blog.csdnimg.cn/direct/15542743a83d4618b102d036f6011b2d.png
图3.3.8 此处判断i是否小于等于9,如果是跳转到.L4


[*]算术运算操作
源代码编译后不是栈相干的运算只有i++运算,编译完成之后在.L4的末端,举行加法操作。详细见图3.3.5。
https://img-blog.csdnimg.cn/direct/1d79f022e19348b880cee41a5f474abf.png
图3.3.9 加法运算操作

[*]数组操作
必要访问的数组为argv,放在栈中,按地址举行访问。
源代码中操作为:
https://img-blog.csdnimg.cn/direct/d1e3028b528d4eedaa677fc0c8c6227f.png
图3.3.10 源代码中对数组访问
在编译后的文件中,访问方式如下:
https://img-blog.csdnimg.cn/direct/51e805ac978a46e8980c0421207ae9d9.png
图3.3.11 编译后文件对数组的访问

[*]函数操作
源代码中所使用的函数有printf、exit、atoi、sleep、getchar,下面将结合截图依次介绍。
printf:
源代码中有两个printf。

[*]首先介绍输入个数不准确的printf:首先将要打印的字符放到edi/rdi中,由于只有一个字符串,以是优化为puts函数。
https://img-blog.csdnimg.cn/direct/ae37f906593141e4b768f81ab1e24d70.png
图3.3.12 只有一个参数的printf会被编译器优化为puts函数

[*]然后介绍多个参数的printf:在.L4中,同样要将参数加入到对应的寄存器中,然后调用printf函数
https://img-blog.csdnimg.cn/direct/521baf72314941489fd8c63163977ebd.png
图3.3.13 多个参数的printf会被直接调用
exit:
在.LFB6中被直接调用。
https://img-blog.csdnimg.cn/direct/dda583d9f8e1431cbc2433627a2934b5.png
图3.3.14 exit
atoi、sleep:参数被放入寄存器中,在.L4中被直接调用
https://img-blog.csdnimg.cn/direct/9d51c60be9964920ad33fe3a7224d189.png
图3.3.15 atoi、sleep
getchar:在.L3中被直接调用
https://img-blog.csdnimg.cn/direct/7b89e9349e6a4e6383dc3aa45c2013ff.png
图3.3.16 getchar

[*]函数返回
全部函数的返回值都是将返回值放入寄存器rax之中,然后ret即可
3.4 本章小结

本章紧张报告了编译器如何将预处理之后的文本编译为汇编代码的。
分别介绍了:数据、关系操作、控制转移、算数操作、数组操作、函数调用、函数返回。
在编译阶段,编译器将高级语言编译成汇编语言。汇编语言是直接面向处理器的语言。汇编语言指令是机器指令的一种符号表现,而不同类型的CPU 有不同的机器指令体系,也就有不同的汇编语言,以是,汇编语言程序与机器有着密切的关系。
同时,编译器在编译代码的时候,会举行一些隐式的优化,函数顺序也并不完全按照代码的构思来,最终生成hello.s文件。

第4章 汇编

4.1 汇编的概念与作用

概念:
汇编(as),就是把汇编指令转为机器码的过程,机器码可以被CPU直接实行。汇编程序输入的是用汇编语言书写的源程序,输出的是用机器语言表现的目标程序。
作用:
汇编代码只是人可以看懂的代码,但机器并不能读懂,真正机器可以读懂并实行的是机器代码,也就是二进制代码。汇编的作用就是将之前在hello.s中生存的汇编代码翻译成可以供机器实行的二进制代码,这样机器就可以根据01代码,真正的开始实行所写的程序了。
4.2 在Ubuntu下汇编的命令

操作命令:gcc -c hello.s -o hello.o
https://img-blog.csdnimg.cn/direct/9c527dfd77e44f458098e21b3c78d0dd.png
图 4.2.1 汇编命令及文件
4.3 可重定位目标elf格式

使用 readelf -a hello.o > ELF.elf 命令获得 hello.o 文件的 ELF 格式。
https://img-blog.csdnimg.cn/direct/418fd9150fab456aa69df7d01e6cf846.png
图4.3.1 命令及生成文件
ELF的组成:

[*]ELF Header
以Magic开始,Magic 形貌了生成该文件的体系的字的大小和字节顺序,ELF 头剩下的部分包括ELF头的大小、目标文件的类型、机器类型、字节头部表(section header table)的文件偏移,以及节头部表中条目的大小和数量等信息。
https://img-blog.csdnimg.cn/direct/cf184b905644495fb9637c5093f2282c.png
图4.3.2 ELF Header内容

[*]Section Headers:节头部表,包含了文件中各个节的语义,包括节的类型、位置和大小等。
https://img-blog.csdnimg.cn/direct/ac0dfb2adbe843dd95532a7951a52162.png
图4.3.3 Section Header内容

[*]重定位节.rela.text ,包含.text节中必要举行重定位的信息,目标文件和其他文件组合时,必要修改这些位置。如图 4.4,图中 8 条重定位信息分别是对.L0、puts 函数、exit 函数、.L1、printf 函数、atoi函数、sleep函数、getchar 函数举行重定位声明。
https://img-blog.csdnimg.cn/direct/16d0521453c94b47a1409563c24ceea7.png
图4.3.4 重定位节内容
4.4 Hello.o的结果剖析

操作命令:objdump -d -r hello.o >objdump.txt
https://img-blog.csdnimg.cn/direct/74e605af26ec4bcc8caaf8a7f991c1c9.png
图4.4.1 反汇编命令与结果
机器语言指的是二进制的机器指令集合,由操作码和操作数构成的。汇编语言的主体是汇编指令。汇编指令和机器指令的差别在于指令的表现方法上,汇编指令是机器指令便于记忆的书写格式。
https://img-blog.csdnimg.cn/direct/56ed353f0dbd4443a90d5312e5adc41f.png
图4.4.2 反汇编文件汇编文件对比
分支转移:反汇编代码跳转指令的操作数使用的不是段名称如.L0之类,段名称是汇编语言中便于编写的助记符,但是在反汇编中就要是详细的地址。
函数调用:在.s 文件中,函数调用之后直接跟着函数名称,而在反汇编程序中,call的目标地址是当前下一条指令。由于 hello.c 中调用的函数必要通过动态链接器才气确定函数的运行时实行地址,在汇编成为机器语言时,将其call指令后的相对地址设置为全0,然 后在.rela.text 节中为其添加重定位条目。
全局变量访问:在.s文件中,访问.rodata,使用段名称+%rip,在反汇编代码中 0+%rip。
4.5 本章小结

本章探究了汇编操作将汇编代码转化为机器码的过程。也就是hello.s到可重定位目标文件hello.o的过程,同时也对比了汇编代码与机器码生成的反汇编代码之间的区别。

第5章 链接

5.1 链接的概念与作用

概念:
链接是通过链接器(ld)将各种代码和数据片断收集并组合成一个单一可实行文件的过程。这个文件可以被加载(复制)到内存并实行。
作用:
符号剖析:将每个符号引用与一个确定的符号界说关联起来
重定位:将单独的代码节和数据节归并为单个节,将独好从.o文件的相对位置重新定位到可实行文件的最终内存位置。
5.2 在Ubuntu下链接的命令

链接命令:
ld /usr/lib/x86_64-linux-gnu/crt1.o /usr/lib/x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/8/crtbeginT.o -L/usr/lib/gcc/x86_64-linux-gnu/8 hello.o -lc -lgcc -dynamic-linker /lib64/ld-linux-x86-64.so.2 /usr/lib/gcc/x86_64-linux-gnu/8/crtend.o /usr/lib/x86_64-linux-gnu/crtn.o -o hello
https://img-blog.csdnimg.cn/direct/2ad3e9d7616e42b496fdf91d233bb697.png
图5.2.1 链接命令及生成文件
5.3 可实行目标文件hello的格式

操作命令:readelf -a hello > ELF2.elf
ELF Header几乎没有变革,只是type从REL变为EXEC
https://img-blog.csdnimg.cn/direct/a45dee64cbb648a9ab159ba0ed78b4d7.png
图5.3.1 ELF Header
Section Header、和重定位节等几乎没有变革
5.4 hello的虚拟地址空间

细致观察edb表现的hello文件的ELF信息和5.3中的文件ELF信息,打现ELF头的信息是完全雷同的,但是再第17项开始的时候,地址发生较大变革,edb中没有这些节的信息,由于这些节是共享库的信息,再edb中想要获得这些信息要单独检察。这里简朴使用readelf检察
https://img-blog.csdnimg.cn/direct/c5da593a592d4329a56abb7335b62a37.png
图5.4.1 elf头
5.5 链接的重定位过程分析

https://img-blog.csdnimg.cn/direct/b0d1b30267574226a80afd68aed73a23.png
举个简朴的例子,看连接前的反汇编,其中je的参数是2f,其中的解释只是对main的一个偏移量,显然对hello.o来说,这段代码是不详细的。
但是对于之后的反汇编,很显然我们就能发现,代码的详细解释变成了体系库里的函数,偏移量也是准确的偏移量了。以是链接过程中,链接器会将我们链接的库函数与其他文件在可实行文件中准确定位出来。
以是重定位的详细流程大抵如下:
    重定位节和符号界说。将全部雷同类型的节归并为同一类型的新的聚合节。然后将运行时内存地址赋给新的聚合节,赋给输入模块界说的每个节,以及赋给输人模块界说的每个符号。当这一步完成时,程序中的每条指令和全局变量都有唯一的运行时内存地址了。
    重定位节中的符号引用。修改代码节和数据节中对每个符号的引用,使得它们指向准确的运行时地址。
    关于重定位的计算方法,这里我们简朴给出一个例子:
    相对地址重定位:
    重定位条目r由四个字段组成:
    r.offset=0x25
    r.symbol=exit
    r.type=R_X86_64_PC32
    r.addend=-4,
    R_X86_64_PC32重定位算法摘抄书本如下:
    Refaddr=0x400532+0x25=0x400557
    *refptr=(unsigned)(ADDR(r.exit)+r.addend-refaddr)
    =0x 600ab8+(-0x4)-0x400557
    =(unsigned) 0x20055d
    决定地址重定位:
    r.offset=0x16
    r.symbol=.rodata
    r.type=R_X86_64_PC32
    r.addend=0,
    *refptr=(unsigned)(ADDR(r.rodata)
    =0x400758
5.6 hello的实行流程

hello在实行的过程中一共要实行三个大的过程,分别是载入、实行和退出。载入过程的作用是将程序初始化。
ld-2.27.so! _dl_start 0x7fce 8cc38ea0
ld-2.27.so! _dl_init 0x7fce 8cc47630
hello! _start 0x400500
libc-2.27.so! __libc_start_main 0x7fce 8c867ab0
-libc-2.27.so! __cxa_atexit 0x7fce 8c889430
-libc-2.27.so! __libc_csu_init 0x4005c0
hello! _init 0x400488
libc-2.27.so! _setjmp 0x7fce 8c884c10
-libc-2.27.so! _sigsetjmp 0x7fce 8c884b70
–libc-2.27.so! __sigjmp_save 0x7fce 8c884bd0
hello! main 0x400532
hello! puts@plt 0x4004b0
hello! exit@plt 0x4004e0
*hello! printf@plt
*hello! sleep@plt
*hello! getchar@plt
ld-2.27.so! _dl_runtime_resolve_xsave 0x7fce 8cc4e680
-ld-2.27.so! _dl_fixup 0x7fce 8cc46df0
–ld-2.27.so! _dl_lookup_symbol_x 0x7fce 8cc420b0
libc-2.27.so! exit 0x7fce 8c889128
5.7 Hello的动态链接分析

对于动态共享链接库中 PIC 函数,编译器没有办法猜测函数的运行时地址,所 以必要添加重定位记录,等待动态链接器处理,为避免运行时修改调用模块的代 码段,链接器采用延迟绑定的策略。动态链接器使用过程链接表 PLT+全局偏移量 表 GOT 实现函数的动态链接,GOT 中存放函数目标地址,PLT 使用 GOT 中地址 跳转到目标函数。 在 dl_init 调用之前,对于每一条 PIC 函数调用,调用的目标地址都实际指向 PLT 中的代码逻辑,GOT 存放的是 PLT 中函数调用指令的下一条指令地址。
在函数调用时,首先跳转到PLT实行.plt中操作,第一次访问跳转时GOT地址为下一条指令,将函数序号入栈,然后跳转到PLT,之后将重定位表地址入栈,访问动态链接器,在动态链接器中使用在栈里生存的函数序号和重定位表计算函数运行时的地址,重写GOT,返回调用函数.之后如果还有对该函数的访问,就不用实行第二次跳转,直接参看GOT信息。
5.8 本章小结

本章简述了链接的过程,重点叙述了连接过程中对文件的处理,着重介绍了hello的虚拟地址空间,重定位过程,实行流程,动态链接过程。

第6章 hello进程管理

6.1 进程的概念与作用

概念:进程的经典界说是一个实行中程序的实例。更详细来说是操作体系对一个正在运行的程序中对处理器、主存、I/O装备的抽象表现。
作用:每次用户通过向shell输入一个可实行目标文件的名字,运行程序时,shell就会创建一个新的进程,然后在这个进程的上下文中运行这个可实行目标文件。应用程序也能创建新的进程,而且在这个进程上下文中运行自己的代码和程序。进程提供给应用程序关键的抽象:
1. 一个独立的逻辑控制流,提供一个假象,好像我们的程序独占使用处理器。
2. 一个私有的地址空间,提供一个假象,好像我们的程序独占使用内存体系。
6.2 简述壳Shell-bash的作用与处理流程

作用:shell是一个交互性的应用级程序,代表用户运行其他程序。Shell实行一系列的读/求职步骤,然后停止。读步骤读取来自用户的一个命令行,求值步骤剖析命令行,并代表用户运行程序。
处理流程:
1. 读取来自用户的一个命令行
2. 分析命令行字符串,获取命令行参数,并构造通报给execve的argv向量
3. 查抄第一个(首个、第0个)命令行参数是否是一个内置的shell命令
4. 如果是,立刻运行
5. 如果不是内部命令,调用fork( )创建新进程/子进程
6. 在子进程中用步骤2获取的参数,调用execve( )实行指定程序
7. 如果用户没要求配景运行(命令末端没有&号)否则shell使用waitpid(或wait...)等待作业停止后返回。
8. 如果用户要求配景运行(如果命令末端有&号),则shell返回。
6.3 Hello的fork进程创建过程

体系调用fork函数,创建一个子进程,该子进程拥有和父进程完全一致的代码、数据和资源,使用条件语句if(fork()==0)后加子进程内容即可。这个为后面的execve过程。 子进程得到与父进程用户级虚拟地址空间雷同的(但是独立的)一份副本,包括代码和数据段、堆、共享库以及用户栈。子进程进程还获得与父进程任何打开文件形貌符雷同的副本,这就意味着当父进程调用fork 时,子进程可以读写父进程中打开的任何文件。
6.4 Hello的execve过程

在之前创建的子进程中通过if(pid==0),在其中调用execve加载可实行文件,调用启用代码,启动代码设置栈,将栈和堆初始化为0,代码段与数据段初始化为可实行文件中的内容,最后将PC指向_start的地址,将控制通报给hello程序的主函数。
只有当出现错误时,比方找不到filename, execve 才会返回到调用程序。以是,与fork 一次调用返回两次不同, execve 调用一次并从不返回。
6.5 Hello的进程实行

上下文信息:上下文就是内核重新启动一个被抢占的进程所必要的状态,它由通用寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据布局等对象的值构成。
时间进程片:实行进程的控制流的一部分的每一时间段。
用户态与焦点态:
处理器同非常使一个寄存器提供两种模式的区分,该寄存器形貌进程当前的权限,在没有设置模式位的时候,进程处于用户态,此时进程不允许实行特权指令,同时也不允许直接引用地址空间中内核地区的代码和数据;但是一旦设置了模式位,该进程就处于焦点态,可以实行指令会合任何指令,也可以访问体系中任何内存位置。
进程调理的含义就是,在实行过程中,内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程,这个过程称为调理。内存收到停止信号之后,将当前进程加入等待序列,举行上下文切换将当前的进程控制权交给其他进程,当再次收到停止信号时将hello从等待队列加入运行队列。
6.6 hello的非常与信号处理

非常种类:
1. 异步非常:停止
2. 同步非常:陷阱、故障、停止
停止是来自I/O装备的信号,异步发生,停止处理程序对其举行处理,返回后实行下一条指令
陷阱是有意的非常,实行一条指令的结果,调用后返回下一条指令
故障是无意的非常,能够被修正。若被修正则返回到当前指令再实行一次,否则停止。
停止是不可修复的故障引起的。
信号是易中关照用户非常发送的机制,比方较为底层的硬件非常以及较高层的软件事件,比如Ctrl-Z和Ctrl-C,分别触发SIGCHLD和SIGINT信号。
 程序运行过程中可以按键盘,如不绝乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps  jobs  pstree  fg  kill 等命令,请分别给出各命令及运行结截屏,说明非常与信号的处理。
https://img-blog.csdnimg.cn/direct/efeaf4644fa34f02897bec654f2063cf.png
图6.6.1 乱按截图
https://img-blog.csdnimg.cn/direct/be2cdddf505b472ea2de478fea23fadf.png
图6.6.2 Ctrl+C截图
https://img-blog.csdnimg.cn/direct/3bcd24cb9e2c4b80b70f052cf7478650.png
图6.6.3 Ctrl+Z截图
https://img-blog.csdnimg.cn/direct/b0662cab428c4b49a90ac464daadee54.png
图6.6.4 Ctrl+Z后ps

显然,在Ctrl-Z之后,进程只是被挂起放到了配景,通过ps指令我们可以看到hello没有结束,接下来还可以继续刚刚挂起的进程。也可以直接kill掉进程。
但是在Ctrl-C之后,进程已经被结束了,已经被直接接纳掉了。
https://img-blog.csdnimg.cn/direct/182127c31c6446f6be0f769d3b13da04.png
图6.6.5 Ctrl+Z后kill
https://img-blog.csdnimg.cn/direct/fe9f1259c7394a6f838ad7f8dea10f33.png
图6.6.6 Ctrl+Z后pstree
https://img-blog.csdnimg.cn/direct/e6ee8766b26345f9a6e8a9f41db483e9.png
图6.6.6 Ctrl+Z后jobs

6.7本章小结

本章简朴叙述了进程的概念和shell的原理以及简朴的使用操作,同时介绍了信号和非常的相干信息。

第7章 hello的存储管理

7.1 hello的存储器地址空间

逻辑地址:
又称相对地址,是程序运行由CPU产生的与段相干的偏移地址部分。
形貌了程序在运行时的地址。

物理地址:
CPU地址总线传来的地址,由硬件电路控制(如今这些硬件是可编程的了)其详细含义。物理地址中很大一部分是留给内存条中的内存的,但也常被映射到其他存储器上(如显存、BIOS等)。在没有使用虚拟存储器的机器上,虚拟地址被直接送到内存总线上,使具有雷同地址的物理存储器被读写;而在使用了虚拟存储器的情况下,虚拟地址不是被直接送到内存地址总线上,而是送到存储器管理单元MMU,把虚拟地址映射为物理地址。

线性地址:
是一个32位无符号整数,可以用来表现高达4GB的地址,也就是,高达4294967296个内存单元。线性地址通常用十六进制数字表现,值得范围从0x00000000到0xfffffff)程序代码会产生逻辑地址,通过逻辑地址变换就可以生成一个线性地址。如果启用了分页机制,那么线性地址可以再颠末变换以产生一个物理地址。如果没有启用分页机制,那么线性地址直接就是物理地址。

虚拟地址:与线性地址类似,同样是对程序运行区块的相对映射。
Hello运行于物理地址,对于CPU而言, hello的运行地址是逻辑地址,在详细操作的过程中,CPU会将逻辑地址转换成线性地址再变成物理地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理

概念叙述:
1. 段寄存器:用于存放段选择符,用来确定对应段的首地址。
2. 段选择符:分为三个部分,索引,TI(全局形貌符or局部形貌符),RPL(CPU特权级)
知识配景:
1、逻辑地址=段选择符+偏移量
2、每个段选择符大小为16位,段形貌符为8字节(注意单元).
3、GDT为全局形貌符表,LDT为局部形貌符表.
4、段形貌符存放在形貌符表中,也就是GDT或LDT中.
5、段首地址存放在段形貌符中.
每个段的首地址都存放在自己的段形貌符中,而全部的段形貌符都存放在一个形貌符表中(形貌符表分为全局形貌符表GDT和局部形貌符表LDT)。而要想找到某个段的形貌符必须通过段选择符才气找到。
操作流程:
1. 根据逻辑地址判断出段选择符和段内偏移地址。
2. 根据段选择符判断当前转换段属于GDT还是LDT,再根据相应寄存器得到地址和大小。
3. 根据段选择符查找出对临汾的段形貌服,得到基地址
4. 线性地址=基地址+偏移地址
7.3 Hello的线性地址到物理地址的变换-页式管理

概念叙述:
1. 页表条目(PTE):页表将虚拟页映射到物理页,其每一项称为页表条目,由有效位和一个n位的地址字段组成。
2. 内存管理单元(MMU):CPU芯片上有一个专门的硬件,功能是动态的将虚拟地址翻译成物理地址。
操作流程:
1. 处理器生成一个虚拟地址,并把它传送给MMU
2. MMU生成PTE地址,并从高速缓存/主存请求得到它
3. 高速缓存/主存向MMU返回PTE
4. MMU构造物理地址,并把它传送给高速缓存/主存
5. 高速缓存/主存返回所请求的数据字给处理器.
7.4 TLB与四级页表支持下的VA到PA的变换

概念叙述:
1. TLB:PTE的缓存。
2. 多级页表:将完备的页表分组,分别对应到低一级的一节页表的一个PTE中,在实行程序的过程中,如果我们用到了一个特定的页表,那么我们就在一级页表后面动态的开出来。
操作流程:
1. CPU产生虚拟地址VA,VA传送给MMU,MMU使用前36位VPN作为TLBT(前32位+TLBI(后4位)向TLB中匹配
2. 如果命中,则得到 PPN (40bit)与VPO(12bit)组合成 PA(52bit)
3. 如果没有命中,MMU 向页表中查询,CR3确定第一级页表的起始地址,VPN1(9bit)确定在第一级页表中的偏移量,查询出 PTE,如果在物理内存中且权限符合,确定第二级页表的起始地址,以此类推,最终在第四级页表中查询到 PPN,与VPO组合成PA,而且向TLB 中添加条目。
7.5 三级Cache支持下的物理内存访问

先在TLB中找,若不命中,则在页表中找到PTE,构造出物理地址PA,然后去L1中使用物理地址分为CT,CI,CO,若没命中,依次向低级访存,最后返回结果。
详细流程:
获得了物理地址VA之后,使用CI(倒数7-12位)举行组索引,每组8路,对8路的块分别匹配CT(前40位)如果匹配成功且块的valid标志位为1,则命中,根据数据偏移量CO(后6位)取出数据返回。
如果没有匹配成功大概匹配成功但是标志位是1,则不命中,向下一级缓存中查询数据(L2 Cache->L3 Cache->主存),查询到数据之后,一种简朴的放置策略如下:如果映射到的组内有空闲块,则直接放置,否则组内都是有效块,产生辩论(evict),则采用最近最少使用策略LFU(Least frequently used)举行替换。也就是替换掉最不经常访问的一次数据。
7.6 hello进程fork时的内存映射

概念叙述:
1. mm_struct(内存形貌符):形貌了一个进程的整个虚拟内存空间。
2. vm_area_struct(地区布局形貌符):形貌了进程的虚拟内存空间的一个区间。
操作流程:
1. 创建当前进程的mm_struct,vm_area_struct和页表的原样副本。
2. 两个进程的每个页面都标志为只读页面。
3. 两个进程的每个vm_area_struct都标志为私有,只能在写入时复制。
7.7 hello进程execve时的内存映射

操作流程:
1. 删除已存在用户地区
2. 映射私有地区
3. 映射共享地区
4. 程序计数器PC指向代码入口点
7.8 缺页故障与缺页停止处理

页表相当于磁盘缓存,以是会存在缓存不命中现象。
对于一个访问内存的指令的现象,如果发生了缺页故障,就会调用非常处理程序,从磁盘中探求必要访问的页存,选择捐躯页,将PTE信息更新。
然后故障修正成功,返回当前程序,再次实行,就不会发生缺页故障了。
7.9动态存储分配管理

动态内存分配器维护着一个进程的虚拟内存地区-堆,分配器将堆视为一组不同大小的块来维护,每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。用户调用相应的申请和开释函数,动态内存分配器就会改变相应的块来完成要求,或查抄相应的块,或遍历探求空闲块。
隐式空闲链表分配器:
隐式空闲链表分配器的实现涉及到特别的数据布局。其所使用的堆块是由一个子的头部、有效载荷,以及大概的一些额外的填充组成的。头部含有块的大小以及是否分配的信息。有效载荷用来存储数据,而填充块则是用来对付外部碎片以及对齐要求。
基于这样的基本单元,便可以组成隐式空闲链表。
隐式空闲链表:
一个块是由一个字的头部,有效载荷,以及大概的一些额外的填充组成的。头部编码了这个块的大小,以及这个块是已分配的还是空闲的。如果我们强加一个双字的对齐约束条件,那么块大小就总是8的倍数,且块大小的最低3位总是零。因此,我们只必要内存大小的29个高位,开释剩余的3位来编码其他信息.在这种情况中,我们用其中的最低位(已分配位)来指明这个块是已分配的还是空闲的。
7.10本章小结

本章紧张介绍了hello的存储地址空间、段式管理、页式管理、物理内存访问,还介绍了 hello 进程 fork 时的内存映射、execve 时的内存映射、缺页故障与缺页停止处理、动态存储分配管理。

第8章 hello的IO管理

8.1 Linux的IO装备管理方法

装备的模型化:文件
全部的I/O装备(比方网络、磁盘和终端)都被模型化为文件,而全部的输入和输出都被当作对响应文件的读和写来举行。
装备管理:unix io接口
使得全部的输入和输出都能以一种统一且一致的方式来举行,包括打开文件,改变当前的文件位置,读写文件和关闭文件等操作。
8.2 简述Unix IO接口及其函数

. 打开或创建文件:
一个应用程序通过要求内核打开相应的文件,来宣告它想要访间一个I/O 装备。内核返回一个小的非负整数,叫做形貌符,它在后续对此文件的全部操作中标识这个文件。内核记录有关这个打开文件的全部信息,应用程序只需记住这个形貌符。
int open(char *filename, int flags, int perms);
int creat(char *name, int perms);
2. 关闭文件与删除文件:
当应用完成了对文件的访问之后,它就关照内核关闭这个文件。作为响应,内核开释文件打开时创建的数据布局,并将这个形貌符恢复到可用的形貌符池中。无论一个进程由于何种原因停止时,内核都会关闭全部打开的文件并开释它们的内存资源。
int close(int fd);
int unlink(char *filename);
3. 读取文件与写入文件:
一个读操作就是从文件复制n>0 个字节到内存,从当前文件位置k 开始,然后将k增加到k+n 。给定一个大小为m 字节的文件,当k~m 时实行读操作会触发一个称为end-of-file(EOF) 的条件,应用程序能检测到这个条件。在文件末端处并没有明确的“EOF 符号” 。类似地,写操作就是从内存复制n>0 个字节到一个文件,从当前文件位置k开始,然后更新k ,直到k+n。
int read(int fd, char *buf, int n );
int write(int fd, char *buf, int n);
4. 游标移动
对于每个打开的文件,内核保持着一个文件位置k,初始为0。这个文件位置是从文件开头起始的字节偏移量。应用程序能够通过实行seek 操作,显式地设置文件的当前位置为K。
int lseek(int fd, long offset, int origion); 
8.3 printf的实现分析

要实现printf函数,简朴来说就是担当fmt格式,将匹配到的参数按照格式输出即可。
https://www.cnblogs.com/pianist/p/3315801.html
从vsprintf生成表现信息,到write体系函数,到陷阱-体系调用 int 0x80或syscall等.
字符表现驱动子程序:从ASCII到字模库到表现vram(存储每一个点的RGB颜色信息)。
表现芯片按照刷新频率逐行读取vram,并通过信号线向液晶表现器传输每一个点(RGB分量)。
8.4 getchar的实现分析

异步非常-键盘停止的处理:键盘停止处理子程序。担当按键扫描码转成ascii码,生存到体系的键盘缓冲区。
getchar等调用read体系函数,通过体系调用读取按键ascii码,直到担当到回车键才返回。
8.5本章小结

本章报告了I/O装备管理机制,简朴叙述了开、关、读、写、转移文件的接口及相干函数,简朴分析了printf和getchar函数的实现方法。


结论

下面简朴形貌以下hello的一生,没有ppt中充足艺术,但是我努力尝试形貌。
1. 首先我们通过IDE使用C语言编写了hello.c文件,这是hello的胚胎。
2. 然后通过预处理将hello.c文件颠末初步修改,头文件、宏界说的展开变为更容易被编译器理解的hello.i文件
3. 接着编译器将hello.i文件变为了更为底子的语言:汇编语言,并将代码生存在了hello.s文件中
4. 随后,汇编器将hello.s中的汇编语言处理成了能够被机器理解的机器语言,生成了可重定位的目标文件hello.o
5. 链接器将hello.o与外部文件(库)举行链接,这时候的hello.o终于变为了一个可实行文件hello
6. 随后在shell中输入运行指令./hello 2022112276 jyr 18846752508 3,内核分配好必要的空间
7. 同时此时的我们还能在外部给相应信号对进程举行操作,比如Ctrl-Z。
8. 在hello必要访问磁盘信息的时候,CPU通过MMU帮助hello寻址
9. 最终,hello进程实行结束之后,shell对它举行接纳
这次大作业帮助我更加全面体系地了解了计算机体系这门课程,同时帮助我完成了对书本更详细翻阅工作,在考前替我梳理了计算机体系的相干知识。让我感受到了计算机底层体系的复杂性和严密性,同时也告诉我如何写一个对计算机底层体系友好的代码。
附件

文件名称 文件作用
hello.c 源代码
hello.i 预处理之后的文件
hello.s 编译后的汇编文件
hello.o 汇编之后的目标文件
hello 链接之后的可实行文件
objdump.txt 对hello.o的反汇编文件
Objdump2.txt 对hello的反汇编文件
ELF.elf hello.o的elf文件
ELF2.elf hello的elf文件

参考文献

 Bryant,R.E. 深入理解计算机体系
 Printf函数的深入分析
https://www.cnblogs.com/pianist/p/3315801.html
 EDB的安装与使用
https://blog.csdn.net/Franklins_Fan/article/details/103643965
逻辑地址到线性地址的转换
https://blog.csdn.net/xuwq2015/article/details/48572421


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