hello.c的一生

摘  要

本文旨在研究hello.c程序从预处理到进程结束的全过程,通过在linux下的逐步追踪,生动清晰地展现了程序运行的过程。

关键词:csapp;hello.c;预处理;编译;进程;虚拟内存;IO。                           

目  录

第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 本章小结... - 7 -

第3章 编译... - 8 -

3.1 编译的概念与作用... - 8 -

3.2 在Ubuntu下编译的命令... - 8 -

3.3 Hello的编译结果解析... - 8 -

3.4 本章小结... - 11 -

第4章 汇编... - 12 -

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

汇编的概念是指的将汇编语言(xxx.s)翻译成机器指令,并将这些指令打包成一种叫做可重定位目标程序,并将这个结果保留在(xxx.o)中。这里的xxx.o是二进制文件。汇编过程的作用是将汇编指令转换成一条条机器可以直接读取分析的机器指令。... - 12 -

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

4.3 可重定位目标elf格式... - 12 -

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

4.5 本章小结... - 15 -

第5章 链接... - 16 -

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

链接是将多个文件拼接合并成一个可执行文件的过程,链接行为可以在编译/汇编/加载/运行时执行。链接的存在降低了模块化编程的难度。... - 16 -

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

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

5.4 hello的虚拟地址空间... - 17 -

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

5.6 hello的执行流程... - 21 -

5.7 Hello的动态链接分析... - 22 -

5.8 本章小结... - 22 -

第6章 hello进程管理... - 23 -

6.1 进程的概念与作用... - 23 -

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

6.3 Hello的fork进程创建过程... - 23 -

6.4 Hello的execve过程... - 24 -

6.5 Hello的进程执行... - 24 -

6.6 hello的异常与信号处理... - 25 -

6.7本章小结... - 26 -

第7章 hello的存储管理... - 27 -

7.1 hello的存储器地址空间... - 27 -

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

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

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

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

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

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

7.8 缺页故障与缺页中断处理... - 30 -

7.9动态存储分配管理... - 30 -

7.10本章小结... - 30 -

第8章 hello的IO管理... - 32 -

8.1 Linux的IO设备管理方法... - 32 -

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

8.3 printf的实现分析... - 33 -

8.4 getchar的实现分析... - 34 -

8.5本章小结... - 35 -

结论... - 35 -

附件... - 36 -

参考文献... - 37 -

第1章 概述

1.1 Hello简介

根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。

P2P

Hello程序先通过预处理器生成了修改了的源程序hello.i,然后通过编译器生成了汇编程序hello.s再通过汇编器生成可重定位的目标程序(二进制文件)hello.o最后将hello.o与调用的库中的其他可重定位的目标程序通过链接器链接生成了可执行目标程序hello就可以运行啦!

020

程序开始运行前,进程中并没有这个程序这是最开始的zero-0,程序运行时赋予他一个PID即一个进程的编号记录程序当前的运行状态,当程序运行结束后程序将终止,等待父进程或者shell回收这个进程。回收完成后shell中又没有这个程序运行的消息所以说是从zero-0 到 zero-0

1.2 环境与工具

硬件环境

Dell precision 3551

1.2.2 软件环境

Windows 10 x64

Ubuntu  20.04.4

1.2.3 开发工具

       Edb,vs,dev c++;objdump。

1.3 中间结果

Hello.c(源文件)

Hello.i(预处理后文件)

Hello.s(编译文件)

Hello.o(汇编后,可重定位文件)

Hello.out(可执行文件)

Hello_o.s(反汇编)

1.4 本章小结

       本章主要介绍P2P和020的过程,以及描述了实验的软硬件环境和实验中间结果。

(第1章0.5分)

第2章 预处理

2.1 预处理的概念与作用

预处理器(cpp)根据以字符#开头的命令,修改原始的C程序。比如 hello.c中第1行的#include 命令告诉预处理器读取系统头文件 stdio.h的内容,并把它直接插人程序文本中。结果就得到了另一个C程序,通常是以.i作为文件扩展名。

2.2在Ubuntu下预处理的命令

cpp hello.c  > hello.i

2.3 Hello的预处理结果解析

下面两个图片展示了预处理后的结果

