计算机系统大作业

计算机系统

大作业

题 目 程序人生-Hello’s P2P
专 业 计算学部
学   号 1190201208
班   级 1903007
学 生 吴浩田  
指 导 教 师 吴锐

计算机科学与技术学院
2021年5月
摘 要
本文会通过所学知识对hello的一生进行分析和探讨,同时对学校内容进行回顾。过程中会利用到各种课程中学习到的工具和方式来进行分析,加强对计算机系统的深入了解

关键词:hello;计算机系统;程序;

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

目 录

第1章 概述 - 4 -
1.1 HELLO简介 - 4 -
1.2 环境与工具 - 4 -
1.3 中间结果 - 4 -
1.4 本章小结 - 4 -
第2章 预处理 - 5 -
2.1 预处理的概念与作用 - 5 -
2.2在UBUNTU下预处理的命令 - 5 -
2.3 HELLO的预处理结果解析 - 5 -
2.4 本章小结 - 7 -
第3章 编译 - 8 -
3.1 编译的概念与作用 - 8 -
3.2 在UBUNTU下编译的命令 - 8 -
3.3 HELLO的编译结果解析 - 8 -
3.3.1 数据和赋值 - 8 -
3.3.2 算术操作 - 10 -
3.3.3 关系操作和控制转移 - 10 -
3.3.4 数组/指针/结构操作 - 11 -
3.3.5 函数操作 - 12 -
3.4 本章小结 - 14 -
第4章 汇编 - 15 -
4.1 汇编的概念与作用 - 15 -
4.2 在UBUNTU下汇编的命令 - 15 -
4.3 可重定位目标ELF格式 - 15 -
4.3.1 命令: - 15 -
4.3.2 ELF头: - 16 -
4.3.3 节头目表: - 17 -
4.3.4 重定位节 - 17 -
4.3.5 符号表: - 18 -
4.4 HELLO.O的结果解析 - 19 -
4.5 本章小结 - 20 -
第5章 链接 - 21 -
5.1 链接的概念与作用 - 21 -
5.2 在UBUNTU下链接的命令 - 21 -
5.3 可执行目标文件HELLO的格式 - 21 -
5.4 HELLO的虚拟地址空间 - 24 -
5.5 链接的重定位过程分析 - 25 -
5.6 HELLO的执行流程 - 32 -
5.7 HELLO的动态链接分析 - 32 -
5.8 本章小结 - 33 -
第6章 HELLO进程管理 - 34 -
6.1 进程的概念与作用 - 34 -
6.2 简述壳SHELL-BASH的作用与处理流程 - 34 -
6.3 HELLO的FORK进程创建过程 - 35 -
6.4 HELLO的EXECVE过程 - 35 -
6.5 HELLO的进程执行 - 36 -
6.6 HELLO的异常与信号处理 - 37 -
6.7本章小结 - 41 -
第7章 HELLO的存储管理 - 42 -
7.1 HELLO的存储器地址空间 - 42 -
7.2 INTEL逻辑地址到线性地址的变换-段式管理 - 42 -
7.3 HELLO的线性地址到物理地址的变换-页式管理 - 43 -
7.4 TLB与四级页表支持下的VA到PA的变换 - 43 -
7.5 三级CACHE支持下的物理内存访问 - 44 -
7.6 HELLO进程FORK时的内存映射 - 45 -
7.7 HELLO进程EXECVE时的内存映射 - 45 -
7.8 缺页故障与缺页中断处理 - 45 -
7.9动态存储分配管理 - 46 -
7.10本章小结 - 47 -
第8章 HELLO的IO管理 - 48 -
8.1 LINUX的IO设备管理方法 - 48 -
8.2 简述UNIX IO接口及其函数 - 48 -
8.3 PRINTF的实现分析 - 49 -
8.4 GETCHAR的实现分析 - 51 -
8.5本章小结 - 51 -
结论 - 52 -
附件 - 53 -
参考文献 - 54 -

第1章 概述
1.1 Hello简介
P2P:
Program:在编辑器中输入代码,保存为hello.c的程序
Process: c程序经过cpp的预处理,ccl的编译、as的汇编、ld的链接最终成为可执行目标文件hello。在shell中输入启动命令,shell将其fork成进程来运行。
020:
Shell为进程进行execve,映射虚拟内存,进入程序入口后程序开始载入物理内存。
进入main函数执行目标代码,cpu为hello分配时间片执行逻辑控制流。
程序运行结束时,shell父进程回收hello进程,内核删除为其产生的数据结构。
1.2 环境与工具
硬件环境:九代i7;16gram;x86-64架构
软件环境:windows10;ubuntu 20.04;vmware
开发与调试工具:gcc;gdb;as;ld;edb;readelf;vscode
1.3 中间结果
Hello.i:经过预处理后的文件
Hello.s:编译后生成的汇编文件
Hello.o:汇编后生成的可重定位文件
Hello:链接后生成的可执行目标文件
Hello_o_elf.txt:hello.o的elf格式文件
Disas_hello.s:hello.o的反汇编代码
Hello.elf:hello的elf格式
Hello_objdump.s:hello的反汇编代码
1.4 本章小结
本章对实验内容进行了一个总体的概括,介绍了hello从产生到执行的大致经过,介绍了开发环境,简述了写论文过程中生成的中间文件。
(第1章0.5分)

第2章 预处理
2.1 预处理的概念与作用
概念:在该阶段,编译器会展开以#起始的行,试图解释为预处理指令。经过此处理,生成一个没有宏定义、没有条件编译指令、没有特殊符号的输出文件。生成文件仍为c文件。
预处理的作用:

  1. 将源文件中用#include 形式声明的头文件等复制到新的程序中。
  2. 用实际值代替用#define定义的值
  3. 根据#if后面的调节决定之后要编译的代码
    2.2在Ubuntu下预处理的命令
    cpp hello.c > hello.i

图2.1预处理指令
2.3 Hello的预处理结果解析
看到程序已扩充到3065行,原先hello.c的程序出现在3046行后,而之前的内若那个则是头文件stdio.h unistd.h stdlib.h的依次展开。在展开时,遇到#define的语句则根据情况再次进行替换展开,最终产生的文件中不含#define。对于有#ifdef #ifndef 条件编译的语句, cpp 会对条件值进行判断来决定是否执行包含其中的逻辑。
插入库所在位置:

图2.2 hello.i库文件部分

库中预置函数:

图2.3 hello.i声明函数部分
源代码位置:

图2.3 hello.i源代码部分

2.4 本章小结
本章介绍了预处理的概念和功能,linux预处理的指令,以及对hello.i文件的具体分析,理解了预处理操作的内涵。
(以下格式自行编排,编辑时删除)

(第2章0.5分)

第3章 编译
3.1 编译的概念与作用
编译的概念:
编译程序通过词法分析和语法分析,确认所有指令都符合语法规则后将其翻译成等价的汇编代码。
编译的作用:

  1. 代码优化:对程序进行多种等价变换,使得从变换后的程序出发,能生成更有效的目标代码。
  2. 编译产生了汇编语言,便于下一步继续生成可重定位文件。
  3. 编译分析语法是否符合语法规则,对代码正确性进行检查。
    3.2 在Ubuntu下编译的命令
    gcc -S hello.i -o hello.s

