计算机系统大作业
(所有图片见下面)
摘 要
摘要:计算机是一个经各方完美配合后能完成复杂功能的系统,这篇论文就以hello的一生为例生动的讲述一个程序从程序员编写到程序运行结束整个过程,包括源代码的预处理、编译、汇编以及链接的过程,还包括为程序创建进程,加载,运行时内存的变化,还有I/O接口…
关键词:编译,进程,内存,IO;
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
第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简介
P2P过程:from program to process,最初使我们编写的源代码,经过编译器对其进行预处理,编译,汇编和链接之后形成可执行目标程序,然后在执行该目标程序时,内核会调用fork函数形成一个子进程,分配相应的内存资源,包括CPU的使用权限和虚拟内存等。
O2O过程:from 0 to 0,在创建进程之后调用execve函数加载并运行该程序,映射虚拟内存,进入程序入口后程序开始载入物理内存,程序开始执行,CPU为运行的hello分配时间片执行逻辑控制流,结束后,进程被回收,并删除相关数据。
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
X64 CPU;2GHz;Windows7 64位以上;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64位;GCC; cpp; as ;ld;edb;readelf;
1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
hello.i 源程序经预处理后的文本文件
hello.s 预处理文件经编译后生成的汇编语言文件
hello.o .s文件经汇编后生成的二进制文件(可重定位目标文件)
hello 链接后的可执行目标文件
hellooelf 用readelf读hello.o的ELF文件格式
helloelf 用readelf读hello.o的ELF文件格式
oobjdump hello.o的反汇编文件
Objdump hello的反汇编文件
1.4 本章小结
了解了P2P与O2O的基本意义,为整个hello历程确定方向。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
预处理概念:预处理一般是指在程序源代码被翻译为目标代码的过程中,生成二进制代码之前的过程。预处理器(cpp)根据以字符#开头的命令,修改原始的C程序并得到以.i为文件扩展名的新的C程序,例如宏定义,文件包含,条件编译等。
预处理作用:
1.复制引用的文件,例如#include <stdio.h>命令告诉预处理器读取头文件stdio.h的内容,并把它直接插入程序文本中。
2.替换宏定义在源代码中的所有显示。
3.规定限制编译某段代码的条件。
2.2在Ubuntu下预处理的命令
cpp hello.c hello.i
2.3 Hello的预处理结果解析
首先,include的三个头文件stdio.h、unistd.h和stdlib.h的代码在主函数之前,
替换所有源代码中的宏定义并且检查新添加代码中有无新的#define,递归此过程,观察打开后的代码发现其中使用了大量的#ifdef #ifndef的语句,预处理器会对条件值进行判断来决定是否执行其后的代码。
2.4 本章小结
确定了预处理的过程,了解其具体功能。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
编译的概念:编译是指把代码转化为汇编代码的过程。
作用:用来生成只于CPU相关的汇编指令,为进一步生成机器代码着准备。
3.2 在Ubuntu下编译的命令
gcc -S hello.i -o hello.s
3.3 Hello的编译结果解析
3.3.1字符串
源代码中已规定的字符串在.rodata段中保存:
使用时直接使用标志即可:
3.3.2整型数据
int i;在main函数中作为局部变量直接保存在栈中。
argc: 作为参数传入。
3.3.3数组
传入三个参数与该文件的地址名字组成一个字符串数组argv[],并且这些数据也都保存在栈中,每个元素占8个字节。
这栈中的32个字节就是用来保存四个参数的。
3.3.4关系操作
判断argc是否等于4,cmpl一句计算argc-4,并且更新条件码,以便后面判断。
判断i是否小于8,同上,这个是小于等于而已。
3.3.5控制转移
A.if语句:
两个分支结构,判断条件是否成立之后,若成立,则继续执行,若不成立,则跳转到for语句。
B.for语句
.L2是循环语句初始化过程,然后跳转到.L3-判断条件是否满足,若满足,则回溯到.L4即循环体的内容,执行完后,顺序进入到.L3判断过程,直到条件不满足停止循环。
3.3.6算术操作
i++;
3.3.7函数调用
A.printf先把要打印字符串的地址放到rdi中,即作为参数传给函数,因为只有一个字符串,故函数printf退化为函数puts,然后调用函数puts。
B.exit,传入退出状态参数1,然后调用函数exit.
C.Printf,因为默认函数调用前保存参数的寄存器顺序为rdi,rsi,rdx,rcx.故因为本次调用涉及三个参数,
rdi中保存字符串,rsi中是argv[1],rdx中是argv[2].最后调用printf。
D.atoi同上,把字符串的首地址放入rdi中,然后调用函数,并且函数的返回值会放入寄存器eax中。
E.sleep,把atoi的返回值放入rdi中,调用sleep函数。
F.Getchar,没有参数,直接调用。
3.3.8函数返回
调用函数main进行控制转移,来开始main的运行,执行完所有功能代码后返回0,把0移入默认返回寄存器eax,然后ret.
3.3.9数组操作
数组保存在栈中,利用与rbp的相对位置进行寻址。
,找到初始位置,后面引用加相对位置即可。
3.4 本章小结
清晰了C语言的各种数据类型与结构操作在机器中的具体指令执行。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
汇编的概念:把文本形式的汇编代码转化为二进制形式的机器代码,这个过程是CPU相关的,由CPU的指令集体系结构决定,最终生成可重定位目标文件。
汇编的作用:生成可重定位目标文件。
4.2 在Ubuntu下汇编的命令
as hello.s -o hello.o
4.3 可重定位目标elf格式
ELF格式:首先是ELF头,接着是
项目分析:
偏移量是指需要进行重定向的代码在.text或.data节中的偏移位置。
信息:包括symbol和type两部分,其中symbol占前4个字节,type占后4个字节,symbol代表重定位到的目标在.symtab中的偏移量,type代表重定位的类型。
类型:重定位到的目标的类型。
名称:重定向到的目标的名称。
加数:计算重定位位置的辅助信息。
对于相对寻址的重定位,需要计算其目标机器代码中的地址:
在(偏移量+该节的基地址)处修改,再使下一条指令的地址减去变量地址即可。
4.4 Hello.o的结果解析
objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。
机器语言指令由指令指示符、寄存器指示符和操作数指示符构成。除含具体操作数的指令外,机器语言与汇编语言有一一对应关系,任何汇编指令都有唯一的机器语言编码。
不一致:
A.在指令跳转时,包括实现分支转移、循环和函数调用时需要的地址,在汇编代码中,会直接使用规定的标志符号或函数名称,是字符集表示,而在反汇编代码中则运用了相对于该函数的偏移量,而在二进制代码中,则把该地址置为NULL,以后重定位时再填充。
B.在访问全局变量时,汇编代码中使用的是,而反汇编中用的是0,,因为rodata中数据地址也是在运行时确定,故访问也需要重定位。所以在汇编成为机器语言时,将操作数设置为全0并添加重定位条目。
4.5 本章小结
更好地了解计算机的工作原理。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
将多个可重定位的目标文件合并成可执行文件。链接器将每个符号引用与一个确定的符号定义关联起来。将多个单独的代码节和数据节合并为单个节。将符号从它们的在.o文件的相对位置重新定位到可执行文件的最终绝对内存位置。更新所有对这些符号的引用来反映它们的新位置。
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文件所有节构成,第一列为各段的大小,倒数第二列为各段的起始地址。
5.4 hello的虚拟地址空间
使用edb加载hello,查看hello进程的虚拟地址空间各段信息,根据段头部表的结构和内容,可以看出其与上述ELF文件中的数据是对应相同的,虚拟地址和物理地址等。
各段信息:
1 _init 初始化
2 gmon_start 程序通过gprof可以输出函数调用等信息
3 _dl_relcoate_static_pie 静态库链接
4 .plt 动态链接
5 .dynamic 存放被ld.so使用的动态链接信息
6 .got 动态链接-全局偏移量表-存放变量
7 .got.plt 动态链接-全局偏移量表-存放函数
8 .data 初始化了的数据
9 _start 编译器为可执行文件加上了一个启动例程
10 __libc_csu_init 程序调用libc库来对程序初始化函数
11 _fini 当程序正常终止时需要执行的代码
5.5 链接的重定位过程分析
objdump -d -r hello > objdump 分析hello与hello.o的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
Hello与hello.o的不同:
1.在hello的反汇编代码中不仅加入了所调用的函数的汇编代码,还多了辅助信息如.init等节。
2.在hello的反汇编代码中调用的函数,引用的全局变量的地址以及偏移量等都相应的变成了虚拟地址。
链接的过程:
为了构造可执行文件,链接器必须完成两个主要任务:
●符号解析(symbol resolution)。 目标文件定义和引用符号,每个符号对应于一个函数、一个全局变量或一个静态变量(即C 语言中任何以static属性声明的变量)。符号解析的目的是将每个符号引用正好和一一个符号定义关联起来。
●重定位(relocation)。 编译器和汇编器生成从地址0开始的代码和数据节。链接器通过把每个符号定义与一个内存位置关联起来,从而重定位这些节,然后修改所有对这些符号的引用,使得它们指向这个内存位置。链接器使用汇编器产生的重定位条目(relocation entry)的详细指令,不加甄别地执行这样的重定位。
重定位的过程:
以函数printf第一次出现为例:
用4004f0(puts函数代码的首地址)-4005a3(下一条代码的地址,即当前PC的值)=-b3,其补码表示为ff ff ff 4d,即上述二进制代码中表示的值。
重定位由两步组成:
●重定位节和符号定义。在这一步中,链接器将所有相同类型的节合并为同一类型的新的聚合节。例如,来自所有输人模块的.data节被全部合并成一个节,这个节成为输出的可执行目标文件的.data节。然后,链接器将运行时内存地址赋给新的聚合节,赋给输人模块定义的每个节,以及赋给输人模块定义的每个符号。当这一步完成时,程序中的每条指令和全局变量都有唯一的运行时内存地址了。
●重定位节中的符号引用。在这一:步中,链接器修政代码节和数据节中对每个符号的引用,使得它们指向正确的运行时地址。要执行这一步、链接器依赖于可重定位目标模块中称为重定位条目的数据结构,我们接下来将会描述这种数据结构。
5.6 hello的执行流程
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
0x7ffff7b0de40 __argp_fmtstream_putc
0x7ffff7b14080__option_is_end
0x7ffff7a05910 __libc_init_first
0x7ffff7a05aa0_dl_start(void)
0x7ffff7a05ab0 __libc_start_main
601048 W data_start
400580 T _dl_relocate_static_pie
400684 T _fini
4004c0 T _init
400680 T __libc_csu_fini
400610 T __libc_csu_init
400582 T main
400550 T _start
5.7 Hello的动态链接分析
在edb调试之后我们发现原先0x00600a10开始的global_offset表是全0的状态,在执行过_dl_init之后被赋上了相应的偏移量的值。这说明_dl_init操作是给程序赋上当前执行的内存地址偏移量,这是初始化hello程序的一步。
5.8 本章小结
本章介绍了链接的各个过程,分析了hello的ELF格式,了解了重定位和执行过程还有动态链接的过程的相关知识。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
进程的概念:进程是操作系统对一个正在运行的程序的一种抽象,进程是对处理器、主存和I/O设备的抽象表示,进程是一个执行中程序的实例。
进程的作用:当运行一个程序时,进程允许操作系统提供一种假象,就好像系统上只有这个程序在运行,程序看上去是独占地使用处理器、主存和I/O设备。处理器看上去就像在不间断地一条接一条地执行程序中的指令,即该程序的代码和数据是系统内存中唯一的对象。
6.2 简述壳Shell-bash的作用与处理流程
作用:shell是一个交互性的应用级程序,它代表用户运行其他程序,shell执行一系列的读求值步骤,然后终止。
处理流程:
读步骤读取来自用户的一个命令行,然后eval函数对命令行求值,首先调用parseline函数,这个函数解析了以空格分隔的命令行参数,并构造出最终会传递给execve的argv向量。第一个参数被假设为要么是一个内置的shell命令名,马上就会解释这个命令,要么是一个可执行的目标文件,会在一个新的子进程的上下文中加载并运行这个文件。最后一个参数如果是&字符,那么parseline返回1,表示应该在后台执行,否则返回0,在前台执行。
在解析了命令行之后,eval函数调用builtin_command函数,该函数检查第一个参数是否是一个内置的shell命令名,马上就会解释这个命令,并返回1.如果返回0,shell就会创建一个子进程,并在子进程中执行所请求的程序。
如果用户要求在后台运行该程序,那么shell返回到循环的顶部,等待下一个命令行,否则shell使用waitpid函数等待作业终止,当作业终止时,shell就开始下一轮迭代。
6.3 Hello的fork进程创建过程
系统调用fork函数,创建一个子进程,该子进程拥有和父进程完全一致的代码、数据和资源,由于fork函数在子进程和父进程中的返回值不同,利用条件语句if(fork()==0)后加子进程内容即可加入只在子进程中执行的代码,故创建了一个hello进程。
6.4 Hello的execve过程
在为hello创建的子进程中,系统调用execve函数,该过程加载可执行文件,之后调用启动代码,启动代码设置栈,并将控制传递给hello程序的主函数。
6.5 Hello的进程执行
进程调度的过程:
在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先的被抢占了的进程。这种决策就叫做调度。 在内核调度了一个新的进程运行后,它就抢占当前进程,并使用种称为上下文切换的机制来将控制转移到新的进程。
上下文切换1)保存当前进程的上下文,2)恢复某个先前被抢占的进程被保存的上下文,3)将控制传递给这个新恢复的进程。
当内核代表用户执行系统调用时,可能会发生上下文切换。如果系统调用因为等待某个事件发生而阻塞,那么内核可以让当前进程休眠,切换到另一个进程。
例如,系统调用sleep函数,就先将本来运行在用户模式中的hello程序,转移到核心态,由内核完成sleep函数的执行,最后再把控制转移给用户即回到用户态。
6.6 hello的异常与信号处理
(以下格式自行编排,编辑时删除)
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
可能会出现中断、陷阱和系统调用、故障和终止这几类异常,会产生下列几种信号:
名称 默认行为 相应事件
SIGINT 终止 来自键盘的中断
SIGKILL 终止 杀死程序(该信号不能被捕获不能被忽略)
SIGSEGV 终止 无效的内存引用(段故障)
SIGALRM 终止 来自alarm函数的定时器信号
SIGCHLD 忽略 一个子进程停止或终止
输入回车:刚好8个,并没有影响该进程,说明输入的回车键被忽略。
Ctrl+C:说明终止了该进程,发送了SIGINT信号。
Ctrl+Z:发送一个 SIGTSTP 信号给前台进程组中的进程,从而将其挂起.
输入命令ps查看所有进程及其运行时间,发现hello还没有终止,输入命令jobs查看当前暂停的进程,发现了hello的进程,输入命令fg使之在前台运行,导致了剩余几行的打印,输入命令kill,结束进程hello。
此时,再用ps查看,已经不存在hello进程了。
6.7本章小结
本章通过对进程的讨论,使shell加载和运行hello程序的过程完整地展现出来,并且通过进一步操作,了解信号对进程的影响。
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址是指由程式产生的和段相关的偏移地址部分,它是相对于当前进程数据段的地址,程序代码会产生逻辑地址。
线性地址是由逻辑地址即段中的偏移地址加上相应段的基址得到的,是处理器可寻址的内存空间中的地址, 是逻辑地址到物理地址变换之间的中间层。
物理地址是用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应。现代操作系统都提供了一种内存管理的抽像,即虚拟内存。进程使用虚拟内存中的地址,即虚拟地址,由操作系统协助相关硬件,把它“转换”成真正的物理地址。hello.s中使用的就是虚拟空间的虚拟地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
首先,将逻辑地址分成段选择符+段描述符的判别符(TI)+地址偏移量的形式,然后先判断TI字段,判断它是局部段描述符(ldt)还是全局段描述符(gdt),然后再将其组合成段描述符+地址偏移量的形式,这样就转换成线性地址了。
7.3 Hello的线性地址到物理地址的变换-页式管理
在这个转换中要用到翻译后备缓冲器(TLB),首先我们先将线性地址分为VPN(虚拟页号)+VPO(虚拟页偏移)的形式,然后再将VPN拆分成TLBT(TLB标记)+TLBI(TLB索引)然后去TLB缓存里找所对应的PPN(物理页号)如果发生缺页情况则直接查找对应的PPN,若是没有找到,即有效位为0,则去告诉缓冲器或主存中,找到后更新TLB,若没有找到,则触发一个缺页异常,最终找到PPN之后,将其与VPO组合变为PPN+VPO就是生成的物理地址了。
7.4 TLB与四级页表支持下的VA到PA的变换
虚拟地址VA被分成VPN和VPO两部分,VPN被分为TLBT和TLBI用于在TLB中查询。根据TLBI确定TLB中的组索引,并根据TLBT判断PPN是否已被缓存到TLB中,如果TLB命中,则直接返回PPN,否则会到页表中查询PPN。在页表中查询PPN时,VPN会被分为四个部分,分别用作一二三四级页表的索引,而前三级页表的查询结果为下一级页表的基地址,第四级页表的查询结果为PPN。将查询到的PPN与VPO组合,得到物理地址PA。
7.5 三级Cache支持下的物理内存访问
如下图所示,先在TLB中找,若不命中,则在页表中找到PTE,构造出物理地址PA,然后去L1中利用物理地址分为CT,CI,CO,若没命中,依次向低级访存,最后返回结果。
7.6 hello进程fork时的内存映射
fork函数为每个进程提供私有的虚拟地址空间。首先,创建当前进程的mm_struct, vm_area_struct和页表的原样副本,两个进程中的每个页面都标记为只读,两个进程中的每个区域结构(vm_area_struct)都标记为私有的写时复制。当hello返回时,hello进程拥有与调用fork进程相同的虚拟内存,随后的写操作通过写时复制机制创建新页面。
7.7 hello进程execve时的内存映射
1.删除已存在的用户区域。
2.创建新的私有区域(.malloc,.data,.bss,.text)
3.创建新的共享区域(libc.so.data,libc.so.text)
4.设置PC,指向代码的入口点。
7.8 缺页故障与缺页中断处理
缺页故障:当指令引用一个虚拟地址,在MMU中查找页表时发现与该地址相对应的物理地址不在内存(高速缓存和主存)中,因此必须从磁盘中取出的时候就会发生故障。
缺页中断处理:缺页处理程序是系统内核中的代码,选择一个牺牲页面,如果这个牺牲页面被修改过,那么就将它写回内存,磁盘中,换入新的页面并更新页表。当缺页处理程序返回时,CPU重新启动引起缺页的指令,这条指令再次发送VA到MMU,这次MMU就能正常翻译VA了。
7.9动态存储分配管理
动态内存分配器维护着一个进程的虚拟内存区域-堆,分配器将堆视为一组不同大小的块来维护,每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。用户调用相应的申请和释放函数,动态内存分配器就会改变相应的块来完成要求,或检查相应的块,或遍历寻找空闲块。
带边界标签的隐式空闲链表分配器原理
边界标记即在有效荷载的前端和后端都加一个相同的标记,一个称为头部,另一个称为脚部,结构为 (块大小 | alloc),这样分配器就可以在常数时间内知道任意一个块它的前驱和后继的情况。
显示空间链表的基本原理
显示空闲链表将空闲块组织为某种形式的显示数据结构,并且实现这个数据结构的指针存放在这些空闲块的主体里。
使用双向链表,可以使用后进先出的顺序维护链表,即将新释放的块放置在链表的开始处,也可以按照地址顺序来维护链表,即每个块的地址都小于它后继的地址。
7.10本章小结
首先是计算机系统中各种形式地址的使用与它们之间的相互转换方法,然后对于hello进程中是如何使用这些地址的过程的了解和动态内存分配中的具体过程。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件,所有的I/O设备(例如网络、磁盘和终端)都被模型化为文件,而所有的输入和输出都被当作对响应文件的读和写来进行。
设备管理:unix io接口,使得所有的输入和输出都能以一种统一且一致的方式来进行,包括打开文件,改变当前的文件位置,读写文件和关闭文件等操作。
8.2 简述Unix IO接口及其函数
A.打开或创建文件:
int open(char *filename, int flags, int perms);
int creat(char *name, int perms);
flags的主要值有三个 O_RDONLY O_WRONLY O_RDWR
perms即unix文件权限,对于open,perms填0就可以读取。
B.关闭文件与删除文件:
int close(int fd);用于断开与文件的链接 释放文件描述符
int unlink(char *filename);用于删除文件
C. 读取文件与写入文件:
int read(int fd, char *buf, int n );
int write(int fd, char *buf, int n);
buf是缓冲区,可以是一个数组名, 也可以时char指针,n为每次要传输的字节数,read和write的返回值均为真实传输的字节数
D.游标移动
int lseek(int fd, long offset, int origion);
lseek可以在文件中随意移动而不会修改文件内容
其中offset是相对于origin移动的距离,origin可以为 0 1 2,分别代表文件头、当前位置、文件尾。
8.3 printf的实现分析
arg表示的是…中的第一个参数的地址,vsprintf函数生成显示信息,并且返回arg所指字符串中的字符个数,vsprintf的作用就是格式化。它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出。然后通过系统调用安全的write函数说明打印buf中的i个字符。
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
int getchar(void)
{
char c;
return (read(0,&c,1)==1)?(unsigned char)c:EOF
}
getchar函数通过调用read函数来读取字符。 int read(int fd, char *buf, int n );
read函数由三个参数,第一个参数为文件描述符fd,fd为0表示标准输入;第二个参数为输入内容的指针;第三个参数为读入字符的个数。read函数的返回值是读入字符的个数,若出错则返回-1。
当用户按键时,会触发一个异步异常,进行键盘中断的处理,键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
unix io接口为我们提供了一种机制,使得所有的输入和输出都能以一种统一且一致的方式来进行,可以方便的用来实现一些功能,例如printf,getchar等。
(第8章1分)
结论
用计算机系统的语言,逐条总结hello所经历的过程。
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
最初使我们编写的源代码,经过编译器对其进行预处理,编译,汇编和链接之后形成可执行目标程序,然后在执行该目标程序时,内核会调用fork函数形成一个子进程,分配相应的内存资源,包括CPU的使用权限和虚拟内存等。在创建进程之后调用execve函数加载并运行该程序,,在运行hello的时候,首先bash会新建一个进程,清空当前进程的数据并加载hello,从函数的入口进入,开始执行,由于各种各样的原因,我们的hello,可能会暂时的休息(系统调用或者计时器中断),这时我们保留当前进度,并切换上下文,内核去处理别的进程,我们还可以输入信号来终止或挂起hello进程,hello输出信息时需要调用printf和getchar,而printf和getchar的实现需要调用Unix I/O中的write和read函数,而它们的实现需要借助系统调用,在最后结束之后bash等到exit,作为hello的父进程回收hello,映射虚拟内存,进入程序入口后程序开始载入物理内存,程序开始执行,CPU为运行的hello分配时间片执行逻辑控制流,结束后,进程被回收,并删除相关数据。整个过程需要硬件,操作系统软件等的密切配合,并且hello程序赤条条来,赤条条去,并没有留下什么影响,对计算机的维护也是有利的。
(结论0分,缺失 -1分,根据内容酌情加分)
附件
列出所有的中间产物的文件名,并予以说明其作用。
(附件0分,缺失 -1分)
hello.i 源程序经预处理后的文本文件
hello.s 预处理文件经编译后生成的汇编语言文件
hello.o .s文件经汇编后生成的二进制文件(可重定位目标文件)
hello 链接后的可执行目标文件
hellooelf 用readelf读hello.o的ELF文件格式
helloelf 用readelf读hello.o的ELF文件格式
oobjdump hello.o的反汇编文件
Objdump hello的反汇编文件
参考文献
为完成本次大作业你翻阅的书籍与网站等
[1] 兰德尔 E.布莱恩特 大卫 R.奥哈拉伦 深入理解计算机系统
[2]百度百科 Unix IO接口
[3]行者小朱 C语言三种预处理功能
[4]程序人生 P2P历程