P2P

hit04
立个Flag:我会在寒假把它更新成排版好的版本的。

计算机系统

大作业

题 目 程序人生-Hello’s P2P

*

计算机科学与技术学院

2018年12月

摘 要

hello,作为每个计算机入门新手都会敲下的程序。它的背后隐藏着诸多秘密。从预处理、编译、汇编、链接、fork()、exceve()、异常控制流、虚拟内存、系统I/O。这些都是计算机系统背后真正的高深的内容。本文就试图从hello这个简单的程序出发,逐步揭示计算机系统背后的奥秘。

**关键词:**计算机系统;编译;存储结构

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

目 录

第1章 概述 - 4 -

1.1 Hello简介 - 4 -

1.2 环境与工具 - 4 -

1.2.1 硬件环境 - 4 -

1.2.2 软件环境 - 4 -

1.2.3 开发工具 - 4 -

1.3 中间结果 - 4 -

1.4 本章小结 - 4 -

第2章 预处理 - 6 -

2.1 预处理的概念与作用 - 6 -

2.2在Ubuntu下预处理的命令 - 6 -

2.3 Hello的预处理结果解析 - 7 -

2.4 本章小结 - 8 -

第3章 编译 - 9 -

3.1 编译的概念与作用 - 9 -

3.2 在Ubuntu下编译的命令 - 9 -

3.3 Hello的编译结果解析 - 9 -

3.4 本章小结 - 11 -

第4章 汇编 - 12 -

4.1 汇编的概念与作用 - 12 -

4.2 在Ubuntu下汇编的命令 - 12 -

4.3 可重定位目标elf格式 - 13 -

4.4 Hello.o的结果解析 - 14 -

4.5 本章小结 - 15 -

第5章 链接 - 16 -

5.1 链接的概念与作用 - 16 -

5.2 在Ubuntu下链接的命令 - 16 -

5.3 可执行目标文件hello的格式 - 16 -

5.4 hello的虚拟地址空间 - 16 -

5.5 链接的重定位过程分析 - 17 -

5.6 hello的执行流程 - 18 -

5.7 Hello的动态链接分析 - 18 -

5.8 本章小结 - 19 -

第6章 hello进程管理 - 20 -

6.1 进程的概念与作用 - 20 -

6.2 简述壳Shell-bash的作用与处理流程 - 20 -

6.3 Hello的fork进程创建过程 - 20 -

6.4 Hello的execve过程 - 21 -

6.5 Hello的进程执行 - 21 -

6.6 hello的异常与信号处理 - 22 -

6.7本章小结 - 23 -

第7章 hello的存储管理 - 24 -

7.1 hello的存储器地址空间 - 24 -

7.2 Intel逻辑地址到线性地址的变换-段式管理 - 24
-

7.3 Hello的线性地址到物理地址的变换-页式管理 - 25
-

7.4 TLB与四级页表支持下的VA到PA的变换 - 25
-

7.5 三级Cache支持下的物理内存访问 - 26 -

7.6 hello进程fork时的内存映射 - 26 -

7.7 hello进程execve时的内存映射 - 27 -

7.8 缺页故障与缺页中断处理 - 28 -

7.9动态存储分配管理 - 29 -

7.10本章小结 - 29 -

第8章 hello的IO管理 - 30 -

8.1Linux的IO设备管理方法 - 30 -

8.2 简述Unix IO接口及其函数 - 30 -

8.3 printf的实现分析 - 31 -

8.4 getchar的实现分析 - 31 -

8.5本章小结 - 32 -

结论 - 33 -

附件 - 34 -

参考文献 - 35 -


第1章 概述

1.1 Hello简介

Hello的P2P过程:Hello的一生从我们(程序猿)的手中开始。我们赋予它基因(代码)后生成了hello.c源文件,紧接着cpp,
ccl, as,
ld齐心协力,赋予它身体,将hello.c经过预处理、编译、汇编、链接等过程,最终成为可执行目标程序hello。

Hello的020的过程:在shell中键入命令后,shell进行fork, execve,
hello的生命过程就开始了,这其中异常控制流,虚拟内存,I/O齐心协力。最终hello走到了它生命的尽头,被shell回收,它的一生便结束了

1.2 环境与工具

1.2.1 硬件环境

