计算机系统大作业

计算机系统

大作业

计算机科学与技术学院
2018年12月
摘 要
通过对Hello进行深入分析,而对整个学期所学期的知识点融会贯通。这次大作业加深了对于课本知识的深刻理解,提高了对于各项实验的实践能力,是对于计算机系统学习的深化与升华。

关键词:Hello,计算机系统,HIT,大作业

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

目 录

第1章 概述 - 4 -
1.1 HELLO简介 - 4 -
1.2 环境与工具 - 4 -
1.3 中间结果 - 4 -
1.4 本章小结 - 5 -
第2章 预处理 - 6 -
2.1 预处理的概念与作用 - 6 -
2.2在UBUNTU下预处理的命令 - 6 -
2.3 HELLO的预处理结果解析 - 6 -
2.4 本章小结 - 8 -
第3章 编译 - 9 -
3.1 编译的概念与作用 - 9 -
3.2 在UBUNTU下编译的命令 - 9 -
3.3 HELLO的编译结果解析 - 9 -
3.4 本章小结 - 11 -
第4章 汇编 - 13 -
4.1 汇编的概念与作用 - 13 -
4.2 在UBUNTU下汇编的命令 - 13 -
4.3 可重定位目标ELF格式 - 13 -
4.4 HELLO.O的结果解析 - 15 -
4.5 本章小结 - 15 -
第5章 链接 - 18 -
5.1 链接的概念与作用 - 18 -
5.2 在UBUNTU下链接的命令 - 18 -
5.3 可执行目标文件HELLO的格式 - 18 -
5.4 HELLO的虚拟地址空间 - 20 -
5.5 链接的重定位过程分析 - 22 -
5.6 HELLO的执行流程 - 22 -
5.7 HELLO的动态链接分析 - 23 -
5.8 本章小结 - 23 -
第6章 HELLO进程管理 - 24 -
6.1 进程的概念与作用 - 24 -
6.2 简述壳SHELL-BASH的作用与处理流程 - 24 -
6.3 HELLO的FORK进程创建过程 - 24 -
6.4 HELLO的EXECVE过程 - 24 -
6.5 HELLO的进程执行 - 25 -
6.6 HELLO的异常与信号处理 - 25 -
6.7本章小结 - 25 -
第7章 HELLO的存储管理 - 27 -
7.1 HELLO的存储器地址空间 - 27 -
7.2 INTEL逻辑地址到线性地址的变换-段式管理 - 27 -
7.3 HELLO的线性地址到物理地址的变换-页式管理 - 27 -
7.4 TLB与四级页表支持下的VA到PA的变换 - 29 -
7.5 三级CACHE支持下的物理内存访问 - 29 -
7.6 HELLO进程FORK时的内存映射 - 29 -
7.7 HELLO进程EXECVE时的内存映射 - 30 -
7.8 缺页故障与缺页中断处理 - 30 -
7.9动态存储分配管理 - 31 -
7.10本章小结 - 32 -
第8章 HELLO的IO管理 - 34 -
8.1 LINUX的IO设备管理方法 - 34 -
8.2 简述UNIX IO接口及其函数 - 34 -
8.3 PRINTF的实现分析 - 34 -
8.4 GETCHAR的实现分析 - 35 -
8.5本章小结 - 36 -
结论 - 36 -
附件 - 37 -
参考文献 - 38 -

第1章 概述
1.1 Hello简介
通过文本输入得到Hello.c,为源程序
对Hello.c这一源程序依次进行预处理,编译,汇编,链接的操作之后,生成可执行程序Hello。在shell中使其运行,fork产生子进程,则Hello从program成为Process,这个过程即为P2P(Program to process)。
紧接着进行execve,通过依次进行对虚拟内存的映射,物理内存的载入,进入主函数执行代码。hello调用write等系统函数在屏幕打印信息,之后shell父进程bash回收Hello的进程,一切相关的数据结构被删除。
以上即为020(From Zero to Zero)的全部过程。

1.2 环境与工具
硬件:CPU Intel Core i3 3110m 500GB HDD 4G RAM
软件环境
Microsoft Windows7 专业版 64位; VMware Workstation 14 Pro; Ubuntu 16.04
开发工具
Visual Studio 2017 64位; CodeBlocks 64位;readelf;vim+gcc;Hexedit;edb
1.3 中间结果
文件的名字 文件的作用
hello.c 大作业的hello.c源程序
hello.i 经过预处理后的中间文件
hello.s 经过编译后的汇编文件
hello.o 经过汇编后的可重定位目标执行文件
hello 经过链接后的可执行程序
Asm1.txt hello反汇编生成的代码
hello.elf Hello的ELF
Asm2.txt hello.o反汇编生成的代码
ohello.elf hello.o的ELF

