哈工大计算机体系大作业----步伐人生

打印 上一主题 下一主题

主题 629|帖子 629|积分 1887

摘  要

本研究聚焦于在Linux体系下,以简单的C语言文件hello.c为对象,全面深入地探讨了该步伐的完备生命周期。从最初的原始步伐出发,体系性地研究了编译、链接、加载、运行、停止和接纳等关键阶段,以揭示hello.c文件的整个“一生”过程。通过深入分析hello.c步伐,将重点放在其在Ubuntu体系下的实行过程上,并团结《深入理解计算机体系》一书的相关理论知识以及在课堂上老师的深入讲解。通过对hello步伐的生命周期进行研究,致力于将计算机体系的各个方面有机地串联起来,使之形成一个完备而深刻的体系。

关键词:计算机体系;C语言;步伐生命周期;底层原理;                           

(摘要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简介

Hello的P2P是指hello.c文件从可实行步伐(Program)变为运行时历程(Process)的过程。在Linux体系下,hello.c文件依次经过cpp(C Pre-Processor,C预处置惩罚器)预处置惩罚、ccl(C Compiler,C编译器)编译、as(Assembler,汇编器)汇编、ld(Linker,链接器)链接最终成为可实行目的步伐hello(在Linux下该文件无固定后缀)。打开shell,输入命令./hello后,shell通过fork产生子历程,hello便从可实行步伐(Program)变成为历程(Process)。
Hello的020是指hello.c文件“From Zero-0 to Zero-0”,初始时内存中并无hello文件的相关内容,这便是“From Zero-0”。通过在shell下调用execve函数,体系会将hello文件载入内存,实行相关代码,当步伐运行结束后,hello历程被接纳,并由内核删除hello相关数据,这即为“to Zero-0”。
1.2 环境与工具

硬件:AMD Ryzen 7 5800H with Radeon Graphics  3.20 GHz
      16GB RAM
      512GB SSD + 1T SSD
软件:Windows 11 家庭中文版 64 位
      VMware Workstation 16.2.2
Ubuntu 20.04.4 LTS 64 位
调试工具:Visual Studio 2022 64-bit;
gcc,readelf, objdump, edb
1.3 中心结果

表格 1 中心结果

文件名

描述

hello.i

预处置惩罚后得到的ASCII码的中心文件

hello.s

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

hello.o

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

hello1.elf

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

asm1.txt

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

hello2.elf

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

asm2.txt

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


1.4 本章小结

本章简要先容了hello的P2P,020的具体含义,同时列出了研究时采用的软硬件环境和中心结果步伐。
(第1章0.5分)



第2章 预处置惩罚

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

2.1.1 预处置惩罚的概念
预处置惩罚步调是指步伐开始运行时,预处置惩罚器(cpp,C Pre-Processor,C预处置惩罚器)根据以字符#开头的命令,修改原始的C步伐的过程。例如,hello.c文件7到9行中的#include命令会告诉预处置惩罚器读取体系头文件stdio.h,unistd.h,stdlib.h的内容,并把这些内容直接插入到步伐文本中。用现实值更换用#define界说的字符串。除此之外,预处置惩罚过程还会删除步伐中的注释和多余的空白字符。预处置惩罚通常得到另一个以.i作为拓展名的ASCII码的中心文件。
2.1.2 预处置惩罚的作用
预处置惩罚过程将#include后继的头文件内容直接插入步伐文本中,完成字符串的更换,方便后续处置惩罚。预处置惩罚过程中并未直接解析步伐源代码的内容,而是对源代码进行相应的分割、处置惩罚和更换。简单来说,预处置惩罚是一个文本插入与更换的过程,生成的 hello.i文件仍然是文本文件。

2.2在Ubuntu下预处置惩罚的命令

在Ubuntu体系下,进行预处置惩罚的命令为:
cpp hello.c > hello.i

图 2-1 预处置惩罚过程
2.3 Hello的预处置惩罚结果解析

在Linux下打开hello.i文件,可以发现hello.i步伐已经拓展为3061行,行数比起hello.c文件大幅增加。其中,hello.c中的main函数相关代码在hello.i步伐中对应着3048行到3061行。

图 2-2 预处置惩罚结果部门展示
在main函数内代码出现之前是大段的头文件stdio.h unistd.h stdlib.h的依次展开。展开的具体流程概述如下(以stdio.h为例):CPP先删除指令#include <stdio.h>,并到Ubuntu体系的默认的环境变量中寻找stdio.h,最终打开路径/usr/include/stdio.h下的stdio.h文件。若stdio.h文件中利用了#define语句,则按照上述流程继续递归地展开,直到全部#define语句都被表明更换掉为止。除此之外,CPP还会进行删除步伐中的注释和多余的空白字符等操作,并对一些值进行更换。
2.4 本章小结

本章主要先容了预处置惩罚的概念及作用、并团结hello.c文件预处置惩罚之后得到的hello.i步伐对预处置惩罚结果进行了解析。

(第2章0.5分)


第3章 编译


3.1 编译的概念与作用

3.1.1 编译的概念
编译是指C编译器ccl通过词法分析和语法分析,将正当指令翻译成等价汇编代码的过程。通过编译过程,编译器将文本文件hello.i翻译成汇编语言文件hello.s,在hello.s中,以文本的形式描述了一条条低级呆板语言指令。
3.1.2 编译的作用
将文本文件翻译成汇编语言步伐,为后续将其转化为二进制呆板码做预备。
注意:这儿的编译是指从 .i 到 .s 即预处置惩罚后的文件到生成汇编语言步伐      

3.2 在Ubuntu下编译的命令

根据PPT要求,在Ubuntu体系下,进行预处置惩罚的命令为:
gcc -m64 -no-pie -fno-PIC -S hello.i -o hello.s

图 3-1 编译过程
3.3 Hello的编译结果解析

3.3.1 文件布局分析
对hello.s文件整体布局分析如下:
表格 2  hello.s文件布局

内容

含义

.file

源文件

.text

代码段

.section .rodata

存放只读变量

.align

对齐方式

.global

全局变量

.type

表示是函数类型/对象类型

.size

表示大小

.long .string

表示是long类型/string类型


3.3.2 数据类型
在hello.s中,涉及的数据类型包罗以下三种:整数,字符串,数组。下面对每种数据类型依次进行分析。
一、整数
在hello.s中,涉及的整数有:

  • int i
检察C语言文件可知,i为int型局部变量。编译器将局部变量存储在寄存器大概栈空间中。i作为函数内部的局部变量,并不占用文件现实节的空间,只存在于运行时栈中。对于i的操作就是直接对寄存器或栈进行操作。
在 hello.s 中我们可以看出,i占据了4字节的地址空间:

图 3-2 整数
其中movl表示传送双字,‘l’表示4字节整数。

  • int argc
argc是main函数的第一个参数,64位编译下,由寄存器%rdi传入,进而保存在栈中。

图 3-3 argv

  • 立即数4
立即数4在汇编语句中直接以$4的形式出现,对应C源步伐中if(argc!=4)

图 3-4 立即数


  • 字符串
hello.s中保存了两个字符串,分别为:

图 3-5 字符串
两者均为字符串常量,储存在.text 数据段中。\XXX为UTF-8编码,一个汉字对应三个字节。


  • 数组
步伐中涉及的数组为char *argv[],即函数的第二个参数。在hello.s中,其首地址由寄存器%rsi传入,进而保存在栈中。

图 3-6 数组
访问时,通过如下寄存器寻址的方式访问,-32(%rbp)是数组的首地址即argv[0]的地址,+16得到argv[2]的地址,+8得到argv[1]的地址,这是因为char *数据类型大小为8字节。

图 3-7 数组
3.3.3 赋值操作
1. int i
在for循环中,i起首被赋值为0,然后再经过一系列++操作:

图 3-8 赋值操作
对局部变量的赋值在汇编代码中通过mov指令完成。具体利用哪条mov指令由数据的大小决定,如图所示:

图 3-9 数据传输指令
3.3.4 类型转换
    在 C 语言源步伐中包罗一个类型转换:
    

atoi完成从字符串到整数的类型转换,返回值保存到%rax中,再传入%rdi作为sleep的参数,接着调用sleep

3.3.5 算数操作
汇编语言中,算数操作的指令包罗:

图 3-10 算术操作

图 3-11 算术操作
在hello.s中,具体涉及的算数操作包罗:

  • subq   $32, %rsp 开发栈帧
  • addq  $16, %rax 修改地址偏移量
  • addq  $8, %rax  修改地址偏移量
  • addq   $24, %rax 修改地址偏移量
  • addl   $1, -4(%rbp) 实现 i++的操作
3.3.6 关系操作
在 hello.s 中,具体涉及的关系操作包罗:

  • argv!=4
检查argc是否不等于3。在hello.s中,利用cmpl $4, -20(%rbp),比力 argc
与4的大小并设置条件码,为下一步je利用条件码进行跳转作预备。
    


  • i<8
检查i是否小于8。在hello.s中,利用cmpl   $7, -4(%rbp)比力i与7的大小,然后设置条件码,为下一步jle利用条件码进行跳转做预备。
    

3.3.7 数组操作
如前述3.3.2所述,hello.s中存在数组char *argv[],对其的访问操作通过寄存器寻址方式实现。
3.3.8 控制转移
步伐中控制转移的具体表现有两处:

  • if(argc!=4)
当argc不等于4时,实行函数体内部的代码。在hello.s中,利用cmpl $4, -20(%rbp),比力argc与4是否相称,若相称,则跳转至.L2,不实行后续部门内容;若不等则继续实行函数体内部对应的汇编代码。


  • for(i=0;i<8;i++)
当i < 8时进行循环,每次循环i++。在hello.s中,利用cmpl $7, -4(%rbp),比力i与7是否相称,在 i<=7 时继续循环,进入.L4,i>7时跳出循环。

3.3.9 函数操作
C语言中,调用函数时进行的操作如下:
1. 通报控制:
进行过程Q的时间,步伐计数器必须设置为Q的代码的起始地址,然后在返回时,要把步伐计数器设置为P中调用Q背面那条指令的地址。
2. 通报数据:
P必须可以或许向Q提供一个或多个参数,Q必须可以或许向P中返回一个值。
3. 分配和释放内存:
在开始时,Q大概需要为局部变量分配空间,而在返回前,又必须释放这些空间。
具体到hello.s中,步伐入口处,调用了main函数,其在hello.s中标注为@function函数类型。之后又调用puts,printf,sleep,exit,getchar函数,对函数的调用都通过call指令进行。


     图 3-12 分配释放内存
3.4 本章小结

本章先容了编译的概念与作用,编译是将文本文件翻译成汇编语言步伐,为后续将其转化为二进制呆板码做预备的过程。同时,本章以hello.s文件为例,先容了编译器怎样处置惩罚各个数据类型以及各类操作,验证了大部门数据、操作在汇编代码中的实现。
(第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-1 汇编过程
4.3 可重定位目的elf格式

在shell中输入readelf -a hello.o > hello1.elf指令获得hello.o文件的ELF格式:

图 4-2 生成elf格式文件
其布局分析如下:

  • ELF头(ELF Header):
以16字节序列Magic开始,其描述了生成该文件的体系的字的大小和字节顺序,ELF头剩下的部门包罗帮助链接器语法分析和表明目的文件的信息,其中包罗ELF头大小、目的文件类型、呆板类型、节头部表的文件偏移,以及节头部表中条目的大小和数目等相关信息。

图 4-3 ELF头

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

图 4-4 节头

  • 重定位节.rela.text:
一个.text节中位置的列表,包罗.text节中需要进行重定位的信息,当链接器把这个目的文件和其他文件组合时,需要修改这些位置。在这里,8条重定位信息分别是对.L0(第一个printf中的字符串)、puts函数、exit函数、.L1(第二个printf中的字符串)、printf函数、atoi函数、sleep函数、getchar函数进行重定位声明。

图 4-5 重定位节.rela.text
      2.重定位节.rela.eh_frame:

图 4-6 重定位节.rela.eh_frame
      3.符号表symbol table
符号表中保存着定位、重定位步伐中符号界说和引用的信息,全部重定位需要引用的符号都在其中声明。

图 4-7 符号表
4.4 Hello.o的结果解析

利用objdump -d -r hello.o > asm1.txt分析hello.o的反汇编,并与第3章的hello.s文件进行对照分析。

图 4-8 反汇编hello.o
通过对比asm1.txt与hello.s可知,两者在如下地方存在差异:
1. 分支转移:
在hello.s中,跳转指令的目的地址直接记为段名称,如.L2,.L3等。而在反汇编得到的asm1.txt中,跳转的目的为具体的地址,在呆板代码中表现为目的指令地址与当前指令下一条指令的地址之差,如下。

19+14=2d

36+46=7c

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


    3. .rodata数据访问:
在hello.s文件中,利用段名称访问.rodata中数据(printf中的字符串),而在反汇编得到的asm1.txt中,利用0地址进行访问。.rodata中数据地址在链接时才气确定,故需要重定位。在链接时,将0地址更换为绝对地址。

4.5 本章小结

本章先容了汇编的概念与作用,在Ubuntu下通过现实操作将hello.s文件翻译为hello.o文件,并生成hello.o的ELF格式文件hello.elf,研究了ELF格式文件的具体布局。通过比力hello.o的反汇编代码(保存在asm1.txt中)与hello.s中代码,了解了汇编语言与呆板语言的异同之处。
(第4章1分)


5链接


5.1 链接的概念与作用

5.1.1 链接的概念
链接是指通过链接器(Linker),将步伐编码与数据块网络并整理成为一个单一文件,生成完全链接的可实行的目的文件(Windows体系下为.exe文件,Linux体系下一般省略后缀名)的过程。
5.1.2 链接的作用
提供了一种模块化的方式,可以将步伐编写为一个较小的源文件的聚集,且实现了分开编译更改源文件,从而淘汰整体文件的复杂度与大小,增加容错性,也方便对某一模块进行针对性修改。
注意:这儿的链接是指从 hello.o 到hello生成过程。
5.2 在Ubuntu下链接的命令

在Ubuntu下链接的命令如下:
ld -o hello -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/gcc/x86_64-linux-gnu/9/crtbegin.o /usr/lib/gcc/x86_64-linux-gnu/9/crtend.o /usr/lib/x86_64-linux-gnu/crtn.o hello.o -lc -z relro
链接过程如下:

图 5-1 链接过程
5.3 可实行目的文件hello的格式

在shell中输入命令readelf -a hello > hello2.elf生成hello步伐的ELF格式文件,保存为hello2.elf:

图 5-2 生成hello的elf文件
打开hello2.elf,分析hello的ELF格式如下:

  • ELF头(ELF Header):
hello2.elf中的ELF头与hello.elf中的ELF头包罗的信息种类基本相同,以描述了生成该文件的体系的字的大小和字节顺序的16字节序列Magic开始,剩下的部门包罗帮助链接器语法分析和表明目的文件的信息。与hello1.elf相比力,hello2.elf中的基本信息未发生改变(如 Magic,种别等),而类型发生改变,步伐头大小和节头数目增加,而且获得了入口地址。

图 5-3 ELF头

  • 节头:
hello2.elf中的节头包罗了文件中出现的各个节的语义,包罗节的类型、位置、偏移量和大小等信息。与 hello1.elf 相比,其在链接之后的内容更加丰富具体(此处仅截取部门展示)。


图 5-4 节头

  • 步伐头:
步伐头部门是一个布局数组,描述了体系预备步伐实行所需的段或其他信息。


图 5-5 步伐头

  • Dynamic section:

图 5-6 Dynamic section

  • Symbol table:
符号表中保存着定位、重定位步伐中符号界说和引用的信息,全部重定位需要引用的符号都在其中声明(此处仅截取部门展示)。

图 5-7 Symbol table
5.4 hello的假造地址空间

打开edb,通过data dump检察加载到假造地址的步伐代码。

图 5-8 hello假造地址空间
根据Linux x86-64的特性,假造地址空间的起始地址为0x400000,如上图。由hello2.elf中的节头得到.interp段的起始地址为0x4002e0:

在edb中找到.interp段:

图 5-9 .interp
.text段的起始地址为0x4010f0:

在edb中找到.text段:

图 5-10 .text
.rodata段的起始地址为0x402000:

在edb中找到.rodata段:

图 5-11 .rodata
5.5 链接的重定位过程分析

在shell中利用命令objdump -d -r hello > asm2.txt生成反汇编文件asm2.txt,与第四章中生成的asm1.txt文件进行比力,其差别之处如下:

图 5-12 反汇编hello

  • 链接后函数数目增加。链接后的反汇编文件asm2.txt中,多出了puts@plt,printf@plt,getchar@plt,exit@plt,sleep@plt等函数的代码。这是因为链接器实行了一些静态链接以用于后续动态链接器将共享库中hello.c用到的函数加入假造内存中。

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

图 5-14 指令call的参数发生变化
4011f9 + fffffe97 = (100)401090
401203 + fffffecd = (100)4010d0
      3.跳转指令参数发生变化。

图 5-15 跳转指令参数发生变化

5.6 hello的实行流程

利用edb实行hello,说明从加载hello到_start,到call main,以及步伐停止的全部过程。请列出其调用与跳转的各个子步伐名或步伐地址。
利用edb实行hello,连续单步实行,观察遇到的jmp指令和call指令及其操作数,发现子步伐则step into进入,该过程中发现了如下图中的子步伐:

图 5-16 edb实行hello



图 5-17 edb追踪过程
汇总各子步伐及其地址如下:
表格 3 步伐名称与步伐地址

步伐名称

步伐地址

hello!_start

0x4010f0

libc-2.31.so!__libc_start_main

0x7febeca2af90

libc-2.31.so!__cxa_atexit

0x7febeca4dde0

hello!__libc_csu_init

0x401270

hello!_init

0x401000

frame_dummy

0x4011d0

hello!register_tm_clones

0x401160

libc-2.31.so!_setjmp

0x7fc8d54b9c80

libc-2.31.so!_sigsetjmp

0x7f4d51077bb0

hello!main

0x4011d6

printf@plt

0x4010a0

atoi@plt

0x4010c0

sleep@plt

0x4010e0

getchar@plt

0x4010b0

libc-2.31.so!exit

0x7f08323daa40


5.7 Hello的动态链接分析

编译器没有办法猜测函数的运行时地址,所以需要添加重定位记录,等待动态链接器处置惩罚,为制止运行时修改调用模块的代码段,链接器采用耽误绑定的策略。动态链接器利用过程链接表PLT+全局偏移量表GOT实现函数的动态链接,在GOT中存放函数目的地址,PLT利用GOT中地址跳转到目的函数,在加载时,动态链接器会重定位GOT中的每个条目,使得它包罗目的的正确的绝对地址。
观察hello2.elf得.got从地址0x403ff0开始。

通过edb检察,在dl_init调用前,其内容如下:

图 5-18 dl_init调用前.got内容
在调用后,其内容变为:

图 5-19 dl_init调用后.got内容
5.8 本章小结

本章中先容了链接的概念与作用、并得到了链接后的hello可实行文件的ELF格式文本hello2.elf,据此分析了hello2.elf与hello.elf的异同;之后,根据反汇编文件asm2.txt与asm1.txt的比力,加深了对重定位与动态链接的理解。
(第5章1分)



6hello历程管理


6.1 历程的概念与作用

6.1.1 历程的概念
历程是一个正在运行的步伐的实例,体系中的每一个步伐都运行在某个历程的上下文中。
6.1.2 历程的作用
给应用步伐提供两个关键抽象:
1. 一个独立的逻辑控制流,提供一个假象,好像步伐独占地利用处置惩罚器
2. 一个私有地址空间,提供一个假象,好像步伐独占地利用内存体系
6.2 简述壳Shell-bash的作用与处置惩罚流程

shell的作用:
shell是一个交互性的应用级步伐,它代表用户运行其他步伐。shell实行一系列的读/求值步调,然后停止。读步调读取来自用户的一个命令行。求值步调解析命令行,并代表用户运行步伐。
shell的处置惩罚流程大抵如下:
1.shell打印一个命令行提示符,等待用户在stdin上输入命令行,然后对这个命令行求值。
2.对命令行求值的代码起首调用parseline函数,这个函数解析了以空格分隔的命令行参数,并构造最终会通报给execve的argv向量。第一个参数被假设为要么是一个内置的shell命令名,要么是一个可实行目的文件,会在一个新的子历程的上下文中加载并运行这个文件。如果末了一个参数是一个“&”字符,那么parseline返回1,表示应该在后台实行该步伐(shell不会等待它完成)。否则,它返回0,表示应该在前台实行这个步伐(shell会等待它完成)。
3.在解析了命令行之后,eval函数调用builtin_command函数,该函数检查第一个命令行参数是否是一个内置的shell命令,如果是,它就立即表明这个命令,并返回值1。否则返回0。
4.如果builtin_command返回0,那么shell创建一个子历程,并在子历程中实行所哀求的步伐。如果用户要求在后台运行该步伐,那么shell返回到循环的顶部,等待下一个命令行。否则,shell利用waitpid函数等待作业停止。当作业停止时,shell就开始下一轮迭代。
6.3 Hello的fork历程创建过程

在shell中输入命令./hello 2022113557 宫名扬 1
fork历程的创建过程如下:起首,带参实行当前目次下的可实行文件hello,父历程会通过fork函数创建一个新的运行的子历程hello。子历程获取了与父历程的上下文,包罗栈、通用寄存器、步伐计数器,环境变量和打开的文件相同的一份副本。子历程与父历程的最大区别是有着跟父历程不一样的PID,子历程可以读取父历程打开的任何文件。当子历程运行结束时,父历程如果仍然存在,则实行对子历程的接纳,否则就由 init 历程接纳子历程。

图 6-1 shell运行hello
6.4 Hello的execve过程

调用函数fork创建新的子历程之后,子历程会调用execve函数,在当前历程的上下文中加载并运行一个新步伐hello。execve函数从不返回,它将删除该历程的代码和地址空间内的内容并将其初始化。将私有的地区映射进来,例如打开的文件,代码、数据段,然后将公共的地区映射进来。然后加载器跳转到步伐的入口点,即设置PC指向_start地址。_start函数调用体系启动函数__libc_start_main,其调用hello中的main函数,这样,便完成了在子历程中的加载。
6.5 Hello的历程实行

在步伐运行时,shell为hello fork了一个子历程,这个子历程与shell有独立的逻辑控制流。在hello的运行过程中,若hello历程不被抢占,则正常实行;若被抢占,则进入内核模式,进行上下文切换,转入用户模式,调度其他历程。直到当hello调用 sleep 函数时,为了最大化利用处置惩罚器资源,sleep函数会向内核发送哀求将hello挂起,并进行上下文切换,进入内核模式切换到其他历程,切换回用户模式运行抢占的历程。与此同时,将hello历程从运行队列加入等待队列,由用户模式变成内核模式,并开始计时。当计时结束时,sleep函数返回,触发一个中断,使得hello历程重新被调度,将其从等待队列中移出,并内核模式转为用户模式。此时hello历程就可以继续实行其逻辑控制流。
6.6 hello的非常与信号处置惩罚


  • 在步伐正常运行时,打印8次提示信息,输入恣意字符结束步伐,并接纳历程。

图 6-2 正常运行
    2. 在步伐运行时乱按,包罗回车,步伐可以正常结束。在步伐实行过程中乱按所造成的输入均缓存到stdin,当getchar的时间读出一个’\n’结尾的字串作为输入,hello结束后,stdin中的其他字串会当做shell的命令行输入。

图 6-3 乱按正常
3. 按下Ctrl + C,历程收到SIGINT信号,结束并接纳hello历程。

图 6-4 Ctrl + C结束历程

  • 按下Ctrl + Z,历程收到SIGSTP信号,shell显示提示信息并挂起hello历程。

图 6-5 Ctrl + Z挂起历程
对hello历程的挂起可由ps和jobs命令检察,可以发现hello历程确实被挂起而非被接纳,且其job代号为1。

图 6-6 ps和jobs检察
在shell中输入pstree命令,可以将全部历程以树状图显示(此处仅展示部门):

图 6-7 pstree检察
输入kill命令,则可以杀死指定历程:

图 6-8 kill杀死历程
大概输入fg %1命令将hello历程再次调到前台实行,shell显示历程continue,hello再从挂起处继续运行,打印剩下的语句。步伐仍然可以正常结束,并完成历程接纳。

图 6-9 fg继续运行
6.7本章小结

本章先容了历程的概念与作用,以及Shell-bash的基本概念。针对历程,在这一章中根据hello可实行文件的具体示例研究了fork,execve函数的原理与实行过程,并给出了hello带参实行情况下各种非常与信号处置惩罚的结果。
(第6章1分)


7hello的存储管理


7.1 hello的存储器地址空间

团结hello说明逻辑地址、线性地址、假造地址、物理地址的概念。
1. 逻辑地址
逻辑地址由两个部门组成:段选择符和偏移量。这两者一起构成了逻辑地址,指向内存中的一个具体位置。
2. 线性地址
逻辑地址经过段机制转化后为线性地址,其为处置惩罚器可寻址空间的地址,用于描述步伐分页信息的地址。具体以hello而言,线性地址标记着hello应在内存上哪些具体数据块上运行。
3. 假造地址
假造地址即为上述线性地址。
4. 物理地址
真实的物理内存对应地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理

Intel处置惩罚器从逻辑地址到线性地址的变换通过段式管理的方式实现。每个步伐在体系中都保存着一个段表,段表保存着该步伐各段装入主存的状况信息,包罗段号或段名、段起点、装入位、段的长度、主存占用地区表、主存可用地区表等,从而方便进行段式管理。在段寄存器中,存放着段选择符,可以通过段选择符来得到对应段首地址。
段选择符是一个16位的值,它由一个索引和一个表(通常是全局描述符表或局部描述符表)标识。这个选择符用来指定一个段的起始地址和访问权限。
通过一个索引,可以定位到段描述符,进而通过段描述符得到段基址。段基址与偏移量团结就得到了线性地址,假造地址。
7.3 Hello的线性地址到物理地址的变换-页式管理

CPU中的一个控制寄存器,页表基址寄存器(PTBR)指向当前页表。n位的假造地址即线性地址包罗两个部门:一个p位的假造页面偏移(VPO)和一个(n一p)位的假造页号(VPN)。MMU利用VPN来选择适当的PTE。例如,VPN 0选择PTE 0,VPN 1选择PTE 1,以此类推。将页表条目中物理页号(PPN)和假造地址中的VPO串联起来,就得到相应的物理地址。因为物理和假造页面都是P字节的,所以物理页面偏移(PPO)和VPO是相同的。

图 7-1 页式管理
若PTE的有效位为1,则发生页命中,可以直接获取到物理页号PPN,PPN与PPO共同组成物理地址。若PTE的有效位为0,说明对应假造页没有缓存到物理内存中,产生缺页故障,调用操作体系的内核的缺页处置惩罚步伐,确定牺牲页,并调入新的页面。再返回到原来的历程,再次调用导致缺页的指令。此时发生页命中,获取到PPN,与PPO共同组成物理地址。
7.4 TLB与四级页表支持下的VA到PA的变换

TLB是一个小的、假造寻址的缓存,其中每一行都保存着一个由单个PTE组成的块。TLB通常有高度的相联度。如图所示,用于组选择和行匹配的索引和标记字段是从假造地址中的假造页号中提取出来的。如果TLB有T=2^t个组,那么TLB索引(TLBI)是由VPN的t个最低位组成的,而TLB标记(TLBT)是由 VPN中剩余的位组成的。

图 7-2 TLB
36位VPN被划分成四个9位的片,每个片被用作到一个页表的偏移量。CR3寄存器包罗L1页表的物理地址。VPN 1提供到一个Ll PTE的偏移量,这个PTE包罗L2页表的基地址。VPN2提供到一个L2 PTE的偏移量,以此类推。第四级页表中PTE包罗一个40位的PPN,它指向物理内存中某一页的基地址,与PPO组成物理地址。

图 7-3 四级页表
7.5 三级Cache支持下的物理内存访问

三级Cache布局如下图:

图 7-4 三级Cache布局
参数S和B将m个地址位分为了三个字段,如图所示。A中s个组索引位是一个到S个组的数组的索引。第一个组是组0,第二个组是组1,依此类推。组索引位被表明为一个无符号整数,它告诉我们这个字必须存储在哪个组中。一旦我们知道了这个字必须放在哪个组中,A中的t个标记位就告诉我们这个组中的哪一行包罗这个字(如果有的话)。当且仅当设置了有效位而且该行的标记位与地址A中的标记位相匹配时,组中的这一行才包罗这个字。一旦我们在由组索引标识的组中定位了由标号所标识的行,那么b个块偏移位给出了在B个字节的数据块中的字偏移。如果缓存不命中,那么它需要从存储器条理布局中的下一层取出被哀求的块,然后将新的块存储在组索引位指示的组中的一个高速缓存行中。一般而言,如果组中都是有效高速缓存行了,那么必须要驱逐出一个现存的行。


图 7-5 Cache布局
在7.4中我们已经由假造地址VA转换得到了物理地址PA,起首利用CI进行组索引,每组8路,对8路的块分别匹配CT(前40位)如果匹配乐成且块的valid标记位为1,则命中(hit),根据数据偏移量 CO 取出相应的数据后返回。若没有匹配乐成大概匹配乐成但是标记位是1,则不命中(miss),向下一级缓存中哀求数据(哀求顺序为 L2 Cache→L3 Cache→主存,若仍不命中才继续向下一级哀求)。查询到数据之后,需要对数据进行读入,一种简单的放置策略如下:若映射到的组内有空闲块,则直接放置在空闲块中,若当前组内没有空闲块,则产生冲突(evict),采用最不常利用(LFU)策略进行更换。

图 7-6 物理内存访问
7.6 hello历程fork时的内存映射

当fork函数被当前历程hello调用时,内核为新历程hello创建各种数据布局,并分配给它一个唯一的PID。为了给这个新的hello创建假造内存,它创建了当前历程的mm_struct、地区布局和页表的原样副本。它将两个历程中的每个页面都标记为只读,并将两个历程中的每个地区布局都标记为私有的写时复制。
当 fork 在新历程中返回时,新历程现在的假造内存刚好和调用fork时存在的假造内存相同。当着两个历程中的任一个厥后进行写操作时,写时复制机制就会创建新页面,因此,也就为每个历程保持了私有地址空间的抽象概念。
7.7 hello历程execve时的内存映射

execve函数加载并运行hello需要以下几个步调:
1. 删除已存在的用户地区
删除当前历程hello假造地址的用户部门中的已存在的地区布局。
2. 映射私有地区
为新步伐的代码、数据、bss和栈地区创建新的私有的、写时复制的地区布局。其中,代码和数据地区被映射为hello文件中的.text和.data区。bss地区是哀求二进制零的,映射到匿名文件,其大小包罗在hello中。栈和堆地区也是哀求二进制零的,初始长度为零。
3. 映射共享地区
若hello步伐与共享对象或目的(如标准C库libc.so)链接,则将这些对象动态链接到hello步伐,然后再映射到用户假造地址空间中的共享地区内。
4. 设置步伐计数器
末了,execve设置当前历程上下文中的步伐计数器,使之指向代码地区的入口点。
7.8 缺页故障与缺页中断处置惩罚

假设MMU在试图翻译某个假造地址A时,触发了一个缺页。这个非常导致控制转移到内核的缺页处置惩罚步伐,处置惩罚步伐随后就实行下面的步调:
1)假造地址A是正当的吗?换句话说,A在某个地区布局界说的地区内吗?为了回答这个题目,缺页处置惩罚步伐搜索地区布局的链表,把A和每个地区布局中的vm_start和vm_end做比力。如果这个指令是不正当的,那么缺页处置惩罚步伐就触发一个段错误,从而停止这个历程。这个情况在图中标识为“1”。
因为一个历程可以创建恣意数目的新假造内存地区,所以顺序搜索地区布局的链表花销大概会很大。因此在现实中,Linux利用某些我们没有显示出来的字段,Linux在链表中构建了一棵树,并在这棵树上进行查找。
2)试图进行的内存访问是否正当?换句话说,历程是否有读、写大概实行这个地区内页面的权限?例如,这个缺页是不是由一条试图对这个代码段里的只读页面进行写操作的存储指令造成的?这个缺页是不是因为一个运行在用户模式中的历程试图从内核假造内存中读取字造成的?如果试图进行的访问是不正当的,那么缺页处置惩罚步伐会触发一个保护非常,从而停止这个历程。这种情况在图中标识为“2”。
3)此刻,内核知道了这个缺页是由于对正当的假造地址进行正当的操作造成的。它是这样来处置惩罚这个缺页的:选择一个牺牲页面,如果这个牺牲页面被修改过,那么就将它互换出去,换入新的页面并更新页表。当缺页处置惩罚步伐返回时,CPU重新启动引起缺页的指令,这条指令将再次发送A到MMU。这次,MMU就能正常地翻译A,而不会再产生缺页中断了。
    