Intel Core i7-7700HQ 8G RAM 1TB HHD + 128MB SSD

1.2.2 软件环境

Windows 10 64bits

Vmware 14 Pro + Ubuntu 16.04 LTS

1.2.3 开发工具

Dev-C++ (For Windows) Vim(For Ubuntu)

1.3 中间结果

列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。

生成的文件:

hello.i——由预处理器cpp生成的预处理后的文件

hello.s——由编译器ccl生成的汇编文件

hello.o——由汇编器as生成的可重定位目标文件

hello——由链接器ld生成的可执行文件

1.4 本章小结

本章中对hello的自白进行了简单的翻译,大致简介了hello的生命历程;介绍了分析hello的环境与工具;列出了hello的生命过程中的中间结果。

Hello的一生,从基因编码到生命诞生,生命开始到结束。一段伟大的旅程即将揭开帷幕!

(第1章0.5分)


第2章 预处理

2.1 预处理的概念与作用

2.1.1 预处理的概念

预处理器(cpp)根据以字符#开头的命令,修改原始的C程序。一般包括宏定义、文件包含、条件编译。

2.1.2 预处理的作用

1.宏展开:将宏名替换为文本(这个文本可以是字符串、可以是代码等)

宏展开不占运行时间,只占编译时间

2.文件包含:将include要包含的文件插入到该位置

3.条件编译:使用条件编译可以使目标程序变小,运行时间变短

./media/image2.png

2.2在Ubuntu下预处理的命令

gcc -v -E hello.c -o hello.i(-v能够可视化整个过程)

图2.1 hello的预处理可视化过程

./media/image3.png

2.3 Hello的预处理结果解析

图2.2 hello的预处理结果与源文件对比

上图左边是hello.c,右边是hello.i。通过对比可以发现,hello.i比hello.c长得多,可以见下图,发现hello的main函数在hello.i的3110行开始。说明在预处理的过程中插入了非常多的库文件

图2.3 hello.i中的main函数起始位置

2.4 本章小结

在真正进行下一步编译前,hello的基因编码还不齐全。程序猿们往往以为自己完成了代码输入就完成了对hello的基因编辑,其实不然。Hello还需要经过预处理过程,将程序猿偷懒的结果,宏定义、文件包含、条件编译等加入到基因中。至此hello的基因才齐全。能够走向下一步编译了。

(第2章0.5分)


第3章 编译

3.1 编译的概念与作用

3.1.1 编译的概念

编译是利用编译器(ccl)将已经预处理好的源文件转化为汇编语言程序的过程。

3.1.2 编译的作用

1.语法分析:将字符串分离出来

2.语义分析:将各个语法单元的意义翻译

3.优化:对代码结构优化

注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序

3.2 在Ubuntu下编译的命令

gcc -v -S hello.i -o hello.s(-v能够可视化整个过程)

图3.1 hello.s的可视化过程

3.3 Hello的编译结果解析

此部分使用hello.s中的源码来说明问题,不额外增加截图。

3.3.1 数据

  • 如字符串定义

.LC0:

.string “Usage: Hello \345\255\246\345\217\267
\345\247\223\345\220\215\357\274\201”

  • 如全局变量sleepsecs

.globl sleepsecs

.data

.align 4

.type sleepsecs, @object

.size sleepsecs, 4

3.3.2 赋值

movl $0, -4(%rbp)

整形数据的赋值操作由mov来实现。

3.3.3 算术操作

addl $1, -4(%rbp)

这是i++的汇编代码,使用了add指令来实现i+1的操作。

3.3.4 关系操作

cmpl $9, -4(%rbp)

利用比较操作来实现关系比较,之后会设置条件码等等。可以之后进行条件跳转。

3.3.5 控制转移

Hello中有两种控制转移:一种是无条件跳转:

jmp .L3

还有就是条件跳转:

cmpl $9, -4(%rbp)

jle .L4

3.3.6 函数操作

在hello中函数操作的实例有:

call puts

call printf

call getchar

可以发现,这些都是直接通过函数名来直接调用的,并没有给出具体的地址。Call之前会对%rsp,要传递的参数进行压栈等处理。

3.3.7 类型转换

程序中涉及隐式类型转换的是:int sleepsecs=2.5,将float类型的2.5转换为int类型。

