hello的一生--计算机系统

计算机系统

大作业

计算机科学与技术学院

2019 年 12 月

摘 要

为了学习计算机系统并对计算机有更好的了解,本文通过一个简单的小程序

hello.c 从产生到死亡的一生,来介绍 Linux 系统下的程序从代码到运行再到最后终止过程的底层实现进行了分析,描述了与之相关的计算机组成与操作系统的相关内容。过 gcc、objdump、gdb、edb 等工具对一段程序代码预处理、编译、汇编、链接与反汇编的过程进行分析与比较,并且通过 shell 及其他 Linux 内置程序对进程运行过程进行了分析。

研究内容对理解底层程序与操作系统的实现原理具有一定指导作用。

**关键词:**链接,进程管理,计算机组成原理…

目 录

第 1 章 概述…- 4 -

1.1 HELLO 简介… - 4 -

1.2 环境与工具… - 4 -

1.3 中间结果… - 4 -

1.4 本章小结… - 5 -

第 2 章 预处理…- 6 -

2.1 预处理的概念与作用… - 6 -

2.2 在 UBUNTU 下预处理的命令… - 6 -

2.3 HELLO 的预处理结果解析… - 7 -

2.4 本章小结… - 9 -

第 3 章 编译…- 10 -

3.1 编译的概念与作用… - 10 -

3.2 在 UBUNTU 下编译的命令… - 10 -

3.3 HELLO 的编译结果解析… - 10 -

3.4 本章小结… - 18 -

第 4 章 汇编…- 19 -

4.1 汇编的概念与作用… - 19 -

4.2 在 UBUNTU 下汇编的命令… - 19 -

4.3 可重定位目标 ELF 格式… - 19 -

4.4 HELLO.O 的结果解析 … - 25 -

4.5 本章小结… - 26 -

第 5 章 链接…- 27 -

5.1 链接的概念与作用… - 27 -

5.2 在 UBUNTU 下链接的命令… - 27 -

5.3 可执行目标文件 HELLO 的格式 … - 28 -

5.4 HELLO 的虚拟地址空间… - 30 -

5.5 链接的重定位过程分析… - 31 -

5.6 HELLO 的执行流程 … - 32 -

5.7 HELLO 的动态链接分析… - 33 -

5.8 本章小结… - 36 -

第 6 章 HELLO 进程管理…- 37 -

6.1 进程的概念与作用… - 37 -

6.2 简述壳 SHELL-BASH 的作用与处理流程… - 37 -

6.3 HELLO 的 FORK 进程创建过程 … - 38 -

6.4 HELLO 的 EXECVE 过程 … - 38 -

6.5 HELLO 的进程执行… - 39 -

6.6 HELLO 的异常与信号处理… - 42 -

6.7 本章小结… - 45 -

第 7 章 HELLO 的存储管理…- 46 -

7.1 HELLO 的存储器地址空间… - 46 -

7.2 INTEL 逻辑地址到线性地址的变换-段式管理… - 47 -

7.3 HELLO 的线性地址到物理地址的变换-页式管理 … - 49 -

7.4 TLB 与四级页表支持下的 VA 到 PA 的变换(虚拟->物理) … - 51 -

7.5 三级 CACHE 支持下的物理内存访问 … - 53 -

7.6 HELLO 进程 FORK 时的内存映射… - 54 -

7.7 HELLO 进程 EXECVE 时的内存映射… - 55 -

7.8 缺页故障与缺页中断处理… - 56 -

7.9 动态存储分配管理… - 57 -

7.10 本章小结… - 60 -

第 8 章 HELLO 的 IO 管理…- 61 -

8.1 LINUX 的 IO 设备管理方法… - 61 -

8.2 简述 UNIX IO 接口及其函数… - 61 -

8.3 PRINTF 的实现分析… - 62 -

8.4 GETCHAR 的实现分析… - 64 -

8.5 本章小结… - 65 -

结论…- 65 -

附件**…- 67 -**

参考文献**…- 68 -**

第 1 章 概 述

1.1 Hello 简介

Hello.c 文件横空出世,然后就要在计算机里来度过自己的一生

首先,已知一个 hello.c 文件,由预处理器 cpp 寻找以#开头的命令,然后修改 这个程序变成hello.i 文件;编译器cll 将预处理文件变成可读的汇编语言程序hello.s。然后汇编器 cs 将 hello.s 变成机器码,将其打包成可重定位的目标文件 hello.o。这个文件直接打开是打不开的,不可读。可以使用 objdump 来反汇编查看。然后使用链接器 ld 合并,获得执行目标文件 hello。然后在 Linux 系统中内置命令行解释器

shell 加载运行 hello 程序,经过 fork,hello 完成了 p2p 过程。

Shell 使用 execve 来加载运行 hello,为其分配虚拟内存空间,构建虚拟内存映射,MMU 组织各级页表与 cache 为其开路,给予 hello 想要的所有信息,CPU 给予时间片并控制逻辑流,hello 终于能够大放异彩。最后,shell 回收 hello 的进程, 内核清除与 hello 相关的所有存在,hello 完成 O2O 的过程。

1.2 环境与工具

1.2.1 硬件环境:

Intel Core i7-7700HQ x64CPU,8G RAM,128G SSD +1T HDD.

1.2.2 软件环境:

Ubuntu16.04 LTS

1.2.3 开发与调试工具:

vim,gcc,as,ld,edb,readelf,objdump

1.3 中间结果

文件名称 文件作用
hello.c Hello 源程序
hello.i 预处理生成的文件
hello.s 编译之后的汇编文件
hello.o 汇编之后的可重定位目标执行
hello.elf hello.o 的 ELF 格式
hellooooo.s hello.o 的反汇编代码文件
hello hello 链接之后的可执行文件
hello 反汇编.txt Hello 的反汇编代码文件

1.4 本章小结

本章从大体上简单介绍了 hello 的执行过程,并且列出了实验所需要的硬件环境和软件环境以及开发调试工具,

最后介绍了研究过程中的中间结果,以及其用途和对研究过程产生的作用。

(第 1 章 0.5 分)

第 2 章 预处理

2.1 预处理的概念与作用

**1.**概念

程序设计领域中,预处理一般是指在程序源代码被翻译为目标代码的过程中, 生成二进制代码之前的过程。

2.作用:

预处理是指预处理器(cpp)根据以字符#开头的命令,修改原始的 C 程序,比如#include <stdio.h>命令告诉预处理器读取系统头文件 stdio.h 的内容,并把它直接插入程序文本中。结果就得到了另一个 C 程序,通常是以 .i 作为文件扩展名。

3.常见预处理指令

常见的预处理指令有 #endif(条件编译)、#define(宏定义)、#include(源文件包含)、#line(行控制)、#error(错误指令)、#pragma(和实现相关的杂注) 以及单独的#(空指令)

2.2 在 Ubuntu 下预处理的命令

预处理命令:gcc -E hello.c -o hello.i 或 cpp hello.c > hello.i

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KiIdsodL-1577623497686)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image003.jpg)]

图 2-1 预处理命令

我拿这两条命令分别生成了两个 .i 文件,通过观察发现这两个文件是完全相同的,说明这两条命令等价。

img

图 2-2 预处理结果

img

img

图 2-3 两个完全一样的 .i 文件

2.3 Hello 的预处理结果解析

经过预处理之后,hello.c 文件转化为 hello.i 文件。查看 hello.i 文件可以看到, 在原有代码的基础上,将头文件 stdio.h 的内容引入,例如声明函数、定义结构体、定义变量、定义宏等内容。

原文件中的宏进行了宏展开,头文件中的内容被包含进该文件中。以 stdio.h 的展开为例,cpp 到默认的环境变量下寻找 stdio.h,打开/usr/include/stdio.h 发现其中依然使用了#define 语句,cpp 对此递归展开,所以最终.i 程序中是没有#define 的。而且发现其中使用了大量的#ifdef #ifndef 的语句,cpp 会对条件值进行判断来决定是否执行包含其中的逻辑。其他类似。

Hello.i 仍为可以阅读的 C 语言程序文本文件。

img

图 2-4 hello.i 内容(部分 1)

可以看到程序被扩展成了 3126 行,而 main 函数出现在 3110 行

img

图 2-5 hello.i 内容(尾部)

2.4 本章小结

本章介绍了编译器预处理的相关概念

