ToB企服应用市场:ToB评测及商务社交产业平台

标题: HIT CSAPP——程序人生-Hello’s P2P [打印本页]

作者: 大连密封材料    时间: 2024-6-14 22:56
标题: HIT CSAPP——程序人生-Hello’s P2P

本文链接:https://blog.csdn.net/QingFeng_0813/article/details/139468749?spm=1001.2014.3001.5502

盘算机系统

大作业



题     目  程序人生-Hellos P2P 
专       业    医学与健康学院      
学     号    2022110762          
班   级    2252003             
学       生    刘佳龙              
指 导 教 师     史先俊           






盘算机科学与技术学院

20245

摘  要

本研究详细追踪并解析了hello.c源文件从创建到转变为可执行程序的全过程。起首,源文件履历了预处理、编译、汇编和链接阶段,最终生成可执行文件。接着,研究了该程序在执行过程中怎样成为进程,进程在操纵系统中的运行情况,以及其最终被停止的过程。本文探究了盘算机在编译和运行hello.c时,在操纵系统、处理器、内存和I/O装备等各层面之间的交互机制。  

关键词:盘算机系统;预处理;编译;汇编;链接;进程管理;存储管理;IO管理;                            

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








目  录


第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(Program to Process)
Hello的P2P形貌了hello.c文件从源代码转变为运行时进程的过程。在Linux系统中,hello.c文件需要履历多个步调才能变为可执行程序:起首,通过cpp(C预处理器)举行预处理;接着,由ccl(C编译器)编译;然后,由as(汇编器)举行汇编;最后,由ld(链接器)举行链接,生成最终的可执行文件hello(在Linux系统中,此文件通常没有固定的后缀)。当在shell中输入下令./hello时,shell会通过fork创建一个子进程,使得hello从一个可执行文件变为一个正在运行的进程。

图1.1-1 编译系统

020(From 0 to 0)
Hello的020解释了hello.c文件从内存中无内容到程序运行结束、内存被清空的过程。起初,内存中没有hello文件的任何数据,这就是“From 0”。当在shell中使用execve函数时,系统将hello文件载入内存并执行其代码。程序执行完毕后,hello进程结束,内核会回收相关内存并删除hello文件的相关数据,这个过程即为“to 0”。

1.2 情况与工具

硬件情况:x64 CPU;3.20GHz;16G RAM
软件情况:Windows 11 64位;Ubuntu 20.04
开辟与调试工具:edb,gcc,vim,objdump,readelf
1.3 中间结果

表1 中间结果文件

文件的名字
文件的作用
hello.i
hello.c预处理后得到的文本文件
hello.s
hello.i编译后得到的文本文件
hello.o
hello.s汇编后得到的二进制文件
hello.elf
hello.o的ELF格式文件
hello.asm
hello.o的反汇编文件
hello
hello.o链接得到的可执行目标文件
hello2.elf
hello的ELF格式文件
hello2.asm
hello的反汇编文件


1.4 本章小结

本章主要介绍了hello的P2P和020的整个过程、软硬件情况、开辟工具以及相应的中间结果。
(第1章0.5分)



第2章 预处理

2.1 预处理的概念与作用

1. 预处理的概念
预处理是编译之前的步调,在此阶段,源代码会被扫描以辨认并处理预处理指令和宏界说。此过程会替换包含预处理指令的语句和宏界说,还会移除代码中的注释和多余的空缺字符,最终生成调解后的源代码供编译器使用。

2. 预处理的作用
预处理的主要功能可以分为以下三部分:
(1) 宏展开:预处理器会辨认并展开程序中的“#define”标识符,将其替换为界说的实际值(比方字符串或代码段)。
(2) 文件包含复制:预处理器会处理用“#include”指令包含的文件,将这些文件的内容插入到该指令地点的位置,删除原指令,实现雷同于复制粘贴的效果,使包含的文件和当前源文件合并成一个新的源文件。
(3) 条件编译处理:根据“#if”、“#endif”、“#ifdef”和“#ifndef”等指令后的条件,确定哪些部分的源代码需要编译。

2.2在Ubuntu下预处理的下令


图2.2-1 预处理下令截图


图2.2-2 hello.i截图

2.3 Hello的预处理结果解析

观察预处理结果后,我们发现hello.i是一个文本文件,内容仍旧是C语言代码,但量非常庞大。带有-P参数的 hello.i 有近两千行,而不带-P参数的则有三千行。在文件结尾,我们可以看到我们的源程序代码,而且它并没有被修改。

这是因为在 hello.c 的头部引入了标准头文件 stdio.h、unistd.h 和 stdlib.h。预处理器将它们替换为系统文件中的内容。因此,即使我们的程序中没有写系统文件的内容,经过预处理器的替换后,我们仍旧能够得到这些函数。在编译后,我们可以直接使用这些系统函数,而不需要在源程序中编写这些庞大而复杂的系统函数。

图2.2-3 hello.i截图


2.4 本章小结