3

3.4 本章小结

本章中主要讲述了汇编中如何处理C语言中的代码。把本来程序猿写好的代码又全部大改一通,hello可能有些一头雾水:我不是刚刚扩充好代码嘛,怎么突然又给我全部转化了?这个过程其实就像是DNA转RNA的过程,是一个解码的过程。正是有了这个解码过程,才能接下来真正运行hello的生命。

(第3章2分)


第4章 汇编

4.1 汇编的概念与作用

4.4.1 汇编的概念

汇编器(as)将hello.s翻译成机器语言指令,把这些指令打包成可重定位目标程序,并将结果保存在目标文件hello.o中。

4.4.2 汇编的作用

汇编的作用是将汇编语言编为机器语言,变为可执行的机器代码。

4.2 在Ubuntu下汇编的命令

gcc -v -c hello.s -o hello.o(-v能够可视化整个过程)

4

图4.1 hello.o的可视化过程

4.3 可重定位目标elf格式

分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。

4

图4.2 hello.o中的各节基本信息

重定位信息分析:使用readelf -r读取特定区域的信息

4

图4.3 hello.o中的可重定位信息

4.4 Hello.o的结果解析

4

图4.4 hello.o反汇编与hello.s对比

objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。

1.分支转移:在.s中分支转移指令的跳转操作数被保留为段名称,在变为机器语言后.o中已经为函数跳转预留了空间,使用了具体地址。

2.函数调用:在.s文件中,函数调用之后直接跟着函数名称,而在反汇编程序中,call的目标地址是当前下一条指令。因为hello.c中调用的函数都是共享库中的函数,需要通过动态链接器才能确定函数的运行时执行地址,在汇编的时候,将call指令后的相对地址设置为全0(目标地址正是下一条指令),然后在.rela.text节中为其添加重定位条目,等待静态链接的进一步确定。

3.全局变量访问:在.s文件中,访问rodata(printf中的字符串),使用段名称+%rip,在反汇编代码中0+%rip,因为rodata中数据地址也是在运行时确定,故访问也需要重定位。所以在汇编成为机器语言时,将操作数设置为全0并添加重定位条目。

4.5 本章小结

本章主要说明了从hello.s到hello.o进行汇编的过程,也就是从一个汇编代码到一个可重定位目标文件的过程。比较了它们elf文件和内容中的差异。可以发现hello的基因被进一步进化了。Hello离成为一个真正能够运行的程序就只差一步了!

(第4章1分)


第5章 链接

5.1 链接的概念与作用

5.5.1 链接的概念

链接是指将各种代码和数据段收集并组合成为一个单一文件的过程。

5.5.2 链接的作用

链接的作用是使得分离编译成为可能。使用共享库等概念可以进一步进行动态更新。

5.2 在Ubuntu下链接的命令

5

图5.1 hello的链接

5.3 可执行目标文件hello的格式

hello的elf文件中的内容对比hello.o增加了许多,一个是可执行目标文件,一个是可重定位目标文件,它们的结构上是有些许差别的。

图5.2 hello的elf文件内容

5.4 hello的虚拟地址空间

使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。

5

图5.3 用EDB查看hello

1.PHDR保存程序头表。

2.INTERP指定在程序已经从可执行文件映射到内存之后,必须调用的解释器(如动态链接器)。

3.LOAD表示一个需要从二进制文件映射到虚拟地址空间的段。其中保存了常量数据(如字符串)、程序的目标代码等。

4.DYNAMIC保存了由动态链接器使用的信息。

5.NOTE保存辅助信息。

6.GNU_STACK:权限标志,标志栈是否是可执行的。

7.GNU_RELRO:指定在重定位结束之后那些内存区域是需要设置只读

和5.3中的hello还是存在区别

5.5 链接的重定位过程分析

objdump -d -r hello > hello.txt分析hello与hello.o的不同,说明链接的过程。

链接的过程:

1.在使用ld命令链接的时候,指定了动态链接器为/lib64/ld-linux-x86-64.so.2,crt1.o、crti.o、crtn.o中主要定义了程序入口_start、初始化函数_init,_start程序调用hello.c中的main函数,libc.so是动态链接共享库,其中定义了hello.c中用到的printf、sleep、getchar、exit函数和_start中调用的__libc_csu_init,__libc_csu_fini,__libc_start_main。链接器将上述函数加入。