本章完成了对 hello.c 的预处理工作。使用 Ubuntu 下的预处理指令可以将其转换为.i 文件。完成该阶段转换后,可以进行下一阶段的汇编处理。

(第 2 章 0.5 分)

第 3 章 编 译

3.1 编译的概念与作用

3.1.1 概念

编译是指编译器(ccl)将文本文件 hello.i 翻译成文本文件 hello.s 的过程,它包含了一个汇编语言程序。

3.1.2 作用

编译器的基本功能是把源程序(高级语言)翻译成目标程序。

除了基本功能之外,编译程序还具备语法检查、调试措施、修改手段、覆盖处理、目标程序优化、不同语言合用以及人机联系等重要功能。

3.2 在 Ubuntu 下编译的命令

编译命令:gcc -S hello.i -o hello.s

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5QW7HBBH-1577623497687)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image010.jpg)]

图 3.2.1 编译命令

可以看到多了一个 hello.s 文件:

img

图 3-1 编译成功

3.3 Hello 的编译结果解析

img

3.3.1 数据

图 3-2 图为得到的汇编结果

Hello.c 里一共有三种数据类型:整数,字符串型,数组

1. 整数:

(1)

img

int sleepsecs:hello.c 中 int sleepsecs 是全局变量,已经被赋值为 2.5。

图 3-3,sleepsecs 赋初值

然后编译器进行处理,根据图 3-4,把 sleepsecs 声明为 globl 全局变量,存放在.data 节里,设置 4 字节对齐,声明他的类型是对象,大小是 4 字节,然后被赋值为 long 型 2(因为定义 sleepsecs 是 int,所以默认隐式的对 2.5 进行向零舍入成数字 2,long 和 int 大小都是 4 字节,声明成 long 应该是编译器的自动优化)

img

(2) int i

图 3-4 hello.s 中 sleepsecs 的声明

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wWeSF14L-1577623497688)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image017.jpg)]

i 是循环时候的计数变量[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GSm2HoAx-1577623497689)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image019.jpg)],初始值 0,从 0 变到 9。编译器将局部变量存储在寄存器或者栈空间中

图 3-5 局部变量 i

由图 3-5 可以看出 hello.s 将 i 存储在-4(%rbp)中,占据 4 个字节。

(3) argc

argc 是从终端输入的参数的个数,是 main 函数第一个参数,通常由%rdi 保存。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qzfsHyAC-1577623497690)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image021.jpg)]

**(4)**立即数

图 3-6 局部变量 argc

在程序中直接出现的整数,都是以立即数的形式$xxxx 出现的,直接硬编码在汇编代码中。

2、字符串:

原 C 语言程序中一共有两个字符串,都是 printf 函数输出的内容,存储在.rodata 只读数据节。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LvQ9MNb5-1577623497691)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image023.jpg)]

图 3-7 c 程序中两个字符串

两个字符串:

LC0:“Usage: Hello 学号 姓名!\n”,汉字使用 utf-8 编码,一个汉字在 utf-8 编

码中占三个字节,一个\代表一个字节

LC1:“Hello %s %s\n”, 两个%s 为用户在终端输入的两个参数。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uBlZunPH-1577623497692)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image025.jpg)]

图 3-8 hello.s 中声明在.LC0 和.LC1 段中的字符串

(3)数组:

hello.c 中只有一个数组,就是 main 函数的第二个参数 char* argv[],argv 作为存放 char 指针的数组同时是第二个参数传入。保存在寄存器%rsi 中,

argv 指针指向已经分配好的、一片存放着字符指针的连续空间,起始地址为

argv,main 函数访问数组元素时,从起始地址开始每次+8 字节来计算地址取出数据,如图 3-9,使用了两个(%rax)来取出值:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RTochDzD-1577623497693)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image027.jpg)]

图 3-9 计算地址取出数组值

这两个值分别是 argv[1]和 argv[2]。

3.3.2 赋值

hello.c 里一共有两次赋值操作:

1、int sleepsecs=2.5,全局变量赋值,所以存放到.data 节里,因为定义 sleepsecs 是

int,所以默认隐式的对 2.5 进行向零舍入成数字 2

2、int i = 0,这是 for 循环里的局部变量,用来计数,一般存储到寄存器或者栈空间中,由 movl 赋值。如图 3-10

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yYsiCdSB-1577623497694)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image028.jpg)]

图 3-10 hello.s 给 i 赋初值

其中 movl 中的 l 是后缀,mov 类一共有四条指令,后缀分别是 b、w、l、q。分别表示操作的数据大小不同:1/2/4/8 字节。

3.3.3 类型转换

程序中只有一个隐式类型转换:int sleepsecs=2.5,2.5 是浮点数,给 sleepsecs

赋值时向零舍入到 2。(啊啊啊啊前面都说过了啊)。

浮点数默认类型为 double,所以上述强制转化是 double 强制转化为 int 类型。遵从向零舍入的原则。

3.3.4 算数操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-umW59HNB-1577623497695)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image029.jpg)]

图 3-11 汇编语言中算数和逻辑操作指令

Hello.c 里只有一处算数操作,就是 for 循环里每次 i++。如图 3-12,addl

指令,其中$1 是立即数,-4(%rbp)是 i 的地址。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6ITO40og-1577623497696)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image030.jpg)]

图 3-12 hello.s 中的 i++

3.3.5 关系操作

C 语言中关系操作的种类有关系操作:== 、!= 、> 、 < 、 >= 、 <=。

cmp 的功能相当于减法指令,只是对操作数之间运算比较,不保存结果。cmp 指令执行后,将对标志寄存器产生影响。其他相关指令通过识别这些被影响的标志寄存器位来得知比较结果。

test 指令是测试指令,用于测试某些位是否为 0,执行的是与运算,并且会将

CF 和 OF 置 0,并且影响 SF,ZF。若测试的位均为 0,则 SF=0,ZF=1。但是 test

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NjmOmQad-1577623497696)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image031.jpg)]

命令的两个操作数不会被改变。运算结果在设置过相关标记位后会被丢弃。在 hello.c 中有两个关系操作,分别是 argc!=3 和 i<10

图 3-13 hello.s 中的两处关系操作

3.3.6 数组/指针/结构操作

Hello.c 里有一个数组,就是 main 函数的第二个参数 argv[],然后访问数组的地址,就是知道首地址之后,在这个地址基础上加上数据元素的大小,就到了下一位。分别取了 argv[2]和 argv[1]。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CNpnmnej-1577623497697)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image033.jpg)]

图 3-14 hello.s 中的数组操作

3.3.7 控制转移

C 语言中的控制转移就是,if,switch,while,case,continue,break…之类的语句,如果满足某个条件,则跳转至某个位置。在汇编语言中主要有 jmp 命令来实

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rPfJg84x-1577623497698)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image034.jpg)]

现。

图 3-15 jmp 指令

Hello.c 里一共有两个控制转移:

(i):if(argc!=3)。汇编中,编译器判断 argc 是否等于 3,要是相等就跳到 L2,否则就继续向下执行

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pdAdBxBS-1577623497699)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image035.jpg)]

图 3-16 argc 是否等于 3

(2):for 循环中 i<10,判断 i 是否小于 10, 若小于,就继续向下执行,要是大于等于 10,就跳转到.L4 位置。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ouDZorW4-1577623497699)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image036.jpg)]

图 3-17 i 是否小于 10

3.3.8 函数操作

Hello.c 中总共有五个函数:main(),printf(),exit(),sleep()和 getchar()。

(1) main 函数。

main 函数,又称主函数,是程序执行的起点,开始被存放到.text 节,标记类型是函数。有两个参数 argc 和 argv[],由命令行输入,存储在%rdi 和%rsi 中。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k7izLukj-1577623497700)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image038.jpg)]

图 3-18 hello.s 中的 main 函数

(2) printf 函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xRhgh9Kf-1577623497701)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image039.jpg)]

Hello.c 中一共有两个 printf 函数,

图 3-19 hello.c 中 printf 函数

第一个 printf 函数参数只有一个,就是输出字符串,所以在汇编中被优化成

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oG8UUSqE-1577623497701)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image040.jpg)]

puts 函数。

图 3-20 hello.s 中第一个 printf 函数

第二个 printf 函数有三个参数,分别是输出字符串,还有 argv[1]和 argv[2], 所以不能优化成 puts:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p7iwbdeg-1577623497702)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image041.jpg)]

