【无标题】

 

计算机系统

大作业

题     目  程序人生-Hellos P2P  

专       业     计算学部           

学     号     120L021324         

班   级     2003011            

学       生     邵宗琪       

指 导 教 师     郑贵滨          

计算机科学与技术学院

2021年5月

摘  要

本文主要阐述了在Linux系统下hello程序的生命周期,了解hello程序从hello.c经过预处理、编译、汇编、链接生成可执行文件,并由操作系统进行进程管理、存储管理和I/O管理的全过程。从而对CSAPP课程中的内容进行全面地总结和梳理,加深对计算机系统的理解。

关键词:计算机系统;Linux;预处理;编译;汇编;链接;进程管理;存储管理;IO管理                            

目  录

第1章 概述

1.1 Hello简介

1.2 环境与工具

1.3 中间结果

1.4 本章小结

第2章 预处理

2.1 预处理的概念与作用

2.2在Ubuntu下预处理的命令

2.3 Hello的预处理结果解析

2.4 本章小结

第3章 编译

3.1 编译的概念与作用

3.2 在Ubuntu下编译的命令

3.3 Hello的编译结果解析

3.3.1数据:

3.3.2操作:

3.4 本章小结

第4章 汇编

4.1 汇编的概念与作用

4.2 在Ubuntu下汇编的命令

4.3 可重定位目标elf格式

4.4 Hello.o的结果解析

4.5 本章小结

5链接

5.1 链接的概念与作用

5.2 在Ubuntu下链接的命令

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

5.4 hello的虚拟地址空间

5.5 链接的重定位过程分析

5.6 hello的执行流程

5.7 Hello的动态链接分析

5.8 本章小结

6hello进程管理

6.1 进程的概念与作用

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

6.3 Hello的fork进程创建过程

6.4 Hello的execve过程

6.5 Hello的进程执行

6.6 hello的异常与信号处理

6.7本章小结

7hello的存储管理

7.1 hello的存储器地址空间

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

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

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

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

7.6 hello进程fork时的内存映射

7.7 hello进程execve时的内存映射

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

7.9动态存储分配管理

7.10本章小结

8hello的IO管理

8.1 Linux的IO设备管理方法

8.2 简述Unix IO接口及其函数

8.3 printf的实现分析

8.4 getchar的实现分析

8.5本章小结

结论

附件


第1章 概述

1.1 Hello简介

P2P:程序员在编辑器上编程,形成hello.c(programme),再由预处理器cpp生成hello.i、编译器ccl生成hello.s、汇编器as生成hello.o、链接器ld生成可执行文件hello,运行该可执行文件,得到进程(process)。

O2O:可执行文件hello运行时,shell调用fork创建进程,再通过excve将hello载入,将该进程映射到虚拟内存,并载入物理内存。指令进入CPU流水线执行。执行结束后shell父进程回收hello子进程,删除这一进程的相关信息与数据,回到zero。

1.2 环境与工具

硬件环境:X64 CPU;2GHz;2G RAM;256GHD Disk 以上

软件环境:Windows7/10 64位以上;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64位/优麒麟 64位 以上;

开发与调试工具:Visual Studio 2010 64位以上;CodeBlocks 64位;vi/vim/gedit+gcc

列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。

1.3 中间结果

Hello.i:预处理所得的文件

Hello.s:编译得到的汇编代码

Hello.o:汇编得到的可重定位目标文件

Hello:链接后得到的可执行文件

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

1.4 本章小结

本章简述了P2P、O2O的过程,介绍了过程中使用的环境与工具,并给出中间结果文件的介绍。


第2章 预处理

2.1 预处理的概念与作用

概念:在编译前进行的处理,预处理器cpp根据#开头的命令修改原始程序

作用:

  1. 删除程序中的注释及多余空格;
  2. 处理宏定义:将#define的内容进行宏替换
  3. 处理头文件包含:将#include后的头文件插入文本中
  4. 处理条件编译:对#ifdef、#else等指令进行处理,将部分代码包含进来或排除在外,并过滤非必要的代码。

2.2在Ubuntu下预处理的命令