图3.1编译命令
3.3 Hello的编译结果解析
3.3.1 数据和赋值
3.3.1.1 常量
在程序中出现的0,10,1,2等等常量保存在.text中,作为指令的一部分。

而在下列语句:

字符串则被存储在.rodata节中:

3.3.1.2 变量
全局变量:
初始化的全局变量储存在.data节,它的初始化不需要汇编语句,而是直接完成的。

局部变量:
局部变量存储在寄存器或栈中。
在汇编代码中

在循环前将i赋值为0的操作,i被保存在栈当中、%rsp-4的位置上。
3.3.2 算术操作
在循环操作中,使用了自加++操作符:

汇编代码:

每次循环执行代码段结束将i加一,栈上存储的i值加一。
3.3.3 关系操作和控制转移
程序第16行中判断传入参数argc是否等于3:
汇编代码为

je用于判断cmpl产生的条件码,若两个操作数的值不相等则跳转到指定地址;
for循环中的循环执行条件

汇编代码为

jle用于判断cmpl产生的条件码,若后一个操作数的值小于等于前一个则跳转到指定地址;
3.3.4 数组/指针/结构操作
Main中参数有指针数组char *argv[]、

在argv数组中,argv[0]指向输入程序的路径和名称,argv[1]和argv[2]分别表示两个字符串。
因为char* 数据类型占8个字节,根据

可知通过%rsi-8和%rax-16,分别可得到argv[1]和argv[2]两个字符串。
3.3.5 函数操作
main函数:
参数传递:参数argc和argv[],用寄存器%rdi和%rsi存储。
函数调用:被系统启动函数调用。
函数返回:设置%eax为0并且返回,对应return 0 。
源代码:

汇编代码:
main:

argc存储在%edi中,argv存储在%rsi中;

设置%eax为0并且返回
printf函数:
参数传递:if语句中只传入了字符串参数首地址;for循环时传入了 argv[1]和argc[2]的地址。
函数调用:if判断满足条件后调用,与for循环中被调用。
If源代码:

if汇编代码:
.LC0:
.string “\347\224\250\346\263\225: Hello 1183710109 \351\203\255\350\214\201\345\256\201\357\274\201”

for源代码:

for汇编代码:

exit函数:
参数传递:传入的参数为1,再执行退出命令
函数调用:if判断条件满足后被调用.
源代码:

汇编代码:

sleep函数:
参数传递:传入参数sleepsecs,
函数调用:for循环下被调用
源代码:

汇编代码:

getchar函数:
函数调用:在main中被调用
源代码:

汇编代码:

3.4 本章小结
介绍了编译的概念和作用。通过hello程序展示了c语言转换成汇编代码的过程。介绍了汇编代码如何实现变量、常量、传递参数以及分支和循环。编译程序所做的工作。并对编译的过程进行进一步的分析,加深了对c语言的数据与操作,对c语言翻译成汇编语言的逻辑有进一步的掌握。
(第3章2分)

第4章 汇编
4.1 汇编的概念与作用
概念:驱动程序运行汇编器as,将汇编语言翻译成机器语言的过程称为汇编,同时这个机器语言文件也是可重定位目标文件。
作用:汇编将高级语言转化为机器可直接识别执行的代码文件。汇编器将.s 汇编程序翻译成机器语言指令,把这些指令打包成可重定位目标程序的格式,并将结果保存在.o目标文件中,.o文件是一个二进制文件,它包含程序的指令编码。
4.2 在Ubuntu下汇编的命令
as hello.s -o hello.o

图4.1汇编命令
4.3 可重定位目标elf格式
4.3.1 命令:
readelf -a hello.o > ./ hello_o_elf.txt

图4.2生成并导出elf文件命令

4.3.2 ELF头:
包含了系统信息,编码方式,ELF头大小,节的大小和数量等等一系列信息。Elf头内容如下:
ELF 头:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
类别: ELF64
数据: 2 补码,小端序 (little endian)
Version: 1 (current)
OS/ABI: UNIX - System V
ABI 版本: 0
类型: REL (可重定位文件)
系统架构: Advanced Micro Devices X86-64
版本: 0x1
入口点地址: 0x0
程序头起点: 0 (bytes into file)
Start of section headers: 1240 (bytes into file)
标志: 0x0
Size of this header: 64 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 64 (bytes)
Number of section headers: 14
Section header string table index: 13
4.3.3 节头目表:
描述了.o文件中出现的各个节的类型、位置、所占空间大小等信息。
节头:
[号] 名称 类型 地址 偏移量
大小 全体大小 旗标 链接 信息 对齐
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
0000000000000085 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 00000388
00000000000000c0 0000000000000018 I 11 1 8
[ 3] .data PROGBITS 0000000000000000 000000c8
0000000000000004 0000000000000000 WA 0 0 4
[ 4] .bss NOBITS 0000000000000000 000000cc
0000000000000000 0000000000000000 WA 0 0 1
[ 5] .rodata PROGBITS 0000000000000000 000000d0
0000000000000032 0000000000000000 A 0 0 8
[ 6] .comment PROGBITS 0000000000000000 00000102
000000000000002b 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 0000012d
0000000000000000 0000000000000000 0 0 1
[ 8] .note.gnu.propert NOTE 0000000000000000 00000130
0000000000000020 0000000000000000 A 0 0 8
[ 9] .eh_frame PROGBITS 0000000000000000 00000150
0000000000000038 0000000000000000 A 0 0 8
[10] .rela.eh_frame RELA 0000000000000000 00000448
0000000000000018 0000000000000018 I 11 9 8
[11] .symtab SYMTAB 0000000000000000 00000188
00000000000001b0 0000000000000018 12 10 8
[12] .strtab STRTAB 0000000000000000 00000338
000000000000004d 0000000000000000 0 0 1
[13] .shstrtab STRTAB 0000000000000000 00000460
0000000000000074 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)
4.3.4 重定位节
表述了各个段引用的外部符号等,在链接时,需要通过重定位节对这些位置的地址进行修改。链接器会通过重定位条目的类型判断该使用什么养的方法计算正确的地址值,通过偏移量等信息计算出正确的地址
本程序需要重定位的信息有:.rodata中的模式串,puts,exit,printf,slepsecs,sleep,getchar这些符号。
重定位节 ‘.rela.text’ at offset 0x388 contains 8 entries:
偏移量 信息 类型 符号值 符号名称 + 加数
00000000001c 000500000002 R_X86_64_PC32 0000000000000000 .rodata - 4
000000000021 000d00000004 R_X86_64_PLT32 0000000000000000 puts - 4
00000000002b 000e00000004 R_X86_64_PLT32 0000000000000000 exit - 4
000000000054 000500000002 R_X86_64_PC32 0000000000000000 .rodata + 21
00000000005e 000f00000004 R_X86_64_PLT32 0000000000000000 printf - 4
000000000064 000a00000002 R_X86_64_PC32 0000000000000000 sleepsecs - 4
00000000006b 001000000004 R_X86_64_PLT32 0000000000000000 sleep - 4
00000000007a 001100000004 R_X86_64_PLT32 0000000000000000 getchar - 4