图 3-21 hello.s 中第二个 printf 函数

(3) exit 函数

若用户输入的参数不是 3 个,则调用 exit 函数

exit()通常是用在子程序中用来终结程序用的,使用后程序自动结束,跳回操作系统。

exit(0) 表示程序正常退出,exit⑴/exit(-1)表示程序异常退出。

把立即数 1 赋给 edi 就是函数第一个参数,说明 exit 要以状态 1 退出。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0mxATxin-1577623497703)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image042.jpg)]

(4) sleep 函数

图 3-22 hello.s 中 exit 函数

执行挂起一段时间

Sleep 函数的参数是 sleepsecs+%rip,其中%rip 是计数器,是指令指针寄存器,

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ECC1UUu6-1577623497704)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image044.jpg)]

保存下一条指令的地址。

图 3-23 hello.s 中 sleep 函数

(5) getchar 函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-25mC6Akg-1577623497704)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image045.jpg)]

getchar 函数没有任何参数,在执行完循环后直接用call getchar 指令调用函数。

图 3-24 hello.s 中 getchar 函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HUgbUYnr-1577623497705)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image046.jpg)]

图 3-25 栈帧结构

3.4 本章小结

本章主要介绍了编译器是如何处理C 语言中的各种各样的数据类型和操作的。需要同时观察 hello.c 和 hello.s ,一起看才能知道他们之间的映射关系。编译的目的就是让语言变得低级,然后让机器能看得懂。

(第 3 章 2 分)

第 4 章 汇 编

4.1 汇编的概念与作用

4.1.1 概念:

汇编大多是指汇编语言,汇编程序。把汇编语言翻译成机器语言的过程称为汇

编。

4.1.2 作用:

将.s 汇编程序翻译成机器语言指令, 把这些指令打包成可重定位目标程序的

格式,并将结果保存在.o 目标文件中,.o 文件是一个二进制文件。它包含程序的指令编码。

4.2 在 Ubuntu 下汇编的命令

命令:as hello.s -o hello.o

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c4WWGWF0-1577623497706)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image048.jpg)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4j9JTjeJ-1577623497707)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image050.jpg)]

图 4-1 汇编命令

4.3 可重定位目标 elf 格式

先使用 readelf -a hello.o > hello.elf 指令获得 hello.o 文件的 ELF 格式。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hnxSDnib-1577623497707)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image052.jpg)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hWM6R66o-1577623497708)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image050.jpg)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bQ0Igcyr-1577623497709)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image053.jpg)]

图 4-2 命令得到 elf 头的文件

图 4-3 可重定位的目标文件 ELF 格式

打开 hello.elf 文件查看 Elf 头:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z7D5Itcl-1577623497710)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image054.jpg)]

图 4-4 hello.o 的 elf 头

1ELF (ELF feader)以一个 16 字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。ELF 头剩下的部分包含帮助链接器语法分析和解释目标文件的信息。其中包括 ELF 头的大小、目标文件的类型(如可重定位、可执行或者共享的)、机器类型(如 x86-64)、节头部表(section header table)的 文件偏 移,以及节头部表中条目的大小和数量。不同节的位置和大小是由节头部表描述的, 其中目标文件中每个节都有一个固定大小的条目(entry)。

**(2)**节头部。如图 4-5,可以看到,节头部表描述了 hello.o 文件各个节的类型、位置和大小等信息,比如.text 节的地址是 0,偏移量是 0x40,大小是 0x7d。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A7ggei4S-1577623497711)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image056.jpg)]

图 4-5 hello.o 的 各种节

3)重定位节: 图 4-6,:重定位节.rela.text ,一个.text 节中位置的列表,包含.text 节中需要进行重定位的信息,当链接器把这个目标文件和其他文件组合时, 需要修改这些位置。

图中 8 条重定位信息分别是对.L0(第一个 printf 中的字符串)、puts 函数、

exit 函数、.L1(第二个 printf 中的字符串)、printf 函数、sleepsecs、sleep 函数、

getchar 函数进行重定位声明。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zdkfvSbf-1577623497712)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image058.jpg)]

图 4-6 重定位节.rela.text

那么如何进行重定位呢,在教材的 P480 页给出了链接器重定位的算法的伪代码:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nl2xn9o8-1577623497712)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image059.jpg)]

图 4-7 重定位伪代码

R_X86_64_PC32:重定位一个使用 32 位 PC 相对地址的引用R_X86_64_32:重定位一个使用 32 位绝对地址的引用offset 是需要被修改的引用的节偏移,

type 告知连接器如何修改新的引用,

addend 是一个有符号常数,一些类型的重定位要使用它对被修改引用的值做偏移调整。

每个节 S 是一个字节数组

假设当算法运行时,链接器已经为每个节(用 ADDR(s)表示)和每个符号都选择了运行时地址(用 ADDR(r.symbol)表示)。第 3 行计算的是需要被重定位的 4 字节引用的数组 S 中的地址。

如果这个引用使用的是 PC 相对寻址,那么它就用第 5~9 行来重定位。如果该引用使用的是绝对寻址,它就通过第 11~13 行来重定位。

下面就以 hello.o 中的 puts 函数为例,演示链接器是如何通过以上算法将

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nho7WGDg-1577623497713)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image061.jpg)]puts 函数重定位的,首先需要将.o 文件反汇编,才能被我们看到:

图 4-8 hello.o 返汇编->hellooooo.s

记得在上面分析过,hello。S 中第一个 printf 被优化成 puts 函数。Main 函数里引用了 puts,所以汇编器会为每一个引用产生一个重定位条目,在下一行显示出来。如图 4-10 中红色框起来的部分。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r0eHgGlY-1577623497714)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image062.jpg)]

图 4-9 hellooooo.s 中 puts 函数的引用

这里 R_X86_64_PC32 说明要使用 PC 相对寻址。从重定位条目还可以看出offset = 0x1b 是偏移量,addend = -0x4。假设链接器已经确定 ADDR(.text)=A, ADDR(puts)=B。链接器首先计算出引用的运行时地址:

refaddr = ADDR(.text) + offset = a+0x1b,

然后更新该引用,使得它在运行时指向 puts 函数:

*refptr = (unsigned)(ADDR(puts)+addend-refaddr) = (unsigned)(BA-0x1f)

在将来得到的可执行文件中,执行 call 指令分为两个步骤

(1)将 PC 压入栈中

(2)PC = PC + *refptr,这样就得到了要执行的 puts 函数的第一条指令。其他的函数调用与该过程原理相同。

**(4)**符号表 symtab:符号表中包含用于重定位的信息,符号名称、符号是全局还是局部,同时标识了符号的对应类型,比如 hello.c 标识的是 FILE。

name 是字符申表中的字节侗移。指向符号的以 null 结尾的字符串名字.。value 是符号的地址。对于可重定位的模块来说,value 是距定义目标的节的起始位置的偏移。对于可执行目标文件来说,该值是一个绝对运行时地址。size 是目标的大小(以字节为单位),type 通常要么是数据,要么是函数。符号表还可以包含各个节的条目,以及对应原始源文件的路径名的条目。所以这些目标的类型也有所不同。binding 字段表示符号是本地的还是全局的。

每个符号都被分配到目标文件的某个节,由 section 字段表示, 该字段也是一个到节头部表的索引。有三个特殊的伪节(pseudosection),它们在节头部表中是没有条目的:

ABS 代 表 不 该 被 重 定 位 的 符 号 : lUNDEF 代表未定义的符号,也就是在本目标模块中引用,但是却在其他地方

定义的符号:

ICOMMON 表示还未被分配位置的未初始化的数据日标。对于 COMMON 符号,value 字段给出对齐要求,而 size 给出最小的大小。注意,只有可重定位目标文件中才有这些伪节,可执行目标文件中是没有的。

COMMON 和.bss 的区别很细微。现代的 GCC 版本根据以下规则来将可重定位目标文件中的符号分配到 COMMON 和.bss 中:( COMMON 未初始化的全局变量。.bss 未初始化的静态变量,以及初始化为 0 的全局或静态变量)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4d1DXpiS-1577623497715)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image064.jpg)]

图 4-10 hello.o symtab 节

4.4 Hello.o 的结果解析

objdump -d -r hello.o 分析 hello.o 的反汇编,并请与第 3 章的 hello.s 进行对照分析。