2.链接器解析重定条目时发现对外部函数调用的类型为R_X86_64_PLT32的重定位,此时动态链接库中的函数已经加入到了PLT中,.text与.plt节相对距离已经确定,链接器计算相对距离,将对动态链接库中函数的调用值改为PLT中相应函数与下条指令的相对地址,指向对应函数。对于此类重定位链接器为其构造.plt与.got.plt。

3.链接器解析重定条目时发现两个类型为R_X86_64_PC32的对.rodata的重定位(printf中的两个字符串),.rodata与.text节之间的相对距离确定,于是链接器直接修改call之后的值为目标地址与下一条指令的地址之差,使其指向相应的字符串。

5.6 hello的执行流程

使用edb执行hello,说明从加载hello到_start,到call
main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。

_dl_start 地址:0x7ff806de3ea0

_dl_init 地址:0x7f75c903e630

_start 地址:0x400500

_libc_start_main 地址:0x7fce59403ab0

_cxa_atexit 地址:0x7f38b81b9430

_libc_csu_init 地址:0x4005c0

_setjmp 地址:0x7f38b81b4c10

_sigsetjmp 地址:0x7efd8eb79b70

_sigjmp_save 地址:0x7efd8eb79bd0

main 地址:0x400532

puts 地址:0x4004b0

print 地址:0x4004c0

sleep 地址:0x4004f0 (以上两个在循环体中执行10次)

getchar 地址:0x4004d0

_dl_runtime_resolve_xsave 地址:0x7f5852241680

_dl_fixup 地址:0x7f5852239df0

_uflow 地址:0x7f593a9a10d0

exit 地址:0x7f889f672120

5.7 Hello的动态链接分析

分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。

图5.4 dl_init 前

对于动态共享链接库中PIC函数,编译器没有办法预测函数的运行时地址,所以需要添加重定位记录,等待动态链接器处理。所以可以看到,调用dl之后GOT有了正确的地址,就能够访问到动态链接的函数

图5.5 dl_init后

5.8 本章小结

本章讲述了hello从一个孤单的个体,经过链接逐渐形成一个真正可以执行的文件hello的过程。至此,hello的生命产生过程至此结束,hello的一生即将拉开序幕。

以下格式自行编排,编辑时删除

(第5章1分)


第6章 hello进程管理

6.1 进程的概念与作用

以下格式自行编排,编辑时删除

6.1.1 进程的概念

进程的经典定义是一个执行中的程序的实例。系统中的每个程序都运行在某个进程的上下文中。

6.1.2 进程的作用

进程提供给应用程序一个关键抽象。

  • 一个独立的逻辑控制流,它提供一个假象,好像我们的程序独占地使用处理器。

  • 一个私用的地址空间,它提供一个假象,好像我们的程序独占地使用内存系统。

6.2 简述壳Shell-bash的作用与处理流程

以下格式自行编排,编辑时删除

shell的作用是:作为命令语言,它交互式解释和执行用户输入的命令或者自动地解释和执行预先设定好的一连串的命令。

处理流程:

1.读取输入的命令行

2.解析输入的命令,如果是内部命令,就直接执行

3.为外部命令分配新的子进程并运行

4.程序运行期间接收外部信号,并执行相应的异常控制

5.回收中止的子进程

6.3 Hello的fork进程创建过程

shell调用fork(),将会创建一个新的子进程。这个新的子进程有和父进程相同的上下文,但是与父进程的PID不同。在两个进程的执行过程中,fork()会返回不同的值。通过这个值在程序中可以区别两个不同的进程。然后hello就将在shell为其创建的新进程中开始它的生命。

6.4 Hello的execve过程

在fork()一个新的进程后,为了运行这个新的进程,shell将会使用execve()在当前进程上下文中加载并运行一个新程序。

execve()在加载了hello后,调用加载器。加载器将可执行目标文件中的代码和数据从磁盘复制到内存中,还会设置栈,然后通过跳转程序的第一条指令或入口点,将控制转移给新程序的主函数来运行这个程序。Hello的生命便开始运行了。

6.5 Hello的进程执行

6

图6.1 hello的进程执行上下文切换