重定位节 ‘.rela.eh_frame’ at offset 0x448 contains 1 entry:
偏移量 信息 类型 符号值 符号名称 + 加数
000000000020 000200000002 R_X86_64_PC32 0000000000000000 .text + 0
4.3.5 符号表:
.symtab是一个符号表,它存放在程序中定义和引用的函数和全局变量的信息。
Symbol table ‘.symtab’ contains 18 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
4: 0000000000000000 0 SECTION LOCAL DEFAULT 4
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 0 SECTION LOCAL DEFAULT 7
7: 0000000000000000 0 SECTION LOCAL DEFAULT 8
8: 0000000000000000 0 SECTION LOCAL DEFAULT 9
9: 0000000000000000 0 SECTION LOCAL DEFAULT 6
10: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 sleepsecs
11: 0000000000000000 133 FUNC GLOBAL DEFAULT 1 main
12: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND GLOBAL_OFFSET_TABLE
13: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND puts
14: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND exit
15: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf
16: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND sleep
17: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND getchar
4.4 Hello.o的结果解析
命令:objdump -d -r hello.o > Disas_hello.s
hello.o: 文件格式 elf64-x86-64

Disassembly of section .text:

0000000000000000 :
0: f3 0f 1e fa endbr64
4: 55 push %rbp
5: 48 89 e5 mov %rsp,%rbp
8: 48 83 ec 20 sub $0x20,%rsp
c: 89 7d ec mov %edi,-0x14(%rbp)
f: 48 89 75 e0 mov %rsi,-0x20(%rbp)
13: 83 7d ec 03 cmpl $0x3,-0x14(%rbp)
17: 74 16 je 2f <main+0x2f>
19: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 20 <main+0x20>
1c: R_X86_64_PC32 .rodata-0x4
20: e8 00 00 00 00 callq 25 <main+0x25>
21: R_X86_64_PLT32 puts-0x4
25: bf 01 00 00 00 mov $0x1,%edi
2a: e8 00 00 00 00 callq 2f <main+0x2f>
2b: R_X86_64_PLT32 exit-0x4
2f: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
36: eb 3b jmp 73 <main+0x73>
38: 48 8b 45 e0 mov -0x20(%rbp),%rax
3c: 48 83 c0 10 add $0x10,%rax
40: 48 8b 10 mov (%rax),%rdx
43: 48 8b 45 e0 mov -0x20(%rbp),%rax
47: 48 83 c0 08 add $0x8,%rax
4b: 48 8b 00 mov (%rax),%rax
4e: 48 89 c6 mov %rax,%rsi
51: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 58 <main+0x58>
54: R_X86_64_PC32 .rodata+0x21
58: b8 00 00 00 00 mov $0x0,%eax
5d: e8 00 00 00 00 callq 62 <main+0x62>
5e: R_X86_64_PLT32 printf-0x4
62: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # 68 <main+0x68>
64: R_X86_64_PC32 sleepsecs-0x4
68: 89 c7 mov %eax,%edi
6a: e8 00 00 00 00 callq 6f <main+0x6f>
6b: R_X86_64_PLT32 sleep-0x4
6f: 83 45 fc 01 addl $0x1,-0x4(%rbp)
73: 83 7d fc 09 cmpl $0x9,-0x4(%rbp)
77: 7e bf jle 38 <main+0x38>
79: e8 00 00 00 00 callq 7e <main+0x7e>
7a: R_X86_64_PLT32 getchar-0x4
7e: b8 00 00 00 00 mov $0x0,%eax
83: c9 leaveq
84: c3 retq
分析hello.o的反汇编,与第3章的 hello.s进行对照分析:

  1. hello.s中的操作数还为十进制,hello.o反汇编代码中的操作数为十六进制。
  2. 跳转语句后面,hello.s中是.L2和.LC1等段名称,而反汇编代码中跳转指令之后是间接地址。
  3. hello.s中,call指令使用的是函数名称,而反汇编代码中call指令使用的是main函数的相对偏移地址。并在.rela.text节中为其添加了重定位条目,等待链接后确定运行时的地址。
    4.5 本章小结
    本章对汇编的概念和作用进行了概述。通过汇编器的操作,汇编语言转化为机器语言,可重定位目标文件的生成为后面链接做准备。又可重定位目标elf格式进行了分析,分析了重定位项目。同时对hello.o文件进行反汇编,与之前生成的hello.s进行对比,更了解汇编对代码的改变。
    (第4章1分)

第5章 链接
5.1 链接的概念与作用
概念:
链接是将各种不同文件的代码和数据部分收集(符号解析和重定位)起来并组合成一个单一文件的过程。
作用:
令源程序节省空间而未编入的常用函数文件(如printf.o)进行合并,生成可以正常工作的可执行文件。这令分离编译成为可能,节省了大量的工作空间。
5.2 在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 hello.o /usr/lib/x86_64-linux-gnu/libc.so /usr/lib/x86_64-linux-gnu/crtn.o

图5.1链接命令
5.3 可执行目标文件hello的格式
命令:readelf -a hello > hello_elf.txt
ELF文件头:
ELF 头:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
类别: ELF64
数据: 2 补码,小端序 (little endian)
Version: 1 (current)
OS/ABI: UNIX - System V
ABI 版本: 0
类型: EXEC (可执行文件)
系统架构: Advanced Micro Devices X86-64
版本: 0x1
入口点地址: 0x4010d0
程序头起点: 64 (bytes into file)
Start of section headers: 14200 (bytes into file)
标志: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 12
Size of section headers: 64 (bytes)
Number of section headers: 27
Section header string table index: 26
节头:
描述了各个节的大小、偏移量和其他属性。链接器链接时,会将各个文件的相同段合并成一个大段,并且根据这个大段的大小以及偏移量重新设置各个符号的地址。
节头:
[号] 名称 类型 地址 偏移量
大小 全体大小 旗标 链接 信息 对齐
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 00000000004002e0 000002e0
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.gnu.propert NOTE 0000000000400300 00000300
0000000000000020 0000000000000000 A 0 0 8
[ 3] .note.ABI-tag NOTE 0000000000400320 00000320
0000000000000020 0000000000000000 A 0 0 4
[ 4] .hash HASH 0000000000400340 00000340
0000000000000034 0000000000000004 A 6 0 8
[ 5] .gnu.hash GNU_HASH 0000000000400378 00000378
000000000000001c 0000000000000000 A 6 0 8
[ 6] .dynsym DYNSYM 0000000000400398 00000398
00000000000000c0 0000000000000018 A 7 1 8
[ 7] .dynstr STRTAB 0000000000400458 00000458
0000000000000057 0000000000000000 A 0 0 1
[ 8] .gnu.version VERSYM 00000000004004b0 000004b0
0000000000000010 0000000000000002 A 6 0 2
[ 9] .gnu.version_r VERNEED 00000000004004c0 000004c0
0000000000000020 0000000000000000 A 7 1 8
[10] .rela.dyn RELA 00000000004004e0 000004e0
0000000000000030 0000000000000018 A 6 0 8
[11] .rela.plt RELA 0000000000400510 00000510
0000000000000078 0000000000000018 AI 6 21 8
[12] .init PROGBITS 0000000000401000 00001000
000000000000001b 0000000000000000 AX 0 0 4
[13] .plt PROGBITS 0000000000401020 00001020
0000000000000060 0000000000000010 AX 0 0 16
[14] .plt.sec PROGBITS 0000000000401080 00001080
0000000000000050 0000000000000010 AX 0 0 16
[15] .text PROGBITS 00000000004010d0 000010d0
0000000000000135 0000000000000000 AX 0 0 16
[16] .fini PROGBITS 0000000000401208 00001208
000000000000000d 0000000000000000 AX 0 0 4
[17] .rodata PROGBITS 0000000000402000 00002000
000000000000003a 0000000000000000 A 0 0 8
[18] .eh_frame PROGBITS 0000000000402040 00002040
00000000000000fc 0000000000000000 A 0 0 8
[19] .dynamic DYNAMIC 0000000000403e50 00002e50
00000000000001a0 0000000000000010 WA 7 0 8
[20] .got PROGBITS 0000000000403ff0 00002ff0
0000000000000010 0000000000000008 WA 0 0 8
[21] .got.plt PROGBITS 0000000000404000 00003000
0000000000000040 0000000000000008 WA 0 0 8
[22] .data PROGBITS 0000000000404040 00003040
0000000000000008 0000000000000000 WA 0 0 4
[23] .comment PROGBITS 0000000000000000 00003048
000000000000002a 0000000000000001 MS 0 0 1
[24] .symtab SYMTAB 0000000000000000 00003078
00000000000004c8 0000000000000018 25 30 8
[25] .strtab STRTAB 0000000000000000 00003540
0000000000000150 0000000000000000 0 0 1
[26] .shstrtab STRTAB 0000000000000000 00003690
00000000000000e1 0000000000000000 0 0 1
5.4 hello的虚拟地址空间
使用edb加载hello, Data Dump 窗口可以查看加载到虚拟地址中的 hello 程序。查看 ELF 格式文件中的 Program Headers,它告诉链接器运行时加载的内容,并提供动态链接的信息。每一个表项提供了各段在虚拟地址空间和物理地址空间的各方面的信息。在下面可以看出,程序包含PHDR,INTERP,LOAD ,DYNAMIC,NOTE ,GNU_STACK,GNU_RELRO几个部分,如下图所示。