(因为我在 4.3 部分已经反汇编了,所以我就使用 hellooooo.s 文件来分析) 把机器语言反汇编之后得到的代码和 hello.s 中的汇编代码大致相同。但是也

有一些地方存在差异:

(1)立即数:hello.s 文件中的立即数都是十进制的,而 hellooooo.s 中的立即数则是十六进制的。

(2)分支转移:hello.s 中jmp 之后都是接.L1,.L2 这样的段名称,而在hellooooo.s中就是具体的地址了。

(3)函数调用:在 hellooooo.s 中,call 的地址是下一条指令的地址,而对应

机器码中的操作码为 0。这是因为 hello.c 中调用的函数都是共享库中的函数,在链接后才能确定函数的最终地址。因而在 hello.o 中只是将 call 的地址置为下一条指令的地址,而机器码的操作数则为目标位置(这里为下一条指令)相对于下一条指令的偏移,即 0。

(4)访问全局变量:根据 hello.o 的第 30 行,可以看到汇编语言中%edi 赋值时直接调用了.LC0 这个全局变量。而汇编器在对.s 文件进行汇编时会对每一个全局符号的引用产生一个重定位条目,就是 hellooooo.s 的第 16 行,.LC0 字符串存放在.rodata 节中,并且是绝对地址的引用,汇编器对所有全局符号处理完后, 链接器将对这些重定位条目指定的符号进行重定位,把这些符号(比如这里的字符串)的真正地址计算出来并生成最终的可执行文件。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EBmH2GT2-1577623497715)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image066.jpg)]

图 4-11 hello.s 和 hellooooo.s 对比(部分)

4.5 本章小结

本章介绍了汇编过程,由 .s 变到 .o 文件。通过查看 ELF 头,里面记录了这个文件的基本信息,通过查看这张表可以得到段基址、文件节头数、符号表等等信息。然后再反汇编和原来的 hello.s 对比,分析了汇编语言与机器码的关系。

汇编为接下来的链接过程做好准备。

(第 4 章 1 分)

第 5 章 链 接

5.1 链接的概念与作用

5.1.1 概念:

链接(linking)是将各种代码和数据片段收集并组合成为一个单一文件的过程, 这个文件可被加载(复制)到内存并执行。链接可以执行于编译时(compte tmer)也就是在源代码被翻译成机器代码时;也可以执行于加载时(load time),也就是在程序被加载器(loader)加载到内存并执行时;甚至执行于运行时(runtime),也就是由应用程序来执行。在早期的计算机系统中,链接是手动执行的。在现代系统中,链接是由叫做链接器(linker)的程序自动执行的。

5.2.2 作用

链接器在软件开发中扮演着一个关键的角色,因为它们使得分离编译( separate compilation)成为可能。我们不用将一个大型的应用程序组织为一个巨大的源文件,而是可以把它分解为更小、更好管理的模块,可以独立地修改和编译这些模块。当我们改变这些模块中的一个时,只需简单地重新编译它,并重新链接应用,而不必重新编译其他文件。

注意:这儿的链接是指从 hello.o 到 hello 生成过程。

5.2 在 Ubuntu 下链接的命令

链接命令:

ld -dynamic-linker /lib64/ld-linux-x86-64.so.2 /usr/lib/x86_64-linux-gnu/crt1.o

/usr/lib/x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/7/crtbegin.o hello.o -lc

/usr/lib/gcc/x86_64-linux-gnu/7/crtend.o /usr/lib/x86_64-linux-gnu/crtn.o -z relro -o hello

如图 5-1,链接成功。因为需要生成的是 64 位的程序,所以,使用的动态链接器和链接的目标文件都应该是 64 位的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IGJ55J88-1577623497716)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image068.jpg)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WNZjJyuw-1577623497716)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image070.jpg)]

图 5-1 链接命令

使用 ld 的链接命令,应截图,展示汇编过程! 注意不只连接 hello.o 文件

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

分析 hello 的 ELF 格式,用 readelf 等列出其各段的基本信息,包括各段的起始地址,大小等信息。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jud34SQH-1577623497717)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image072.jpg)]

图 5-2 可执行文件 hello 的 ELF 格式(节头部表)(部分)

ELF 头中主要介绍了程序节表大小、节表数量、数据存储方式(这里是小端)、文件类型(可执行文件)、程序入口点(0x400510)等等与文件整体框架相关的信息

节头表中列举了程序所有的节头,每一个节头都包含了它的节名称、节大小、节类型、节的开始地址、节的偏移量(向地址最高位对齐的相对偏移量)等等

5.4 hello 的虚拟地址空间

使用 edb 加载 hello, 通过 edb 的 Data Dump 窗口查看加载到虚拟地址中的

hello 程序。查看虚拟地址 0x00400000 到 0x00400fff 之 间的内容,可以看到开头是 ELF 头:

img

图 5-3 hello 的虚拟地址空间

地址 0x00400200 对应节头部表中的.interp 节,存的是 linux 动态共享库的路径。根据图 5-2 里其他节的地址,我们可以找到其他节:

img

图 5-4 hello 的虚拟地址空间.interp 节

地址 0x00400338 的位置保存的是与动态链接相关的导入导出符号,对应于图 5-2 里的.dynsym 节,该节保存动态符号表

img

图 5-5 hello 的虚拟地址空间 .dynsym 节

.rodata 节起始地址对应虚拟地址为 0x00400710,在这里我们可以看到 hello.c

中输出的两个字符串。

img

图 5-6 hello 的虚拟地址空间 .rodata 节

通过 Data Dump 查看虚拟地址段 0x600000~0x602000,在 0~fff 空间中,与0x400000~0x401000 段的存放的程序相同,在 fff 之后存放的是.dynamic~.shstrtab 节。

5.5 链接的重定位过程分析

使用 objdump -d -r hello > hello 反汇编.txt 获得 hello 的反汇编代码:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uC6KBHHK-1577623497718)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image078.jpg)]

图 5-7 hello 的反汇编代码

Hello.o 和 hello 生成的反汇编代码其实差不多完全一样,唯一不同之处就是地址由相对偏移变为了可以由 CPU 直接访问的虚拟地址。链接器把 hello.o 中的偏移量加上程序在虚拟内存中的起始地址 0x0040000 和.text 节的偏移量就得到了hello 反汇编.txt 中的一个个地址。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JPT8l7og-1577623497719)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image080.jpg)]

图 5-8 Hello.o 和 hello 生成的反汇编代码

通过比较 hellooooo.s 和 hello 反汇编.txt 了解链接器。

(1)函数个数:在使用 ld 命令链接的时候,指定了动态链接器为 64 的/lib64/ld- linux-x86-64.so.2,crt1.o、crti.o、crtn.o 中主要定义了程序入口_start、初始化函数

_init,_start 程序调用 hello.c 中的 main 函数,libc.so 是动态链接共享库,其中定义了 hello.c 中用到的 printf、sleep、getchar、exit 函数和_start 中调用的 libc_csu_init,

libc_csu_fini, libc_start_main。链接器将上述函数加入。

( 2 ) 函数调用: 链接器解析重定条目时发现对外部函数调用的类型为R_X86_64_PLT32 的重定位,此时动态链接库中的函数已经加入到了 PLT 中,.text 与.plt 节相对距离已经确定,链接器计算相对距离,将对动态链接库中函数的调用值改为 PLT 中相应函数与下条指令的相对地址,指向对应函数。对于此类重定位链接器为其构造.plt 与.got.plt。

(3).rodata 引用:链接器解析重定条目时发现两个类型为 R_X86_64_PC32 的对.rodata 的重定位(printf 中的两个字符串),.rodata 与.text 节之间的相对距离确定,因此链接器直接修改 call 之后的值为目标地址与下一条指令的地址之差,指向相应的字符串。这里以计算第一条字符串相对地址为例说明计算相对地址的算法

具体的算法我在 4.3 节里已经介绍过了,就不再赘述了。

hello 和 hello.o 除了在反汇编生成的汇编代码有所不同,hello 的反汇编文件还在开头比 hello.o 多了.init、.fini、.plt 和.plt.got 节,其中.init 节是程序初始 化需要执行的代码,.fini 是程序正常终止时需要执行的代码,.plt 和.plt.got 节 分别是动态链接中的过程链接表和全局偏移量表。

5.6 hello 的执行流程

