摘 要
本文通过分析一个hello.c的完整的生命周期,hello.c源程序从预处理到编译到汇编到链接等一系列操作完成从源程序到可执行程序的转化,讲解了Linux计算机系统执行一个程序的完整过程。
关键词:操作系统,进程,程序的生命周期
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
第1章 概述 - 4 -
1.1 Hello简介 - 4 -
1.2 环境与工具 - 4 -
1.3 中间结果 - 4 -
1.4 本章小结 - 4 -
第2章 预处理 - 5 -
2.1 预处理的概念与作用 - 5 -
2.2在Ubuntu下预处理的命令 - 5 -
2.3 Hello的预处理结果解析 - 5 -
2.4 本章小结 - 5 -
第3章 编译 - 6 -
3.1 编译的概念与作用 - 6 -
3.2 在Ubuntu下编译的命令 - 6 -
3.3 Hello的编译结果解析 - 6 -
3.4 本章小结 - 6 -
第4章 汇编 - 7 -
4.1 汇编的概念与作用 - 7 -
4.2 在Ubuntu下汇编的命令 - 7 -
4.3 可重定位目标elf格式 - 7 -
4.4 Hello.o的结果解析 - 7 -
4.5 本章小结 - 7 -
第5章 链接 - 8 -
5.1 链接的概念与作用 - 8 -
5.2 在Ubuntu下链接的命令 - 8 -
5.3 可执行目标文件hello的格式 - 8 -
5.4 hello的虚拟地址空间 - 8 -
5.5 链接的重定位过程分析 - 8 -
5.6 hello的执行流程 - 8 -
5.7 Hello的动态链接分析 - 8 -
5.8 本章小结 - 9 -
第6章 hello进程管理 - 10 -
6.1 进程的概念与作用 - 10 -
6.2 简述壳Shell-bash的作用与处理流程 - 10 -
6.3 Hello的fork进程创建过程 - 10 -
6.4 Hello的execve过程 - 10 -
6.5 Hello的进程执行 - 10 -
6.6 hello的异常与信号处理 - 10 -
6.7本章小结 - 10 -
第7章 hello的存储管理 - 11 -
7.1 hello的存储器地址空间 - 11 -
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 -
7.9动态存储分配管理 - 11 -
7.10本章小结 - 12 -
第8章 hello的IO管理 - 13 -
8.1 Linux的IO设备管理方法 - 13 -
8.2 简述Unix IO接口及其函数 - 13 -
8.3 printf的实现分析 - 13 -
8.4 getchar的实现分析 - 13 -
8.5本章小结 - 13 -
结论 - 14 -
附件 - 15 -
参考文献 - 16 -
第1章 概述
1.1 Hello简介
根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
P2P:由高级语言C语言编写的hello.c源程序文本文件,通过编译器的预处理,生成hello.i文本文件,再经过编译变成汇编语言文本文件hello.s。然后经过汇编器处理生成hello.o可重定位目标程序二进制文件。最后通过链接器的链接和重定位,生成可执行目标程序ELF二进制文件。在shell中键入命令后,目标文件执行,shell为其fork产生子进程并通过execve函数加载子进程,至此完成了从程序到进程的转变P2P(From Program to Process)
020:
Shell通过execve函数将hello加载至内存,顺着逻辑控制流,通过软件硬件的结合,最终实现hello在屏幕上的输出。进程结束后,操作系统回收hello进程,实现O2O:From Zero-0 to Zero-0。
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
硬件环境
X64 CPU;2GHz;2G RAM;256GHD Disk 以上
软件环境
Windows7 64位以上;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64位/优麒麟 64位;
开发工具
GCC;GDB/OBJDUMP;DDD/EDB等
1.3 中间结果
预处理后的文本文件
hello.s :
hello.o :hello.s汇编后的可重定位目标文件
hello_objdump :hello的反汇编代码
hello.0_objdump :hello.o的反汇编代码
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
hello.c hello源代码
hello.i hello.c预处理后的文本文件
hello.s hello.i编译后的汇编文件
hello.o hello.s汇编后的可重定位目标文件
hello 链接后的可执行文件
1.4 本章小结
本章介绍了P2P和020的过程,以及为编写本论文使用的软硬件环境和开发与调试工具,同时列出了本实验得到的中间结果文件及其作用。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
概念:预处理是指在编译前做出的处理。
预处理器(cpp)根据以字符#开头的命令(宏定义(#define)、文件包含(#include)、条件编译(#ifdef)),修改原始的C程序。比如将头文件从库中提取出来,然后插入到程序文本中,得到一个完整的源程序,通常以.i作为文件扩展名。合理使用预处理功能编写的程序便于阅读、修改、移植和调试,也有利于模块化程序设计。
2.2在Ubuntu下预处理的命令
gcc -E hello.c > hello.i
cpp hello.c > hello.i
2.3 Hello的预处理结果解析
修改得到的C程序hello.i从hello.c的二十行增加到3047行,同时发现main函数在文件的最后部分。
在main函数之前,预处理器(cpp)读取头文件stdio.h 、stdlib.h 、和unistd.h中的内容,三个系统头文件依次展开。同时如果还含有#开头的宏定义,预处理器会对此继续递归展开,最终的hello.i程序中没有#define。
2.4
本章介绍了预处理的过程,hello.c文件通过预处理生成hello.i文件,并且对hello.c的头文件和宏定义进行了展开。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序的过程。
编译器,是指把用高级程序设计语言书写的源程序,翻译成等价的汇编语言格式目标程序的翻译程序,便于计算机理解和处理。除了基本功能之外,编译程序还具备语法检查、调试措施、修改手段、覆盖处理、目标程序优化、不同语言合用以及人机联系等重要功能。
3.2 在Ubuntu下编译的命令
gcc -S hello.i -o hello.s
3.3 Hello的编译结果解析
此部分是重点,说明编译器是怎么处理C语言的各个数据类型以及各类操作的。应分3.3.1~ 3.3.x等按照类型和操作进行分析,只要hello.s中出现的属于大作业PPT中P4给出的参考C数据与操作,都应解析。
指令 含义
.file 声明汇编的源文件名
.text 代码段
.globl 声明全局变量
.data 代码段
.align 对齐方式
.type 声明对象或函数类型
.size 声明大小(字节数)
.long/.string 声明long或string类型
标签.LC0:表示紧接着的字符串的名称是 .LC0.
标签main:表示指令 pushq %rbp是main函数的第一个指令
3.3.1 数据
整型变量:
1.int sleepsecs
在hello.c定义全局变量sleepsecs并赋初值为2.5,编译器对其进行了隐式的类型转换,下图中,编译器在.text段声明sleepsec为全局变量,在.data段设置对齐方式为4字节,类型为object,大小为4字节,设置为long类型其值变成2。
2.int argc argc是main函数传入的参数个数
3.int i 局部变量通常保存在寄存器或栈中,这里的i在.L2被声明,存放在-4(%ebp),占据四字节。
常量:在hello.s中一些如3和9的常量以立即数的形式出现。
数组:
hello.c中涉及数组只有一个char argv[],函数执行的命令行参数。
字符串:两个字符串都被存放在.rodata段,作为全局变量
3.3.2 赋值
2.开辟栈空间
3.3.5 关系操作
cmpl设置条件码,根据结果进行下一步的跳转。
3.3.6 数组操作
指针数组:char *argv[]:在argv数组中,argv[0]指向输入程序的路径和名称,argv[1]和argv[2]分别表示两个字符串。通过(%rax)和%rax+8,分别得到argv[1]和argc[2]两个字符串,并将其分别存储在%rdx和%rsi两个寄存器中
3.3.7 控制转移
if(argc!=3)
比较3和-20(%ebp)的值是否相同,相同则跳转到L2
设置i=0后跳转到L3
for(i=0;i<10;i++)
当i大于9时跳出循环,否则进入.L4循环体内部执行。
3.3.8 函数操作
1.main函数
参数传递:int argc,char *argv[],分别用寄存器%rdi和%rsi存储
函数调用:主函数
函数返回:return 0
2printf函数
参数传递:字符串地址
函数返回:void
3.exit函数
参数传递:1
函数调用:argc!=3时被调用
函数返回:void
4.sleep函数
参数传递:sleepsecs
函数调用:for循环中调用
5.getchar
函数调用:main函数中调用
3.4 本章小结
本章介绍了编译的过程,hello.i文件通过编译生成hello.s文件,将C语言转变成汇编语言,通过对比hello.c和hello.s可以发现汇编对C语言数据和操作的实现。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程
汇编程序是把汇编语言书写的程序翻译成与之等价的机器语言程序的翻译程序,才能被机器识别。
4.2 在Ubuntu下汇编的命令
gcc -c hello.s -o hello.o
4.3 可重定位目标elf格式
通过readelf -a hello.o语句查看hello.o的elf格式
elf头:描述整个文件的组织:
节头表:包含文件各个section的属性信息
重定位节:重定位的信息
符号表:记录程序中定义和引用的函数和全局变量的信息
4.4 Hello.o的结果解析
反汇编命令:objdump -d -r hello.o 分析hello.o的反汇编,并与第3章的 hello.s进行对照分析。
机器语言由二进制字节码组成。在不同的设备中,汇编语言对应不同的机器语言指令集,通过汇编过程转换成机器指令。机器语言与汇编语言具有一一对应的映射关系,一条机器语言程序对应一条汇编语言语句,但不同平台之间不可直接移植。
分支转移:汇编语言中,跳转语句后面跟着L3、L4等段名称,而机器语言后面是具体的跳转地址
函数调用:汇编语言中,函数调用直接通过函数名称实现,而机器语言中是通过函数偏移地址调用。
4.5 本章小结
本章实现了hello.s到hello.o,汇编语言到机器语言的转换过程,并分析了二者的映射关系和不同点。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
链接是指从 hello.o 到hello生成过程。
链接将各种代码和数据片段收集并组合成为一个单一文件,这个单一文件可以被加载到内存中执行,链接使得分离编译成为可能。
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的格式
根据文件头的信息,可以知道该文件是可执行目标文件,有25个节
根据hello的段头表可以得到各段的基本信息,包括各段的起始地址,大小。
5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
.text段起始地址是0x401080,大小是0x121
5.5 链接的重定位过程分析
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
通过比较,hello与hello.o有以下不同:
1.hello中加入了libc.so这个动态链接共享库的函数,如puts、getchar、printf等。
2.hello.o中的相对偏移地址变成了hello中的虚拟内存地址,重定位的值得到确定,跳转不再需要偏移量实现。
5.6 hello的执行流程
0000000000401000 <_init>:
0000000000401020 <.plt>:
0000000000401030 puts@plt:
0000000000401040 printf@plt:
0000000000401050 getchar@plt:
0000000000401060 exit@plt:
0000000000401070 sleep@plt:
0000000000401080 <_start>:
00000000004010b0 <_dl_relocate_static_pie>:
00000000004010b1 :
0000000000401140 <__libc_csu_init>:
00000000004011a0 <__libc_csu_fini>:
00000000004011a4 <_fini>:
5.7 Hello的动态链接分析
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
5.8 本章小结
本章介绍了从hello.o到hello的链接过程,通过链接和重定位,等到了可执行的二进制程序。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
进程的概念:进程是一个执行中程序的示例
进程的作用:
1.每次用户通过向shell输入一个可执行目标文件的名字,运行程序时,shell就会创建一个新的进程,然后在这个进程的上下文中运行这个可执行目标文件。应用程序也能够创建新进程,并且在新进程的上下文中运行它们自己的代码或其他应用程序。
2.进程提供给应用程序的关键抽象:一个独立的逻辑控制流,好像我们的程序独占地使用处理器;一个私有的地址空间,好像我们的程序独占地使用内存系统。
6.2 简述壳Shell-bash的作用与处理流程
Shell是用户和Linux内核之间的接口程序,用户输入的命令通过shell解释,然后传给Linux内核,然后将内核的处理结果翻译给用户。
处理流程:首先shell读取用户输入的命令并进行解析,得到参数列表,然后检查这条命令是否是内核命令,如果是则直接执行,如果不是则fork子进程,启动加载器在当前进程中加载并运行程序。
6.3 Hello的fork进程创建过程
父进程通过调用fork函数创建一个新的运行的子进程:
pid_t fork(void);
fork子进程时,系统创建一个与父进程几乎但不完全相同的子进程,子进程得到与父进程用户级虚拟地址空间相同但独立的一份副本,包括代码、数据段、堆、共享库以及用户栈,子进程获得与父进程任何打开文件描述符相同的副本,这就意味着当父进程调用fork时,子进程可以读写父进程中的内容,但它们有着不同的PID,在父进程中,fork返回子进程的PID,在子进程中,fork返回0。
6.4 Hello的execve过程
execve函数加载并运行可执行目标文件:
int execve(const *filename, const char *argv[], const char *envp[]);
其中filename是可执行目标文件,argv是参数列表,envp是环境变量列表
它调用一次,从不返回,只有出现错误时execve才会返回到调用程序
Loader删除子进程现有的虚拟内存段,创建一组新的段(栈与堆初始化为0),并将虚拟地址空间中的页映射到可执行文件的页大小的片chunk,新的代码与数据段被初始化为可执行文件的内容,然后跳到_start,新程序启动后的栈结构如下:
6.5 Hello的进程执行
上下文:进程的物理实体(代码和数据等)和支持进程运行的环境合称为进程的上下文;由进程的程序块、数据块、运行时的堆和用户栈(两者通称为用户堆栈)等组成的用户空间信息被称为用户级上下文,用户级上下文地址空间和系统级上下文地址空间一起构成了一个进程的整个存储器映像。
进程时间片:进程时间片即CPU分配给每个进程的时间
Hello进程调度的过程:首先shell通过加载器加载可执行目标文件hello,操作系统进行上下文切换,切换到hello的进程中,这是我们在用户态,接下来hello调用sleep函数,进入内核态,当sleep的时间到时定时器发送一个中断信号,通过信号处理函数处理,通过上下文切换再进入hello进程,回到用户态。
6.6 hello的异常与信号处理
在程序运行过程中,键入Ctrl-C,会向当前进程发送一个SIGINT信号,从而使当前进程中断
输入回车会被忽略
在程序运行过程中输入Ctrl-Z,那么会发送一个 SIGTSTP 信号给前台进程组中的进程,从而将其挂起
输入ps查看进程及其运行时间
输入jobs查看当前暂停的进程
输入fg使进程在前台执行
使用kill杀死特定进程
6.7本章小结
本章简述了进程的概念与作用,以及shell的执行流程,总结了fork和execve的运行过程,以及在上下文切换中用户态和内核态的切换,探究了在进程运行过程中不同信号的作用。
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:指机器语言指令中,用来指定一个操作数或者是一条指令的地址。一个逻辑地址,是由一个段标识符加上一个指定段内相对地址的偏移量。
线性地址(虚拟地址):跟逻辑地址类似,它也是一个不真实的地址,假设逻辑地址是相应的硬件平台段式管理转换前地址的话,那么线性地址则相应了硬件页式内存的转换前地址。
物理地址:用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应,是内存单元的真正地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
一个逻辑地址由两部分组成,段标识符和段内偏移量。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号。后面3位包含一些硬件细节。通过段标识符的前13位,可以直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段。
段描述符有三类,全局的段描述符,就放在“全局段描述符表(GDT)”中,局部的段描述符,例如每个进程自己的,就放在“局部段描述符表(LDT)”中,最后中断的描述符,就放在“中断描述符表IDT”中。
7.3 Hello的线性地址到物理地CPU中的一个控制寄存器,页表基址寄存器指向当前页表,n位的虚拟地址由虚拟页面偏移(VPO, n位)和虚拟页号(VPN, n - p位)组成。MMU利用VPN来选择适当的PTE(页表条目),将页表条目中的物理页号(PPN)与虚拟地址的页面偏移量(VPO)串联起来,就得到相应的物理地址。
页面命中时,CPU硬件执行的步骤如下:
页面不命中时,CPU硬件执行的步骤如下:
7.4 TLB与四级页表支持下的VA到PA的变换
TLB与四级页表支持下的在这里要用到翻译后备缓冲器(TLB),每个VA被分为VPN、VPO,每个VPN又分为三段,根据TLB标记(TLBT)和TLB索引(TLBI)到TLB中找对应的虚拟页号(PPN),找到的PPN+VPO即为物理地址。
在TLB模式下,如果能直接命中即可直接得到PPN,若发生缺页则要去页表里再进行查找,VPN被分为了4段,在查找时通过每一级页表一级一级往下找,最后找到相应的PPN,加上虚拟页面偏移量VPO即可得到物理地址。VA到PA的变换
7.5 三级Cache支持下的物理内存访问
在三级cache下,将物理地址分成CT(标记)+CI(索引)+CO(偏移量),首先在一级cache下找,若发生不命中miss则到下一级缓存即二级cache下找,若不命中则到三级cache下访问。
7.6 hello进程fork时的内存映射
当fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID。为了给这个新进程创建虚拟内存,它创建了当前进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。
当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同,当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,因此,也就为每个进程保持了私有地址空间。
7.7 hello进程execve时的内存映射
execve函数在当前进程中加载并运行新程序a.out时:
·删除已存在的用户区域。删除当前进程虚拟地址的用户部分中的已存在的区域结构。
·创建新的区域结构,这些新的区域都是私有的、写时复制的,代码和初始化数据映射到.text和.data区,.bss和栈堆映射到匿名文件。
·映射共享区域。如果a.out程序与共享对象链接,那么这些对象都是动态链接到这个程序的,再映射到用户虚拟地址空间中的共享区域内。
·设置程序计数器(PC)。设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。
7.8 缺页故障与缺页中断处理
当出现缺页故障时,即DRAM缓存不命中,此时调用缺页处理程序,内存会确定一个牺牲页,若页面被修改,则换出到磁盘,再将新的目标页替换牺牲页写入,缺页处理程序返回到原来的进程,重启导致缺页的指令。
7.9动态存储分配管理
7.9.1 隐式空闲链表
对于带边界标签的隐式空闲链表分配器,一个块是由一个字的头部、有效载荷、可能的一些额外的填充,以及在块的结尾处的一个字的脚部组成的。头部编码了这个块的大小(包括头部和所有的填充),以及这个块是已分配的还是空闲的。如果我们强加一个双字的对齐约束条件,那么块大小就总是8的倍数,且块大小的最低3位总是0。因此,我们只需要内存大小的29个高位,释放剩余的3位来编码其他信息。在这种情况中,我们用其中的最低位(已分配位)来指明这个块是已分配的还是空闲的。
头部后面就是应用调用malloc时请求的有效载荷。有效载荷后面是一片不使用的填充块,其大小可以是任意的。需要填充有很多原因。比如,填充可能是分配器策略的一部分,用来对付外部碎片。或者也需要用它来满足对齐要求。
我们将对组织为一个连续的已分配块和空闲块的序列,这种结构称为隐式空闲链表,是因为空闲块是通过头部中的大小字段隐含地连接着的。分配器可以通过遍历堆中所有的块,从而间接地遍历整个空闲块的集合。注意:此时我们需要某种特殊标记的结束块,可以是一个设置了已分配位而大小为零的终止头部。
Knuth提出了一种边界标记技术,允许在常数时间内进行对前面快的合并。这种思想是在每个块的结尾处添加一个脚部,其中脚部就是头部的一个副本。如果每个块包括这样一个脚部,那么分配器就可以通过检查它的脚部,判断前面一个块的起始位置和状态,这个脚部总是在距当前块开始位置一个字的距离。
7.9.2 显式空闲链表
显式空闲链表在空闲块中使用指针连接空闲块。它将空闲块组织为某种形式的显式数据结构,只保留空闲块链表,而不是所有块。在每个空闲块中,都包含一个前驱(pred)和后继(succ)指针。
维护显式空闲链表有两种方式,分别为后进先出(LIFO)和按照地址顺序来维护。后进先出的顺序维护将新释放的块放置在链表的开始处,使用LIFO的顺序和首次适配的放置策略,分配器会最先检查最近使用过的块。按照地址顺序来维护链表时,链表中每个块的地址都小于它的后继的地址,按照地址排序的首次适配比LIFO排序的首次适配有更高的内存利用率,接近最佳适配的利用率。
7.9.3 寻找空闲块
·首次适配 (First fit):从头开始搜索空闲链表,选择第一个合适的空闲块。
·下一次适配 (Next fit):从链表中上一次查询结束的地方开始搜索空闲链表,选择第一个合适的空闲块。
·最佳适配 (Best fit):查询链表,选择一个最好的空闲块。
7.9.4 分配空闲块——分割
当分配块比空闲块小时,我们可以把空闲块分割成两部分,并将多余的空间重新加到空闲链表中,以减少内部碎片。
7.9.5 空闲块的合并——带边界标记的合并
合并后面的空闲块时,当前块的头部指向下一个块的头部,可以检查这个指针以判断下一个块是否是空闲的,如果是,就将它的大小简单地加到当前块头部的大小上,使得两个块在常数的时间内被合并。
合并前面的空闲块时,在每个块的结尾处添加一个脚部,它是头部的一个副本,分配器可以通过检查它的脚部判断前一个块的起始状态和状态。
7.10本章小结
本章简述了在计算机中的虚拟内存管理,虚拟地址、物理地址、线性地址、逻辑地址的区别以及它们之间的变换,linux 系统下CPU产生的虚拟地址是如何被翻译成物理地址的,以及段式、页式的管理模式,在了解了内存映射的基础上重新认识了共享对象、fork和execve,同时认识了系统动态内存的分配方法。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:所有IO设备都被模型化为文件,所有的输入和输出都能被当做相应文件的读和写来执行。
设备管理:Linux内核有一个简单、低级的接口,成为Unix I/O,是的所有的输入和输出都能以一种统一且一致的方式来执行。
8.2 简述Unix IO接口及其函数
(以下格式自行编排,编辑时删除)
8.3 printf的实现分析
(以下格式自行编排,编辑时删除)
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本章小结
(以下格式自行编排,编辑时删除)
(第8章1分)
结论
C语言编写的代码保存在hello.c中,然后用GCC编译器驱动程序读取源文件hello.c,经过预处理器的预处理变为hello.i,再经过编译器变为汇编程序hello.s,再经过汇编器变为可重定位的二进制目标程序hello.o,最后经过链接器生成hello可执行的二进制目标程序;
在shell中经过了fork和execve,把hello加载到其中;
然后在磁盘中去读取它,在运行的过程中,shell接受键盘的信号做出不同的处理,映射虚拟内存进行访问,进行动态内存分配;
最后进程终止,进程被shell回收。
(结论0分,缺失 -1分,根据内容酌情加分)
附件
列出所有的中间产物的文件名,并予以说明起作用。
(附件0分,缺失 -1分)
hello.c hello源代码
hello.i hello.c预处理后的文本文件
hello.s hello.i编译后的汇编文件
hello.o hello.s汇编后的可重定位目标文件
hello 链接后的可执行文件
参考文献
[1] Randal E. Bryant & David R. O’Hallaron. 深入理解计算机系统[M]. 北京:机械工业出版社,2019.
[2]
https://blog.csdn.net/zheng123123123123/article/details/13017655
[3]
逻辑地址、线性地址、物理地址和虚拟地址
http://www.cnblogs.com/diyingyun/archive/2012/01/03/2311327.html