图 7-7 缺页处置惩罚
7.9动态存储分配管理

动态内存分配器维护着一个称为堆的历程的假造内存地区。分配器将堆视为一组差别大小的块的聚集来维护。每个块就是一个一连的假造内存片,要么是已分配的,要么是空闲的。已分配的块显式地保存为供应用步伐利用。空闲块可用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放可以由应用步伐显式实行或内存分配器自身隐式实行。
具体而言,分配器分为两种基本风格:显式分配器、隐式分配器。
显式分配器:要求应用显式地释放任何已分配的块。
隐式分配器:要求分配器检测一个已分配块何时不再利用,那么就释放这个块,自动释放未利用的已经分配的块的过程叫做垃圾网络。
下面先容动态存储分配管理中较为重要的概念:
1. 隐式链表
堆中的空闲块通过头部中的大小字段隐含地连接,分配器通过遍历堆中全部的块,从而间接遍历整个空闲块的聚集。
对于隐式链表,其布局如下:

图 7-8 隐式链表的布局
2. 显式链表
在每个空闲块中,都包罗一个前驱(pred)与后继(succ)指针,从而淘汰了搜索与适配的时间。
显式链表的布局如下:

图 7-9 显式链表的布局
3. 带界限标记的合并
接纳利用界限标记的堆块的格式,在堆块的末尾为其添加一个脚部,其为头部的副本。添加脚部之后,分配器就可以通过检查前面一个块的脚部,判断前面一个块的起始位置和状态。从而实现快速合并,减小性能消耗。
4. 分离存储
维护多个空闲链表,其中,每个链表的块具有相同的大小。将全部大概的块大小分成一些等价类,从而进行分离存储。
7.10本章小结

