哈尔滨工业大学 计算机系统 大作业 22春

计算机系统大作业

计算机科学与技术学院
2022年5月

摘 要

摘要是论文内容的高度概括,应具有独立性和自含性,即不阅读论文的全文,就能获得必要的信息。摘要应包括本论文的目的、主要内容、方法、成果及其理论与实际意义。摘要中不宜使用公式、结构式、图表和非公知公用的符号与术语,不标注引用文献编号,同时避免将摘要写成目录式的内容介绍。

本文以hello程序的生命周期为线索,对一个程序从编写完成,经过各阶段处理得到可执行文件,再经过执行的各个阶段,最终结束执行,周期结束的全过程进行了分析。具体来说,对预处理、编译、汇编、链接、进程、存储、IO管理都进行了解释并结合hello程序进行分析。

关键词:生命周期;从c程序到可执行;执行各阶段;

目 录

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

第1章 概述

1.1 Hello简介
通过文本编辑器或者各种IDE编写hello程序,得到hello.c源程序;
运行C预处理器将hello.c进行预处理生成hello.i文件;
运行C编译器将hello.i翻译为汇编语言文件hello.s;
运行汇编器将hello.s翻译为可重定位目标文件hello.o;
运行链接器将hello.o和系统目标文件相结合,创建可执行目标文件hello;
通过shell输入./hello,shell通过fork函数创建新进程,调用execve映射虚拟内存,通过mmap为hello程序开创一段虚拟地址;
CPU从虚拟内存中的.text,.data节获取代码和数据,调度器为进程规划时间片,接受异常时出发异常处理子程序;
程序运行结束时,父进程回收hello进程和它创建的子进程,内核删除对应数据结构。
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
硬件:

图1:个人电脑配置截图
软件:
Linux ubuntu 20.04
Windows11

1.3 中间结果
hello.c 源程序
hello.i 预处理后文件
hello.s 编译后的汇编文件
hello.o 汇编后的可重定位目标执行文件
hello 链接后的可执行文件
1.4 本章小结
总体介绍了hello程序一步步变成可执行文件的过程,提供了进行实验的基本设备软硬件信息。
(第1章0.5分)

第2章 预处理

2.1 预处理的概念与作用
预处理是可运行代码产生的第一步,预处理阶段计算机进行的工作包括:展开头文件、宏替换、去掉注释、条件编译。
预处理器cpp根据以#开头的命令,包括头文件声明、宏定义、条件编译,修改原始的C程序。
预处理是一种“展开”,所做的工作本质上来说就是复制、粘贴、删除,复制头文件/宏的具体内容,然后粘贴到声明头文件/宏的位置,删除注释。

2.2在Ubuntu下预处理的命令
gcc hello.c -E -o hello,i

图2:ubuntu下预处理hello.c

2.3 Hello的预处理结果解析
查看生成的文件hello.i,发现原始的c程序在预处理后扩展到了3060行;
原始的hello.c部分的代码在预处理后被放到了程序的末尾,前面三千多行代码都是预处理器复制粘贴替换得到的;由此验证了预处理不会更改main部分的代码,而是对带#前缀的所有部分进行展开。
并且如果展开过程中出现了嵌套(展开的文件中含有需要展开的部分),预处理器会进行递归式的展开,直到展开后所有代码内容中不含需要展开的部分。

图3:hello.i文件的开头

图4 hello.i文件的结尾
2.4 本章小结
复习了预处理的定义、内容、操作指令,对hello.c文件进行了预处理,分析了hello.i的内容。
(第2章0.5分)

第3章 编译

3.1 编译的概念与作用

注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序
编译阶段,由编译器检查语法并生成汇编程序。生成的.s文件是汇编语言编写的,我们还是可以直接查看文件、检查错误。

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

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

图6 编译结果

3.3.1 汇编声明部分
.file:声明源文件为hello.c;
.align:数据、指令地址对齐方式(8字节);
.string:两个程序中出现的字符串,对应.c文件中printf里的两个字符串;
.global:声明全局变量;
.type:声明符号类型为函数或数据类型;

