步伐人生-Hello’s P2P

风雨同行  金牌会员 | 2024-7-22 07:05:56 | 来自手机 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 511|帖子 511|积分 1533





盘算机系统


大作业



题     目  步伐人生-Hellos P2P 
专       业       物联网工程           
学     号       2022111806           
班   级        2237301             
学       生         赵天一         
指 导 教 师          吴锐           






盘算机科学与技术学院

20245

摘  要

本文以hello.c这个步伐为中央,论述了Hello步伐在Linux系统的生命周期,从hello.c依次深入研究了编译、链接、加载、运行、终止、接纳的过程,从而相识hello.c文件的“一生”。并结合课程所学知识阐明了Linux操作系统怎样对Hello步伐举行进程管理和存储管理等。本文通过在Ubuntu系统下对hello.c步伐的深入研究,得以把盘算机系统整个的体系串联在一起,回首了学过的知识点,加深了对盘算机系统的明白。

关键词:Hello步伐;盘算机系统;步伐生命周期;盘算机底层原理     






目  录


第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简介

1.1.1  P2P过程
1.编辑:Hello以步伐员的手指在键盘上的敲击,将本身以C语言的情势存储在hello.c文件中。

2.预处理:通过预处理器,处理hello.c文件,解析宏定义和条件编译等预处理指令,生成颠末预处理的源代码。

3.编译:编译器将预处理后的源代码转换为汇编语言。

4.汇编:汇编器将汇编语言转换为呆板可执行的二进制指令。

5.链接:连接器将汇编生成的可执行文件与所需的库文件链接在一起,生成终极的可执行文件。

6.进程创建:通过操作系统的进程管理,操作系统为Hello创建了进程,使其在系统中成为一个独立的执行实体。

1.1.2  O2O过程
1.编辑器:Hello作为编辑器中的文本内容存在,等候步伐员的操作。
2.编译器:Hello被编译器解析、转换、优化,并生成可执行文件。
3.汇编器:Hello的指令被转换成呆板语言。
4.链接器:Hello的可执行文件被连接成完备的步伐。
5.操作系统:操作系统管理着Hello的运行环境,包罗进程管理、存储管理、IO管理等。
6.CPU/RAM/IO等硬件:Hello在盘算机硬件上执行,CPU执行指令,RAM存储数据,IO装备处理输入输出。
在这两个过程中,Hello履历了从步伐到进程的转变,以及从零到整个系统的运行过程。通过操作系统和盘算机系统的支持,Hello得以在盘算机中运行、表演、完成任务,并终极被系统收尸,完成了本身的生命周期。
窗体顶端
窗体底端
1.2 环境与工具

硬件:12th Gen Intel(R) Core(TM) i7-12700H CPU @ 2.70GHz
   NVIDA GeForce RTX 3060 Laptop GPU
   32GB RAM
   1T 512GB SSD
软件:Windows11 23H2
Ubuntu 20.04.4 LTS 64位
调试工具:Visual Studio Code 64-bit;
gedit,gcc,notepad++,readelf, objdump, hexedit, edb
1.3 中间结果

表格 1 中间结果
文件名

功能

hello.i

预处理后得到的文本文件

hello.s

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

hello.o

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

hello.elf

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

hello.asm

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

hello2.elf

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

hello2.asm

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




1.4 本章小结

本章简要介绍了hello 的P2P,020的具体含义,同时列出了论文研究时接纳的具体软硬件环境和中间结果。


(第1章0.5分)



第2章 预处理

2.1 预处理的概念与作用

步伐预处理是指在编译或解释源代码之前对其举行的一系列处理步骤。其主要目的是为了在实际编译或解释阶段之前对源代码举行一些必要的转换、修改或预备工作,以便于后续的编译或解释过程能够顺遂举行。预处理器可以识别源代码中的宏,并将其替换为相应的文本。这使得步伐员可以利用宏来定义常量、函数或代码片段,从而提高代码的可读性和灵活性。预处理器还可以根据条件指令来选择性地包含或排除源代码的部分内容。这使得步伐员可以根据不同的编译选项或平台条件来编写灵活的代码,以实现跨平台或不同版本的兼容性。预处理器可以处理源代码中的头文件包含指令,将头文件的内容插入到源文件中。如允许以将步伐分割成多个模块,提高代码的构造性和可维护性。
2.2在Ubuntu下预处理的下令

打开终端,输入下令cpp hello.c > hello.i



图1 预处理过程



2.3 Hello的预处理结果解析

打开预处理结果hello.i文件,结果被扩展为3000余行,行数大幅度增长。

图2 预处理部分结果

预处理器会处理#include指令,将指定的头文件内容包含到当前文件中。如许做的目的是为了将代码模块化,提高代码的可维护性和重用性,还会删除多余的空行和空格,以减小生成的预处理文件的巨细。
2.4 本章小结

本章主要介绍了预处理的概念及作用、在Ubuntu系统下预处理hello.c文件而且对结果举行分析。

(第2章0.5分)


第3章 编译


3.1 编译的概念与作用