1.4 本章小结
1.介绍了Hello的P2P与020过程
2.列出了大作业的各种环境及中间各结果
(第1章0.5分)

第2章 预处理
2.1 预处理的概念与作用
概念:

程序设计领域中,预处理一般是指在程序源代码被翻译为目标代码的过程中,生成二进制代码之前的过程。典型地,由预处理器(preprocessor) 对程序源代码文本进行处理,得到的结果再由编译器核心进一步编译。这个过程并不对程序的源代码进行解析,但它把源代码分割或处理成为特定的单位——(用C/C++的术语来说是)预处理记号(preprocessing token)用来支持语言特性(如C/C++的宏调用)。
最常见的预处理是C语言和C++语言。ISO C和ISO C++都规定程序由源代码被翻译分为若干有序的阶段(phase) ,通常前几个阶段由预处理器实现。预处理中会展开以#起始的行,试图解释为预处理指令 (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.3 Hello的预处理结果解析

用vim打开hello.i,发现总行数变为3126行,增加了许多#开头的代码,这些代码从stdio.h等中提取

可以看到int main()来到了第3110行,之前插入了大量代码,包含了相关函数的申明,如printf

如图为printf的函数申明
2.4 本章小结
Hello.c在编译之前先经过预处理生成hello.i,在源代码的基础上插入了大量#开头的代码,以便于接下来的编译工作。
(以下格式自行编排,编辑时删除)

(第2章0.5分)

第3章 编译
3.1 编译的概念与作用

概念:
编译器(ccl)将文本文件hello.i翻译成文本文件hello.s,即将经过预处理的源代码转换为汇编代码
注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序
作用:将更偏向于人的高级语言翻译为更偏向于机器的汇编语言,以便于之后机器的执行

3.2 在Ubuntu下编译的命令
gcc -S hello.i -o hello.s

3.3 Hello的编译结果解析
出现的标志
file 源文件

.data 数据段
.globl 全局标识符
.string 字符串类型
.string 字符串类型
.long long类型
.text 代码段

全局变量int sleepecs: 大小为4字节,并赋了初值2

参数int argc:使用了寄存器%rbp作为数组argc[]的栈指针

其中,%rbp-32为argc[1]的地址,之后的地址依次加8即可。
Printf中的字符串:

赋值:通过movl完成对i赋值为0的操作

条件判断转移:通过比较cmpl与跳转je,jle来实现argc!=3和i<10的比较,然后转移

For循环:
先通过赋值给i一个初值0,然后通过条件控制转移,当i<10时反复执行.l4,当i=10则跳出,执行后面的语句

相关函数的调用:
Printf:其中寄存器%rsi存放argv[1],%rdx存放argv[2]

Sleep:设置edi为sleepsecs

Exit:参数为1

Getchar:

3.4 本章小结
详细分析了hello.c翻译成汇编语言时里面各变量各条件控制以及各函数调用。

(第3章2分)

第4章 汇编
4.1 汇编的概念与作用
概念:
汇编器(as)将汇编语言hello.s翻译成机器语言,打包生成可重定位目标程序的形式,储存结果生成hello.o(为二进制文件)

注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。
作用:
将偏向于机器的汇编语言翻译成完全可以由机器执行的二进制命令,以便于之后的链接

4.2 在Ubuntu下汇编的命令
as hello.s -o hello.o

4.3 可重定位目标elf格式

ELF头:
Magic:Magic为16字节序列,包含了该文件的大小与字节顺序
其余部分为各种信息,如ELF头的大小,目标文件类型,小端法还是大端法,文件类型,机器类型,节头部表的文件偏移条目的大小和数量等

节头部表:用来描述各个节的大小,类型,位置等各种信息

重定位节:
描述了.text中的各个重定位信息,每个信息包含:名称;8字节的计算辅助信息;类型;8字节的信息Info(由4字节的偏移量symbol和4字节的类型type组成);8字节的偏移位置offset
下图为hello.c中各函数与字符串的重定位信息

4.4 Hello.o的结果解析