本章主要探究了预处理的概念、作用和下令,分析了hello.c源代码文件的预处理过程和结果,此过程深化了对C语言预处理的理解。C语言预处理一般由预处理器(cpp)举行,主要完成四项工作:宏展开、文件包含复制、条件编译处理和删除注释及多余空缺字符。


(第2章0.5分)


第3章 编译


3.1 编译的概念与作用

编译(Compilation)是指将高级程序源代码转换为盘算机可以执行的目标代码,在这个过程中,程序源文件起首经过预处理器的处理,生成的预处理文件被转换为汇编程序。编译器(Compiler)负责将预处理文件中的高级语言转换为汇编语言,生成形貌程序的汇编语言文本文件。
编译的作用:
1.将高级语言转换为呆板可执行的目标代码:编译器将程序员用高级语言编写的代码转换为盘算机硬件能够理解和执行的二进制指令大概汇编语言。
2.优化程序性能:编译器可以对程序举行优化,使得生成的目标代码在执行时能够更加高效地使用盘算机资源,提高程序的运行速度和性能。
3.查抄代码错误:编译器在编译过程中会查抄代码的语法错误和类型错误等,提供编译错误信息,帮助程序员发现和修复代码中的问题。
4.提供平台无关性:编译器可以将高级语言代码编译成针对差别硬件平台的目标代码,从而实现程序的跨平台运行。
注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序
3.2 在Ubuntu下编译的下令


图3.2-1编译下令


图3.2-2 hello.s截图


3.3 Hello的编译结果解析

3.3.1数据:
3.3.1.1字符常量



图3.3.1-1字符常量对应汇编语言

分别为两个字符串提示符,放在静态存储区,不可改变,且不绝存在。

3.3.1.2变量


(1)局部变量i。只在初次使用时赋值,储存在栈中,当地点函数返回,栈会被复原,局部变量在栈中所占的空间也会被释放。


图3.3.1-2 局部变量对应汇编语言


(2)函数参数。即形式参数,实际为局部变量,只在所调用的函数中起作用,函数调用开始时储存在栈中,当函数返回所占空间被释放。


图3.3.1-3 函数参数对应汇编语言

main将argc和argv作为局部变量存储在栈中(argc为main函数下令行参数个数,argv为存放参数字符串指针的数组)


3.3.1.3表达式


C 语言中的表达式分为多种类型,其中包罗:变量常量表达式、算术表达式、赋值表达式、逗号表达式、关系表达式和逻辑表达式。复合表达式是其他表达式的组合,因此不需要单独讨论。在这里,我们将重点介绍其他五种表达式的情况。

(1)算术表达式:

算术表达式是使用运算符(一元或二元)将操纵数连接起来的表达式,比方像 a + b 或 i++如许的形式。

在我们的程序中,i++表示每次循环都将i 自增 1。


图3.3.1-4 自增1算术表达式对应汇编语言

每次循环结束编译器使用ADD指令为储存在栈中的局部变量i增加1。

(2)赋值表达式

编译器使用MOV类指令将一个值赋给另一个地点:


图3.3.1-5 赋值表达式对应汇编语言

(3)关系表达式

编译器使用CMP对两个地点的值举行对比,然后根据比较结果使用jump指令跳转到相应地点。


图3.3.1-6 argc参数个数关系表达式对应汇编语言



图3-12,循环i关系表达式对应汇编语言

3.3.2赋值
源代码i=0,为i赋初值,通过movl指令实现:

图3.3.2-1 赋值例1截图


源代码i++,在每次循环i赋值为i+1,通过addl指令实现:

图3.3.2-2 赋值例2截图

3.3.3算术操纵

源代码i++,通过addl指令实现:


图3.3.3-1 算术操纵截图

3.3.4关系操纵
源代码argc!=5,将argc与5比较,通过cmpl指令实现:

图3.3.4-1 关系操纵例1截图

3.3.5控制转移

编译器使用 jump 指令举行跳转转移,通常用于实现判断或循环的分支操纵。由于差别的逻辑表达式结果会导致程序执行差别的代码,编译器会先使用 CMP 指令更新条件码寄存器,然后根据比较结果使用相应的 jump指令跳转到对应代码的地点。


图3.3.5-1 hello.s中的控制转移1


图3.3.5-2 hello.s中的控制转移2


图3.3.5-3 hello.s中的控制转移3

3.3.6数组操纵

argv 是一个字符串指针数组,其中存储着下令行输入的参数字符串的指针。我们可以通过数组内元素的地点来访问这些参数。假设 argv 被存储在 -32(%rbp),由于在 64 位系统中指针大小为 8 个字节,因此每个参数字符串地点相差 8 个字节。

编译器使用 -32(%rbp) 作为数组的起始基地点,然后通过差别的偏移量来访问各个字符串指针的地点。具体来说,编译器会使用以下盘算方式来获取每个参数的地点:

第一个参数的地点:-32(%rbp)

第二个参数的地点:-32(%rbp) + 8

第三个参数的地点:-32(%rbp) + 16

第四个参数的地点:-32(%rbp) + 24

每个地点的盘算都基于 rbp 寄存器减去一个固定的偏移量,再加上对应的字节数,以此来访问存储在 argv 数组中的各个字符串指针。