编译过程的概念和作用可以简要概括如下:
1.词法分析: 编译器首先会对.i文件举行词法分析,也称为扫描或标志化。在这个阶段,编译器会将源代码分割成一个个词法单元,比如关键字、标识符、操作符等。
2.语法分析: 接着,编译器会对词法单元举行语法分析,也称为解析(Parsing)。在这个阶段,编译器会根据编程语言的语法规则构建语法树大概其他中间表示情势。
3.语义分析: 在语法分析后,编译器会举行语义分析,检查代码是否符合语言规范的语义要求,比如类型检查、变量声明等。
4.优化: 接下来是编译器的优化阶段,编译器会尝试对中间表示的代码举行优化,以提高步伐的性能大概减小生成的目标代码的巨细。
5.代码生成: 最后一步是将优化后的中间表示情势翻译成目标代码,对于C语言而言,就是将中间表示情势翻译成汇编语言代码,生成.s文件。

整个编译过程的目的是将高级语言(如C语言)编写的代码转换成盘算性能够执行的呆板代码。编译过程中的各个阶段都有其特定的功能和作用,通过这些阶段的处理,可以包管终极生成的目标代码能够正确地执行所要求的功能,而且在性能和资源利用上到达一定的优化水平。
        

3.2 在Ubuntu下编译的下令


打开终端,输入下令 gcc -S hello.i -o hello.s

图3 编译过程

3.3 Hello的编译结果解析

3.3.1 数据与赋值
(1)常量数据
  1.printf字符串被保存在.rodata段
·源步伐代码:
printf("用法: Hello 学号 姓名 手机号 秒数!\n");
printf("Hello %s %s %s\n",argv[1],argv[2],argv[3]);
·汇编代码:
.section .rodata
.align 8
.LC0:
.string "\347\224\250\346\263\225:Hello \345\255\246\345\217\267 \345\247\223\345\220\215\346\211\213\346\234\272\345\217\267 \347\247\222\346\225\260\357\274\201"(第一个字符串)

.LC1:
.string "Hello %s %s %s\n"(第二个字符串)

  • if判断值与for循环终止条件值在.text段
·源步伐及对应汇编代码如下
if(argc!=5)  cmpl $5, -20(%rbp)
for(i=0;i<10;i++) cmpl $9, -4(%rbp)

  • 变量数据
局部变量i以int类型存储
·源步伐代码及对应汇编代码如下
int i; movl $0, -4(%rbp) (赋初值)
for(i=0;i<10;i++) addl $1, -4(%rbp)

可见,局部变量i被保存在%rbp-4的栈位置上。
3.3.2 算术操作
变量i在for循环中自增1
·源步伐代码及对应汇编代码如下
for(i=0;i<10;i++)           addl $1, -4(%rbp) (自增部分)

3.3.3关系操作及状态转移
(1)if条件判断部分
·源步伐代码及对应汇编代码如下
if(argc!=5)        cmpl $5, -20(%rbp)
je .L2
je举行判断,若argc即是5,条件不建立,控制转移至.L2,步伐继续举行;若argc不即是5(即执行步伐时传入的参数个数不符合要求),执行输出提示信息并退出步伐。

  • for循环终止条件判断部分
·源步伐代码及对应汇编代码如下
for(i=0;i<10;i++)   cmpl $9, -4(%rbp)
jle .L4
jle举行判断,若变量i的值小于即是9,终止条件不建立,控制转移至.L4,继续执行循环体。若变量i的值到达10,终止条件建立,跳出循环体,继续执行步伐。
3.3.4数组、指针、结构操作
源步伐涉及到的数组为char *argv[],即主函数的第二个参数在hello.s中,其首地址保存在栈中。

图4数组部分


3.3.5函数操作
源步伐涉及到的函数:main(),printf(),exit(),sleep(),atoi(),getchar()

  • main()函数
a.传入参数
·源步伐代码及对应汇编代码如下
int argc,char *argv[]  movl %edi, -20(%rbp)
movq %rsi, -32(%rbp)
第一个参数通过寄存器EDI通报,第二个参数通过寄存器RSI通报,写入栈空间。
b.函数调用
main:
.LFB6:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
此部分汇编代码用来标志了步伐入口等信息
c.函数返回
movl $0, %eax
leave
函数竣事,给出返回值0

  • printf()函数
a.传入参数
·源步伐代码及对应汇编代码如下
printf("用法: Hello 学号 姓名 手机号 秒数!\n");
leaq .LC0(%rip), %rax
movq %rax, %rdi
call puts@PLT

printf("Hello %s %s %s\n",argv[1],argv[2],argv[3]);
movq -32(%rbp), %rax
addq $24, %rax
movq (%rax), %rcx
movq -32(%rbp), %rax
addq $16, %rax
movq (%rax), %rdx
movq -32(%rbp), %rax
addq $8, %rax
movq (%rax), %rax
movq %rax, %rsi
leaq .LC1(%rip), %rax
movq %rax, %rdi
movl $0, %eax
call printf@PLT
b.函数调用:主函数通过call指令调用。
c.函数返回:忽略。
(3) exit()函数
a.传入参数
·源步伐代码及对应汇编代码如下
exit(1);     movl    $1, %edi
              call    exit@PLT
b.函数调用:主函数通过call指令调用。