3.3.2 数据
1)字符串

图7 字符串
26、41行:分别使用了两个字符串作为printf函数的参数,字符串内容在第5~8行;

2)局部变量

图8 i
30~32行:声明一个变量i,放在栈上-4(%rbp);
3)参数argc
是用户传给main的第一个参数,放在堆栈中;
4)数组 char*argv[]

图9 L4
是传给main的第二个参数,每个元素都是一个指向字符类型的指针,起始地址放在栈中-32(%rdp);
5)立即数:直接写在汇编码中;

3.3.3全局函数
由3.3.1的global之后声明可知,main函数是全局函数;

3.3.4 赋值操作
赋值操作在汇编语言中,使用MOV系列指令实现,分为:
movb:字节
movw:字(两字节)
movl:双字;
movq:四字;

3.3.5算术操作

图10 i++
Hello中有算术操作的部分是循环递增变量i++,对应51行汇编代码;

3.3.6 关系操作
1)argc!=4:对应24、25行,相等则跳转至L2;

图11
2)循环条件:i<8,对应53、54,当大于7时跳转L4:

图12

3.3.7控制转移指令
3.3.6的两部分,关系操作后进行了控制转移;

3.3.8 函数操作
1)传递控制:A调用过程B时,程序计数器(%rip)需要设置为B的起始位置,然后在返回时,将(%rip)设为A中,调用B的指令的后一条指令的地址;
2)传递数据:A需要向B提供参数(列表),B需要返回值给A;
3)分配/释放内存 函数调用前可能预留栈空间为局部变量分配空间,调用后弹栈释放预留空间;

3.3.9 类型转换
Main中使用atoi(argv[3])将字符串类型转换为整数类型;
3.4 本章小结
本章以hello为例,介绍了编译的定义、作用,展示了编译阶段编译器的操作,对比hello.c和hello.s进行分析,总结了C语言中各种类型和操作对应的汇编代码。
(第3章2分)

第4章 汇编

4.1 汇编的概念与作用
注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。
汇编阶段,计算机将汇编代码转换为机器可执行的机器码;对于生成的机器码我们可以进行反汇编来进行调试。
4.2 在Ubuntu下汇编的命令
gcc hello.s -c -o hello.o

图13汇编指令
4.3 可重定位目标elf格式
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
1)ELF头
ELF header首先包括了一个16字节的magic,描述了生成该文件的系统的字的大小和字节顺序;
剩下的部分包括ELF headers的大小、各种header的大小、目标文件的类型、机器类型、版本信息、入口点地址、程序头起点、字节头部表的文件偏移、节头部表中条目的大小、数量等信息。

图14 ELF头
2)节头
节头部表。包含文件中出现的各个节的语义,包括节的类型、位置、偏移量(大小)等信息;
因为是可重定位目标文件,所以每个节都从0开始以用于重定位;
在文件头中得到节头表的信息,然后使用节头表中的偏移量信息,得到各个节在文件中的起始位置以及所占空间的大小,同时可以观察到:代码可执行但不可写(flag不含W),数据段和只读数据段不可执行,只读数据段不可写。

图15 节头
3)重定位节
常见的重定位条目有两种(type):
第一种,R_X86_64_32:重定位绝对引用。重定位时使用一个32位的绝对地址的引用,通过绝对寻址,CPU直接使用在指令中编码的32位值作为有效地址;
第二种,R_X86_64_PC32:重定位PC相对引用。重定位时使用一个32位PC相对地址的引用。

图16 重定位节
4)符号表
.symtab存放在程序中定义和引用的函数和全局变量的信息,但不包含局部变量的条目。

图17 符号表
4.4 Hello.o的结果解析
objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。

图18 hello.o反汇编结果
和hello.s相比,各个指令前都增加了十六进制表示(机器语言);
操作数也从hello.s的十进制变为16进制;
条件跳转语句中,反汇编结果里跳转到一个main+偏移量,hello.s中跳转到函数名;
调用函数时,反汇编文件使用重定向方式进行跳转,根据重定位节获取地址;
反汇编代码访问全局变量使用$0x0,0(%rip),hello.s使用LC0,sleepsecs(%rip)
4.5 本章小结
介绍汇编的概念、作用,对hello.c进行汇编,生成可重定位目标文件hello.o,分析了可重定位文件的ELF头、节头部表、符号表、可重定位节的内容,比较了hello.o和hello.s的不同。
(第4章1分)