本章主要先容了hello的存储器地址空间、Intel的段式管理、hello的页式管理,VA到PA的变换、物理内存访问,hello历程fork、execve时的内存映射、缺页故障与缺页中断处置惩罚、动态存储分配管理。
(第7章 2分)


8hello的IO管理


8.1 Linux的IO设备管理方法

设备的模型化:文件
设备管理:unix io接口
全部的IO设备都被模型化为文件,而全部的输入和输出都被当做对相应文件的读和写来实行。这种将设备映射为文件的方式,允许Linux内核引出一个简单、低级的应用接口,称为Unix I/O。这使得全部的输入和输出都能以一种同一且一致的方式来实行:打开文件、改变当前的文件位置、读写文件、关闭文件。
8.2 简述Unix IO接口及其函数

8.2.1 Unix I/O 接口:
    1. 打开文件。一个应用步伐通过要求内核打开相应的文件,来宣告它想要访问一个I/O设备。内核返回一个小的非负整数,叫做描述符,它在后续对此文件的全部操作中标识这个文件。内核记录有关这个打开文件的全部信息。应用步伐只需记住这个描述符。
2. Linux shell创建的每个历程开始时都有三个打开的文件:标准输入(描述符为0)、标准输出(描述符为1)和标准错误(描述符为2)。头文件< unistd.h>界说了常量STDIN_FILENO、STDOUT_FILENO和 STDERR_FILENO,它们可用来代替显式的描述符值。
3. 改变当前的文件位置。对于每个打开的文件,内核保持着一个文件位置k,初始为0。这个文件位置是从文件开头起始的字节偏移量。应用步伐可以或许通过实行seek操作,显式地设置文件的当前位置为k。
4. 读写文件。一个读操作就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增加到k十n。给定一个大小为m字节的文件,当k≥m时实行读操作会触发一个称为end-of-file(EOF)的条件,应用步伐能检测到这个条件。在文件结尾处并没有明白的“EOF符号”。类似地,写操作就是从内存复制n>0个字节到一个文件,从当前文件位置k开始,然后更新k。
5. 关闭文件。当应用完成了对文件的访问之后,它就关照内核关闭这个文件。作为响应,内核释放文件打开时创建的数据布局,并将这个描述符规复到可用的描述符池中。无论一个历程因为何种缘故起因停止时,内核都会关闭全部打开的文件并释放它们的内存资源。
8.2.2 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的实现分析