可以看到预处理后代码多了很多

文件大小从1kb增加至64kb,通过对代码的阅读,可见预处理是代换头文件的源码。

2.4 本章小结

.c文件中存在头文件,或者外部文件和一些程序员注释,条件编译和完善程序文本文件操作都需要预处理来实现,预处理可以使程序在后续操作中不受阻碍,是重要操作

(第2章0.5分)

第3章 编译

3.1 编译的概念与作用

编译阶段,编译器(ccl)将文本文件hello.i翻译成文本文件hello.s,它包含一个汇编语言程序。该程序包含函数main的定义

定义中的每条语句都以一种文本格式描述了一条低级机器语言指令。 汇编语言是非常有用的,因为它为不同高级语言的不同编译器提供了通用的输出语言。

3.2 在Ubuntu下编译的命令

gcc –S  hello.c –o hello.s

3.3 Hello的编译结果解析

hello.s 部分内容

解析:

3.3.1变量分析

能够看到hello.c中只有一个变量i

可以看到在使用对应变量时,对应变量才在汇编语句中被定义,在这个程序中,他被分配到了-4(%Rbp)的位置上。

3.3.2操作关系符和控制语句

在hello.c中出现了!=关系操作符

可以看到在这里出现了cmpl,它将4和参数数组进行比较,若相等则跳转到L2,不相等就继续下面的指令

3.3.3四则运算符及复合语句处理

加法:x=x+y               addq y,x

减法:x=x-y                subq y,x

乘法:x=x*y                imulq y,x

除法:z=x/y                                   movq x,z

cqto

idivq y

复合语句可以类比上图。

或者如下:

比如z=x+A*y+B(A,B为立即数)可以被表示成:       leaq B(x,y,A) , z

3.3.4对数组,指针,结构的操作

本程序对参数数组的访问使用的就是偏移寻址的方式

3.3.5对函数的操作

返回值:一个函数的返回值一般储存在储存器%eax中,在ret返回,以hello.c为例子,具体操作如下:

函数调用及参数传递,在不同函数中传递参数,一般需要选取几个寄存器,将数据储存在其中,通过call语句执行。

3.4 本章小结

本章显示简述了编译的概念和作用,具体分析了一个c程序是如何被编译器编译成一个汇编程序的过程,还详细分析了不同的c语句和翻译成汇编语句之后的表示方法。(第32分)

第4章 汇编

4.1 汇编的概念与作用

汇编的概念是指的将汇编语言(xxx.s)翻译成机器指令,并将这些指令打包成一种叫做可重定位目标程序,并将这个结果保留在(xxx.o)中。这里的xxx.o是二进制文件。汇编过程的作用是将汇编指令转换成一条条机器可以直接读取分析的机器指令。

4.2 在Ubuntu下汇编的命令

gcc –c hello.s –o hello.o

4.3 可重定位目标elf格式

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

ELF header:用于总的描述ELF文件各个信息的段。

Section Header:描述了.o文件中出现的各个节的类型、位置、所占空间大小等信息

.rela.text:重定位节,这个节包含了.text(具体指令)节中需要进行重定位的信息。这些信息描述的位置,在由.o文件生成可执行文件的时候需要被修改(重定位)。在这个hello.o里面需要被重定位的有printf , puts , exit , sleepsecs , getchar , sleep ,rodata里面的两个元素(.L0和.L1字符串)

.rela.eh_frame是eh_frame节的重定位信息。

4.4 Hello.o的结果解析

我们可以看出跟hello.s相比,hello.o反汇编之后虽然右边的汇编代码没有太大的差别(多了一些跳转位置的解释和注释),但是左边多了一大堆东西。在冒号前面的是运行时候的机器指令的位置,冒号后面的呢,就是每一行汇编语句所对应的机器指令啦。机器语言是完全由0/1构成的,在这里显示的时候表示成十六进制的

4.4.1分支跳转语句

我们可以发现,在hello.s中跳转到的目标位置都是用.L3/.L4来表示的,在hello.o反汇编之后,这些目标被用具体的地址位置代替。

4.4.2函数调用

在原先的hello.s中,调用一个函数只需被表示成call+函数名,但是在hello.o反汇编的结果中我们可以看见,这里的call是call一个具体的地址位置。