图3.3.6-1  hello.s中的数组操纵对应汇编语言

3.3.7函数操纵

3.3.7.1参数传递

在函数调用之前,编译器会将参数存储在寄存器中,以便被调用的函数使用。在参数个数不超过 6 个时,参数按以下优先级存放在寄存器中:rdi,rsi,rdx,rcx,r8,r9。如果参数个数超过 6 个,前六个参数使用寄存器存放,其余的参数则压入栈中。

在我们的程序中,多次举行了函数调用。比方,当参数不足时,程序会打印提示信息。此时,编译器将提示字符串的地点赋给rdi寄存器,并调用puts函数来输出提示信息。


图3.3.7-1 调用printf传递参数对应汇编语言

在程序中,通过相对寻址获取参数字符串的地点并将其加载到适当的寄存器中,比方 rdi, rsi, rdx,然后调用相应的函数。具体来说,使用 rdi 寄存器传递第一个参数给 atoi 函数,将字符串转换为整型值,并将结果传递给 sleep 函数来停息程序执行。通过这种方式,可以精确地处理和转换参数字符串,并执行后续操纵:


图3.3.7-2 调用atoi传递参数对应汇编语言

3.3.7.2 函数调用

程序使用汇编指令 call 加函数地点来调用函数。在 hello.o 文件中,由于没有重定位信息,编译器使用函数名作为助记符来代替具体的函数地点。当执行 call 指令时,程序会将返回地点压入栈中,为局部变量和函数参数建立栈帧,然后跳转到被调用函数的地点。


图3.3.7-3 函数调用


3.3.7.3 函数返回

程序使用汇编指令ret从调用的函数中返回,还原栈帧,返回栈中保存的返回地点。


图3.3.7-4,函数返回



3.4 本章小结

本章主要介绍了编译的概念与作用,在Ubuntu下编译的下令,对hello的编译结果举行解析,详细分析了编译器怎样处理C语言中的数据、赋值、算术运算、关系操纵、控制转移、数组操纵和函数操纵。


(第3章2分)


第4章 汇编


4.1 汇编的概念与作用


4.1.1 汇编的概念

汇编是指汇编器(assembler)将以 .s 结尾的汇编程序翻译成呆板语言指令的过程。汇编器会将这些指令打包成可重定位的目标程序格式,并最终将结果保存到 .o 目标文件中。
4.1.2 汇编的作用
汇编的主要作用是将汇编语言转换为呆板语言,并将这些指令以可重定位目标程序的格式保存到 .o 文件中。如许,程序在链接阶段可以使用这些目标文件生成最终的可执行文件。

注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成呆板语言二进制程序的过程。
4.2 在Ubuntu下汇编的下令

在Ubuntu下汇编的下令为:
gcc -m64 -no-pie -fno-PIC -c hello.s -o hello.o
汇编过程如下:

图4.2-1 汇编的下令


4.3 可重定位目标elf格式

分析hello.o的ELF格式,用readelf等列出其各节的根本信息,特殊是重定位项目分析。
起首,在shell中输入readelf -a hello.o > hello.elf 指令得到 hello.o 文件的 ELF 格式:

图 4.3-1 生成ELF文件

其布局分析如下:
1.ELF 头(ELF Header):
以 16字节序列 Magic 开始,其形貌了生成该文件的系统的字的大小和字节顺序,ELF 头剩下的部分包含帮助链接器语法分析和解释目标文件的信息,其中包罗 ELF 头大小、目标文件类型、呆板类型、节头部表的文件偏移,以及节头部表中条目标大小和数量等相关信息。

图4.3-2  ELF头

2.节头:
包含了文件中出现的各个节的意义,包罗节的类型、位置和大小等信息。

图4.3-2 节头

3.重定位节
重定位节包含在代码中使用的一些外部变量和函数的符号信息。当程序举行链接时,链接器根据这些重定位信息对符号举行处理和修改,确保最终生成的可执行文件中的地点引用是精确的。


图4.3-3 重定位节


4.符号表Symbol table
符号表是目标文件中的一个关键数据布局,用于存储程序中所有符号(包罗变量、函数等)的界说和引用信息。符号表中的每个条目形貌了一个符号的属性,包罗其名称、地点、大小、类型和作用域等。


图4.3-4 符号表



4.4 Hello.o的结果解析

下令:objdump -d -r hello.o > hello.asm

图4.4-1 生成hello.asm文件

通过对比hello.asm与hello.s可知,两者在如下地方存在差别:
在hello.s中,跳转指令的目标地点直接记为段名称,如.L2,.L3等。而在反汇编得到的hello.asm中,跳转的目标为具体的地点,在呆板代码中体现为目标指令地点与当前指令下一条指令的地点之差。


图4.4-1 分支转移