在hello程序的运行过程中,在执行每个命令之时或者在执行每个命令之后,如果碰到异常,就会发生进程切换。

对于一个普通的hello程序,它是运行在用户模式中的。处理器通常使用一个寄存器提供两种模式的区分,该寄存器描述了进程当前享有的特权,当没有设置模式位时,进程就处于用户模式中,用户模式的进程不允许执行特权指令,也不允许直接引用地址空间中内核区内的代码和数据;设置模式位时,进程处于内核模式,该进程可以执行指令集中的任何命令,并且可以访问系统中的任何内存位置。

Hello不是在运行过程中独占处理器的,而是有着自己的控制流。在每次切换之前,hello都会保存自己当前的上下文。它由通用寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构等对象的值构成。

综上内容,hello在运行时的过程就如同上图所示。在每个命令执行结束之后,检查是否有要执行的切换。如果有,控制就将转移给内核进行调度;如果没有,hello就会继续运行。

6.6 hello的异常与信号处理

在hello的运行过程中进行的各种尝试如下:

6

图6.2 hello运行过程中的异常与信号处理

1.hello正常执行结束,将会向shell发送SIGCHILD信号。Shell收到该信号会将hello回收。

2.可以看到不停乱按键盘时,shell没有任何反应。

3.按回车时,shell也默认不做处理。

4.按Ctrl+C,会直接中止程序

6

图6.3 Ctrl-C结束hello

5.先按Ctrl+Z,再按ps,jobs,fg,kill等命令:

输入ps后,会发现hello在进程单上。输入jobs后,发现hello是当前要运行的任务。输入fg后,发现hello又开始运行了起来,说明hello是一个前台程序。输入kill和hello的PID后,向hello发送了SIGKILL信号,hello在继续运行后被杀死。

6.7本章小结

本章详细地叙述了hello一生的运行过程。从它生命开始的fork(),exceve(),到shell在进程间不断进行切换,hello的一生中经历了无数的过客。最后在各种结局中走向生命的尽头,无论是正常结束,还是被发送信号强行杀死,还是就此挂起再也不理。Hello都完成了它一生的任务。

(第6章1分)


第7章 hello的存储管理

7.1 hello的存储器地址空间

物理地址:计算机的内存被组织成一个由连续字节的单元组成的数组,每字节都有一个独立的物理地址。对于hello来说,就是每个字节对应了一个数组的地址。

逻辑地址:逻辑地址指由程序产生的与段相关的偏移地址部分。hello的汇编中的左侧标记地址便是一种逻辑地址。

7

图7.1 hello的汇编中的逻辑地址标识

线性地址:线性地址是逻辑地址到物理地址变换之间的中间层。在分段部件中逻辑地址是段中的偏移地址,其偏移量加上基地址就是线性地址。hello的反汇编文件中看到的地址(即逻辑地址)中的偏移量,加上对应段的基地址,便得到了hello中内容对应的线性地址。

虚拟地址:使用虚拟寻址时,CPU通过生成一个虚拟地址来访问主存,这个虚拟地址在被送至内存前先转换成适当的物理地址。这个过程要经过虚拟内存管理,包括MMU等元件要在其中发挥作用。

7.2 Intel逻辑地址到线性地址的变换-段式管理

图7.2 段式管理示例

7.3 Hello的线性地址到物理地址的变换-页式管理

以下格式自行编排,编辑时删除

图7.3 页式管理示例

如图是CSAPP中的一个14位虚拟地址到12位物理地址的寻址。其中虚拟页偏移VPO和物理页偏移PPO。从虚拟页号VPN到物理页号PPN需要先查TLB,未命中则需要查页表,再次未命中的话就去磁盘上调出相应数据。

7.4 TLB与四级页表支持下的VA到PA的变换

7

图7.4 从虚拟地址到物理地址的变换

7.5 三级Cache支持下的物理内存访问

7

图7.5 三级cache下的内存访问

先从L1缓存中查找所需要的数据,从物理地址中取出缓存偏移CO、缓存组索引CI以及缓存标记CT。若缓存中CI所指示的组有标记与CT匹配的条目且有效位为1,则检测到一个命中,读出在偏移量CO处的数据字节,并把它返回给MMU,随后MMU将它传递给CPU。若不命中,则需到低一级cache中取出相应的块将其放入当前cache中,重新执行对应指令,访问要找的数据。一直重复上述步骤直到向磁盘上调取数据为止。