4.5 本章小结

本章简述了hello.s汇编指令被转换成hello.o机器指令的过程,通过readelf查看hello.o的ELF、反汇编的方式查看了hello.o反汇编的内容,比较其与hello.s之间的差别。学习了汇编指令映射到机器指令的具体方式。(第41分)

5章 链接

5.1 链接的概念与作用

链接是将多个文件拼接合并成一个可执行文件的过程,链接行为可以在编译/汇编/加载/运行时执行。链接的存在降低了模块化编程的难度。

5.2 在Ubuntu下链接的命令

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

  

可以看到上次section headers节头表的数量还只有14个,现在有31个了。

Size表示的是节头表对应的节的大小

Address表示的是被载入到虚拟地址后的地址是多少

Offset表示的是这个节在程序里面的地址偏移量

5.4 hello的虚拟地址空间

可以看到程序在0x0000562fc5efc000开始加载,在0x0000562fc5efd000结束

再来分析分析elf里面的Program Headers:

PHDR:程序头表

INTERP:程序执行前需要调用的解释器

LOAD:程序目标代码和常量信息

DYNAMIC:动态链接器所使用的信息

NOTE::辅助信息

GNU_EH_FRAME:保存异常信息

GNU_STACK:使用系统栈所需要的权限信息

GNU_RELRO:保存在重定位之后只读信息的位置

其余的从.dynamic到.strtab节的内容是存放在0x0000562fc5efd000后面

5.5 链接的重定位过程分析

分析一下比hello.o多出来的这些节头表:

.interp:保存ld.so的路径

.note.ABI-tag

.note.gnu.build-i:编译信息表

.gnu.hash:gnu的扩展符号hash表

.dynsym:动态符号表

.dynstr:动态符号表中的符号名称

.gnu.version:符号版本

.gnu.version_r:符号引用版本

.rela.dyn:动态重定位表

.rela.plt:.plt节的重定位条目

.init:程序初始化

.plt:动态链接表

.fini:程序终止时需要的执行的指令

.eh_frame:程序执行错误时的指令

.dynamic:存放被ld.so使用的动态链接信息

.got:存放程序中变量全局偏移量

.got.plt:存放程序中函数的全局偏移量

.data:初始化过的全局变量或者声明过的函数

通过对比

我们可以发现:

hello_o从.text节开始,而hello.s从.init节开始

在hello.s中导入了诸如puts、printf、getchar、sleep等在hello程序中使用的函数,而这些函数的在hello_o中就没有出现

在hello_o中调用函数都是使用call+<main+偏移量的做法>,而在hello_.s是直接使用call+<函数名> 的方法来直接调用的。

5.6 hello的执行流程

程序名称                               

载入:

_dl_start                             

_dl_init                                     

开始执行:

_start                                  

_libc_start_main                      

_init                                   

执行main:

_main

_printf

_exit

_sleep

_getchar

_dl_runtime_resolve_xsave

_dl_fixup

_dl_lookup_symbol_x

退出:

exit

5.7 Hello的动态链接分析

在edb调试之后我们发现原先0x00600a10开始的global_offset表是全0的状态,在执行过_dl_init之后被赋上了相应的偏移量的值。这说明dl_init操作是给程序赋上当前执行的内存地址偏移量,这是初始化hello程序的一步。

5.8 本章小结

本章介绍了链接的概念和作用,分析了hello的ELF格式,虚拟地址空间的分配,重定位和执行过程还有动态链接的过程。(第51分)

6章 hello进程管理

6.1 进程的概念与作用

进程是计算机程序需要进行对数据集合进行操作所运行的一次活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。

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

shell是一个应用程序,他在操作系统中提供了一个用户与系统内核进行交互的界面。他的处理过程一般是这样的:

1.读取用户的输入

2.分析输入内容,获得输入参数

3.如果是内核命令则直接执行,否则调用相应的程序执行命令

4.在程序运行期间,shell需要监视键盘的输入内容,并且做出相应的反应

6.3 Hello的fork进程创建过程