gcc -E hello.c -o hello.i

2.3 Hello的预处理结果解析

从原始的24行变为3060行,其中增加的内容均在main函数之前,是hello中包含的三个头文件stdio.h、unistd.h、stdlib.h内容的展开。原本1-4行的注释内容被删去。

2.4 本章小结

本章介绍了预处理的概念与作用及Ubuntu下预处理的命令,并给出预处理结果的解析。


第3章 编译

3.1 编译的概念与作用

概念:编译器ccl通过对预处理后的.i文件进行一系列词法、语法分析与优化后将其转变为汇编语言文件.s的过程。

作用:是将高级语言程序转化为机器可直接识别处理执行的的机器码的中间步骤。汇编语言为不同的高级语言的不同编译器提供了通用的输出语言,相对于预处理文件更利于机器理解。并能够根据用户指定的不同优化等级对代码进行安全的、等价的优化,提升代码在执行时的性能。

3.2 在Ubuntu下编译的命令

gcc -S hello.i -o hello.s

3.3 Hello的编译结果解析

3.3.1数据:

  1. 常量:

常数在汇编语言中以立即数形式存在,格式为$+数值:如$32即常数32,$4即常数4。存储在.text段。

在printf函数中使用的字符串常量储存形式仍为”string”。存储在.rodata段。

  1. 局部变量:

局部变量存储在栈中或寄存器中,源代码中一共有三个局部变量:i、argc和数组argv。

i存储在栈空间-4(%rbp)中:

argc存储在栈空间-20(%rbp)中:

argv[]数组也存在栈中,利用栈指针偏移量读取,argv[1]存储在%rdx中,argv[2]存储在%rsi中:

3.3.2操作:

  1. 赋值:赋值操作使用mov语句,如对变量i赋初值0。

赋初值/不赋初值

  1. 算术操作:使用add、sub语句进行加减操作,如每次循环结束后对i值+1:

如对%rax中存储的地址+8或+16,得到偏移后地址:

  1. 关系操作:compare与jump语句组合,如,比较argc与4的值,若相等则跳转至.L2:

比较i与7的值,若小于等于则跳转至.L4:

  1. 函数操作:

函数第1~6个参数依次储存在%rdi、%rsi、%rdx、%rcx、%r8、%r9六个寄存器中,其余的参数保存在栈中。将函数需要的参数存放在寄存器中或栈中来进行参数传递,函数调用结束之后,返回值保存在寄存器%rax中。

函数调用:call,如调用printf:

函数返回:ret

  1. 类型转换(隐式或显式) unsigned/char/int/long/float/double
  2. Sizeof
  3. 逻辑/位操作:逻辑&& ||  !    位 & | ~ ^    移位>>   <<    复合操作如 “|=” 或“<<=”等
  4. 数组/指针/结构操作:A[i]    &v   *p    s.id    p->id
  5. 控制转移:if/else switch for while  do/while  ?: continue  break

此部分是重点,说明编译器是怎么处理C语言的各个数据类型以及各类操作的。应分3.3.1~ 3.3.x等按照类型和操作进行分析,只要hello.s中出现的属于大作业PPT中P4给出的参考C数据与操作,都应解析

3.4 本章小结

本章介绍了编译的概念与作用及在Ubuntu下的编译命令,并从编译器对各类数据、操作的处理对hello的编译过程进行解析。


第4章 汇编

4.1 汇编的概念与作用

概念:汇编器as将汇编程序翻译成机器指令,产生可重定位目标程序.o

作用:将汇编代码转化为二进制机器语言,使机器能够完全读懂

4.2 在Ubuntu下汇编的命令

gcc -c hello.s -o hello.o

4.3 可重定位目标elf格式

4.3.1 ELF Headers

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

ELF头以16字节的序列Magic开始,描述生成改文件的系统字的大小和字节顺序,ELF头剩下的部分包含帮助链接器语法分析和解释目标文件的信息,其中包括ELF头的大小、目标文件的类型、机器类型、字节头部表的文件偏移,以及节头部表中条目的大小和数量等信息。根据头文件的信息,可以看出该文件是可重定位目标文件,有14个节。

