计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 计算机科学与技术
学 号 1190202402
班 级 1936602
学 生 李培意
指 导 教 师 刘宏伟
计算机科学与技术学院
2021年6月
从hello这一文件看到一个程序背后的故事。围绕我们每个程序员最先写下的能正常运行的程序hello,透过其整个生命的流程,分析出其背后的种种过程以及各种系统中的管理,来反馈出计算机系统这一复杂系统的理解。也了解硬件,操作系统,程序之间的配合关系。也让hello从编程软件中脱离出来,经历预处理、编译、汇编、再到链接。成为一个系统可运行的程序。也从系统的进程管理,存储管理,IO的管理角度进行分析。以及最后被系统回收。从hello的一生,来看计算机系统的运行。
关键词:hello的一生;计算机系统;存储管理;进程管理;预处理、编译、汇编、链接;
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
2.2在Ubuntu下预处理的命令............................................................................. - 5 -
5.3 可执行目标文件hello的格式....................................................................... - 8 -
6.2 简述壳Shell-bash的作用与处理流程........................................................ - 10 -
6.3 Hello的fork进程创建过程........................................................................ - 10 -
7.2 Intel逻辑地址到线性地址的变换-段式管理............................................... - 11 -
7.3 Hello的线性地址到物理地址的变换-页式管理.......................................... - 11 -
7.4 TLB与四级页表支持下的VA到PA的变换................................................ - 11 -
7.5 三级Cache支持下的物理内存访问............................................................. - 11 -
7.6 hello进程fork时的内存映射..................................................................... - 11 -
7.7 hello进程execve时的内存映射................................................................. - 11 -
7.8 缺页故障与缺页中断处理.............................................................................. - 11 -
8.2 简述Unix IO接口及其函数.......................................................................... - 13 -
第1章 概述
1.1 Hello简介
根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
P2P:From Program to process:
Hello刚开始只是一段C语言代码,存于内存当中,也就是最初的hello.c程序,也就是最初的program。编译器驱动程序。然后编译器驱动程序在需要时候调用预处理器、编译器、汇编器和链接器。首先是预处理器(cpp)将C语言源程序hello.c预处理为一个中间文件;编译器(ccl)将该中间文件翻译为一个汇编语言文件;汇编器(as)将汇编语言文件翻译为可重定位目标文件;最后链接器(ld)创建一个可执行目标文件。可以通过shell创建一个子进程运行该可执行目标文件。
020:From Zero-0 to Zero-0
运行可执行目标文件,调用程序wxecve,一次进行对于虚拟内存的映射,物理内存载入,进入主程序运行代码,调用系统函数
1.2 环境与工具
硬件环境:X64 CPU;2GHz;2G RAM;256GHD Disk 以上
软件环境:Windows7 64位以上;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64位/优麒麟 64位
开发与调试工具:gcc,vim,edb,readelf,HexEdit
1.3 中间结果
hello.c (C语言源程序)
hello.i (hello.c预处理之后的中间文件)
hello.s (hello.i编译成汇编语言之后的汇编语言文件)
hello.o (hello.s生成的二进制文件,也就是可重定位目标文件)
hello (可执行目标文件)
。
1.4 本章小结
介绍了Hello的基本信息,介绍了实验的基本环境工具,以及可能生成的实验的中间结果。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
(以下格式自行编排,编辑时删除)
预处理的概念:预处理器根据字符#开头的命令,修改原始程序,将引用的库合并成完整文件。
C语言的预处理主要有三个方面的内容:
1.宏定义(#define);
2.文件包含(#include);
3.条件编译(#ifdef/#ifndef/#if/#else/#elseif/#endif);
C预处理器是C语言、C++语言的预处理器。用于在编译器处理程序之前预扫描源代码,完成头文件的包含, 宏扩展, 条件编译, 行控制(line control)等操作。
这样做的目的是使得编译器在对程序进行翻译的时候更加方便。
2.2在Ubuntu下预处理的命令
预处理命令:gcc -E hello.c -o hello.i
应截图,展示预处理过程!
2.3 Hello的预处理结果解析
原本只有几行的源代码扩展到了3000多行并且在在原有的代码基础上加入了系统库中包含的typedef,结构体,以及外部引用符号。另外还添加了以#开头的语句,这些都是用来辅助表示代码是源自哪个文件以及行号的。
typedef
结构体,#语句
2.4 本章小结
本章介绍了C预处理的概念和作用。展示了用linux虚拟机进行预处理的过程以及结果,还有对该结果的分析
(以下格式自行编排,编辑时删除)
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
将某种高级程序设计语言翻译为低一级的汇编语言并生成汇编语言文件
是高级语言和机器语言之间的媒介,方便程序进一步转换为机器可读的语言。
注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序
3.2 在Ubuntu下编译的命令
编译命令:gcc -S hello.i -o hello.s
应截图,展示编译过程!
3.3 Hello的编译结果解析
编译结果:
.file "hello.c"
.text
.globl sleepsecs
.data
.align 4
.type sleepsecs, @object
.size sleepsecs, 4
sleepsecs:
.long 2
.section .rodata
.align 8
.LC0:
.string "Usage: Hello 1190202402 \346\235\216\345\237\271\346\204\217\357\274\201"
.LC1:
.string "Hello %s %s\n"
.text
.globl main
.type main, @function
main:
.LFB6:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $32, %rsp
movl %edi, -20(%rbp)
movq %rsi, -32(%rbp)
cmpl $3, -20(%rbp)
je .L2
leaq .LC0(%rip), %rdi
call puts@PLT
movl $1, %edi
call exit@PLT
.L2:
movl $0, -4(%rbp)
jmp .L3
.L4:
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), %rdi
movl $0, %eax
call printf@PLT
movl sleepsecs(%rip), %eax
movl %eax, %edi
call sleep@PLT
addl $1, -4(%rbp)
.L3:
cmpl $9, -4(%rbp)
jle .L4
call getchar@PLT
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE6:
.size main, .-main
.ident "GCC: (Ubuntu 10.3.0-1ubuntu1~20.10) 10.3.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
- 全局变量:全局变量在data节中定义它,并且解释了它。
.globl sleepsecs
- 局部变量:局部变量会在栈中储存它,给他分配一个大小符合的字节大小。
argc/argv 调用main函数里传入的参数,存放在%rdi中
主函数:
main:
.LFB6:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $32, %rsp
movl %edi, -20(%rbp)
movq %rsi, -32(%rbp)
cmpl $3, -20(%rbp)
je .L2
leaq .LC0(%rip), %rdi
call puts@PLT
movl $1, %edi
call exit@PLT
其余略,
rdi和rsi均为从堆栈中调用数据
i 局部变量,在循环中出现,作为每次循环更新的变量 保存在%ebx中
- 赋值
.long 2(全局变量赋值)
movl $0, -4(%rbp) (局部变量赋值)
4.类型转换
.type sleepsecs, @object
.size sleepsecs, 4
sleepsecs:
.long 2
movl sleepsecs(%rip), %eax
movl %eax, %edi
- 算数操作
addl $1, -4(%rbp)(对应i++)
addq $16, %rax
addq $8, %rax
subq $32, %rsp
- 关系操作
cmpl $9, -4(%rbp)(循环中的<操作
cmpl $3, -20(%rbp) (if语句)
- 控制转移
je .L2 根据条件进行跳转
jmp .L3
ret 返回
- 函数操作
call puts@PLT
call exit@PLT
call sleep@PLT
call getchar@PLT
- 头部
.file: 说明文件名字为从hello.c编译而来
.text 代码段
.globl 全局变量:说明sleepsecs是全局变量
.data 数据段
.align 对齐方式,.align 4说明是按四个字节对齐
.type sleepsec,说明这个变量是对象类型
.size 大小,说明这个变量占用的大小为4字节
.long 说明sleepsec的类型是long
.section 说明sleepsecs的节在于只读数据段
注意 main为全局符号在.text段中
3.4 本章小结
介绍了编译的概念及其作用,利用汇编文件解析了汇编文件中各部分的作用,并与源程序对比,看汇编文件有哪些方面不同,从汇编文件中看源程序。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
通过汇编器,将汇编语言翻译成机器语言,并生成机器语言的二进制程序
转化为了机器能够理解的二进制文本,方便程序的执行。
4.2 在Ubuntu下汇编的命令
汇编命令:as hello.s -o hello.o
应截图,展示汇编过程!
4.3 可重定位目标elf格式
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
由图可知,ELF头以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。ELF头剩下的部分包含帮助链接器语法分析和解释目标文件的信息。其中包括ELF头的大小、目标文件的类型(如可重定位、可执行或共享的)、机器类型(如x86-64)、节头部表(section header table)的文件偏移,以及节头部表中条目的大小和数量。不同节的位置和大小是由节头部表描述的,其中目标文件中每个节都有一个固定大小的头目(entry)。
节头部表,描述了每个节的名字,种类,地址偏移,大小,全体大小,flag,链接,信息,对齐。
但没有这些头:
重定位节:有静态链接内容pc也有动态链接内容plt,也存在一个异常处理单元
符号表
4.4 Hello.o的结果解析
hello.o: 文件格式 elf64-x86-64
Disassembly of section .text:
0000000000000000 <main>:
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
- 十进制数被翻译为了16进制数
- 一些符号信息已经被放入了表中,特别是引用函数中的符号的重定位信息
- 堆栈申请大小不同
- 没有再细分便于理解的各种段
- 地址由相对偏移量转变为了可由cpu直接访问的虚拟地址
- 跳转指令的变化
4.5 本章小结
对汇编的概念以及作用的叙述,并且在linux虚拟机中进行了演示,并探究了重定位前和重定位后反汇编的汇编程序的差异。分析了ELF格式文件的组成。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
链接是将各种代码和数据片段收集并组合成一个单一文件的过程,将一个或多个由编译器或汇编器生成的目标文件外加库,链接为一个可执行文件。这个文件可以被加载到内存并执行。链接可以在编译时实现,也就是在源代码被翻译成机器代码时,也可以在加载时实现,也就是在程序被加载器加载到内存并执行时,甚至可以在运行时实现,也就是由应用程序来执行。
产生一个可以直接被加载到内存并执行的文件。
5.2 在Ubuntu下链接的命令
链接命令:gcc hello.o -o hello
5.3 可执行目标文件hello的格式
5.4 hello的虚拟地址空间
前四行为堆栈分配的虚拟空间。
5.5 链接的重定位过程分析
(以下格式自行编排,编辑时删除)
hello: 文件格式 elf64-x86-64
Disassembly of section .init:
0000000000001000 <_init>:
1000: f3 0f 1e fa endbr64
1004: 48 83 ec 08 sub $0x8,%rsp
1008: 48 8b 05 d9 2f 00 00 mov 0x2fd9(%rip),%rax # 3fe8 <__gmon_start__>
100f: 48 85 c0 test %rax,%rax
1012: 74 02 je 1016 <_init+0x16>
1014: ff d0 callq *%rax
1016: 48 83 c4 08 add $0x8,%rsp
101a: c3 retq
Disassembly of section .plt:
0000000000001020 <.plt>:
1020: ff 35 7a 2f 00 00 pushq 0x2f7a(%rip) # 3fa0 <_GLOBAL_OFFSET_TABLE_+0x8>
1026: f2 ff 25 7b 2f 00 00 bnd jmpq *0x2f7b(%rip) # 3fa8 <_GLOBAL_OFFSET_TABLE_+0x10>
102d: 0f 1f 00 nopl (%rax)
1030: f3 0f 1e fa endbr64
1034: 68 00 00 00 00 pushq $0x0
1039: f2 e9 e1 ff ff ff bnd jmpq 1020 <.plt>
103f: 90 nop
1040: f3 0f 1e fa endbr64
1044: 68 01 00 00 00 pushq $0x1
1049: f2 e9 d1 ff ff ff bnd jmpq 1020 <.plt>
104f: 90 nop
1050: f3 0f 1e fa endbr64
1054: 68 02 00 00 00 pushq $0x2
1059: f2 e9 c1 ff ff ff bnd jmpq 1020 <.plt>
105f: 90 nop
1060: f3 0f 1e fa endbr64
1064: 68 03 00 00 00 pushq $0x3
1069: f2 e9 b1 ff ff ff bnd jmpq 1020 <.plt>
106f: 90 nop
1070: f3 0f 1e fa endbr64
1074: 68 04 00 00 00 pushq $0x4
1079: f2 e9 a1 ff ff ff bnd jmpq 1020 <.plt>
107f: 90 nop
Disassembly of section .plt.got:
0000000000001080 <__cxa_finalize@plt>:
1080: f3 0f 1e fa endbr64
1084: f2 ff 25 6d 2f 00 00 bnd jmpq *0x2f6d(%rip) # 3ff8 <__cxa_finalize@GLIBC_2.2.5>
108b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
Disassembly of section .plt.sec:
0000000000001090 <puts@plt>:
1090: f3 0f 1e fa endbr64
1094: f2 ff 25 15 2f 00 00 bnd jmpq *0x2f15(%rip) # 3fb0 <puts@GLIBC_2.2.5>
109b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
00000000000010a0 <printf@plt>:
10a0: f3 0f 1e fa endbr64
10a4: f2 ff 25 0d 2f 00 00 bnd jmpq *0x2f0d(%rip) # 3fb8 <printf@GLIBC_2.2.5>
10ab: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
00000000000010b0 <getchar@plt>:
10b0: f3 0f 1e fa endbr64
10b4: f2 ff 25 05 2f 00 00 bnd jmpq *0x2f05(%rip) # 3fc0 <getchar@GLIBC_2.2.5>
10bb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
00000000000010c0 <exit@plt>:
10c0: f3 0f 1e fa endbr64
10c4: f2 ff 25 fd 2e 00 00 bnd jmpq *0x2efd(%rip) # 3fc8 <exit@GLIBC_2.2.5>
10cb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
00000000000010d0 <sleep@plt>:
10d0: f3 0f 1e fa endbr64
10d4: f2 ff 25 f5 2e 00 00 bnd jmpq *0x2ef5(%rip) # 3fd0 <sleep@GLIBC_2.2.5>
10db: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
Disassembly of section .text:
00000000000010e0 <_start>:
10e0: f3 0f 1e fa endbr64
10e4: 31 ed xor %ebp,%ebp
10e6: 49 89 d1 mov %rdx,%r9
10e9: 5e pop %rsi
10ea: 48 89 e2 mov %rsp,%rdx
10ed: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
10f1: 50 push %rax
10f2: 54 push %rsp
10f3: 4c 8d 05 c6 01 00 00 lea 0x1c6(%rip),%r8 # 12c0 <__libc_csu_fini>
10fa: 48 8d 0d 4f 01 00 00 lea 0x14f(%rip),%rcx # 1250 <__libc_csu_init>
1101: 48 8d 3d c1 00 00 00 lea 0xc1(%rip),%rdi # 11c9 <main>
1108: ff 15 d2 2e 00 00 callq *0x2ed2(%rip) # 3fe0 <__libc_start_main@GLIBC_2.2.5>
110e: f4 hlt
110f: 90 nop
0000000000001110 <deregister_tm_clones>:
1110: 48 8d 3d 01 2f 00 00 lea 0x2f01(%rip),%rdi # 4018 <__TMC_END__>
1117: 48 8d 05 fa 2e 00 00 lea 0x2efa(%rip),%rax # 4018 <__TMC_END__>
111e: 48 39 f8 cmp %rdi,%rax
1121: 74 15 je 1138 <deregister_tm_clones+0x28>
1123: 48 8b 05 ae 2e 00 00 mov 0x2eae(%rip),%rax # 3fd8 <_ITM_deregisterTMCloneTable>
112a: 48 85 c0 test %rax,%rax
112d: 74 09 je 1138 <deregister_tm_clones+0x28>
112f: ff e0 jmpq *%rax
1131: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
1138: c3 retq
1139: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
0000000000001140 <register_tm_clones>:
1140: 48 8d 3d d1 2e 00 00 lea 0x2ed1(%rip),%rdi # 4018 <__TMC_END__>
1147: 48 8d 35 ca 2e 00 00 lea 0x2eca(%rip),%rsi # 4018 <__TMC_END__>
114e: 48 29 fe sub %rdi,%rsi
1151: 48 89 f0 mov %rsi,%rax
1154: 48 c1 ee 3f shr $0x3f,%rsi
1158: 48 c1 f8 03 sar $0x3,%rax
115c: 48 01 c6 add %rax,%rsi
115f: 48 d1 fe sar %rsi
1162: 74 14 je 1178 <register_tm_clones+0x38>
1164: 48 8b 05 85 2e 00 00 mov 0x2e85(%rip),%rax # 3ff0 <_ITM_registerTMCloneTable>
116b: 48 85 c0 test %rax,%rax
116e: 74 08 je 1178 <register_tm_clones+0x38>
1170: ff e0 jmpq *%rax
1172: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
1178: c3 retq
1179: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
0000000000001180 <__do_global_dtors_aux>:
1180: f3 0f 1e fa endbr64
1184: 80 3d 89 2e 00 00 00 cmpb $0x0,0x2e89(%rip) # 4014 <completed.0>
118b: 75 2b jne 11b8 <__do_global_dtors_aux+0x38>
118d: 55 push %rbp
118e: 48 83 3d 62 2e 00 00 cmpq $0x0,0x2e62(%rip) # 3ff8 <__cxa_finalize@GLIBC_2.2.5>
1195: 00
1196: 48 89 e5 mov %rsp,%rbp
1199: 74 0c je 11a7 <__do_global_dtors_aux+0x27>
119b: 48 8b 3d 66 2e 00 00 mov 0x2e66(%rip),%rdi # 4008 <__dso_handle>
11a2: e8 d9 fe ff ff callq 1080 <__cxa_finalize@plt>
11a7: e8 64 ff ff ff callq 1110 <deregister_tm_clones>
11ac: c6 05 61 2e 00 00 01 movb $0x1,0x2e61(%rip) # 4014 <completed.0>
11b3: 5d pop %rbp
11b4: c3 retq
11b5: 0f 1f 00 nopl (%rax)
11b8: c3 retq
11b9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
00000000000011c0 <frame_dummy>:
11c0: f3 0f 1e fa endbr64
11c4: e9 77 ff ff ff jmpq 1140 <register_tm_clones>
00000000000011c9 <main>:
11c9: f3 0f 1e fa endbr64
11cd: 55 push %rbp
11ce: 48 89 e5 mov %rsp,%rbp
11d1: 48 83 ec 20 sub $0x20,%rsp
11d5: 89 7d ec mov %edi,-0x14(%rbp)
11d8: 48 89 75 e0 mov %rsi,-0x20(%rbp)
11dc: 83 7d ec 03 cmpl $0x3,-0x14(%rbp)
11e0: 74 16 je 11f8 <main+0x2f>
11e2: 48 8d 3d 1f 0e 00 00 lea 0xe1f(%rip),%rdi # 2008 <_IO_stdin_used+0x8>
11e9: e8 a2 fe ff ff callq 1090 <puts@plt>
11ee: bf 01 00 00 00 mov $0x1,%edi
11f3: e8 c8 fe ff ff callq 10c0 <exit@plt>
11f8: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
11ff: eb 3b jmp 123c <main+0x73>
1201: 48 8b 45 e0 mov -0x20(%rbp),%rax
1205: 48 83 c0 10 add $0x10,%rax
1209: 48 8b 10 mov (%rax),%rdx
120c: 48 8b 45 e0 mov -0x20(%rbp),%rax
1210: 48 83 c0 08 add $0x8,%rax
1214: 48 8b 00 mov (%rax),%rax
1217: 48 89 c6 mov %rax,%rsi
121a: 48 8d 3d 0c 0e 00 00 lea 0xe0c(%rip),%rdi # 202d <_IO_stdin_used+0x2d>
1221: b8 00 00 00 00 mov $0x0,%eax
1226: e8 75 fe ff ff callq 10a0 <printf@plt>
122b: 8b 05 df 2d 00 00 mov 0x2ddf(%rip),%eax # 4010 <sleepsecs>
1231: 89 c7 mov %eax,%edi
1233: e8 98 fe ff ff callq 10d0 <sleep@plt>
1238: 83 45 fc 01 addl $0x1,-0x4(%rbp)
123c: 83 7d fc 09 cmpl $0x9,-0x4(%rbp)
1240: 7e bf jle 1201 <main+0x38>
1242: e8 69 fe ff ff callq 10b0 <getchar@plt>
1247: b8 00 00 00 00 mov $0x0,%eax
124c: c9 leaveq
124d: c3 retq
124e: 66 90 xchg %ax,%ax
0000000000001250 <__libc_csu_init>:
1250: f3 0f 1e fa endbr64
1254: 41 57 push %r15
1256: 4c 8d 3d 3b 2b 00 00 lea 0x2b3b(%rip),%r15 # 3d98 <__frame_dummy_init_array_entry>
125d: 41 56 push %r14
125f: 49 89 d6 mov %rdx,%r14
1262: 41 55 push %r13
1264: 49 89 f5 mov %rsi,%r13
1267: 41 54 push %r12
1269: 41 89 fc mov %edi,%r12d
126c: 55 push %rbp
126d: 48 8d 2d 2c 2b 00 00 lea 0x2b2c(%rip),%rbp # 3da0 <__do_global_dtors_aux_fini_array_entry>
1274: 53 push %rbx
1275: 4c 29 fd sub %r15,%rbp
1278: 48 83 ec 08 sub $0x8,%rsp
127c: e8 7f fd ff ff callq 1000 <_init>
1281: 48 c1 fd 03 sar $0x3,%rbp
1285: 74 1f je 12a6 <__libc_csu_init+0x56>
1287: 31 db xor %ebx,%ebx
1289: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
1290: 4c 89 f2 mov %r14,%rdx
1293: 4c 89 ee mov %r13,%rsi
1296: 44 89 e7 mov %r12d,%edi
1299: 41 ff 14 df callq *(%r15,%rbx,8)
129d: 48 83 c3 01 add $0x1,%rbx
12a1: 48 39 dd cmp %rbx,%rbp
12a4: 75 ea jne 1290 <__libc_csu_init+0x40>
12a6: 48 83 c4 08 add $0x8,%rsp
12aa: 5b pop %rbx
12ab: 5d pop %rbp
12ac: 41 5c pop %r12
12ae: 41 5d pop %r13
12b0: 41 5e pop %r14
12b2: 41 5f pop %r15
12b4: c3 retq
12b5: 66 66 2e 0f 1f 84 00 data16 nopw %cs:0x0(%rax,%rax,1)
12bc: 00 00 00 00
00000000000012c0 <__libc_csu_fini>:
12c0: f3 0f 1e fa endbr64
12c4: c3 retq
Disassembly of section .fini:
00000000000012c8 <_fini>:
12c8: f3 0f 1e fa endbr64
12cc: 48 83 ec 08 sub $0x8,%rsp
12d0: 48 83 c4 08 add $0x8,%rsp
12d4: c3 retq
汇编代码段增加,增加了.init .plt .plt.sec .fini
.text段除了main增加了一些函数,如_start等
增加了外部的共享库函数
0000000000001090 <puts@plt>:
1090: f3 0f 1e fa endbr64
1094: f2 ff 25 15 2f 00 00 bnd jmpq *0x2f15(%rip) # 3fb0 <puts@GLIBC_2.2.5>
109b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
00000000000010a0 <printf@plt>:
10a0: f3 0f 1e fa endbr64
10a4: f2 ff 25 0d 2f 00 00 bnd jmpq *0x2f0d(%rip) # 3fb8 <printf@GLIBC_2.2.5>
10ab: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
00000000000010b0 <getchar@plt>:
10b0: f3 0f 1e fa endbr64
10b4: f2 ff 25 05 2f 00 00 bnd jmpq *0x2f05(%rip) # 3fc0 <getchar@GLIBC_2.2.5>
10bb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
00000000000010c0 <exit@plt>:
10c0: f3 0f 1e fa endbr64
10c4: f2 ff 25 fd 2e 00 00 bnd jmpq *0x2efd(%rip) # 3fc8 <exit@GLIBC_2.2.5>
10cb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
00000000000010d0 <sleep@plt>:
10d0: f3 0f 1e fa endbr64
10d4: f2 ff 25 f5 2e 00 00 bnd jmpq *0x2ef5(%rip) # 3fd0 <sleep@GLIBC_2.2.5>
10db: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
重定位
重定位节和符号:将所有类型相同的节合并在一起后,这个节就作为可执行目标文件的节。然后链接器把运行时的内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号,当这一步完成时,程序中每条指令和全局变量都有唯一运行时的地址。也就是.text段中增加的一些新的函数,这些函数最终合并为同一聚合结。
重定位节中的符号引用:链接器修改代码节和数据节中对每个符号的引用,使得他们指向正确的运行地址。
5.6 hello的执行流程
_start 0x00000000004010fe
_libc_start_main 0x00007fffff5d70b3
main 0x0000000000401105
printf@plt 0x0000000000401090
sleep@plt 0x4010c0
getchar 0x7fffff63e6e0
_rtld_global_ro 0x7fffff7dd620
5.7 Hello的动态链接分析
地址在dl_init后被改写了
进入改写地址后发现进入了一个新的函数,并且实现了GOT的动态链接。PLT使用GOT中的地址跳转到目标函数。
5.8 本章小结
介绍了链接器的概念和作用,探讨了程序运行时的虚拟地址空间,分析了可执行文件的ELF,研究了重定位以及动态链接的过程。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
进程的经典定义是一个执行中程序的实例,系统的每个程序都运行在某个进程的上下文。上下文是由程序正确运行所需的状态组成的,这个状态包括存放在内存里的程序的代码和数据,它的栈,通用目的寄存器的内容,程序计数器,环境变量以及打开文件描述符的集合。
通过进程,我们会得到一种假象,好像我们的程序是当前唯一运行的程序,我们的程序独占处理器和内存,我们程序的代码和数据好像是系统内存中唯一的对象。
6.2 简述壳Shell-bash的作用与处理流程
shell俗称壳,它是指UNIX系统下的一个命令解析器;主要用于用户和系统的交互。UNIX系统上有很多种Shell。首个shell,即Bourne Shell,于1978年在V7(AT&T的第7版)UNIX上推出。后来,又演变出C shell、bash等不同版本的shell。
Shell接受用户的命令并解释该命令然后将其送入内核执行。
处理流程::打印一个提示符,等待用户输入命令行,从终端读入输入的命令。将输入的命令行切分得出参数,如果是内置命令则立即执行,否则调用相应的程序为其分配子进程并运行,.shell应该接受键盘输入信号,并对这些信号进行相应处理。并最终回收。
6.3 Hello的fork进程创建过程
Shell调用fork函数创建子进程,形成自身的一个拷贝,为运行hello做准备
6.4 Hello的execve过程
Shell 调用execve 函数在当前进程的上下文中加载并运行一个新的程序,即hello程序
6.5 Hello的进程执行
逻辑控制流:即为一系列程序计数器PC的值序列进程是轮流使用处理器的,在同一个处理器核心中,每个进程执行它的流的一部分后被抢占(暂时挂起),然后轮到其他进程。
时间片:进程执行它的控制流的部分时间段。
用户模式和内核模式:用户模式即为未设置模式位,不允许执行特权指令直接引用地址空间中内核区内的代码和数据;内核模式为设置模式位时,该进程执行所有命令访问所有数据。
上下文信息:上下文就是内核重启被抢占的进程所需要的状态,它由通用寄存器等各种内核数据构成。分别三个步骤:
保存当前进程的上下文
恢复某个先前被抢占的进程被保存的上下文
将控制传递给这个新恢复的进程。
6.6 hello的异常与信号处理
正常运行:
乱按
回车
Ctrl+c
Ctrl+z
Ps:
Jobs:
Pstree:
Fg:
Kill
6.7本章小结
讨论了进程的概念和作用,简单介绍了shell以及其重要功能函数,最后进行了异常信号的处理。
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:汇编程序中地址。逻辑地址由选择符和偏移量组成。是经过编译后出现在汇编程序中的地址。
线性地址:逻辑地址经过段机制后转化为线性地址,以描述符:偏移量的组合形式存在。分页机制中线性地址作为输入。
虚拟地址:类似于线性地址
物理地址:真实的物理内存对应地址。 CPU对内存的访问是通过连接着CPU和北桥芯片的前端总线来完成的。在前端总线上传输的内存地址都是物理内存地址
7.2 Intel逻辑地址到线性地址的变换-段式管理
最初8086处理器的寄存器是16位的,为了能够访问更多的地址空间但不改变寄存器和指令的位宽,所以引入段寄存器,8086共设计了20位宽的地址总线,通过将段寄存器左移4位加上偏移地址得到20位地址,这个地址就是逻辑地址。将内存分为不同的段,段有段寄存器对应,段寄存器有一个栈、一个代码、两个数据寄存器。
分段功能在实模式和保护模式下有所不同。
实模式,即不设防,也就是说逻辑地址=线性地址=实际的物理地址。段寄存器存放真实段基址,同时给出32位地址偏移量,则可以访问真实物理内存。
在保护模式下,线性地址还需要经过分页机制才能够得到物理地址,线性地址也需要逻辑地址通过段机制来得到。段寄存器无法放下32位段基址,所以它们被称作选择符,用于引用段描述符表中的表项来获得描述符。描述符表中的一个条目描述一个段,构造如下:
7.3 Hello的线性地址到物理地址的变换-页式管理
线性地址到物理地址之间的转换通过分页机制完成。而分页机制是对虚拟地址内存空间进行分页。
首先Linux系统有自己的虚拟内存系统,Linux将虚拟内存组织成一些段的集合,段之外的虚拟内存不存在因此不需要记录。内核为hello进程维护一个段的任务结构即图中的task_struct,其中条目mm指向一个mm_struct,它描述了虚拟内存的当前状态,pgd指向第一级页表的基地址(结合一个进程一串页表),mmap指向一个vm_area_struct的链表,一个链表条目对应一个段,所以链表相连指出了hello进程虚拟内存中的所有段。
系统将每个段分割为被称为虚拟页(VP)的大小固定的块来作为进行数据传输的单元,在linux下每个虚拟页大小为4KB,类似地,物理内存也被分割为物理页(PP/页帧),虚拟内存系统中MMU负责地址翻译,MMU使用存放在物理内存中的被称为页表的数据结构将虚拟页到物理页的映射,即虚拟地址到物理地址的映射。
7.4 TLB与四级页表支持下的VA到PA的变换
36位的虚拟地址被分割成4个9位的片。CR3寄存器包含L1页表的物理地址。VPN1有一个到L1 PTE的偏移量,找到这个PTE以后又会包含到L2页表的基础地址;VPN2包含一个到L2PTE的偏移量,找到这个PTE以后又会包含到L3页表的基础地址;VPN3包含一个到L3PTE的偏移量,找到这个PTE以后又会包含到L4页表的基础地址;VPN4包含一个到L4PTE的偏移量,找到这个PTE以后就是相应的PPN(物理页号)。
7.5 三级Cache支持下的物理内存访问
cache:高速缓存,从上图就可以看出高速缓存的高效和高昂,三级cache是为了能在达到效率的前提下降低成本。
获得物理地址VA后,使用CI(后六位再后六位)进行组索引,每组8路,对8路的块分别匹配CT(前40位)如果匹配成功且块的valid标志位为1,则命中(hit),根据数据偏移量CO(后六位)取出数据返回。
如果没有匹配成功或者匹配成功但是标志位是1,则不命中(miss),向下一级缓存中查询数据(L2 Cache->L3 Cache->主存)。查询到数据之后,一种简单的放置策略如下:如果映射到的组内有空闲块,则直接放置,否则组内都是有效块,产生冲突(evict),则采用最近最少使用策略LFU进行替换
7.6 hello进程fork时的内存映射
fork的内存映射是使用特殊文件提供匿名内存映射,而这种内存映射,适用于具有亲缘关系的进程之间;由于父子进程特殊的亲缘关系,在父进程中先调用mmap(),然后调用fork()。那么在调用fork()之后,子进程继承父进程匿名映射后的地址空间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区域进行通信了。注意,这里不是一般的继承关系。一般来说,子进程单独维护从父进程 继承下来的一些变量。而mmap()返回的地址,却由父子进程共同维护。
7.7 hello进程execve时的内存映射
exceve函数加载和执行程序hello的步骤为:首先删除已存在的用户区域。然后为hello的代码、数据、bss和栈区域创建新的区域结构,所有这些区域都是私有的、写时复制的。再映射共享区域。比如hello程序与标准C库libc.so链接,这些对象都是动态链接到hello,然后存储在用户虚拟地址空间中的共享区域内。最后设置程序计数器(PC)。
7.8 缺页故障与缺页中断处理
DRAM缓存的不命中被称为缺页。DRAM缓存的不命中触发一个缺页故障,缺页故障调用内核中的缺页异常处理程序,该程序会选择一个牺牲页,如果该牺牲页已经做了更改,那么内核会将它复制回磁盘,否则不会进行复制即写回,然后将牺牲页从DRAM中出去,更新该页的位置放入待取的页面。然后CPU重新执行造成缺页故障的命令此时将可以正常运行。
7.9动态存储分配管理
在程序运行时应使用动态内存分配器给引用程序分配内存,动态内存分配器的维护着一个进程的虚拟内存(堆)。分配器将堆视为一组不同大小的块的集合来进行维护,每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留以供应用程序使用。空闲块可用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。
分配器分为两种基本风格:显式分配器、隐式分配器。
显式分配器:要求应用显式地释放任何已分配的块。
隐式分配器:要求分配器检测一个已分配块何时不再使用,那么就释放这个块。
7.10本章小结
通过对虚拟内存的介绍了解,学习了TLB和四级页表支持下VA到PA的转换,以及得到了PA后,三级cache下的物理内存的访问过程,掌握了各种函数与虚拟内存的关系。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:所有的IO设备都被模型化为文件,而所有的输入和输出都被当做对相应文件的读和写来执行,这种将设备优雅地映射为文件的方式,允许Linux内核引出一个简单低级的应用接口,称为Unix I/O。
8.2 简述Unix IO接口及其函数
接口:
1. 打开文件。一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个I/O设备,内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件,内核记录有关这个打开文件的所有信息。
2. Shell创建的每个进程都有三个打开的文件:标准输入,标准输出,标准错误。
3. 改变当前的文件位置:对于每个打开的文件,内核保持着一个文件位置a,初始为0,这个文件位置是从文件开头起始的字节偏移量,应用程序能够通过执行seek,显式地将改变当前文件位置a。
4. 读写文件:一个读操作就是从文件复制n>0个字节到内存,从当前文件位置a开始,然后将a增加到a+n,给定一个大小为m字节的而文件,当a>=m时,触发EOF。类似一个写操作就是从内存中复制n>0个字节到一个文件,从当前文件位置a开始,然后更新a。
5. 关闭文件,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中去。
函数:
open()函数:这个函数回打开一个已经存在的文件或者创建一个新的文件。
close()函数:这个函数关闭一个已经打开的文件。
read()函数:从描述符为fd的当前文件位置复制最多n个字节到内存位置buf。返回值-1表示一个错误,返回值为0表示EOF。否则返回值表示的是实际传送的字节数量。
write()函数:从内存buf位置复制至多n个字节到描述符fd的当前文件位置。
8.3 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;
}
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程序按照格式fmt结合参数args生成格式化之后的字符串,并返回字串的长度。
在printf中调用系统函数write(buf,i)将长度为i的buf输出
Write:
write:
mov eax, _NR_write
mov ebx, [esp + 4]
mov ecx, [esp + 8]
int INT_VECTOR_SYS_CALL
在write函数中,将栈中参数放入寄存器,ecx是字符个数,ebx存放第一个字符地址,int INT_VECTOR_SYS_CALLA代表通过系统调用syscall
Sys_call:
call save
push dword [p_proc_ready]
sti
push ecx
push ebx
call [sys_call_table + eax * 4]
add esp, 4 * 3
mov [esi + EAXREG - P_STACKBASE], eax
cli
ret
syscall将字符串中的字节“Hello 1190202402 李培意”从寄存器中通过总线复制到显卡的显存中,显存中存储的是字符的ASCII码。
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
学习了linux中的I/O管理机制,以及其各类的函数的功能等。也对printf和getchar函数有了更多更新的研究。
(第8章1分)
结论
最终跟随着hello程序走完了它的一生,也从它的一生中,了解了计算机系统的种种内容。从开始,到结束,也是我们编写的代码的开始,到可能的将来的结束。但当下,一个hello结束了,我们还要有更多的程序,从编写,到交给系统,再到电脑硬件的执行,最后的结束回收,在这个过程中循环往复。赋能人类的生活。
1.预处理器将hello.c源代码经过初步的修改变成了hello.i文件。
2.编译器处理hello.i文件使之成为汇编代码并保存在hello.s文件中。
3.汇编器将hello.s文件处理成了可重定位的目标程序,也就是hello.o文件,这个时候,我们的程序离可以运行就只差一步了。
4.链接器将我们的hello.o与外部文件进行链接,终于我们得到了可以跑起来的hello文件了。
5.当我们在shell中运行hello程序时,内核会为我们分配好运行程序所需要的堆、用户栈、虚拟内存等一系列信息。使我们的hello程序能够正常的运行。
6.从外部对hello程序进行操控只需要在键盘上给一个相应的信号,hello程序就会按照我们的指令来执行。
7.在hello需要访问磁盘中的信息的时候,MMU会将程序中使用的虚拟内存地址通过页表映射成物理地址。
8.当hello执行结束,shell父进程回收子进程,内核删除为这个进程创建的所有数据结构,hello也就结束了它的一生。
(结论0分,缺失 -1分,根据内容酌情加分)
附件
Hello.c C语言源文件
Hello.i 预处理后的文本文件
Hello.o 汇编之后的可重定位目标执行文件
Hello.s 编译后的文本文件
Hello 链接之后的可执行的目标文件
Elf hello.o的ELF
Elf2 hello的ELF
(附件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.
(参考文献0分,缺失 -1分)