与hello.s大体相同,区别如下:

  1. 跳转:
    在跳转时使用了确定的地址,而非.l1,.l2之类的段名称跳转表

  2. 调用函数:
    在callq时采用了call offset的地址方式,而非hello.s的call symbol

  3. 全局变量:
    与2类似,使用全局变量时采用了offset(%rip)的形式,而非hello.s使用的symbol(%rip)

函数的一一对应:

4.5 本章小结
深入分析了hello.o的ELF,看到了各表的相关信息,比较了.o的反汇编与.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.3 可执行目标文件hello的格式

下图分别为ELF头,节头部表和重定位节,具体分析与4.3类似不再赘述

5.4 hello的虚拟地址空间
1. .plt

  1. .text

3 .rodata

  1. .data

  2. .got

5.5 链接的重定位过程分析

不同之处:

  1. 增加函数:相对于hello.o, hello的反汇编增加了许多函数,例如plt表,init等

  2. 函数调用:直接跳转到下一条指令的地址

重定位记录分为PC相对地址的引用和绝对地址的引用。
根据.rela.text和.rela.data中的重定位记录,在.symtab中查找需要修改的记录的符号,并结合符号与重定位记录中的位置信息对目标位置进行修改。如是本地符号,计算偏移量并修改目标位置;否则如果是共享库中的符号,则创建.got表
5.6 hello的执行流程
函数名 地址
ld-2.23.so!_dl_start 0x7fb78d93ad77
ld-2.23.so!_dl_init 0x7fce8dc47381
Start 0x400520
Init 0x4004c9
Main 0x400666
Exit 0x7fb78d5a67a4
libc-2.23.so!exit 0x7fce8c786565

5.7 Hello的动态链接分析
动态链接器使用过程链接表PLT+全局偏移量表GOT实现函数的动态链接,它在数据段开始的地方创建了一个表,叫做全局偏移量表(GOT)。在GOT中,每个被这个目标模块引用的全局数据目标都有一个8字节条目。编译器还为GOT中每个条目生成 一个重定位记录。在加载时,动态链接器会重定位GOT中的每个条目,使得它包含目标的正确的绝对地址。
在init调用之前,PIC函数调用的目标地址都实际指向PLT中的代码逻辑,PLT中函数调用指令的下一条指令地址存放在GOT

在调用之后,其中GOT指向重定位表(.plt节需要重定位的函数的运行时地址)用来确定,指向动态链接器运行时地址。

函数调用则跳转到PLT,执行GOT地址的下一条指令,函数和重定位表地址压栈,最后访问动态链接器,通过一系列计算得到函数运行时地址,重写GOT,再将控制传递给目标函数。(特别地,如果该函数曾被调用,第一次访问跳转直接跳转到目标函数。)
5.8 本章小结
研究了hello的链接全过程,深刻分析了虚拟地址,重定位,执行流程与动态链接。
(第5章1分)

第6章 hello进程管理
6.1 进程的概念与作用
概念:
进程是一个具有独立功能的程序关于某个数据集合的一次运行活动。它可以申请和拥有系统资源,是一个动态的概念,是一个活动的实体。它不只是程序的代码,还包括当前的活动,通过程序计数器的值和处理寄存器的内容来表示。
作用:
为用户提供了以下假象:我们的程序好像是系统中当前运行的唯一程序一样,我们的程序好像是独占的使用处理器和内存,处理器好像是无间断的执行我们程序中的指令,我们程序中的代码和数据好像是系统内存中唯一的对象。
6.2 简述壳Shell-bash的作用与处理流程
作用:
shell作为命令语言,它交互式解释和执行用户输入的命令或者自动地解释和执行预先设定好的一连串的命令;作为程序设计语言,它定义了各种变量和参数,并提供了许多在高级语言中才具有的控制结构,包括循环和分支。
处理流程:

  1. 读取命令,分析参数
  2. 如果是内部命令则直接执行
  3. 否则fork一个子进程执行
  4. 执行完毕后回收该进程

6.3 Hello的fork进程创建过程
运行命令./hello 1171000712 LiYuxuan
分析命令,发现./hello并不是内置命令,1171000712 LiYuxuan为参数
之后fork子进程,与它父进程虚拟地址相同(但是是独立的,这需要注意),拥有着不同的PID
6.4 Hello的execve过程
子进程创建完成后,execve开始加载运行hello,通过栈等将参数(1171000712 LiYuxuan)传递给main(),开始运行
6.5 Hello的进程执行
逻辑控制流:即为一系列程序计数器PC的值序列进程是轮流使用处理器的,在同一个处理器核心中,每个进程执行它的流的一部分后被抢占(暂时挂起),然后轮到其他进程。
时间片:进程执行它的控制流的部分时间段。
用户模式和内核模式:用户模式即为未设置模式位,不允许执行特权指令直接引用地址空间中内核区内的代码和数据;内核模式为设置模式位时,该进程执行所有命令访问所有数据。
上下文信息:上下文就是内核重启被抢占的进程所需要的状态,它由通用寄存器等各种内核数据构成。分别三个步骤:
保存当前进程的上下文
恢复某个先前被抢占的进程被保存的上下文
将控制传递给这个新恢复的进程。