7.6 hello进程fork时的内存映射

以下格式自行编排,编辑时删除)fork为新进程创建虚拟内存,:首先创建当前进程的mm_struct,
vm_area_struct和页表的原样副本;两个进程中的每个页面都标记为只读;两个进程中的每个区域结构(vm_area_struct)都标记为私有的写时复制。在新进程中返回时,新进程拥有与调用fork进程相同的虚拟内存。

7.7 hello进程execve时的内存映射

7

图7.6 execve时的内存映射

Execve函数在当前进程中加载并运行新程序的步骤:

1.删除已存在的用户区域

2.创建新的区域结构:私有的、写时复制;代码和初始化数据映射到.text和.data区;.bss和堆栈映射到匿名文件,栈堆的初始长度为0

3.共享对象由动态链接映射到本进程共享区域

4.设置PC,指向代码区域的入口点

./media/image25.png

7.8 缺页故障与缺页中断处理

图7.7 缺页中断和缺页故障处理

7.9动态存储分配管理

Printf会调用malloc,请简述动态内存管理的基本方法与策略。

动态内存分配器维护着一个进程的虚拟内存区域,称为堆。假设堆是一个请求二进制零的区域,它紧接在未初始化的数据区域后开始,并向上生长。对于每个进程,内核维护着一个变量brk,它指向堆的顶部。

分配器将堆视为一组不同大小的块(block)的集合来维护。每个块就是一个连续的虚拟内存片(chunk),要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可以用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被释放。这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。

分配器有显式分配器和隐式分配器两种,各自有着独特的结构。根据不同的结构还要建立不同的维护方式。但是目标都是提高利用率和速度。

7.10本章小结

本章从存储器的各级结构出发,分析了虚拟内存在计算机系统中的实际运用。表面上看上去和hello没用太多的关系,但是虚拟内存才是hello真正生存过的环境。Hello曾经来过,却不留下一丝痕迹,这也就是020的奥秘吧。

(第7章 2分)


第8章 hello的IO管理

8.1Linux的IO设备管理方法

设备的模型化:文件

设备管理:unix io接口

8.2 简述Unix IO接口及其函数

所有的I/O设备,例如网络、磁盘和终端等,都被模型化为文件,而所有的输入和输出都被当做对相应的文件的读和写来执行。这种将设备优雅地映射为文件的方式,允许Linux内核引出一个简单的、低级的应用接口,称为Unix
I/O。这使得所有的输入和输出都能以一种统一且一致的方式来执行。

1.打开文件

8

图8.1 打开文件

2.关闭文件

8

图8.2 关闭文件

3.读写文件

8

图8.3 读写文件

4.读取文件元数据

8

图8.4 读取文件元数据

5.I/O重定向

8

图8.5 I/O重定向

8.3 printf的实现分析

https://www.cnblogs.com/pianist/p/3315801.html

其实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;

}

从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.

字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。

显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。

8.4 getchar的实现分析

其实getchar的函数体长这样

int getchar(void)

{

static char buf[BUFSIZ];

static char char* bb = buf;

static int n = 0;

if(n == 0)

{

n = read(0, buf, BUFSIZ);

bb = buf;

}

return (–n>=0)?(unsigned char)*bb++:EOF;

}

异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。

getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。

8.5本章小结

本章介绍了系统级I/O是如何工作的,提供了一个Unix系统在运行时内外部交互的方法。看似和hello也没有关系,但是,如果没有I/O,hello要如何和大家说hello呢?这也是hello这一生都在向大家打招呼的秘密啊!

(第8章1分)

结论

用计算机系统的语言,逐条总结hello所经历的过程。

hello经历了一段不同寻常的旅程。

从基因编码到开始生命——

1.一代代程序猿用他们各自的语言敲下了hello,编写了hello的初代基因。

2.预处理器(cpp)将hello扩展为hello.i。经过预处理过程,将宏定义、文件包含、条件编译等加入到基因中。至此hello的基因才齐全。

3.编译器(ccl)将已经预处理好的源文件转化为汇编语言程序.
就像是DNA转RNA的过程,是一个解码的过程。正是有了这个解码过程,才能接下来真正运行hello的生命.