父进程通过fork创建一个子进程,新创建的子进程几乎但不完全与父进程相同。子进程得到与父进程用户虚拟地址空间相同的(但是独立的)一份副本,包括代码和数据段、堆、共享库以及用户栈。子进程获得与父进程任何打开文件描述符相同的副本,这就意味着父进程调用fork时,子进程可以读写父进程任何打开的任何文件。父进程和新创建的子进程之间最大的区别在于他们拥有不同的PID。

Fork在执行时被调用一次,但是却返回两次,一次是返回到父进程,一次是返回到新创建的子进程。

父进程和子进程是并发运行的独立进程。内核能够以任意方式交替执行它们的逻辑控制流中的命令。

其运行流程图大致如下:

6.4 Hello的execve过程

Execve函数在当前进程的上下文中加载并运行一个新的程序。Execve加载并运行可执行目标文件filename,且带参数列表argv和环境变量envp。只有当出现错误时,例如找不到filename,execve才会返回到调用程序。所以与fork调用一次返回两次不一样,execve调用一次并且从不返回。使用execve函

数之后,会清楚原先程序在虚拟内存中存储的信息,并重新初始化。程序新开始时的栈帧如下:

6.5 Hello的进程执行

对于这个循环中的这两个语句,shell是这样子解决的:

调用getchar()时先是运行在前端hello进程中,然后调用时切换到内核进程的标准读取程序中,从键盘输入读取到一个字符之后再回到hello进程

在内核和前端之前切换的动作被称为上下文切换。

6.6 hello的异常与信号处理

 输入ctrl+Z,这个操作向进程发送了一个sigtstp信号,让进程暂时挂起,输入ps命令符可以发现hello进程还没有被关闭。

输入ctrl+C,这个操作向进程发送了一个sigint信号,让进程直接结束,输入ps命令可以发现当前hello进程已经被终止了。

使用fg命令,让后台挂起程序继续运行。

Jobs命令可以看到当前进程状态。

6.7本章小结

本章初步讲述了进程的概念和作用讲述了shell如何在用户程序运行时通过fork函数创建一个新的进程以及通过execve函数加载一个新的进程讲述了shell如何在用户和系统内核直接按通过上下文切换建立了一个交互运行的桥梁,还通过简单距离讲述了信号机制在程序运行中的作用,了解到了前端程序和后台程序。

(第61分)

7章 hello的存储管理

7.1 hello的存储器地址空间

逻辑地址:很简单,就是你源程序里使用的地址,或者源代码经过编译以后编译器将一些标号,变量转换成的地址,或者相对于当前段的偏移地址。

物理地址:计算机系统的主存被组织成一个由M个连续的字节大小的单元组成的数组。每个字节都有唯一的物理地址。

虚拟地址:虚拟地址就是逻辑地址,又叫虚地址。

线性地址:分段机制下CPU寻址是二维的地址即,段地址:偏移地址,CPU不可能认识二维地址,因此需要转化成一维地址即,段地址*16+偏移地址,这样得到的地址便是线性地址(在未开启分页机制的情况下也是物理地址)。

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

 段式内存管理方式就是直接将逻辑地址转换成物理地址,也就是CPU不支持分页机制。其地址的基本组成方式是段号+段内偏移地址。每个段选择符大小为16位,段描述符为8字节。GDT为全局描述表,LDT为局部描述符。段描述符存放在描述符表中,也就是GDT或LDT中,段首地址存放在段描述符中。

可以看出段选择符由三个部分组成,从右向左依次是RPL、TI、index(索引)。RPL在此不做介绍。先来看TI,当TI=0时,表示段描述符在GDT中,当TI=1时表示段描述符在LDT中。Index表示了在这个描述符表中的偏移量。如果index为1,则偏移8个字节。

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

在这个转换中要用到翻译后备缓冲器(TLB),首先我们先将线性地址分为VPN(虚拟页号)+VPO(虚拟页偏移)的形式,然后再将VPN拆分成TLBT(TLB标记)+TLBI(TLB索引)然后去TLB缓存里找所对应的PPN(物理页号)如果发生缺页情况则直接查找对应的PPN,找到PPN之后,将其与VPO组合变为PPN+VPO就是生成的物理地址了。

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