第5章 链接

5.1 链接的概念与作用
注意:这儿的链接是指从 hello.o 到hello生成过程。
链接由链接器执行,分为静态链接和动态链接;
静态链接是编译时(执行之前)执行的,由链接器将库的内容加入到可执行程序中,把要调用的函数或者过程链接到可执行文件中,成为可执行文件的一部分。
动态链接是运行时执行的,动态链接所调用的函数代码并没有被拷贝到应用程序的可执行文件中去,而仅仅在其中加入了所调用函数的描述信息(通常是重定位信息)。当应用程序进入内存开始运行时,在系统管理下,在应用程序与相对应的函数、库建立链接关系。当要执行调用的库中的函数时,根据链接产生的重定位信息,系统再转去执行库中对应的函数代码。
5.2 在Ubuntu下链接的命令

图19 链接指令
使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件
5.3 可执行目标文件hello的格式
生成ELF文件,查看各部分:

图20 ELF节头

图21 节头

图22 重定位节

图23 符号表

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

图25 edb查看hello虚拟地址
虚拟地址从0x401000开始,结合节头表中的地址、偏移量,可以寻找到.interp、.note.gnu.propert等section对应的虚拟地址;
5.5 链接的重定位过程分析

图26 反汇编hello

图27 hello.out部分截图
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
不同:
Hello相比hello.o,有很多进行重定位之后的函数,图中的有_init,另外还有puts@plt,printf@plt等,而hello.o在text段之后只有一个main函数(加偏移量);
Hello地址从401000开始,是绝对地址,而hello.o从0开始,是绝对地址;
在hello的main函数中,跳转、call指令后都是绝对地址,而hello.o中是相对于main的相对地址(偏移量);
链接器通过符号表和节头,确定.data、.text在每个文件中的偏移量和大小,进行合并,为新合并的数据和代码节分配内存、映射虚拟内存地址;
最后链接器修改对所有符号的引用,完成重定位。
5.6 hello的执行流程
开始执行:_start、_libc_start_main;
执行main:_main、_printf、_exit、_sleep、_getchar;
退出:exit;
程序名 程序地址
_start 0x4010f0
_libc_start_main 0x2f12271d
main 0x401125
_printf 0x401040
_exit 0x401070
_sleep 0x401080
_getchar 0x401050
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
5.7 Hello的动态链接分析
首先在elf文件中找到.got的地址为0x403ff0

图28 hello的elf格式中.got

图29 dl_init前

图30 dl_init后
5.8 本章小结
本章介绍了链接的概念和作用,分为两步:符号定义、重定位;
查看了hello的elf格式内容、虚拟地址空间内容,分析了重定位过程、执行流程、动态链接过程,对链接有了更直观的认识,加深了对知识的理解。
(第5章1分)

第6章 hello进程管理

