Hello在我这里坎坷而又坎坷的一生

Hello在我这里坎坷而又坎坷的一生
第1章 概述
1.1 Hello简介
hello程序首先由用户从键盘,根据高级语言的语法,形成能被编译的hello.c程序。hello.c在linux中经过cpp预处理变成hello.i,ccl的编译得到hello.s(汇编程序),as的汇编得到hello.o(可重定位目标程序),最后经过ld的连接变成hello(可执行程序)。在shell中,在启动之后,shell为可执行文件hello创建一个子进程fork。于是hello就从program变成了progress。这也就是hello的P2P:From Program to Process。
之后,shell通过execve函数加载hello进入内存,映射虚拟内存。进入程序入口之后开始由虚拟内存转换成物理内存。之后进入main函数执行代码,CPU在执行hello时,可能会打出系统中断等信号,将控制权暂时交给其他程序,也就是分配时间片执行逻辑控制流。hello程序执行中,会调用函数向屏幕输出。当程序结束后,shell父进程负责回收子进程,内核删除相关数据,避免产生僵尸程序。以上就是020:From Zero-0 to Zero-0的过程
1.2 环境与工具
硬件环境:X64 CPU;3.3GHz;8G RAM;128GSSD + 1T DISK;
软件环境:Windows10 64位;Vmware 14;Ubuntu 18.04 LTS 64位;
开发工具:Visual Code;gdb/ gcc;
1.3 中间结果
在这里插入图片描述
1.4 本章小结
本章主要介绍了hello程序从代码的生成,到编译,执行,最后到终止的过程(P2P和020)。还列出了研究后续内容所需要的软硬件环境和工具。