TLB即翻译后备缓冲器,它是一个小的、虚拟寻址的缓存,其中每一行都保存着一个由单个PTE组成的块,TLB通常有高度的相连度。所以每次在翻译地址的过程时可以先在TLB中查询是否命中以直接获取物理地址。

多级页表:如果只用一个单独的页表进行地址翻译,会有一个占据内存大量空间的页表驻留在内存中,因此我们需要压缩页表,也就是使用了多级页表。一级页表中的每个PTE负责映射到虚拟地址空间中的一个片,这里每一个片都是由1024个连续的页面组成(也就是二级页表)再用这些二级页表的PTE覆盖整个空间。两级页表层次结构如下图所示:

当TLB与四级页表相结合其地址翻译过程如下:先将这个虚拟地址的VPN分为TLB标记部分和TLB索引部分检查是否再TLB命中如果命中直接取出物理地址,否则的化虚拟地址被划分为4个VPN和一个VPO每个VPN(i)对应了第i级页表的索引,通过这个索引最后对应了一个固定的PPN将这个PPN与VPO结合得到新的物理地址,并把这个物理地址的信息存入TLB缓存。

多级页表VPN的划分的示意图如下:

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

系统在得到物理内存后,先将物理内存分为CT(标记)+ CI(索引)+CO(块偏移)然后在L1cache中找按是否命中如果命中直接返回否则的化进入下一级cache,做相同的操作,如果L2,L3cache都没有命中会从主存中取出这个地址的值。得到地址存储的信息后,会把信息写入上一级的cache中,如图所示:

7.6 hello进程fork时的内存映射

 当fork函数被shell进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID,为了给这个新进程创建虚拟内存,它创建了当前进程的mm_struct、区域结构和页表的原样副本。它将这两个进程的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。

当fork在新进程返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任意一个后来进行写操作时,写时复制机制就会创建新的页面,因此也就为每个进程保持了私有地址空间的抽象概念。

7.7 hello进程execve时的内存映射

  1. 删除已存在的用户区域
  2. 创建新的私有区域(.malloc,.data,.bss,.text)
  3. 创建新的共享区域(libc.so.data,libc.so.text)
  4. 设置PC,指向代码的入口点

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

DRAM缓存的不命中陈称为缺页。DRAM缓存的不命中触发一个缺页故障,缺页故障嗲用内核中的缺页异常处理程序,该程序会选择一个牺牲页,如果该牺牲页已经做了更改,那么内核会将它复制回磁盘,否则不会进行复制即写回,然后将牺牲页从DRAM中出去,更新该页的位置放入待取的页面。然后CPU重新执行造成缺页故障的命令此时将可以正常运行。

7.9动态存储分配管理

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

分配器分为两种基本风格:显式分配器、隐式分配器。

显式分配器:要求应用显式地释放任何已分配的块。

隐式分配器:要求分配器检测一个已分配块何时不再使用,那么就释放这个块,自动释放未使用的已经分配的块的过程叫做垃圾收集。

动态存储分配管理需要考虑地式分配速率和堆栈地利用绿,其中影响堆栈利用率低地主要原因是一种称为碎片地现象,非为外部碎片和内部碎片。

其中内部碎片是在一个已分配块比有效荷载大时发生地。外部碎片是当空西安内存合计起来能够满足一个分配请求,但他们不是连续地时候发生地。

7.10本章小结

本章介绍了系统的存储器地址空间的概念讲述了虚拟地址、物理地址、线性地址以及逻辑地址的概念,还阐述了逻辑地址到线性地址、线性地址到物理地址的翻译过程,讲述了intel的管理方式。同时结合TLB与多级页表详细阐述了如何优化页地址的翻译过程讲述了系统运行时的存储方式。本章还讲述了进程运行时使用fork函数创建新的进程,以及execve函数加载新进程时系统对内存空间做了哪些事,其中比较有趣的是私有的写时复制,大大节省了内存空间的占用。本章还描述了系统是如何应对缺页故障现象的,讲述了动态存储分配的多种管理方式。

(第7 2分)

8章 hello的IO管理

8.1 Linux的IO设备管理方法

每个Linux文件都有一个类型来表明它在系统中的角色:

普通文件:包含人一数据。应用程序常常需要区分文本文件和二进制文件,文本文件是只含有ASCLL或Unicode字符的普通文件;二进制文件是所有其他文件,对于内核来说这二者没有却别。