在hello.s文件中,call之后直接跟着函数名称,而在反汇编得到的hello.asm中,call 的目标地点是当前指令的下一条指令。这是因为 hello.c 中调用的函数都是共享库中的函数,最终需要通过动态链接器作用才能确定函数的运行时执行地点,在汇编成为呆板语言的时候,对于这些不确定地点的函数调用,将其 call 指令后的相对地点设置为全0(此时,目标地点正是下一条指令),然后在.rela.text 节中为其添加重定位条目,等待静态链接进一步确定。


图4.4-3 函数调用 

在hello.s 文件中,使用段名称+%rip访问 rodata(printf 中的字符串),而在反汇编得到的hello.asm中,使用 0+%rip举行访问。其原因与函数调用雷同,rodata 中数据地点在运行时才能确定,故访问时也需要重定位。在汇编成为呆板语言时,将操纵数设置为全 0 并添加相应的重定位条目。


图4.4-4 全局变量访问


4.5 本章小结

本章探究了汇编的根本概念及其作用,详细介绍了在Ubuntu情况下怎样将hello.s文件转换为hello.o文件,并生成ELF格式的hello.elf文件。同时,本章还研究了ELF格式文件的布局。通过对比hello.o的反汇编代码(保存在hello.asm中)与hello.s中的代码,我们深入相识了汇编语言与呆板语言之间的差别和联系。

(第4章1分)


5链接


5.1 链接的概念与作用

链接是链接器将一个或多个可重定位目标文件(包罗特殊的共享目标文件)通过符号解析和重定位生成可执行目标文件的过程。链接包罗静态链接和加载时共享库的动态链接。

注意:这儿的链接是指从 hello.o 到hello生成过程。
5.2 在Ubuntu下链接的下令

在Ubuntu下链接的下令如下:
ld -dynamic-linker /lib64/ld-linux-x86-64.so.2 /usr/lib/x86_64-linux-gnu/crt1.o hello.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 -o hello

图5.2-1 链接下令截图

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

5.3.1readelf下令

下令:readelf -a hello >hello2.elf

图5.3.1-1 readelf下令截图

5.3.2ELF头
hello2.elf中的ELF头与hello.elf中的ELF头包含的信息种类根本相同,以形貌了生成该文件的系统的字的大小和字节顺序的16字节序列 Magic 开始,剩下的部分包含帮助链接器语法分析和解释目标文件的信息。与hello.elf相比较,hello2.elf中的根本信息未发生改变(如Magic,种别等),而类型发生改变,程序头大小和节头数量增加,而且得到了入口地点。与hello.o的ELF头对比可以发现,起首类型变成了EXEC(可执行文件),而且在hello的ELF头中大部分在hello.o的ELF头中临时被填为0的条目被重新填充了,且于是各起始地点也相应向后移动了。


图5.3.2-1 ELF头截图


5.3.3节头
形貌了各个节的大小、偏移量和其他属性。链接器链接时,会将各个文件的相同段合并为一个大段,而且根据这个大段的大小以及偏移量重新设置各个符号的地点。


图5.3.3-1 节头截图

5.3.4重定位节
重定位节内容变为需要动态链接调用的函数,同时重定位类型发生改变。


图5.3.4-1 重定位节截图

5.3.5符号表
符号表中保存着定位、重定位程序中符号界说和引用的信息,所有重定位需要引用的符号都在其中声明

图5.3.5-1 符号表截图

5.4 hello的假造地点空间

使用edb加载hello:

图5.4-1 edb加载hello截图


从Data Dump窗口可以看到hello的假造地点空间分配情况。

图5.4-2 Data Dump窗口截图

init段起始地点:0x401000

图5.4-3 .init段起始地点截图

text段起始地点:0x4010f0


图5.4-4 .text段起始地点截图

以此类推,节头中的各段在edb中均能对应找到,阐明节头表中存储各段的起始地点与各段的假造地点之间存在对应关系。
查察 ELF 格式文件中的 Program Headers,它告诉链接器运行时加载的内容,并提供动态链接的信息。每一个表项提供了各段在假造地点空间和物理地点空间的各方面的信息。
程序包含:

PHDR 保存程序头表;

INTERP 指定在程序已经从可执行文件映射到内存之后,必须调用的解释器;

LOAD 表示一个需要从二进制文件映射到假造地点空间的段。其中保存了常量数据、程序的目标代码等;

DYNAMIC 保存了由动态链接器使用的信息;

NOTE 保存辅助信息;

GNU_STACK,权限标记,用于标记栈是否是可执行;

GNU_RELRO,指定在重定位结束之后哪些内存地域是需要设置只读。



5.5 链接的重定位过程分析

在Shell中使用下令objdump -d -r hello > hello2.asm生成反汇编文件hello2.asm,与第四章中生成的hello.o.asm文件举行比较,其差别之处如下:

图5.5-1 生产.asm文件

1.链接后函数数量增加。链接后的反汇编文件hello2.asm中,多出了puts@plt,printf@plt,getchar@plt,exit@plt,sleep@plt等函数的代码。这是因为动态链接器将共享库中hello.c用到的函数加入可执行文件中。

图5.5-2 链接后的函数

2.函数调用指令call的参数发生变化。在链接过程中,链接器解析了重定位条目,call之后的字节代码被链接器直接修改为目标地点与下一条指令的地点之差,指向相应的代码段,从而得到完整的反汇编代码。