4.3.2 Section Headers

节头包含文件中各个节的语义,包括节的类型、位置、大小等,每个节都从零开始因为是可重定位目标文件,根据节头表中的字节偏移信息可知各节的起始位置以及所占空间的大小。还可以看到代码段可执行不能写,数据段和只读数据段不可执行。

4.3.3 Symtab

存放定义和引用的函数和全局变量的信息,name为名称,对应可重定位目标模块;value为起始位置偏移;size为目标大小;Bind表示是本地的还是全局的;type表示类型,要么是函数要么是数据。可以从符号表中看出符号的大小,类型等信息。如main大小为146字节,类型为FUNC。

4.3.4 重定位节

重定位节中包含了在代码中使用的一些外部变量等信息,在链接的时候需要根据重定位节的信息对这些变量符号进行修改。链接的时候链接器会根据重定位节的信息对外部变量符号决定选择何种方法计算正确的地址,通过偏移量等信息计算出正确的地址。

偏移量:需要被修改的引用节的偏移

信息:包括符号和类型两个部分,符号在前面四个字节,类型在后面四个字节

符号值:标识被修改引用应该指向的符号,

类型:重定位的类型

加数:一个有符号常数,一些重定位要使用它对被修改引用的值做偏移调整

符号名称:重定向到的目标的名称。

4.4 Hello.o的结果解析

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

反汇编得到的文本的主要构成与汇编语言基本一致,主要有一下几方面不同:

  1. 操作数进制不同。hello.s中操作数为十进制数,而反汇编语言中作数为16进制数。
  2. 条件跳转时,hello.s中使用段的名字,而反汇编语言中直接使用跳转目标的地址。
  3. 函数调用时,hello.s中直接使用函数名,而反汇编语言中使用所调用函数的相对偏移地址。

4.5 本章小结

本章介绍了汇编的概念与作用及在Ubuntu下汇编的命令,用readelf分析了hello.o的ELF格式,列出其各节的基本信息。将hello.o反汇编得到的文件与hello.s进行对照分析,着重分析了操作数与跳转与调用时的区别。


5链接

5.1 链接的概念与作用

概念:通过链接器ld将各种代码和数据片段收集并组合成为一个单一文件,这个文件可被加载到内存并执行。

作用:链接可以执行于编译时(complie time)、加载时(load time)、运行时(run time)。链接使分离编译成为可能。无需将一个大型应用程序组织为一个巨大的源文件,可以将它分解成更小、更好管理的模块,并独立地修改和编译这些模块。当我们改变这些模块中的一个时,只需简单地重新编译它,并重新链接应用,而不必重新编译其他文件。

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

使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件

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

分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。

ELF头:

节头:

Symtbl:

重定位节:

5.4 hello的虚拟地址空间

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

观察edb的Data Dump窗口。窗口显示虚拟地址由0x401000开始,到0x401ff0结束

5.5 链接的重定位过程分析

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

  1. 在链接过程中,hello中加入了代码中调用的一些库函数,例如getchar,puts,printf,等,同时每一个函数都有了相应的虚拟地址

  1.  hello中增加了.init和.plt节。

  1. hello.o中main函数的起始地址是0,是相对地址,而hello中起始地址为401125,是已进行重新定位后的虚拟地址,是绝对地址。

  1. 在hello.o的main函数中,条件跳转和函数调用指令后均为相对于main函数的相对地址,而在hello中,条件跳转指令和call指令后均为绝对地址。

结合hello.o的重定位项目,分析hello中对其怎么重定位的。

重定位由两步组成:

  1. 重定位节和符号定义。 编译器和汇编器生成从0开始的代码和数据节。链接器通过把每个符号定义与一个内存位置关联起来,从而重定位这些节。
  2. 重定位节中的符号引用。修改所有对这些符号的引用,使得它们指向这个内存位置。链接器使用汇编器产生的重定位条目的详细指令,不加甄别地执行这样的重定位。

5.6 hello的执行流程

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

0x00000000004010f0<_start>