在 hello 执行过程中,call 后面接的就是一个函数名,然后按下 F7,进入到这个函数里,第一条的地址就是这个函数的地址。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tHxeEE8h-1577623497719)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image081.jpg)]

图 5-9 hello 执行过程

使用 edb 跟踪程序运行记录程序名与程序地址如下表:

程序名 程序地址
ld-2.23.so!_dl_start 0x00007f11037199b0
ld-2.23.so!_dl_init 0x00007f1103728740
hello!_start 0x400510
libc-2.23.so! libc_start_main 0x00007fa17c942740
ld-2.23.so!_dl_fixup 0x00007fb4dc2d39f0
libc-2.23.so! cxa_atexit 0x00007fb4dbf34280
libc-2.23.so! libc_csu_init 0x400690
libc-2.23.so!_setjmp 0x00007fb4dbf2f250
hello!main 0x400606
hello!puts@plt 0x4004a0
hello!exit@plt 0x4004e0
ld-2.23.so!_dl_fixup 0x00007fb4dc2d39f0
libc-2.23.so!exit 0x00007f2137ae8030
libc-2.23.so! run_exit_handlers 0x00007f2137ae7f10

5.7 Hello 的动态链接分析

因为采用的是动态链接的方式进行整合,所以程序在一开始运行时是不知道重定位函数的地址的,故采用延迟绑定技术计算函数的运行时地址。

延迟绑定技术:

动态链接下对于全局变量和静态数据的访问都需要进行 GOT 定位后才能跳转。

GOT 中保存的是函数的目标地址。

动态链接程序调用均变为 func@plt 的调用。程序首先计算 got.plt 的地址,然后在函数调用时更新重定位节并将对应的地址标记为目标函数的符号,然后 got.plt 地址加上一个偏移量得到 got.plt 中目标函数的地址,再根据地址就可以取出函数例程的第一条指令地址,完成动态链接函数调用

过程链接表(PLT):

PLT 是一个数组,其中每个条目为 16 字节代码。其中:

PTL[0]:指向动态连接器

PLT[1]:调用 libc_start_main 函数初始化环境,调用 main 函数并处理返回值

PLT[2]及以后:用于调用用户代码调用的函数

全局偏移量表(GOT):

GOT 是一个数组,每个条目为 8 字节地址,其中:

GOT[0]与 GOT[1]:包含动态链接器在解析函数地址时的有效信息

GOT[2]:动态链接器在 ld-linux.so 模块的入口点(动态链接处理函数)

其余:对应一个函数,在第一次调用时进行解析,结束后将其指向正确的函数运行时地址

img

图 5-10 用 PLT 和 GOT 调用外部函数

接下来观察 dl_init 前后动态链接项目的变化。由图 5-2 可以得知.got.plt 节的起始地址是 0x601000,在 DataDump 中找到该位置,

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-igi42J2n-1577623497720)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image084.jpg)]

图 5-11 dl_init 执行前的.got.plt 节

img

然后 EDB 执行,按 F8,发现地址 0x601000 后发生了变化

图 5-12 dl_init 执行后的.got.plt 节

可以看到图中两处发生了变化可以看到 dl_init 后出现了两个地址,

0x7f8584118168 和 0x7f8583f08870。这就是 GOT[1]和 GOT[2]

地址 0x601018 的位置储存的是 GOT[2]中动态链接器的首地址,在 data Dump 里选中这个地址,右键选择 Follow address in CPU 查看该地址的内容,发现是动态链接函数:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GZYIagAv-1577623497721)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image086.jpg)]

图 5-13 动态链接函数

5.8 本章小结

在本章中主要介绍了链接的概念与作用、hello 的 ELF 格式,分析了 hello 的虚拟地址空间、重定位过程、执行流程、动态链接过程。

链接过程可以发生在编译时,也可以发生在加载时,甚至可以发生在程序执行时。经过链接,ELF 可重定位的目标文件变成可执行的目标文件,链接器会将静态库代码写入程序中,以及动态库调用的相关信息,并且将地址进行重定位,从而保证寻址的正确进行。静态库直接写入代码即可,而动态链接过程相对复杂一些,涉及共享库的寻址。

链接后,程序便能够在作为进程通过虚拟内存机制直接运行。

(第 5 章 1 分)

第 6 章 hello 进程管理

6.1 进程的概念与作用

6.1.1 概念:

进程是计算机科学中最深刻最成功的概念之一。进程的概念主要有两点:

第一,进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。

第二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程。

进程是操作系统中最基本、重要的概念。是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律引进的一个概念,所有多道程序设计操作系统都建立在进程的基础上。

6.1.2 作用

进程提供给应用程序两个关键抽象:

(1)逻辑控制流 (Logical control flow)

· 每个程序似乎独占地使用 CPU

· 通过 OS 内核的上下文切换机制提供

(2)私有地址空间 (Private address space)

· 每个程序似乎独占地使用内存系统

· OS 内核的虚拟内存机制提供

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

6.2.1 作用:

Shell 字面理解就是个“壳”,Shell 是一个用 C 语言编写的程序。是操作系统

(内核)与用户之间的桥梁,充当命令解释器的作用,将用户输入的命令翻译给系统执行。 Linux 中的 shell 与 Windows 下的 DOS 一样,提供一些内建命令(shell 命令)供用户使用,可以用这些命令编写 shell 脚本来完成复杂重复性的工作。

6.2.2 处理流程:

1、从终端或控制台读入用户输入的命令。

2、将输入字符串切分获得所有的参数

3、如果是内置命令则立即执行

4、否则调用相应的程序为其分配子进程并运行

5、shell 应该接受键盘输入信号,并对这些信号进行相应处理

6、判断程序的执行状态,是前台还是后台。若是前台就等待进程结束;否则直接将进程放入后台执行,继续等待用户的下一次输入。

6.3 Hello 的 fork 进程创建过程

首先运行 hello 程序,在终端输入./ hello,

接下来 shell 会判断是否是内置命令,但是她不是内置命令,所以 shell 会从文件系统中找到当前目录下的 hello 文件并执行。

Shell 调用 fork 函数,创建一个子进程。新创建的子进程几乎但不完全与父进程相同。子进程得到与父进程用户级虚拟地址空间相同(但是独立)的一份副本, 包括代码和数据段、堆、共享库以及用户栈。子进程还获得与父进程任何打开文件描述符相同的副本,这就意味着当父进程调用 fork 时,子进程可以读写父进程中的任何文件。子进程父进程最大的区别是他俩的 PID 不同。

然后 hello 将在 fork 创建的子进程中执行。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Pax5U5KJ-1577623497721)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image087.jpg)] 图 6-1 fork 过程

6.4 Hello 的 execve 过程

在 fork 出一个子进程之后,要执行 execve 函数在当前进程的上下文中加载并运行一个新程序。

execve 调用驻留在内存中的被称为启动加载器的操作系统代码来执行 hello 程序,加载器删除子进程现有的虚拟内存段,并创建一组新的代码、数据、堆和栈段。新的栈和堆段被初始化为零,通过将虚拟地址空间中的页映射到可执行文件的页大小的片,新的代码和数据段被初始化为可执行文件中的内容。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-96V8e697-1577623497722)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image088.jpg)]

最后加载器设置 PC 指向_start 地址,_start 最终调用 hello 中的 main 函数。除了一些头部信息,在加载过程中没有任何从磁盘到内存的数据复制。直到 CPU 引用一个被映射的虚拟页时才会进行复制,这时,操作系统利用它的页面调度机制自动将页面从磁盘传送到内存。

图 6-2 Linux 中进程运行时的内存映像

6.5 Hello 的进程执行

6.5.1 逻辑控制流和时间片

逻辑控制流:一系列程序计数器 PC 的值的序列叫做逻辑控制流,进程是轮流使用处理器的,在同一个处理器核心中,每个进程执行它的流的一部分后被抢占

(暂时挂起),然后轮到其他进程。

时间片:一个进程执行它的控制流的一部分的每一时间段叫做时间片。

6.5.2 上下文

操作系统内核使用一种称为上下文切换(conext switch)的较高层形式的异常控制流来实现多任务。

内核为每个进程维持一个上下文(context)。 上下文就是内核重新启动一个被抢占的进程所需的状态。它由一些对象的值组成,这些对象包括通用目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构,比如描述地址空间的页表、包含有关当前进程信息的进程表,以及包含进程已打开文件的信息的文件表。