6.6 hello的异常与信号处理
故障:缺页异常,hello进程的页表被映射到hello文件,然而实际代码拷贝至内存仍未完成,在执行到相应地址的代码时会引发缺页异常。
终止:不可恢复错误发生。
中断:接受到键盘键入的信号,如ctrl Z ,ctrl C等

6.7本章小结
讨论了hello进程的shell-bash,依次分析了fork, execve, 进程执行以及异常及信号处理。
(第6章1分)

第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:逻辑地址(LogicalAddress)是指由程序产生的与段相关的偏移地址部分。
物理地址:要经过寻址方式的计算或变换才得到内存储器中的实际有效地址,即物理地址。
线性地址:线性地址(Linear Address)是逻辑地址到物理地址变换之间的中间层。在分段部件中逻辑地址是段中的偏移地址,然后加上基地址就是线性地址。
虚拟地址:这里的虚拟地址就是线性地址
7.2 Intel逻辑地址到线性地址的变换-段式管理
逻辑地址CS:EA=物理地址CS*16+EA
一个逻辑地址由两部份组成,段标识符和段内偏移量。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号。后面3位包含一些硬件细节

7.3 Hello的线性地址到物理地址的变换-页式管理
这种页式管理方式中,第一级的页表称之为“页目录”,用于存放页表的基地址;第二级才是真正的“页表”用于存放物理内存中页框的基地址。

   1、二级页目录的页式内存管理方式中,第一级的页目录的基址存放在CPU寄存器CR3中,这也是转换的开始点;

   2、每一个活动的进程,都有其对应的独立虚拟内存(页目录也是唯一的),那么它对应一个独立的页目录地址。--运行一个进程,需要将它的页目录地址放到CR3寄存器中,将别的页目录的基址暂时换到内存中;

   3、每个32位的线性地址被划分成三部分,页目录索引(10位),页表索引(10位),偏移量(12位)。

线性地址转换成物理地址的过程如下:

   1、从CR3中取出进程的页目录的地址(操作系统在负责进程的调度的时候,将这个地址装入对应的CR3地址寄存器),取出其前20位,这是页目录的基地址;

   2、根据取出来的页目录的基地址以及线性地址的前十位,进行组合得到线性地址的前十位的索引对应的项在页目录中地址,根据该地址可以取到该地址上的值,该值就是二级页表项的基址;当然你说地址是32位,这里只有30位,其实当取出线性地址的前十位之后还会该该前十位左移2位,也就是乘以4,一共32位;之所以这么做是因为每个地址都是4B的大小,因此其地址肯定是4字节对齐的,因此左移两位之后的32位的值恰好就是该前十位的索引项的所对应值的起始地址,只要从该地址开始向后读四个字节就得到了该十位数字对应的页目录中的项的地址,取该地址的值就是对应的页表项的基址;

   3、根据第二步取到的页表项的基址,取其前20位,将线性地址的10-19位左移2位(原因和第2步相同),按照和第2步相同的方式进行组合就可以得到线性地址对应的物理页框在内存中的地址在二级页表中的地址的起始地址,根据该地址向后读四个字节就得到了线性地址对应的物理页框在内存中的地址在二级页表中的地址,然后取该地址上的值就得到线性地址对应的物理页框在内存中的基地址;(这一步的地址比较绕,还请仔细琢磨,反复推敲)

   4、根据第3步取到的基地址,取其前20位得到物理页框在内存中的基址,再根据线性地址最后的12位的偏移量得到具体的物理地址,取该地址上的值就是最后要得到值;