图5.5-3 跳转指令的参数



3.跳转指令参数发生变化。在链接过程中,链接器解析了重定位条目,并盘算相对距离,修改了对应位置的字节代码为PLT 中相应函数与下条指令的相对地点,从而得到完整的反汇编代码。


图 5.5-4 跳转指令的参数


5.6 hello的执行流程

5.6.1执行过程


图5.6-1 edb执行hello截图

1.从加载hello到_start:程序先调用_init函数,之后是puts、printf等库函数,最后调用_start函数。

2.从_start到call main:程序先调用__libc_csu_init等函数,完成初始化工作,随后调用main函数。

3.从main函数到程序停止:程序执行main函数调用main函数用到的一些函数,main函数执行完毕之后调用__libc_csu_fini、_fini完成资源释放和清理的工作。

5.6.2子程序名或程序地点

0000000000401000 <_init>

0000000000401020 <.plt>

0000000000401030 <puts@plt>

0000000000401040 <printf@plt>

0000000000401050 <getchar@plt>

0000000000401060 <atoi@plt>

0000000000401070 <exit@plt>

0000000000401080 <sleep@plt>

00000000004010f0 <_start>

0000000000401120 <_dl_relocate_static_pie>

0000000000401125 <main>

00000000004011d0 <__libc_csu_init>

0000000000401240 <__libc_csu_fini>

0000000000401248 <_fini>

5.7 Hello的动态链接分析

在elf文件中:



图 5.7-1 hello的elf文件内容

进入edb查察:


图 5.7-2 edb执行init前的地点




图 5.7-3 edb执行init后的地点


一开始地点的字节都为0,调用_init函数之后GOT内容产生变化,指向精确的内存地点,下一次调用跳转时可以跳转到精确位置。


5.8 本章小结

本章介绍了链接的概念和功能,分析可执行文件hello的ELF格式及其假造地点空间,并对重定位、动态链接举行深入的分析。
(第5章1分)



6hello进程管理


6.1 进程的概念与作用

概念:进程可以被界说为正在执行的程序的一个实例。每个程序在系统中都在一个特定的进程上下文中运行。这个上下文包含了程序精确执行所需的状态信息,包罗内存中程序的代码和数据、栈、通用寄存器的内容、程序计数器、情况变量和打开的文件形貌符等。

作用:每当用户通过 shell 输入可执行文件的名称以运行程序时,shell 会创建一个新的进程,并在这个新进程的上下文中执行该可执行文件。应用程序也可以创建新的进程,并在这些新进程的上下文中运行自身代码或其他应用程序。如许,系统可以有用地管理和调理各个程序的执行。

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

作用:shell是指为使用者提供操纵界面的软件,是一个交互型应用级程序,它接收用户下令然后调用相应的应用程序。shell提供了用户与内核举行交互操纵的接口,最紧张的功能是下令解释。Linux系统上的所有可执行文件都可以作为shell下令来执行,同时它也提供一些内置下令。别的shell还包罗通配符、下令补全、下令汗青、重定向、管道、下令替换等功能。

Shell的处理流程大致如下:


6.3 Hello的fork进程创建过程

父进程通过调用 fork函数来创建一个新的子进程。这个新创建的子进程几乎完全复制了父进程的情况,包罗代码段、数据段、堆、共享库以及用户栈,但它们是相互独立的副本。



在创建子进程时,子进程会继承父进程中任何打开的文件形貌符的副本。这意味着子进程可以读写父进程中打开的文件。父进程和子进程之间的主要区别在于它们拥有差别的进程ID(PID)。

fork 函数的独特之处在于它只调用一次,却返回两次:一次在父进程中返回,返回值是新创建的子进程的PID;一次在子进程中返回,返回值为0。通过查抄 `fork` 的返回值,程序可以判断当前执行的是父进程还是子进程。

6.4 Hello的execve过程

execve函数在当前进程的上下文中加载并运行指定的可执行文件(如 hello),并传递参数列表 argv 和情况变量列表 envp。与 fork差别,fork 函数调用一次会返回两次:一次在父进程中,另一次在子进程中。而 execve函数则只调用一次,而且永远不会返回到调用程序,除非发生错误(比方找不到指定的 hello 文件)。因此,execve会将当前进程完全替换为新的可执行文件的进程映像。
6.5 Hello的进程执行


在程序运行时,Shell 会为hello 程序 fork 一个子进程。这个子进程有独立的逻辑控制流,与 Shell 分开运行。在 `hello` 程序的执行过程中,如果该进程不被抢占,它会正常执行;如果被抢占,系统会进入内核模式,举行上下文切换,然后再转入用户模式以调理其他进程。

当hello调用 sleep 函数时,为了最大化处理器资源的使用,sleep 会向内核发送哀求将 hello挂起,并举行上下文切换。系统会切换到内核模式,将当前运行的 hello进程从运行队列中移除,放入等待队列,接着切换到其他进程并返回用户模式,继承执行被抢占的进程。