在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程。这种决策就叫做调度(scheduling),是由内核中称为调度器(scheduler) 的代码处理的。当内核选择一个新的进程运行时,我们说内核调度了这个进程。在内核调度了一个新的进程运行后,它就抢占当前进程,并使用一种称为上下文切换的机制来将控制转移到新的进程,上下文切换

1)保存当前进程的上下文,

2)恢复某个先前被抢占的进程被保存的上下文。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8FlEwWmf-1577623497722)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image089.jpg)]

3)将控制传递给这个新恢复的进程。

图 6-3 进程的上下文切换

6.5.3 用户模式和内核模式

为了使操作系统内核提供一个无懈可击的进程抽象,处理器必须提供一种机制,限制一个应用可以执行的指令以及它可以访问的地址空间范围。

处理器通常是用某个控制寄存器中的一个模式位(mode bit)来提供这种功能的, 该寄存器描述了进程当前享有的特权。当设置了模式位时,进程就运行在内核模式

中(有时叫做超级用户模式)。一个运行在内核模式的进程可以执行指令集中的任何指令,并且可以访问系统中的任何内存位置。

没有设置模式位时,进程就运行在用户模式中。用户模式中的进程不允许执行特权指令(privileged instruction),比如停止处理器、改变模式位,或者发起一个 I/O 操作。也不允许用户模式中的进程直接引用地址空间中内核区内的代码和数据。任何这样的尝试都会导致致命的保护故障。反之,用户程序必须通过系统调用接口间接地访问内核代码和数据。

运行应用程序代码的进程初始时是在用户模式中的。进程从用户模式变为内核模式的唯一方法是通过诸如中断、故障或者陷阱系统调用这样的异常。当异常发生时,控制传递到异常处理程序,处理器将模式从用户模式变为内核模式。处理程序运行在内核模式中,当它返回到应用程序代码时,处理器就把模式从内核模式改回到用户模式。

6.5.4 hello 的进程执行:

hello 在刚开始运行时内核为其保存一个上下文,进程在用户状态下运行。, 如果没有异常或中断信号的产生,hello 将继续正常地执行。

如果 hello 程序不被抢占则顺序执行,假如发生被抢占的情况,则进行上下文切换。

hello 初始运行在用户模式,在 hello 进程调用 sleep 之后陷入内核模式。内核处理休眠请求主动释放当前进程,并将 hello 进程从运行队列中移出加入等待队列, 定时器开始计时,内核进行上下文切换将当前进程的控制权交给其他进程,当定时器(2.5secs)时发送一个中断信号,中断当前正在进行的进程,进行上下文切换, 恢复 hello 在休眠前的上下文信息,控制权回到 hello 继续执行。

当循环结束后,hello 调用 getchar 函数,之前 hello 运行在用户模式下,在调用 getchar 时进入内核模式,内核中的陷阱处理程序请求来自键盘缓冲区的

DMA 传输,并执行上下文切换,并把控制转移给其他进程。当完成键盘缓冲区到内存的 数据传输后,引发一个中断信号,此时内核从其他进程切换回 hello 进程, 然后 hello 执行 return,进程终止。

6.6 hello 的异常与信号处理

(一)hello 执行过程中可能出现四类异常:中断、陷阱、故障和终止

1、中断

中断是异步发生的,是来自处理器外部的 1/O 设备的信号的结果。硬件中断不是由任何一条专门的指令造成的,从这个意义上来说它是异步的。硬件中断的异常处理程序常常称为中断处理程序(interrupt handler)。

2、陷阱和系统调用

陷阱是有意的异常,是执行一条指令的结果。就像中断处理程序一-样,陷阱处理程序将控制返回到下一条指令。陷阱最重要的用途是在用户程序和内核之间提供一个像过程一样的接口,叫做系统调用。

3、故障

由错误情况引起,它可能够被故障处理程序修正。当故障发生时,处理器将控制转移给故障处理程序。如果处理程序能够修正这个错误情况,它就将控制返回到引起故障的指令,从而重新执行它。否则,处理程序返回到内核中的 abort 例程,

abort 例程会终止引起故障的应用程序。

4.终止

终止是不可恢复的致命错误造成的结果,通常是一 些硬件错误: 比如 DRAM 或者 SRAM 位被损坏时发生的奇偶错误。终止处理程序从不将控制返回给应用程序。

(二)接下来分析 hello 对各种信号的处理:

Hello 正常运行时,若输入的参数小于三个就会输出提示信息 Usage:hello 学号 姓名!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UmQxJikq-1577623497723)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image091.jpg)]若输入正确的信息,hello 就会打印出信息十次。

图 6-4 hello 正常运行

图 6-5 是 hello 在运行的时候乱按键盘出现的情况,发现我们可以在键盘上随便的输入,并不会影响 hello 的输出,在 hello 的循环结束后调用 getchar 函数, 把我们乱按的输出一行一行的读走,每一个都被 shell 当做是一个命令来执行。但是当然…谁都不知道是啥命令。可见在运行 hello 的过程中从键盘的输入被缓存到了输入缓存区。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-onHlJomD-1577623497724)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image093.jpg)]

图 6-5 hello 运行时乱按

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ObrB8FbQ-1577623497724)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image095.jpg)]按下 Ctrl+C,进程终止,通过 ps 命令可以看到 hello 程序的 pid。

图 6-5 ctrl+c 终止

按下 Ctrl+Z,hello 被挂起,通过 jobs 命令可以看到 hello 进程被挂起。进程号是 1.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2h14Tqqo-1577623497725)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image097.jpg)]

图 6-6 ctrl+z 挂起

输入 pstree 命令,可以以在进程树中找到 bash 下的 hello 进程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nUIEhJ0W-1577623497726)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image099.jpg)]图 6-7 进程树 bash 下的 hello 进程

因为程序的进程号为 1,故用命令 fg 1 将其调至前台继续运行,程序正常结束

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BmxR5yHn-1577623497727)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image101.jpg)]

图 6-8 命令 fg 1 将 hello 重新运行

用 ps 看到 hello 的 PID 是 4724,故可以用 kill -9 4724 终止 hello 进程。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cJADWnYU-1577623497728)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image103.jpg)]图 6-9 kill 杀死了进程

6.7 本章小结

本章介绍了程序在 shell 执行及进程的相关概念。程序在 shell 中执行是通过

fork 函数及 execve 创建新的进程并执行程序。进程拥有着与父进程相同却又独立的环境,与其他系统进并发执行,拥有各自的时间片,在内核的调度下有条不紊的执行着各自的指令。

程序运行中难免遇到异常,异常分为中断、陷阱、故障和终止四类,均有对应的处理方法。操作系统提供了信号这一机制,实现了异常的反馈。这样,程序能够对不同的信号调用信号处理子程序进行处理。

(第 6 章 1 分)

第 7 章 hello 的存储管理

7.1 hello 的存储器地址空间

1**、物理地址****(physical address)**

计算机系统的主存被组织成一个由 M 个连续的字节大小的单元组 成的数组, 其每一个字节都被给予一个唯一的地址,这个地址称为物理地址。

用于内存芯片级的单元寻址,与处理器和 CPU 连接的地址总线相对应。这个概念应该是这几个概念中最好理解的一个,

2、虚拟内存(virtual memory)

CPU 启动保护模式后,程序运行在虚拟地址空间中。与物理地址相 似,虚拟内存被组织为一个存放在磁盘上的 N 个连续的字节大小的单元组成的数 组,其每个字节对应的地址成为虚拟地址。虚拟地址包括 VPO(虚拟页面偏移量)、VPN

(虚拟页号)、TLBI(TLB 索引)、TLBT(TLB 标记)。

计算机会呈现出要比实际拥有的内存大得多的内存量。因此他允许程式员编制并运行比实际系统拥有的内存大得多的程式。这使得许多大型项目也能够在具有有限内存资源的系统上实现

3、逻辑地址(logical address)

是指由程式产生的和段相关的偏移地址部分,。分为两个部分,一个部分为段基址,另一个部分为段偏移量。

4、线性地址(linear address)

地址空间是一个非负整数地址的有序集合,而如果此时地址空间中 的整数是连续的,则我们称这个地址空间为线性地址空间。

其中,由逻辑地址翻译得到的线性地址即为虚拟地址

img

图 7-1 地址空间

7.2 Intel 逻辑地址到线性地址的变换-段式管理