printf函数的函数体如下:

图 8-1 printf
形参列表中的...是可变形参的一种写法,当通报参数的个数不确定时,用这种方式来表示。
va_list的界说:typedef char *va_list ,说 明它是一个字符指针,其中(char*)(&fmt) + 4即arg表示的是...中的第一个参数。
再进一步检察vsprintf函数体:

图 8-2 vsprinf
则知道vsprintf步伐按照格式fmt团结参数args生成格式化之后的字符串,并返回字串的长度。
在printf中调用体系函数write(buf,i)将长度为i的buf输出。write函数如下:
printf函数的功能为接受一个格式化命令,并按指定的匹配的参数格式化输出, i = vsprintf(buf, fmt, arg)是得到打印出来的字符串长度,其后的write(buf, i)是将buf中的i个元素写到终端。
因此,vsprintf的作用为接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,进而产生格式化输出。
再进一步对 write 进行追踪:

图 8-3 write
这里给几个寄存器通报了参数,然后以一个int INT_VECTOR_SYS_CALL结束。INT_VECTOR_SYS_CALL代表通过体系调用syscall,检察syscall的实现:

图 8-4 syscall
syscall将字符串中的字节从寄存器中通过总线复制到显卡的显存中,显存中存储的是字符的ASCII码,符显示驱动子步伐:从ASCII 字模库到显示vram(存储每一个点的RGB颜色信息)。显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析

