程序人生-Hello’s P2P

程序人生-Hello’s P2P

摘要
本文通过对hello.c文件在linux系统下的生命周期的分析,简单地介绍计算机的整个运行过程。结合GCC、objdump等工具,本文首先介绍hello.c的预处理、编译、汇编、链接过程,然后介绍可执行文件hello的加载、运行、IO设备交互、异常处理。
关键词:
程序;生命周期;预处理;编译;汇编;链接;进程管理;存储管理;IO管理;P2P;O2O;

第一章 概述

1.1 Hello简介

P2P:在linux中,hello.c经过cpp的预处理、ccl的编译、as的汇编、ld的链接最终成为可执行目标程序hello,在shell中键入启动命令后,shell为其fork,产生子进程
020:之后shell为其execve,映射虚拟内存,进入程序入口后程序开始载入物理内存,然后进入 main函数执行目标代码,CPU为运行的hello分配时间片执行逻辑控制流。当程序运行结束后,shell父进程负责回收hello进程,内核删除相关数据结构

1.2环境与工具

(1)硬件环境:X64CPU;8GHz;8GRAM;1TB HD
(2)软件环境:Windows10 64位;VirtualBox;Ubuntu 16.04 LTS 64位
(3)使用工具:Codeblocks;Objdump;Gdb;Hex Editor Neo

1.3中间结果

hello.i hello.c预处理的文本程序
hello.s hello.i编译的汇编文本程序
hello.o hello.s汇编的二进制文件
hello hello.o链接的可执行二进制文件
hello_o.s hello使用objdump反汇编后的汇编文本程序
hello.elf hello的elf表

1.4本章小结

这一章主要了解了p2p和020的概念,并记录了个人软硬件的基本信息,中间文件产物

第二章 预处理

2.1预处理的概念与作用

**预处理的概念:**程序设计领域中,预处理一般是指在程序源代码被翻译为目标代码的过程中,生成二进制代码之前的过程。预处理中会展开以#起始的行,试图解释为预处理指令(preprocessing directive),其中ISO C/C++要求支持的包括#if/#ifdef/#ifndef/#else/#elif/#endif(条件编译)、#define(宏定义)、#include(源文件包含)、#line(行控制)、#error(错误指令)、#pragma(和实现相关的杂注)以及单独的#(空指令)。预处理指令一般被用来使源代码在不同的执行环境中被方便的修改或者编译
在这里插入图片描述
**预处理的作用:**预处理会从系统的头文件包中将这三个头文件插入到hello.c的文本中生成hello.i文件。在编译代码的第一时间,就把其设定的标识符,全部一一替代,成为了中间码后,再进行正式的编译工作,便于编译。

2.2在Ubuntu下预处理的命令

gcc -E -o hello.i hello.c
在这里插入图片描述

2.3 Hello的预处理结果解析

在这里插入图片描述
我们发现main函数的内容没有变化,变化的是之前的#变为了三个头文件的展开,变为了3000多行

2.4 本章小结

本章主要了解了预处理的定义与作用、并结合预处理之后的程序对预处理结果进行了解析。

第三章 编译

3.1 编译的概念与作用

**编译的概念:**广义的编译是说将某一种程序设计语言写的程序翻译成等价的另一种语言。在这里指的是把高级语言文本程序翻译成等价的汇编语言文本程序。是把高级语言转化为机器二进制代码的必经之路。
**编译的作用:**编译是把高级语言转化为机器二进制代码的必经之路。它把高级语言翻译成更接近机器语言的汇编语言,使生成过程更加方便顺畅。

3.2 在Ubuntu下编译的命令

gcc -S -o hello.s hello.i
在这里插入图片描述
新增了.s文件

3.3 Hello的编译结果解析

3.3.1函数

main函数被解析为全局符号,字符串常量被存储在数据区
在这里插入图片描述

3.3.2赋值

i=0 : 整形数据的赋值通过mov指令,寄存器来赋值
在这里插入图片描述

3.3.3类型转换

sleep函数参数为int类型,将字符串型的argv用call atoi转换为int
在这里插入图片描述

3.3.4关系操作符及控制语句

C代码中出现了如下!=的关系运算符
在这里插入图片描述
编译器转换为如下
在这里插入图片描述
在汇编语言中就是cmpl的-20(%rbp)与立即数4进行比较,可以看到je指令。cmpl与je是放在一起的,如果两数相等je条件成立,跳转.L2也就是后面的for循环,否则跳过je继续向下执行。

3.3.5四则运算及其复合

(1)加: x=x+y汇编语言是addq y, x
(2)减: x=x-y 汇编语言是subq y, x
(3)乘: x=xy 汇编语言是imulq y, x
(4)除: z=x/y 汇编语言是movq x, z
cqto
idivq y
在这里插入图片描述
如图,这里的addl就是每次循环将i的值加1

3.3.6数组,指针及结构体

在这里插入图片描述
如图,从上至下依次为为传递argv[2],argv[1],和argv[3],通过相对偏移来访问

3.3.7处理函数

(1)函数的调用与传参:给函数传参需要先设定寄存器,将参数传给所设寄存器,在通过call来跳转到调用的函数开头地址。在源代码hello.c中调用了printf、getchar、sleep和exit(1):第一个printf转换成了puts,把.LC0段的立即值传入的%edi中,然后call跳转到puts。
在这里插入图片描述
printf用%rdi传参
在这里插入图片描述
sleep用%edi传参
在这里插入图片描述
exit用%edi传参
在这里插入图片描述
atoi用%rdi传参
这里的exit是把立即数1传入了%edi中,然后call跳转到exit。第二个printf有三个参数,第一个是.LC1中的格式化字符串存在%eax中,后面的两个依次是%rdx和%rsi,然后call跳转到printf接下来是sleep,它有一个参数传到%edi中,之后call跳转sleep中。getchar是不需要参数的,直接call跳转即可。

(2)返回值:函数的返回值一般存在寄存器%eax中,如果有返回值,则要先把返回值存到%eax中再用ret返回。源程序中有主函数的return 0:就是先把返回值立即数0存到%eax中,然后再ret返回。

3.4本章小结

本章显示简述了编译的概念和作用,具体分析了一个c程序是如何被编译器编译成一个汇编程序的过程,还详细分析了不同的c语句和翻译成汇编语句之后的表示方法。

第四章 汇编

4.1汇编的概念与作用

**汇编的概念:**汇编是指把汇编语言翻译成机器语言的过程,在这里还包括把这些机器语言指令打包成为可重定位目标程序的过程。
**汇编的作用:**把汇编语言一一对应翻译成机器可以直接执行的机器指令。

4.2 在Ubuntu下汇编的命令

gcc -c hello.s -o hello.o
在这里插入图片描述

4.3 可重定位目标elf格式

ELF Header:用于总的描述ELF文件各个信息的段。

在这里插入图片描述
在这里插入图片描述
Section Header:描述了.o文件中出现的各个节的类型、位置、所占空间大小等信息

.rela.text:重定位节,这个节包含了.text(具体指令)节中需要进行重定位的信息。这些信息描述的位置,在由.o文件生成可执行文件的时候需要被修改(重定位)。在这个hello.o里面需要被重定位的有printf , puts , exit , sleepsecs , getchar , sleep ,rodata里面的两个元素(.L0和.L1字符串)
在这里插入图片描述
符号表:全局符号main是一个位于.text段,段偏移为0,大小125字节的函数
在这里插入图片描述

4.4 Hello.o的结果解析

在这里插入图片描述
对比发现区别:
(1)分支跳转语句:hello.s中跳转到目标位置都是用.L3/.L4等等来表示的,而反汇编之后则是用具体的地址表示。
(2)函数调用:原本的hello.s只需要call加上函数名,但是反汇编后需要call加具体地址。
(3) 操作数: hello.s中的操作数是十进制,而在hello.o反汇编中操作数是十六进制。

说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。

4.5 本章小结

了解了hello从hello.s到hello.o的汇编过程,查看hello.o的elf格式和使用objdump得到反汇编代码与hello.s进行比较的方式,了解到从汇编语言映射到机器语言汇编器需要实现的转换

第五章 链接

5.1 链接的概念与作用

链接的概念:
链接是指在电子计算机程序的各模块之间传递参数和控制命令,并把它们组成一个可执行的整体的过程。总之是把多个文件拼接合并成一个可执行文件。
链接的作用:
链接可以在编译、汇编、加载和运行时执行。链接方便了模块化编程。

5.2在Ubuntu下链接的命令

ld -o hello -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 hello.o /usr/lib/x86_64-linux-gnu/libc.so /usr/lib/x86_64-linux-gnu/crtn.o
在这里插入图片描述

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

ELF头
在这里插入图片描述
节头
在这里插入图片描述
interp段:动态链接器在操作系统中的位置不是由系统配置决定,也不是由环境参数指定,而是由 ELF 文件中的 .interp 段指定。该段里保存的是一个字符串,这个字符串就是可执行文件所需要的动态链接器的位置,常位于 /lib/ld-linux.so.2。

dynamic段:该段中保存了动态链接器所需要的基本信息,是一个结构数组,可以看做动态链接下 ELF 文件的“文件头”。存储了动态链接会用到的各个表的位置等信息。

dynsym段:该段与 “.symtab”段类似,但只保存了与动态链接相关的符号,很多时候,ELF文件同时拥有 .symtab 与 .synsym段,其中 .symtab 将包含 .synsym 中的符号。该符号表中记录了动态链接符号在动态符号字符串表中的偏移,与.symtab中记录对应。

dynstr段:该段是 .dynsym 段的辅助段,.dynstr 与 .dynsym 的关系,类比与 .symtab 与 .strtab 的关系

hash段:在动态链接下,需要在程序运行时查找符号,为了加快符号查找过程,增加了辅助的符号哈希表,功能与 .dynstr 类似

rel.dyn段:对数据引用的修正,其所修正的位置位于 “.got”以及数据段(类似重定位段 “rel.data”)

rel.plt段:对函数引用的修正,其所修正的位置位于 “.got.plt分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。

5.4 hello的虚拟地址空间

用edb加载hello,然后在Data Dump 中查看虚拟地址的信息。发现hello的虚拟地址从0x400000-0x401000
在这里插入图片描述
Data Dump里向我们展示处,hello的虚拟地址从0x400000 – 0x401000,并给我们列出了整个文件在内存上存储的是什么。我们可以通过5.3节的每段起始地址,找到每一段的信息。
如.interp段,根据5.3,这段应该是存储动态链接器的位置,我们在它的起始地址0x400200查看。
在这里插入图片描述
这个地方存储了一个字符串,代表动态存储器的位置。

5.5 链接的重定位过程分析

objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。结合hello.o的重定位项目,分析hello中对其怎么重定位的。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
Offset=0x74,symbol=sleep,type=0xR_X86_64_PLT32,addend=-4链接器修改开始于偏移量0x74处的32位PLT相对引用,这样在运行时会指向sleep的例程,接着链接器会按照0xR_X86_64_PLT32重定位类型具体的规定计算出相应的数值。

5.6hello的执行流程

加载:_dl_start ,_dl_init
开始执行:_start ,_libc_start_main ,_init 执行main:_main,_printf,_exit,_sleep,_getchar_dl_runtime_resolve_xsave,_dl_fixup,_dl_lookup_symbol_x
退出:exit

5.7 hello的动态链接分析

dl_init前后的一些变化
在这里插入图片描述
通过前面的节地址,我们找到got.plt节,这个节里面,每8个字节是一个条目,与plt联合使用时,GOT[0],GOT[1]是包含动态链接器在解析函数地址时会使用的信息,GOT[2]是动态链接器ld-linux.so模块的入口。在dl_init前这里是空的,而在dl_init后这里
在这里插入图片描述
这里GOT[2]变为0x7f169ad84750,找到这个地址,对应的是一个叫dl_runtime_resolve的函数,所有动态库函数在第一次调用时,都是通过XXX@plt -> 公共@plt -> _dl_runtime_resolve调用关系做地址解析和重定位的。

5.8 本章小结

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

第六章 hello的进程管理

6.1 进程的概念与作用

进程的概念:
进程是计算机程序需要进行对数据集合进行操作所运行的一次活动。
进程的作用:
是系统进行资源分配和调度的基本单位,是操作系统结构的基础。

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

作用:Shell 是指一种应用程序,Shell应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务。
处理流程:
(1).读取输入的命令行。
(2).解析引用并分割命令行为各个单词,各单词称为token。其中重定向所在的token会被保存下来,直到扩展步骤(5)结束后才进行相关处理,如进行扩展、截断文件等。
(3).检查命令行结构。主要检查是否有命令列表、是否有shell编程结构的命令,如if判断命令、循环结构的for/while/select/until,这些命令属于保留关键字,需要特殊处理。
(4).对第一个token进行别名扩展。如果检查出它是别名,则扩展后回到(2)再次进行token分解过程。如果检查出它是函数,则执行函数体中的复合命令。如果它既是别名,又是函数(即命令别名和函数同名称的情况),则优先执行别名。在概念上,别名的临时性最强,优先级最高。
(5).进行各种扩展。扩展顺序为:大括号扩展;波浪号扩展;参数、变量和命令替换、算术扩展(如果系统支持,此步还进行进程替换);单词拆分;文件名扩展。
(6).引号去除。经过上面的过程,该扩展的都扩展了,不需要的引号在此步就可以去掉了。
(7).搜索和执行命令。
(8).返回退出状态码。

6.3 Hello的fork进程创建过程

我们在shell上输入./hello,这个不是一个内置的shell命令,所以shell会认为hello是一个可执行目标文件,通过条用某个驻留在存储器中被称为加载器的操作系统代码来运行它。当shell运行一个程序时,父进程通过fork函数生成这个程序的进程。这个子进程几乎与父进程相同,子进程得到与父进程相同的虚拟地址空间(独立)的一个副本,包括代码,数据段,堆,共享库以及用户栈。唯一的不同是与父进程的PID不同。

6.4 Hello的execve过程

当fork之后,子进程调用execve函数(传入命令行参数)在当前进程的上下文中加载并运行一个新程序即hello程序,execve调用驻留在内存中的被称为启动加载器的操作系统代码来执行hello程序,加载器删除子进程现有的虚拟内存段,并创建一组新的代码、数据、堆和栈段。新的栈和堆段被初始化为零,通过将虚拟地址空间中的页映射到可执行文件的页大小的片,新的代码和数据段被初始化为可执行文件中的内容。最后加载器设置PC指向_start地址,_start最终调用hello中的main函数。除了一些头部信息,在加载过程中没有任何从磁盘到内存的数据复制。直到CPU引用一个被映射的虚拟页时才会进行复制,这时,操作系统利用它的页面调度机制自动将页面从磁盘传送到内存
在这里插入图片描述

6.5 hello的进程执行

逻辑控制流:一系列程序计数器PC的值的序列叫做逻辑控制流,进程是轮流使用处理器的,在同一个处理器核心中,每个进程执行它的流的一部分后被抢占(暂时挂起),然后轮到其他进程。
用户模式和内核模式:处理器通过用某个控制寄存器中的一个模式位来限制一个应用可以执行的指令和它可以访问的地址空间范围。当设置了模式位时,进程就运行着在内核模式,可以执行指令集中的任何指令,并且可以访问系统中的任何内存位置。当没有设置模式位时,进程就处于用户模式中,用户模式的进程不允许执行特权指令,也不允许直接引用地址空间中内核区内的代码和数据。
用户模式与内核模式之间的转换方法:运行应用程序代码的进程初始时是在用户模式中的。进程从用户模式变为内核模式的唯一方法是通过中断,故障或陷入系统调用这样的异常。当异常发生时,控制传递到异常处理程序,处理器将用户模式变为内核模式,处理程序运行在内核模式中,当它返回到应用程序代码时,处理前就把模式从内核模式改为用户模式。
上下文信息:内核为每个进程维持一个上下文,上下文就是内核重新启动一个被抢占的进程所需的状态。并通过上下文切换的机制来将控制转移到新的进程。
进程时间片:一个进程执行它的控制流的一部分的每一时间段叫做时间片。
Hello的进程调度过程:hello初始运行在用户模式,在hello进程调用sleep之后陷入内核模式,计时器开始计时,内核通过上下文切换将当前进程将当前进程的控制权交给其他进程。当sleep函数时间到达时发送一个中断信号,此时进入内核状态执行中断处理,然后内核将进程控制权交还给hello进程,hello进程继续执行自己的控制逻辑流。

在这里插入图片描述

6.6 hello的异常与信号处理

(1)Ctrl+Z:这时向进程发送了sigtstp信号,让进程挂起,输入ps可以发现hello未关闭。
在这里插入图片描述
(2) Ctrl+C:向进程发送sigint信号,让进程结束,输入ps发现hello已经结束。
在这里插入图片描述
3) fg命令:让挂起的进程继续执行。
在这里插入图片描述
(4) jobs命令:可以查看当前命令内容。
在这里插入图片描述
(5) pstree命令:用进程树把各个进程用树状图方式连接起来。
在这里插入图片描述
(6) kill指令:向固定进程发送某些信号,如图就表示向PID为4185的进程hello,发送了一个sighup的信号,用fg命令继续运行就会出现电源失效。如果是信号30的话就是显示电源失效。不同的信号会返回不同的信息来描述这个信号。
在这里插入图片描述

6.7本章小结

本章介绍了进程的概念和作用,描述了shell如何在用户和系统内核之间建起一个交互的桥梁。讲述了shell的基本操作以及各种内核信号和命令,还总结了shell是如何fork新建子进程、execve如何执行进程、hello进程如何在内核和前端中反复跳跃运行的。

第七章 hello的内存管理

7.1 hello的存储器地址空间

(1)逻辑地址:汇编程序中地址。逻辑地址由选择符和偏移量组成。
(2)线性地址:逻辑地址经过段机制后转化为线性地址,为描述符:偏移量的组合形式。分页机制中线性地址作为输入。
(3)虚拟地址:类似于线性地址
(4)物理地址:真实的物理内存对应地址。 CPU对内存的访问是通过连接着CPU和北桥芯片的前端总线来完成的。在前端总线上传输的内存地址都是物理内存地址。

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

先将逻辑地址分成段选择符+段描述符的判别符(TI)+地址偏移量的形式,然后先判断TI字段,看看这个段描述符究竟是局部段描述符(ldt)还是全局段描述符(gdt),然后再将其组合成段描述符+地址偏移量的形式,这样就转换成线性地址了。
在这里插入图片描述

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

在这里插入图片描述
线性地址(书里的虚拟地址VA)到物理地址(PA)之间的转换通过分页机制完成。而分页机制是对虚拟地址内存空间进行分页。
首先Linux系统有自己的虚拟内存系统,Linux将虚拟内存组织成一些段的集合,段之外的虚拟内存不存在因此不需要记录。内核为hello进程维护一个段的任务结构即图中的task_struct,其中条目mm指向一个mm_struct,它描述了虚拟内存的当前状态,pgd指向第一级页表的基地址(结合一个进程一串页表),mmap指向一个vm_area_struct的链表,一个链表条目对应一个段,所以链表相连指出了hello进程虚拟内存中的所有段。
系统将每个段分割为被称为虚拟页(VP)的大小固定的块来作为进行数据传输的单元,在linux下每个虚拟页大小为4KB,类似地,物理内存也被分割为物理页(PP/页帧),虚拟内存系统中MMU负责地址翻译,MMU使用存放在物理内存中的被称为页表的数据结构将虚拟页到物理页的映射,即虚拟地址到物理地址的映射
不考虑TLB与多级页表,虚拟地址分为虚拟页号VPN和虚拟页偏移量VPO,根据位数限制分析可以确定VPN和VPO分别占多少位是多少。通过页表基址寄存器PTBR+VPN在页表中获得条目PTE,一条PTE中包含有效位、权限信息、物理页号,表示三种状态:未分配,未缓存,已缓存
如果有效位是0+NULL则代表没有在虚拟内存空间中分配该内存,如果是有效位0+非NULL,则代表在虚拟内存空间中分配了但是没有被缓存到物理内存中,如果有效位是1则代表该内存已经缓存在了物理内存中,可以得到其物理页号PPN,与虚拟页偏移量共同构成物理地址PA。

7.4 TLB与四级页表支持下的VA到PA的变换

将VPN分成三段,对于TLBT和TLBI来说,可以在TLB中找到对应的PPN,但是有可能出现缺页的情况,这时候就需要到页表中去找。此时,VPN被分成了更多段(这里是4段)CR3是对应的L1PT的物理地址,然后一步步递进往下寻址,越往下一层每个条目对应的区域越小,寻址越细致,在经过4层寻址之后找到相应的PPN让你和和VPO拼接起来。
在这里插入图片描述

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

得到物理地址之后,先将物理地址拆分成CT(标记)+CI(索引)+CO(偏移量),然后在一级cache内部找,如果未能寻找到标记位为有效的字节(miss)的话就去二级和三级cache中寻找对应的字节,找到之后返回结果。
在这里插入图片描述

7.6 hello进程fork时的内存映射

当fork 函数被shell调用时,内核为hello进程创建各种数据结构,并分配给它一个唯一的PID 。为了给hello进程创建虚拟内存,它创建了hello进程的mm_struct 、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。当fork 在hello进程中返回时,hello进程现在的虚拟内存刚好和调用fork 时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,因此,也就为每个进程保持了私有地址空间的抽象概念。

7.7 hello进程execve时的内存映射

在这里插入图片描述
Execve函数在当前进程中,加载并运行包含在可执行目标文件hello中的程序,用hello程序有效地替代了当前程序。加载并运行hello需要以下几个步骤:
1.删除已存在的用户区域。删除shell虚拟地址的用户部分中的已存在的区域结构。
2.映射私有区域。为hello的代码、数据、bss 和栈区域创建新的区域结构。所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为hello 文件中的.text和.data 区。bss 区域是请求二进制零的,映射到匿名文件,其大小包含在hello 中。栈和堆区域也是请求二进制零的,初始长度为零。下图概括了私有区域的不同映射。
3.映射共享区域。如果hello程序与共享对象(或目标)链接,比如标准C 库libc. so, 那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内。
4.设置程序计数器(PC) execve 做的最后一件事情就是设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。

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

1.段错误:首先,先判断这个缺页的虚拟地址是否合法,那么遍历所有的合法区域结构,如果这个虚拟地址对所有的区域结构都无法匹配,那么就返回一个段错误(segment fault)
2.非法访问:接着查看这个地址的权限,判断一下进程是否有读写改这个地址的权限。
3.如果不是上面两种情况那就是正常缺页,那就选择一个页面牺牲然后换入新的页面并更新到页表。
在这里插入图片描述
在这里插入图片描述

7.9动态存储分配管理

动态内存分配器简介:动态内存分配器维护着一个进程的虚拟内存区域,称为堆(heap) 。系统之间细节不同,但是不失通用性,假设堆是一个请求二进制零的区域,它紧接在未初始化的数据区域后开始,并向上生长(向更高的地址) 。对于每个进程,内核维护着一个变量brk, 它指向堆的顶部。分配器将堆视为一组不同大小的块(block) 的集合来维护。每个块就是一个连续的虚拟内存片(chunk),要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。
基本方法:
1.首次适配从头开始搜索空闲链表,选择第一个合适的空闲块,优点是趋向于将大的空闲块保留在链表的后面,缺点是会在链表的起始处留下许多小空闲块的碎片。
2.显示空闲链表。在空闲块中使用指针连接空闲块。
3.分离的空闲链表.维护多个空闲链表,每个链表的块有大致相等的大小放置策略:
1.隐式空闲链表是空闲块通过头部中的大小字段隐含着连接的,分配器可以通过遍历堆中的所有块简介遍历整个空闲块集合,优点是简单,缺点是比较浪费时间。
2.下次适配。下次适配和首次适配的方式很相似,只不过是从上一次查询结束的地方开始,而不是从链表的起始处开始搜索。
3.最佳适配。检查每个空闲块,选择适合所需请求大小的空闲块。

7.10本章小结

本章主要介绍了hello的存储器地址空间、intel的段式管理、hello的页式管理,以intel Core7在指定环境下介绍了VA到PA的变换、物理内存访问,还介绍了hello进程fork时的内存映射、execve时的内存映射、缺页故障与缺页中断处理、动态存储分配管理

第八章 hello的IO管理

8.1 Linux的IO设备管理方法

设备的模型化:文件
文件的类型:
1.普通文件(regular file):包含任意数据的文件。
2.目录(directory):包含一组链接的文件,每个链接都将一个文件名映射到一个文件(他还有另一个名字叫做“文件夹”)。
3.套接字(socket):用来与另一个进程进行跨网络通信的文件
4.命名通道
5.符号链接
6.字符和块设备
设备管理:unix io接口
1.打开和关闭文件
2.读取和写入文件
3.改变当前文件的位置
8.2 简述Unix IO接口及其函数
打开和关闭文件:
1.open()函数:这个函数会打开一个已经存在的文件或者创建一个新的文件
在这里插入图片描述
如果open的返回值为-1则说明其打开该文件失败
2.close()函数:这个函数会关闭一个打开的文件

在这里插入图片描述
读取和写入文件:
1.read()函数:这个函数会从当前文件位置复制字节到内存位置
在这里插入图片描述
2.write()函数:这个函数从内存复制字节到当前文件位置读写文件时,如果返回值<0则说明出现错误
改变文件位置:
lseek()函数

8.3 printf的实现分析

在这里插入图片描述

为printf的代码,发现调用了两个函数,vsprintf和write
在这里插入图片描述
如图可知,vsprintf函数的作用是将所有的参数内容格式化之后存入buf,然后返回格式化数组的长度。
write函数是将buf中的i个元素写到终端的函数。
Printf的运行过程:从vsprintf生成显示信息,显示信息传送到write系统函数,write函数再陷阱-系统调用 int 0x80或syscall.字符显示驱动子程序。从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。

8.4 getchard的实现分析

在这里插入图片描述

getchar调用了一个read函数,这个read函数是将整个缓冲区都读到了buf里面,然后将返回值是缓冲区的长度。我们可以发现,如果buf长度为0,getchar才会调用read函数,否则是直接将保存的buf中的最前面的元素返回。
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。

本章小结

介绍了linux的IO设备管理方法,列出开、关、读、写、转移文件的接口及相关函数,简单分析了printf和getchar函数的实现。

结论

(1)预处理:由预处理器cpp将.c文件转换为.i
(2)编译:gcc编译器将,i文件转换为.s
(3)汇编:汇编器as将.s文件转换为可重定位文件.o
(4)链接:链接器ld将许多.o文件链接形成可执行文件
(5)进程管理:提供一个独立逻辑控制流和私有地址空间
(6)存储管理:程序存储信息
(7)IO:IO设备模型化为文件,所有输入输出看做文件的读写

附件

hello.c C文件
hello.i 预处理器转换的
hello.s hello.i编译成的汇编语言
hello.o hello.s生成的二进制文件
hello 可执行hello文件
hello_o.s hello.o反汇编得到的
hello_.s hello反汇编得到的
hello.elf hello的elf表

参考文献

[1] 深入理解计算机系统
[2] http://www.cnblogs.com/zhengbin/p/5755141.html
[3]https://blog.csdn.net/qq_31811537/article/details/79312908
[4] https://www.cnblogs.com/wangcp-2014/p/5146343.html
[5] https://www.cnblogs.com/pianist/p/3315801.html
[6] https://blog.csdn.net/Quincuntial/article/details/53700161

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值