计算机系统大作业
题 目 程序人生-Hello’s P2P
专 业 信息安全
学 号 2022110520
班 级 2203201
学 生 董祉若
指 导 教 师 史先俊
计算机科学与技术学院
2024年5月
本文分析了程序hello的一生,结合本学期计算机系统课程学习的知识,以ubuntu为主要操作系统,对计算机系统的具体工作原理进行深入的了解。
关键词:P2P;020;CS;程序的一生
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
第1章 概述
1.1 Hello简介
根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
P2P:即From Program to Process,从程序到进程。Hello源程序以程序文本形式存在磁盘,在运行该代码文件后,经cpp的预处理生成hello.i文件,ccl的编译生成hello.s文件、as的汇编生成hello.o文件、最后经ld的链接成为可执行目标文件hello。
O2O:即From Zero-0 to Zero-0,Hello从最开始不占用系统资源到内核删除。在shell中输入相关命令后,调用fork函数为这一程序创建进程,之后将通过exceve在进程的上下文中加载并运行 hello,将进程映射到虚拟内存空间,并加载需要的物理内存。执行时,在 CPU 的分配下,指令进入 CPU 流水线执行。当执行结束后shell父进程回收hello进程,内核删除为其产生的数据结构,这一进程就结束了。
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
硬件环境:
硬件型号:Windows11
处理器 12th Gen Intel(R) Core(TM) i5-1240P 1.70 GHz
机带 RAM 24.0 GB (23.7 GB 可用)
系统类型 64 位操作系统, 基于 x64 的处理器
软件环境:
Windows10 64 位;Ubuntu 20.04
1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
中间结果文件名称 | 文件作用 |
Hello.i | cpp预处理后已改源程序文件(文本) |
Hello.s | ccl编译后汇编程序(文本) |
Hello.o | as汇编后可重定位目标文件(二进制) |
Hello | Id处理后可执行目标程序(二进制) |
Elf.txt | Hello.o的ELF格式 |
Dis_hello.txt | Hello.o的反汇编代码 |
Hello.elf | Hello的elf 格式文件 |
Hello.txt | Hello的反汇编代码 |
1.4 本章小结
本章仅对hello程序执行总流程进行简述,并交代了大作业过程中使用的软硬件条件及过程文件和对应文件功能。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
概念:
以#起始修改源程序的程序文件
作用:
展开以#起始的行,试图解释为预处理指令(preprocessing directive) ,其中ISO C/C++要求支持的包括#if/#ifdef/#ifndef/#else/#elif/#endif(条件编译)、#define(宏定义)、#include(源文件包含)、#line(行控制)、#error(错误指令)、#pragma(和实现相关的杂注)以及单独的#(空指令) 。预处理指令一般被用来使源代码在不同的执行环境中被方便的修改或者编译。
2.2在Ubuntu下预处理的命令
cpp hello.c >hello.i
图2.1 ubuntu下预处理的命令
2.3 Hello的预处理结果解析
原本源代码文件扩展为3061行,源代码位于3048行之后,扩展的内容为cpp对hello.c引用的头文件 stdio.h、unistd.h、stdlib.h的展开,以及该三个头文件中#后的其他文件展开,并且可见cpp预处理后删除了源代码的注释,具体信息如下图展示:
图2.2 头文件信息
图2.3 hello.i中源代码信息
2.4 本章小结
本章介绍了预处理器的工作(头文件展开、宏替换、删除、注释、条件替换等),展示了Ubuntu系统上hello.c文件的预处理过程与结果。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
概念:
编译器ccl将hello.i文件翻译为汇编程序文本文件hello.s
作用:
①利用编译程序从源语言编写的源程序产生目标程序的过程。②用编译程序产生目标程序的动作。 编译就是把高级语言变成计算机可以识别的2进制语言,计算机只认识1和0,编译程序把人们熟悉的语言换成2进制的。 编译程序把一个源程序翻译成目标程序的工作过程分为五个阶段:词法分析;语法分析;语义检查和中间代码生成;代码优化;目标代码生成。主要是进行词法分析和语法分析,又称为源程序分析,分析过程中发现有语法错误,给出提示信息。
3.2 在Ubuntu下编译的命令
gcc -S hello.i -o hello.s
图3.1 ubuntu下编译的命令
3.3 Hello的编译结果解析
-
-
- 数据
-
(1)常量
①数字常量:源代码数字常量(比较时的数字变量5、循环的比较变量等)存在.text段,如下图:
图3.2 数字常量储存
②字符串常量:函数使用的字符串常量在.rotate段,如下图:
图3.3 字符串常量储存
(2)变量
①全局变量:全局变量存储在.data段,本程序不存在全局变量
②局部变量:局部变量在栈空间,栈地址是不固定的。本程序局部变量共三个,分别是循环变量i、argc和数组argv
对i:
地址在-4(%rbp)
图3.4 局部变量i的储存信息
对argc:
地址在-20(%rbp),第24行进行与5的比较从而判断是否执行后续代码
图3.5 局部变量argc的储存信息
对argv:
等待持续输入的数组
图3.6 局部变量argv储存信息
-
-
- 赋值
-
本程序仅存在对循环变量i的赋值,每次循环i+=1,如下:
图3.7 对局部变量i的赋值
-
-
- 算术操作
-
本程序仅存在循环结束后i+=1一个算术操作,如下:
图3.8 对局部变量i的算术操作
-
-
- 关系操作
-
本程序存在两个关系操作
- argc!=5
图3.9 对局部变量argc的关系操作
- for循环中i<10,可以看到当i>=9时进行条件跳转
图3.10 局部变量i在for循环中的关系操作
-
-
- 数组/指针/结构操作
-
本程序只有argv数组,储存信息如下图:
图3.11 局部变量argv数组操作
-
-
- 函数调用
-
有五种函数调用:主函数main,函数printf, exit, sleep以及函数getchar。第一至六个参数按序存储在%rdi、%rsi、%rdx、%rcx、%r8、%r9,其余存在栈中其他位置。
- main函数:传入argc、argv参数,返回0(将%eax置为0)
图3.12 main函数汇编代码
- printf函数:
图3.13 第一次调用printf
图3.14 第二次调用printf
- sleep函数
图3.15 sleep函数调用
- exit函数
图3.16 exit函数调用
- getchar函数
图3.17 getchar函数调用
3.4 本章小结
本章简述了ccl编译过程,分析了hello.i到hello.s过程中的语法分析;语义检查和中间代码生成;代码优化;目标代码生成。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
概念:
用助记符(Memoni)代替操作码,用地址符号(Symbol)或标号(Label)代替地址码,把hello.s转化为hello.o可重定位目标程序二进制文件。
作用:
把汇编语言翻译成机器语言。
4.2 在Ubuntu下汇编的命令
as hello.s -o hello.o
图4.1 ubuntu下汇编的命令
4.3 可重定位目标elf格式
-
-
- 命令
-
readelf -a hello.o > ./elf.txt生成elf文件
图4.2 生成elf格式文件
-
-
- ELF头
-
ELF文件由4部分组成,分别是ELF头(ELF header)、程序头表(Program header table)、节(Section)和节头表(Section header table)。实际上,一个文件中不一定包含全部内容,而且它们的位置也未必如同所示这样安排,只有ELF头的位置是固定的,其余各部分的位置、大小等信息由ELF头中的各项值来决定。
图4.3 ELF头
-
-
- 节头表
-
描述了.o 文件中每一个节出现的位置,大小,目标文件中的每一个节都有个固定大小的条目
图4.4 节头表
-
-
- 重定位节
-
重定位节中包含了在代码中使用的一些外部变量等信息,在链接的时候需要根据重定位节的信息对这些变量符号进行修改。链接的时候链接器会根据重定位节的信息对外部变量符号决定选择何种方法计算正确的地址,通过偏移量等信息计算出正确的地址。
图4.5 重定位节
-
-
- 符号表
-
存放在程序中定义和引用的函数和全局变量的信息。
图4.6 符号表
4.4 Hello.o的结果解析
命令:objdump -d -r hello.o > Disas_hello.s
图4.7 Ubuntu下反汇编命令
反汇编代码:
图4.8 反汇编代码内容
解析:与第三章hello.s比较
- 进制由十进制变为十六进制
- 每一行都是对应的地址
- call指令从函数名变成偏移值
4.5 本章小结
本章对汇编进行简述,描述了as生成可重定位目标程序.o的过程,并与hello.s的反汇编对比,加深理解。
(第4章1分)
第5章 链接
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.1 ubuntu下链接的命令
5.3 可执行目标文件hello的格式
命令:readelf -a hello > hello1.elf
-
-
- ELF头
-
图5.2 ELF头
-
-
- 节头
-
图5.3 节头表
-
-
- 程序头
-
图5.4 程序头
-
-
- 重定位节
-
图5.5 重定位节
-
-
- 符号表
-
图5.6 符号表
5.4 hello的虚拟地址空间
图5.7 edb中data dump
该段程序地址从0x000055bd42019000开始,PHDR保存的是程序头表;INTERP保存了程序执行前需要调用的解释器;LOAD记录程序目标代码和常量信息;DYNAMIC 储存了动态链接器所使用的信息;NOTE 记录的是一些辅助信息;GNU_EH_FRAME 保存异常信息;GNU_STACK使用系统栈所需要的权限信息;GNU_RELRO 保存在重定位之后只读信息的位置。
5.5 链接的重定位过程分析
命令:objdump -d -r hello > hello_objdump.s
图5.8 连接重定位
反汇编代码(部分):
图5.9 hello反汇编代码(部分)
分析:
- hello 中加入了代码中调用的一些库函数
- 用0加上%rip 的值来表示全局变量的位置
- 增加了.init 和.plt 节,和一些节中定义的函数。
5.6 hello的执行流程
使用gdb/edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程(主要函数)。请列出其调用与跳转的各个子程序名或程序地址。
401000<_init>
401020<.plt>
401090 <puts@plt>
4010a0 <printf@plt>
4010b0 <getchar@plt>
4010c0 <atoi@plt>
4010d0 <exit@plt>
4010e0 <sleep@plt>
4010f0 <_start>
401120 <_dl_relocate_static_pie>
401125 <main>
4011d0 <__libc_csu_init>
401240 <__libc_csu_fini>
401248 <_fini>
图5.10 edb运行画面
5.7 Hello的动态链接分析
ld通过GOT 和过程链接表 PLT 的协作来解析函数的地址,GOT提供准确的地址,PLT用来调用不同函数。
图5.11 edb.got和.got.plt信息
图5.12 edb运行前地址
图5.13 edb运行后地址
5.8 本章小结
本章经过链接将巨大的源程序分为数个小的管理模块,得到一个可执行目标程序,可以通过shell调用命令执行。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
概念:
是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
作用:
1)一个独立的逻辑控制流,提供一个程序独占处理器的假象;
2)一个私有的地址空间,提供一个程序独占地使用内存系统的假象。
6.2 简述壳Shell-bash的作用与处理流程
作用:
执行一系列的读/求值步骤,然后终止。
处理流程:
1.Shell 首先从命令行中找出特殊字符(元字符),在将元字符翻译成间隔符号。元字符将命令行划分成小块tokens。
2.程序块tokens被处理,检查看他们是否是shell中所引用到的关键字。
3.当程序块tokens被确定以后,shell根据aliases文件中的列表来检查命令的第一个单词。如果这个单词出现在aliases表中,执行替换操作并且处理过程回到第一步重新分割程序块 tokens。
4.Shell对~符号进行替换。
5.Shell对所有前面带有$符号的变量进行替换。
6.Shell将命令行中的内嵌命令表达式替换成命令;他们一般都采用$(command)标记法。
7.Shell计算采用$(expression)标记的算术表达式。
8.Shell将命令字符串重新划分为新的块tokens。这次划分的依据是栏位分割符号,称为IFS。缺省的IFS变量包含有:SPACE,TAB和换行符号。
9.Shell执行通配符*、?、[]的替换。
10.Shell把所有从处理的结果中用到的注释删除,并且按照下面的顺序实行命令的检查:
- 内建的命令
B. shell 函数(由用户自己定义的)
C. 可执行的脚本文件(需要寻找文件和 PATH 路径)
11.在执行前的最后一步是初始化所有的输入输出重定向。
12.最后,执行命令。
6.3 Hello的fork进程创建过程
父进程通过调用 fork 函数创建一个新的运行的子进程。调用 fork 函数后,新创建的子进程几乎但不完全与父进程相同:子进程得到与父进程虚拟地址空间相同的(但是独立的)一份副本,包括代码、数据段、堆、共享库以及用户栈,子进程获得与父进程任何打开文件描述符相同的副本,这意味着当父进程调用 fork 时,子进程可以读写父进程中打开的任何文件。fork 被调用一次,却返回两次,子进程返回 0,父进程返回子进程的 PID。父进程和新创建的子进程之间最大的区别在于它们有不同的 PID。
6.4 Hello的execve过程
exceve 函数在当前进程的上下文中加载并运行一个新程序。exceve 函数加载并运行可执行目标文件,并带参数列表和环境变量列表。只有当出现错误时,exceve才会返回到调用程序。所以,与 fork 一次调用返回两次不同,在 exceve 调用一次并从不返回。当加载可执行目标文件后,exceve 调用启动代码,启动代码设置栈,将可执行目标文件中的代码和数据从磁盘复制到内存中,然后通过跳转到程序的第一条指令或入口点来运行该程序,由此将控制传递给新程序的主函数。
6.5 Hello的进程执行
结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。
-
-
- 进程调度
-
即使在系统中通常有许多其他程序在运行,进程也可以向每个程序提供一种假象,好像它在独占地使用处理器。如果想用调试器单步执行程序,我们会看到一系列的程序计数器(PC)的值,这些值唯一的对应于包含在运行时动态链接到程序的共享对象中的指令。这个 PC 的序列叫做逻辑控制流,或者简称逻辑流。进程是轮流适用处理器的,每个进程执行它的流的一部分,然后被抢占,然后轮到其他进程。
-
-
- 内核模式转变为用户态模式
-
操作系统内核使用上下文切换来实现多任务。内核为每个进程维持一个上下文,它是内核重启被抢占的进程所需的状态,包括通用目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构的值。通过上下文切换机制来转移控制到新的进程:1)保存当前进程上下文;2)恢复某个先前被抢占的进程被保存的上下文 3)将控制转移给这个新恢复的进程。当内核代表用户执行系统调用时,可能会发生上下文切换,这时就存在着用户态与核心态的转换。
6.6 hello的异常与信号处理
正常执行时:
图6.1 hello程序正确运行截图
异常类型:
类别 | 原因 | 异步/同步 | 返回行为 |
中断 | 来自I/O设备信号 | 异步 | 总是返回到下一条指令 |
陷阱 | 有意异常 | 同步 | 总是返回到下一条指令 |
故障 | 潜在可恢复错误 | 同步 | 可能返回到当前指令 |
终止 | 不可恢复错误 | 同步 | 不会返回 |
处理方式:
图6.2 中断的处理方式
图6.3 陷阱处理方式
图6.4 故障处理方式
图6.5 终止处理方式
不停乱按:系统会认为输入是命令,不影响当前进程
图6.6 不停乱按的操作截图
按下Ctrl-Z:产生中断异常,程序被挂起
图6.7 按下Ctrl-Z截图及运行ps jobs结果截图
图6.8 hello挂起后pstree运行结果
图6.9 hello挂起后执行fg
图6.10 hello运行中按下Ctrl-C
6.7本章小结
本章介绍了hello可执行文件的执行过程,并介绍了异常和中断等信息
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:逻辑地址指由程序产生的与段相关的偏移地址部分,也叫相对地址。要经过寻址方式的计算或变换才得到内存储器中的实际有效地址,即物理地址。从hello的反汇编代码中看到的地址,它们需要通过计算,通过加上对应段的基地址才能得到真正的地址,这些便是hello中的逻辑地址。
线性地址:是逻辑地址到物理地址变换之间的中间层。程序hello的代码会产生逻辑地址,hello的反汇编文件中看到的地址(即逻辑地址)中的偏移量,加上对应段的基地址,便得到了hello中内容对应的线性地址。
虚拟地址:有时我们也把逻辑地址称为虚拟地址。因为与虚拟内存空间的概念类似,逻辑地址也是与实际物理内存容量无关的。
物理地址:是指出现在CPU外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果地址。在hello的运行中,在访问内存时需要通过CPU产生虚拟地址,然后通过地址翻译得到一个物理地址,并通过物理地址访问内存中的位置。
7.2 Intel逻辑地址到线性地址的变换-段式管理
逻辑地址由段选择符和偏移量组成,线性地址为段首地址与逻辑地址中的偏移量组成。段式管理以段为单位分配内存,每段分配一个连续的内存区,由于各段长度不等,所以这些存储区的大小不一,同一进程包含的各段之间不要求连续,内存分配与释放在作业或进程的执行过程中动态进行。其中,段首地址存放在段描述符中。而段描述符存放在描述符表中,也就是GDT(全局描述符表)或LDT(局部描述符表)中。
图7.1 段式管理示意图
7.3 Hello的线性地址到物理地址的变换-页式管理
页式管理:将各进程的虚拟空间划分成若干个长度相等的页(page),页式管理把内存空间按页的大小划分成片或者页面(page frame),然后把页式虚拟地址与内存地址建立一一对应页表,并用相应的硬件地址变换机构,来解决离散地址变换问题,页式管理采用请求调页或预调页技术实现了内外存存储器的统一管理。
7.4 TLB与四级页表支持下的VA到PA的变换
首先虚拟地址是由VPN和VPO组成的,VPN可以作为在TLB中的索引,如上图所示,TLB可以看作是一个PTE的cache,将常用的PTE缓存到TLB中,加速虚拟地址的翻译。TLB是具有高相连度的,应该是为了一次多存一些PTE。如果能够在TLB中找到与VPN对应的PTE,即为TLB hit,TLB直接给出PPN,然后PPO即为VPO,这样就构成了一个物理地址。
如果不能做到TLB hit就要到四级页表当中取寻址,在i7中VPN有36位,被分成了四段,从左往右的前三个九位的地址分别对应于在前三级页表当中的偏移,偏移在页表中所对应的页表条目指向某一个下一级页表,而下一个9位VPN就对应的是在这个页表中的偏移。最后一级页表中的页表条目存放的是PPN
7.5 三级Cache支持下的物理内存访问
MMU发送物理地址PA给L1缓存,L1缓存从物理地址中抽取出缓存偏移CO、缓存组索引CI以及缓存标记CT。高速缓存根据CI找到缓存中的一组,并通过CT判断是否已经缓存地址对应的数据,若缓存命中,则根据偏移量直接从缓存中读取数据并返回;若缓存不命中,则继续从L2、L3缓存中查询,若仍未命中,则从主存中读取数据。
7.6 hello进程fork时的内存映射
当fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给他一个唯一的pid。为了给这个新进程创建虚拟内存,系统创建了当前进程的 mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有写时复制。当fork从新进程返回,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,也就为每个进程保持了私有地址空间的抽象概念。
7.7 hello进程execve时的内存映射
共四步:
(1)删除已存在的用户区域
(2)映射私有区域:为新程序hello的代码、数据、bss和栈区域创建新的区域结构。
所有这些新的区域都是私有的、写时复制的。
(3)映射共享区域:如果hello程序与共享对象(或目标)链接,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内。
(4)设置程序计数器(PC),指向代码的入口点。
7.8 缺页故障与缺页中断处理
共七步:
(1)处理器生成一个虚拟地址,并将它传送给MMU
(2)MMU生成PTE地址,并从高速缓存/主存请求得到它
(3)高速缓存/主存向MMU返回PTE
(4)PTE中的有效位是0,所以MMU出发了一次异常,传递CPU中的控制到操作系统内核中的缺页异常处理程序。
(5)缺页处理程序确认出物理内存中的牺牲页,如果这个页已经被修改了,则把它换到磁盘。
(6)缺页处理程序页面调入新的页面,并更新内存中的PTE
(7)缺页处理程序返回到原来的进程,再次执行导致缺页的命令。CPU将引起缺页的虚拟地址重新发送给MMU。因为虚拟页面已经换存在物理内存中,所以就会命中。
7.9动态存储分配管理
基本方法与策略:
1.带边界标签的隐式空闲链表分配器管理
带边界标记的隐式空闲链表的每个块是由一个字的头部、有效载荷、可能的额外填充以及一个字的尾部组成的。当一个应用请求一个 k 字节的块时,分配器搜索空闲链表,查找一个符合大小的空闲块来放置这个请求块。分配器有三种放置策略:首次适配、下一次适配合最佳适配。在释放一个已分配块的时候需要考虑是否能与前后空闲块合并,减少系统中碎片的出现。
2.显示空间链表管理
显式空闲链表是将空闲块组织为某种形式的显式数据结构。因为根据定义,程序不需要一个空闲块的主体,所以实现这个数据结构的指针可以存放在这些空闲块的主体里面。如,堆可以组织成一个双向链表,在每个空闲块中,都包含一个前驱与一个后继指针。放置策略与上述放置策略一致。
7.10本章小结
本章主要介绍了 hello 进程在执行的过程中的虚拟内存与物理内存之间的转换关系和相关机制、系统如何处理缺页故障与缺页中断以及动态分配内存的方法与策略。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件,所有I/O设备均视为文件,所有输入输出视为对相应文件的读写操作。
设备管理:unix io接口,允许 Linux 内核引出一个简单、低级的应用接口。
8.2 简述Unix IO接口及其函数
1.打开文件
open函数:函数原型int open(char * path,int oflag,...)返回值是一个文件描述符,path顾名思义就是文件名oflage文件是打开方式,第三个形参应用于创建文件时使用。
图8.1 open函数
2.读写文件
read函数:函数原型ssize_t read(int fd , void* buf , size_t nbytes)返回值是文件读取字节数,在好几种情况下会出现返回值不等于文件读取字节数,也就是第三个参数nbytes的情况,第二个形参buf读取到buf的内存,文件偏移量(current file offset)受改变。
图8.2 read函数
write函数:函数原型ssize_t write(int fd , const void* buf, size_t nbytes)返回值是文件写入字节数,fd是文件描述符,将buf内容写入nbytes个字节到文件,但这里需要注意默认情况是需要在系统队列中等待写入(打开方式不同也会不同),以上三个出错都返回-1
图8.3 write函数
3.关闭文件
close函数:函数原型int close(int fd)成功返回0,失败-1
图8.4 close函数
8.3 printf的实现分析
图8.5 printf函数的函数体
图8.6 vsprintf函数的函数体
printf执行流程:vsprintf 函数将所有的参数内容格式化之后存入 buf,然后返回格式化数组的长度。write 函数将buf中的i个元素写到终端。从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
getchar是读入函数的一种。它从标准输入里读取下一个字符,相当于getc(stdin)。返回类型为int型,为用户输入的ASCII码或EOF。getchar由宏实现:#define getchar() getc(stdin)。getchar有一个int型的返回值。当程序调用getchar时.程序就等着用户按键。用户输入的字符被存放在键盘缓冲区中。直到用户按回车为止(回车字符也放在缓冲区中)。当用户键入回车之后,getchar才开始从stdio流中每次读入一个字符。getchar函数的返回值是用户输入的字符的ASCII码,若文件结尾(End-Of-File)则返回-1(EOF),且将用户输入的字符回显到屏幕。如用户在按回车之前输入了不止一个字符,其他字符会保留在键盘缓存区中,等待后续getchar调用读取。也就是说,后续的getchar调用不会等待用户按键,而直接读取缓冲区中的字符,直到缓冲区中的字符读完后,才等待用户按键。
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章介绍了Linux系统的I/O设备及管理和printf及getchar函数的实现方法。
(第8章1分)
结论
hello程序的一生:
1.预处理,hello.c文件通过cpp的预处理,得到了扩展后的源程序文件hello.i
2.编译,hello.i通过编译器的处理,被翻译成了汇编语言程序hello.s
3.汇编,在汇编器as的处理下,hello.s生成了可重定位文件hello.o
4.链接,链接器将重定位目标文件链接为可执行目标文件hello
5.生成子进程,在shell中输入指定命令shell调用fork函数为hello生成进程。
6.Execve加载并运行hello程序,将它映射到对应虚拟内存区域,并依需求载入物理内存。
7.I/O设备,在hello程序中存在输入与输出,这些部分与printf,getchar函数有关,这些函数与linux系统的I/O设备密切相关。
8.Hello将在cpu流水线中执行每一条指令
9.程序运行结束后,父进程会对其进行回收,内核把它从系统中清除。
(结论0分,缺失 -1分,根据内容酌情加分)
附件
列出所有的中间产物的文件名,并予以说明起作用。
中间结果文件名称 | 文件作用 |
Hello.i | cpp预处理后已改源程序文件(文本) |
Hello.s | ccl编译后汇编程序(文本) |
Hello.o | as汇编后可重定位目标文件(二进制) |
Hello | Id处理后可执行目标程序(二进制) |
Elf.txt | Hello.o的ELF格式 |
Dis_hello.txt | Hello.o的反汇编代码 |
Hello.elf | Hello的elf 格式文件 |
Hello.txt | Hello的反汇编代码 |
(附件0分,缺失 -1分)
参考文献
为完成本次大作业你翻阅的书籍与网站等
[1]伍之昂. Linux Shell 编程从初学到精通 [M]. 北京:电子工业出版社
[2]《深入理解计算机系统》 Randal E.Bryant David R.O’Hallaron 机械工业出版社
[3]CSDN:ELF——可重定位目标文件格式
[4]CSDN: UNIX IO 简介_unix 标准socket io-CSDN博客
[5] https://www.cnblogs.com/pianist/p/3315801.html
(参考文献0分,缺失 -1分)