第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 -
P2P展开是from program to process,对源程序进行预处理,汇编,链接等一系列操作,生成可执行文件,执行该文件时,OS为该文件fork产生进程(process)。
020展开是from zero to zero,程序开始执行后,OS将其映射到虚拟内存,执行目标代码,然后mmap分配时间片,最终在硬件上实现,实现后由内核使内存等恢复初始状态。
hello.c:源文件
hello.i:预处理输出文件
hello.s:编译输出文件
hello.o:汇编可重定位目标程序
hello:链接后的可执行目标文件
hello1.txt:hello.o的反汇编文件
hello2.txt:hello的反汇编文件
本章介绍了hello中的P2P和020,环境和hello从无到有的中间生成的文件
概念:当对一个源程序进行编译,系统先会进行预处理,然后才进入编译过程,一般预处理hello.c生成hello.i文件。
作用:宏替换,将宏替换成文本,加载头文件,处理条件编译
hello.c源程序
hello.i文件
经过对比,我们很容易发现,源程序从短短23行变成了那么多行,除了预处理指令,都放在最后,也就是说前面那些行都是#include
本章介绍了什么是预处理,预处理的作用,还用实例展示了生成.i文件的过程。
编译是利用编译程序从源语言编写的源程序产生目标程序的过程,即计算机可识别的二进制语言,其过程分为五部分:词法分析,语法分析,语义检查和中间代码生成,代码优化,目标代码生成。
作用:进行词法分析和语法分析,分析过程中如果发现错误会报错,将hello.i翻译成hello.s
3.31数据:
1.常量
这里两个常量,LC0,LC1,对应“用法:Hello 学号 姓名 秒数!\n”和“Hello %s %s\n”,这里的数字编码是汉字UTF-8的编码。
2.变量
这里是将立即数传给ebx寄存器,即对i进行赋值。
3.32赋值
这里是将立即数传给ebx寄存器,即对i进行赋值。
3.33数组与指针
这里rcx,rdx分别对应argv[1],argv[2]的地址,rbp是*argv[]的地址
3.34算数操作
将左侧立即数加到ebx上,即ebx加1。
3.35函数操作
call对函数的调用,直接跳转到sleep函数所在地址
ret指令返回调用前位置
本章介绍了将hello.i文件转化成hello.s的过程,将hello.c中的变量,数据,操作转化成了汇编语言,通过阅读汇编语言可以对应到c语言的变量,数据和操作。
汇编器将.s文件翻译成二进制机器语言指令,把这些指令打包成可重定位目标程序的格式,并将结果保存到.o文件中.
4.31 ELF头
ELF头以一个16字节的序列开始,这个序列描述了生成该文件的字的大小,字节顺序。ELF头剩下的部分包含帮助链接器语法分析和解释目标文件的信息。其中包括ELF头的大小、目标文件的类型、机器类型、节头部表的文件偏移,节头部表中条目的大小和数量。
4.32可重定位相关
当目标文件进行链接时,链接器将所有相同类型的节合并成同一类型的新的聚合节。在这一步中,链接器修改代码节和数据节中对每个符号的引用,使得它们指向正确的运行时地址。执行这一步,需要依赖于可重定位目标模块中称为重定位条目的数据结构。而代码的重定位条目放在.rel.text中,已初始化数据的重定位条目放在.rel.data中。
而.rel.text节,我们能看到puts,exit,sleep等函数,这些都是需要重定位的函数。
每个可重复定位目标模块m都有一个符号表,他包含m的定义和引用的符号信息,在链接器上下文有三种不同符号:由m定义并能被其他模块引用的全局符号,由其他模块定义并被模块m引用的全局符号,和只被模块m定义的引用的局部符号。
.symtab中的符号表不包含对应于本地非静态程序变量的任何符号,他们在运行时在栈中被管理。
与hello.s相比,反汇编代码前有与之对应的指令,一般来说,指令有操作数、目的寄存器、源寄存器、立即数等几个部分,这些部分与汇编语句的种类、源操作数、目的操作数、立即数相对应。反汇编以16进制表示数字,而hello.s以10进制表示;.o反汇编call后跟着下一条指令的地址,而.s中call后跟着函数的名字;.o中字符串常量以立即数0表示,而.s以.LC1表示。
本章主要介绍了汇编的概念和作用,可重定位目标elf格式与其各节详细信息,还介绍了反汇编代码和hello.s的区别。
链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可以被加载到内存并执行,链接可执行于编译,加载,运行。
链接使得分离编译成为可能,可将一大型的应用程序分解为更小的模块,可独立修改编译这些模块,当我们改变其中一个时,只需要简单的重新链接应用,不用重新编译其他文件。
ELF头:
与hello.o的ELF头类似,ELF头描述文件的总体格式。
节头部表:
描述目标文件的节,同时给出各节的类型,地址,偏移量等信息。
程序头:
可执行文件的连续的片被映射到了连续的内存段,而程序头就描述了这种映射关系。
Section to Segment mapping:
Dynamic section at offset 0xe50 contains 21 entries:
重定位节:
符号表:
版本信息:
PHDR:保存程序头表,起始地址0x400000偏移0x40字节处,大小为0x2a0字节
INTERP:指定程序从可执行文件映射到内存,后必须调用的解释器,位于内存起始位置0x400000偏移0x200字节处,大小为0x1c个字节,记录了ELF的位置:/lib64/ld-linux-x86-64.so.2
不同:多出了一些函数,多了.plt节和.init节等,地址从相对地址变成了虚拟地址,call指令,jmp指令等添加了正确地址:
分析:
符号解析中,链接器会将每个引用与他输入的可重定位目标文件的符号表中的一个确定的符号定义关联起来,当遇到一个不是当前模块定义的符号,会先生成一个链接器符号条目,在其他模块溯源,如果在任何输入模块都找不到就报错。之后就可以开始重定位,链接器将所有相同类型的节合并为同一类型的新的聚合节,链接器将运行时的内存地址赋给新的聚合节,赋给输入模块定义的每个节以及输入模块定义的每个符号,这样每条指令和全局变量就有唯一的运行时内存地址了。
_start 0x4005c0
_libc_start_main 0x7ffff7a03a56
main 0x4005f2
printf 0x400560
exit 0x4005a0
现代系统使用可以加载而无需重定位的代码,可加载到内存任何位置而无需链接器修改,这样多个进程可以共享一个共享模块的单一副本。
数据段与代码段相对距离不变,全局变量PIC引用使用了全局偏移量表。
在调用共享库函数时,应该引用生成一条重定位记录,然后动态链接器在程序加载时再分析他,链接器采用延迟绑定以避免运行时修改调用模块的代码,动态链接器使用过程链接表和全局偏移量表GOT实现函数的动态链接,GOT中存放函数目标地址,PLT使用got中地址跳转到目标函数
.got.plt节
_init前
_init后
本节介绍了链接的概念和作用,描述了readelf看hello文件各节内容,以及hello动态链接的过程。
进程是正在运行的程序的实例,是一个具有一定独立功能的程序关于某个数据集合的一次运行活动,他是操作系统动态执行的基本单元,在传统的操作系统,进程是基本的分配单元,也是基本的执行单元。
给与一个程序在系统中唯一运行的假象,程序好像能够独占使用处理器与内存。
作用:shell是一个交互型应用级程序,代表用户运行其他程序,是信号处理的代表,代表各进程创建与程序加载运行及前后台控制,作业调用,信号发送管理等。
处理流程:shell读取命令,如果是内置命令就直接执行,如果不是,则创建子进程,加载命令程序运行,并根据用户的需求在前台或后台运行。在前台,则将控制权递给程序,等待程序的结束;在后台,则shell仍能继续输入与执行命令。当子程序终止后,将由shell回收子进程。
int fork(void),子进程返回0,父进程返回子进程的PID,新建的子进程几乎与父进程相同,但不是完全相同,子进程得到与父进程虚拟地址空间相同的一份副本,以及与父进程任何打开文件描述符相同的副本,但有不同于父进程的PID。当shell加载hello时,会先判断他是否是内置命令,不是则认为命令是一个可执行文件,于是shell创建一个子进程并调用execve()加载程序。
intexecve(char*filename,char* argv[],char* envp[])
在当前进程中载入并运行程序:loader加载器函数,filename:可执行文件,目标文件或脚本;argv:参数列表:argv[0]==filename;envp:环境变量列表。加载器删除子进程现有的虚拟内存段,并创建一组新的代码、数据、堆和栈段。新的栈和堆段被初始化为零,通过将虚拟地址空间中的页映射到可执行文件的页大小的片,新的代码和数据段被初始化为可执行文件中的内容。最后加载器设置PC指向_start地址,_start最终调用hello中的main函数。除了一些头部信息,在加载过程中没有任何从磁盘到内存的数据复制。直到CPU引用一个被映射的虚拟页时才会进行复制,这时,操作系统利用它的页面调度机制自动将页面从磁盘传送到内存。
进程管理由操作系统负责。在决定进行调度后,进入内核态,保存当前进程的上下文,并装载目标进程的上下文。完成这些工作之后,操作系统将程序控制交给目标进程,并恢复为用户态。
系统可以自主决定何时切换。
中途按下ctrl+c
正常运行
中途按下ctrl +z
ps
jobs
pstree
fg
kill
本章介绍了进程的概念,shell,以及上下切换的过程与异常与信号的处理。
先找到基地址,再加上偏移量,得到线性地址Base+Offset.
线性地址分为VPN和VPO,在页表中查找PPN,得到之后拼接上VPO,即得到物理地址。
将VPN拆为四个部分,先进入L1 PT,得到L2 PT的地址;再进入L2 PT……最后进入页表,得到PPN。拼接上VPO即为物理地址。
先将物理地址拆分成标记、索引、偏移量,然后在L1 cache内部寻找,未能找到则去L2、L3. 若都不能找到,则在内存寻找。过程中可能会发生块的替换。
当fork 函数被shell调用时,内核为hello进程创建各种数据结构,并分配给它一个唯一的PID 。为了给hello进程创建虚拟内存,它创建了hello进程的mm_struct 、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。当fork 在hello进程中返回时,hello进程现在的虚拟内存刚好和调用fork 时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,因此,也就为每个进程保持了私有地址空间的抽象概念。
execve函数在shell中加载并运行包含在可执行文件hello中的程序,用hello程序有效地替代了当前程序。首先删除已存在的用户区域,也就是将shell与hello都有的区域结构删除。然后映射私有区域,即为新程序的代码、数据、bss和栈区域创建新的区域结构,均为私有的、写时复制的。下一步是映射共享区域,将一些动态链接库映射到hello的虚拟地址空间,最后设置程序计数器,使之指向hello程序的代码入口。
先判断地址是否合法,如果不合法则触发故障;检查权限,若无权限则触发段错误;如果是正常缺页,则触发缺页异常,牺牲一个页面,换入新的页面,更新页表。
动态内存分配器维护着一个进程的虚拟内存区域,称为堆。分配器将堆视为 一组不同大小的块来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块用来分配。空闲块保持空闲,直到显式地被分配。一个已分配的块保持已分配状态,直到被释放。这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。
分配器有自身的分配方案,例如利用显式空闲链表。
程序调用malloc时,分配器为之分配一片空间;调用free时释放这部分空间。
虚拟内存提供一种抽象,使得程序好像独占整个内存空间,使得程序的内存管理变得简单。
设备的模型化:文件
设备管理:unix io接口
所有的I/ O 设备(例如网络、磁盘和终端)都被模型化为文件,而所有的输入和输出都被当作对相应文件的读和写来执行。
1.打开文件:一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个I/O设备。内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件。内核记录有关这个打开文件的所有信息。应用程序只需记住这个描述符。
2.Linux shell创建的每个进程开始时都有三个打开的文件:标准输入、标准输出和标准错误。
3.改变当前的文件位置:对于每个打开的文件,内核保持着一个文件位置k,初始为0.这个文件位置是从文件开头起始的字节偏移量。应用程序能够通过执行seek操作,显式地设置文件的当前位置为k。
4.读写文件:一个读操作就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增加到k+n。给定一个大小为m字节的文件,当k>=m时执行读操作会触发一个称为end-of-file(EOF)的条件,应用程序能检测到这个条件。在文件结尾处并没有明确的“EOF符号”。类似地,写操作就是从内存复制n>0个字节到一个文件,从当前文件位置k开始,然后更新k。
5.关闭文件:当应用完成了对文件的访问之后,它就通 知内核关闭这个文件。作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。无论一个进程因为何种原因终止时,内核都会关闭所有打开的文件并释放它们的内存资源。
Unix I/O函数:
int open(char* filename,int flags,mode_t mode) :进程通过调用open函数来打开一个存在的文件或是创建一个新文件的。open函数将filename转换为一个文件描述符,并且返回描述符数字,返回的描述符总是在进程中当前没有打开的最小描述符,flags参数指明了进程打算如何访问这个文件,mode参数指定了新文件的访问权限位。
int close(fd):fd是需要关闭的文件的描述符,close返回操作结果。
ssize_t read(int fd,void *buf,size_t n):read函数从描述符为fd的当前文件位置赋值最多n个字节到内存位置buf。返回值-1表示一个错误,0表示EOF,否则返回值表示的是实际传送的字节数量。
ssize_t wirte(int fd,const void *buf,size_t n):write函数从内存位置buf复制至多n个字节到描述符为fd的当前文件位置。
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;
}
从第一个参数取得格式串,用其他的参数来填充。
处理完成之后,调用write函数:设置陷阱INT 80,产生系统中断,由系统将字符串输出。
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
unix I/O给出了一个非常方便的方法来读写文件。本章分析了printf和getchar