第2章 预处理
2.1 预处理的概念与作用
概念:在编译之前进行的处理(预处理命令以符号“#”开头)C语言的预处理主要有三个方面的内容: 1.宏定义; 2.文件包含; 3.条件编译。 1. 宏定义:将宏名替换为文本(这个文本可以是字符串、可以是代码等),将所有的#define删除,并且展开所有的宏定义。2. 文件包含:处理#include <文件名>,编译时以包含处理以后的文件为编译单位,被包含的文件是源文件的一部分。将被包含的文件插入到该预编译指令的位置3. 条件编译:处理所有条件编译指令,如#if,#ifdef等,使用条件编译可以使目标程序变小,运行时间变短。
作用:预处理是C语言的一个重要功能,它由预处理程序负责完成。当对一个源文件进行编译时,系统将自动引用预处理程序对源程序中的预处理部分作处理,处理完毕自动进入对源程序的编译。合理使用预处理功能编写的程序便于阅读、修改、移植和调试,也有利于模块化程序设计
2.2在Ubuntu下预处理的命令命令:
gcc -E hello.c -o hello.i
在这里插入图片描述
2.3 Hello的预处理结果解析
在这里插入图片描述
hello.i文件较长,可以看见最下面时hello.c的代码,但是缺少带#的预处理部分。原文件中的宏进行了宏展开,头文件中的内容被包含到了hello.i文件中。可以看到,文件的长度变成了3118行。
2.4 本章小结
本章主要介绍了hello.c文件在linux中进行预处理的方式,并且对处理后的结果进行了简略的解释。完成本阶段之后,可以进行下一阶段的汇编处理了。

第3章 编译
3.1 编译的概念与作用
编译时利用编译程序从源语言编写的源程序产生目标程序的过程。用编译程序产生目标程序的动作。 编译就是把高级语言变成计算机可以识别的2进制语言,计算机只认识1和0,编译程序把人们熟悉的语言换成2进制的。
编译程序把一个源程序翻译成目标程序的工作过程分为五个阶段:词法分析;语法分析;语义检查和中间代码生成;代码优化;目标代码生成。主要是进行词法分析和语法分析,又称为源程序分析,分析过程中发现有语法错误,给出提示信息。
3.2 在Ubuntu下编译的命令
gcc -S hello.i
在这里插入图片描述3.3 Hello的编译结果解析
3.3.1 数据
1.全局变量int sleepsecs,编译器将其编译成:
在这里插入图片描述
2.字符串Usage: Hello 1170300121 刘诗琦!\n 字符串被编码成 UTF-8 格式,Hello %s %s\n 后两个字符串都声明在了.rodata 只读数据节。 在这里插入图片描述

3.3.2 赋值
1.全局变量int sleepsecs=2.5:
在这里插入图片描述
在hello.c中,包含一个全局变量int sleepsecs=2.5。经过编译之后,sleepsecs被存放在.rodata节中。
2.局部变量int i: 在这里插入图片描述
定义是没有初始化,只是给它开辟了栈空间。
3.之后将i赋值为0,是用movl的操作 在这里插入图片描述
整型数据的赋值使用 mov 指令完成,后缀体现了数据的大小

3.3.3 类型转换
对于sleepsecs:在这里插入图片描述
由于sleepsecs被定义为int型,所以为其赋初值2.5后,会被强制转换成2。当在 double 或 float 向 int 进行类型转换的时候,程序改变数值和位模式的原则是:值会向零舍入。

3.3.4 算术操作以hello程序里的“i++”为例,通过ADD操作实现。 在这里插入图片描述
使用addl $1, -4(%rbp),实现i++。 在这里插入图片描述

3.3.5 关系操作
1.以hello程序里的“i<10”为例 在这里插入图片描述
在.L3部分中,使用i与9进行比较来实现i<10进入.L4的操作
2.在实现argc!=3中,与上述例子类似 在这里插入图片描述

3.3.6 数组操作
printf函数里面的一系列对指针和对数组的操作编译器编译为在这里插入图片描述
对数组的索引实际上就是在第一个元素地址的基础上通过加索引值乘以数据大小来实现。

3.3.7 控制转移
1.if (argv!=3): 在这里插入图片描述
首先 cmpl 比较 argv 和 3,不相等就直接跳转到.L2,否则顺序执行下一条语句,即执行 if 中的代码。
2.for(i=0;i<10;i++): 在这里插入图片描述
首先跳转到.L3中,使用 cmpl 进行比较,如果 i<=9,则跳入.L4 for 循环体执行,否则说明循环结束,之后顺序执行 在这里插入图片描述
在.L4中再执行i++,之后再进入.L3,使用cmpl进行比较。

3.3.8 函数操作
函数调用的之前,调用者会将其参数压入栈中,及其返回地址,也就是在函数运行完后回到调用者代码指令的地址。
1.调用printf函数: 在这里插入图片描述
先记录返回值0,再调用printf
2.调用sleep函数: 在这里插入图片描述
函数见互相调用的实现:在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
3.4 本章小结
本章先介绍了在linux系统中编译的命令行gcc -S hello.i,并按照各数据类型和操作,结合原理和示例,将hello.s中的汇编代码给出了了具体的解释。
编译器将hello.i文件编译成hello.s文件。经过编译后,hello就变成了机器更加易于理解的汇编语言,为下面生成机器代码奠定了基础。

第4章 汇编
4.1 汇编的概念与作用
汇编是指汇编器(as)将hello.s类型的汇编程序翻译成机器语言指令,把这些指令打包成可重定位目标程序的格式,并将结果保存到目标文件hello.o中。hello.o是一个2进制的文件,它包括程序的指令代码。
由于汇编更接近机器语言,能够直接对硬件进行操作,生成的程序与其他的语言相比具有更高的运行速度,占用更小的内存,因此在一些对于时效性要求很高的程序、许多大型程序的核心模块以及工业控制方面大量应用。
4.2 在Ubuntu下汇编的命令
gcc -c hello.s -o hello.o 在这里插入图片描述
4.3 可重定位目标elf格式 在这里插入图片描述
使用readelf –h hello.o查看文件头信息。根据文件头的信息,可以知道该文件是可重定位目标文件,有13个节(Number of section headers: 13) 在这里插入图片描述
使用readelf -S hello.o查看节头表。可以得到各节的大小,以及可以进行的操作 在这里插入图片描述
在这里插入图片描述
使用readelf -s hello.o可以查看符号表的信息 在这里插入图片描述
4.4 Hello.o的结果解析 在这里插入图片描述
在这里插入图片描述
objdump -d hello.o > helloo.txt 将hello.o的反汇编输出到文本文件helloo.txt中。将
该反汇编结果与第3章的hello.s进行比较,可以发现其主要流程没有不同,以下为不同的地方
1.操作数在hello.s里面都是十进制,在到hello.o里都以十六进制储存。
2.涉及到地址的语句中地址都变成了相对偏移地址。
机器语言是二进制的机器指令的集合,机器指令又是由操作码和操作数构成的,是电脑真正可以识别的语言。
汇编语言的主体是汇编指令,是以人们比较熟悉的词句直接表述CPU动作形成的语言,是最接近CPU运行原理的较为通俗的比较容易理解的语言。
机器语言与汇编语言具有一一对应的映射关系,一条机器语言程序对应一条汇编语言语句。汇编指令和机器指令的差别在于指令的表示方法上,汇编指令是机器指令便于记忆的书写格式。
4.5 本章小结
本章介绍了hello从hello.s到hello.o的汇编过程。首先给出了汇编的命令行,将其转换成可重定位目标文件,机器可以直接处理它。可以通过readelf读取其elf信息与重定位信息,得到其符号表的相关信息。之后通过反汇编,将其结果与.s汇编程序代码进行比较,了解了2者间的区别与联系。为下一步连接奠定了基础。

第5章 链接
5.1 链接的概念与作用
连接是指将分别在不同的目标文件中的各种函数和静态库及动态库连接,并将它们打包成目标文件,即可执行文件。连接器的核心工作就是符号表解析和重定位。
通过链接可以实现将头文件中引用的函数并入到程序中。
5.2 在Ubuntu下链接的命令
ld /usr/lib/x86_64-linux-gnu/crt1.o /usr/lib/x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/7/crtbeginT.o -L/usr/lib/gcc/x86_64-linux-gnu/7 hello.o -lc -lgcc -dynamic-linker /lib64/ld-linux-x86-64.so.2 /usr/lib/gcc/x86_64-linux-gnu/7/crtend.o /usr/lib/x86_64-linux-gnu/crtn.o -o hello 在这里插入图片描述
5.3 可执行目标文件hello的格式
使用 readelf -a hello > hello.elf 命令生成 hello 程序的 ELF 格式文件。 在这里插入图片描述在这里插入图片描述在这里插入图片描述
5.4 hello的虚拟地址空间在这里插入图片描述
5.5 链接的重定位过程分析
使用 objdump -d -r hello > hello.objdump 获得 hello 的反汇编代码。 在这里插入图片描述
(连接后反汇编)
使用objdump -d -r hello.out得到反汇编结果。可以明显发现该结果与hello.o的反汇编结果不同。可执行文件的反汇编结果中给出了重定位结果,即虚拟地址的确定。而hello.o的反汇编结果中,各部分的开始地址均为0。 在这里插入图片描述
(未连接时的反汇编)
并且在链接的过程中,已经将库函数与hello.o链接,加入了库函数的汇编部分。 在这里插入图片描述
经过链接:函数的个数增加,头文件的函数加入至代码中;各类相对寻址确定,动态库函数指向PLT;函数的起始地址也得到了确定。
5.6 hello的执行流程
<ld-2.27.so!_dl_start+0> 0x00007f3c1345aea0
<ld-2.27.so!_dl_init+0> 0x00007fa3b4312630
<hello!_start+0> 0x0000000000400500
<libc-2.27.so!__libc_start_main+0> 0x00007f60e06f4ab0
<hello!main+2b> 0x0000000000400648
<hello!puts@plt+0> 0x00000000004004b0
<ld-2.27.so!_dl_fixup+0> 0x00007f60e0ad3df0
<ld-2.27.so!_dl_lookup_symbol_x+0> 0x00007f60e0acf0b0
<libc-2.27.so!puts+0> 0x00007f60e07539c0
<hello!exit@plt+0> 0x00000000004004e0
5.7 Hello的动态链接分析
在dl_init调用之前,每一条PIC函数调用,调用的目标地址都实际指向PLT中的代码逻辑,在之后的函数调用时,首先跳转到PLT执行.plt中逻辑,第一次访问跳转时GOT地址为下一条指令,将函数序号压栈,然后跳转到PLT[0],在PLT[0]中将重定位表地址压栈,然后访问动态链接器,在动态链接器中使用函数序号和重定位表确定函数运行时地址,重写GOT,再将控制传递给目标函数。之后如果对同样函数调用,第一次访问跳转直接跳转到目标函数。
动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序虽然动态链接把链接过程推迟到了程序运行时,但是在形成可执行文件时(注意形成可执行文件和执行程序是两个概念),还是需要用到动态链接库。
5.8 本章小结
本章主要介绍了连接的概念,作用。首先给出了在linux系统下连接的命令行,让其在linux系统中生成.out类型可执行目标文件。除此之外,还分析了连接过程的具体细节。这个阶段之后,就得到了可执行的二进制文件。这样,程序就可以作为进程通过虚拟内存机制直接运行了。

第6章 hello进程管理
6.1 进程的概念与作用
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
进程是操作系统中最基本、重要的概念。是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律引进的一个概念,所有多道程序设计操作系统都建立在进程的基础上。
6.2 简述壳
Shell-bash的作用与处理流程Shell-bash是内核与用户之间的桥梁,充当命令解释器的作用,将用户输入的命令翻译给系统执行。Shell可以帮助用户打开文件或者代替执行一些工作。
处理流程:
①将读入的命令进行切分,获得参数信息
②如果是内置命令,就立即执行
③如果不是内置命令,就分配子程序运行命令
④shell也可以接受信号,并对信号进行相应的处理
6.3 Hello的fork进程创建过程
shell作为父进程通过fork函数为hello创建一个新的进程。子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份拷贝。 在这里插入图片描述
以下为创建子进程fork的一个例子 在这里插入图片描述
6.4 Hello的execve过程 在这里插入图片描述
创建进程后,在子进程中通过判断即fork()函数的返回值,如果处于子进程,则会通过execve函数在当前进程的上下文中加载并运行一个新程序。execve加载并运行可执行目标文件,且带参数列表argv和环境变量列表envp。只有当出现错误时,execve才会返回到调用程序。
6.5 Hello的进程执行 在这里插入图片描述
以下为参考书上的几个概念:
逻辑控制流:一系列程序计数器 PC 的值的序列叫做逻辑控制流,进程是轮流使用处理器的,在同一个处理器核心中,每个进程执行它的流的一部分后被抢占(暂时挂起),然后轮到其他进程。
时间片:一个进程执行它的控制流的一部分的每一时间段叫做时间片。
用户模式和内核模式:处理器通常使用一个寄存器提供两种模式的区分,该寄存器描述了进程当前享有的特权,当没有设置模式位时,进程就处于用户模式中,用户模式的进程不允许执行特权指令,也不允许直接引用地址空间中内核区内的代码和数据;设置模式位时,进程处于内核模式,该进程可以执行指令集中的任何命令,并且可以访问系统中的任何内存位置。
上下文信息:上下文就是内核重新启动一个被抢占的进程所需要的状态,它由通用寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构等对象的值构成
下面就hello sleep进程调度进行简单的分析处理:
在执行hello程序一直到执行sleep之前,如果发生了被抢占的情况,就进行上下文切换,是内核中调度器完成的。当内核又调度新的进程运行后,它就会抢占当前进程,并进行 ①保存以前进程的上下文
②恢复新恢复进程被保存的上下文
③将控制传递给这个新恢复的进程 ,来完成上下文切换
当hello进程在用户模式,调用sleep之后,进入内核模式,内核模式处理sleep休眠请示,将hello从运行队列中移除,加入到等待队列。定时器开始计时,内核进行上下文切换,将控制权交给其他进程,当定时器(2.secs)到时后,发送一个中断信号,则是进入内核状态执行中断处理。将hello从等待队列中移除,加入到运行队伍,直至内核又调度新的进程,hello再抢占当前进程,就可以急促控制逻辑流了。
6.6 hello的异常与信号处理
hello在执行的过程中,可能会出现处理器外部I/O设备引起的异常,执行指令导致的陷阱、故障和终止。第一种被称为外部异常,常见的有时钟中断、外部设备的I/O中断等。第二种被称为同步异常。
陷阱指的是有意的执行指令的结果。
故障是非有意的可能被修复的结果。
而终止是非故意的不可修复的致命错误。
常见信号种类如下: 在这里插入图片描述
1.正常运行:在这里插入图片描述
2.CTRL+C在这里插入图片描述
3.CTRL+Z在这里插入图片描述
4.CTRL+Z 后 ps在这里插入图片描述6.CTRL+Z 后 jobs在这里插入图片描述
7.TRL+Z 后 pstree在这里插入图片描述
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
8.CTRL+Z 后 fg 再停止
在这里插入图片描述9.CTRL+Z 后 kill ps在这里插入图片描述
10.执行中乱按
在这里插入图片描述
6.7 本章小结
在本章中,先是说明了进程的定义,作用。之后简单介绍了shell的一般处理流程。程序在shell中执行是通过fork函数及execve创建新的进程并执行程序。并且针对hello的程序,大致说明了shell的工作流程,分析了在程序运行过程中,计算机硬件、软件和操作系统之间的配合和协作的方式。。
程序在运行过程中难免会遇到异常,本章也解释异常的种类。而且针对不同信号和异常,程序是如何处理的,也给出了完整的截图。

第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:有地址变换功能的计算机中,访内指令给出的地址 (操作数) 叫逻辑地址,也叫相对地址。要经过寻址方式的计算或变换才得到内存储器中的实际有效地址,即物理地址。在hello的程序中,程序的地址都是相对地址,因为hello在运行之前并不知道将会被放在哪个实际的内存当中,所以采用相对寻址的方式可以让程序有更大的灵活性。(就是 hello.o 里面的相对偏移地址。)
线性地址:线性地址是一个32位无符号整数,可以用来表示高达4GB的地址,也就是,高达4294967296个内存单元。线性地址通常用十六进制数字表示,值的范围从0x00000000到0xffffffff)。(就是 hello 里面的虚拟内存地址。)
虚拟地址:CPU启动保护模式后,程序运行在虚拟地址空间中。并不是所有的“程序”都是运行在虚拟地址中。CPU在启动的时候是运行在实模式的,Bootloader以及内核在初始化页表之前并不使用虚拟地址,而是直接使用物理地址的。Hello在执行时会被分配一个虚拟地址,虚拟地址需要通过地址翻译成物理地址才可以访问内存。(hello.s中使用的就是虚拟空间的虚拟地址)
物理地址:在计算机科学中,物理地址,也叫实地址、二进制地址,它是在地址总线上,以电子形式存在的,使得数据总线可以访问主存的某个特定存储单元的内存地址。在和虚拟内存的计算机中,物理地址这个术语多用于区分虚拟地址。Hello访问内存最终需要物理地址对内存的访问(就是 hello 在运行时虚拟内存地址对应的物理地址。)
7.2 Intel逻辑地址到线性地址的变换-段式管理
段描述符是一种数据结构,实际上就是段表项,分两类:用户的代码段和数据段描述。系统控制段描述符,又分两种:特殊系统控制段描述符,包括:局部描述符表(LDT)描述符和任务状态段(TSS)描述符控制转移类描述符,包括:调用门描述符、任务门描述符、中断门描述符和陷阱门描述符
段描述符的定义: 在这里插入图片描述
被选中的段描述符先被送至描述符cache,每次从描述符cache中取32位段基址,与32位段内偏移量(有效地址)相加得到线性地址在这里插入图片描述
7.3 Hello的线性地址到物理地址的变换-页式管理
虚拟内存被组织为一个由存放在磁盘上的N个连续的字节大小的单元组成的数组。磁盘上数组的内容被缓存在物理内存中 (DRAM cache)。这些内存块被称为页。虚拟页存在未分配的、缓存的、未缓存的三种状态。其中缓存的页对应于物理页。 在这里插入图片描述
页表 是一个页表条目 (Page Table Entry, PTE)的数组,将虚拟页地址映射到物理页地址。 DRAM中的每个进程都使用的核心数据结构 在这里插入图片描述
从虚拟地址到物理地址的翻译通过MMU(内存管理单元),它通过虚拟地址索引到对应的PTE,如果已缓存则命中。
1) 处理器生成一个虚拟地址,并将其传送给MMU
2-3) MMU 使用内存中的页表生成PTE地址
4) MMU 将物理地址传送给高速缓存/主存
5) 高速缓存/主存返回所请求的数据字给处理器 在这里插入图片描述
否则不命中称为缺页。
1) 处理器将虚拟地址发送给 MMU
2-3) MMU 使用内存中的页表生成PTE地址
4) 有效位为零, 因此 MMU 触发缺页异常
5) 缺页处理程序确定物理内存中牺牲页 (若页面被修改,则换出到磁盘)
6) 缺页处理程序调入新的页面,并更新内存中的PTE7) 缺页处理程序返回到原来进程,再次执行缺页的指令 在这里插入图片描述
7.4 TLB与四级页表支持下的VA到PA的变换 在这里插入图片描述
上图解释了如何将虚拟地址通过四级页表翻译成物理地址的过程
第1~3级页表条目格式: 在这里插入图片描述
第4级页表条目格式: 在这里插入图片描述
7.5 三级Cache支持下的物理内存访问 在这里插入图片描述
在这里插入图片描述
CPU 发出一个虚拟地址再 TLB 里搜索,如果命中,直接发送到 L1cache 里,如果没有命中,就现在也表里加载到之后再发送过去,到了 L1 中,寻找物理地址又要检测是否命中,如果没有命中,就向 L2/L3 中查找。
7.6 hello进程fork时的内存映射
当shell调用fork时,内核会复制一份页表的复制,并分配给它一个唯一的 PID,为了给这个新进程创建虚拟内存,它创建了当前进程的mm_struct、区域结构和页表的原样副本。它将这两个进程的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。
7.7 hello进程execve时的内存映射
Execve函数在当前进程中加载并运行包含可执行目标文件hello.out中的程序,加载、运行 hello 需要以下步骤
1 . 删除已存在的用户区域。删除 shell 虚拟地址的用户部分中的已存在的区域结构。
2. 映射私有区域。为 hello 的代码、数据、bss 和栈区域创建新的区域结构。所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为hello 文件中的.text 和.data 区。bss 区域是请求二进制零的,映射到匿名文件,其大小包含在 hello 中。栈和堆区域也是请求二进制零的,初始长度为零。图 7.7 概括了私有区域的不同映射。
3. 映射共享区域。如果 hello 程序与共享对象(或目标)链接,比如标准 C 库libc. so, 那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内。
4. 设置程序计数器(PC) 。execve 做的最后一件事情就是设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。在这里插入图片描述
7.8 缺页故障与缺页中断处理
Page fault缺页: 虚拟内存中的字不在物理内存中 (DRAM 缓存不命中)在这里插入图片描述
缺页故障:当指令引用一个虚拟地址,在 MMU 中查找页表时发现与该地址相对应的物理地址不在内存中,因此必须从磁盘中取出的时候就会发生故障在这里插入图片描述
缺页中断处理:选择一个牺牲页面,换入新的页面并更新页表。当缺页处理程序返回时,CPU 重新启动引起缺页的指令,这条指令再次发送 VA 到MMU,这次 MMU 就能正常翻译 VA 了。在这里插入图片描述
7.9动态存储分配管理
Printf会调用malloc。
在程序运行时程序员使用动态内存分配器 (比如 malloc) 获得虚拟内存.数据结构的大小只有运行时才知道.动态内存分配器维护者一个进程的虚拟内存区域,称为堆. 在这里插入图片描述
分配器将堆视为一组不同大小的 块(blocks)的集合来维护,每个块要么是已分配的,要么是空闲的。
分配器的类型
显式分配器: 要求应用显式地释放任何已分配的快
隐式分配器: 应用检测到已分配块不再被程序所使用,就释放这个块
1. 带边界标签的隐式空闲链表在这里插入图片描述
在内存块中增加 4B 的 Header 和 4B 的 Footer,其中 Header 用于寻找下一个 blcok,Footer 用于寻找上一个 block。
2.显示空间链表基本原理
将空闲块组织成链表形式的数据结构。堆可以组织成一个双向空闲链表,在每个空闲块中,都包含一个 pred(前驱)和 succ(后继)指针将空闲块组织成链表形式的数据结构。堆可以组织成一个双向空闲链表,在每个空闲块中,都包含一个 pred(前驱)和 succ(后继)指针在这里插入图片描述
使用双向链表而不是隐式空闲链表,使首次适配的分配时间从块总数的线性时间减少到了空闲块数量的线性时间。
动态内存管理的策略包括首次适配、下一次适配和最佳适配。首次适配会从头开始搜索空闲链表,选择第一个合适的空闲块。搜索时间与总块数(包括已分配和空闲块)成线性关系。会在靠近链表起始处留下小空闲块的“碎片”。下一次适配和首次适配相似,只是从链表中上一次查询结束的地方开始。比首次适应更快,避免重复扫描那些无用块。最佳适配会查询链表,选择一个最好的空闲块,满足适配,且剩余最少空闲空间。它可以保证碎片最小,提高内存利用率。
7.10本章小结
本章主要涉及从磁盘到内存,再到高速缓存,最后到寄存器的层级储存。介绍了hello的地址空间,段式管理,页式管理,VA-PA的变换,以及hello进程 fork 时的内存映射、execve 时的内存映射、缺页故障与缺页中断处理、动态存储分配管理。