6.1 进程的概念与作用
进程的经典定义就是一个执行中程序的实例,进程会占用对应的内存区域,由CPU进行执行和运算。
6.2 简述壳Shell-bash的作用与处理流程
Shell能够解释用户输入的命令,将其传递给内核,也可以:
调用其他程序,给其他程序传递数据或参数,并获取程序的处理结果;
在多个程序之间传递数据,把一个程序的输出作为另一个程序的输入;
本身也可被其他程序调用。
处理流程:

  1. 读取从键盘输入的命令
  2. 判断命令是否正确,且将命令行的参数改造为系统调用execve() 内部处理所要求的形式
  3. 终端进程调用fork() 来创建子进程,自身则用系统调用wait() 来等待子进程完成
  4. 当子进程运行时,它调用execve() 根据命令的名字指定的文件到目录中查找可行性文件,调入内存并执行这个命令
  5. 如果命令行末尾有后台命令符号& 终端进程不执行等待系统调用,而是立即发提示符,让用户输入下一条命令;如果命令末尾没有& 则终端进程要一直等待。当子进程完成处理后,向父进程报告,此时终端进程被唤醒,做完必要的判别工作后,再发提示符,让用户输入新命令。
    6.3 Hello的fork进程创建过程
    父进程调用fork函数时会创建一个新的子进程,新的子进程几乎与父进程相同但不完全意义;子进程得到与父进程用户级虚拟地址空间相同的一份副本,包括代码和数据段、堆、共享库、用户栈。
    子进程和父进程最大的区别是PID不同,另外在父进程中fork()返回子进程的PID,而子进程中fork()返回0;通过返回值我们可以分辨当前程序在父进程还是在子进程中执行。
    6.4 Hello的execve过程
    Execve函数在当前进程的上下文中加载并运行一个程序,步骤如下:
    删除已存在的用户区域、之前进程在用户部分中已存在的结构;
    创建新的代码、数据、堆栈段:所有这些区域结构都为私有、写时复制,虚拟地址空间的代码个数据区域被映射为hello文件的.txt和.data区。bss区域用于请求二进制0,初始长度为0。
    映射共享区域:如果hello程序与共享对象链接,比如库,那么这些对象都是动态链接到程序,然后再映射到用户虚拟地址空间中的共享区域。
    设置程序计数器:execve设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。
    6.5 Hello的进程执行
    结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。
    进程可以向每个程序提供一种假象,好像它再独占地使用处理器。如果使用调试器单步执行程序,可以看到一系列PC值,这个PC值序列节逻辑控制流。
    一个逻辑流的执行时间与另一个流有重叠,称为并发流,这两个流称为并发地运行。进程也为每个程序提供一种假象,好像它独占地使用系统地址空间。
    上下文切换:如果系统调用因为等待某个事件发生而阻塞,那么内核可以让当前进程休眠,切换大另一个进程,上下文就是内核重新启动一个被抢占的进程所需要的状态。
    时间片:一个进程执行它的控制流的一部分的每一时间段叫做时间片。
    用户模式和内核模式:shell使得用户可以有机会修改内核,所以需要设置一些防护措施保护内核,如限制指令类型、限制指令作用范围。
    上下文信息:上下文就是内核重新启动一个被抢占的进程所需要的状态,由通用寄存器、浮点寄存器、程序计数器、用户栈和各种内核数据结构等对象的值构成。
    在hello中,程序执行sleep时,显示请求让调用进程休眠,调度器抢占当前的进程,并利用上下文切换转移到新进程,sleep函数结束后,再通过上下文切换返回hello函数。
    6.6 hello的异常与信号处理
    1.乱按:如果不按回车则只显示,按回车则结束程序,乱按结果作为命令输入;

图31 乱按
2.Ctrl-C

图32 Ctrl-C
按下后会使内核发送一个SIGINT信号到前台进程组的每个进程,默认情况下终止前台作业;
3.Ctrl-Z

图33 Ctrl-Z
按下Ctrl-Z会导致内核发送一个SIGTSTP信号到前台进程组的每个进程,默认情况下停止(挂起)前台作业。
4.ps
5.jobs

图34 ps、jobs
6.pstree

图35 pstree
7.fg

图36 fg

6.7本章小结
本章介绍了进程、进程管理的相关概念,介绍了shell的作用和处理流程,使用fork创建子进程、execve加载进程的方法、操作。展示了hello程序执行的具体过程,和异常信号的处理机制。
(第6章1分)

第7章 hello的存储管理