图5.2 edb中Data Dump视图

其中PHDR 保存程序头表。INTERP 指定在程序已经从可执行文件映射到内存之后,必须调用的解释器。LOAD 表示一个需要从二进制文件映射到虚拟地址空间的段。其中保存了常量数据、程序的目标代码等。DYNAMIC 保存了由动态链接器使用的信息。NOTE 保存辅助信息。GNU_STACK为权限标志,用于标志栈是否是可执行。GNU_RELRO指定在重定位结束之后哪些内存区域是需要设置只读。

图5.3 edb中Data Dump视图

5.5 链接的重定位过程分析
objdump -d -r hello > hello_objdump.s

hello: 文件格式 elf64-x86-64

hello: 文件格式 elf64-x86-64

Disassembly of section .init:

0000000000401000 <_init>:
401000: f3 0f 1e fa endbr64
401004: 48 83 ec 08 sub $0x8,%rsp
401008: 48 8b 05 e9 2f 00 00 mov 0x2fe9(%rip),%rax # 403ff8 <gmon_start>
40100f: 48 85 c0 test %rax,%rax
401012: 74 02 je 401016 <_init+0x16>
401014: ff d0 callq *%rax
401016: 48 83 c4 08 add $0x8,%rsp
40101a: c3 retq

Disassembly of section .plt:

0000000000401020 <.plt>:
401020: ff 35 e2 2f 00 00 pushq 0x2fe2(%rip) # 404008 <GLOBAL_OFFSET_TABLE+0x8>
401026: f2 ff 25 e3 2f 00 00 bnd jmpq *0x2fe3(%rip) # 404010 <GLOBAL_OFFSET_TABLE+0x10>
40102d: 0f 1f 00 nopl (%rax)
401030: f3 0f 1e fa endbr64
401034: 68 00 00 00 00 pushq $0x0
401039: f2 e9 e1 ff ff ff bnd jmpq 401020 <.plt>
40103f: 90 nop
401040: f3 0f 1e fa endbr64
401044: 68 01 00 00 00 pushq $0x1
401049: f2 e9 d1 ff ff ff bnd jmpq 401020 <.plt>
40104f: 90 nop
401050: f3 0f 1e fa endbr64
401054: 68 02 00 00 00 pushq $0x2
401059: f2 e9 c1 ff ff ff bnd jmpq 401020 <.plt>
40105f: 90 nop
401060: f3 0f 1e fa endbr64
401064: 68 03 00 00 00 pushq $0x3
401069: f2 e9 b1 ff ff ff bnd jmpq 401020 <.plt>
40106f: 90 nop
401070: f3 0f 1e fa endbr64
401074: 68 04 00 00 00 pushq $0x4
401079: f2 e9 a1 ff ff ff bnd jmpq 401020 <.plt>
40107f: 90 nop

Disassembly of section .plt.sec:

0000000000401080 puts@plt:
401080: f3 0f 1e fa endbr64
401084: f2 ff 25 8d 2f 00 00 bnd jmpq *0x2f8d(%rip) # 404018 <puts@GLIBC_2.2.5>
40108b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)

0000000000401090 printf@plt:
401090: f3 0f 1e fa endbr64
401094: f2 ff 25 85 2f 00 00 bnd jmpq *0x2f85(%rip) # 404020 <printf@GLIBC_2.2.5>
40109b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)

00000000004010a0 getchar@plt:
4010a0: f3 0f 1e fa endbr64
4010a4: f2 ff 25 7d 2f 00 00 bnd jmpq *0x2f7d(%rip) # 404028 <getchar@GLIBC_2.2.5>
4010ab: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)

00000000004010b0 exit@plt:
4010b0: f3 0f 1e fa endbr64
4010b4: f2 ff 25 75 2f 00 00 bnd jmpq *0x2f75(%rip) # 404030 <exit@GLIBC_2.2.5>
4010bb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)

00000000004010c0 sleep@plt:
4010c0: f3 0f 1e fa endbr64
4010c4: f2 ff 25 6d 2f 00 00 bnd jmpq *0x2f6d(%rip) # 404038 <sleep@GLIBC_2.2.5>
4010cb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)

Disassembly of section .text:

00000000004010d0 <_start>:
4010d0: f3 0f 1e fa endbr64
4010d4: 31 ed xor %ebp,%ebp
4010d6: 49 89 d1 mov %rdx,%r9
4010d9: 5e pop %rsi
4010da: 48 89 e2 mov %rsp,%rdx
4010dd: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
4010e1: 50 push %rax
4010e2: 54 push %rsp
4010e3: 49 c7 c0 00 12 40 00 mov $0x401200,%r8
4010ea: 48 c7 c1 90 11 40 00 mov $0x401190,%rcx
4010f1: 48 c7 c7 05 11 40 00 mov $0x401105,%rdi
4010f8: ff 15 f2 2e 00 00 callq *0x2ef2(%rip) # 403ff0 <__libc_start_main@GLIBC_2.2.5>
4010fe: f4 hlt
4010ff: 90 nop

