计算机系统
计算机科学与技术学院
2022年5月
摘 要
本文以hello程序的一生为线索脉络,仔细的探究了一个高级语言程序在完成编写后,是如何一步步通过预处理、编译、汇编、链接生成可执行文件,进而在shell中创建进程,使用虚拟空间等直至结束程序,擦去程序一切使用痕迹。加深了我对程序是如何在计算机运行的理解,加深了我对计算机系统原理的理解。
关键词:计算机系统 hello的一生 程序的完整运行过程
目 录
第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的预处理结果解析 – 6 –
2.4 本章小结 – 6 –
第3章 编译 – 8 –
3.1 编译的概念与作用 – 8 –
3.2 在UBUNTU下编译的命令 – 8 –
3.3 HELLO的编译结果解析 – 8 –
3.4 本章小结 – 12 –
第4章 汇编 – 13 –
4.1 汇编的概念与作用 – 13 –
4.2 在UBUNTU下汇编的命令 – 13 –
4.3 可重定位目标ELF格式 – 13 –
4.4 HELLO.O的结果解析 – 16 –
4.5 本章小结 – 18 –
第5章 链接 – 19 –
5.1 链接的概念与作用 – 19 –
5.2 在UBUNTU下链接的命令 – 19 –
5.3 可执行目标文件HELLO的格式 – 19 –
5.4 HELLO的虚拟地址空间 – 24 –
5.5 链接的重定位过程分析 – 26 –
5.6 HELLO的执行流程 – 27 –
5.7 HELLO的动态链接分析 – 28 –
5.8 本章小结 – 29 –
第6章 HELLO进程管理 – 30 –
6.1 进程的概念与作用 – 30 –
6.2 简述壳SHELL-BASH的作用与处理流程 – 30 –
6.3 HELLO的FORK进程创建过程 – 30 –
6.4 HELLO的EXECVE过程 – 32 –
6.5 HELLO的进程执行 – 32 –
6.6 HELLO的异常与信号处理 – 32 –
6.7本章小结 – 39 –
第7章 HELLO的存储管理 – 40 –
7.1 HELLO的存储器地址空间 – 40 –
7.2 INTEL逻辑地址到线性地址的变换-段式管理 – 40 –
7.3 HELLO的线性地址到物理地址的变换-页式管理 – 40 –
7.4 TLB与四级页表支持下的VA到PA的变换 – 40 –
7.5 三级CACHE支持下的物理内存访问 – 40 –
7.6 HELLO进程FORK时的内存映射 – 40 –
7.7 HELLO进程EXECVE时的内存映射 – 40 –
7.8 缺页故障与缺页中断处理 – 40 –
7.9动态存储分配管理 – 40 –
7.10本章小结 – 41 –
第8章 HELLO的IO管理 – 42 –
8.1 LINUX的IO设备管理方法 – 42 –
8.2 简述UNIX IO接口及其函数 – 42 –
8.3 PRINTF的实现分析 – 42 –
8.4 GETCHAR的实现分析 – 42 –
8.5本章小结 – 42 –
结论 – 42 –
附件 – 44 –
参考文献 – 45 –
第1章 概述
1.1 Hello简介
P2P: From Program to Process
当我们使用高级语言C语言完成了hello.c程序的编写时,hello短暂而又漫长的一生便由此揭开了帷幕。首先,hello.c源程序经由cpp进行预处理,成为hello.i这一被修改了的源程序。接着经由ccl编译形成汇编程序hello.s。再经由汇编器as转化为机器语言,并完成从文本文件hello.i到二进制文件转化,从而形成的可重定位的目标程序hello.o。最后经由链接器ld,与库函数printf.o链接并完成重定位操作,生成可执行的目标文件hello。然后shell运行加载生成好的可执行文件hello,再调用fork()函数产生子进程。以上种种步骤便是hello从C语言程序(program)到进程(process)的全过程。
020:From Zero-0 to Zero-0
由fork函数产生的子进程,通过调用execve函数对hello进行加载。其中操作系统为程序分配虚拟空间。将整个文件从虚拟空间中映射到的物理内存中的所有数据,从磁盘载入物理内存中,然后再跳转至main函数入口处,进行CPU的时间片的分配和逻辑控制流的执行。数据首先从磁盘流向CPU中,然后内存管理器通过高速缓存,TLB与分级页表完成相应数据的存储以及高效调用,然后hello通过I/O设备进行输出。当相关代码完成执行,程序成功结束运行,shell回收hello进程,由内核删除hello的所有相关数据与相应痕迹。便完成了020的过程。
1.2 环境与工具
1.2.1 硬件环境
X64 CPU;2GHz;2G RAM;256GHD Disk
1.2.2 软件环境
Windows11 64位; Vmware 11;Ubuntu 16.04 LTS 64位
1.2.3 开发工具
CodeBlocks 64位,Visual Studio 2022, dev-c++5.8.4;vi/vim/gedit+gcc,edb,gdb
1.3 中间结果
hello.i:源程序hello.c预处理后产生的文本文件
hello.s:hello.i编译后生成的汇编程序
hello.o:hello.s汇编后的可重定位目标程序
helloo.elf:hello.o文件的ELF格式
hello:hello.o文件经过链接生成的可执行目标文件
hello.objdump:hello.o反汇编后生成的文件
hello.elf:hello文件的ELF格式
hello.objdumpp:hello文件的反汇编文件
1.4 本章小结
本章简明扼要地介绍了hello如何从原始的C语言一步步转化为生成相关文件再到完成程序执行(即所谓P2P,020)的过程,并说明了本次使用到的环境,工具与生成的中间文件等相关信息。
第2章 预处理
2.1 预处理的概念与作用
(1) 概念
预处理器cpp根据以#开头的命令修改原始的C程序。主要包括:头文件,宏定义与条件编译三个方面。根据不同的类别以不同的方式将文本以插入与替换,并且删除文件中多余的空白字符与注释。预处理是后续处理基础。
(2) 作用
- 文件包含:头文件的预处理。将预处理器读到的相应系统头文件的内容直接插入程序文本中。
- 宏代换:通过宏#define使用标识符作为某一字符串的等价代换。在文中逐一比较,发现与标识符相同的字符串就直接进行替换。
- 条件编译:根据条件编译指令进行相应的处理。
- 删除多余的空白字符与无用注释
2.2在Ubuntu下预处理的命令
命令:gcc -E hello.c -o hello.i
图2-1.Ubuntu预处理命令
2.3 Hello的预处理结果解析
使用vim打开生成的hello.i文件,可以发现,在进行了预处理的代码插入与替换后,代码长度达到3060行,大幅度上升。其中主要是进行了三个头文件(#include #include #include)的内容的插入。并且hello.c中main函数相关代码在hello.i中从第3047行开始到3060行结束,与原来相差不大。
图2-2.预处理结果hello.i中部分代码展示
2.4 本章小结
本章主要介绍了hello的预处理,包括预处理的概念、功能,并在Ubuntu系统下进行了相应的实践操作,查看了预处理生成结果文件hello.i代码进行结果解析。
第3章 编译
3.1 编译的概念与作用
(1) 概念
编译器ccl将文本文件hello.i通过词法分析与语法分析翻译成一个包含汇编语言程序的文本文件hello.s。其中hello.s中的每条语句都以一种文本格式描述了一条低级机器语言指令。
(2) 作用
将.i文本文件翻译成.s汇编语言程序文本文件,为后续文件转化为机器指令的二进制文件做准备。
3.2 在Ubuntu下编译的命令
命令:gcc -S hello.i -o hello.s
图3-1.Ubuntu下进行编译的过程
3.3 Hello的编译结果解析
表1 hello.s的文件结构
内容 含义
.file 源文件
.text 代码段
.global 全局变量
.data 存放已经初始化的全局和静态C 变量
.section .rodata 存放只读变量
.align 对齐方式
.type 表示是函数类型/对象类型
.size 表示大小
.long .string 表示是long类型/string类型
3.3.1 常量:
图3-2.ELF格式文件中的字符串常量
由源程序可得,存在字符串常量”用法: Hello 学号 姓名 秒数!\n”和”Hello %s %s\n”,在.s中查找,其编译形式如上。易知,中文字符被编码成立utf-8格式,编译链接时存放进.rodata节。
3.3.2 变量:
- 全局变量
初始化的全局变量储存在.data节,sleepsecs全局变量被存放在.data节 - 局部变量
局部变量存储在寄存器或栈中。
图3-3.局部变量i的定义
由源程序可得,存在进行循环的局部变量i,在.s中查找,其编译形式如上。编译时将局部变量放入堆栈中%rbp-4的地方进行初始化。
3.3.3 赋值:
局部变量采取MOV指令赋值。
图3-4.局部变量i的初始化赋值
由源程序可得,在对局部变量i进行初始化时,进行赋值0。在.s中查找,其编译形式如上。
3.3.4 算术操作:
编译器使用add,sub,imul,div代表加减乘除运算。
1.
图 0-1. i的自增操作
在循环操作中使用i++进行步长为1的自增操作。
图 0-7. argv数组计算
根据argv[0]地址进行加减操作得到argv[1],argv[2]地址。
3.3.5 关系操作:
编译器通过cmp指令进行关系操作。
图 0-8. 判断命令行输入是否合法
24行为判断argc!=4
图 0-9. 判断循环终止条件
53行为循环中判断i<10
3.3.6 数组/指针操作:
图 0-10. 指针运算
主函数main的参数中有指针数组char *argv[],argv[0]指向输入程序的路径和名称,argv[1]和argv[2]分别表示两个字符串。
3.3.7 控制转移:
在使用比较关系操作进行判断后,程序将按判断结果经如下代码跳转至L2或L4,进入if语句或继续进入循环
图 0-2. if语句跳转
图 0-3. for语句跳转
3.3.8 函数操作:
由源程序可得,hello.c中存在函数如下:main函数,printf函数,sleep函数,
getchar函数。
- main函数
参数传递:分别用寄存器%edi和%rsi存储传入参数argc,argv[],
函数调用:由系统启动函数调用
图 0-13. main函数返回
2) printf函数
i. call puts@PLT
参数传递:传入字符串参数首地址
函数调用:if判断满足条件后被调用
图 3-14. call puts汇编代码
ii. call printf@PLT
参数传递:传入 argv[1]和argc[2]的地址
函数调用:由for循环里调用
图 0-15.call printf汇编代码
3) sleep函数
参数传递:传入全局变量sleepsecs
函数调用:由for循环里调用
图 3-16. sleep函数汇编代码
4) getchar函数
5) 参数传递:无
函数调用:在for循环结束后被调用
图 0-17. getchar函数汇编代码
3.4 本章小结
本章主要介绍了hello的编译,包括编译的概念、功能,并在Ubuntu系统下进行了相应的实践操作,查看了预处理生成结果文件hello.s代码进行详尽的结果解析,逐一分析了汇编代码是如何实现各类数据变量以及函数的。
第4章 汇编
4.1 汇编的概念与作用
(1) 概念
通过汇编器as将汇编语言翻译成机器语言,即hello.s转化为hello.o的过程。
(2) 作用
汇编器as将hello.s翻译成机器语言指令,将这些指令打包成一种叫做可重定位目标程序的格式,并将结果保存在二进制目标文件hello.s中。
4.2 在Ubuntu下汇编的命令
命令:as hello.s -o hello.o
图 4-1 使用汇编指令生成.o文件
4.3 可重定位目标elf格式
- 命令为readelf -a hello.o > helloo.elf
图 4-2 使用指令生成可重定位目标elf格式
2. ELF头
ELF头包括系统信息(字的大小和字节顺序),编码方式,ELF头大小,机器类型,条目的大小和数量等等一系列信息。
图 0-1 ELF文件头内容
3. 节头
节头描述了.o文件中出现的各个节的名称、类型、位置、所占空间大小、偏移量及对齐等信息。
图 0-2 节头部表内容
4. 重定位节
当链接器将当前目标文件和其他文件进行链接时,需要通过重定位节对表中这些外部符号位置的地址进行一定的修改。
修改方法如下:
若重定义类型为R_X86_64_PC32,则在引用32位PC相对地址。若重定义类型为R_X86_64_32,则在引用一个32位绝对地址。那么根据重定位条目和重定位算法就可以得到对应重定位位置
图 0-3 重定位节内容
5. 符号表
用于存放程序中定义和引用的函数和全局变量的信息。
图 0-4 符号表内容
4.4 Hello.o的结果解
命令:objdump -d -r hello.o
图 0-5 反汇编文件内容
- 机器语言的构成:机器语言由机器指令集构成,能够直接被机器执行。
- 机器语言与汇编语言的映射关系:汇编语言接近机器语言,可以看做是机器语言的另一种形式,计算机在运行时也需要将其变为机器语言的二进制才可运行。
- 机器语言中的操作数与汇编语言的不一致:
a) 分支转移:hello.s使用.L0,.L1转移,而hello.objdump直接使用地址进行跳转。
b) 函数调用:hello.s直接调用函数名,而hello.objdump进行相对寻址,要进行重定位链接后才能确定真正地址。
4.5 本章小结
本章主要介绍了hello的汇编,包括汇编的概念、功能,得到了.o文件,查看了其可重定位目标elf格式 。在此基础上使用objdump进行反汇编,将生成反汇编文件与.s进行对比分析,以获得更深入的理解。
第5章 链接
5.1 链接的概念与作用
(1) 概念
链接是将各种不同文件的代码和数据部分收集并组合成一个单一文件的过程。
(2) 作用
将调用到的外部函数等所在的程序与数据通过ld链接器合并在.o程序中,组合生成1个可执行目标文件。令分离编译成为可能,让程序员能够独立的修改和编译单个文件而不影响整体。
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/9/crtbegin.o hello.o -lc /usr/lib/gcc/x86_64-linux-gnu/9/crtend.o /usr/lib/x86_64-linux-gnu/crtn.o -z relro -o hello
图5-1 链接命令
5.3 可执行目标文件hello的格式
- 命令:readelf -a hello > hello.elf
图5-2 可生成执行目标文件
2. ELF头
ELF头:以16字节序列开始,描述了生成该文件系统的字的大小和字节顺序
图 5-3 ELF文件头内容
3. 节头部表
节头描述了.o文件中出现的各个节的名称、类型、位置、所占空间大小、偏移量及对齐等信息,一共描述了26个节。
图 5-1 节头部表内容
4. 节段映射:
图5-5 节段映射
5. 动态节
图 5-6 动态节
- 重定位节
图5-7 重定位节
- 符号表
图5-8符号表
8. 版本信息
图 5-9 版本信息
5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
图 5-10 虚拟地址空间信息
图5-11 program headers
查看 ELF 格式文件中的 Program Headers,它告诉链接器运行时加载的内容,并提供动态链接的信息。每一个表项提供了各段在虚拟地址空间和物理地址空间的各方面的信息。
表2 hello虚拟空间的相关解释
PHDR: 程序头表
INTERP: 在程序执行前必须调用的解释器
LOAD: 程序目标代码和常量信息
DYNAMIC: 动态链接器所使用的信息
NOTE: 保存辅助信息
GNU_STACK: 使用系统栈所需要的权限信息
GNU_RELRO: 保存在重定位之后只读信息的位置,其余的节的内容是存放在0x00400fff后面。
5.5 链接的重定位过程分析
命令:objdump -d -r hello
图 0-12 hello反汇编代码
- 分析hello与hello.o的不同:
b) 在hello.o中是相对偏移地址,hello为虚拟内存地址。
c) hello的反汇编文件比hello.o的反汇编文件多了_init,.plt,puts@plt,printf@plt,getchar@plt,exit@plt,sleep@plt,_start,_dl_relocate_static_pie,__libc_csu_init,__libc_csu_fini,_fini等节和需要用到的库函数;
d) hello中调用为call+函数名,hello.o中为call+相对偏移地址。
e)
f) hello.o将lea后的操作数置为0,并添加重定位条目。
g) hello增加了外部函数。 - 链接的过程:
链接器将各个目标文件组装在一起,文件中的各个函数段按照一定规则累积在一起。 - 结合hello.o的重定位项目,分析hello中对其怎么重定位的:
链接器在完成符号解析之后,就将每个符号引用和一个符号定义之间关联起来。于是乎,链接器就知道它的输入目标模块中的代码节和数据节的确切大小。进而开始重定位,在该步骤中,首先将合并输入模块,并在此基础上为每个符号分配运行时的地址。在 hello 到 hello.o 中,首先是重定位节和符号定义,链接器将所有输入到 hello 中相同类型的节合并为同一类型的新的聚合节。当这一步完成时,程序中的每条指令和全局变量都有唯一的运行时内存地址了。然后是重定位节中的符号引用,链接器会修改 hello 中的代码节和数据节中对每一个符号的引用,使得他们指向正确的运行地址。
5.6 hello的执行流程
表3 hello执行过程中相应程序名的使用及相关地址
程序名称 程序地址
ld-2.31.so!_dl_start 0x7f9e3faa3000
ld-2.31.so!_dl_init 0x7f9e3faa7630
hello!_start 0x4010f0
libc-2.31.so!__libc_start_main 0x7f9e3fb67ab0
-libc-2.31.so!__cxa_atexit 0x7f9e3fb89430
-libc-2.31.so!__libc_csu_init 0x4005c0
hello!_init 0x401000
libc-2.31.so!_setjmp 0x7f9e3fc884c10
-libc-2.31.so!_sigsetjmp 0x7f9e3fc884b70
–libc-2.31.so!__sigjmp_save 0x7f9e3fc884bd0
hello!main 0x4011d6
hello!puts@plt 0x401030
hello!exit@plt 0x401070
*hello!printf@plt –
*hello!sleep@plt –
*hello!getchar@plt –
ld-2.31.so!_dl_runtime_resolve_xsave 0x7f9e3fc4e680
-ld-2.31.so!_dl_fixup 0x7f9e3fc46df0
–ld-2.31.so!_dl_lookup_symbol_x 0x7f9e3fc420b0
libc-2.31.so!exit 0x7f9e3f889128
5.7 Hello的动态链接分析
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
编译器没有办法预测函数的运行时地址,所以需要添加重定位记录,等待动态链接器处理,为避免运行时修改调用模块的代码段,链接器采用延迟绑定的策略。动态链接器使用过程链接表PLT+全局偏移量表GOT实现函数的动态链接,在GOT中存放函数目标地址,PLT使用GOT中地址跳转到目标函数,在加载时,动态链接器会重定位GOT中的每个条目,使得它包含目标的正确的绝对地址。
.got与.plt节保存着全局偏移量表GOT,其内容从地址0x601000开始。通过edb查看,在dl_init调用前,其内容如下:
图 0-13 hello的ELF格式文件中.got的地址
图 5-14 调用前的情况
在调用后,其内容变为:
图 5-15 调用后的情况
比较可以得知,0x403ff0~0x404000之间的内容,对应着全局偏移量表GOT[1]和GOT[2]的内容发生了变化。GOT[1]保存的是指向已经加载的共享库的链表地址。GOT[2]是动态链接器在ld-linux.so模块中的入口。这样,接下来执行程序的过程中,就可以使用过程链接表PLT和全局偏移量表GOT进行动态链接。
5.8 本章小结
本章主要介绍了hello的链接,包括汇编的概念、功能。并在Ununtu下对hello.o进行链接操作,同时得到ELF格式文件,以便于从过个方面来进行比较。
第6章 hello进程管理
6.1 进程的概念与作用
(1) 概念
一个特定的执行中的程序的实例。
(2) 作用
父进程通过调用fork()函数创建一个新的运行的子程序。其返回值分以下两种情况:若在子进程中,返回0;若在父进程中,返回子进程的PID。
并且新创建的子进程几乎但不完全与父进程相同,子进程得到与父进程虚拟地址空间相同的但是独立的一份副本,子进程获得与父进程任何打开文件描述符相同的副本,最大区别是子进程有不同于父进程的PID
6.2 简述壳Shell-bash的作用与处理流程
(1)作用:
Shell 是一种交互型的应用级程序,它可以代表用户执行程序。它是用户和Linux内核之间的接口程序。你在提示符下输入的每个命令都由shell先解释然后传给Linux内核。
shell 是一个命令语言解释器(command-language interpreter)。拥有自己内建的 shell 命令集。此外,shell也能被系统中其他有效的Linux 实用程序和应用程序(utilities and application programs)所调用。
它负责确保用户在命令提示符后输入的命令被正确执行。其功能包括:
(1) 读取输入并解析命令行
(2) 替换特别字符,比如通配符和历史命令符
(3) 设置管道、重定向和后台处理
(4) 处理信号
(5) 程式执行的相关设置
执行一系列的读/求值步骤然后终止。该步骤读取来自用户的一个命令行。求值步骤读取来自用户的一个命令行,求值步骤解析该命令行,并代表用户执行程序。在解析命令后,如果是内置命令,则解析指令并执行,否则就根据相关文件路径执行可执行目标文件。
1.可以交互,和非交互的使用shell。在交互式模式,shell从键盘接收输入;在非交互式模式,shell从文件中获取输入。
2.可以同步和异步的执行命令。在同步模式,shell要等命令执行完,才能接收下面的输入。在异步模式,命令运行的同时,shell就可接收其它的输入。重定向功能,可以更细致的控制命令的输入输出。另外,shell允许设置命令的运行环境。
3.提供少量的内置命令,以便自身功能更加完备和高效。
4.提供了变量,流程控制,引用和函数等,类似高级语言一样,能编写功能丰富的程序。
5. shell强大的的交互性除了可编程,还体现在作业控制,命令行编辑,历史命令,和别名等方面
(2)处理流程:
命令行是一串 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 :将停止的后台作业更改为正在运行的后台作业。
fg :将停止或运行的后台作业更改为在前台运行。
kill :终止作业。
6.3 Hello的fork进程创建过程
首先,带参执行当前目录下的可执行文件hello,父进程会通过fork函数创建一个新的运行的子进程hello。子进程获取了与父进程的上下文,包括栈、通用寄存器、程序计数器,环境变量和打开的文件相同的一份副本。子进程与父进程的最大区别是有着跟父进程不一样的PID,子进程可以读取父进程打开的任何文件。当子进程运行结束时,父进程如果仍然存在,则执行对子进程的回收,否则就由init进程回收子进程。
6.4 Hello的execve过程
execve函数在当前进程的上下文中加载并运行可执行目标文件hello,且带参数列表argv和环境变量列表envp。只有当出现错误时,例如找不到hello文件,execve才会返回到调用程序。所以与fork一次调用返回两次不同,execve调用一次并从不返回。
6.5 Hello的进程执行
一个进程执行它的控制流的一部分的每一时间段叫做时间片。处理器通常使用一个寄存器提供两种模式的区分,该寄存器描述了进程当前享有的特权,当没有设置模式位时,进程就处于用户模式中,用户模式的进程不允许执行特权指令,也不允许直接引用地址空间中内核区内的代码和数据;设置模式位时,进程处于内核模式,该进程可以执行指令集中的任何命令,并且可以访问系统中的任何内存位置。上下文就是内核重新启动一个被抢占的进程所需要的状态,它由通用寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构等对象的值构成。
图6-1 进程调度
由图可知,hello在前台printf中,由于调用sleep,进入后台内核模式,开始sleep,然后中断信号,再回到前台继续执行下一个操作。
6.6 hello的异常与信号处理
(1) 程序执行中往往会出现4种异常:
- 中断 :原因是来自I/O设备的信号,属于异步信号,以总是返回到下一条指令的状态返回。
- 陷阱:原因是有意的异常,属于同步信号,以总是返回到下一条指令的状态返回
- 故障:原因是潜在可恢复的错误,属于同步信号,以可能返回到当前指令的状态返回
- 终止:原因是不可恢复的错误,属于同步信号,不会返回
(2) 信号处理方式
在软件形式的层面声,存在Linux信号这一软件形式的异常。它允许进程和内核终端的其他进程。信号提供了一种机制,使用信号来进行进程之间传递消息,通知用户进程发生了异常。
表4 信号及说明
编号 信号名称 缺省动作 说明
1 SIGHUP 终止 终止控制终端或进程
2 SIGINT 终止 键盘产生的中断(Ctrl-C)
3 SIGQUIT dump 键盘产生的退出
4 SIGILL dump 非法指令
5 SIGTRAP dump debug中断
6 SIGABRT/SIGIOT dump 异常中止
7 SIGBUS/SIGEMT dump 总线异常/EMT指令
8 SIGFPE dump 浮点运算溢出
9 SIGKILL 终止 强制进程终止
10 SIGUSR1 终止 用户信号,进程可自定义用途
11 SIGSEGV dump 非法内存地址引用
12 SIGUSR2 终止 用户信号,进程可自定义用途
13 SIGPIPE 终止 向某个没有读取的管道中写入数据
14 SIGALRM 终止 时钟中断(闹钟)
15 SIGTERM 终止 进程终止
16 SIGSTKFLT 终止 协处理器栈错误
17 SIGCHLD 忽略 子进程退出或中断
18 SIGCONT 继续 如进程停止状态则开始运行
19 SIGSTOP 停止 停止进程运行
20 SIGSTP 停止 键盘产生的停止
21 SIGTTIN 停止 后台进程请求输入
22 SIGTTOU 停止 后台进程请求输出
23 SIGURG 忽略 socket发生紧急情况
24 SIGXCPU dump CPU时间限制被打破
25 SIGXFSZ dump 文件大小限制被打破
26 SIGVTALRM 终止 虚拟定时时钟
27 SIGPROF 终止 profile timer clock
28 SIGWINCH 忽略 窗口尺寸调整
29 SIGIO/SIGPOLL 终止 I/O可用
30 SIGPWR 终止 电源异常
31 SIGSYS/SYSUNUSED dump 系统调用异常
(3)各命令以及运行执行
正常执行
不停乱摁
回车
Ctrl+C
Ctrl+Z
Ctrl+Z后ps
Ctrl+Z后jobs
Ctrl+Z后pstree
Ctrl+Z后fg
Ctrl+Z后kill ps
6.7本章小结
本章主要介绍了hello的进程,包括进程的概念、功能与执行,shell的作用与基本处理流程,fork进程的创建过程,execve函数的过程。在此基础上对hello异常与信号的关系进行了分析实验。
第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本章小结
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
(以下格式自行编排,编辑时删除)
设备的模型化:文件
设备管理:unix io接口
8.2 简述Unix IO接口及其函数
(以下格式自行编排,编辑时删除)
8.3 printf的实现分析
(以下格式自行编排,编辑时删除)
https://www.cnblogs.com/pianist/p/3315801.html
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
(以下格式自行编排,编辑时删除)
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
结论
用计算机系统的语言,逐条总结hello所经历的过程。
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
- hello的一生
a) 通过预处理,翻译hello.c并引入替换相关代码最后拓展到hello.i文件中。
b) hello.i经过编译得到hello.s汇编代码文件。
c) 汇编器处理得到可重定位目标文件 hello.o。
d) 将hello.o与可重定位目标文件和动态链接库链接成为可执行目标程序hello。
e) shell进程调用fork函数创建子进程。
f) 调用execve函数,启动加载器,映射虚拟内存。
g) CPU为hello分配时间片, hello可以执行自己的控制逻辑流。
h) 高速缓存使用三级cache进行内存访问,并将虚拟地址映射成物理地址。
i) printf调用malloc申请分配动态内存。
j) shell进行回收子进程,并令内核删除这个进程使用到的所有数据。 - 感悟
我常常觉得人生就像是一个被设定好的程序。而所谓天命,就是指冥冥之中我们总是在按照程序代码的道路一步步前进,注定会在那个时间点,在那个地方,拥有那么一段经历,这途中的意外与未曾设想也都不过是命运手里的一步棋罢了。但是正如同程序中会存在bug,我相信人生的意义也绝不只是在命运中循规蹈矩。程序的bug会对程序造成不可估量的损失,但人生的
bug却充满了未知。《易经》中有句话是这么说的:“大道五十,天衍四九,人遁其一。”纵使前路茫茫,但机会本身就是一种难得,更遑论机会之后的百万种可能。哪怕吾生不可达,若能见识着万千风景亦何其有幸!
附件
hello.i 源程序hello.c预处理后产生的文本文件
hello.s hello.i编译后生成的汇编程序
hello.o hello.s汇编后的可重定位目标程序
helloo.elf hello.o文件的ELF格式
hello hello.o文件经过链接生成的可执行目标文件
hello.objdump hello.o反汇编后生成的文件
hello.elf hello文件的ELF格式
hello.objdumpp hello文件的反汇编文件
参考文献
[1] Randal E.Bryant, David R.O’Hallaron. Computer Systems[M]. 2016.7