0x00000000004011c0<in __libc_csu_init>

0x0000000000401000<_init>

0x0000000000401125<main>

0x0000000000401090<puts>

0x00000000004010d0<exit>

5.7 Hello的动态链接分析

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

5.8 本章小结

本章介绍了链接的概念与作用及Ubuntu下链接的命令,用readelf分析了hello的ELF格式,列出其各段的基本信息,包括各段的起始地址,大小等信息。使用objdump -d -r hello分析了hello与hello.o的不同,说明了链接的过程。说明了从加载hello到_start,到call main,以及程序终止的所有过程。列出其调用与跳转的各个子程序名或程序地址。分析了hello程序的动态链接项目,通过edb调试,分析了在dl_init前后,这些项目的内容变化。


6hello进程管理

6.1 进程的概念与作用

进程是一个执行中的程序的实例。每一个进程都有它自己的地址空间,一般情况下,包括文本区域、数据区域、和堆栈。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储区着活动 过程调用的指令和本地变量。

进程提供给应用程序的关键抽象:一个独立的逻辑控制流,提供一个程序独占处理器的假象;一个私有的地址空间,提供一个程序独占地使用内存系统的假象。

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

Shell是指为使用者提供操作界面的软件(命令解析器)。它接受用户命令,然后调用相应的应用程序。Linux系统中所有的可执行文件都可以作为Shell命令来执行。

处理流程:

  1. 首先获取用户输入的命令行,对用户输入的命令进行解析,判断命令是否为内置命令,如果为内置命令,调用内置命令处理函数;如果不是内置命令,就fork一个子进程,在该子进程的上下文中运行。
  2. 判断为前台程序还是后台程序,如果是前台程序则直接执行并等待执行结束,如果是后台程序则将其放入后台并返回。
  3. 同时对键盘输入的信号和其他信号有特定的处理。

6.3 Hello的fork进程创建过程

执行hello程序时,由于hello并不是shell程序的内置命令,所以shell会使用fork来创建子进程并进行后续操作。

新创建的子进程与父进程近乎相同。子进程有与父进程不同的PID,除此外子进程得到与父进程用户级虚拟地址空间相同的一份副本,包括代码和数据段、堆、共享库以及用户栈。在父进程中,fork返回子进程的PID。在子进程中,fork返回0。

6.4 Hello的execve过程

6.5 Hello的进程执行

结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。

6.6 hello的异常与信号处理

 hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。

 程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps  jobs  pstree  fg  kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。

6.7本章小结

(第6章1分)


7hello的存储管理

7.1 hello的存储器地址空间

结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。

1.逻辑地址

逻辑地址是指由程序hello产生的与段相关的偏移地址部分,也叫相对地址。

2.线性地址

线性地址是逻辑地址到物理地址变换之间的中间层。程序hello的代码会产生逻辑地址,hello的反汇编文件中看到的地址(即逻辑地址)中的偏移量,加上对应段的基地址,便得到了hello中内容对应的线性地址。

3.虚拟地址

有时我们也把逻辑地址称为虚拟地址。因为与虚拟内存空间的概念类似,逻辑地址也是与实际物理内存容量无关的,是hello中的虚拟地址。

4.物理地址

物理地址是指出现在CPU外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果地址。在hello的运行中,在访问内存时需要通过CPU产生虚拟地址,然后通过地址翻译得到一个物理地址,并通过物理地址访问内存中的位置。

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

逻辑地址由段选择符和偏移量组成,线性地址为段首地址与逻辑地址中的偏移量组成。其中,段首地址存放在段描述符中。而段描述符存放在描述符表中,也就是GDT(全局描述符表)或LDT(局部描述符表)中。

实模式下:逻辑地址CS:EA到物理地址CS*16+EA

保护模式下:以段描述符作为下标,到GDT/LDT表查表获得段地址,段地址+偏移地址=线性地址。
段式管理以段为单位分配内存,每段分配一个连续的内存区。由于各段长度不等,所以这些存储区的大小不一。同一进程包含的各段之间不要求连续。段式管理的内存分配与释放在作业或进程的执行过程中动态进行。

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