c.函数返回:退出步伐。

(4) sleep()函数

a.传入参数

·源步伐代码及对应汇编代码如下
sleep(atoi(argv[4]));  call atoi@PLT
movl %eax, %edi
call sleep@PLT
此处传入的int参数为atoi函数的返回值
b.函数调用:主函数通过call指令调用。
c.函数返回:忽略。
(5) atoi()函数
a.传入参数
sleep(atoi(argv[4])); movq (%rax), %rax
movq %rax, %rdi
call atoi@PLT
将argv[4]所指字符串转换为整形
b.函数调用:主函数通过call指令调用。
c.函数返回:返回int型。
(6) getchar()函数
a.传入参数:无
b.函数调用:主函数通过call指令调用,干系汇编代码如下:

call   getchar@PLT
c.函数返回:返回char类型值,在此步伐中忽略。

3.4 本章小结

本章将hello.i经编译器处理得到hello.s,介绍了编译的概念与过程并具体分析了Hello步伐的编译结果,将源步伐与汇编代码逐一对应。(第3章2分)


第4章 汇编


4.1 汇编的概念与作用

4.1.1汇编的概念
汇编语言是一种符号化的低级语言,它利用助记符和符号来代表呆板指令、寄存器和内存地址等,汇编是指汇编器(assembler)将以.s末端的汇编步伐翻译成呆板语言指令,并把这些指令打包成可重定位目标步伐格式,最闭幕果保存在.o 目标文件中的过程.
4.1.2汇编的作用
汇编能将汇编语言翻译为呆板语言,并将干系指令以可重定位目标步伐格式保存在.o文件中,用来举行对盘算机底层硬件的控制。
4.2 在Ubuntu下汇编的下令