7.4 TLB与四级页表支持下的VA到PA的变换
虚拟地址被分成VPN和VPO两部分,VPN被分为TLBT和TLBI用于在TLB中查询。根据TLBI确定TLB中的组索引,TLBT判断PPN是否已被缓存到TLB中,若TLB命中,返回PPN,否则会到页表中查询PPN。
在页表中查询PPN时,VPN会被分为四个部分,分级页表的索引,前三级页表的查询结果均为下一级页表的基地址,第四级页表的查询结果为PPN。将查询到的PPN与VPO组合,得到物理地址。
7.5 三级Cache支持下的物理内存访问
1.发送物理地址给一级cache
2.分别解析出CO,CT等信息,并根据CT判断是否命中
3.若命中则读取数据否则提交给二级三级cache重复上述步骤
4.若均为命中进入主存中查找
7.6 hello进程fork时的内存映射
当fork函数被shell进程调用时,内核为新进程创建各种数据结构,并分配PID(注意PID是唯一的),它创建了当前进程的mm_struct、区域结构和页表的原样副本。它将这两个进程的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。
7.7 hello进程execve时的内存映射
1.删除已有的区域
即删除已经存在的区域结构
2.映射私有区域
即为新程序的代码、数据、bss和栈区域创建新的区域结构,所有这些新的区域都是私有的、写时复制的.
例如hello中.data区以及.text区
3.映射共享区域
即映射动态链接,共享对象及其相关到虚拟地址的共享区域内
例如hello中的库libc.so
7.8 缺页故障与缺页中断处理
缺页中断就是要访问的页不在主存,需要操作系统将其调入主存后再进行访问。在这个时候,被内存映射的文件实际上成了一个分页交换文件。当指令引用一个虚拟地址,在MMU中查找页表时发现与该地址相对应的物理地址不在内存中,因此必须从磁盘中取出的时候就会发生故障。
中断处理:
缺页中断处理一般流程:

1.硬件陷入内核,在堆栈中保存程序计数器,大多数当前指令的各种状态信息保存在特殊的cpu寄存器中。

2.启动一个汇编例程保存通用寄存器和其他易丢失信息,以免被操作系统破坏。

3.当操作系统发现缺页中断时,尝试发现需要哪个虚拟页面。通常一个硬件寄存器包含了这些信息,如果没有的话操作系统必须检索程序计数器,取出当前指令,分析当前指令正在做什么。

4.一旦知道了发生缺页中断的虚拟地址,操作系统会检查地址是否有效,并检查读写是否与保护权限一致,不过不一致,则向进程发一个信号或者杀死该进程。如果是有效地址并且没有保护错误发生则系统检查是否有空闲页框。如果没有,则执行页面置换算法淘汰页面。

5.如果选择的页框脏了,则将该页写回磁盘,并发生一次上下文切换,挂起产生缺页中断的进程让其他进程运行直到写入磁盘结束。且回写的页框必须标记为忙,以免其他原因被其他进程占用。

6.一旦页框干净后,操作系统查找所需页面在磁盘上的地址,通过磁盘操作将其装入,当页面被装入后,产生缺页中断的进程仍然被挂起,并且如果有其他可运行的用户进程,则选择另一用户进程运行。

7.当磁盘中断发生时,表明该页已经被装入,页表已经更新可以反映他的位置,页框也标记位正常状态。

8.恢复发生缺页中断指令以前的状态,程序计数器重新指向这条指令。

9.调度引发缺页中断的进程,操作系统返回调用他的汇编例程

10.该例程恢复寄存器和其他状态信息,返回到用户空间继续执行,就好像缺页中断没有发生过。

7.9动态存储分配管理
动态内存分配器维护进程的虚拟内存,称为堆。
分配器将堆视为一组不同大小的块的集合来维护。
每个块就是一个连续的虚拟内存片,可以是已分配的、空闲的。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被释放。
分配器分为两种:显式分配器、隐式分配器。
显式分配器:要求应用显式地释放任何已分配的块。
隐式分配器:要求分配器检测一个已分配块何时不再使用,那么就释放这个块, 自动释放未使用的已经分配的块的过程叫做垃圾收集。
隐式:分配器通过遍历堆中所有的块,从而间接地遍历整个空闲块的集合。

显式:
分配块的组织形式与隐式基本一致,而空闲块组织成链表形式的数据结构。每个空闲块中,都包含一个 pred(前驱)和 succ(后继)指针。
优点是首次适配的分配时间从块总数的线性时 间减少到了空闲块数量的线性时间。
维护链表的顺序有:
1.后进先出(LIFO):将新释放的块放置在链表的开始出,释放一个块可以在线性的时间内完成,如果使用了边界标记,那额合并也可以在常熟时间内完成。
2.按照地址顺序:
其中链表中的每个块的地址都小于它的后继的地址,在这种情况下,释放一个块需要线性时间的搜索来定位合适的前驱。优点在于,按照地址排序的首次适配比 LIFO 排序的首次适配有着更高的内存利用率,接近最佳适配的利用率。(因为这样连接空闲链表,使小的碎片更容易产生在前面)