4.汇编器(as)将hello.s翻译成机器语言指令,把这些指令打包成可重定位目标程序,并将结果保存在目标文件hello.o中。hello的基因被进一步进化了。它离成为一个真正能够运行的程序就只差一步了!

5.链接器(ld)将各种代码和数据段收集并组合成为一个单一文件。hello从一个孤单的个体,经过链接逐渐形成一个真正可以执行的文件hello的过程。至此,hello的生命产生过程至此结束,hello的一生即将拉开序幕。

从生命开始到结束——

6.fork()和exceve()为hello打开了生存空间。

7.虚拟内存的优秀机制为hello提供了快速的运行环境

8.I/O为hello提供了向我们打招呼的手段。

shell在进程间不断进行切换,hello的一生中经历了无数的过客。最后在各种结局中走向生命的尽头,被shell回收。

hello这段代码虽然非常简单,但是很少有人真正去思考它背后的意义。从预处理、编译、汇编、链接、fork()、exceve()、异常控制流、虚拟内存、系统I/O。这些都是计算机系统背后真正的高深的内容,也不是作为一个计算机新手能体会到的。hello为我们打开计算机的大门的同时,还指向了一条通往计算机系统奥秘的一条路,这也是hello的伟大之处吧!

(结论0分,缺失 -1分,根据内容酌情加分)


附件

列出所有的中间产物的文件名,并予以说明起作用。

9

hello.c 实验源代码

hello.i 预处理文件 处理了#之后的内容,扩展了hello.c

hello.s 汇编文件 hello的汇编代码形式

hello.o 可重定位文件 重定位之后就能执行的文件

hello 可执行文件 运行hello

hello.o.txt 由hello.o读elf文件得到 查看之中的需重定位信息

hello.txt 由hello经objdump得到 查看反汇编代码,和直接汇编不同

(附件0分,缺失 -1分)


参考文献

为完成本次大作业你翻阅的书籍与网站等

[1] Randal E.Bryant CS:APP深入理解计算机系统. Pearson,2016

[2] Baidu百科 https://baike.baidu.com/item/预处理命令/10204389

[3] 优快云 Blog https://blog.youkuaiyun.com/huoyahuoya/article/details/53083424

[4] 优快云 Blog https://blog.youkuaiyun.com/hahalidaxin/article/details/85144974

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

已经博主授权,源码转载自 https://pan.quark.cn/s/a4b39357ea24 QueueForMcu 基于单片机实现的队列功能模块,主要用于8位、16位、32位非运行RTOS的单片机应用,兼容大多数单片机平台。 开源代码:https://.com/xiaoxinpro/QueueForMcu 一、特性 动态创建队列对象 动态设置队列数据缓冲区 静态指定队列元素数据长度 采用值传递的方式保存队列数据 二、快速使用 三、配置说明 目前QueueForMcu只有一个静态配置项,具体如下: 在文件 中有一个宏定义 用于指定队列元素的数据长度,默认是 ,可以根据需要更改为其他数据类型。 四、数据结构 队列的数据结构为 用于保存队列的状态,源码如下: 其中 为配置项中自定义的数据类型。 五、创建队列 1、创建队列缓存 由于我们采用值传递的方式保存队列数据,因此我们在创建队列前要手动创建一个队列缓存区,用于存放队列数据。 以上代码即创建一个大小为 的队列缓存区。 2、创建队列结构 接下来使用 创建队列结构,用于保存队列的状态: 3、初始化队列 准备好队列缓存和队列结构后调用 函数来创建队列,该函数原型如下: 参数说明: 参考代码: 六、压入队列 1、单数据压入 将数据压入队列尾部使用 函数,该函数原型如下: 参数说明: 返回值说明: 该函数会返回一个 枚举数据类型,返回值会根据队列状态返回以下几个值: 参考代码: 2、多数据压入 若需要将多个数据(数组)压入队列可以使用 函数,原理上循环调用 函数来实现的,函数原型如下: 参数说明: 当数组长度大于队列剩余长度时,数组多余的数据将被忽略。 返回值说明: 该函数将返回实际被压入到队列中的数据长度。 当队列中的剩余长度富余...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值