0000000000401100 <_dl_relocate_static_pie>:
401100: f3 0f 1e fa endbr64
401104: c3 retq

0000000000401105 :
401105: f3 0f 1e fa endbr64
401109: 55 push %rbp
40110a: 48 89 e5 mov %rsp,%rbp
40110d: 48 83 ec 20 sub $0x20,%rsp
401111: 89 7d ec mov %edi,-0x14(%rbp)
401114: 48 89 75 e0 mov %rsi,-0x20(%rbp)
401118: 83 7d ec 03 cmpl $0x3,-0x14(%rbp)
40111c: 74 16 je 401134 <main+0x2f>
40111e: 48 8d 3d e3 0e 00 00 lea 0xee3(%rip),%rdi # 402008 <_IO_stdin_used+0x8>
401125: e8 56 ff ff ff callq 401080 puts@plt
40112a: bf 01 00 00 00 mov $0x1,%edi
40112f: e8 7c ff ff ff callq 4010b0 exit@plt
401134: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
40113b: eb 3b jmp 401178 <main+0x73>
40113d: 48 8b 45 e0 mov -0x20(%rbp),%rax
401141: 48 83 c0 10 add $0x10,%rax
401145: 48 8b 10 mov (%rax),%rdx
401148: 48 8b 45 e0 mov -0x20(%rbp),%rax
40114c: 48 83 c0 08 add $0x8,%rax
401150: 48 8b 00 mov (%rax),%rax
401153: 48 89 c6 mov %rax,%rsi
401156: 48 8d 3d d0 0e 00 00 lea 0xed0(%rip),%rdi # 40202d <_IO_stdin_used+0x2d>
40115d: b8 00 00 00 00 mov $0x0,%eax
401162: e8 29 ff ff ff callq 401090 printf@plt
401167: 8b 05 d7 2e 00 00 mov 0x2ed7(%rip),%eax # 404044
40116d: 89 c7 mov %eax,%edi
40116f: e8 4c ff ff ff callq 4010c0 sleep@plt
401174: 83 45 fc 01 addl $0x1,-0x4(%rbp)
401178: 83 7d fc 09 cmpl $0x9,-0x4(%rbp)
40117c: 7e bf jle 40113d <main+0x38>
40117e: e8 1d ff ff ff callq 4010a0 getchar@plt
401183: b8 00 00 00 00 mov $0x0,%eax
401188: c9 leaveq
401189: c3 retq
40118a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)

0000000000401190 <__libc_csu_init>:
401190: f3 0f 1e fa endbr64
401194: 41 57 push %r15
401196: 4c 8d 3d b3 2c 00 00 lea 0x2cb3(%rip),%r15 # 403e50 <_DYNAMIC>
40119d: 41 56 push %r14
40119f: 49 89 d6 mov %rdx,%r14
4011a2: 41 55 push %r13
4011a4: 49 89 f5 mov %rsi,%r13
4011a7: 41 54 push %r12
4011a9: 41 89 fc mov %edi,%r12d
4011ac: 55 push %rbp
4011ad: 48 8d 2d 9c 2c 00 00 lea 0x2c9c(%rip),%rbp # 403e50 <_DYNAMIC>
4011b4: 53 push %rbx
4011b5: 4c 29 fd sub %r15,%rbp
4011b8: 48 83 ec 08 sub $0x8,%rsp
4011bc: e8 3f fe ff ff callq 401000 <_init>
4011c1: 48 c1 fd 03 sar $0x3,%rbp
4011c5: 74 1f je 4011e6 <__libc_csu_init+0x56>
4011c7: 31 db xor %ebx,%ebx
4011c9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
4011d0: 4c 89 f2 mov %r14,%rdx
4011d3: 4c 89 ee mov %r13,%rsi
4011d6: 44 89 e7 mov %r12d,%edi
4011d9: 41 ff 14 df callq *(%r15,%rbx,8)
4011dd: 48 83 c3 01 add $0x1,%rbx
4011e1: 48 39 dd cmp %rbx,%rbp
4011e4: 75 ea jne 4011d0 <__libc_csu_init+0x40>
4011e6: 48 83 c4 08 add $0x8,%rsp
4011ea: 5b pop %rbx
4011eb: 5d pop %rbp
4011ec: 41 5c pop %r12
4011ee: 41 5d pop %r13
4011f0: 41 5e pop %r14
4011f2: 41 5f pop %r15
4011f4: c3 retq
4011f5: 66 66 2e 0f 1f 84 00 data16 nopw %cs:0x0(%rax,%rax,1)
4011fc: 00 00 00 00

0000000000401200 <__libc_csu_fini>:
401200: f3 0f 1e fa endbr64
401204: c3 retq

Disassembly of section .fini:

0000000000401208 <_fini>:
401208: f3 0f 1e fa endbr64
40120c: 48 83 ec 08 sub $0x8,%rsp
401210: 48 83 c4 08 add $0x8,%rsp
401214: c3 retq 分析hello与hello.o的不同,说明分析hello与hello.o的不同:

  1. 链接增加了新的函数:
    在hello中加入了用到的库函数,如exit、printf等等
  2. 增加的节:
    Hello中增加了.init和.plt节,和一些节中定义的函数。
  3. 函数调用
    Hello中没有了重定位条目,跳转和函数调用的地址都变为了虚拟内存地址。
  4. 地址访问
    hello.o中的相对偏移地址变成了hello中的虚拟内存地址。Hello.o文件中对于地址的定位时不明确的,地址在运行中才能重定位。
    链接的过程:
    链接器将各个目标文件合在一起生成最终的可执行文件,对函数段和变量等调用进行明确。
    5.6 hello的执行流程
    401000 <_init>
    401020 <.plt>
    401030 puts@plt
    401040 printf@plt
    401050 getchar@plt
    401060 atoi@plt
    401070 exit@plt
    401080 sleep@plt
    401090 <_start>
    4010c0 <_dl_relocate_static_pie>
    4010c1
    401150 <__libc_csu_init>
    4011b0 <__libc_csu_fini>
    4011b4 <_fini>

图5.4 edb运行程序

使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。
5.7 Hello的动态链接分析
在elf文件中:
[20] .got PROGBITS 0000000000403ff0 00002ff0
0000000000000010 0000000000000008 WA 0 0 8
[21] .got.plt PROGBITS 0000000000404000 00003000
0000000000000040 0000000000000008 WA 0 0 8
进入edb查看:

对于变量而言,我们利用代码段和数据段的相对位置不变的原则计算正确地址。对于库函数而言,需要plt、got合作,plt初始存的是一批代码,它们跳转到got所指示的位置,然后调用链接器。初始时got里面存的都是plt的第二条指令,随后链接器修改got,下一次再调用plt时,指向的就是正确的内存地址。plt就能跳转到正确的区域。

5.8 本章小结
本章主要分析了链接的过程。通过对比hello和hello.o的反汇编代码,更好的掌握了链接中重定位的模式。也对动态链接过程进行了一定分析,加深了理解。
(以下格式自行编排,编辑时删除)
(第5章1分)