在 hello 进程被放入等待队列后,内核会开始计时。计时结束时,sleep函数返回,触发中断,使得 hello 进程重新被调理。系统会将 hello 从等待队列中移出,切换回内核模式,然后转为用户模式。此时,hello进程可以继承执行其逻辑控制流。
6.6 hello的异常与信号处理

hello执行过程中会出现4类异常:


信号处理方式:


图 6.6-1 中断处理


图 6.6-2 陷阱处理



图 6.6-3故障处理


图 6.6-4 停止处理

程序运行过程中,分别举行如下操纵:

回车不影响正常运行,只是插入了空行。


图6.6-6回车运行结果

进程收到SIGTSTP信号,临时挂起,输入ps下令符查察PID发现hello的PID为6262,hello进程还没有被关闭,输入fg下令恢复背景进程。

再次Ctrl-Z后,输入“kill -9 -6262”下令后,hello进程被停止,输入ps下令进程hello不存在。



图 6.6-7 Ctrl-Z运行结果

输入pstree查察进程树:


图6.6-7 进程树(部分)


图 6.6-8 进程树(部分)

发送SIGINT信号,结束hello。在ps中没有其相关信息。


图 6.6-9 Ctrl-Z运行结果

输入的字符被保存在缓冲区,被认为是下令,在程序结束后执行。


图 6.6-10 中途乱按运行结果

6.7本章小结

在这一部分,我们探究了hello进程的概念以及shell的功能。我们详细分析了hello进程的执行过程,探究了fork和execve在程序运行中的作用。别的,我们还对信号处理举行了相识,并深入研究了hello进程在内核和用户空间之间怎样反复跳跃执行的情况。

(第6章1分)


7hello的存储管理


7.1 hello的存储器地点空间

逻辑地点是程序产生的与段相关的偏移地点部分,如hello.o中的相对偏移地点。
线性地点是地点空间中的连续整数地点聚集,即hello程序中的假造内存地点。CPU生成的假造地点即为假造地点,如hello程序中的假造内存地点。
物理地点则是盘算机主存中每个字节唯一的地点,即运行时假造内存地点对应的物理地点。
7.2 Intel逻辑地点到线性地点的变换-段式管理

为了最大限度地使用内存空间,Intel 8086设置了四个段寄存器,用于存储段地点:CS(代码段寄存器)、DS(数据段寄存器)、SS(堆栈段寄存器)、ES(附加段寄存器)。

当程序执行时,需要确定代码、数据和堆栈在内存中的位置,这通过设置段寄存器CS、DS和SS来指示这些起始位置。通常情况下,DS保持稳定,而根据需要修改CS。这使得程序可以在小于64K的可寻址空间内以恣意大小编写,限定了程序及其数据组合的大小在DS指向的64K内,这也是COM文件大小限定为64K的原因。

段寄存器是为了对内存举行分段管理而设置的。在形貌内存分段时,需要思量段的大小、起始地点和管理属性(如写入权限、执行权限、系统专用等)。

在保护模式中,段寄存器存放段选择符,其中前13位是一个索引号,背面的3位包含一些硬件细节。根据段选择符,处理器在全局段形貌符表(GDT)或局部段形貌符表(LDT)中查找段形貌符,然后使用段地点和偏移地点盘算线性地点。

而在实模式中,段寄存器包含段值。在访问内存时,处理器将相应的段寄存器值乘以16,形成20位的段基地点,然后通过段基地点加上偏移地点来盘算线性地点。
7.3 Hello的线性地点到物理地点的变换-页式管理

Intel处理器通过页式管理将线性地点转换为物理地点,具体步调如下:

1. 获取页目录基地点:
   从控制寄存器CR3中取出当前进程的页目录的地点,并取出其前20位,这是页目录的基地点。

2. 盘算页目录项地点:
   使用页目录基地点和线性地点的前10位举行组合,得到线性地点前10位的索引对应的项在页目录中的地点。通过该地点可以取到页目录项,该项的值即为二级页表的基址。

3. 盘算页表项地点:
   根据第二步得到的二级页表基址,取其前20位,并将线性地点的第10到第19位左移2位,按照与第二步相同的方式举行组合,得到线性地点对应的物理页框在内存中的地点在二级页表中的地点。读取该地点上的值,就得到了线性地点对应的物理页框在内存中的基地点。

4. 盘算物理地点:
   使用第三步得到的物理页框基地点的前20位,再根据线性地点的最后12位偏移量,盘算出具体的物理地点。读取该物理地点上的值,即为最终需要的值。

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

如果TLB掷中,MMU从TLB中取出相应的PTE,将假造地点翻译为物理地点。如果TLB不掷中,根据VPN1在一级页表中选择对应的PTE,该PTE包含二级页表的基地点;根据VPN2在二级页表中选择对应的PTE,该PTE包含三级页表的基地点;根据VPN3在三级页表中选择对应的PTE,该PTE包含四级页表的基地点;在四级页表中取出对应的PPN,与VPO串联起来,就得到相应的物理地点。
7.5 三级Cache支持下的物理内存访问

