计算机系统大作业
题 目 程序人生-Hello’s P2P
专 业 计算机
学 号 1190200303
班 级 1936602
学 生 程明明
指 导 教 师 刘宏伟
计算机科学与技术学院
2021年6月
本文通过简单的hello程序为切入,详细阐述了一个程序如何由源代码通过预处理、编译、汇编、链接等步骤成为可执行程序,同时通过进程管理、存储管理、IO管理等系统机制使得程序能够在操作系统中正确运行的过程。并以此为契机将计算机的各种机制和操作原理结合已经学习的知识正确阐述出来。
关键词:程序人生;计算机系统;预处理;编译;汇编;链接;系统管理;CSAPP
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
2.2在Ubuntu下预处理的命令............................................................................. - 5 -
5.3 可执行目标文件hello的格式........................................................................ - 8 -
6.2 简述壳Shell-bash的作用与处理流程........................................................ - 10 -
6.3 Hello的fork进程创建过程......................................................................... - 10 -
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 -
8.2 简述Unix IO接口及其函数.......................................................................... - 13 -
第1章 概述
1.1 Hello简介
由键盘输入,形成hello.c文件。然后将hello.c文件通过预处理、编译、汇编、链接,历经艰辛得到一个可执行目标文件。然后在Shell中通过命令运行该可执行文件,读取到运行的命令行,调用fork函数创建一个新的子进程,再调用execve在内存里开辟空间,将hello文件复制到开辟的空间,运行hello进程,通过IO管理输出,当程序运行结束后,由shell或者init清除空间和痕迹,最终回到程序运行前的状态,就像没有hello来过一样。
1.2 环境与工具
硬件环境:Intel i5 @2.4GHz,16GBRAM;256GHD Disk
软件环境:Windows 10 64位,VirtualBox;Ubuntu 16.04 LTS 64位
开发工具:gcc,objdump,gdb ,readelf,vim
1.3 中间结果
hello.c:老师提供的源文件。
hello.i:预处理后产生的文件,用于查看预处理过程。
hello.s:编译后产生的汇编文件,查看编译过程。
hello.o:汇编产生二进制的可重定位目标文件.o,无法直接查看。
hello:生成的可执行程序。
hello.oelf:通过ELF工具查看hello.o而产生的文件,记录了hello.o的ELF格式。
hello.elf:通过ELF工具查看hello而产生的文件,记录了hello的ELF格式
helloo.txt:hello.o的反汇编文件,用于查看和与汇编前的hello.s进行比较。
hello.txt:hello的反汇编文件,查看链接后的反汇编以及与helloo.txt进行比较查看链接过程。
1.4 本章小结
本章概述了hello的P2P与O2O过程,展示了实验的环境和中间结果。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
概念:预处理一般是指在程序源代码被翻译为目标代码的过程中,生成二进制代码之前的过程。通常会将#起始的代码对整个代码进行替换与分割处理。
作用:预处理包括是对宏定义、文件包含、条件编译三个方面的处理。会对于宏定义进行替换,对于文件包含,会导入包含的文件与头文件中的内容,而条件编译指令决定编译程序代码处理的范围。最终使得代码更简单直接和完整,便于进一步处理。预处理生成hello.i文件。
2.2在Ubuntu下预处理的命令
命令:gcc -m64 -no-pie -fno-PIC -E hello.c -o hello.i
截图:
2-1.预处理命令
2-2.预处理结果
2.3 Hello的预处理结果解析
删去注释。hello.c代码中的预处理指令有三条,预处理器找到这三个头文件的定义文件,然后所有的内容全部到复制到hello.i当中。函数主体部分基本没有变化。
2.4 本章小结
通过预处理,将hello.c中三个头文件的内容全部包含在了文件中,将代码的注释部分删除,使得代码文件更简单直接和完整,便于进一步处理。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
概念:通过编译器,将hello.i文件转变成汇编文件。转变过程中,编译器还会分析并优化c代码,最终得到hello.s。
作用:不仅能够将c代码翻译成机器更加容易理解的汇编语言,而且编译的过程中还会对代码进行分析优化,让其有更好的效率。
3.2 在Ubuntu下编译的命令
命令:gcc -m64 -no-pie -fno-PIC -S hello.i -o hello.s
截图:
3-1.输入的命令
3.3 Hello的编译结果解析
常量:
3-2.常量存储
在只读数据rodata段里存储了字符串常量.LC0和.LC1,对应了两个printf函数的内容。而如3,10等数字常量都以立即数的形式存在于汇编代码中。
全局变量:
3-3.全局变量存储
存在一个全局变量sleepsecs,赋值为2.5,可以看出编译器将其从int型的变量优化为long型,进行了隐式的类型转换。sleepsecs被存放在.rotate节中。
局部变量:
3-4.局部变量存储
首先由%rsp开辟了一个栈空间。虽然局部变量i在代码里被首先声明,但是观察汇编文件发现,没有为其开辟空间。仅仅当需要使用i时,转跳至.L2,申请了栈里的空间,暂存在-4(%rbp)中,并且被初始化为0。
赋值:
由于全局变量的赋值直接在处理时在.data节声明,初始化为值为2的long型变量。对于局部变量的赋值使用mov语句完成,找到其存储的栈空间或者寄存器,直接对值使用mov语句。
算术操作:
3-5.循环变量自加一
hello.c仅仅存在加法操作,对于局部变量i在循环后自加1。直接对于i所存储的栈空间的值用addl命令加即可。
逻辑操作:
存在判断是否相等与大小的判断。
3-6.判断是否等于3
判断参数是否等于3,利用cmpl指令即可完成。
3-7.判断是否小于等于9
对于比较大小,也使用cmpl指令。可以发现,<10被编译器优化成了<=9,这样就可以直接使用指令完成比较。
数组与指针操作:
3-8.访问数组
程序中访问了argv数组的元素。数组本质为一串相连的存储空间,所以通过基地址加上偏移量的方式访问数组元素。而argv数组的起始地址-32(%rbp),通过对%rax寄存器加16来访问argv[2]传递给%rdx,同理%rax寄存器加8来访问argv[1] 并传递给%rax,最后将 %rdx和%rax寄存器中的值作为参数传递给printf函数来输出。
函数操作:
分别调用了exit函数,printf函数,sleep函数和getchar函数。
3-9.调用exit函数
对于exit函数只有一个参数,于是通过将1赋值给%edi,来传递参数。
3-10.传递参数
该函数实际上系统函数,功能是暂时挂起进程。它以sleepsecs作为参数,将值传递到%eax,然后将%eax的值赋值给第一个参数寄存器%edi,最后通过%edi将值传入sleep使用。
3-11.调用puts函数
第一次的printf函数被优化成了puts函数,直接将.LC0作为参数写入%edi寄存器,然后作为参数传递。
3-12.调用printf函数
第二次printf函数就直接编译为printf函数,参数argv[1]和参数argv[2]被从栈中复制到寄存器%rdx和%rax中,常量字符串地址则保存在寄存器%edi中作为参数传入。
3-13.调用getchar函数
getchar函数的原型是没有参数的,所以它没有参数,因此不会使用到参数寄存器。
3.4 本章小结
通过编译器,将hello.i文件转变成汇编文件hello.s。通过对比,能够清晰认识到代码转换的方式及优化。让其有更好的效率和更加适合机器运行。离翻译成二进制机器语言以便机器运行又更近了一步。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
概念:汇编就是把汇编文件hello.s经过处理转化成二进制形式的可重定位目标文件.o,这样机器可以直接识别。
作用:由汇编指令到机器指令,机器可以直接识别。
4.2 在Ubuntu下汇编的命令
命令:gcc -m64 -no-pie -fno-PIC -c hello.s -o hello.o
4-1.输入的命令
4.3 可重定位目标elf格式
4-2.ELF文件头
ELF头:
展示了机器和文件的最基本信息。
4-3.ELF节头部表
节头部表:
展示了各个节的大小、类型、地址、偏移量各种信息,方便查找。
4-4.ELF重定位节
重定位节:
这部分描述了需要重定位的信息,这些信息在生成可执行文件时就会被重定位。可以发现在hello.o中需要被重定位的有.radata, puts, exit, printf, sleepsecs, sleep, getchar等。
4-5.ELF符号表
符号表:
记录和列举了程序中用到的函数和全局变量等符号。
4.4 Hello.o的结果解析
4-6.反汇编文件 4-7. hello.s汇编文件
通过objdump -d -r hello.o得到hello.o的反汇编,与 hello.s进行对比,发现基本相同,但是也存在差异。
首先是操作数的格式,由十进制变为了十六进制补码格式,因为hello.o文件已经是二进制文件形式了。除此之外,在汇编语言中,分支转移的跳转位置都是用.L3, .L4来表示的,但在机器语言中它们被偏移量表示的内存地址所替代。调用函数也不相同,在汇编语言中,函数的调用都是用函数名来跳转的,但在机器语言中它们调用的是一个待重定位的相对地址。
4.5 本章小结
分析了hello.o的elf格式,对其结构有进一步认识。除此之外,利用hello.o的反汇编代码展示了二进制机器语言文件的格式和特性。由hello.o与hello.s的对比可以发现从汇编代码到机器语言的过程是完全按照对应的转换关系的。留下了可重定位的标记和地址填充的标记,为链接提供条件。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
概念:链接器通过符号解析(Symbol resolution)和重定位两个过程,赋予符号表中的符号唯一定义,并将被链接文件中的分散的代码片段组织起来,生成一个完全链接的可执行对象文件的过程。
作用:链将各种代码和数据片段收集并组合成为一个单一文件的过程,让程序能够完整的载入内存并运行。链接使得分离编译成为可能,能够将一个大型的应用程序拆分为较小的模块,而只要修改和编译这些模块,再将其重新就可以完成对整个应用程序的修改,而不必重新编译未修改的文件,大大节约了时间和人力成本。
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-1.输入的命令
5.3 可执行目标文件hello的格式
观察可以发现其程序头由13个增加到26个。详细信息如下:
5-2.ELF文件中26个程序头
5.4 hello的虚拟地址空间
5-3.EDB界面
地址由0x401000开始,结束于0x401ff0。
5.5 链接的重定位过程分析
观察可以发现其节头表由13个增加到26个,多出来的分别为:
interp ,.note.ABI-tag,.hash,.gnu.hash,.dynsym,.gnu.version,.gnu.version_r,.rela.dyn,.rela.plt,.init,.plt,.plt.sec,fini,.got,.got.plt等。这些都来自于动态共享库,在链接的过程插入到了原来的节当中。
5-4.链接前的ELF文件
5-5.链接后的ELF文件
很明显可以发现hello.o中main函数的地址并没有确定,为0x0000,而在链接之后确定了地址,一些具体函数的调用在hello.o中只是以相对位置于main函数的偏移地址,而在hello中给出了绝对地址。在hello.o需要重定位的信息在hello中已经全部填充,hello是一个完整的文件。
重定位时:先将各个文件合并,然后程序中将需要链接的符号于符号表中进行查找,找到后将其内存地址填充到重定位标记处。
5.6 hello的执行流程
5-6.ELF文件
执行过程中的进程分别为:
0x401000 <_init>;
0x401020 <.plt>;
0x401080 <puts@plt>:
0x 401090 <printf@plt>:
0x 4010a0 <getchar@plt>:
0x 4010b0 <exit@plt>
0x 4010c0 <sleep@plt>:
0x 4010d0 <_start>:
0x 401100 <_dl_relocate_static_pie>:
0x 401105 <main>:
0x 401190 <__libc_csu_init>:
0x 401200 <__libc_csu_fini>:
0x 0401208 <_fini>:
5.7 Hello的动态链接分析
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
5.8 本章小结
本章通过对链接前后的程序的ELF格式和反汇编代码的比较,体现了链接前后的区别以及链接的过程。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
概念:进程是一个执行中程序的实例,它的上下文提供了程序运行所需的状态。
作用:进程为应用程序提供了两个关键抽象,分别是逻辑控制流和私有地址空间。
6.2 简述壳Shell-bash的作用与处理流程
Shell是Linux操作系统内核提供给用户的交互程序。它为用户提供了向内核传递命令的窗口。
处理流程:Shell首先获得输入的字符串,通过读入命令行或者文件的内容,结果是得到字符串。对字符串进行解析,通过元字符将其分割为词汇,检查词汇是否为内置命令的关键字,若是内置命令,直接执行。否则认为是程序文件,在当前目录下查找是否有对应的程序文件。若找到,直接建立进程和上下文,加载程序文件,让系统内核运行。程序运行后等待其退出并回收进程。
6.3 Hello的fork进程创建过程
父进程调用fork函数,创建一个子进程,子进程具有与父进程虚拟地址空间相同却独立的副本,且具有不同的pid。两个进程的代码、数据段、堆、共享库以及用户栈均相同,所以子进程可以访问父进程的文件。Fork函数调用一次返回两次,在父进程中返回子进程的pid,在子进程中返回0.
当我们在shell中输入 ./hello 1190200303 程明明 的时候,shell先进行命令解析,判断hello是一个当前目录下的可执行目标文件,通过fork过程创建子进程。
6.4 Hello的execve过程
execve函数加载并运行可执行目标文件,函数原型为int execve(const *filename, const char *argv[], const char *envp[])。通过读入可执行文件filename,argv是参数列表,envp是环境变量列表。对当前进程的上下文加载一个进程,直接覆盖了原先利用fork函数创建的进程。并且execve函数只有在调用失败之后才会返回-1,这与fork函数调用一次返回两次是不同的,它调用一次从不返回。
当fork子进程后,调用execve函数,直接覆盖了原先利用fork函数创建的子进程,然后重新创建一组新的代码、数据、堆和栈段。新的堆和栈的段被初始化为0,通过虚拟地址空间中的页映射到可执行文件的页大小的片。新代码被初始化为可执行文件的内容。最后加载器跳转到_start处运行程序。
6.5 Hello的进程执行
Shell根据特定算法为所有进程分配时间片,包括hello程序进程。当到了hello程序运行的时间片时,首先加载hello的进程上下文信息,然后从控制从内核转换到hello程序。在hello程序中,我们认为它的进程被抢断发生在sleep发生的时候,调用了sleep(sleepsecs);,休眠了2秒,使得hello时间片结束而将控制返还给内核,此时要保存hello的进程上下文信息,然后根据下一时间片到来时,进程重新执行上述过程,所以程序应该输出十次,但每一次输出中间会出现2秒的间隔。
6-1.运行程序
6.6 hello的异常与信号处理
6-2.回车处理
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
对于回车的异常,完全没有影响,只会改变输出的位置。
6-3.Ctrl-Z处理
Ctrl-Z的异常会导致发送一个SIGSTP信号给shell父进程。信号处理函数将hello进程挂起但并不结束,可使用fg命令可以把被挂起的hello重新调度到前台运行,继续将剩下的步骤运行完。
6-4.Ctrl-C处理
Ctrl-C的异常会发送SIGINT信号给shell父进程,使得将hello进程挂起并结束,此时hello进程已经结束。
6-5.乱按处理
随便乱按,Shell会随时用一个getchar函数监听输入的字符,并且分析读入的字符串,当字符串符合命令,就认为输入了命令然后执行,否则程序正常运行。
6.7本章小结
Shell是Linux用于对用户命令交互的应用程序,对于接收的命令行执行对应的可执行文件或者内置命令。而shell执行程序的方式为先构造要传递给execve函数的字符串组,fork子进程,并设置进程组id,然后调用execve函数,根据传入的字符串组复制可执行程序到内存执行。
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:指的是在机器语言中,是为一个偏移地址,是由一个段标识符加上段内相对地址的偏移量, 表示为 [段标识符:段内偏移量]。在hello的程序里说即是hello.o里面的相对偏移地址。
线性地址:使用逻辑地址与对应的段的基地即可转化为线性地址。在分页机制中线性地址作为输入。
虚拟地址:是CPU寻址假定的内存空间地址,是通过MMU(内存管理单元)使虚拟地址转换为物理地址,仅仅当需要向地址写入内容时才会给对应的地址开辟真正的物理空间。之前看见的均为hello的虚拟内存地址。
物理地址:计算机主存被认为是M个块内存组成的数组,所以每个字节都有一个唯一的地址,而这个地址就是物理地址。对于hello程序来说,就会通过MMU由虚拟地址映射到物理地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
将程序分为多个段进行管理,通过段表,将段的信息记录下来。而对于逻辑地址可以分为两部分:段标识符和段内偏移量。通过段标识符,在段表之中权匹配查询,在段描述符表中找到一个具体的段描述符,就可以得到基地址,然后再将其与段内偏移量结合,即可得到线性地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
页映射就是从线性地址到物理地址的映射,是由cpu的页式管理单元负责管理的,可以将线性地址翻译为物理地址。在物理内存中的最小分配单位是帧,页是虚拟内存中最小的分配单位。线性地址的前半部分为页号,对应了虚拟内存的某页,而后半部分是页偏移量,可以在页内查找。而物理内存中还存在一个页表,它存储着虚拟内存页和物理内存帧的对应关系。这样就可以通过页表查询页号来找到对应的物理内存帧,再通过页偏移量,就得到了物理内存的地址。
7.4 TLB与四级页表支持下的VA到PA的变换
四级页表指的是虚拟页号被等分为四段,用每段页号依次向下查找,第一级页表中找到相应的第二级页表,以此类推。如此节约了页表的总大小。但是仍然页表是庞大的,所以建立了一个缓存称为TLB,查询过的页表项会存入TLB中,当下一次查询就可以直接查询而不需要通过页表,大大节约了时间。
7.5 三级Cache支持下的物理内存访问
在三级cache下,将物理地址分成CT(标记)+CI(索引)+CO(偏移量),首先在先通过索引去寻找到对应的组,然后在组内去匹配标记,当匹配上是看有效位是否有效,然后通过偏移量读取数据。如果在一级cache未命中,则到二级cache中去匹配,若仍然不命中则到三级cache访问。
7.6 hello进程fork时的内存映射
Shell调用fork来生成子进程时,内核为hello进程创建了对应代码、数据、堆和栈段并且给了唯一的PID。而为了hello进程创建好虚拟内存,就需要将原本的页表
它创建了hello进程的mm_struct 、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。
当fork在hello进程中返回时,hello进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,因此,也就为每个进程保持了私有地址空间的抽象概念。
7.7 hello进程execve时的内存映射
首先,execve会删除当前进程的虚拟内存的内容;其次,要为新程序的代码、数据.bss和栈区创建内存区域,分别将已经初始化的数据映射到.data节, 代码映射到.text节,未初始化的数据映射到.bss。然后请求栈空间。除此之外,将共享库链接后,映射到共享库的内存映射区域。
7.8 缺页故障与缺页中断处理
当CPU发送一个虚拟地址传递给MMU,但是MMU通过查看TLB与PTE发现对应的物理地址不在内存中,而必须从磁盘中取出到内存,这就是缺页故障。缺页的对应处理程序会在内存里确定牺牲页,需要判断该页面是否被修改。若被修改就需要将修改后的内容写入磁盘。然后通过调入之前请求的页到物理内存去覆盖掉牺牲页,并且更新PTE,添加一条对应记录。最后将控制返回给引起故障的指令,让其重新执行调用该页的指令,再发送虚拟地址给MMU就不会引起缺页故障了。
7.9动态存储分配管理
通过一个动态内存分配器来维护进程的某一块虚拟内存区域,这一块区域被称为堆。位于未初始化的数据区域(.bss节)后开始,并向高的地址生长。对于进程,内核利用一个变量brk指针,它指向堆顶。堆被动态分配器视为块的集合,每一个块就是一个连续的虚拟内存片(chunk),保留为供应用程序使用,而块分为已分配和空闲两种状态。
空闲块是可用来来分配的。而分配器分为显式和隐式,显式的分配方法,块只有在应用要求释放时才能得到释放的,而隐式则会自动释放不使用的块,称为垃圾收集。
空闲的块由称为空闲链表的数据结构记录,它也有显式和隐式之分。0而malloc就是显式的分配方法。
7.10本章小结
本章主要解释了hello程序运行时,对于存储空间的分配操作和过程。对于各种地址的变换和翻译有了一定程度的解释。讲诉了存储器各级间的关系和相互间调用的操作。让我们对于hello程序的存储和在存储器上的调用有了更深的理解。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件
设备管理:unix i/o接口。将所有输入输出设备视为文件后,所有的输入与输出都被看成了读写文件,使得所有设备的输入输出具有统一的格式和执行方式,方便管理与使用。而从Linux的内核引出的一个简单的应用接口,称之为 unix I/O接口。
8.2 简述Unix IO接口及其函数
1.open()函数:
功能描述:用于打开或创建文件,在打开或创建文件时可以指定文件的属性及用户的权限等各种参数。
函数原型:int open(const char *pathname,int flags,int perms)
参数:pathname:被打开的文件名,flags:文件打开方式。
返回值:成功则返回文件描述符;失败则返回-1。
2.close()函数:
功能描述:用于关闭一个被打开的的文件。
所需头文件:#include <unistd.h>
函数原型:int close(int fd)
参数:fd:文件描述符。
返回值:成功返回0,出错返回-1。
3.read()函数:
功能描述:从文件读取数据。
所需头文件:#include <unistd.h>
函数原型:ssize_t read(int fd, void *buf, size_t count);
参数:fd:文件描述符;buf:读取的数据存放的缓冲区地址;count:一次read操作读取的字符数量。
返回值:读取成功返回所读取的字节数;读到EOF返回0;出错返回-1。
4.write()函数:
功能描述:向文件写入数据。
所需头文件:#include <unistd.h>
函数原型:ssize_t write(int fd, void *buf, size_t count);
参数:fd:文件描述符;buf:写入的数据所在的缓冲区地址;count:一次write操作写入的字符数量。
返回值:写入成功则返回写入的字节数;出错返回-1。
5.lseek()函数:
功能描述:用于在指定的文件描述符中将将文件指针定位到相应位置。
所需头文件:#include <unistd.h>,#include <sys/types.h>
函数原型:off_t lseek(int fd, off_t offset,int whence);
参数:fd:文件描述符;offset:偏移量,每一个读写操作需要移动的字节数,向前移为正,向后移为负。
返回值:成功就返回当前位移量;失败则返回-1。
8.3 printf的实现分析
https://www.cnblogs.com/pianist/p/3315801.html
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
Getchar函数原型里调用了read函数,它从缓冲区将buf的大小读满,当方生异常时再监听键盘输入读取缓冲区,读取时获得读入的字节数,如果大于0则返回缓冲区第一个字节,否则返回eof。
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章解释了unix系统的io管理,解析了io接口的相关函数,并且通过分析printf和getchar两个函数的具体实现来展示了i/o的过程。
(第8章1分)
结论
程序由源代码通过预处理、编译、汇编、链接等步骤成为可执行程序,在执行过程中通过进程管理、存储管理、IO管理等一系列的系统控制机制,使得程序能够由硬盘上的文件到内存中的进程,最后成功通过IO设备展示出结果。详细解释了hello程序的“P2P”(From Program to Process)和“O2O”(From Zero-0 to Zero-0)过程,深刻理解了程序的“一生”。
通过对hello程序的分析,成功地回忆和加深了对计算机系统所学的知识的理解。更重要的是,能够通过hello的“一生”将各个章节的知识联系起来,让我们对计算机系统有了一个全面的认识。
学习计算机系统后,对计算机的整个运行逻辑和机制有了清晰的认识。
(结论0分,缺失 -1分,根据内容酌情加分)
附件
hello.c:老师提供的源文件。
hello.i:预处理后产生的文件,用于查看预处理过程。
hello.s:编译后产生的汇编文件,查看编译过程。
hello.o:汇编产生二进制的可重定位目标文件.o,无法直接查看。
hello:生成的可执行程序。
hello.oelf:通过ELF工具查看hello.o而产生的文件,记录了hello.o的ELF格式。
hello.elf:通过ELF工具查看hello而产生的文件,记录了hello的ELF格式
helloo.txt:hello.o的反汇编文件,用于查看和与汇编前的hello.s进行比较。
hello.txt:hello的反汇编文件,查看链接后的反汇编以及与helloo.txt进行比较查看链接过程。
(附件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.
(参考文献0分,缺失 -1分)