第6章 hello进程管理
6.1 进程的概念与作用
概念:
进程是执行中程序的抽象。
作用:
1.每次运行程序时,shell创建新进程,在这个进程的上下文切换中运行这个可执行目标文件。应用程序也能够创建新进程,并且在新进程的上下文中运行它们自己的代码或其他应用程序。
2.进程提供给应用程序的关键抽象:一个独立的逻辑控制流,如同程序独占处理器;一个私有的地址空间,如同程序独占内存系统。

6.2 简述壳Shell-bash的作用与处理流程
作用:解释命令,连接用户和操作系统以及内核
流程:
shell先分词,判断命令是否为内部命令,如果不是,则寻找可执行文件进行执行,重复这个流程:
1. Shell首先从命令行中找出特殊字符(元字符),在将元字符翻译成间隔符号。元字符将命令行划分成小块tokens。Shell中的元字符如下所示:SPACE , TAB , NEWLINE , & , ; , ( , ) ,< , > , |
2. 程序块tokens被处理,检查看他们是否是shell中所引用到的关键字。
3. 当程序块tokens被确定以后,shell根据aliases文件中的列表来检查命令的第一个单词。如果这个单词出现在aliases表中,执行替换操作并且处理过程回到第一步重新分割程序块tokens。
4. Shell对~符号进行替换。
5. Shell对所有前面带有 符 号 的 变 量 进 行 替 换 。 6 . S h e l l 将 命 令 行 中 的 内 嵌 命 令 表 达 式 替 换 成 命 令 ; 他 们 一 般 都 采 用 符号的变量进行替换。 6. Shell将命令行中的内嵌命令表达式替换成命令;他们一般都采用 6Shell(command)标记法。
7. Shell计算采用$(expression)标记的算术表达式。
8. Shell将命令字符串重新划分为新的块tokens。这次划分的依据是栏位分割符号,称为IFS。缺省的IFS变量包含有:SPACE , TAB 和换行符号。
9. Shell执行通配符* ? [ ]的替换。
10. shell把所有從處理的結果中用到的注释删除,並且按照下面的顺序实行命令的检查:
I. 内建的命令
II. shell函数(由用户自己定义的)
III. 可执行的脚本文件(需要寻找文件和PATH路径)
11. 在执行前的最后一步是初始化所有的输入输出重定向。
12. 最后,执行命令。
6.3 Hello的fork进程创建过程
根据shell的处理流程,输入命令执行hello后,父进程如果判断不是内部指令,即会通过fork函数创建子进程。子进程与父进程近似,并得到一份与父进程用户级虚拟空间相同且独立的副本——包括数据段、代码、共享库、堆和用户栈。父进程打开的文件,子进程也可读写。二者之间最大的不同或许在于PID的不同。Fork函数只会被调用一次,但会返回两次,在父进程中,fork返回子进程的PID,在子进程中,fork返回0。
6.4 Hello的execve过程
execve函数在加载并运行可执行目标文件Hello,且带列表argv和环境变量列表envp。该函数的作用就是在当前进程的上下文中加载并运行一个新的程序。
只有当出现错误时,例如找不到Hello时,execve才会返回到调用程序,这里与一次调用两次返回的fork不同。
在execve加载了Hello之后,它调用启动代码。启动代码设置栈,并将控制传递给新程序的主函数,该主函数有如下的原型:
int main(intargc , char **argv , char *envp);
结合虚拟内存和内存映射过程,可以更详细地说明exceve函数实际上是如何加载和执行程序Hello:

  1. 删除已存在的用户区域(自父进程独立)。
  2. 映射私有区:为Hello的代码、数据、.bss和栈区域创建新的区域结构,所有这些区域都是私有的、写时才复制的。
  3. 映射共享区:比如Hello程序与标准C库libc.so链接,这些对象都是动态链接到Hello的,然后再用户虚拟地址空间中的共享区域内。
  4. 设置PC:exceve做的最后一件事就是设置当前进程的上下文中的程序计数器,使之指向代码区域的入口点。

6.5 Hello的进程执行
逻辑控制流:
一系列程序计数器 PC 的值的序列叫做逻辑控制流。由于进程是轮流使用处理器的,同一个处理器每个进程执行它的流的一部分后被抢占,然后轮到其他进程。
用户模式和内核模式:
处理器使用一个寄存器提供两种模式的区分。用户模式的进程不允许执行特殊指令,不允许直接引用地址空间中内核区的代码和数据;内核模式进程可以执行指令集中的任何命令,并且可以访问系统中的任何内存位置。
上下文:
上下文就是内核重新启动一个被抢占的进程所需要恢复的原来的状态,由寄存器、程序计数器、用户栈、内核栈和内核数据结构等对象的值构成。
示例:sleep进程的调度过程

图6.1 进程上下文切换
初始时,控制流再hello内,处于用户模式
调用系统函数sleep后,进入内核态,此时间片停止。
2s后,发送中断信号,转回用户模式,继续执行指令。
调度的过程:
在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程,这种决策就叫做调度,是由内核中称为调度器的代码处理的。当内核选择一个新的进程运行,我们说内核调度了这个进程。在内核调度了一个新的进程运行了之后,它就抢占了当前进程,并使用上下文切换机制来将控制转移到新的进程。
以执行sleep函数为例,sleep函数请求调用休眠进程,sleep将内核抢占,进入倒计时,当倒计时结束后,hello程序重新抢占内核,继续执行。
用户态与核心态转换:
为了能让处理器安全运行,不至于损坏操作系统,必然需要先知应用程序可执行指令所能访问的地址空间范围。因此,就存在了用户态与核心态的划分,核心态可以说是“创世模式”,拥有最高的访问权限,处理器以一个寄存器当做模式位来描述当前进程的特权。进程只有故障、中断或陷入系统调用时才会得到内核访问权限,其他情况下始终处于用户权限之中,保证了系统的安全性。
6.6 hello的异常与信号处理

异常类型:

图6.2 异常类型

处理方式:

图6.3 中断处理

图6.4 陷阱处理

图6.5 故障处理

图6.9 终止处理

Ctrl+Z:进程收到 SIGSTP 信号, hello 进程挂起。用ps查看其进程PID,可以发现hello的PID是15720;再用jobs查看此时hello的后台 job号是1,调用 fg 1将其调回前台。

图6.10 Ctrl+Z

Ctrl+C:进程收到 SIGINT 信号,结束 hello。在ps中查询不到其PID,在job中也没有显示,可以看出hello已经被彻底结束。

图6.11 Ctrl+C

中途乱按:将屏幕的输入缓存到缓冲区。乱码被认为是命令

图6.12 中途乱按

Kill命令:挂起的进程被终止,在ps中无法查到到其PID。

图6.13 kill

6.7本章小结
本章回顾了hello进程的执行过程。描述了hello进程的创建、加载和终止。在hello运行过程中,内核对其进行管理和分配,决定何时进行上下文切换。在hello的运行过程中,接受到不同的异常信号时,异常处理程序将对异常信号做出相应,执行相应的代码。通过分析,对hello执行过程中的信号处理机制有了更深的认识,对linux的后台管理机制有了进一步的了解。
(第6章1分)

第7章 hello的存储管理
7.1 hello的存储器地址空间
 逻辑地址
逻辑地址(Logical Address)是指由程序hello产生的与段相关的偏移地址部分。
 线性地址