得到物理地点PA之后,根据缓存大小和组数的要求,将PA拆分成CT(标记)、CI(索引)和CO(偏移量)。用CI位举行索引,如果匹配成功且valid值为1,则为掷中,根据偏移量在L1缓存中取数。如果未掷中,就去二级和三级缓存中重复以上步调,掷中后返回结果。7.6 hello进程fork时的内存映射

7.7 hello进程execve时的内存映射

execve函数在当前进程中加载并运行包含可执行目标文件hello中的程序,加载、运行 hello 需要以下步调:
1. 删除 shell 假造地点的用户部分中的已存在的地域布局。
2. 映射私有地域。为 hello 的代码、数据、bss 和栈地域创建新的地域布局。所有这些新的地域都是私有的、写时复制的。代码和数据地域被映射为hello 文件中的.text 和.data 区。bss 地域是哀求二进制零的,映射到匿名文件,其大小包含在 hello 中。栈和堆地域也是哀求二进制零的,初始长度为零。
3. 映射共享地域。如果 hello 程序与共享对象(或目标)链接,好比标准 C 库libc. so, 那么这些对象都是动态链接到这个程序的,然后再映射到用户假造地点空间中的共享地域内。
4. 设置程序计数器(PC) 。execve 做的最后一件事变就是设置当前进程上下文中的程序计数器,使之指向代码地域的入口点。
经过此内存映射的过程,下一次调理hello进程时,就能够从hello的入口点开始执行了。

7.8 缺页故障与缺页中断处理
当发生缺页异常时,控制会转移到内核的缺页处理程序。缺页处理程序起首判断假造地点是否合法。如果假造地点不合法,则会产生一个段错误并停止进程。如果假造地点合法,缺页处理程序会执行以下步调:
1. 确定牺牲页:从物理内存中选择一个页面作为牺牲页。如果该牺牲页已被修改过,则将其内容写回到磁盘(换出)。
2. 换入新页面:将所需的新页面从磁盘读入物理内存中,并更新页表以反映新页面的位置。
3. 返回处理器:缺页处理程序完成后,控制返回给CPU,重新执行引起缺页的指令,并将缺页的假造地点重新发送给MMU。
由于新的假造页面现在已缓存在物理内存中,此时会掷中,主存将所哀求的字返回给处理器,从而继承执行程序。
7.9动态存储分配管理

动态内存分配管理使用动态内存分配器来举行。动态内存分配器维护着一个进程的假造内存地域,称为堆。分配器将堆视为一组差别大小的块的聚集来维护,每个块就是一个连续的假造内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可以用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配的状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。



分配器有两种风格——显式分配器和隐式分配器。C语言中的malloc程序包是一种显式分配器。显式分配器必须在一些相当严格的束缚条件下工作:①处理恣意哀求序列;②立刻响应哀求;③只使用堆;④对齐块(对齐要求);⑤不修改已分配的块。在以上限定条件下,分配器要最大化吞吐率和内存使用率。

常见的放置计谋包罗:首次适配,从头开始搜索空闲链表,选择第一个合适的空闲块。下一次适配,雷同于首次适配,但从上一次查找结束的地方开始搜索。最佳适配,选择所有空闲块中得当所需哀求大小的最小空闲块。

一些组织内存块的方法包罗:

1. 隐式空闲链表:空闲块通过头部中大小字段隐含连接,可添加边界标记提高合并空闲块的速度。

2. 显式空闲链表:在隐式空闲链表块布局的底子上,在每个空闲块中添加一个前驱(pred)指针和一个后继(succ)指针。

3. 分离的空闲链表:将块按块大小划分大小类,分配器维护一个空闲链表数组,每个大小类一个空闲链表,淘汰分配时间同时也提高了内存使用率。C语言中的malloc程序包采用的就是这种方法。

4. 红黑树等树形布局:按块大小将空闲块组织为树形布局,同样有淘汰分配时间和提高内存使用率的作用。

7.10本章小结

在本章中,我们深入探究了hello程序的存储器地点空间,介绍了Intel处理器的段式管理以及hello程序的页式管理。详细讲解了在特定情况下VA到PA的转换过程,涉及了TLB的作用以及缺页异常处理的流程。别的,还探究了进程fork和execve时的内存映射机制,以及动态内存分配管理所涉及的主要内容,包罗差别的分配器风格和常见的放置计谋。


(第7章 2分)


8hello的IO管理


8.1 Linux的IO装备管理方法

装备的模子化:文件
装备管理:unix io接口
装备的模子化将所有的 I/O 装备(如网络、磁盘和终端)都抽象为文件的形式。因此,对这些装备的读写操纵就可以像对文件一样举行。这种文件系统的计划方式使得操纵系统能够以统一的方式管理装备,并提供了一种简朴、低级的应用接口,即 Unix I/O 接口。
在 Unix I/O 接口中,所有的输入和输出操纵都通过文件形貌符举行。每个打开的文件(包罗装备文件)都有一个相应的文件形貌符,应用程序可以通过文件形貌符来举行读取和写入操纵,而无需相识底层装备的细节。这种计划使得应用程序能够以一种统一且一致的方式与差别类型的装备举行交互,大大简化了程序的开辟和维护工作。
8.2 简述Unix IO接口及其函数

