2.2在Ubuntu下预处理的命令............................................................................. - 5 -
5.3 可执行目标文件hello的格式........................................................................ - 8 -
6.2 简述壳Shell-bash的作用与处理流程........................................................ - 10 -
6.3 Hello的fork进程创建过程......................................................................... - 10 -
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 -
8.2 简述Unix IO接口及其函数.......................................................................... - 13 -
第1章 概述
1.1 Hello简介
根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
1 预处理:将源程序文本中#开头的宏定义对应的代码文本添加到文件中
2 编译:将源程序文本转化为汇编语言文本
3 汇编:汇编文本通过符号解析,重定位,转化为重定位二进制文件
4 链接:将多个重定位二进制文件以及其中包含的库文件组合为可执行目标程序。
图1.1.1:hello的p2p过程
P2P:hello.c的源程序从process,通过预处理,编译,汇编,链接,最终生成可执行文件hello,在shell中键入./hello+学号+姓名+秒数,shell自动fork一个新的子进程,再调用execve将shell程序加载到新的子进程中,由此实现了由程序program到进程process的转变,即为p2p。
020:execve将程序加载到新创建的进程中,通过虚拟内存映射将程序从磁盘
.3 中间结果
源程序:hello.c
预处理:hello.i处理头文件
编译:hello.s查看汇编
汇编:hello.elf查看elf条目
对hello反汇编:hello_ld查看重定位之后汇编的变化
可执行目标文件elf表:hello1.elf对比可执行的elf和可重定位的elf
可执行文件hello
2.1 预处理的概念与作用
2.1.1预处理的概念:
- 概念:预处理器根据以字符”#”开头的命令,处理hello.c源程序。
2.1.2作用: 1、对#include的文件进行包含
2、对代码中#define定义的常量进行替换
3、对代码中的所有注释进行删除
2.2在Ubuntu下预处理的命令
图2.2.1 hello 预处理为.i文件
2.3 Hello的预处理结果解析
图2.3.1 hello.c的头文件
图2.3.2 .i文件的内容
可以发现预处理将三个头文件中的内容加载到了hello.i中,包含一些变量定义,内置函数。
2.4 本章小结
这章讲述了预处理的作用,处理方式,宏定义是如何处理的,符号的替代。
第3章 编译
3.1 编译的概念与作用
3.1.1编译的概念:编辑器将文本文件hello.i翻译成文本文件hello.s,即一个汇编语言程序。
3.1.2编译的作用:将代码翻译成汇编代码,语法分析,语义分析,词法分析,以及符号的汇总。
3.2 在Ubuntu下编译的命令
图3.2.1编译指令
3.3 Hello的编译结果解析
图3.3.1字符串
3.3.1字符串:程序中有两个字符串,这两个字符串都在只读数据段中,如图所示:
3.3.2赋值
图3.3.2赋值
把提示字符串给rdi作为参数
3.3.3运算:
图3.3.3减法
Rsp变址寄存器的位置减少32开辟栈空间
3.3.4比较:
图3.3.4跳转
比较当前变量和7大小,大于7时跳转。
3.3.5指针:
图3.3.5函数调用
函数调用也是指针,对应操作:将函数跳转地址放入rip中,将返回地址即下一条指令地址压栈,进行跳转指令,结束后ret将返回地址数取出到rip中执行。
3.3.6局部变量:
图3.3.6局部变量定义
将0赋值给一个局部变量
3.3.7循环:
图3.3.7循环结构
设置变量0到7循环,循环7次,睡眠一次输出一次字符串,直到局部变量大于7结束。
3.4 本章小结
本章分析了hello的汇编代码,包含多种操作,麻雀虽小五脏俱全,字符串数组,指针,函数调用,栈,局部变量,循环,分析了相关的指令。
第4章 汇编
4.1 汇编的概念与作用
经过编译和汇编后生成的是可重定位目标文件,是包含二进制代码和数据的.o文件,可以与其他文件结合。
4.2 在Ubuntu下汇编的命令
图4.2.1汇编命令
4.3 可重定位目标elf格式
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位
图4.3.1elf的结构
汇编后产生的是.o文件,具体的格式是一个ELF节头后面跟着许多节,每个节存放不同的数据,前四个用来保存程序相关数据,在.text中存放程序执行的机器码,各种函数都在里面,在.rodata中存放只读数据,比如字符串。在.data中存放已经定义好的全局和静态变量,在.bss中存放未定义或者定义为0的全局或者静态变量。符号表比较特殊,存放了所有需要重定位的字符的信息,包括位置(相对于所在节的偏移量),类型(是函数还是变量),是本地还是全局。
下面的rel.text和rel.data存放ELF可重定位条目,汇编器工作时遇到了未知符号就把他的信息放在可重定位条目中,注意:在可重定位目标文件中的value时相对地址,在可执行目标文件是绝对地址,根据可重定位条目的信息分为绝对引用和相对引用,这样就确定了每个符号的运行时地址。
图4.3.1 elf头的信息
Elf头列出了魔数,判断是否为.o可重定位目标文件,列出文件的大小,小端存储,类型rel,节头部的大小64bytes,起始位置
图4.3. elf节头的信息
节头:列出了节的:记录各节名称、类型、地址、偏移量、大小、全体大小、旗标、链接、信息、对齐。
由于尚未重定位,所以地址都是0,只记录了偏移量。
图4.3.3 符号表的信息
符号表一共有17个,有一些是自带的函数,main是我们定义的。
4.4 Hello.o的结果解析
objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
图4.4.1hello.s的汇编
图4.4.2Hello_elf的汇编
对比可以发现如下特征:
1:hello.s中只有按顺序列出的汇编语言
而可重定位目标文件列出了操作码和汇编指令.
2 :call指令在.s文件中只有.L助记符号,而在重定位文件是跳转的相对位置。同时出现的所有定位符号都有 重定位信息的32位占位符号
图4.4.1hello.s的汇编
图4.4.2Hello_elf的汇编
3:同样是减去32,在hello.s中是10进制的,而在hello_elf中是16进制的
4.5 本章小结
本章总结了hello的汇编文件格式,分析和.s文件的区别,主要说明了汇编代码对未知符号是如何处理的,以及可重定位条目的内容,信息形式。
第5章 链接
5.1 链接的概念与作用
概念:链接是将各种代码和数据片段收集并合成为一个单一文件的过程,这个文件可被加载(复制)到内存并执行。链接可以执行于编译时,也就是在源代码被翻译成机器代码时;也可以执行于加载时,也就是在程序被加载器加载到内存并执行时;甚至执行于运行时,也就是由应用程序来执行。
作用:链接器在软件开发过程中扮演着一个关键的角色,因为它们使得分离编译成为可能。我们不用将一个大型的应用程序组织为一个巨大的源文件,而是可以把它分解为更小、更好管理的模块,可以独立地修改和编译这些模块。当我们改变这些模块中的一个时,只需简单地重新编译它,并重新链接应用,而不必重新编译其它文件。
5.2 在Ubuntu下链接的命令
使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件
图5.2.1链接命令
5.3 可执行目标文件hello的格式
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
图5.3.1可执行目标文件结构
图5.3.2hello.elf的ELF头
图5.3.3ELF节头位置
图5.3.4符号表
5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
图5.4.1Edb中的程序开始地址
图5.4.2可执行文件的elf
从edb中可以发现和elf中查看的程序开始位置是一致的,相应的信息虽然在内核态但是我们可以从elf中发现他们的位置,他们的地址是一致的。
5.5 链接的重定位过程分析
图5.5.1 重定位符号表
符号表列出了需要重定位的所有符号,标记了他们的类型,有global,local,
许多是内部用到的函数,由于没有定义全局变量和静态变量所以都是函数。
图5.5.2.o文件
图5.5.3可执行目标文件
举例分析:
这里call指令操作码e8 后面操作数32位占位符,在.o尚未定位的文件中可以发现占位符是空的,但是在执行文件中占位符表示的是call指令后的下一条指令和跳转目标指令的相对距离,可以发现,40128c+5之后再加上重定位占位符的数字(表示偏移量)恰好就是401000,即完成了重定位。
5.6 hello的执行流程
图5.6.1hello的执行过程
开始执行前,进入到程序入口,在0x4010f0处,_start,在加载系统启动函数时调用_libc_start_main跳转到403ff0。
在main 函数中,
4010a0 <strtol@plt>
4010b0 <__printf_chk@plt>
401090 <puts@plt>
4010c0 <exit@plt>
4010b0 <__printf_chk@plt>
4010e0 <getc@plt>
5.7 Hello的动态链接分析
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
.got.plt起始表的位置为0x404000。
开始时GOT表调用dl_init前0x404008后的16个字节均为0;
调用dl_init后的.got.plt:
从图中可以看到.got.plt的条目已经发生变化
5.8 本章小结
本章研究了链接的过程。通过edb查看hello的虚拟地址空间,对比hello与hello.o的反汇编代码,深入研究了链接的过程中重定位的过程。
第6章 hello进程管理
6.1 进程的概念与作用
进程:进程是一种对处理器,主存,IO设备的抽象,也是运行中程序的实例
进程给应用程序的两个抽象:逻辑控制流:程序好像单独占用cpu,
私有地址空间:程序好像单独占用主存。
程序和进程的区别:程序是一堆代码和数据,可以作为目标文件存在于磁盘上,或者作为段存在于地址空间中。进程是执行中程序的一个具体的实例,在进程中可以创建程序。
进程的作用:进程作为一个执行中程序的实例,系统中每个程序都运行在某个进程的上下文中,上下文是由程序正确运行所需的状态组成的。这个状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。
6.2 简述壳Shell-bash的作用与处理流程
作用:交互型程序界面
处理流程:
1 shell输入命令
2 shell命令行解释器构造argv和envp;
3调用fork()函数创建子进程
4 调用execve()函数创建新的子进程,调用一次后不返回。
6.3 Hello的fork进程创建过程
Fork 其地址空间与shell父进程完全相同,包括只读代码段、读写数据段、堆及用户栈等,调用一次执行两次,并发执行,父进程和子进程独立同时运行,
拥有相同但是独立的地址空间,共享文件,父子进程的输出都会显示在屏幕上。
6.4 Hello的execve过程
1 删除已存在的用户区域。删除之前进程在用户部分中已存在的结构。
2创建新的代码、数据、堆和栈段。所有这些区域结构都是私有的,写时复制的。虚拟地址空间的代码和数据区域被映射为hello文件的.txt和.data区。bss区域是请求二进制零的,映射匿名文件,其大小包含在hello文件中。栈和堆区域也是请求二进制零的,初始长度为零。
3映射共享区域。如果hello程序与共享对象链接,比如标准C库libc.so,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域。
4设置程序计数器(PC)。exceve做的最后一件事就是设置当前进程的上下文中的程序计数器,使之指向代码区域的入口点。
6.5 Hello的进程执行
进程上下文信息:这些代码从可执行文件载入到进程的地址空间执行。一般程序在用户空间执行当一个程序调用了系统调用或者触发了某个异常,它就陷入了内核空间。此时,我们称内核“代表进程执行”并处于进程上下文。
进程上下文实际上是进程执行活动全过程的静态描述。我们把已执行过的进程指令和数据在相关寄存器与堆栈中的内容称为上文,把正在执行的指令和数据在寄存器和堆栈中的内容称为正文,把待执行的指令和数据在寄存器与堆栈中的内容称为下文。具体的说,进程上下文包括计算机系统中与执行该进程有关的各种寄存器(例如通用寄存器,程序计数器PC,程序状态字寄存器PS等)的值,程序段在经过编译过后形成的机器指令代码集,数据集及各种堆栈值PCB结构。这里,有关寄存器和栈区的内容是重要的,例如没有程序计数器PC和程序 状态寄存器PS,CPU将无法知道下一条待执行指令的地址和控制有关操作。
进程时间片:一个程序执行到打断分配的时间。
用户态与核心态的转换:用户态是用户层面可以操作的操作,核心态是系统内核比较私密的操作,一些系统内函数需要特定的指令调用,异常控制流中的陷阱。保证了系统的安全性。
6.6 hello的异常与信号处理
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
图6.6.1正常情况
输出8次信息
图6.6.2ctrl+c
按下ctrl+c向当前运行程序发送终止信号,程序终止,用jobs显示程序状态空
图6.6.3 ctrl+z
按下ctrl+z向当前进程发送暂停,jobs显示程序暂停
图6.6.4 pstree
图6.6.5不停乱按
原因:父进程和子进程共享文件,在子进程运行时,父进程同时运行shell把输出显示在屏幕上。
6.7本章小结
本章了解了hello的执行过程,以及使用shell,如何向当前进程发送信号,对信号的处理方式,以及fork和execve的过程。
第7章 hello的存储管理
7.1 hello的存储器地址空间
1)逻辑地址:段地址:偏移地址
23:8048000 段寄存器(CS等16位):偏移地址(16/32/64)
实模式下: 逻辑地址CS:EA =物理地址CS*16+EA
保护模式下:以段描述符作为下标,到GDT/LDT表查表获得段地址,
段地址+偏移地址=线性地址。
2)线性地址:逻辑地址向物理地址转化过程中的一步,逻辑地址经过段机制后转化为线性地址,为描述符:偏移量的组合形式,分页机制中线性地址作为输入,最多表示4G的大小,用来转化虚拟地址。
3)虚拟地址:就是线性地址。
4)物理地址:CPU通过地址总线的寻址,找到真实的物理内存对应地址。CPU对内存的访问是通过连接着CPU和北桥芯片的前端总线来完成的。在前端总线上传输的内存地址都是物理内存地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
段式管理:存储器中地址线48根,分段地址和偏移地址两部分,段地址就是16位的段选择符,包括三个部分,
图7.2.1段选择符的结构
索引表示距离段描述表起始地址的偏移量,索引*8+起始地址=
每一页可以是4kb,最多4G的总大小,找到段描述条目之后,就找到了对应的线性基地址,再加上偏移地址(可以是16、32、64)位,就得到了线性地址。
图7.2.2转化为段基址
7.3 Hello的线性地址到物理地址的变换-页式管理
线性地址就是虚拟地址,也分为两部分索引加偏移量,根据索引寻找页表条目,对应的基地址加上偏移地址即为物理地址。
缺页处理:如果对应的页表条目后映射的物理地址不在内存中,那么就在磁盘中找到对应数据,写入到一个牺牲页中,同时将原来对应于牺牲页内存的条目映射到磁盘中的牺牲页数据段。
添页:如果对应的条目映射是null,就把null指向新添加的磁盘位置。
图7.2.3缺页处理
7.4 TLB与四级页表支持下的VA到PA的变换
TLB:在地址翻译单元MMU中的集合,可以直接通过VPN返回PTE条目也就是物理页的位置。具有良好的局部性。TLB也是组相连的,把VPN前一段作为标记,后一段索引找组,确定好在哪一行就得到了PPN的位置。
如果没有找到就需要正常在页表中找。
图7.4.1地址翻译的过程
四级页表:由于地址空间(物理内存)空间很大,页表大小也会随之增加,访问速度和空间都会受到制约,选择分层管理,将虚拟地址分成几块,类似于TLB,将VPN分成多块,分别在目录,页号,页偏移
图7.4.2多级页表的结构
VPN分段,每一段都是偏移地址,用基地址加上偏移地址就得到了新的基地址,最后能得到物理页所在的基址。
7.5 三级Cache支持下的物理内存访问
图7.5.1存储器的结构
高速缓存也成为cache,目前计算机大多使用四cache,前三个为cpu中 的寄存器,高速缓存1,2,3。
Cache下的物理访存可以分为读取内存和写入内存。
读取内存:首先,在高速缓存中找到查找所需字w的副本,如果命中,返回字w,如果不命中,从较低的存储结构中取出包含子w的块并将它写回高速缓存中。
对于写入,分为写入命中和写入不命中,它们分别有两种不同的处理方式。
写入命中时:分为直写和写回,直写:直接在对应的高速缓存写,对应的不命中处理方式是非分配,直接写入对应物理内存中。
而写回:直到快驱逐更新的块时才开始写入缓存,对应写分配,不命中时把对应低层次的块写入高速缓存,再更改这个高速缓存块。
7.6 hello进程fork时的内存映射
Fork创建时和父进程相同的虚拟地址空间相同(但是独立)一份副本,包括代码数据,堆,共享库,以及用户栈,子进程和父进程还获得与父进程任何打开文件描述符相同的副本。
每个进程有相同的用户栈,本地变量,堆,全局变量,代码,但是他们是独立的进程都有自己的私有地址空间,父进程和子进程对变量的更爱都是独立的。
7.7 hello进程execve时的内存映射
execve函数在当前进程中加载并运行新程序a.out的步骤:
- 删除已存在的用户区域
- 创建新的区域结构
- 私有的、写时复制
- 代码和初始化数据映射到.text和.data区(目标文件提供)
- .bss和栈堆映射到匿名文件 ,栈堆的初始长度0
- 共享对象由动态链接映射到本进程共享区域
- 设置PC,指向代码区域的入口点
- Linux根据需要换入代码和数据页面
图7.7.1execve的内存映射
7.8 缺页故障与缺页中断处理
页面命中完全是由硬件完成的,而处理缺页是由硬件和操作系统内核协作完成的,在指令请求一个虚拟地址时,MMU中查找页表,如果这时对应得物理地址没有存在主存的内部,我们必须要从磁盘中读出数据。在虚拟内存的习惯说法中,DRAM缓存不命中成为缺页。在发生缺页后系统会调用内核中的一个缺页处理程序,选择一个页面作为牺牲页面。具体流程如下:
1)处理器生成一个虚拟地址,并将它传送给MMU
2)MMU生成PTE地址,并从高速缓存/主存请求得到它
3)高速缓存/主存向MMU返回PTE
4)PTE中的有效位是0,所以MMU出发了一次异常,传递CPU中的控制到操作系统内核中的缺页异常处理程序。
5)缺页处理程序确认出物理内存中的牺牲页,如果这个页已经被修改了,则把它换到磁盘。
6)缺页处理程序页面调入新的页面,并更新内存中的PTE。
7)缺页处理程序返回到原来的进程,再次执行导致缺页的命令。CPU将引起缺页的虚拟地址重新发送给MMU。因为虚拟页面已经换存在物理内存中,所以就会命中。
7.9动态存储分配管理
分配器将堆视为一组不同大小的 块(blocks)的集合来维护,每个块要么是已分配的,要么是空闲的。
分配器的类型
显式分配器: 要求应用显式地释放任何已分配的快
例如,C语言中的 malloc 和 free
隐式分配器: 应用检测到已分配块不再被程序所使用,就释放这个块
动态内存管理的基本方法与策略:
动态内存分配器维护着一个进程的虚拟内存区域,称为堆,系统之间细节不同,但是不失通用性,假设堆是一个请求二进制零的区域,它紧接在未初始化的数据区域后开始,并向上生长(向更高地址)。对于每个进程,内核维护着一个变量brk,它指向对的顶部。
分配器将堆视为一组不同大小的块的集合来维护。每个块就是一个个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显示地保留为供应用程序使用。空闲块可用来分配。空闲块保持空闲,直到它显示地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显示执行的。要么是内存分配器自身隐式执行的。分配器有两种基本风格。两种风格都要求应用显示地分配块。他们的不同之处在于由哪个实体来负责释放已分配的块。
使用双向链表而不是隐式空闲链表,使首次适配的分配时间从块总数的线性
时间减少到了空闲块数量的线性时间。维护链表的顺序有:后进先出(LIFO),将新释放的块放置在链表的开始处,使用LIFO的顺序和首次适配的放置策略,分配器会最先检查最近使用过的块,在这种情况下,释放一个块可以在线性的时间内完成,如果使用了边界标记,那么合并也可以在常数时间内完成。按照地址顺序来维护链表,其中链表中的每个块的地址都小于它的后继的地址,在这种情况下,释放一个块需要线性时间的搜索来定位合适的前驱。平衡点在于,按照地址排序首次适配比LIFO排序的首次适配有着更高的内存利用率,接近最佳适配的利用率。
7.10本章小结
本章介绍了存储器地址空间、段式管理、页式管理,VA 到 PA 的变换、物理内存访问, hello 进程fork时和execve 时的内存映射、缺页故障与缺页中断处理、包括隐式空闲链表和显式空闲链表的动态存储分配管理。
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
Linux将文件所有的I/O设备都模型化为文件,甚至内核也被映射为文件。这种将设备优雅地映射为文件的方式,允许Linux内核引出一个简单、低级的应用接口,称为Unix I/O。Linux就是基于Unix I/O实现对设备的管理。
设备的模型化:文件
设备管理:unix io接口
8.2 简述Unix IO接口及其函数
Unix IO接口:
1.打开文件。一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个I/O设备,内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件,内核记录有关这个打开文件的所有信息。应用程序只需记住这个描述符。
2.Linux Shell创建的每个进程都有三个打开的文件:标准输入(描述符为0),标准输出(描述符为1),标准错误(描述符为2)。
3.改变当前的文件位置。对于每个打开的文件,内核保持着一个文件位置k,初始为0,这个文件位置是从文件开头起始的字节偏移量,应用程序能够通过执行seek,显式地将改变当前文件位置k。
4.读写文件。一个读操作就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增加到k+n,给定一个大小为m字节的而文件,当k>=m时,触发EOF。类似一个写操作就是从内存中复制n>0个字节到一个文件,从当前文件位置k开始,然后更新k。
5.关闭文件。当应用完成了对文件的访问之后,它就通知内核关闭这个文件,作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。无论一个进程因为何种原因终止时,内核都会关闭所有打开的文件并释放他们的内存资源。
Unix I/O函数:
1.int open(char* filename,int flags,mode_t mode)
进程通过调用open函数来打开一个存在的文件或是创建一个新文件的。open函数将filename转换为一个文件描述符,并且返回描述符数字,返回的描述符总是在进程中当前没有打开的最小描述符,flags参数指明了进程打算如何访问这个文件,mode参数指定了新文件的访问权限位。
2.int close(fd)
进程通过调用close函数关闭一个打开的文件,fd是需要关闭的文件的描述符。
3.ssize_t read(int fd,void *buf,size_t n)
read函数从描述符为fd的当前文件位置赋值最多n个字节到内存位置buf。返回值-1表示一个错误,0表示EOF,否则返回值表示的是实际传送的字节数量。
4.ssize_t wirte(int fd,const void *buf,size_t n)
write函数从内存位置buf复制至多n个字节到描述符为fd的当前文件位置。
8.3 printf的实现分析
vsprintf函数将所有的参数内容格式化之后存入buf,返回格式化数组的长度。write函数将buf中的i个元素写到终端。从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章介绍了Linux的IO设备管理方法,Unix IO接口及其函数,分析了printf函数和getchar函数的实现。
结论
用计算机系统的语言,逐条总结hello所经历的过程。
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
Hello.c程序员编写的代码,以前只是觉得编程序太难了,各种复杂的指令,可是现在回头看小小的hello原来这么不简单!从不觉所以的头文件开始,原来头文件一开始就被处理掉了,宏定义的符号被替换,库文件加入源程序中,再到汇编,作为操作的简化抽象,对寄存器,堆栈,跳转指令都比0101的机器码简化了不少,这种完美利用硬件的设计真是perfect,也许正是计算资源,内存资源的稀缺才让人类花尽心思设计用尽量少的资源计算,精细的划分内存,堆栈,虚拟内存,让可能少的数据线访问更多的数据,段寄存器和页式管理节约了空间,加快了访问速度,最重要的确定了一个用一致的地址线长就能做到从虚拟内存到物理内存的访问。链接做到了把所有的文件整合到一起,又能毫无差错的定位到相应的地址。hello的所有过程都是有明确目的的,每一部分都为了下一步准备,又具有良好的兼容性。
总结一下:程序的生成必须依赖于良好的硬件结构和设计框架,我们一方面要尽量遵守之前的框架,学习大佬和前人的智慧加以应用,又要创新,像多级页表,分块的思想都是很伟大的想法,一句话,计算机的精髓就是分层,分块,就像人和社会一样,每个人都有自己的圈层,加强交流的同时做好自己分工内的事情,就能发挥巨大的力量,
附件
源程序:hello.c
预处理:hello.i处理头文件
编译:hello.s查看汇编
汇编:hello.elf查看elf条目
对hello反汇编:hello_ld查看重定位之后汇编的变化
可执行目标文件elf表:hello1.elf对比可执行的elf和可重定位的elf
可执行文件hello