线性地址是逻辑地址到物理地址变换之间的中间层。程序hello的代码会产生逻辑地址,或者说是段中的偏移地址,它加上相应段的基地址就生成了一个线性地址。
 虚拟地址
有时我们也把逻辑地址称为虚拟地址。因为与虚拟内存空间的概念类似,逻辑地址也是与实际物理内存容量无关的,是hello中的虚拟地址。
 物理地址
物理地址是指出现在CPU外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果地址。如果启用了分页机制,那么hello的线性地址会使用页目录和页表中的项变换成hello的物理地址;如果没有启用分页机制,那么hello的线性地址就直接成为物理地址了。
7.2 Intel逻辑地址到线性地址的变换-段式管理
一个逻辑地址由两部分组成,段标识符,段内偏移量。段标识符是一个16位长的字段组成,称为段选择符,其中前13位是一个索引号。后面三位包含一些硬件细节。
索引号,可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段。
这里面,我们只用关心Base字段,它描述了一个段的开始位置的线性地址。
全局的段描述符,放在“全局段描述符表(GDT)”中,一些局部的段描述符,放在“局部段描述符表(LDT)”中。
GDT在内存中的地址和大小存放在CPU的gdtr控制寄存器中,而LDT则在ldtr寄存器中。
给定一个完整的逻辑地址段选择符+段内偏移地址,
看段选择符的T1=0还是1,知道当前要转换是GDT中的段,还是LDT中的段,再根据相应寄存器,得到其地址和大小。我们就有了一个数组了。
拿出段选择符中前13位,可以在这个数组中,查找到对应的段描述符,这样,它了Base,即基地址就知道了。
把Base + offset,就是要转换的线性地址了
7.3 Hello的线性地址到物理地址的变换-页式管理
页式管理是一种内存空间存储管理的技术,页式管理分为静态页式管理和动态页式管理。将各进程的虚拟空间划分成若干个长度相等的页(page),页式管理把内存空间按页的大小划分成片或者页面(page frame),然后把页式虚拟地址与内存地址建立一一对应页表,并用相应的硬件地址变换机构,来解决离散地址变换问题。页式管理采用请求调页或预调页技术实现了内外存存储器的统一管理。

图7.1页式管理流程图
优点:

  1. 不要求作业或进程的程序段和数据在内存中连续存放,有效地解决了碎片问题。
  2. 动态页式管理提供了内存和外存统一管理的虚存实现方式,使用户可以利用的存储空间大大增加。这既提高了主存的利用率,又有利于组织多道程序执行。
    缺点:
  3. 要求有相应的硬件支持。例如地址变换机构,缺页中断的产生和选择淘汰页面等都要求有相应的硬件支持。这增加了机器成本。
  4. 增加了系统开销,例如缺页中断处理机
  5. 请求调页的算法如选择不当,有可能产生抖动现象。
  6. 虽然消除了碎片,但每个作业或进程的最后一页内总有一部分空间得不到利用果页面较大,则这一部分的损失仍然较大。

7.4 TLB与四级页表支持下的VA到PA的变换
每次CPU产生一个虚拟地址,MMU(内存管理单元)就必须查阅一个PTE(页表条目),以便将虚拟地址翻译为物理地址。在最糟糕的情况下,这会从内存多取一次数据,代价是几十到几百个周期。如果PTE碰巧缓存在L1中,那么开销就会下降1或2个周期。然而,许多系统都试图消除即使是这样的开销,它们在MMU中包括了一个关于PTE的小的缓存,称为翻译后备缓存器(TLB)。
多级页表:
将虚拟地址的VPN划分为相等大小的不同的部分,每个部分用于寻找由上一级确定的页表基址对应的页表条目。

图7.2使用k级页表进行翻译

解析VA,利用前m位vpn1寻找一级页表位置,接着一次重复k次,在第k级页表获得了页表条目,将PPN与VPO组合获得PA
7.5 三级Cache支持下的物理内存访问
CPU发送一条虚拟地址,随后MMU按照上述操作获得了物理地址PA。根据cache大小组数的要求,将PA分为CT(标记位)CS(组号),CO(偏移量)。根据CS寻找到正确的组,比较每一个cacheline是否标记位有效以及CT是否相等。如果命中就直接返回想要的数据,如果不命中,就依次去L2,L3,主存判断是否命中,当命中时,将数据传给CPU同时更新各级cache的cacheline(如果cache已满则要采用换入换出策略)。

图7.3 3级cache

7.6 hello进程fork时的内存映射
当fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID,同时为这个新进程创建虚拟内存。
它创建了当前进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记位只读,并将两个进程中的每个区域结构都标记为私有的写时复制。
当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面。因此,也就为每个进程保持了私有空间地址的抽象概念。
7.7 hello进程execve时的内存映射
1)在bash中的进程中执行了如下的execve调用:execve(“hello”,NULL,NULL);
2)execve函数在当前进程中加载并运行包含在可执行文件hello中的程序,用hello替代了当前bash中的程序。
下面是加载并运行hello的几个步骤:
3)删除已存在的用户区域。
4)映射私有区域
5)映射共享区域
6)设置程序计数器(PC)
exceve做的最后一件事是设置当前进程的上下文中的程序计数器,是指指向代码区域的入口点。而下一次调度这个进程时,他将从这个入口点开始执行。Linux将根据需要换入代码和数据页面。
7.8 缺页故障与缺页中断处理
处理缺页是由硬件和操作系统内核协作完成的:

图7.4 缺页中断处理

整体的处理流程:

  1. 处理器生成一个虚拟地址,并将它传送给MMU
  2. MMU生成PTE地址,并从高速缓存/主存请求得到它
  3. 高速缓存/主存向MMU返回PTE
  4. PTE中的有效位是0,所以MMU出发了一次异常,传递CPU中的控制到操作系统内核中的缺页异常处理程序。
  5. 缺页处理程序确认出物理内存中的牺牲页,如果这个页已经被修改了,则把它换到磁盘。
  6. 缺页处理程序页面调入新的页面,并更新内存中的PTE
    缺页处理程序返回到原来的进程,再次执行导致缺页的命令。CPU将引起缺页的虚拟地址重新发送给MMU。因为虚拟页面已经换存在物理内存中,所以就会命中。
    7.9动态存储分配管理
    动态储存分配管理使用动态内存分配器来进行。动态内存分配器维护着一个进程的虚拟内存区域,称为堆。分配器将堆视为一组不同大小的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可以用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配的状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。动态内存分配主要有两种基本方法与策略:
    带边界标签的隐式空闲链表分配器管理
    带边界标记的隐式空闲链表的每个块是由一个字的头部、有效载荷、可能的额外填充以及一个字的尾部组成的。
    隐式空闲链表:在隐式空闲链表中,因为空闲块是通过头部中的大小字段隐含地连接着的。分配器可以通过遍历堆中所有的块,从而间接地遍历整个空闲块的集合。其中,一个设置了已分配的位而大小为零的终止头部将作为特殊标记的结束块。
    当一个应用请求一个k字节的块时,分配器搜索空闲链表,查找一个足够大的可以放置所请求块的空闲块。分配器有三种放置策略:首次适配、下一次适配合最佳适配。分配完后可以分割空闲块减少内部碎片。同时分配器在面对释放一个已分配块时,可以合并空闲块,其中便利用隐式空闲链表的边界标记来进行合并。
    显式空间链表管理
    显式空闲链表是将空闲块组织为某种形式的显式数据结构。因为根据定义,程序不需要一个空闲块的主体,所以实现这个数据结构的指针可以存放在这些空闲块的主体里面。如,堆可以组织成一个双向链表,在每个空闲块中,都包含一个前驱与一个后继指针。
    显式空闲链表:在显式空闲链表中。可以采用后进先出的顺序维护链表,将最新释放的块放置在链表的开始处,也可以采用按照地址顺序来维护链表,其中链表中每个块的地址都小于它的后继地址,在这种情况下,释放一个块需要线性时间的搜索来定位合适的前驱。
    7.10本章小结
    本章介绍了hello的存储器地址空间、intel的段式管理、hello的页式管理,在指定环境下介绍了 VA 到 PA 的变换、物理内存访问,还介绍 hello 进程 fork 时的内存映射、 execve 时的内存映射、缺页故障与缺页中断处理、动态存储分配管理。
    (第7章 2分)