7.1 hello的存储器地址空间
结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。
逻辑地址:程序经过编译后出现在汇编代码中的地址。逻辑地址用来指定一个操作数或者一条指令的地址,是由一个段标识符+一个指定段内相对地址的偏移量,表示为 段标识符:段内偏移量。
线性地址:逻辑地址向物理地址转换过程中的一步,逻辑地址经过段机制后转化为线性地址,为描述符:偏移量的组合形式,分页机制中线性地址作为输入。
虚拟地址:为了管理内存而对主存进行抽象形成的概念。
物理地址:主存中的实际地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
观察段选择符,0则转换的是GDT中的段,否则就是LDT中的段;
根据相应寄存器,找到起始地址;
通过段标识符找到对应基地址;
用基地址+偏移量,得到线性地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
根据线性地址前十位找到对应页表的地址;
根据之后十位找到对应页的起始地址;
页的起始地址加上线性地址最后十二位得到物理地址。
7.4 TLB与四级页表支持下的VA到PA的变换
将VA分为四段,依次通过每段地址找到对应的PML4,PGD,PMD,PTE表,找到对应地址,组合得到PA。
7.5 三级Cache支持下的物理内存访问
物理地址分为标记、组索引和块偏移。首先在L1中匹配组索引位,若匹配成功则根据标记和偏移的匹配结果决定缺失或是命中。若组索引匹配不成功,则进入下一级cache,重复以上直至进入内存。
7.6 hello进程fork时的内存映射
fork 会为新进程复制一个与父进程相同的只读数据空间,并为其分配另一片内存和虚拟地址;分配时会将其标记为私有,防止过程中被父进程影响。
7.7 hello进程execve时的内存映射
调用execve,系统首先删除当前进程中用户部分已有结构,然后映射私有区域(建立新的文件结构,包括.data等各种节),映射共享区域,设置程序计数器(PC)。
7.8 缺页故障与缺页中断处理
段错误:地址不合法,即无法匹配到已有的区域结构中;
非法访问:没有应有的读写权限;
正常:选择一页进行页替换。
7.9动态存储分配管理
Printf会调用malloc,请简述动态内存管理的基本方法与策略。
动态内存分配器维护着名为【堆】的虚拟内存区域。对于每个进程,内核维护一个变量brk,指向堆的顶部。分配器有两种基本风格:显示分配器和隐式分配器。显示分配器要求应用显示地释放任何已分配的快,隐式分配器要求分配器检测一个已分配块合适不再被程序所用,那么就释放这个块。隐式分配器又叫垃圾收集器,而自动释放未使用的已分配的块的过程叫做垃圾收集。
为实现动态内存分配器,可以使用隐式空闲链表。当一个应用请求k字节的块时,分配器搜索空闲链表,查找一个足够大可以放置所请求块的空闲块。分配器执行这种搜索的方式是由放置策略确定的。一些常见的策略是首次适配、下一次适配和最佳适配。一旦分配器找到一个匹配的空闲块,就需要决定分配这个空闲块中多少空间。一个选择是用整个空闲块,但这样会造成内部碎片。如果匹配不太好,那么分配器会将这个空闲块分割,第一部分变成分配块,剩下的变成一个新的空闲块。利用边界标记,可以允许在常数时间内进行对前面块的合并。这种思想是在每个块的结尾添加一个脚部,其中脚部就是头部的一个副本。这样分配器就可以通过检查它的脚部,判断前面一个块的起止位置和状态,这个脚部总是在距离当前块开始位置一个字的距离。但是这种方法也存在潜在缺陷,就是在应用程序操作许多个小块时,会产生显著的内存开销。
7.10本章小结
本章介绍了存储管理的相关内容,包括存储器的地址空间:物理地址、虚拟地址、逻辑地址、线性地址,对段式管理和页式管理进行了较为详细的描述;讨论了VA到PA的变换、物理内存访问、fork和execve的内存映射、缺页故障和后续处理、动态存储分配管理等内存。
(第7章 2分)

第8章 hello的IO管理