目录:是包含一组链接的文件,其中每一个链接都将一个文件名映射到另一个文件。

套接字:是用来与另一个进程进行跨网络通信的文件。

其他文件类型包括命名通道、符号链接以及字符和块设备。

unix io接口包括打开和关闭文件、读和写文件以及改变当前文件的位置。

8.2 简述Unix IO接口及其函数

打开和关闭文件:

      Open()函数:这个函数回打开一个已经存在的文件或者创建一个新的文件,可以添加参数只读,只写和可读可写。

      Close()函数:这个函数关闭一个已经打开的文件。

      读和写文件:

      应用程序通过分别调用read和write函数来执行输入和输出的。

read函数从描述符为fd的当前文件位置复制最多n个字节到内存位置buf

返回值-1表示一个错误,返回值为0表示EOF。否则返回值表示的是实际传送的字节数量。

write函数从内存buf位置复制至多n个字节到描述符fd的当前文件位置。

改变文件位置。

8.3 printf的实现分析

研究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;

}

可以看到printf接受了一个fmt的格式,然后将匹配到的参数按照fmt格式输出。我们看到printf函数中调用了两个系统调用分别是vsprintf和write,先看看vsprintf函数:

int vsprintf(char *buf, const char *fmt, va_list args)

{

    char *p;

    char tmp[256];

    va_list p_next_arg = args;

    for (p = buf; *fmt; fmt++)

    {

        if (*fmt != '%')

        {

            *p++ = *fmt;

            continue;

        }

        fmt++;

        switch (*fmt)

        {

        case 'x':

            itoa(tmp, *((int *)p_next_arg));

            strcpy(p, tmp);

            p_next_arg += 4;

            p += strlen(tmp);

            break;

        case 's':

            break;

        default:

            break;

        }

    }

    return (p - buf);

}

可以看到这个函数的作用是将所有参数内容格式化后存入buf,然后返回格式化数组的长度。而另一个函数write是一个输出到终端的系统调用在此不做赘述。

所以printf函数的执行过程是:从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息。显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。

8.4 getchar的实现分析

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

getchar函数的实现过程如下:

int getchar(void)

{

    static char buf[BUFSIZ];

    static char* bb=buf;

    static int n=0;

    if(n==0)

    {

        n=read(0,buf,BUFSIZ);

        bb=buf;

    }

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

}

可以看到getchar函数调用了系统函数read,读入BUFSIZE字节到buf,然后返回buf的首地址,注意到只有当n = 0时才会调用read函数,如果n = 0还会返回EOF文件终止符。异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。

8.5本章小结

本章节讲述了一下linux的I/O设备管理机制,了解了开、关、读、写、转移文件的接口及相关函数,简单分析了printf和getchar函数的实现方法以及操作过程。

(第81分)

结论

通过对hello程序一生的逐步拆解,它大致需要经过预处理、编译、汇编和链接四个操作。

预处理器将hello.c预处理称为hello.i, 获得了更多新的知识buff(如hello加载的库文件)

然后编译器将hello.i翻译成了汇编语言hello.s,变成了一个更接近于机器的语言。

汇编器又将hello会变成可重定位二进制文件hello.o就这样hello学会了机器语言,但是它还有很多困惑(外部文件尚未被链接,多种信息需要重定位)

链接器又将hello.o与外部文件链接解决了hello的困惑使得hello称为一个完成的可执行目标文件。

当在shell中下达了执行hello的指令,shell通过execve创建了一个新进程并赋予hello一个全新PID

当hello收到异常信号时,它会自动挂起等待shell的下一步命令。

最后当hello完成工作后,shell会收回它的工作PID,hello将等待它的下一次被调用。‘

同时在完成大作业的过程中,也顺带进行了整本书的回顾,对程序运行更加了解。

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

附件

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

Hello.c(源文件)

Hello.i(预处理后文件)

Hello.s(编译文件)

Hello.o(汇编后,可重定位文件)

Hello.out(可执行文件)

Hello_o.s(反汇编)

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

参考文献

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

[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.

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

[8]  深入理解计算机系统 Randal E.Bryant, David R.O’Hallaron

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值