第8章 hello的IO管理
8.1 Linux的IO设备管理方法
(以下格式自行编排,编辑时删除)
设备的模型化:文件
设备管理:unix io接口
这种将设备优雅地映射为文件的方式,允许Linux内核引出一个简单、低级的应用接口,称为Unix I/O。
我们可以对文件的操作有:打开关闭操作open和close;读写操作read和write;改变当前文件位置lseek等
8.2 简述Unix IO接口及其函数
Unix IO接口:
打开文件:内核返回一个非负整数的文件描述符,通过对此文件描述符对文件进行所有操作。
Linux shell创建的每个进程开始时都有三个打开的文件:标准输入(文件描述符0)、标准输出(描述符为1),标准出错(描述符为2)。头文件<unistd.h>定义了常量STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO,他们可用来代替显式的描述符值。
改变当前的文件位置,文件开始位置为文件偏移量,应用程序通过seek操作,可设置文件的当前位置为k。
读写文件,读操作:从文件复制n个字节到内存,从当前文件位置k开始,然后将k增加到k+n;写操作:从内存复制n个字节到文件,当前文件位置为k,然后更新k
关闭文件:当应用完成对文件的访问后,通知内核关闭这个文件。内核会释放文件打开时创建的数据结构,将描述符恢复到描述符池中
Unix IO函数:

  1. open()函数
    功能描述:用于打开或创建文件,在打开或创建文件时可以指定文件的属性及用户的权限等各种参数。
    函数原型:int open(const char *pathname,int flags,int perms)
    参数:pathname:被打开的文件名(可包括路径名如"dev/ttyS0")flags:文件打开方式,
    返回值:成功:返回文件描述符;失败:返回-1
  2. close()函数
    功能描述:用于关闭一个被打开的的文件
    所需头文件: #include <unistd.h>
    函数原型:int close(int fd)
    参数:fd文件描述符
    函数返回值:0成功,-1出错
  3. read()函数
    功能描述: 从文件读取数据。
    所需头文件: #include <unistd.h>
    函数原型:ssize_t read(int fd, void *buf, size_t count);
    参数:fd:将要读取数据的文件描述词。buf:指缓冲区,即读取的数据会被放到这个缓冲区中去。count: 表示调用一次read操作,应该读多少数量的字符。
    返回值:返回所读取的字节数;0(读到EOF);-1(出错)。
  4. write()函数
    功能描述: 向文件写入数据。
    所需头文件: #include <unistd.h>
    函数原型:ssize_t write(int fd, void *buf, size_t count);
    返回值:写入文件的字节数(成功);-1(出错)
  5. lseek()函数
    功能描述: 用于在指定的文件描述符中将将文件指针定位到相应位置。
    所需头文件:#include <unistd.h>,#include <sys/types.h>
    函数原型:off_t lseek(int fd, off_t offset,int whence);
    参数:fd;文件描述符。offset:偏移量,每一个读写操作所需要移动的距离,单位是字节,可正可负(向前移,向后移)
    返回值:成功:返回当前位移;失败:返回-1

8.3 printf的实现分析
(以下格式自行编排,编辑时删除)
https://www.cnblogs.com/pianist/p/3315801.html
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;
}
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有一个int型的返回值。当程序调用getchar时,程序就等着用户按键,用户输入的字符被存放在键盘缓冲区中直到用户按回车为止(回车字符也放在缓冲区中)。
当用户键入回车之后,getchar才开始从stdio流中每次读入一个字符。getchar函数的返回值是用户输入的第一个字符的ascii码,如出错返回-1,且将用户输入的字符回显到屏幕。如用户在按回车之前输入了不止一个字符,其他字符会保留在键盘缓存区中,等待后续getchar调用读取。也就是说,后续的getchar调用不会等待用户按键,而直接读取缓冲区中的字符,直到缓冲区中的字符读完为后,才等待用户按键。
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章介绍了LinuxI/O设备的基本概念和管理方法,和Unit I/O接口及其函数。又分析了printf和getchar函数的实现。
(第8章1分)

结论
Hello的一生:

  1. hello.c经过预编译,进一步扩充代码生成hello.i文本文件
  2. hello.i经过编译,得到汇编代码hello.s汇编文件
  3. hello.s经过汇编,得到可重定位目标文件hello.o
  4. hello.o经过链接,生成了可执行文件hello
  5. bash进程调用fork函数,生成子进程;并由execve函数加载运行当前进程的上下文中加载并运行新程序hello
  6. hello的变化过程中,会有各种地址,但最终我们需要的是PA物理地址。
  7. hello运行时会调用一些函数,比如printf函数,这些函数与linux I/O的设备模拟化密切相关
  8. hello最终被shell父进程回收,内核为其创建的所有信息被销毁。
    通过一学期的学习,我们对计算机各个基本概念都进行了深入的学习。从最底层的硬件设计,再到指令的运作,再到进程和存储的逻辑,每天都在使用的计算机却有如此复杂的体系结构,一个简单的hello程序却也需要如此复杂的一套运作流程,让人不禁感叹现代计算机工业的精密。
    日后在写程序时,也要时刻牢记计算机的底层设计,提升代码的效率。在整体系统的构建上,也会受到计算机系统的启发,书中很多概念都会使日后的编码更得心应手。另外,尽管经过一学期的学习,恐怕还有诸多问题理解不够深入,在日后的学习中,也会不断学习实践,多阅读此书,做到常读常新。
    (结论0分,缺失 -1分,根据内容酌情加分)

附件
Hello.i:经过预处理后的文件
Hello.s:编译后生成的汇编文件
Hello.o:汇编后生成的可重定位文件
Hello:链接后生成的可执行目标文件
Hello_o_elf.txt:hello.o的elf格式文件
Disas_hello.s:hello.o的反汇编代码
Hello_elf.txt:hello的elf格式
Hello_objdump.s: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 R.O’Hallaron 机械工业出版社
[8] 博客园 [转]printf 函数实现的深入剖析
[9] 博客园 shell命令执行过程
[10] CSDN Ubuntu系统预处理、编译、汇编、链接指令
(参考文献0分,缺失 -1分)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值