7.10本章小结
具体研究了存储器地址空间 逻辑地址到线性地址的变换-段式管理 线性地址到物理地址的变换-页式管理 TLB与四级页表支持下的VA到PA的变换 三级Cache支持下的物理内存访问 hello进程fork时的内存映射 进程fork时的内存映射 进程execve时的内存映射 缺页故障与缺页中断处理 动态存储分配管理等内容。
(第7章 2分)

第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件
设备管理:unix io接口
所有的IO设备都被模型化为文件,而所有的输入和输出都被当做对相应文件的读和写来执行,这种将设备优雅地映射为文件的方式,允许Linux内核引出一个简单低级的应用接口,称为Unix I/O。
8.2 简述Unix IO接口及其函数
UNIX IO:1.打开文件
2.标准输入输出
3.关闭文件
对应函数:

  1. int open(char* filename,int flags,mode_t mode)
    char* filename文件名(包含位置)
    int flags 打开方式
    mode t mode 权限
  2. ssize_t read(int fd,void *buf,size_t n) ssize_t wirte(int fd,const void *buf,size_t n)
    void *buf,size_t n位置信息
  3. int close(fd)

8.3 printf的实现分析
代码:
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’: //以x为例子代替d c 等
itoa(tmp, ((int)
strcpy(p, tmp);
p_next_arg += 4;
p += strlen(tmp);
break;
case ‘s’:
break;
default:
break;
}
}
return (p - buf);
}

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首先确定arg这一格式化参数,然后调用vsprintf函数判断%标志符号(如%d),其中无关符号自动略过,返回需要输出的结果串及长度,最后通过系统函数write输出长度i的串buf到屏幕上
8.4 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由宏实现:#define getchar() getc(stdin)。getchar有一个int型的返回值。当程序调用getchar时.程序就等着用户按键。用户输入的字符被存放在键盘缓冲区中。直到用户按回车为止(回车字符也放在缓冲区中)。当用户键入回车之后,getchar才开始从stdio流中每次读入一个字符。getchar函数的返回值是用户输入的字符的ASCII码,若文件结尾(End-Of-File)则返回-1(EOF),且将用户输入的字符回显到屏幕。如用户在按回车之前输入了不止一个字符,其他字符会保留在键盘缓存区中,等待后续getchar调用读取。也就是说,后续的getchar调用不会等待用户按键,而直接读取缓冲区中的字符,直到缓冲区中的字符读完后,才等待用户按键。
8.5本章小结
介绍了UNIX IO的相关内容及函数,重点解析了printf和getchar两个函数

(第8章1分)
结论
Hello的一生:

  1. 用户输入源代码文本hello.c
  2. 预处理得到文本hello.i
  3. 编译得到汇编代码hello.s
  4. 汇编得到可重定位目标文件hello.o
  5. 链接生成可执行文件hello
  6. 在shell中运行hello
  7. Fork子进程,然后调用execve,载入内存,进入主函数
  8. Cpu分配时间片,执行指令
  9. 通过地址访问内存读取数据
  10. 调用printf和getchar函数输出,读入
  11. 接受键盘信号,如ctrl z
  12. 结束运行,父进程回收
    Hello.c虽然短小,但是hello的一生却相当丰富多彩,而这精彩的一生正是程序从人到机器完美转换的一片缩影,闪烁着计算机系统的无穷魅力。
    (结论0分,缺失 -1分,根据内容酌情加分)

附件
文件的名字 文件的作用
hello.c 大作业的hello.c源程序
hello.i 经过预处理后的中间文件
hello.s 经过编译后的汇编文件
hello.o 经过汇编后的可重定位目标执行文件
hello 经过链接后的可执行程序
Asm1.txt hello反汇编生成的代码
hello.elf Hello的ELF
Asm2.txt hello.o反汇编生成的代码
ohello.elf hello.o的ELF

列出所有的中间产物的文件名,并予以说明起作用。
(附件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]百度百科https://baike.baidu.com/
[8] https://blog.csdn.net/gdj0001/article/details/80135196
逻辑地址、线性地址和物理地址之间的转换
[9] https://blog.csdn.net/a7980718/article/details/80895302
linux内核缺页中断处理
(参考文献0分,缺失 -1分)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值