计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 软件工程
学 号 1183710219
班 级 1837102
学 生 曹书龙
指 导 教 师 史先俊
计算机科学与技术学院
2019年12月
摘 要
本文是Hello这个程序在Linux框架下的P2P(From Program to Process)以及 O2O(From Zero-0 to Zero-0)的整个过程,通过对Hello这个程序的各个过程的观察分析,再结合本门课程在本学期所学的各种知识,不仅复习了知识,还在原来的基础上更进一步地理解这门课的知识;
关键词:hello的一生;预处理;编译;汇编;链接;进程;存储;IO管理;
目 录
第1章 概述 - 5 -
1.1 Hello简介 - 5 -
1.2 环境与工具 - 5 -
1.3 中间结果 - 5 -
1.4 本章小结 - 6 -
第2章 预处理 - 7 -
2.1 预处理的概念与作用 - 7 -
2.2在Ubuntu下预处理的命令 - 7 -
2.3 Hello的预处理结果解析 - 7 -
2.4 本章小结 - 8 -
第3章 编译 - 9 -
3.1 编译的概念与作用 - 10 -
3.2 在Ubuntu下编译的命令 - 10 -
3.3 Hello的编译结果解析 - 10 -
3.4 本章小结 - 14 -
第4章 汇编 - 15 -
4.1 汇编的概念与作用 - 15 -
4.2 在Ubuntu下汇编的命令 - 15 -
4.3 可重定位目标elf格式 - 15 -
4.4 Hello.o的结果解析 - 17 -
4.5 本章小结 - 19 -
第5章 链接 - 20 -
5.1 链接的概念与作用 - 20 -
5.2 在Ubuntu下链接的命令 - 20 -
5.3 可执行目标文件hello的格式 - 20 -
5.4 hello的虚拟地址空间 - 24 -
5.5 链接的重定位过程分析 - 25 -
5.6 hello的执行流程 - 26 -
5.7 Hello的动态链接分析 - 26 -
5.8 本章小结 - 26 -
6.1 进程的概念与作用 - 28 -
6.2 简述壳Shell-bash的作用与处理流程 - 28 -
6.3 Hello的fork进程创建过程 - 28 -
6.4 Hello的execve过程 - 29 -
6.5 Hello的进程执行 - 29 -
6.6 hello的异常与信号处理 - 29 -
6.7本章小结 - 33 -
7.1 hello的存储器地址空间 - 34 -
7.2 Intel逻辑地址到线性地址的变换-段式管理 - 34 -
7.3 Hello的线性地址到物理地址的变换-页式管理 - 34 -
7.4 TLB与四级页表支持下的VA到PA的变换 - 35 -
7.5 三级Cache支持下的物理内存访问 - 36 -
7.6 hello进程fork时的内存映射 - 36 -
7.7 hello进程execve时的内存映射 - 36 -
7.8 缺页故障与缺页中断处理 - 36 -
7.9动态存储分配管理 - 37 -
7.10本章小结 - 38 -
8.1 Linux的IO设备管理方法 - 39 -
8.2 简述Unix IO接口及其函数 - 39 -
8.3 printf的实现分析 - 40 -
8.4 getchar的实现分析 - 40 -
8.5本章小结 - 41 -
结论 - 42 -
参考文献 - 44 -
第1章 概述
1.1 Hello简介
P2P过程:
hello.c文件,经过编译预处理器(cpp)的编译预处理变成hello.i文件,经过ccl的编译变成hello.s文件,as的汇编变成可重定位目标文件hello.o,链接器(ld)的链接(链接了例如printf.o)产生可执行目标文件hello,在shell中键入启动命令,shell为hello进行fork子进程,hello实现了From Program to Process。
O2O过程:
shell调用execve函数,删除当前虚拟地址的用户部分已存在的数据结构,
并为hello的代码、数据、bss和栈区域创建新的区域结构,然后映射共享区域,
设置程序计数器,映射虚拟内存,然后加载物理内存。完成后进入main函数,
执行目标代码,CPU为运行的hello分配时间片执行逻辑控制流。当程序结束,
shell的父进程负责回收hello进程,内核删除相关数据结构,至此,hello的O2O
过程结束
1.2 环境与工具
硬件环境:X64 CPU;2.50GHz;8G RAM;256GHD Disk;
软件环境:Windows10 Ubuntu 19.04 LTS 64位;
开发与调试工具:Windows10;Vmware Pro;Ubuntu19.04;gedit;gcc;edb;readelf等
1.3 中间结果
文件名称 文件作用
hello.i hello.c预处理后产生的文件
hello.o hello.s汇编后产生的可重定位的目标文件
hello.s hello.c预处理后产生的文件
hello.objdump hello.o的反汇编代码文件
hello.elf hello.o的ELF格式文件
hello_2.objdump hello的反汇编文件
hello_2.elf hello的ELF格式文件
hello hello.o经过链接后的可执行目标文件
1.4 本章小结
本章是在全局观念下的概括,对P2P和O2O过程的大致描述,以及介绍自己在这个实验过程中的所用的工具,并对文件进行说明;
第2章 预处理
2.1 预处理的概念与作用
预处理的概念:
预处理就是对C/C++程序进行一个初步的整理,预处理器(CPP)根据以字符#开头的命令,修改原始的C程序。通常以.i作为文件的扩展名
预处理的作用:
将hello.c中的头文件读取到新的.i文件C程序中,并插入到#include处,执行宏定义,处理所有的编译预指令,删除所有的注释等,这些操作都有利于后续的各种例如汇编,编译等操作;
2.2在Ubuntu下预处理的命令
指令:gcc -E hello.c -o hello.i
2.3 Hello的预处理结果解析
经过预处理之后得到的是以.i为扩展名的文件
图2-3-1
图2-3-2
图2-3-1为hello.c文件,显示的是C代码,图2-3-2为hello.i文件,长度很长,截图只是一部分,在图2-3-2中可以看到变量定义#处都会出现对于文件的引用目录例如图2-3-3:
这就是对int的变量定义
图2-3-4
图2-3-4是hello.i文件里的main函数,相比较比hello.c中的main函数,没有多大的变化,所以hello.i文件里多出来的部分就是对#include的部分的扩展,仅缺少的就是hello.c(图2-3-1)中的几个文件注释
2.4 本章小结
本章通过对预处理的分析,了解预处理的概念和作用,复习了预处理部分的知识,并为后续的各种操作奠定了基础;
第3章 编译
3.1 编译的概念与作用
编译的概念:
编译器(CCL)将文本文件hello.i翻译成hello.s,它包含一个汇编语言程序,该程序包含有main函数的定义,并且在.s文件中的每一条语句都以文本格式描述了一条低级机器语言指令;
编译的作用:
编译的过程包含了词法分析、语法分析、语义分析、优化后,生成汇编代码文件这几个过程,通过这几个过程编译后生成的hello.s文件,这期间会优化代码,并且是在代码确认无bug的情况下,hello.s文件里存着汇编文件。
3.2 在Ubuntu下编译的命令
命令:gcc -S hello.c -o hello.s
3.3 Hello的编译结果解析
指示符 含义
.file 源文件
.text 代码段
.section/.rodata rodata节
.align 数据和指令地址的对齐方式
3.3.1文件头部的指令解析
图3-1
3.3.2 数据分析
1.常量
图1-1-1
图1-1-2
显然 printf中的字符串常量存在在.s文件中的格式为string后的;
2.变量与数组
图1-2-1
可以看到程序的三个变量:int argc,int i,int main以及一个数组:char * argv
int argc以及char * argv:
图1-2-2
第一行是argc,第二行是argv,第三行是判断语句;
argc作为传入参数,存放在寄存器%edi中;
Argv是一个存放字符串的二维数组,地址存放在%rsi中,运行之后可以通过%rsp找到argv的字符串的首地址进行argv的调用;
int i:
图1-2-3
i是局部变量,地址为-4(%rbp);
int main:
图1-2-4
主函数mian,存放在.text节,是全局变量的函数;
3.3.2赋值
图3-2-1
赋值操作仅i=0;图3-2-2
3.3.3算术操作
从图3-2-1中得到的算术操作是i++
图3-3,加法用的add代码实现的;
3.3.4关系(大小比较)
在图3-2-1中得到的关系比较为:argc!=4和i<8;
对于argc!=4:
图3-4-1
cmpl语句就是比较语句(代表的是!=),-20(%rbp)中存的是argv,和立即数4进行比较,判断是否执行if语句;
对于i<8:
图3-4-2
cmpl语句在这里是<,判断的循环是否结束;
3.3.5进程转移(控制)
从图3-2-1中的到的是if(argc!=4)和for(i=0;i<8;i++)这两个语句
对于if(argc!=4)这条语句
图3-5-1
程序首先cmpl $4 ,-20(%rbp)来比较立即数4和argc的大小,通过je .L2(会设置标志位ZF)来判断是否跳转,如果argc==4,ZF=0,就不执行接下来的程序,进入L2;
对于for(i=0;i<8;i++)这条语句:
图3-5-2
在for循环中,根据i的值来判断循环结束条件;(for循环体的主体是L4)
程序通过比较立即数7和i,判断是否会退出循环,如果不退出循环,就会跳入L4,继续进行for循环体
3.3.6函数操作
图3-2-1
在图3-2-1中的函数有mian函数,printf函数,exit函数,sleep函数,getchar函数
main函数:
图3-6-1
main函数传递两个参数argc和argv,分别存在于%edi和%rsi中;将返回值%eax设置成0之后返回;
printf函数:
图3-6-2
第一次调用将"用法: Hello 学号 姓名 秒数!\n"的字符串的首地址传递给%rdi,call puts@PLT直接打印字符串;
图3-6-3
第二次调用将"Hello %s %s\n"的首地址传递给%rdi,设置%rsi为argv[1],argv[2]给%rdx,传入了参数,使用printf@PLT;
exit函数:
图3-6-4
将%edi设置为1,使用call exit@PLT;
sleep函数
图3-6-5
getchar函数:
图3-6-6
3.4 本章小结
本章是解析的是我们编写的C代码从人易懂到机器易懂的过程,了解了编译器(CCL)将C语言中各个数据类型以及各类操作转换为汇编语言指令,将hello.i文件编译成hello.s文件,为接下来的汇编过程铺下了基础。
第4章 汇编
4.1 汇编的概念与作用
汇编的概念:
汇编器(as)将hello.s翻译成机器语言指令,把这些指令打包成可重定位目标程序的格式,并将结果存储在hello.o文件中;
汇编的作用:
将hello.s中的汇编代码转换成机器语言指令(二进制,所以直接打开会显示乱码),每个汇编语句对应着一条机器语言指令;
4.2 在Ubuntu下汇编的命令
命令:gcc hello.s -c -o hello.o
4.3 可重定位目标elf格式
通过readelf -a hello.o > hello.elf获得hello.o的ELF格式
1.ELF头
图4-3-1
hello.o的ELF的头以一个16字节的序列开始,ELF头包含了:
数据类型:小端序和补码形式;
目标文件的类型:可重定位的;
系统架构:x86-64;
ELF头的大小:64(bytes);
节头部表中条目的数量(13);
2 节头部表
图4-3-2
描述了不同节的位置与大小,包含了名字、类型、地址、偏移和对齐等信息。每个可装入节的起始地址总是0。
3 .rela.text的重定位信息
图4-3-3
是一个.text节中位置的列表,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。
一般而言,任何调用外部函数或者引用全局变量的指令都需要修改。另一方面,调用本地函数的指令则不需要修改。
在类型中,使用了R_X86_64_PC32和R_X86_64_PLT32两种重定位类型。
4 .rela.eh_frame
图4-3-4
这个是eh_frame节的重定位信息,类型采用的是R_X86_64_PC32
5 .symtab符号表
图4-3-5
存放在程序中定义和引用的函数和全局变量的信息。.symtab符号表不包含局部变量的条目。
每个条目包含了value,size,type,bind,vis,ndx,name信息
4.4 Hello.o的结果解析
objdump指令:
之后gedit hello.objdump
图4-4-1,4-4-2
图4-4-3
图4-4-1和图4-4-2是.s文件里的汇编语言,图4-4-3是机器语言
可以看到这两类语言在格式上差不多,但是对具体的指令的构成却是有不同之处
图4-4-4
图3-4-1
这两个都是对argc和argv 的操作,明显发现,在机器语言中的“20”在汇编语言中是“32”,所以可以知道,在汇编语言中,数是以十进制方式存储,而在机器语言中,数是以十六进制的方式存储;
图4-4-5
图3-4-1
这两个都是if判断语句,但是在机器语言中的je是通过2b
4.5 本章小结
通过对hello.o文件的解析,发现hello.o本身拥有着固定的ELF格式,用以包括各节,如ELF头、节头部表、重定位节、符号表等的基本信息,借此更好的了解了重定位的方式和ELF可重定位目标文件,甚至可以通过一些简单的反汇编代码推出原来的C代码。
第5章 链接
5.1 链接的概念与作用
链接的概念:
链接是由链接器(ld)来执行的,具体就是将各种代码和数据片段收集并组合成一个单一文件的过程,这个文件就是可执行目标文件,在这里就是hello这个文件可被加载到内存并执行。
链接的作用:
链接器使一个大型应用程序可以分解为更小的模块,我们可以独立修改和编译小模块,当我们改变这些模块中的一个时,只需简单执行命令,重新链接使用,这样极大的方便了程序的执行;
5.2 在Ubuntu下链接的命令
链接命令:
图5-2
5.3 可执行目标文件hello的格式
通过readelf -a hello> hello_2.elf获得hello的ELF格式
图5-3-1
1.ELF头
图5-3-2
2.段头表(节头)
图5-3-3
图5-3-3(续)
3.程序头
图5-3-4
4.段节
图5-3-5
5.动态节
图5-3-6
6.重定位节
图5-3-7
7. .dynsym符号表
图5-3-8
8.符号表
图5-3-9和图5-3-9(续)
5.4 hello的虚拟地址空间
图5-4-1
图5-4-2
图5-3-4
图5-4-1和图5-4–2是在Data Dump里观察到的hello程序就是hello的虚拟地址空间
程序被加载到0x401000–0x401ff0段;
图5-3-4的程序头
可执行文件的连续的片被映射到内存段。
程序头部表描述了这种映射关系。程序头部表有如下信息:
Offset:目标文件中的偏移
VirAddr: 内存地址
PhysAddr:物理地址
FileSize: 目标文件段大小
MemSiz: 内存中的段大小
Flags: 运行时访问权限
Align:对齐要求
可以知道 PHDR(保存程序头表),起始地址0x401000偏移0x40字节
同理 INTERP:起始地址0x401000偏移0x230字节处
5.5 链接的重定位过程分析
命令:
对比分析:
图5-5-1(hello.o)
图5-5-2(hello)
两图对比,hello.o是.text节开始;hello是.init节开始
图5-5-3
hello有许多外部函数,这在hello.o中是没有的,而且函数前面的一串数字是该函数的虚拟内存地址,虚拟内存地址是作为跳转jump所需要的的;
链接过程:
链接器(ld)执行时,需要动态链接器,例如初始化函数_init,_start调用hello.c中的main函数,还需要动态链接共享库,其中定义了hello.c中用到的printf,getchar等函数;
hello的重定位的实现由两步组成:
1.重定位节和符号定义:
来自所有的输入模块的.data节被全部合并成一个节,这个节成为hello的.data节然后,链接器将运行时内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每一个符号。此时,程序中的每条指令和全局变量都有唯一的运行时内存地址了。
2.重定位节中的符号引用:
链接器会修改hello中的代码节和数据节中对每一个符号的引用,使得他们指向正确的运行地址。
5.6 hello的执行流程
打开loaded Symbols即可找到:
名称:
hello!_start; 0x401090;
ld-2.29.so!_libc_start_main; 0x7fcbe2c7ca80
libc-2.29.so!_cxa_atexit 0x7fcbe2c9d740
hello!_libc_csu_init 0x401150
libc-2.29.so!_setjmp 0x7fcbe2c99b90
libc-2.29.so!_sigsetjmp 0x7fcbe2c99af0
hello_main : 0x4010c1
hello!puts@plt 0x401030
hello!exit@plt 0x401070
hello!printf@plt 0x401040
hello!sleep@plt 0x401080
hello!atoi@plt 0x401060
hello!getchar@plt 0x401050
libc-2.29.so!exit 0x7fcbe2c9d3c0
5.7 Hello的动态链接分析
这个没有调试出来;没有找到dl_init
5.8 本章小结
本章通过对链接过程的复习,链接就是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可以被加载到内存中执行,了解了链接的重定位,动态链接过程,还了解了hello 的ELF格式;
第6章 hello进程管理
6.1 进程的概念与作用
进程的概念:进程的经典定义就是一个执行中程序的实例。
进程的作用:
1.一个独立的逻辑控制流:提供一个假象,好像我们的程序独占的使用处理器;
2.一个私有的地址空间:提供一个假象,好像我们的程序独占的使用内存系统;
6.2 简述壳Shell-bash的作用与处理流程
作用;读取来意用户的命令行,将用户输入的命令解析,再给系统执行
处理流程:
1.读取命令行参数;
2.是内置的话就马上解释(执行)该命令
3.命令是一个可执行性文件的话,就会fork一个新的子进程,并在新的子进程的上下文加载运行这个文件;
4.进程结束后,shell负责收回这个子进程;
6.3 Hello的fork进程创建过程
在shell上输入命令行./hello 1183710219 曹书龙,这是一个可执行性文件,就会调用fork函数创建一个子进程,新的子进程几乎但不完全与父进程相同,子进程有着和父进程虚拟地址相同但是相互独立的一份副本,包括代码段,数据段,堆,共享库以及用户栈。
图6-3
6.4 Hello的execve过程
execve函数加载并运行可执行目标文件filname,且带参数列表argv和环境变量列表envp;只有当出现错误例如:找不到filname,execve才会返回调用程序;
图6-4
6.5 Hello的进程执行
结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。
hello一开始的时候是运行在用户模式,在hello调用sleep函数之后进入内核模式,并将hello进程从运行队列移入等待队列,计时器开始计时,内核此时进行上下文切换将当前进程,当计时器到时间的时候(根据命令行输入argv[3])发送中断信号,此时进入内核状态执行中断处理,将hello进程从等待队列中重新加入到运行的队列,成为就绪状态之后,hello进程就可以继续自己的控制流了;
图6-5
6.6 hello的异常与信号处理
程序运行的过程中会出现四种异常中断,陷阱,故障,终止。
中断:异步发生的,是来自处理器外部的I/O设备的信号的结果。硬件中断的异常处理程序被称为中断处理程序,当中断处理程序返回时,他就将控制返回给下一条指令,最后程序继续执行,就好像没有发生中断;
陷阱:有意的异常,是执行一条指令的结果。就像中断处理程序一样,陷阱处理程序将控制返回到下一条指令。陷阱最重要的用途是在用户程序和内核之间提供一个像过程一样的接口,叫做系统调用。
故障:由错误情况引起,它可能能够被故障处理程序修正。当故障发生时,处理器将控制转移给故障处理程序。如果处理程序能够修正这个错误情况,它就将控制返回到引起故障的指令,从而重新执行它。否则处理程序返回到内核中的abort例程,abort例程会终止引起故障的应用程序。
终止:不可恢复的致命错误造成的结果,通常是一些硬件错误;。处理程序将控制返回给一个abort例程,该例程会终止这个应用程序。
乱按的过程没有回车,这个时候就只把输入屏幕的字符串缓存起来,如果最后输入了回车,getchar会把回车读入,并把回车之前的字符串作为输入
图6-6-1
输入Ctrl-C,会给进程发送SIGINT信号到前台进程组的每个进程,终止前台进程:
与6-6-2
Ctrl-Z和Ctrl-C的作用类似:
图6-6-3
ps jobs pstree fg kill
PS:
图6-6-4
Jobs:
图6-6-5
Fg:
图6-6-6
Pstree:
图6-6-7
图6-6-7(续1)
图6-6-7(续2)
图6-6-7(续3)
Kill;
6.7本章小结
本章在进程角度解析了hello程序的进程,了解了fork函数和execve函数的作用过程,了解了hello执行过程的上下文切换,以及可能引发的异常处理;
第7章 hello的存储管理
7.1 hello的存储器地址空间
结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。
逻辑地址:是指由程序产生的与段相关的偏移地址部分,就是hello.o中的相对偏移地址
线性地址:由逻辑地址经过段机制转化成,其地址空间是连续的非负整数的地址,是hello中的虚拟内存地址;
虚拟地址:和线性地址类似,在hello程序中也是虚拟内存地址
物理地址::程序运行时加载到内存地址寄存器中的地址,是内存单元的真正地址,在hello中指的是虚拟内存地址对应的物理地址;
7.2 Intel逻辑地址到线性地址的变换-段式管理
一个逻辑地址是由段选择符和偏移地址组成的。段选择符存放在段寄存器中。
一个完整的 48 位逻辑地址包含16位的段选择符和32位的段内偏移地址;
首先关注的是段选择符的TL=0 or1 对于0要转换 GDT 中的段,是1转换 LDT中的段,再根据相应寄存器,得到其地址和大小,这样我们得到一个数组。
然后拿出段选择符中前13位,可以在这个数组中,查找到对应的段描述符,这样,就得到了32位段基地址(Base)。
最后把32位段基地址(Base)和32位段内偏移量相加,就是要转换的线性地址了。
附上段选择符的结构:
TL
一共有16位,前13位是索引,后面紧跟1位TL,最后两位是RPL
7.3 Hello的线性地址到物理地址的变换-页式管理
(以下格式自行编排,编辑时删除)
计算机利用页表,通过MMU来完成从线性地址(虚拟地址)到物理地址的转换。
线性地址的结构:
图7-3
有二级管理模式,页目录中的项是页表的地址,页表中是页的地址,;
转换过程:
从cr3中取出进程的页目录地址;之后根据线性地址的前10位(Directory)找到索引项,再根据线性地址的中间10位(Table),找到页的起始地址;页的起始地址与线性地址中的后12位(offset)相加,得到物理地址;
7.4 TLB与四级页表支持下的VA到PA的变换
VPO一排是虚拟地址 PPO一排是物理地址
图7-4
虚拟地址的36位VPN被划分成四个9位的VPN1~VPN4,每个片作为一个页表的偏移量,cr3中有L1页表的物理地址,VPN1提供给L1偏移量,那么PTE中就包含了L2页表的基地址,再传入L2中,此时VPN2提供偏移量,这样一直到最后,就完成了VA到PA;
7.5 三级Cache支持下的物理内存访问
先在第一级缓存中寻找要找的数据,利用TLBI找到TLB中对应的组,再比较TLBT,若相同且有效位为1,那么就找到了数据;
若是第一级缓存不满足,则就去第二级缓存中寻找,若是找到了数据就将其缓存到第一级数据中,若有空闲块,则放置在空闲块中,否则根据替换策略选择牺牲块,
若是第二级缓存中还是没有找到满足的数据,就去第三级缓存中寻找数据,找到后就缓存到第一,二级缓存中,若是有空闲块,就放置在空闲块中,没有的话,就根据替换策略选择牺牲块,
若是第三级缓存中还是没有找到,就去第四级缓存中寻找,找到后需要缓存在第一,二,三级,若有空闲块,则放置在空闲块中,否则根据替换策略选择牺牲块。
7.6 hello进程fork时的内存映射
当fork 函数被shell调用时,内核会为hello进程创建各种数据结构,并分配给它一个唯一的PID 。
为了给hello进程创建虚拟内存,内核会创建hello进程的mm_struct 、区域结构和页表的原样副本。内核将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。
当fork 在hello进程中返回时,hello进程现在的虚拟内存刚好和调用fork 时存在的虚拟内存相同。
7.7 hello进程execve时的内存映射
当execve函数在shell中加载并运行hello时,需要经过几个步骤:
1.删除已存在的用户区域;
2.映射私有区域,为hello的代码,数据,.bss和栈区域常见新的区域结构;
3.映射共享区域,将hello进程与共享对象进行连接,共享对象由动态链接映射到共享区域
4.设置程序计数器(PC),指向代码区域的入口点
7.8 缺页故障与缺页中断处理
缺页故障:引用的虚拟内存中的字,不在物理内存中,DRAM缓存不命中就是缺页;
缺页中断处理:
页面不命中就会出现缺页异常,这是需要缺页异常处理程序选择一个牺牲页,缺页处理程序调入新的页面,并更新内存中的PTE,然后到返回原来的进程,再次缺页指令;
图7-8
7.9动态存储分配管理
动态内存分配器维护的是一个进程的虚拟内存区域,称为堆,这个堆紧接在未初始化的数据区域后开始,并向上生长,对于每个进程,内核维护这一个变量brk,指向堆的顶部,堆被看成一组大小不同的块的集合,每个块是一个连续的虚拟内存片,已分配和空闲的两种;
基本方法:动态内存分配器有两种,分别是显示分配器和隐式分配器,
显示分配器:要求应用显式地释放任何已分配释放任何已经分配的块,例如C程序中的malloc和free
隐式分配器:要求分配器检测一个已经被分配的块什么时候不再被程序使用,那么就释放这个块,隐式分配器也叫做垃圾收集器;
管理策略:
对于显式分配器,必须在一些相当严格的约束条件下进行,包括有处理任意请求序列,立即响应请求,只使用堆,对齐块,不修改已经分配的块;
为了分配一个块,必须确定请求的大小类,并且对适当的空闲链表做首次适配,查找到了一个合适的块,如果找到了一个,那么就分割它,并将剩余的部分插入到适当的空闲链表;
7.10本章小结
本章通过对hello在储存结构,高速缓存,虚拟内存涉及到的方面进行了分析,最后了解了Linux是如何管理虚拟内存的,阐述了逻辑地址和线性地址以及线性地址和物理地址的转换;
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件
文件是C语言和Linux管理的思想,所有的IO设备都被抽象成为文件,所有的输入输出都作为对文件的操作。
设备管理:unix io接口
这种设备映射为文件的方式,允许Linux内核引出一个简单,低级的应用接口,成为UNIX I/O,这使得输入和输出都能以一种同意且一致的方式来执行
8.2 简述Unix IO接口及其函数
Unix I/O接口及其函数:
1.打开文件:一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个I/O设备,会返回一个叫做描述符的小的非负整数,应用程序只需要记住这个描述符。
其对应的函数为int open(char* filename,int flags,mode_t mode),进程通过调用open函数来打开一个存在的文件或者创建一个新文件,open函数会将filename转换成描述符,并且返回这个描述符;
2. Linux shell 创建的每个进程开始的时候都有三个打开的文件:标准输入,标准输出和标准错误;
3.改变当前的文件位置:对于每个打开的文件,内核保持着一个文件位置k, 初始为0。这个文件位置是从文件开头起始的字节偏移量。应用程序能够通过执行seek 操作,显式地设置文件的当前位置为k;
4.读文件:读操作就是从文件里面赋值n个字节到内存,从当前文件位置k开始,然后将k增加到k+n;
其对应的函数是ssize_t read (int fd,void* buf,size_t n);read函数从描述符为fd的当前文件位置赋值最多n个字节到内存位置buf,返回值-1表示一个错误,0表示EOF,否则返回值表示的是实际传送的字节数量;
5.写文件:写操作就是从内存赋值n>0个字节到一个文件,从当前文件位置k开始,然后更新k;
其对应的函数是ssize_t write(int fd,const void *buf,size_t n):write函数从内存位置buf复制到至多n个字节到描述符为fd的当前文件位置;
8.3 printf的实现分析
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
异步异常-键盘中断的处理:当用户按键之后,会餐生一个SIGINT信号,键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。回车字符也会放在缓冲区中。
当用户键盘输入回车只有,getchar才开始从stdio六中每次读入一个字符。getchar函数的返回值是用户输入的字符的ascii码值,若是文件结尾是EOF,则返回-1,且将用户输入的字符显示到屏幕;若是在按回车之前输入了不止一个字符,其他字符会保留在键盘韩村区,等到后续getchar函数的读取调用,即后续的getchar调用不会等待用户按键,会直接读取缓冲区中的字符,直到读取完成之后才会等待用户按键;
8.5本章小结
本章内容为I/O管理,所有的I/O设备都被模型化为文件,分析了Unix I/O接口及函数,分析了printf函数和getchar函数的实现;
结论
hello程序先被我们程序员用C代码的信息敲到文本文件里面,之后经过编译预处理,hello.c文件转换成为了hello.i文件,在这个过程中,删除了注释并展开了#include,接下来编译过程中,.i文件转换成了.s文件,变成了汇编语言,之后汇编语言变成了机器语言并存储在hello.o中,最后经过链接可重定位的目标文件和动态链接库生成了hello这个可执行文件;
生成完执行文件就好像一个受精卵的形成,这之后还需要发育,在这里我近似类比成在shell中加载运行,调用execve函数启动加载器,映射虚拟内存,这一步类似母体提供受精卵发育所必须的环境空间,载入物理内存之后,进入main函数,开始执行我们的命令,这个时候这个执行文件就成熟了,就好比婴儿的出生,面貌第一次展示在了人们也就是程序员的面前;
写完这个大作业花费了一天半的时间,但还是有地方没有做好,在链接部分的遗憾还是没能找到,看来我的学习和实践还是不够充分,以后要更加努力了!
附件
文件名称 文件作用
hello.i hello.c预处理后产生的文件
hello.o hello.s汇编后产生的可重定位的目标文件
hello.s hello.c预处理后产生的文件
hello.objdump hello.o的反汇编代码文件
hello.elf hello.o的ELF格式文件
hello_2.objdump hello的反汇编文件
hello_2.elf hello的ELF格式文件
hello hello.o经过链接后的可执行目标文件
参考文献
[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.