8.2.1Unix IO接口

1. 打开文件“应用程序通过调用 open 函数来打开一个已存在的文件或创建一个新文件。open 函数将文件名转换为文件形貌符,并返回一个小的非负整数作为形貌符,用于在后续的文件操纵中标识该文件。打开的文件信息由内核管理,并保留在相应的数据布局中。对于每个进程,通常有三个预先打开的文件:标准输入、标准输出和标准错误。

2. 改变当前文件位置:对于每个打开的文件,内核维护着一个文件位置 k,初始值为0,表示从文件开头开始的字节偏移量。应用程序可以通过执行 seek 函数来显式地改变当前文件位置 k。

3. 读写文件:读操纵从文件中复制指定数量的字节到内存中,并将当前文件位置 k 增加相应的字节数。写操纵从内存中复制指定数量的字节到文件中,并更新当前文件位置 k。

4. 关闭文件:应用程序通过调用 close 函数来关闭一个打开的文件。close 函数释放文件打开时创建的数据布局,并将文件形貌符恢复到可用的形貌符池中。

8.2.1Unix IO函数:
打开文件:open()函数

关闭文件:close()函数

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

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

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


8.3 printf的实现分析

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;

}

printf程序按照格式fmt结合参数args生成格式化之后的字符串,并返回字串的长度。printf用了两个外部函数,一个是vsprintf,另有一个是write。


所引用的vsprintf函数:

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函数的作用是将所有的参数内容格式化之后存入buf,然后返回格式化数组的长度。

write函数是将buf中的i个元素写到终端的函数。


从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析

getchar函数在程序中的作用是等待用户从键盘输入信息,而且在用户输入有用信息时将其放入字符缓冲区。具体来说:
当程序调用getchar 时,程序会等待用户从键盘输入信息。
输入的字符被放入字符缓冲区,但getchar不会立刻处理。
当用户输入回车键时,getchar会从字符缓冲区以字符为单位读取数据,但不会读取回车键和文件结束符。
getchar的实现依赖于异步异常,即键盘中断处理程序。当用户举行键盘输入时,盘算机接收到键盘中断信号,并从当前进程跳转到键盘中断处理程序。
键盘中断处理程序将键盘中断通码和断码转换为按键编码,并将其缓存到键盘缓冲区。
然后控制器将使命交换回原来的使命(即getchar地点的进程),如果没有碰到回车键,则继承等待用户输入,重复上述过程。
当 getchar 碰到回车键时,它会按字节读取键盘缓冲区中的内容,并处理完毕后返回。如许 getchar 进程就结束了。

8.5本章小结

本章介绍了Unix I/O,通过LinuxI/O装备管理方法以及Unix I/O接口及函数相识系统级I/O的底层实现机制,并通过对printf和getchar函数的底层解析加深对Unix I/O以及异常中断等的相识。
(第8章1分)


结论

hello的一生主要经过一下过程:
1. 程序员编写了 hello.c,并存储在内存中。

2. 预处理器处理 hello.c 得到 hello.i。

3. 编译器将 hello.i 编译成汇编语言得到 hello.s。

4. 汇编器将 hello.s 汇编成可重定位目标文件 hello.o。

5. 链接器将 hello.o 和其他目标文件链接生成可执行目标文件 hello。

6. 在 shell 中运行 hello 程序。

7. Shell 创建子进程并调用 hello。

8. hello 程序运行,调用 execve 加载程序。

9. 执行 hello 程序的逻辑控制流。

10. 通过三级缓存访问内存,将假造地点映射成物理地点。

11. 处理信号和异常控制流,根据差别的信号执行差别的操纵。

12. 当进程结束或被 kill 时,回收子进程。

深入理解盘算机系统的各个方面,从硬件到软件,从底层到高层,都对于盘算机科学领域的从业者来说至关紧张。这种理解可以帮助我们更好地计划和优化程序,更有用地管理系统资源,以及更好地保护系统安全性。掌握这些知识可以使我们成为更全面、更有竞争力的盘算机专业职员。

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

附件

文件名

功能

hello.i

预处理后得到的文本文件

hello.s

编译后得到的汇编语言文件

hello.o

汇编后得到的可重定位目标文件

hello.elf

用readelf读取hello.o得到的ELF格式信息

hello.asm

反汇编hello.o得到的反汇编文件

hello2.elf

由hello可执行文件生成的.elf文件

hello2.asm

反汇编hello可执行文件得到的反汇编文件


(附件0分,缺失 -1分)


参考文献


为完本钱次大作业你翻阅的书籍与网站等

[1] 袁春风. 盘算机系统底子. 北京:呆板工业出书社,2018.7(2019.8重印)
[2] Randal E.Bryant, David O'Hallaron. 深入理解盘算机系统[M]. 呆板工业出书社.2018.4 
(参考文献0分,缺失 -1分)




免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4