8.1 Linux的IO设备管理方法
设备的模型化:文件
类型:普通文件、目录文件、设备文件;
设备管理:unix io接口
包括:开关文件、读写文件、改变当前文件位置
8.2 简述Unix IO接口及其函数

  1. 打开文件:使用open,要求内核打开相应的文件,返回一个非负整数,叫做文件描述符,后续所有的操作都基于这个文件描述符。内核记录了对应文件描述符的所有信息,应用程序只需要记住这个描述符;
  2. 关闭文件:使用close,成功的时候返回0,现在异常的时候返回-1.关闭文件会通知内核已经完成访问该文件,不能重复关闭同一个文件,必须检查返回码;
  3. 读取文件:使用read,从当前文件位置复制字节到内存,然后更新文件位置(前提是文件支持seeking),返回从文件fd读取到buf的字节数;
  4. 写入文件:使用write,会将字节从内存复制到当前文件位置,然后更新当前文件位置(前提是文件支持seeking);
  5. 寻找位置:使用seek,对于每个打开的文件,内核保存着一个文件位置k,表示从文件开头起始字节的偏移量,默认为0.应用程序可以通过seek显示的设置k的值
    8.3 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;
    }
    va_list是字符指针,(char*)(&fmt+4)表示fmt后第一个参数地址。Vsprintf函数返回值是要打印出来的字符串的长度,作用是格式化,产生格式化的输出并保存在buf中。最后的write函数进行写操作,把buf中i个元素的值写到终端。
    从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
    字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
    显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
    8.4 getchar的实现分析
    当用户按键时,键盘接口会得到一个代表该按键的键盘扫描码,同时产生一个中断请求,中断请求抢占当前进程运行键盘中断子程序,键盘中断子程序先从键盘接口取得该按键的扫描码,然后将该按键扫描码转换成ASCII码,保存到系统的键盘缓冲区之中。getchar函数落实到底层调用了系统函数read,通过系统调用read读取存储在键盘缓冲区中的ASCII码直到读到回车符然后返回整个字串,getchar进行封装,大体逻辑是读取字符串的第一个字符然后返回。
    8.5本章小结
    介绍了Linux系统的IO设备管理方法,Unix的IO接口及函数、函数用法,分析了printf和getchar函数的实现。
    (第8章1分)

结论

用计算机系统的语言,逐条总结hello所经历的过程。
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
Hello首先来自于程序员打出的代码,成为最初的hello.c,作为一种有特殊含义的文本——程序——而存在;
之后对hello.c进行预处理操作,将hello.c中存在的宏定义、使用的库进行展开,将对应的代码复制粘贴进我们的文件,形成hello.i,这一步会将字符量大大增多,并且也是我们向已有工具“借力”的过程;
但是只是这样还不足以使计算机读懂文件,需要经过(狭义的)编译阶段将hello.i转为汇编文件hello.s,相当于降低了语言的级别;
之后为了让机器能够在需要跳转时转到具体的地址,需要将hello.s经过汇编,变成可重定位目标文件hello.o;
接着进行动态链接,hello.o和动态链接库共同链接成可执行目标程序hello,至此就完成了hello从程序到进程的过程。
然后我们就可以使用/执行hello了:再shell中输入./hello 120L021614 name 1然后按回车开始执行;
Shell会使用fork和execve函数加载映射虚拟内存,为hello创建新的代码数据堆栈段;
CPU会为hello分配一个时间片段,在时间片中,hello有对CPU的控制权力,在程序计数器中加入自己的代码并按顺序执行。
之后程序从cache中(快速)取得需要的数据,或者从内存中(较慢)取出需要的数据,或者从磁盘中(很慢)加载数据。
在执行过程中,可以键入ctrl-z挂起程序或者ctrl-c停止进程。
最后shell回收子进程,内核删除这个进程使用时创建的一系列数据结构,至此,hello的一生结束。
(结论0分,缺失 -1分,根据内容酌情加分)

附件
列出所有的中间产物的文件名,并予以说明起作用。
hello.c 源程序
hello.i 预处理后文件
hello.s 编译后的汇编文件
hello.o 汇编后的可重定位目标执行文件
hello 链接后的可执行文件

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

参考文献
为完成本次大作业你翻阅的书籍与网站等
[1] Randal E.Bryant, David R.O’Hallaron. Computer Systems:A Programmer’s Perspective. 北京:机械工业出版社,2016.7.
[2] 木槿花better.c语言编译过程详解,预处理,编译,汇编,链接. https://www.csdn.net,2018.7.
[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.
(参考文献0分,缺失 -1分)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值