**段寄存器(**16 位),用于存放段选择符CS(代码段):程序代码所在段 SS(栈段):栈区所在段

DS(数据段):全局静态数据区所在段

其他 3 个段寄存器 ES、GS 和 FS 可指向任意数据段段选择符中字段的含义:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZL7DVA0P-1577623497729)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image105.jpg)]

TI=0,选择全局描述符表(GDT),TI=1,选择局部描述符表(LDT)

RPL=00,为第 0 级,位于最高级的内核态,RPL=11,为第 3 级,位于最低级

的用户态,第 0 级高于第 3 级

高 13 位-8K 个索引用来确定当前使用的段描述符在描述符表中的位置

img

图 7-2 段寄存器含义

如图 7-4:展示了逻辑地址到线性地址的转化过程:

首先根据段选择符的 TI 部分判断需要用到的段选择符表是全局描述符表还是 局部描述符表,随后根据段选择符的高 13 位的索引(描述符表偏移)到对应的描 述符表中找到对应的偏移量的段描述符,从中取出 32 位的段基址地址,将

32 位的段基址地址与 32 位的段内偏移量相加得到 32 位的线性地址。实模式下:逻辑地址 CS:EA=物理地址 CS * 16 + EA

保护模式下:以段描述符作为下标,到 GDT/LDT 表查表获得段地址, 段地址+偏移地址=线性地址。

img

图 7-3 逻辑地址到线性地址

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

虚拟页面地集合被分为三个不相交的子集:已缓存、未缓存和未分配。

img

图 7-4 虚拟页和物理页

img

图 7-5 页表

hello 的线性地址到物理地址的变换需要查询页表得出,hello 的线性地址被分成两个部分,第一部分虚拟页号 VPN 用于在页表查询物理页号 PPN,而第二部分虚拟页偏移量 VPO 则与查询到的物理页号 PPN 一起组成物理地址。

img

图 7-6 虚拟地址到物理地址

虚拟地址转换为物理地址的过程:首先根据虚拟页号在当 前进程的物理页表中找到对应的页面,若符号位设置为 1,则表示命中,从页面中 取出物理页号+虚拟页偏移量即组成了一个物理地址;否则表示不命中,产生一个 缺页异常,需要从磁盘中读取相应的物理页到内存。

7.4 TLB 与四级页表支持下的 VA 到 PA 的变换(虚拟->物理)

在 Corei7 中 48 位虚拟地址分为 36 位的虚拟页号以及 12 位的页内偏移。

四级页表中包含了一个地址字段,它里面保存了 40 位的物理页号(PPN), 这就要求物理页的大小要向 4kb 对齐

四级页表每个表中均含有 512 个条目,故计算四级页表对应区域如下: 第四级页表:每个条目对应 4kb 区域,共 512 个条目

第三级页表:每个条目对应 4kb512=2MB 区域,共 512 个条目第二级页表:每个条目对应 2MB512 = 1GB 区域,共 512 个条目

第一级页表:每个页表对应 1GB*512 = 512GB 区域,共 512 个条目

img

图 7-7 前三级页表的条目格式

img

图 7-8 第四级页表的条目格式

从 VA 到 PA 的变换:

img

图 7-9 从 VA 到 PA 的变换流程

从 VA 中分出 36 位的 VPN 并根据其中的 TLBI 索引到对应的 TLB 组(类

cache),结合 TLBT 找到对应的行并判断 TLB 是否命中。若是命中,则取出其中的 PPN(物理页号);否则转到页表索引。

将 VPN 分为四段,每段 9 位,里面保存的是对应页表的偏移量。从第一级页表开始索引,找到对应的 PTE 条目,从中取出相应的第二级页表的首地址。这个首地址加上 VPN2 的偏移即得到第二级 PTE,取出其中的内容即为第三级页表的首地址……以此类推从第四级页表中取出的即为 PPN(物理页号)

·将前面得到的 PPN 与 VPO 相加就可以得到虚拟地址翻译对应的物理地址

img

图 7-10 四级页表的多级索引

7.5 三级 Cache 支持下的物理内存访问

MMU 发送物理地址 PA 给 L1 缓存,L1 缓存从物理地址中抽取出缓存偏移CO、缓存组索引 CI 以及缓存标记 CT。高速缓存根据 CI 找到缓存中的一组,并通过 CT 判断是否已经缓存地址对应的数据,若缓存命中,则根据偏移量直接从缓存中读取数据并返回;若缓存不命中,则继续从 L2、L3 缓存中查询,若仍未命中, 则从主存中读取数据。

img

图 7-11 三级 cache 下的物理内存访问

7.6 hello 进程 fork 时的内存映射

img

图 7-12 再看共享对象

虚拟内存和内存映射解释了 fork 函数如何为每个新进程提供私有的虚拟地址空间.

为新进程创建虚拟内存

创建当前进程的的 mm_struct, vm_area_struct 和页表的原样副本. 两个进程中的每个页面都标记为只读

两个进程中的每个区域结构(vm_area_struct)都标记为私有的写时复制(COW) 在新进程中返回时,新进程拥有与调用 fork 进程相同的虚拟内存

随后的写操作通过写时复制机制创建新页面

7.7 hello 进程 execve 时的内存映射

execve 函数在当前进程中加载并运行新程序的步骤:

1、删除已存在的用户区域

2、创建新的区域结构私有的、写时复制

代码和初始化数据映射到.text 和.data 区(目标文件提供)

.bss 和栈堆映射到匿名文件 ,栈堆的初始长度 0 3、共享对象由动态链接映射到本进程共享区域

4、设置 PC,指向代码区域的入口点

Linux 根据需要换入代码和数据页面

img

图 7-12 execve 函数加载时的内存区域结构

7.8 缺页故障与缺页中断处理

缺页故障是一种常见的故障,当指令引用一个虚拟地址,在 MMU 中查找页表时发现与该地址相对应的物理地址不在内存中,因此必须从磁盘中取出的时候就会发生故障。 即缓存不命中。

img

图 7-13 缺页处理

img

图 7-14 Linux 缺页处理

缺页中断处理:缺页处理程序是系统内核中的代码,选择一个牺牲页面,如果这个牺牲页面被修改过,那么就将它交换出去,换入新的页面并更新页表。当缺页处理程序返回时,CPU 重新启动引起缺页的指令,这条指令再次发送VA 到MMU,

这次 MMU 就能正常翻译 VA 了。

7.9 动态存储分配管理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v4MF3T0a-1577623497729)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image121.jpg)]

在程序运行时程序员使用动态内存分配器(比如 malloc)获得虚拟内存。数据结构的大小只有运行时才知道。

图 7-15 动态内存分配器

动态内存分配器维护着一个进程的虚拟内存区域,称为。系统之间细节不同,但是不失通用性,假设堆时一个请求二进制零的区域,它紧接在未初始化的区域后开始,并向上生长(向更高的地址)。对于每个进程,内核维护着一个变量 brk,它指向堆的顶部。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-33YHCUbr-1577623497730)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image122.jpg)]

图 7-16 分配器运行时的堆栈

分配器将堆视为一组不同大小的块的结合来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可用来分配。空闲保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。

分配器有两种基本风格,两种风格都要求应用显式地分配块不同在于由哪个实体来负责释放。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cwmRavVV-1577623497731)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image123.jpg)]

(1)隐式空闲链表:

图 7-17 不使用边界标记的堆块的格式(左)使用边界标记的堆块的格式(右)

在这种情况下,一个块是由一个字的头部、有效载荷,以及可能的一些额外的填充组成的。头部编码了这个块的大小(包括头部和所有的填充),以及这个块是已分配的还是空闲的。我们可以用内存中最低位来指明这个块是已分配的还是空闲的。我们可将堆组织为一个连续的已分配块和空闲块的序列如下图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ul9XNmle-1577623497731)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image124.jpg)]

图 7-18 隐式空闲链表

这种结构为隐式空闲链表,空闲块通过头部中的大小字段隐含地连接着的。分配器可以通过遍历堆中所有块,遍历整个空闲块集合。我们需要某种特殊标记结束块。

隐式链表的优点是简单,显著缺点是任何操作开销要对空闲链表搜索,搜索时间与堆中已分配块和空闲块总数呈线性关系。

(2)显式空闲链表