页式管理是一种内存空间存储管理的技术,页式管理分为静态页式管理和动态页式管理。将各进程的虚拟空间划分成若干个长度相等的页(page),页式管理把内存空间按页的大小划分成片或者页面(page frame),然后把页式虚拟地址与内存地址建立一一对应页表,并用相应的硬件地址变换机构,来解决离散地址变换问题。页式管理采用请求调页或预调页技术实现了内外存存储器的统一管理。

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

n位的虚拟地址包含两个部分:一个p位的虚拟页面偏移(VPO),一个n-p位的虚拟页号(VPN),MMU利用VPN选择适当的PTE,例如VPN 0选择PTE 0。根据PTE,我们知道虚拟页的信息,如果虚拟页是已缓存的,那直接将页表条目的物理页号和虚拟地址的VPO串联起来就得到一个相应的物理地址。这里的VPO和PPO是相同的。如果虚拟页是未缓存的,会触发一个缺页故障。调用一个缺页处理子程序将磁盘的虚拟页重新加载到内存中,然后再执行这个导致缺页的指令。

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

Cashe的物理访存大致过程如下:

1.组选择取出虚拟地址的组索引位,将二进制组索引转化为一个无符号整数,找到相应的组

2.行匹配把虚拟地址的标记为拿去和相应的组中所有行的标记位进行比较,当虚拟地址的标记位和高速缓存行的标记位匹配时,而且高速缓存行的有效位是1,则高速缓存命中。

3.字选择一旦高速缓存命中,我们就知道我们要找的字节在这个块的某个地方。因此块偏移位提供了第一个字节的偏移。把这个字节的内容取出返回给CPU

4.不命中如果高速缓存不命中,那么需要从存储层次结构中的下一层取出被请求的块,然后将新的块存储在组索引位所指示的组中的一个高速缓存行中。一种简单的 放置策略如下:如果映射到的组内有空闲块,则直接放置,否则组内都是有效块,产生冲突(evict),则采用最近最少使用策略 LFU 进行替换。

7.6 hello进程fork时的内存映射

当fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID,同时为这个新进程创建虚拟内存。它创建了当前进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记位只读,并将两个进程中的每个区域结构都标记为私有的写时复制。当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面。因此,也就为每个进程保持了私有空间地址的抽象概念。

7.7 hello进程execve时的内存映射

execve 函数调用驻留在内核区域的启动加载器代码,在当前进程中加载并运 行包含在可执行目标文件 hello 中的程序,加载并运行 hello 需要以下几个步骤:

1.删除已存在的用户区域,删除当前进程虚拟地址的用户部分中的已存 在的区域结构。

2.映射私有区域,为新程序的代码、数据、bss 和栈区域创建新的区域结 构,所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为 hello 文件中的.text 和.data 区,bss 区域是请求二进制零的,映射到匿名 文件,其大小包含在 hello 中,栈和堆地址也是请求二进制零的,初始长 度为零。

3.映射共享区域, hello 程序与共享对象 libc.so 链接,libc.so 是动态链 接到这个程序中的,然后再映射到用户虚拟地址空间中的共享区域内。

4.设置程序计数器(PC),execve 做的最后一件事情就是设置当前进程 上下文的程序计数器,使之指向代码区域的入口点。

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

页面命中完全是由硬件完成的,而处理缺页是由硬件和操作系统内核协作完成的。

处理流程:

处理器生成一个虚拟地址,并将它传送给MMU

MMU生成PTE地址,并从高速缓存/主存请求得到它

高速缓存/主存向MMU返回PTE

PTE中的有效位是0,所以MMU出发了一次异常,传递CPU中的控制到操作系统内核中的缺页异常处理程序。

缺页处理程序确认出物理内存中的牺牲页,如果这个页已经被修改了,则把它换到磁盘。

缺页处理程序页面调入新的页面,并更新内存中的PTE

缺页处理程序返回到原来的进程,再次执行导致缺页的命令。CPU将引起缺页的虚拟地址重新发送给MMU。因为虚拟页面已经换存在物理内存中,所以就会命中。