第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件
设备管理:unix io 接口
所有的 I/ O 设备(例如网络、磁盘和终端)都被模型化为文件,而所有的输入和输出都被当作对相应文件的读和写来执行。这种将设备优雅地映射为文件的方式,允许 Linux 内核引出一个简单、低级的应用接口,称为 Unix I/O,这使得所有的输入和输出都能以一种统一且一致的方式来执行,这就是Unix I/O 接口。
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.关闭文件。当应用完成了对文件的访问之后,它就通知内核关闭这个文件。作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可计算机系统课程报告- 42 -用的描述符池中。无论一个进程因为何种原因终止时,内核都会关闭所有打开的文件并释放它们的内存资源。
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 的当前文件位置
8.3 printf的实现分析
https://www.cnblogs.com/pianist/p/3315801.html
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.
查看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函数:在这里插入图片描述
按照格式 fmt 结合参数 args 生成格式化之后的字符串,并返回字串的长度。 在这里插入图片描述
在这里插入图片描述
Write函数中:ecx 是字符个数,ebx 存放第一个,然后进入sys_call函数l 将字符串中的字节“Hello 1170300121 刘诗琦”从寄存器中通过总线复制到显卡的显存中,显存中存储的是字符的 ASCII 码
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
所以最后“Hello 1170300121 刘诗琦”就显示在屏幕上了。
8.4 getchar的实现分析
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章(对应书上第十章)主要介绍了 Linux 的 IO 设备管理方法、Unix IO 接口及其函数,分析了printf 函数和 getchar 函数的实现。
了解Unix I/O 将帮助我们理解其他的系统概念。在经过本章的实验后,了解了系统级IO的工作原理

结论

  1. 程序员在ide中中写下 hello.c;

  2. 预处理器通过对头文件、注释的处理得到 hello.i;

  3. 编译器翻译成汇编语言得到 hello.;

  4. 汇编器处理得到可重定位目标文件 hello.o;

  5. 链接器将 hello.o 和如 printf.o 的其他可重定位目标文件链接得到可执行目标文件 hello;

  6. 在 shell 里运行hello;

  7. fork 创建子进程,shell 调用;

  8. 运行程序,调用 execve;

  9. 执行指令,为 hello 分配时间片,hello 执行自己的逻辑控制流;

  10. 访问内存,将虚拟地址映射成物理地址;

  11. hello 对不同的信号会执行不同操作;

  12. Killhello,回收子进程;

    一年前,还只是简单的停留在了.c文件的基础上,在.c上废了好大的功夫,当初只想着,自己完成了最难的部分,其他的编译,连接肯定很容易。但经过hello的一生,才终觉自己当初年少轻狂。原来简简单单的hello在实现的时候,却也经过许多的波折。从hello的一生,我确实是将教材大部分内容都整理起来,形成了一个完整的路线,今后也会考虑到hello的后半生,而好好书写它的前半生。

附件
在这里插入图片描述

参考文献[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.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值