将堆组织成一个双向空闲链表,在每个空闲块中,都包含一个 pred 前驱和 succ

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YaPaA2Lp-1577623497732)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image125.jpg)]

后继指针。

图 7-19 使用双向链表的堆块的格式

使用双向链表而不是隐式空闲链表,使首次适配的分配时间从块总数的线性时间减少到了空闲块数量的线性时间。不过,释放一个块的时间可以是现行的, 也可能是个常数。

一种方法是后进先出的顺序维护链表,将新释放的块放置在链表的开始处。使用 LIFO 的顺序和首次配适的放置策略,分配器会最先检查最近使用过的块。另一种是按照地址顺序来维护链表,其中链表中每个块的地址都小于它后继

的地址。在这种情况下,释放一个块需要线性时间的搜索来定位合适的前驱。 一般而言,显示链表的缺点是空闲块必须足够大,以包含所有需要的指针,

以及头部和可能的脚部。这就导致了更大的最小块大小,也潜在地提高了内部碎片程度

内存块合并方式:

立即合并,在每次一个块被释放就合并所有的相邻块。对于某些情况会产生抖动,产生不必要的分割和合并。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lUdEoUz4-1577623497732)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image126.jpg)]

图 7-20 内存块合并四种情况

7.10 本章小结

虚拟内存是对主存的一个抽象,访存时地址需要从逻辑地址翻译到虚拟地址并进一步翻译成物理地址。

现代系统通过将虚拟内存片和磁盘上的文件片关联起来,来初始化虚拟内存片,这个过程称为内存映射。

操作系统通过地址的页式管理来实现对磁盘的缓存、内存管理、内存保护等功能。

虚拟内存为便捷的加载、进程管理提供了可能。程序运行过程中往往涉及动态内存分配,动态内存分配通过动态内存分配器完成

(第 7 章 2 分)

第 8 章 hello 的 IO 管 理

8.1 Linux 的 IO 设备管理方法

设备的模型化:文件 设备管理:unix io 接口

所有的 I/O 设备(例如网络、磁盘和终端)都被模型化为文件,而所有的输入和输出都被当做对相应文件的读和写来执行。这种将设备优雅地映射为文件的方式,允许 linux 内核引出一个简单、低级的应用接口,称为 Unix I/O,这使得所有的输入和输出都能以一种统一且一致的方式来执行:UNIX I/O。

8.2 简述 Unix IO 接口及其函数

接口的定义

接口就是连接CPU 与外设之间的部件,它完成CPU 与外界的信息传送。还包括辅助 CPU 工作的外围电路,如中断控制器、DMA 控制器、定时器、高速CACHE

接口的十大功能

输入输出功能、数据缓冲功能、联络功能、数据转换功能、中断管理功能、提供时序控制功能、寻址功能、可编程功能、电器特征的匹配功能、错误监测功能

图 8-1 简单的 I/O 接口框图

Unix I/O 接口提供了以下函数供应用程序调用:

(1)打开文件:int open(char *filename, int flags, mode_t mode);

关闭文件:int close(int fd);

(2)读文件:ssize_t read(int fd, void *buf, size_t n);

写文件:ssize_t write(int fd, const void *buf, size_t n);

图 8-2 open 函数的声明

图 8-3 close 函数的声明

图 8-4 read 和 write 函数的声明

8.3 printf 的实现分析

参考网址:https://www.cnblogs.com/pianist/p/3315801.html 研究 printf 的实现,首先来看看 printf 函数的函数体:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nMVDDHLd-1577623497733)(file:////Users/jiangxi/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image132.jpg)]

图 8-5 printf 函数的声明

在形参列表里有这么一个 token:…

这个是可变形参的一种写法。 当传递参数的个数不确定时,就可以用这种方式来表示。 很显然,我们需要一种方法,来让函数体可以知道具体调用时参数的个数。

(char*)(&fmt) + 4) 表示的是…中的第一个参数的地址

i = vsprintf(buf, fmt, arg);

让我们来看看 vsprintf(buf, fmt, arg)是什么函数。

图 8-5 函数 vsprintf(buf, fmt, arg)

vsprintf 返回的是要打印出来的字符串的长度

write,顾名思义:写操作,把 buf 中的 i 个元素的值写到终端。

所以说:vsprintf 的作用就是格式化。它接受确定输出格式的格式字符串 fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出。

接着就轮到 write 系统函数了,在 Linux 下,write 函数的第一个参数为 fd, 也 就是描述符,而 1 代表的就是标准输出。查看 write 函数的汇编实现可以发现, 它首先给寄存器传递了几个参数,然后执行 int INT_VECTOR_SYS_CALL,代表通过系统调用 syscall,syscall 将寄存器中的字节通过总线复制到显卡的显存中。显示芯片按照刷新频率逐行读取 vram,并通过信号线向液晶显示器传输每一个点

(RGB 分量)。由此 write 函数显示一个已格式化的字符串

8.4 getchar 的实现分析

异步异常-键盘中断的处理:当用户按键时,键盘接口会得到一个代表该按键的键盘扫描码,同时产生一个中断请求,中断请求抢占当前进程运行键盘中断子程序,键盘中断子程序先从键盘接口取得该按键的扫描码,然后将该按键扫描码转换成 ASCII 码,保存到系统的键盘缓冲区之中。

getchar 函数落实到底层调用了系统函数 read,通过系统调用 read 读取存储在键盘缓冲区中的 ASCII 码直到读到回车符然后返回整个字串,getchar 进行封装, 大体逻辑是读取字符串的第一个字符然后返回。

getchar 函数的大致实现如下

图 8-6 getchar 函数

8.5 本章小结

I/O 时在主存和外部设备之间复制数据的过程。在 Linux 中,I/O 的实现是通过 Unix I/O 函数来执行的。Linux 把所有的 I/O 设备模型化为文件,并提供统一的Unix I/O 接口,这使得所有的输入输出都能以一种统一且一致的方式来执行。

(第 8 章 1 分)

结论

让我们再来回顾一下 hello 的一生:

用户从键盘输入,得到 hello.c 源文件。

编译器和汇编器对 hello.c 进行预处理,然后对其进行编译和汇编,得到可重定位目标文件 hello.o。

链接器对 hello.o 进行链接,并得到可执行目标文件 hello,此时 hello 已经可以被操作系统加载和执行。

bash 执行 hello,首先 bash 会 fork 一个进程,然后在这个新的进程中 execve

hello,execve 会清空当前进程的数据并加载 hello,然后把 rip 指向 hello 的程序入口,把控制权交给 hello。

hello 与许多进程并行执行,执行过程中由于系统调用或者计时器中断,会导致上下文切换,内核会选择另一个进程进行调度,并抢占当前的 hello 进程。

hello 执行的过程中可能收到来自键盘或者其它进程的信号,当收到信号时

hello 会调用信号处理程序来进行处理,可能出现的行为有停止终止忽略等。

hello 输出信息时需要调用 printf 和 getchar,而 printf 和 getchar 的实现需要调用 Unix I/O 中的 write 和 read 函数,而它们的实现需要借助系统调用。

hello 中的访存操作,需要经历逻辑地址到线性地址最后到物理地址的变换, 而访问物理地址的数据可能已被缓存至高速缓冲区,也可能位于主存中,也可能位于磁盘中等待被交换到主存。

hello 结束进程后,bash 作为 hello 的父进程会回收 hello 进程,至此 hello 没了。

回顾 hello 短暂的一生,可以看出一个简单的小程序也需要计算机内部多个元件多个系统的相互协调合作,计算机系统真的是一个庞大而又精细的组织!!

(结论 0 分,缺失 -1 分,根据内容酌情加分)

附件

列出所有的中间产物的文件名,并予以说明起作用。

文件名称 文件作用
hello.c Hello 源程序
hello.i 预处理生成的文件
hello.s 编译之后的汇编文件
hello.o 汇编之后的可重定位目标执行
hello.elf hello.o 的 ELF 格式
hellooooo.s hello.o 的反汇编代码文件
hello hello 链接之后的可执行文件
hello 反汇编.txt 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 深入理解计算机系统第三版,机械工业出版社 2017.10 第一版

[8] 郑贵滨老师精心准备的 ppt

(参考文献 0 分,缺失 -1 分)

发布了7 篇原创文章 · 获赞 3 · 访问量 474

猜你喜欢

转载自blog.csdn.net/qq_39600733/article/details/103757497
今日推荐