7.9动态存储分配管理

7.10本章小结

本章主要介绍了hello存储器的地址空间;虚拟地址到物理地址的转换;cache的物理内存访问;进程 fork、execve 时的内存映射、缺页故障与缺页中断处理;动态存储分配管理。


8hello的IO管理

8.1 Linux的IO设备管理方法

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

我们可以对文件进行打开关闭操作open和close;读写操作read和write等。

8.2 简述Unix IO接口及其函数

Unix I/O使所有输入输出都以统一的方式来执行:

  1. 打开文件。应用程序通过要求内核打开相应的文件,来宣告它想要访问一个I/O设备。内核返回一个小的非负整数,叫做描述符,他在后续对此文件的所有操作中标识这个文件。
  2. 改变当前的文件位置。对于每个打开的文件,内核保持着一个文件位置k,初始为0.这个文件位置是从文件开头起始的字节偏移量。
  3. 读写文件。一个读操作就是从文件复制n个字节到内存,从当前文件位置k开始,然后增加k到k+n。给定一个大小为m字节的文件,k>=m时执行型读操作会触发一个EOF的条件。
  4. 关闭文件。内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符地址中。

函数:

  1. open函数:进程通过调用open函数来打开一个已存在的文件或者创建一个新文件。
  2. close函数:进程通过调用close函数来关闭一个打开的文件。
  3. 应用程序通过调用read和write函数来分别进行输入和输出。
  4. 调用stat和fstat函数检索到关于文件的信息(元数据)。
  5. 应用程序可以用readdir系列函数来读取目录的内容。

8.3 printf的实现分析

[转]printf 函数实现的深入剖析 - Pianistx - 博客园

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;
    }

printf的输入参数是fmt,后面是不定长的参数。在printf内调用了vsprintf和write函数。

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

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

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

8.4 getchar的实现分析

getchar是读入函数的一种,它从标准输入里读取下一个字符。返回类型为int型,为用户输入的ASCII码或EOF。getchar可用宏实现:#define getchar() getc(stdin)。getchar有一个int型的返回值。当程序调用getchar时,程序就等待用户按键。用户输入的字符被存放在键盘缓冲区中,直到用户按回车为止。当用户键入回车之后,getchar才开始从stdin流中每次读入一个字符。getchar函数的返回值是用户输入的字符的ASCII码,若文件结尾(End-Of-File)则返回-1(EOF),且将用户输入的字符回显到屏幕。

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

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

8.5本章小结

本章介绍了Linux的IO设备管理方法以及UNIX IO接口及其函数,简单解释了printf以及getchar函数实现的基本原理。

结论

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

Hello经历的过程:

  1. 程序员在编辑器上使用高级语言编写出程序hello.c程序
  2. hello.c经过预处理器cpp的预处理,得到扩展后的文件hello.i
  3. hello.i经过编译器ccl的编译,得到汇编语言文件hello.s
  4. hello.s经过汇编器as的汇编,生成可重定位文件hello.o
  5. 链接器ld对hello.o链接生成可执行文件hello
  6. 在shell中调用fork函数为hello生成进程
  7. Execve加载并运行hello程序,将它映射到对应虚拟内存区域,并依需求载入物理内存。
  8. I/O设备,在hello程序中存在输入与输出,与printf,getchar函数有关,这些函数与linux系统的I/O设备密切相关。
  9. Hello在CPU流水线中执行每条指令
  10. 被shell父进程回收,内核收回所有信息。

感悟:

经过一学期的学习,通过老师课堂上清晰的讲解,结合CSAPP这本经典教材与CMU的课程实验,对计算机系统的整体框架和底层原理有了大致的认识与的理解。我们的编程学习不应仅仅停留在编写代码层面,更应该计算机系统的底层知识有更深入的理解,这门课程的学习对计算机系统的学习对以后的学习和工作有很大的帮助,为未来的学习和工作打下了坚实的基础。


附件

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

Hello.i:预处理所得的文件

Hello.s:编译得到的汇编代码

Hello.o:汇编得到的可重定位目标文件

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.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值