(通过gcc -c hello.s -o hello.o将hello.s汇编成hello.o。

图5 汇编过程

4.3 可重定位目标elf格式

    通过readelf -a hello.o>helloo.elf得到hello.o的elf格式:

图6 得到ELF过程
4.3.1 elf头:
以 16字节序列 Magic 开始,其描述了生成该文件的系统的字的巨细和字节顺序,ELF 头剩下的部分包含帮助链接器语法分析息争释目标文件的信息,此中包罗 ELF 头巨细、目标文件类型、呆板类型、节头部表的文件偏移,以及节头部表中条目的巨细和数量等干系信息。

图7 ELF图

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

图7 节头

4.3.3 重定位节:
重定位节记录了各段引用的符号干系信息,在链接时,需要通过重定位节对这些位置的地址举行重定位。链接器会通过重定位条目的类型判断怎样盘算地址值并利用偏移量等信息盘算出正确的地址。本步伐需要重定位的符号有:.rodata,puts,exit,printf,atoi,sleep,getchar及.text。留意到重定位类型仅有R_X86_64_PC32(PC相对寻址)和R_X86_64_PLT32(利用PLT表寻址)两种,而未出现R_X86_64_32(绝对寻址)。

图8 重定位节

4.3.4 符号表
符号表中保存着定位、重定位步伐中符号定义和引用的信息,全部重定位需要引用的符号都在此中声明。

图9 符号表

4.4 Hello.o的结果解析

通过objdump -d -r hello.o > helloo.asm 得到hello.o的反汇编,并与第3章的 hello.s举行对照分析。

图10 生成.asm文件过程

4.4.1 分支转移:
在 hello.s 中,分支跳转的目标位置是通过 .L1、.L2 如许的助记符来实现的,而 hello.o中,跳转的目标位置是具体的数值。但留意这个数值还不是具体的一个地址,由于此时还没举行链接,它是通过重定位条目举行盘算得来的,是一个相对的地址值,由于不同文件代码链接合并和,一个文件本身的代码的相对地址不会改变,以是不需要与外部重定位,而可以直接盘算出具体的数值,因此这里就已经完成了全部的操作,这条语句将以这种情势加载到内存中被cpu读取与执行。                        

图11 分支转移

4.4.2 函数调用:
在hello.s中,用call指令举行调用函数时,总会在call指令后直接加上函数名,而通过.o文件反汇编得到的汇编代码中,call指令后会跟着的是函数通过重定位条目指引的信息,由于调用的这些函数都是未在当前文件中定义的,以是一定要与外部链接才能够执行。在链接时,链接器将依靠这些重定位条目对相应的值举行修改,以包管每一条语句都能够跳转到正确的运行时位置。

图12 函数调用

4.4.3 操作数:
反汇编代码中的立即数是十六进制数,而 hello.s 文件中的数是十进制的。寄存器寻址两者相同。内存引用 hello.s 中会用伪指令(如.LC0)取代,而反汇编则是基址加偏移量寻址:0x0(%rip)。
4.5 本章小结


本章对汇编的概念以及作用举行了简朴介绍,而且完成了Ubuntu中汇编的过程,颠末汇编阶段,汇编语言代码转化为呆板语言,生成的可重定位目标文件(hello.o)为随后的链接阶段做好了预备,具体分析了产生的ELF文件的信息以及反汇编产生的asm文件的分析,相识了汇编语言与呆板语言的异同之处。
(第4章1分)


5链接


5.1 链接的概念与作用

链接(Linking)是指将多个目标文件或库文件链接在一起,形成一个可执行文件或共享目标文件的过程。在这个过程中,链接器(Linker)会解析各个目标文件之间的符号引用和定义关系,然后将它们合并成一个整体,终极生成一个可执行文件或共享目标文件。过程包罗解析符号、办理符号引用、地址重定位等步骤,终极生成一个完备的可执行文件,此中包含了步伐的全部代码和数据,以便在盘算机上执行。

链接的作用包罗:

符号解析:链接器通过符号表解析各个目标文件中利用的符号,并将其与相应的定义举行关联。这包罗全局变量、函数、外部库函数等。

符号重定位:在链接过程中,链接器会调解各个目标文件中的符号地址,以便将它们正确地映射到终极的内存地址上。

库链接:链接器还负责将步伐所需的库文件链接到步伐中,以便在运行时能够正确地调用库中的函数和符号。

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
运行截图如下:

图12 链接过程


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

分析hello的ELF格式,用readelf等列出其各段的基本信息,包罗各段的起始地址,巨细等信息。
通过readelf -a hello>hello.elf得到hello.elf。

图13 生成elf文件


  • ELF 头(ELF Header)
hello1.elf中的ELF头与hello.elf中的ELF头包含的信息种类基本相同,以 描述了生成该文件的系统的字的巨细和字节顺序的16字节序列 Magic 开始,剩下的部分包含帮助链接器语法分析息争释目标文件的信息。hello1.elf中的基本信息未发生改变(如Magic,种别等),而类型发生改变,步伐头巨细和节头数量增长,而且得到了入口地址。

图14  ELF头的干系信息


  • 节头
节头包含了文件中出现的各个节的语义,包罗节的类型、位置、偏移量和巨细等信息。与hello.elf相比,其在链接之后的内容更加丰富具体

图15 节头的干系信息(部分)


  • 步伐头
步伐头部分是一个结构数组,描述了系统预备步伐执行所需的段或其他信息。

图16 步伐头的干系信息


  • Dynamic section

图17 Dynamic section的干系信息

符号表

符号表符号表中保存着定位、重定位步伐中符号定义和引用的信息,全部重定位需要引用的符号都在此中声明。

图18符号表的干系信息(部分)


  • 重定位节

图19 重定位节的干系信息


  • 其他

图20 其他干系信息


5.4 hello的虚拟地址空间

用EDB检察hello的虚拟地址 。


图21 edb加载hello结果

虚拟地址范围为0x401000至0x402000。


图22 对应关系

通过symbol逐一对照,得到虚拟地址与节头部表的对应关系。

   
5.5 链接的重定位过程分析

通过objdump -d -r hello > hello.asm得到反汇编文件hello.asm。

图23 重定位过程

不同之处:

  • hello.o的反汇编中只含有.text节,而hello的反汇编中另有.init,.plt,.plt.sec。
  • 在hello中链接加入了exit、printf、sleep、getchar等在hello.c中用到的库函数。
  • hello中不再存在hello.o中的重定位条目,而且跳转和函数调用的地址在hello中都酿成了虚拟内存地址。

图24 链接后的函数

5.6 hello的执行流程

用EDB打开hello。
根据symbol阐明步伐执行的函数以及其地址。

函数名

函数地址

.init

0x401000

.plt

0x401020

puts@plt

0x401030

printf@plt

0x401040

getchar@plt

0x401050

atoi@plt

0x401060

exit@plt

0x401070

sleep@plt

0x401080

_start

0x4010f0

main

0x401125

.fini

0x401248


图25检察调用步伐界面


5.7 Hello的动态链接分析

   动态链接器利用过程链接表PLT+全局偏移量表GOT实现函数的动态链接,在GOT中存放函数量标地址,PLT利用GOT中地址跳转到目标函数,在加载时,动态链接器会重定位GOT中的每个条目,使得它包含目标的正确的绝对地址,以是分析动态连接需要检查.got.plt的环境。
调用dl_init之前.got.plt段的内容:

图26 调用之前内容

调用dl_init之后.got.plt段的内容:

图27 调用之后内容

比力两张图可知GOT[1]和GOT[2]之间发生了变化,查询干系内容可知GOT[1]保存的是指向已经加载的共享库的链表地址。GOT[2]是动态链接器在ld-linux.so模块中的入口。如许,接下来执行步伐的过程中,就可以利用过程链接表PLT和全局偏移量表GOT举行动态链接。

5.8 本章小结

本章首先介绍了链接的概念与作用,然后对文件举行链接,并对可执行文件hello的格式举行了分析,然后检察了hello的虚拟地址,并对链接的重定位过程举行了分析,概述了hello的执行流程,并对hello的动态链接举行了分析。
(第5章1分)



6hello进程管理


6.1 进程的概念与作用

进程是步伐执行的一个实例。一个步伐在运行时会被操作系统加载到内存中,并分配一个独立的执行环境,这个执行环境就是一个进程。进程是操作系统举行资源分配和调度的基本单元,它具有以下特点和作用:

独立性: 每个进程都拥有独立的地址空间,使得它们彼此之间不会相互干扰。进程之间通常是隔离的,一个进程的瓦解不会影响其他进程的正常运行。

并发执行: 操作系统可以同时运行多个进程,每个进程都在本身的执行环境中独立执行。这种并发执行的方式使得盘算机系统可以更有用地利用多核处理器和其他硬件资源。

资源分配: 操作系统为每个进程分配了一定的系统资源,包罗内存空间、CPU时间、文件描述符等。进程可以通过操作系统提供的接口来请求和释放这些资源。

调度和管理: 操作系统负责对进程举行调度和管理,以确保系统资源被合理地分配和利用。这包罗进程的创建、烧毁、挂起、恢复以及切换等操作。

通信与同步: 进程之间可以通过各种机制举行通信和同步,包罗共享内存、消息队列、管道、信号量、锁等。这使得不同进程之间可以举行数据交换和协作,实现复杂的任务分解和并发处理。

步伐执行环境: 每个进程都有本身的步伐执行环境,包罗代码、数据、堆栈、寄存器状态等。操作系统负责管理这些执行环境,并在需要时举行切换和调度。


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

壳是用户与操作系统内核之间的接口,它吸收用户输入的下令并将其转换成操作系统内核能够明白和执行的指令。

壳的主要作用是将我们的指令翻译给OS内核,让内核来举行处理,并把处理的结果反馈给用户。(Windows下的壳步伐就是图形化界面)shell的存在使得用户不会直接操作OS,包管了OS的安全性。

壳的处理流程通常包罗以下步骤: 壳从尺度输入(通常是终端)读取用户输入的下令,对用户输入的下令举行解析,分析下令的结构和含义。根据解析后的下令调用相应的系统步伐或应用步伐举行执行。假如是内建下令(如cd、echo等),则直接在壳内部执行。根据下令中的I/O重定向符号(如<、>、|等)对输入输出举行重定向处理。 假如下令中包含管道符号(|),壳将多个下令连接起来,形成管道,将前一个下令的输出作为后一个下令的输入。检测并处理下令执行过程中大概出现的错误,并将错误信息输出给用户。等候用户输入下令并执行,直到用户退出。

6.3 Hello的fork进程创建过程

输入下令后,shell会判断该下令不是内部指令,转而通过fork函数创建一个子进程hello。hello会得到一份包罗数据段、代码、共享库、堆、用户栈等均与父进程相同且独立的副本。同时子进程还会得到与父进程打开任何文件描述符相同的副本,这表示当父进程调用fork时子进程可以读写父进程的内容。父进程和子进程只有PID不同,在父进程中,fork返回子进程的PID,在子进程中,fork返回0.
6.4 Hello的execve过程

调用函数fork创建新的子进程之后,子进程会调用execve函数,在当前进程的上下文中加载并运行一个新步伐hello。execve 函数从不返回,它将删除该进程的代码和地址空间内的内容并将其初始化,然后通过跳转到步伐的第一条指令或入口点来运行该步伐。将私有的地区映射进来,比方打开的文件,代码、数据段,然后将公共的地区映射进来。背面加载器跳转到步伐的入口点,即设置PC指向_start 地址。_start函数终极调用hello中的 main 函数,如许,便完成了在子进程中的加载。
6.5 Hello的进程执行

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

(1)在步伐正常运行时,打印十次提示信息,以输入回车为标志竣事步伐,而且接纳进程。


图28 步伐正常执行结果

(2)在步伐运行时按回车,会多打印几处空行,步伐可以正常竣事,竣过后也会多几行空指令。


图29 输入回车的环境

(3)按下ctrlc,Shell进程收到SIGINT信号^C,竣事并接纳hello进程。

图30 输入ctrl+c的环境

(4)按下ctrlz,Shell进程收到SIGSTP信号^Z,Shell显示屏幕提示信息[1]+  已停止,并挂起hello进程。

图31 输入ctrl+z的环境


  • 对hello进程的挂起可由ps和jobs下令检察,可以发现hello进程确实被挂起而非被接纳,且其job代号为1。


图32 ps与jobs指令


  • Shell中输入pstree指令检察进程树

图33 pstree指令



6.7本章小结
本章主要介绍了进程的概念与作用,以及Shell-bash的基本概念。针对进程根据hello可执行文件中的具体环境分析了fork,execve函数的原理与执行过程,而且举行了hello步伐带着各种参数环境下举行的各种结果。
(第6章1分)


7hello的存储管理


7.1 hello的存储器地址空间


  • 逻辑地址
逻辑地址是指由步伐产生的与段干系的偏移地址部分,逻辑地址由选择符和偏移量两部分组成。具体而言,其为hello.asm中的相对偏移地址。

  • 线性地址
逻辑地址颠末段机制转化后为线性地址,其为处理器可寻址空间的地址,用于描述步伐分页信息的地址。具体以hello而言,线性地址标志着 hello 应在内存上哪些具体数据块上运行。

  • 虚拟地址
根据CSAPP教材,虚拟地址即为上述线性地址。

  • 物理地址
CPU通过地址总线的寻址,找到真实的物理内存对应地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理

为了运用全部的内存空间,Intel 8086设定了四个段寄存器,专门用来保存段地址:CS(Code Segment):代码段寄存器;DS(Data Segment):数据段寄存器;SS(Stack Segment):堆栈段寄存器;ES(Extra Segment):附加段寄存器。
当一个步伐要执行时,就要决定步伐代码、数据和堆栈各要用到内存的哪些位置,通过设定段寄存器CS,DS,SS来指向这些起始位置。通常是将DS固定,而根据需要修改CS。以是,步伐可以在可寻址空间小于64K的环境下被写成恣意巨细。以是,步伐和其数据组合起来的巨细,限制在DS所指的64K内,这就是COM文件不得大于64K的缘故原由。
段寄存器是由于对内存的分段管理而设置的。
盘算机需要对内存分段,以分配给不同的步伐利用(类似于硬盘分页)。在描述内存分段时,需要有如下段的信息:1.段的巨细;2.段的起始地址;3.段的管理属性(禁止写入/禁止执行/系统专用等)。
保护模式(如今大多数呆板已经不再支持):
段寄存器的唯一目的是存放段选择符,其前13位是一个索引号,背面3位包含一些硬件细节(另有一些隐藏位,此处略)。
寻址方式为:以段选择符作为下标,到GDT/LDT表(全局段描述符表(GDT)和局部段描述符表(LDT))中查到段地址,段地址+偏移地址=线性地址。
实模式:
段寄存器含有段值,访问存储器形成物理地址时,处理器引用相应的某个段寄存器并将其值乘以16,形成20位的段基地址,段基地址·段偏移量=线性地址。
7.3 Hello的线性地址到物理地址的变换-页式管理

线性地址(VA)到物理地址(PA)之间的转换通过对虚拟地址内存空间举行分页的分页机制完成。
通过7.2节中的段式管理过程,可以得到了线性地址/虚拟地址,记为VA。虚拟地址可被分为两个部分:VPN(虚拟页号)和VPO(虚拟页偏移量),根据盘算机系统的特性可以确定VPN与VPO的具体位数,由于虚拟内存与物理内存的页巨细相同,因此VPO与PPO(物理页偏移量)一致。而PPN(物理页号)则需通过访问页表中的页表条目(PTE)获取,如下图所示。

图 34 Hello的线性地址到物理地址的变换-页式管理

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

针对Intel Core i7 CPU研究VA到PA的变换。
Intel Core i7 CPU的基本参数如下:

  • 虚拟地址空间48位(n=48)
  • 物理地址空间52位(m=52)
  • TLB四路十六组相连
  • L1,L2,L3块巨细为64字节
  • L1,L2八路组相连
  • L3十六路组相连
  • 页表巨细4KB(P=4x1024=2^12),四级页表,页表条目(PTE)巨细8字节
由上述信息可以得知,VPO与PPO有p=12位,故VPN为36位,PPN为40位。单个页表巨细4KB,PTE巨细8字节,则单个页表有512个页表条目,需要9位二进制举行索引,而四级页表则需要36位二进制举行索引,对应着36位的VPN。TLB有16组,故TLBI有t=4位,TLBT有36-4=32位。

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

如图所示, CPU产生虚拟地址VA,并将其传送至MMU,MMU利用前36位VPN作为TLBT(前32位)+TLBI(后4位)在TLB中举行匹配,若命中,则得到PPN(40bit)与VPO(12bit)组合成物理地址PA(52bit)。若TLB没有命中,则MMU向页表中查询,由CR3确定第一级页表的起始地址,VPN1(9bit)确定在第一级页表中的偏移量,查询出PTE,假如在物理内存中且权限符合,则执行下一步确定第二级页表的起始地址,以此类推,终极在第四级页表中查询到PPN,与VPO组合成PA,并向TLB中添加条目。多级页表的工作原理展示如下:

s

若查询PTE的时候发现不在物理内存中,则引发缺页故障。假如发现权限不敷,则引发段错误。
7.5 三级Cache支持下的物理内存访问

由于三级Cache的工作原理基本相同,以是在这里以L1 Cache为例,介绍三级Cache支持下的物理内存访问。
L1 Cache的基本参数如下:

  • 8路64组相连
  • 块巨细64字节
由L1 Cache的基本参数,可以分析知:
块巨细64字节→需要6位二进制索引→块偏移6位
共64组→需要6位二进制索引→组索引6位
余下标志位→需要PPN+PPO-6-6=40位
故L1 Cache可被分别如下(从左到右):
CT(40bit)CI(6bit)CO(6bit)
在7.4中我们已经由虚拟地址VA转换得到了物理地址PA,首先利用CI举行组索引,每组8路,对8路的块分别匹配CT(前40位)假如匹配成功且块的valid标志位为1,则命中(hit),根据数据偏移量CO取出相应的数据后返回。
若没有匹配成功大概匹配成功但是标志位是1,则不命中(miss),向下一级缓存中请求数据(请求顺序为L2 Cache→L3 Cache→主存,若仍不命中才继续向下一级请求)。查询到数据之后,需要对数据举行读入,一种简朴的放置策略如下:若映射到的组内有空闲块,则直接放置在空闲块中,若当前组内没有空闲块,则产生辩论(evict),接纳LFU策略举行替换。
7.6 hello进程fork时的内存映射

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

execve函数加载并运行hello需要以下几个步骤:

  • 删除已存在的用户地区
删除当前进程hello虚拟地址的用户部分中的已存在的地区结构。

  • 映射私有地区
为新步伐的代码、数据、bss和栈地区创建新的私有的、写时复制的地区结构。此中,代码和数据地区被映射为hello文件中的.text和.data区。bss地区是请求二进制零的,映射到匿名文件,其巨细包含在hello中。栈和堆地区也是请求二进制零的,初始长度为零。

  • 映射共享地区
若hello步伐与共享对象或目标(如尺度C库libc.so)链接,则将这些对象动态链接到hello步伐,然后再映射到用户虚拟地址空间中的共享地区内。

  • 设置步伐计数器
最后,execve设置当前进程上下文中的步伐计数器,使之指向代码地区的入口点。
7.8 缺页故障与缺页中断处理

在虚拟内存的习惯说法中,DRAM缓存不命中称为缺页。缺页故障属于非常种别中的故障,是潜伏可恢复的错误。
缺页非常调用内核中的缺页非常处理步伐,该步伐会选择一个牺牲页,假如牺牲页已经被修改了,内核会将其复制回磁盘。随后内核从磁盘复制引发缺页非常的页面至内存,更新对应的页表项指向这个页面,随后返回。
缺页非常处理步伐返回后,内核会重新启动导致缺页的指令,该指令会把导致缺页的虚拟地址重发送到地址翻译硬件,此次页面会命中。
7.9动态存储分配管理

动态内存管理的基本方法与策略介绍如下:
动态内存分配器维护着一个称为堆的进程的虚拟内存地区。分配器将堆视为一组不同巨细的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用步伐利用。空闲块可用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放可以由应用步伐显式执行或内存分配器自身隐式执行。
具体而言,分配器分为两种基本风格:显式分配器、隐式分配器。
显式分配器:要求应用显式地释放任何已分配的块。
隐式分配器:要求分配器检测一个已分配块何时不再利用,那么就释放这个块,自动释放未利用的已经分配的块的过程叫做垃圾网络。
下面介绍动态存储分配管理中较为告急的概念:

  • 隐式链表
堆中的空闲块通过头部中的巨细字段隐含地连接,分配器通过遍历堆中全部的块,从而间接遍历整个空闲块的集合。

图36 隐式链表的结构


  • 显式链表
在每个空闲块中,都包含一个前驱(pred)与后继(succ)指针,从而减少了搜刮与适配的时间。

图37 显式链表的结构


  • 带边界标志的合并
接纳利用边界标志的堆块的格式,在堆块的末尾为其添加一个脚部,其为头部的副本。添加脚部之后,分配器就可以通过检查前面一个块的脚部,判断前面一个块的起始位置和状态。从而实现快速合并,减小性能斲丧。

  • 分离存储
维护多个空闲链表,此中,每个链表的块具有相同的巨细。将全部大概的块巨细分成一些等价类,从而举行分离存储。
7.10本章小结

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


8hello的IO管理


8.1 Linux的IO装备管理方法

1.装备的模子化——文件

全部的I/O装备(比方网络、磁盘和终端)都被模子化为文件。

比方:/dev/sda2文件是用户磁盘分区,/dev/tty2文件是终端。

2.装备管理——Unix IO接口

将装备模子化为文件的方式允许Linux内核引入一个简朴、低级的应用接口,称为Unix IO,这使得全部的输入和输出都能以一种统一且一致的方式来执行。

8.2 简述Unix IO接口及其函数

8.2.1Unix I/O接口:


  • 打开文件
一个应用步伐通过要求内核打开相应的文件,来宣告它想要访问一个I/O装备,内核返回一个小的非负整数,叫做描述符,它在后续对此文件的全部操作中标识这个文件,内核记录有关这个打开文件的全部信息。对于Shell创建的每个进程,其都有三个打开的文件:尺度输入,尺度输出,尺度错误。

  • 改变当前的文件位置
对于每个打开的文件,内核保持着一个文件位置k,初始为0,这个文件位置是从文件开头起始的字节偏移量,应用步伐能够通过执行seek,显式地将改变当前文件位置k。

  • 读写文件
一个读操作就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增长到k+n,给定一个巨细为m字节的而文件,当k>=m时,触发EOF。类似一个写操作就是从内存中复制n>0个字节到一个文件,从当前文件位置k开始,然后更新k。

  • 关闭文件
内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中去。

8.2.2Unix I/O函数:


  • int open(char* filename,int flags,mode_t mode)
进程通过调用open函数来打开一个存在的文件或是创建一个新文件的。open函数将filename转换为一个文件描述符,而且返回描述符数字,返回的描述符总是在进程中当前没有打开的最小描述符,flags参数指明了进程计划怎样访问这个文件,mode参数指定了新文件的访问权限位。

  • int close(fd)
fd是需要关闭的文件的描述符,close返回操作结果。

  • ssize_t read(int fd,void *buf,size_t n)
read函数从描述符为fd的当前文件位置赋值最多n个字节到内存位置buf。返回值-1表示一个错误,0表示EOF,否则返回值表示的是实际传送的字节数量。

  • ssize_t wirte(int fd,const void *buf,size_t n)
write函数从内存位置buf复制至多n个字节到描述符为fd的当前文件位置。
8.3 printf的实现分析


  • printf函数体:
int printf(const char *fmt, ...)

{

    int i;

    va_list arg = (va_list)((char *)(&fmt) + 4);

    i = vsprintf(buf, fmt, arg);

    write(buf, i);

    return i;

}

分析:printf函数调用了vsprintf函数,最后通过系统调用函数write举行输出;va_list是字符指针类型;((char *)(&fmt) + 4)表示...中的第一个参数。


  • printf调用的vsprintf函数:
int vsprintf(char *buf, const char *fmt, va_list args)

{

    char *p;

    chartmp[256];

    va_listp_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。用格式字符串对个数变化的参数举行格式化,产生格式化输出写入buf供系统调用write输出时利用。


  • write系统调用:
write: 

mov eax, _NR_write

mov ebx, [esp + 4]

mov ecx, [esp + 8]

int INT_VECTOR_SYS_CALL

分析:这里通过几个寄存器举行传参,随后调用中断门int INT_VECTOR_SYS_CALL即通过系统来调用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 

分析:通过逐个字符直接写至显存,输特别式化的字符串。 


  • 输出部分:
字符显示驱动子步伐实现从ASCII到字模库到显示vram(即显存,存储 每一个点的RGB颜色信息)。显示芯片按照刷新频率逐行读取vram,并通过 信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析

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

本章主要介绍了Linux的IO装备管理方法和及其接口和函数,对printf函数和getchar函数的底层实现有了基本相识,相识了Unix IO在Linux系统中的应用
(第8章1分)



结论

hello步伐的一生履历了如下过程:


  • 预处理
将hello.c中include的全部外部的头文件头文件内容直接插入步伐文本中,完成字符串的替换,方便后续处理;

  • 编译
通过词法分析和语法分析,将正当指令翻译成等价汇编代码。通过编译过程,编译器将hello.i 翻译成汇编语言文件 hello.s;

  • 汇编
将hello.s汇编步伐翻译成呆板语言指令,并把这些指令打包成可重定位目标步伐格式,最闭幕果保存在hello.o 目标文件中;

  • 链接
通过链接器,将hello的步伐编码与动态链接库等网络整理成为一个单一文件,生成完全链接的可执行的目标文件hello;

  • 加载运行
打开Shell,在此中键入 ./hello 1190200208 李旻翀,终端为其fork新建进程,并通过execve把代码和数据加载入虚拟内存空间,步伐开始执行;

  • 执行指令
在该进程被调度时,CPU为hello其分配时间片,在一个时间片中,hello享有CPU全部资源,PC寄存器一步一步地更新,CPU不断地取指,顺序执行本身的控制逻辑流;

  • 访存
内存管理单元MMU将逻辑地址,一步步映射成物理地址,进而通过三级高速缓存系统访问物理内存/磁盘中的数据;

  • 动态申请内存
printf 会调用malloc 向动态内存分配器申请堆中的内存;

  • 信号处理
进程时刻等候着信号,假如运行途中键入ctr-c ctr-z 则调用shell 的信号处理函数分别举行停止、挂起等操作,对于其他信号也有相应的操作;

  • 终止并被接纳
Shell父进程等候并接纳hello子进程,内核删除为hello进程创建的全部数据结构。
(结论0分,缺失 -1分,根据内容酌情加分)

附件

文件名

作用

hello.i
hello.c预处理后得到的文本文件
hello.s
hello.i编译后得到的汇编代码
hello.o
hello.s汇编得到的可重定位目标文件
helloo.elf
readelf读取hello.o得到的文本
helloo.asm
objdump反汇编hello.o得到的反汇编文件
hello
hello.o链接后得到的可执行文件
hello.elf
readelf读取hello得到的文本
hello.asm
objdump反汇编hello得到的反汇编文件
(附件0分,缺失 -1分)


参考文献


[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.
[7] Randal E.Bryant, David O'Hallaron. 深入明白盘算机系统[M]. 机械工业出版社.2018.4 
[8] Pianistx.printf 函数实现的深入剖析[EB/OL].2013[2021-6-9].

https://www.cnblogs.com/pianist/p/3315801.html. 

[9] 空想之家xiao_chen.ELF文件头更具体结构[EB/OL].2017[2021-6-10].
https://blog.csdn.net/qq_32014215/article/details/76618649.
[10] Florian.printf背后的故事[EB/OL].2014[2021-6-10].
https://www.cnblogs.com/fanzhidongyzby/p/3519838.html.
[11] printf函数实现的深入剖析. 博客园. 
https://www.cnblogs.com/pianist/p/3315801.html
[12] read和write系统调用以及getchar的实现. CSDN博客.
read和write系统调用以及getchar的实现_getchar 和read-CSDN博客
[13] 深入明白盘算机系统(原书第三版).机械工业出版社, 2016.
(参考文献0分,缺失 -1分)



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

使用道具 举报

0 个回复

倒序浏览

快速回复

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

本版积分规则

风雨同行

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

标签云

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