马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
盘算机系统
大作业
盘算机科学与技能学院
2022年11月
摘 要
本文基于《深入明白盘算机系统》,通过对一个简单的程序Hello在Linux情况下的生命周期的分析,论述其从hello.c颠末预处置惩罚、编译、汇编、链接等一系列操作生成可执行文件hello,再通过程序对进程的管理、内存空间的分配、信号和非常的处置惩罚、对 I/O 设备的调用等环节彻底解释hello从创建到结束的过程,进而加深对盘算机系统的明白。
关键词:盘算机系统;程序的生命周期;hello的一生;程序编译过程
目 录
第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 本章小结............................................... - 5 -
第3章 编译................................................... - 6 -
3.1 编译的概念与作用............................... - 6 -
3.2 在Ubuntu下编译的命令.................... - 6 -
3.3 Hello的编译结果剖析........................ - 6 -
3.4 本章小结............................................... - 6 -
第4章 汇编................................................... - 7 -
4.1 汇编的概念与作用............................... - 7 -
4.2 在Ubuntu下汇编的命令.................... - 7 -
4.3 可重定位目标elf格式........................ - 7 -
4.4 Hello.o的结果剖析............................. - 7 -
4.5 本章小结............................................... - 7 -
第5章 链接................................................... - 8 -
5.1 链接的概念与作用............................... - 8 -
5.2 在Ubuntu下链接的命令.................... - 8 -
5.3 可执行目标文件hello的格式........... - 8 -
5.4 hello的虚拟地点空间......................... - 8 -
5.5 链接的重定位过程分析....................... - 8 -
5.6 hello的执行流程................................. - 8 -
5.7 Hello的动态链接分析........................ - 8 -
5.8 本章小结............................................... - 9 -
第6章 hello进程管理.......................... - 10 -
6.1 进程的概念与作用............................. - 10 -
6.2 简述壳Shell-bash的作用与处置惩罚流程.. - 10 -
6.3 Hello的fork进程创建过程............ - 10 -
6.4 Hello的execve过程........................ - 10 -
6.5 Hello的进程执行.............................. - 10 -
6.6 hello的非常与信号处置惩罚................... - 10 -
6.7本章小结.............................................. - 10 -
第7章 hello的存储管理...................... - 11 -
7.1 hello的存储器地点空间................... - 11 -
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 -
7.9动态存储分配管理.............................. - 11 -
7.10本章小结............................................ - 12 -
第8章 hello的IO管理....................... - 13 -
8.1 Linux的IO设备管理方法................. - 13 -
8.2 简述Unix IO接口及其函数.............. - 13 -
8.3 printf的实现分析.............................. - 13 -
8.4 getchar的实现分析.......................... - 13 -
8.5本章小结.............................................. - 13 -
结论............................................................... - 14 -
附件............................................................... - 15 -
参考文献....................................................... - 16 -
第1章 概述
1.1 Hello简介
1.1.1 P2P: From Program to Process
P2P,即从程序到进程。用户利用高级语言C语言编写hello.c源程序,颠末cpp预处置惩罚形成hello.i,再颠末ccl编译形成汇编语言程序hello.s,然后颠末as转换为机器语言指令,形成可重定位目标程序hello.o,末了通过ld与库函数链接并符号剖析与重定位,形成可执行目标文件hello。而后可执行文件hello通过shell加载,fork产生子进程,颠末以上步骤hello程序(program)变成了hello进程(process)。
1.1.2 020: From Zero-0 to Zero -0
020,即从运行到结束。初始时内存中没有hello文件相干的内容,通过fork产生hello子进程后,通过execve进行加载,先删除当前虚拟地点已存在的数据结构,为hello的代码、数据、bss等创建地域,然后映射共享地域,设置程序计数器,进入main函数,CPU分配时间片执行逻辑控制流。执行过程中,虚拟内存为进程提供独立的空间;存储结构层层递进,让数据从磁盘传输到CPU中;TLB、分级页表等也为数据的高效访问提供保障;I/O设备通过形貌符与接口实现了hello的输入输出。多方面互助共同之下,hello完成执行。然后,shell回收hello进程,删除hello的全部痕迹,释放运行中占用的内存空间。至此,hello从运行到结束,完成020过程。
1.2 情况与工具
1.2.1 硬件情况
X64 CPU;2GHz;2G RAM;256GHD Disk
1.2.2 软件情况
Windows 10 64位;Vmware-workstation-16;Ubuntu 20.04 LTS 64位
1.2.3 开发与调试工具
Codeblocks 64位;vi/vim/gedit+gcc
1.3 中间结果
表1.3-1 中间结果
文件名
| 功能
| hello.c
| 源代码
| hello.i
| 预处置惩罚后的文本文件
| hello.s
| 编译后的汇编文件
| hello.o
| 汇编后的可重定位目标文件
| hello
| 链接后的可执行文件
| hello.elf
| 用readelf读取hello.o的ELF格式信息
| hello.asm
| 反汇编hello.o的反汇编文件
| hello2.elf
| 由hello可执行文件生成的.elf文件
| hello2.asm
| 反汇编hello可执行文件得到的反汇编文件
| 1.4 本章小结
本章重要先容了hello的P2P和020过程,从总体上扼要阐述了hello的一生,给出了论文研究时的情况与工具以及中间生成的文件信息。
第2章 预处置惩罚
2.1 预处置惩罚的概念与作用
2.1.1 预处置惩罚的概念
预处置惩罚步骤是指程序开始运行时,预处置惩罚器(cpp)根据以字符#开头的命令,修改原始的C程序的过程。
2.1.2 预处置惩罚的作用
- 宏睁开:将全部的宏定义进行替换,然后删除#define
- 条件编译:如果源代码中包含条件预处置惩罚指令(如#if),就会先判断条件,再修改源代码
- 头文件睁开:对文件包含命令#include,引入对应头文件,将头文件的内容(.h)插入到命令所在位置,从而把头文件和当前源文件毗连成一个源文。
2.2在Ubuntu下预处置惩罚的命令
预处置惩罚命令:gcc -E hello.c -o hello.i
图2.2-1 预处置惩罚命令截图
2.3 Hello的预处置惩罚结果剖析
预处置惩罚后的源文件从23行扩展到了3060行,main函数在第3047行,预处置惩罚器将头文件中的内容引入hello.i,将必要用到的库函数等参加到了文本中,让程序能够继承被编译器编译。
- 包含文件信息
图2.3-1 hello.i中部分文件信息
图2.3-2 hello.i中部分范例定义信息
图2.3-3 hello.i中部分函数声明信息
图2.3-4 hello.i中源码部分
2.4 本章小结
在本章中,我们对hello.c进行了预处置惩罚,生成hello.i,预处置惩罚器会进行宏睁开、头文件睁开、条件编译等处置惩罚,并删除注释,对函数源码并不做过多修改,hello.i文件可用于下一步的处置惩罚。
第3章 编译
3.1 编译的概念与作用
3.1.1 编译的概念
编译是指预处置惩罚后,编译器(ccl)将预处置惩罚文件hello.i翻译成汇编语言文件hello.s。
3.1.2 编译的作用
- 语法查抄:查抄代码是否存在语法错误,如果有错误的话就会报错。
- 生成汇编代码:将程序翻译成汇编语言,从而在下一阶段可以让汇编器翻译成机器语言指令。
- 代码优化:编译器会对程序进行优化,生成效率更高的目标代码。
3.2 在Ubuntu下编译的命令
编译命令:gcc -S hello.i -o hello.s
-
图3.2-1 编译命令
3.3 Hello的编译结果剖析
3.3.1 汇编代码展示
- .file "hello.c"
- .text
- .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 \347\247\222\346\225\260\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 $4, -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
- movq -32(%rbp), %rax
- addq $24, %rax
- movq (%rax), %rax
- movq %rax, %rdi
- call atoi@PLT
- movl %eax, %edi
- call sleep@PLT
- addl $1, -4(%rbp)
- .L3:
- cmpl $8, -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 9.4.0-1ubuntu1~20.04.1) 9.4.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:
| 3.3.2 汇编文本结构分析
表3.2.2-1 hello.s文件结构
内容
| 寄义
| .file
| 源文件
| .text
| 代码段
| .global
| 全局变量
| .data
| 存放已经初始化的全局和静态C 变量
| .section .rodata
| 存放只读变量
| .align
| 对齐方式
| .type
| 表示是函数范例/对象范例
| .size
| 表示大小
| .long .string
| 表示是long范例/string范例
| 3.3.3 常量
编译时对常量进行编码,并将其存储在只读代码区的 .rodata节,在程序运行时会直接通过寻址找到常量。
例如将hello.c中“Usage: Hello 学号 姓名 秒数!”编译为汇编代码第6行:
- .string "\347\224\250\346\263\225: Hello \345\255\246\345\217\267 \345\247\223\345\220\215 \347\247\222\346\225\260\357\274\201"
3.3.4 变量
不同范例的变量在不同位置定义,初始化的全局变量和静态变量定义在只读代码区的.bss节,已初始化的全局和静态变量定义在只读代码区的.data节,局部变量在堆上进行定义和释放。
例如,局部变量int i被保存在栈上,通过机器指令对其赋值
3.3.5 赋值操作
对局部变量进行赋值操作,利用MOV指令,根据不同的数据大小选择不同指令movb、movw、movl、movq等。具体见3.3.4例。
3.3.5 算术运算
表3.3.5-1 算数指令
指令
| 效果
| leaq s,d
| d=&s
| inc d
| d+=1
| dec d
| d-=1
| neg d
| d=-d
| add s,d
| d=d+s
| sub s,d
| d=d-s
| imulq s
| r[%rdx]:r[%rax]=s*r[%rax]
| mulq s
| r[%rdx]:r[%rax]=s*r[%rax]
| idivq s
| r[%rdx]=r[%rdx]:r[%rax] mod s
r[%rax]=r[%rdx]:r[%rax] div s
| divq s
| r[%rdx]=r[%rdx]:r[%rax] mod s
r[%rax]=r[%rdx]:r[%rax] div s
| 在hello.s中,例如,实现i++的操作:
开辟栈以及回收栈:
3.3.6 比较和跳转操作
通过COM指令进行比较,盘算两个值相减大小,根据结果设置条件码,根据条件码来判断跳转值,也可通过跳转指令J判断有无符号。
例如,查抄argc是否不便是4。在hello.s中,利用cmpl $4,-20(%rbp),比较 argc与4的大小并设置条件码,为下一步je利用条件码进行跳转作准备。
- cmpl $4, -20(%rbp)
- je .L2
表3.3.6-1 跳转指令
指令
| 条件
| jmp
| 直接跳转
| je
| 相等
| Jne
| 不便是
| Js
| 小于
| Jns
| 小于便是
| Jg
| 大于
| Jge
| 大于便是
| Ja
| 大于(无符号)
| Jae
| 大于便是(无符号)
| jbe
| 小于便是(无符号)
|
3.3.7 数组/指针操作
对数组的索引相当于在第一个元素地点的底子上通过加索引值乘以数据大小来实现。
例如,在hello.c中,存在char *argv[],根据图3-3可知,根据argv首地点得到argv[1]和argv[2]必要通过加减操作:
- movq -32(%rbp), %rax
- addq $16, %rax
- movq (%rax), %rdx
- movq -32(%rbp), %rax
- addq $8, %rax
- movq (%rax), %rax
- movq %rax, %rsi
3.3.8 函数操作
hello.c中包罗main函数,printf函数,sleep函数,getchar函数,exit函数。
首先,内核shell获取命令行参数和情况变量地点,执行main函数,在main中必要调用其它函数,在main中为被调用函数分配栈空间。调用函数必要借助栈,先将返回地点压入栈中,并将PC设为被调用函数的起始地点,然后调用。返回时,先从栈中弹出返回地点,再PC设置为该返回地点。return正常返回后,leave恢复栈空间。
在hello.s中调用函数有:
call puts@PLT
call exit@PLT
call printf@PLT
call sleep@PLT
call getchar@PLT
3.4 本章小结
本章重要探讨编译器将颠末预处置惩罚阶段后的C程序hello.i翻译成汇编语言程序的处置惩罚过程,包罗对数据、算术操作、关系操作、控制转移、数组操作、函数操作的处置惩罚。编译器也会在处置惩罚过程中对程序进行一些优化,最终的结果被保存在hello.s文件中,能够在下一阶段让汇编器翻译机器语言指令。
第4章 汇编
4.1 汇编的概念与作用
4.1.1 汇编的概念
汇编是指汇编器(as)将hello.s翻译成机器语言指令的过程,把这些指令打包成可重定位目标程序的格式,并将结果保存在目标文件hello.o中。
4.1.2 汇编的作用
将汇编指令转换成机器可以直接读取分析的机器指令,生成hello.o文件,用于后续的链接。
4.2 在Ubuntu下汇编的命令
汇编命令:gcc -m64 -no-pie -fno-PIC -c hello.s -o hello.o
-
图4.2-1 汇编命令
4.3 可重定位目标elf格式
在shell中输入readelf -a hello.o > hello.elf指令得到hello.o文件的 ELF 格式:
-
-
图4.3-1 生成elf格式文件
4.3.1 ELF头
以 16字节序列 Magic 开始,其形貌了生成该文件的系统的字的大小和字节顺序,ELF 头剩下的部分包含帮助链接器语法分析和解释目标文件的信息,其中包罗 ELF 头大小、目标文件范例、机器范例、节头部表的文件偏移,以及节头部表中条目的大小和数目等相干信息。
-
-
图4.3.1-1 ELF头
4.3.2 节头
节头记录了各节名称及大小、范例及全体大小、地点及旗标、毗连、信息和偏移量及对齐信息。
-
图4.3.2-1节头
4.3.3 重定位节
当链接器把这个目标文件和其他文件组合时,必要修改表中的这些位置。一样平常,调用外部函数或者引用全局变量的指令都必要修改。
其中包罗R_X86_64_PC32( PC相对地点的引用)和R_X86_64_32(绝对地点的引用)。
图4.3.3-1重定位头
4.3.4 符号表
符号表中保存着定位、重定位程序中符号定义和引用的信息,全部重定位必要引用的符号都在其中声明。
-
图4.3.4-1符号表
4.4 Hello.o的结果剖析
利用objdump -d -r hello.o > hello.asm分析hello.o的反汇编,并与第3章的 hello.s文件进行对照分析。
-
-
图4.4-1生成反汇编文件
4.4.1 反汇编代码
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 04 cmpl $0x4,-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 48 jmp 80 <main+0x80>
- 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+0x22
- 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: 48 8b 45 e0 mov -0x20(%rbp),%rax
- 66: 48 83 c0 18 add $0x18,%rax
- 6a: 48 8b 00 mov (%rax),%rax
- 6d: 48 89 c7 mov %rax,%rdi
- 70: e8 00 00 00 00 callq 75 <main+0x75>
- 71: R_X86_64_PLT32 atoi-0x4
- 75: 89 c7 mov %eax,%edi
- 77: e8 00 00 00 00 callq 7c <main+0x7c>
- 78: R_X86_64_PLT32 sleep-0x4
- 7c: 83 45 fc 01 addl $0x1,-0x4(%rbp)
- 80: 83 7d fc 08 cmpl $0x8,-0x4(%rbp)
- 84: 7e b2 jle 38 <main+0x38>
- 86: e8 00 00 00 00 callq 8b <main+0x8b>
- 87: R_X86_64_PLT32 getchar-0x4
- 8b: b8 00 00 00 00 mov $0x0,%eax
- 90: c9 leaveq
- 91: c3 retq
4.4.2 与汇编代码比较
将hello.asm和hello.s进行比较,大部分相同,重要有一下几个方面不同:
hello.s中包含.type .size .align以及.rodata只读数据段等信息,而hello.asm中只有函数的相干内容。
2.分支转移:
在hello.s中,跳转指令的目标地点直接记为段名称,如.L1,.L2等。而在反汇编得到的hello.asm中,跳转的目标为具体的地点。
3.函数调用:
在hello.s文件中,call之后直接跟着函数名称,而在反汇编得到的hello.asm中,call 的目标地点是当前指令的下一条指令。
4. 全局变量访问:
在hello.s 文件中,利用段名称+%rip访问 rodata(printf 中的字符串),而在反汇编得到的hello.asm中,利用 0+%rip进行访问。
4.4.3 机器语言的构成
机器语言是机器能直接识别的程序语言或指令代码,无需颠末翻译,每一操作码在盘算机内部都有相应的电路来完成它,或指不经翻译即可为机器直接明白和担当的程序语言或指令代码。机器语言利用绝对地点和绝对操作码。不同的盘算机都有各自的机器语言,即指令系统。从利用的角度看,机器语言是最低级的语言。
4.4.4 机器语言与汇编语言的映射关系
汇编语言和机器语言一样平常是一一对应的,汇编语言是机器语言的符号表示方式。而不同范例的CPU 有不同的机器指令系统,也就有不同的汇编语言,所以,汇编语言程序与机器有着密切的关系。所以,除了同系列、不同型号CPU 之间的汇编语言程序有一定程度的可移植性之外,其它不同范例(如:小型机和微机等)CPU 之间的汇编语言程序是无法移植的,也就是说,汇编语言程序的通用性和可移植性要比高级语言程序低。
4.5 本章小结
本章讨论了汇编阶段将汇编代码hello.s翻译成机器语言指令hello,o的过程,等候下一步链接器的处置惩罚。同时,比较了汇编代码与反汇编代码之间的不同之处。
第5章 链接
5.1 链接的概念与作用
5.1.1 链接的概念
链接是指链接器(ld)将各种代码和数据片断收集并组合成一个单一可执行目标文件的过程。
5.1.2 链接的作用
使得分离编译成为可能,我们可以独立的修改和编译模块,当我们改变这些模块的其中一个时,只需简单的重新编译它,并重新链接应用,而不必重新编译其他文件。
5.2 在Ubuntu下链接的命令
链接命令:ld -dynamic-linker /lib64/ld-linux-x86-64.so.2 /usr/lib/x86_64-linux-gnu/crt1.o hello.o /usr/lib/x86_64-linux-gnu/crti.o /usr/lib/x86_64-linux-gnu/libc.so /usr/lib/x86_64-linux-gnu/crtn.o -o hello
-
图5.2-1 链接命令
5.3 可执行目标文件hello的格式
在Shell中输入命令 readelf -a hello > hello2.elf 生成 hello 程序的 ELF 格式文件,保存为hello2.elf:
-
图5.3-1 生成hello.o的elf格式
5.3.1 ELF头
以 16字节序列 Magic 开始,其形貌了生成该文件的系统的字的大小和字节顺序,ELF 头剩下的部分包含帮助链接器语法分析和解释目标文件的信息,其中包罗 ELF 头大小、目标文件范例、机器范例、节头部表的文件偏移,以及节头部表中条目的大小和数目等相干信息。
-
图5.3.1-1 ELF头
5.3.2 节头
节头记录了各节名称及大小、范例及全体大小、地点及旗标、毗连、信息和偏移量及对齐信息。
-
图5.3.2-1节头
5.3.3 程序头
一个结构数组,形貌了系统准备程序执行所需的段或其他信息。
-
图5.3.3-1程序头
5.3.4 Dynamic section(动态section)
图5.3.4-1 Dynamic section
5.3.5 符号表
符号表中保存着定位、重定位程序中符号定义和引用的信息,全部重定位必要引用的符号都在其中声明。
-
图5.3.5-1符号表
5.4 hello的虚拟地点空间
利用edb加载hello,检察本进程的虚拟地点空间各段信息,并与5.3对照分析分析。
根据盘算机系统的特性,程序被载入至地点0x400000~0x401000中。在该地点范围内,每个节的地点都与前一节中节对应的 Address 相同。通过ELF可知,程序从0x00400000到0x00400fff,在0x400fff之后存放的是.dynamic到.shstrtab节的内容。
-
图5.4-1用edb检察虚拟地点空间
5.5 链接的重定位过程分析
在Shell中利用命令objdump -d -r hello > hello2.asm生成反汇编文件hello2.asm,与第四章中生成的hello.o.asm文件进行比较:
-
图5.5-1生成反汇编文件
5.5.1 重定位概念
链接器在完成符号剖析以后,就把代码中的每个符号引用和一个符号定义关联起来。此时,链接器就知道它的输入目标模块中的代码节和数据节的确切大小。
然后就可以开始重定位步骤了,在这个步骤中,将合并输入模块,并为每个符号分配运行时的地点。在 hello 到 hello.o 中,首先是重定位节和符号定义,链接器将全部输入到 hello 中相同范例的节合并为同一范例的新的聚合节。当这一步完成时,程序中的每条指令和全局变量都有唯一的运行时内存地点了。然后是重定位节中的符号引用,链接器会修改 hello 中的代码节和数据节中对每一个符号的引用,使得他们指向精确的运行地点。
5.5.2 hello与hello.o的不同
1.链接后函数数目增加,hello中增加了外部函数。
2.函数调用指令call的参数发生厘革,hello中调用为call+函数名,hello.o中为call+相对偏移地点。
3.跳转指令参数发生厘革。
4.hello.o中是相对偏移地点,hello为虚拟内存地点。
5.6 hello的执行流程
hello在执行的过程中要执行载入、执行和退出三个过程,列出其调用与跳转的各个子程序名或程序地点。
表5.6-1 程序名称与程序地点
程序名称
| 程序地点
| <_start>
| 4010f0
| <__libc_csu_init>
| 4011c0
| <_init>
| 401000
| <main>
| 401125
| <.plt>
| 401020
| <puts@plt>
| 401090
| <printf@plt>
| 4010a0
| <atoi@plt>
| 4010c0
| <exit@plt>
| 4010d0
| <sleep@plt>
| 4010e0
| <getchar@plt>
| 4010b0
| <__libc_csu_fini>
| 401230
| <_fini>
| 401238
|
5.7 Hello的动态链接分析
延迟绑定是通过GOT和PLT实现的。GOT是数据段的一部分,而PLT是代码段的一部分。两表内容分别为:
PLT:PLT是一个数组,其中每个条目是16字节代码。PLT[0]是一个特别条目,它跳转到动态链接器中。每个被可执行程序调用的库函数都有它本身的PLT条目。每个条目都负责调用一个具体的函数。
GOT:GOT是一个数组,其中每个条目是8字节地点。和PLT联合利用时,GOT[O]和GOT[1]包含动态链接器在剖析函数地点时会利用的信息。GOT[2]是动态链接器在1d-linux.so模块中的入口点。别的的每个条目对应于一个被调用的函数,其地点必要在运行时被剖析。每个条目都有一个相匹配的PLT条目。
-
图5.7-1调用 dl_init 之前的全局偏移表
图5.7-2调用 dl_init 之后的全局偏移表
由上图对比可知,调用dl_init函数后,发生动态链接,GOT条目改变。
5.8 本章小结
本章先容了链接的概念和功能,分析可执行文件hello的ELF格式及其虚拟地点空间,并对重定位、动态链接进行深入的分析。
第6章 hello进程管理
6.1 进程的概念与作用
6.1.1 进程的概念
进程的经典定义就是一个执行中的程序的实例。进程是盘算机中的程序关于某数据聚集上的一次运行运动,是系统进行资源分配和调度的基本单元,是操作系统结构的底子。在早期面向进程计划的盘算机结构中,进程是程序的基本执行实体;在今世面向线程计划的盘算机结构中,进程是线程的容器。程序是指令、数据及其构造情势的形貌,进程是程序的实体。
6.1.2 进程的作用
每次用户通过向 shell 输入一个可执行目标文件的名字,运行程序时, shell 就会创建一个新的进程,然后在这个新进程的上下文中运行这个可执行目标文件。应用程序也能够创建新进程,并且在这个新进程的上下文中运行它们本身的代码或其他应用程序。
6.2 简述壳Shell-bash的作用与处置惩罚流程
6.2.1 Shell作用
读取输入并剖析命令行;替换特别字符,比如通配符和汗青命令符;设置管道、重定向和配景处置惩罚;处置惩罚信号;程式执行的相干设置。
1. 可交互,和非交互的利用shell。在交互式模式,shell从键盘吸收输入;在非交互式模式,shell从文件中获取输入。
2. shell中可以同步和异步的执行命令。在同步模式,shell要等命令执行完,才气吸收下面的输入。在异步模式,命令运行的同时,shell就可吸收其它的输入。重定向功能,可以更细致的控制命令的输入输出。另外,shell允许设置命令的运行情况。
3. shell提供了少量的内置命令,以便自身功能更加完备和高效。
4. shell除了执行命令,还提供了变量,流程控制,引用和函数等,雷同高级语言一样,能编写功能丰富的程序。
5. shell强盛的的交互性除了可编程,还体现在作业控制,命令行编辑,汗青命令,和别名等方面。
6.2.2 Shell 处置惩罚流程
命令行是一串 ASCII 字符由空格分隔。字符串的第一个单词是一个可执行程序,或者是 shell 的内置命令。命令行的别的部分是命令的参数。
如果第一个单词是内置命令,shell 会立即在当前进程中执行。否则,shell 会新建一个子进程,然后再子进程中执行程序。新建的子进程又叫做作业。通常,作业可以由 Unix 管道毗连的多个子进程构成。
如果命令行以&符号末端,那么作业将在配景运行,这意味着在打印提示符并等候下一个命令之前,shell 不会等候作业终止。否则,作业在前台运行,这意味着 shell 在作业终止前不会执行下一条命令行。 因此,在任何时候,最多可以在一个作业中运行在前台。 但是,恣意数目的作业可以在配景运行。例如,键入命令行:sh> jobs,会让 shell 运行内置命令 jobs。键入命令行 sh> /bin/ls -l -d 会导致 shell 在前台运行 ls 程序。根据约定,shell会执行程序的 main 函数 int main(int argc, char *argv[]),argc 和 argv 会吸收到下面的值:
argc == 3,
argv[0] == ‘‘/bin/ls’’,
argv[1]== ‘‘-l’’,
argv[2]== ‘‘-d’’.
下面以&末端的命令行会在配景执行 ls 程序:
sh> /bin/ls -l -d &
Unix shell 支持作业控制的概念,允许用户在前台和配景之间来回移动作业,并更改进程的状态(运行,克制或终止)。在作业运行时,键入 ctrl-c会将 SIGINT 信号传递到前台作业中的每个进程。SIGINT 的默认动作是终止进程。雷同地,键入 ctrl-z 会导致 SIGTSTP 信号传递给全部前台进程。SIGTSTP 的默认操作是克制进程,直到它被 SIGCONT 信号唤醒为止。Unixshell 还提供支持作业控制的各种内置命令。例如:
jobs:列出运行和克制的配景作业。
bg <job>:将克制的配景作业更改为正在运行的配景作业。
fg <job>:将克制或运行的配景作业更改为在前台运行。
kill <job>:终止作业。
6.3 Hello的fork进程创建过程
父进程通过调用fork函数创建一个新的、处于运行状态的子进程。
函数原型:int fork(void);
调用fork函数后,子进程返回0,父进程返回子进程的PID;新创建的子进程几乎但不完全与父进程相同:子进程得到与父进程虚拟地点空间相同的(但是独立的)一份副本;子进程得到与父进程任何打开文件形貌符相同的副本;但是子进程有不同于父进程的PID。fork函数:被调用一次,却返回两次!
Fork具体处置惩罚hello文件过程:
首先,带参执行当前目录下的可执行文件hello,父进程会通过fork函数创建一个新的运行的子进程hello。子进程获取了与父进程的上下文,包罗栈、通用寄存器、程序计数器,情况变量和打开的文件相同的一份副本。子进程与父进程的最大区别是有着跟父进程不一样的PID,子进程可以读取父进程打开的任何文件。当子进程运行结束时,父进程如果仍然存在,则执行对子进程的回收,否则就由init进程回收子进程。
-
图6.3-1程序执行过程
6.4 Hello的execve过程
调用函数fork创建新的子进程之后,子进程会调用execve函数,在当前进程的上下文中加载并运行一个新程序hello。execve 函数从不返回,它将删除该进程的代码和地点空间内的内容并将其初始化,然后通过跳转到程序的第一条指令或入口点来运行该程序。将私有的地域映射进来,例如打开的文件,代码、数据段,然后将公共的地域映射进来。后面加载器跳转到程序的入口点,即设置PC指向_start 地点。_start函数最终调用hello中的 main 函数,如许,便完成了在子进程中的加载。
6.5 Hello的进程执行
6.5.1 进程时间片
一个进程执行它的控制流的一部分的每一时间段叫做时间片。
6.5.2 上下文信息
上下文就是内核重新启动一个被抢占的进程所必要的状态,它由通用寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构等对象的值构成。
6.5.3 用户模式和内核模式
处置惩罚器通常利用一个寄存器提供两种模式的区分,该寄存器形貌了进程当前享有的特权,当没有设置模式位时,进程就处于用户模式中,用户模式的进程不允许执行特权指令,也不允许直接引用地点空间中内核区内的代码和数据;设置模式位时,进程处于内核模式,该进程可以执行指令会合的任何命令,并且可以访问系统中的任何内存位置。
6.5.4 执行过程
进程调度指在执行过程中,内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程,这个过程称为调度。内存收到停止信号之后,将当前进程参加等候序列,进行上下文切换将当前的进程控制权交给其他进程,当再次收到停止信号时将hello从等候队列参加运行队列。
6.6 hello的非常与信号处置惩罚
非常可以分为四类:停止(interrupt),陷阱(trap),故障(fault)和终止(abort)。
停止是异步发生的,是来自处置惩罚器外部的I/O设备的信号的结果。在当前指令完成执行后,处置惩罚器留意到停止引脚的电压变高,就从系统总线读取非常号,然后调用适当的停止处置惩罚程序。当处置惩罚程序返回后,它就将控制返回给下一条指令。
陷阱是有意的非常,是执行一条指令的结果。应用程序执行一次系统调用,然后把控制传递给处置惩罚程序,陷阱处置惩罚程序运行后,返回到syscall之后的指令。
故障由错误情况引起,故障发生时处置惩罚器将控制转移给故障处置惩罚程序。如果处置惩罚程序能够修正这个错误情况,它就将控制返回到引起故障的指令,从而重新执行它。否则,处置惩罚程序返回到内核中的abort例程,abort例程会终止引起故障的应用程序。
终止是不可恢复的致命错误造成的结果。终止处置惩罚程序从不将控制返回给应用程序,处置惩罚程序将控制返回给一个abort例程,该例程会终止这个应用程序。
以下给出程序运行过程中按键盘,如不停乱按,包罗回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令的运行结果:
图6.6-1 乱按键盘
-
图6.6-2 执行Ctrl-c
-
图6.6-3 执行Ctrl-z以及ps、jobs
图6.6-4 执行pstree
-
图6.6-5 执行kill
图6.6-6 执行fg返回前台
6.7本章小结
本章重要讨论了进程和shell的概念与作用,进程的创建和执行过程,以及对非常和信号的处置惩罚。
第7章 hello的存储管理
7.1 hello的存储器地点空间
(以下格式自行编排,编辑时删除)
结合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动态存储分配管理
(以下格式自行编排,编辑时删除)
Printf会调用malloc,请简述动态内存管理的基本方法与策略。
7.10本章小结
(以下格式自行编排,编辑时删除)
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
(以下格式自行编排,编辑时删除)
设备的模子化:文件
设备管理:unix io接口
8.2 简述Unix IO接口及其函数
(以下格式自行编排,编辑时删除)
8.3 printf的实现分析
(以下格式自行编排,编辑时删除)
[转]printf 函数实现的深入分析 - Pianistx - 博客园
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
(以下格式自行编排,编辑时删除)
异步非常-键盘停止的处置惩罚:键盘停止处置惩罚子程序。担当按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到担当到回车键才返回。
8.5本章小结
(以下格式自行编排,编辑时删除)
(第8章1分)
结论
hello的一生,从预处置惩罚、编译、汇编到链接,生成可执行文件,再到内存加载运行,又从内存回收,结束生命。它经历的过程包罗:
程序员通过高级语言编写hello.c程序,存储在内存中;
将hello.c中include的全部外部的头文件头文件内容直接插入程序文本中,完成字符串的替换,方便后续处置惩罚;
通过编译过程,编译器将hello.i 翻译成汇编语言文件 hello.s;
将hello.s汇编程序翻译成机器语言指令,并把这些指令打包成可重定位目标程序格式,最终结果保存在hello.o 目标文件中;
通过链接器,将hello的程序编码与动态链接库等收集整理成为一个单一文件,生成可执行的目标文件hello;
打开Shell,在其中键入 ./hello 2021112888 lbz 2,终端为其fork新建进程,并通过execve把代码和数据加载入虚拟内存空间,程序开始执行;
在该进程被调度时,CPU为hello其分配时间片,在一个时间片中,hello享有CPU全部资源,PC寄存器一步一步地更新,CPU不停地取指,顺序执行本身的控制逻辑流;
内存管理单元MMU将逻辑地点,一步步映射成物理地点,进而通过三级高速缓存系统访问物理内存/磁盘中的数据;
printf 会调用malloc 向动态内存分配器申请堆中的内存;
进程时候等候着信号,如果运行途中键入ctr-c ctr-z 则调用shell 的信号处置惩罚函数分别进行克制、挂起等操作,对于其他信号也有相应的操作;
Shell父进程等候并回收hello子进程,内核删除为hello进程创建的全部数据结构。
感悟:
在这次大作业中,通过一个小小的程序HELLO,让我形象地看到了一个程序从诞生,到运行,到完成使命脱离的全过程,更好地明白了盘算机系统原理,并对盘算机底层产生了深厚的兴趣,想要更进一步地学习。
附件
文件名
| 功能
| hello.c
| 源代码
| hello.i
| 预处置惩罚后的文本文件
| hello.s
| 编译后的汇编文件
| hello.o
| 汇编后的可重定位目标文件
| hello
| 链接后的可执行文件
| hello.elf
| 用readelf读取hello.o的ELF格式信息
| hello.asm
| 反汇编hello.o的反汇编文件
| hello2.elf
| 由hello可执行文件生成的.elf文件
| hello2.asm
| 反汇编hello可执行文件得到的反汇编文件
|
参考文献
[1] Randal E.Bryant,David R.O’Hallaron. 深入明白盘算机系统
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |