HIT-CSF2019 Hello的一生

HIT-CSF2019 Hello的一生摘 要摘要是论文内容的高度概括,应具有独立性和自含性,即不阅读论文的全文,就能获得必要的信息。摘要应包括本论文的目的、主要内容、方法、成果及其理论与实际意义。摘要中不宜使用公式、结构式、图表和非公知公用的符号与术语,不标注引用文献编号,同时避免将摘要写成目录式的内容介绍。关键词:预处理;编译;汇编;链接;进程管理;存储管理;I/O管理本文主要介绍了...
摘要由CSDN通过智能技术生成

HIT-CSF2019 Hello的一生

摘 要
摘要是论文内容的高度概括,应具有独立性和自含性,即不阅读论文的全文,就能获得必要的信息。摘要应包括本论文的目的、主要内容、方法、成果及其理论与实际意义。摘要中不宜使用公式、结构式、图表和非公知公用的符号与术语,不标注引用文献编号,同时避免将摘要写成目录式的内容介绍。

关键词:预处理;编译;汇编;链接;进程管理;存储管理;I/O管理
本文主要介绍了计算机学习最基础的程序hello形成的过程。
程序员通过编辑器创建出文本文件hello.c,通过预处理器(cpp)根据以#开头的命令,修改得到hello.i。编译器(ccl)将hello.i翻译成汇编程序hello.s,再由汇编器(as)将hello.s翻译成机器指令,并打包成可重定位目标程序hello.o。再通过与标准C库经过链接器链接形成可执行目标文件hello。
用户在shell里输入“./hello”,shell会fork一个子进程,并在此子进程中调用execve,为其映射虚拟内存。程序会跳转到_start地址,调用hello的main函数。程序运行结束后, hello 进程由shell的父进程回收,内核会删除相关数据结构。

(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)

第1章 概述
1.1 Hello简介
P2P:程序员通过编辑器创建出文本文件hello.c,通过预处理器(cpp)根据以#开头的命令,修改得到hello.i。编译器(ccl)将hello.i翻译成汇编程序hello.s,再由汇编器(as)将hello.s翻译成机器指令,并打包成可重定位目标程序hello.o。再通过与标准C库经过链接器链接形成可执行目标文件hello。hello完成从程序Program到进程Process的转变,即P2P。
O2O:用户在shell里输入“./hello”,shell会fork一个子进程,并在此子进程中调用execve,为其映射虚拟内存。程序会跳转到_start地址,调用hello的main函数。程序运行结束后, hello 进程由shell的父进程回收,内核会删除相关数据结构。hello从无到有,再到被清空,即020。
1.2 环境与工具
X64 CPU;2GHz;2G RAM;256GHD Disk 以上
Windows7/10 64位以上;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64位/优麒麟 64位 以上;
Visual Studio 2010 64位以上;CodeBlocks 64位;vi/vim/gedit+gcc
1.3 中间结果
1.hello.i
hello.c经cpp预处理后得到的修改了的源程序。
2.hello.s
hello.i经ccl编译后形成的汇编程序。
3.hello.o
hello.s经as汇编后的形成的可重定位目标文件。
4.hello
hello.o与标准C库经过链接的形成的可执行目标文件。
1.4 本章小结
本章介绍了Hello的P2P,020的过程以及实验的环境、工具和中间结果。

(第1章0.5分)

第2章 预处理
2.1 预处理的概念与作用
概念:预处理器(cpp) 根据以字符#开头的命令,修改原始的C 程序。
作用:
1.将头文件中的内容(源文件之外的文件)插入到源文件中。
2.替换了用#define定义的字符串。
3.条件编译。
4.删除掉注释的过程。
2.2在Ubuntu下预处理的命令
gcc -E hello.c -o hello.i
在这里插入图片描述
图2-1 在Ubuntu下预处理的命令
2.3 Hello的预处理结果解析
#include<stdio.h>,#include<unistd.h>,#include<stdlib.h>等头文件包含的文件被插入到该预编译指令的位置。注释等无用信息被删除掉了。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

图2-2 hello.i文件
2.4 本章小结
本章以hello.c为例介绍了C程序在编译前的预处理过程,主要介绍了预处理的概念和作用,在Ubuntu下对预处理过程进行演示生成.i文件,并解析了预处理的过程。
(第2章0.5分)

第3章 编译
3.1 编译的概念与作用
概念:编译器(ccl)将预处理生成的文本文件翻译成包含一个汇编语言程序的文本文件。
作用:将高级语言翻译成汇编语言,为了方便计算机将其翻译为二进制的机器语言指令。
注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序。
3.2 在Ubuntu下编译的命令
gcc -S hello.i -o hello.s
在这里插入图片描述
图3-1 在Ubuntu下编译的命令
3.3 Hello的编译结果解析
3.3.1汇编指令
.file 源文件
.text 代码段
.globl 全局标识符
.data 数据段
.align 数据或指令存放地址对齐方式
.type 指定函数或对象类型
.size 声明大小
.long 声明long类型
.section .rodata rodata节
.string 字符串类型
3.3.2数据
hello.s中用到的 C 数据类型:整数类型、字符串、数组.
整数类型:
在编译器处理后, hello.s先声明了一个全局变量sleepsecs,将 sleepsecs 存放在.data节,为其分配4字节大小的空间,为其赋初值2。
在这里插入图片描述
图3-2 hello.s文件
参数int argc,局部变量i出现在main的栈帧中。局部变量通常被存储在寄存器或者程序栈中,在这里,我们可以看出,hello.s将局部变量i存储在-4(%rbp)中,初始值为 0。i的值每次循环加1,当i大于7时退出循环。argc 由寄存器%edi 保存,然后又被存入-20(%rbp)。
在这里插入图片描述
在这里插入图片描述在这里插入图片描述

字符串:
.rodata段有两个printf语句中的格式字符串。
第一个字符串.LC0包含汉字,每个汉字在utf-8编码中被编码为3个字节,第二个字符串.LC1的两个%s为用户在终端输入的两个参数。
在这里插入图片描述
数组:
char *argv[]作为main参数出现在栈帧中。
在这里插入图片描述
3.3.3运算与操作
运算
加法运算。
在这里插入图片描述在这里插入图片描述在这里插入图片描述

赋值操作
循环开始时将全局变量i赋值为0。在这里插入图片描述
比较操作
在这里插入图片描述
在这里插入图片描述
数组操作
argv[2]:首先从-32(%rbp)读取argv地址存入rax,然后rax增加16个字节,此时rax中存放的是&(argv[2]),读取此地址指向的argv[2]放入rdx。
argv[1]:首先从-32(%rbp)读取argv地址存入rax,然后rax增加8个字节,此时rax中存放的是&(argv[1]),读取此地址指向的argv[1]放入rax,最后存入rsi。
在这里插入图片描述
3.3.4控制转移
如果满足某条件,则跳转至某个位置。
在这里插入图片描述在这里插入图片描述在这里插入图片描述

3.3.5函数调用
main 函数
main函数开始时被存在.text节,标记类型为函数,程序运行时,将由系统启动函数调用。main函数的两个参数 argc和argv[],存储在%rdi和%rsi。
在这里插入图片描述
printf 函数
printf的调用,参数被存放在寄存器传递。格式化字符串被存放在edi传递,argv[1]被放在rsi,argv[2]被放在rdx。用call来调用printf,而printf的返回值则会被存入eax返回。
在这里插入图片描述
exit 函数
参数被存放在edi传递,用call指令调用exit。
在这里插入图片描述
sleep 函数
参数被存放在edi传递,用call指令调用sleep。
在这里插入图片描述
getchar 函数
直接用call指令,main的返回值放在eax传递。
在这里插入图片描述
3.4 本章小结
本章解释了编译的概念和作用,展示了将hello.i编译成hello.s的过程,详细分析了不同的C语句与翻译成汇编语句后的表示和作用。
(第3章2分)

第4章 汇编
4.1 汇编的概念与作用
概念:汇编器(as) 将hello.s 翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序的格式,并将结果保存在目标文件hello.o 中。
作用:汇编将原本的文本文件变为二进制文件,将汇编代码变成机器可执行的命令,便于进行链接。
注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程
4.2 在Ubuntu下汇编的命令
gcc -c hello.s -o hello.o
在这里插入图片描述
图4-1 在Ubuntu下汇编的命令
4.3 可重定位目标elf格式
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。

图4-2 查看hello.o的ELF格式
用命令readelf -a hello.o > hello.elf,查看hello.o的ELF格式.
在这里插入图片描述在这里插入图片描述
图4-3 ELF头
ELF头开头为生成该文件的的系统的字的大小与字节顺序的16字节的序列。
同时,ELF头包含了节头大小(64字节)、目标文件的类型(可重定位文件)、机器类型(x86-64)、节头部表的文件偏移、节头部表中条目的大小和数量(13)等信息。

在这里插入图片描述

图4-4 节头表
节头表描述了不同节的位置与大小、名称、类型、地址、偏移量、旗标、链接、信息、对齐的信息。
在这里插入图片描述
图4-5 符号表
符号表包含了可重定位目标模块定义和引用的符号的信息。每个符号都由一个条目来说明。
在这里插入图片描述
图4-6 重定位表
重定位表中包含了重定位条目的信息,用以完成对最终位置未知的目标引用。
4.4 Hello.o的结果解析
在这里插入图片描述
图4-7 解析hello.o指令
在这里插入图片描述在这里插入图片描述

图4-8 helloo.objdump
1.操作数:在汇编语言为十进制,在机器语言为二进制。
在这里插入图片描述
在这里插入图片描述

图4-9 与hello.s操作数对比
2.分支转移:在汇编语言中,分支转移命令是由助记符来标识,在机器语言中,分支转移命令是直接跳转入目的地址。
在这里插入图片描述
在这里插入图片描述
图4-10 与hello.s分支转移语句对比
3.函数调用:在汇编语言中,函数调用是对函数名的引用。在机器语言中,是通过对在.rela.text节中的重定位条目进行解析从而得到目的函数地址。
在这里插入图片描述
在这里插入图片描述
图4-11 与hello.s函数调用语句对比
4.5 本章小结
本章简述了汇编的概念和作用,展示了将汇编指令转换成机器指令的过程,通过使用readelf查看hello.o的ELF,利用反汇编的方式查看了hello.o反汇编的内容,与hello.s进行对照分析。
(第4章1分)

第5章 链接
5.1 链接的概念与作用
概念:链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载(复制)到内存并执行。
作用:可以使得分离编译成为可能,当我们需要修改一个大型的应用程序时,可以单独修改和编译其中某些模块。当我们改变这些模块中的一个时,只需简单地重新编译它,并重新链接应用,而不必重新编译其他文件。
注意:这儿的链接是指从 hello.o 到hello生成过程。
5.2 在Ubuntu下链接的命令
在这里插入图片描述
图5-1 在Ubuntu下链接的命令
5.3 可执行目标文件hello的格式
可执行目标文件格式类似于可重定位目标文件格式。但它不再需要rel节。
在这里插入图片描述
图5-2 查看hello的ELF格式
在这里插入图片描述
在这里插入图片描述在这里插入图片描述

图5-3 节头表
5.4 hello的虚拟地址空间
在这里插入图片描述
图5-4 程序头表
ELF可执行文件被设计得容易加载到内存,可执行文件的连续的片被映射到内存段。程序头表描述了这种映射关系。程序头表包含偏移,内存地址,物理地址,目标文件段大小,内存中的段大小,运行时访问权限,对齐要求等信息。
在这里插入图片描述
图5-5 edb虚拟内存空间
5.5 链接的重定位过程分析
用objdump -d -r hello > hello.objdump得到hello的反汇编代码。
在这里插入图片描述
图5-6 hello的反汇编指令
在这里插入图片描述在这里插入图片描述

图5-7 hello.objdump
对照hello.o可以发现,hello.o中只有main一个函数,而hello中多了_init,_start和.plt节中的函数。
5.6 hello的执行流程
_dl_start
_dl_init
_start
_libc_start_main
_init
_main
_printf
_exit
_sleep
_getchar
_dl_runtime_resolve_xsave
_dl_fixup
_dl_lookup_symbol_x
exit
5.7 Hello的动态链接分析
经edb调试,可以发现从原来0x00600a10开始的global_offset表是全0,在执行过_dl_init之后被赋上了相应偏移量的值。说明了dl_init是给程序赋上当前执行的内存地址偏移量。
5.8 本章小结
本章介绍了链接的概念和作用,分析了hello的ELF格式,分析了虚拟地址空间的分配,重定位过程,执行流程和动态链接的过程。
(第5章1分)

第6章 hello进程管理
6.1 进程的概念与作用
概念:进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。进程是一个执行中的程序的实例。
作用:在现代系统上运行一个程序时,会得到一个假象,就好像这个程序是系统中当前运行的唯一的程序一样。我们的程序好像是独占地使用处理器和内存,处理器就好像是无间断地一条接一条地执行我们程序中的指令。最后,我们程序中的代码和数据好像是系统内存中唯一的对象。这些假象都是进程提供给我们的。
6.2 简述壳Shell-bash的作用与处理流程
Shell 的作用:Shell 是一个用 C 语言编写的程序,是用户使用 Linux 的桥梁。Shell 是指一种应用程序,Shell为用户提供了输入指令界面,用户通过这个界面访问操作系统内核的服务。
shell-bash处理流程:
1)从终端读入用户输入的命令。
2)将输入字符串切分获得所有的参数
3)分析指令,若为内置命令则立即执行
4)否则fork一个子进程,在子进程中运行
5)shell 应该接受键盘输入信号并处理
6.3 Hello的fork进程创建过程
先要运行hello程序,在 shell 输入./hello 1171200301 武云峰。终端程序会解析输入的命令行,判断语义为执行当前目录下的可执行目标文件hello,然后终端程序 先会调用 fork 函数,创建一个新的运行的子进程,注意,这个子进程几乎但不完全与父进程相同,子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)副本,说明当父进程调用fork函数时,子进程可以读写在父进程中打开的任何文件。二者最大的区别在于它们的PID不同。

在这里插入图片描述
图6-1 Hello的fork进程创建进程图
6.4 Hello的execve过程
execve 函数原型为:int execcve(const char *filename,const char argv[],const char envp[])
execve函数加载并运行可执行目标文件filename, 参数列表argv 和环境变量列表envp 。加载hello后,调用_start,_start设置栈,将控制传递给新程序的主函数。
在这里插入图片描述
图6-2 加载器创建的虚拟内存段
6.5 Hello的进程执行
时间片是指从进程开始运行直到被抢占的时间。
操作系统内核使用一种称为上下文切换的较高层形式的异常控制流来实现多任务。内核为每个进程维持一个上下文,上下文就是内核重新启动一个被抢占的进程所需的状态。它由一些对象的值组成,这些对象包括通用寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构。
在内核调度一个新的进程运行后,它就抢占当前进程,并会使用上下文切换的机制来将控制转移到新的进程。上下文切换的过程如下:
1.保存当前进程的上下文
2.恢复某个先前被抢占的进程被保存的上下文
3.将控制传递给这个新恢复的进程
执行hello时,sleep函数会显式地请求让调用函数休眠,此时内核会决定执行上下文切换,切换过程如下图:
在这里插入图片描述
图6-3 进程上下文切换的剖析
hello调用getchar时,实际上是执行输入流是stdin的系统read。hello运行在用户态中,它通过执行系统调用read陷入到内核。内核中的陷阱处理程序请求来自磁盘控制器的DMA传输,并且安排在磁盘控制器完成从磁盘到内存的数据传输后,磁盘中断处理器。
磁盘读取数据要用一段相对较长的时间,所以内核决定执行上下文切换,等磁盘发出一个中断信号,表示数据从键盘缓冲区读入内存后,内核再进行上下文切换,将控制返回给hello,依此类推。
6.6 hello的异常与信号处理
fg命令可以使后台挂起的进程继续运行。
jobs命令可以查看当前的关键命令(ctrl+Z/ctrl+C等)内容。
在这里插入图片描述
图6-4 crtl+C操作
向进程发送了一个sigint信号,让进程直接结束,输入ps命令可以发现当前hello已经被终止。
在这里插入图片描述
向进程发送一个sigtstp信号,让进程暂时挂起,输入ps命令可以发现hello没有关闭。
图6-5 crtl+Z操作
在这里插入图片描述
图6-6 pstree
在这里插入图片描述
图6-7 kill
Kill:输入kill 2997杀死子进程。shell收到SIGCHLD信号,得到hello终止信息,利用信号处理程序回收hello子进程。当再用fg 1命令时,提示已终止。
乱按:
在这里插入图片描述
图6-8 乱按
乱按不会阻碍程序进行,而是会暂时缓存,当循环结束后,会被当作shell命令行输入。
6.7本章小结
本章介绍了进程的概念和作用,讲述了shell的操作和各种内核信号和命令,阐明了shell的fork新建子进程、execve执行进程、hello执行时可能进行的上下文切换以及shell对信号的处理的整个过程。
(第6章1分)

第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:由程序产生的与段相关的偏移地址部分。在这里是hello.o里的相对偏移地址。
线性地址:地址空间是一个非负整数地址的有序集合,如果地址空间中的整数是连续的,那么我们说它是一个线性地址空间。在这里是hello里的虚拟内存地址。
虚拟地址:CPU 通过生成一个虚拟地址。在这里也是hello里的虚拟内存地址。
物理地址:用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应。计算机系统的主存被组织成一个由M 个连续的字节大小的单元组成的数组。每字节都有一个唯一的物理地址。在这里是hello在运行时虚拟内存地址对应的物理地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
在这里插入图片描述
图7-1 Intel逻辑地址到线性地址的变换-段式管理
保护模式下:下标为段描述符,到GDT/LDT表查表获得段地址
实模式下:逻辑地址CS:EA=物理地址CS*16+EA
段地址+偏移地址=线性地址
7.3 Hello的线性地址到物理地址的变换-页式管理
在这里插入图片描述
图7-2 Hello的线性地址到物理地址的变换-页式管理
将线性地址分为VPN(虚拟页号)+VPO(虚拟页偏移量)的形式,再将VPN拆分成TLBT(TLB标记)+TLBI(TLB索引)。在TLB缓存里找到对应的PPN(物理页号)如果发生缺页,则直接查找对应的PPN。之后与VPO组合变为PPN+VPO,得到生成的物理地址。
7.4 TLB与四级页表支持下的VA到PA的变换
在这里插入图片描述
图7-3 TLB与四级页表支持下的VA到PA的变换
根据TLBI和TLBT判断PPN是否已被缓存到TLB中,如果TLB命中,则直接返回PPN,如果出现缺页,就会到页表中查询PPN。此时如果在页表中查询PPN,VPN会被分为多段(此处为4段),分别用作各级页表的索引,每个前一级页表的查询结果就是下一级页表的基地址,最后一级页表的查询结果为PPN。将PPN与VPO组合就得到PA。
7.5 三级Cache支持下的物理内存访问
图如7.4
CPU发出一个虚拟地址在TLB里面搜索。如果命中就将PTE发送给L1Cache,否则先在页表中更新PTE再发送。然后再进行L1根据PTE寻找物理地址,继续检测是否命中。
7.6 hello进程fork时的内存映射
在shell调用fork函数的时候,内核为hello进程创建各种数据结构,分配给它一个唯一的PID,创建了mm_struct、区域结构和页表的原样副本,它将这两个进程的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。在新进程中返回的时候,新进程和调用fork进程的虚拟内存相同,随后写操作通过写时复制机制创建新页面。
7.7 hello进程execve时的内存映射
execve函数在当前进程中加载并运行包含在可执行文件hello中的程序,加载器使hello中的程序能够替代当前程序的步骤如下:
1.删除已存在的用户区域。删除当前虚拟空间中已有的用户部分已存在的区域结构。
2.映射私有区域。为新程序的代码、数据、bss和栈区域创建新的区域结构。所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为hello文件中的.text和.data区。bss区域时请求二进制零的,映射到匿名文件,其大小包含在hello中。栈和堆区域也是请求二进制零的,初始长度为0。
3.映射共享区域。如果hello程序与共享对象(或目标链接),比如C库libc.so,那么这些对象都是动态链接到这个程序的,然后映射到用户虚拟地址空间中的共享区域内。
4.设置程序计数器(PC)。execve做的最后一件事情就是设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。
7.8 缺页故障与缺页中断处理
缺页故障:虚拟内存中的字不在物理内存中(DRAM缓存不命中)
在这里插入图片描述
图7-4 缺页异常示意图
如上例,CPU 引用了VP 3 中的一个字, 地址翻译硬件从内存中读取PTE 3, 推断出VP 3 未被缓存,触发一个缺页异常。调用内核中的缺页异常处理程序,该程序会选择一个牺牲页,上例中即为存放在PP3中的VP4 。如果VP4已被修改,那么内核就会将它复制回磁盘。即内核修改VP4 的页表条目,反映了VP 4 不再缓存在主存中的事实。
7.9动态存储分配管理
Printf会调用malloc。
动态内存分配器维护着一个进程的虚拟内存区域,称为堆。系统之间细节不同,但是不失通用性,假设堆是一个请求二进制零的区域,它紧接在未初始化的数据区域后开始,并向上生长(向更高的地址),对于每个进程,内核维护着一个变量brk,它指向堆的顶部。
分配器将堆视为一组不同大小的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可用来分配。空闲块保持空闲,直到它被显式地应用所分配。一个已分配的块保持已分配的状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。
分配器有两种基本风格,两种风格都要求应用显式地分配块,它们的不同之处在于由哪个实体来负责释放已分配的块。
显式分配器:要求应用显式地释放任何已分配的块。
隐式分配器:要求分配器检测一个已分配块何时不再使用,那么就释放这个块,自动释放未使用的已经分配的块的过程叫做垃圾收集。

任何实际的分配器都需要一些数据结构,允许它来区别块边界以及已分配块和空闲块。大多数分配器将这些信息嵌入块本身,下面介绍带边界标记的隐式空闲链表:
在这里插入图片描述
图7-5 一个简单的堆块的格式
相对于普通的隐式空闲链表,带边界标记的隐式空闲链表在块结尾处添加了一个脚部,其中脚部就是头部的一个副本,分配器可以通过检查一个块的脚部,判断前面一个块的起始位置和状态。这个脚部总是在距当前块开始位置的一个字的距离。
接着介绍一些分配器利用隐式空闲链表管理块的策略:
1.放置已分配的块
当一个应用请求一个k字节的块时,分配器搜索空闲链表,查找一个足够大可以放置所请求块的空闲块。分配器执行这种搜索方式是由放置策略确定的。常见的放置策略有三种:首次适配、下一次适配、最佳适配。
(1)首次适配:从头开始搜索空闲链表,选择第一个合适的空闲块。
(2)下一次适配:从上一次查询结束的地方开始,搜索空闲链表,选择第一个合适的空闲块
(3)最佳适配:检查每个空闲块,选择合适所需请求大小的最小空闲块。
2.分割空闲块
如果分配器为请求匹配的空闲块不太好,那么分配器通常会选择将这个空闲块分割为两部分。第一部分变成分配块,而剩下的变成一个新的空闲块。
3.合并空闲块
对于合并空闲块,分配器可以选择立即合并或者推迟合并。立即合并就是在每次一个块被释放时,就合并所有的相邻块。推迟合并是等到某个稍晚的时候再合并空闲块。有了脚部后,空闲块的合并变得简单起来。通过脚部和头部来访问当前空闲块的上一个块和下一个块,若有同为空闲块的块,那么修改块的头部和脚部即可合并空闲块。
7.10本章小结
本章学习了hello的内存管理,介绍了hello的存储器地址空间、intel逻辑地址到线性地址的变换、hello的线性地址到物理地址的变换、TLB与四级页表支持下的VA到PA的变换、三级cache支持下的物理内存的访问、hello进程fork时的内存映射、hello进程execve时的内存映射、缺页故障与缺页中断处理和动态存储分配管理,通过对这些结构的了解,我们可以学会编写一些对高速缓存相对友好的代码。
(第7章 2分)

第8章 hello的IO管理
8.1 Linux的IO设备管理方法
所有的 IO 设备都被模型化为文件,而所有的输入和输出都被 当做对相应文件的读和写来执行,这种将设备优雅地映射为文件的方式,允许 Linux 内核引出一个简单低级的应用接口,称为 Unix I/O。
设备的模型化:文件
设备管理:unix io接口
8.2 简述Unix IO接口及其函数
Unix I/O接口:
1.打开文件。一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个I/O 设备。内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件。内核记录有关这个打开文件的所有信息。应用程序只需记住这个描述符。
2.Linux shell 创建的每个进程开始时都有三个打开的文件:标准输入(描述符为0) 、标准输出(描述符为1) 和标准错误(描述符为2) 。头文件< unistd.h> 定义了常量STDIN_FILENO 、STOOUT_FILENO 和STDERR_FILENO, 它们可用来代替显式的描述符值。
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函数:
1.进程是通过调用open 函数来打开一个已存在的文件或者创建一个新文件的:
int open(char *filename, int flags, mode_t mode);
open 函数将filename 转换为一个文件描述符,并且返回描述符数字。返回的描述符总是在进程中当前没有打开的最小描述符。flags 参数指明了进程打算如何访问这个文件,mode 参数指定了新文件的访问权限位。
返回:若成功则为新文件描述符,若出错为-1。
2.进程通过调用close 函数关闭一个打开的文件。
int close(int fd);
返回:若成功则为0, 若出错则为-1。
3.应用程序是通过分别调用read 和write 函数来执行输入和输出的。
ssize_t read(int fd, void *buf, size_t n);
read 函数从描述符为fd 的当前文件位置复制最多n 个字节到内存位置buf 。返回值-1表示一个错误,而返回值0 表示EOF。否则,返回值表示的是实际传送的字节数量。
返回:若成功则为读的字节数,若EOF 则为0, 若出错为-1。
ssize_t write(int fd, const void *buf, size_t n);
write 函数从内存位置buf 复制至多n 个字节到描述符fd 的当前文件位置。
返回:若成功则为写的字节数,若出错则为-1。
8.3 printf的实现分析
printf函数:
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;
}
arg 获得输出的时候格式化串对应的值。
vsprintf函数:
int vsprintf(char *buf, const char fmt, va_list args)
{
char p;
char tmp[256];
va_list p_next_arg = args;
for (p=buf;*fmt;fmt++)
{
if (*fmt != ‘%’)
{
*p++ = *fmt;
continue;
}
fmt++;
switch (*fmt)
{
case ‘x’:
itoa(tmp, ((int)p_next_arg));
strcpy(p, tmp);
p_next_arg += 4;
p += strlen(tmp);
break;
case ‘s’:
break;
default:
break;
}
}
return (p - buf);
}
vsprintf接受确定输出格式的格式字符串fmt,用格式字符串对个数变化的参数产生格式化输出,返回要打印出来的字符串的长度。
write函数:
write:
mov eax, _NR_write
mov ebx, [esp + 4]
mov ecx, [esp + 8]
int INT_VECTOR_SYS_CALL
表示要通过系统来调用sys_call这个函数。
Sys_call函数:
sys_call:
call save
push dword [p_proc_ready]

sti

push ecx
push ebx
call [sys_call_table + eax * 4]
add esp, 4 * 3

mov [esi + EAXREG - P_STACKBASE], eax

cli

ret
syscall将字符串中的字节从寄存器中通过总线复制到显卡的显存中,字符显示驱动子程序将通过ASCII码在字模库中找到点阵信息将点阵信息存储到vram中。
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本章小结
本章讲述了linux的I/O设备管理机制,介绍了linux的I/O设备管理方法、Unix的接口及其函数、printf的实现分析、getchar的实现分析。
(第8章1分)
结论
1.hello.c经过预处理生成hello.i。
2.hello.i经过编译生成hello.s。
3.hello.s经过汇编生成可重定位目标文件hello.o。
4.hello.o在链接器与加载器的作用下与动态链接库libc.so链接生成可执行目标文件hello。
5.shell调用fork函数为hello程序创建子进程。
6.shell调用execve函数,加载映射虚拟内存,进入程序入口后开始载入物理内存,进入main函数。
7.MMU将程序中使用的虚拟地址翻译为物理地址,通过物理地址访问内存。
8.printf 会调用 malloc 向动态内存分配器申请堆中的内存。
9.hello程序可接受一些信号并进行信号处理。
10.shell回收hello进程和它的子进程。
(结论0分,缺失 -1分,根据内容酌情加分)

附件
1.源程序hello.c。
2.hello.c经预处理后hello.i。
3.hello.i经编译后的形成的汇编程序hello.s。
4.hello.s经汇编后的形成的可重定位目标文件hello.o。
5.hello.o经过链接的形成的可执行目标文件hello。
6.hello.o的反汇编文件helloo.objdump。
7.hello的反汇编文件hello.objdump。
8.hello的ELF格式hello.elf。
(附件0分,缺失 -1分)

参考文献
为完成本次大作业你翻阅的书籍与网站等
[1] Randal E.Bryant. Computer Systems:A Programmer’s Perspective,Third Edition.北京:机械工业出版社,2016.7.
[2]林来兴. 空间控制技术[M]. 北京:中国宇航出版社,1992:25-42.
[3] 辛希孟. 信息技术与信息服务国际研讨会论文集:A集[C]. 北京:中国科学出版社,1999.
[4] 赵耀东. 新时代的工业工程师[M/OL]. 台北:天下文化出版社,1998 [1998-09-26]. http://www.ie.nthu.edu.tw/info/ie.newie.htm(Big5).
[5] 谌颖. 空间交会控制理论与方法研究[D]. 哈尔滨:哈尔滨工业大学,1992:8-13.
[6] KANAMORI H. Shaking Without Quaking[J]. Science,1998,279(5359):2063-2064.
[7] 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.

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值