getchar的源代码如下:

图 8-5 getchar
异步非常-键盘中断的处置惩罚:键盘中断处置惩罚子步伐。接受按键扫描码转成ascii码,保存到体系的键盘缓冲区。
getchar等调用read体系函数,通过体系调用读取按键ascii码,直到接受到回车键才返回。
getchar调用体系函数read,发送一个中断信号,内核抢占这个历程,用户输入字符串,键入回车后(字符串和回车都保存在缓冲区内),再次发送信号,内核重新调度这个历程,getchar从缓冲区读入字符。
8.5本章小结

本章主要先容了Linux的IO设备管理方法和及其接口和函数,对printf函数和getchar函数的底层实现有了基本了解。
(第8章1分)

结论

hello步伐的一生履历了如下过程:
1. 预处置惩罚
将hello.c中include的全部外部的头文件头文件内容直接插入步伐文本中,完成字符串的更换,得到hello.i,方便后续处置惩罚;
2. 编译
通过词法分析和语法分析,将正当指令翻译成等价汇编代码。通过编译过程,编译器将hello.i翻译成汇编语言文件hello.s;
3. 汇编
将hello.s汇编步伐翻译成呆板语言指令,并把这些指令打包成可重定位目的步伐格式,最终结果保存在hello.o目的文件中;
4. 链接
通过链接器,将hello的步伐编码与动态链接库等网络整理成为一个单一文件,生成完全链接的可实行的目的文件hello;
5. 加载运行
打开shell,在其中输入./hello 2022113557 宫名扬 1,终端为其fork新建历程,并通过execve把代码和数据加载入假造内存空间,步伐开始实行;
6. 实行指令
在该历程被调度时,CPU为hello其分配时间片,在一个时间片中,hello享有CPU全部资源,PC寄存器逐步更新,CPU按照控制逻辑流实行指令;
7. 访存
内存管理单位MMU将逻辑地址,一步步映射成物理地址,进而通过三级高速缓存体系访问物理内存中的数据;
8. 动态申请内存
printf会调用malloc向动态内存分配器申请堆中的内存;
9. 信号处置惩罚
历程时刻等待着信号,如果运行途中键入ctrl-c ctrl-z则调用shell的信号处置惩罚函数分别进行停止、挂起等操作,对于其他信号也有相应的操作;
10. 停止并被接纳
shell父历程等待并接纳hello子历程,内核删除为hello历程创建的全部数据布局。
(结论0分,缺失 -1分,根据内容酌情加分)

附件

文件名

描述

hello.i

预处置惩罚后得到的ASCII码的中心文件

hello.s

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

hello.o

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

hello1.elf

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

asm1.txt

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

hello2.elf

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

asm2.txt

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


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


参考文献


为完成本次大作业你翻阅的册本与网站等

[1] Randal E.Bryant, David O'Hallaron. 深入理解计算机体系[M]. 机械工业出书
社.2018.4
[2] printf 函数实现的深入分析
https://www.cnblogs.com/pianist/p/3315801.html
[3] 内存管理:物理地址、假造地址、逻辑地址
https://blog.csdn.net/weixin_48524215/article/details/125589384
(参考文献0分,缺失 -1分)



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

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

万万哇

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表