本次为辅修人工智能中,《计算机系统基础》的大作业。
提前声明:本人仅代表工大最低水准,不能体现工大的水平。无奈为母校丢人十分抱歉。此处隐去教师的姓名也是怕丢老师的脸。
计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 能源动力类
学 号 1180200704
班 级 1802007
学 生 肖家豪
指 导 教 师
计算机科学与技术学院
2020年3月
摘 要
本文主要论述了hello.c程序,在linux系统下的运行情况。详细陈述了其在机器级别的编译,内存的调用,及存储器的使用,系统级别的连接,虚拟内存的使用已经I/O设备的管理等方面的内容。详细介绍了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简介
P2P:
如上图所示,hello.c文件通过cpp成为hello.i文件,再由编译器生成hello.o文件,由连接器与其他的文件连接生成可执行文件。由shell启动程序,调用fork创建子进程。
O2O:由shell通过execve在fork产生的子进程中加载hello。映射虚拟内存,进入程序的入口,载入物理内存,执行main函数。之后通过I/O设备输入输出。程序结束后,shell回收hello进程,删除hello的痕迹。
1.2 环境与工具
硬件环境:AMD Ryzen 5 2500U ,8GB RAM
软件环境:windos10 64位,Ubuntu64位
开发与调试工具:edb,Ultraedit,gdb
1.3 中间结果
1.hello.i:cpp根据以字符#开头的命令,修改原始的C程序。主要是将头文件,例如stdio文件插入程序文本中,得到新的C程序。
2.hello.s:编译器将C语言翻译成汇编语言,作为中间的过度阶段。
3.hello.o:汇编器将汇编语言转化为机器可读的机器语言。
4.hello:源程序中调用了printf,getchar,sleep,atoi等函数。利用连接器,将C语言函数库中相应的可连接文件与3中的文件连接,生成可执行的文件。
5.hello.elf:hello.o的可执行可连接格式。
6.hello1.elf:hello文件的elf格式。
7.hello0.txt:hello.o的反汇编代码。
8.hello1.txt:hello的返回编码。
9.a.out:可执行文件。
1.4 本章小结
本章主要讲述了P2P和O2O的过程,程序的开发环境以及文件生成的中间过程。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
概念:程序设计领域中,预处理一般是指在程序源代码被翻译为目标代码的过程中,生成二进制代码之前的过程。
作用:最常见的预处理是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下预处理的命令
对hello.c文件预处理命令:gcc -E -o hello.i hello.c。
目录下会增加一个hello.c文件。
hello.c截图:
hello.i文件部分截图:
2.3 Hello的预处理结果解析
从截图中可以看到C的源程序其实依然存在,多出的3000多行带代码实际上是源文件中头文件的源代码。
2.4 本章小结
本章主要利用Linux下的预处理命令,对hello.i文件进行了分析。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
概念:
1、利用编译程序从源语言编写的源程序产生目标程序的过程。 2、用编译程序产生目标程序的动作。 编译就是把高级语言变成计算机可以识别的2进制语言
注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序
作用:
将高级语言转化为机器可以读懂的2进制机器码。
3.2 在Ubuntu下编译的命令
对hello.i进行编译的命令是:gcc -S -o hello.s hello.i。
目录下会有一个hello.文件。
3.3 Hello的编译结果解析
3.3.1处理变量(包含赋值
源程序中只有局部变量,int i;分析汇编码知,该变量由系统默认初始化为0,存在寄存器地址-4(%rbp)处。
3.3.2处理关系操作符与控制语句
程序中有if(argc!=4)的操作语句,转化为汇编语言如图所示。其中,i与4利用cmpl指令进行比较。由其后je指令可知,当比较的结果为相等或为0的时候,进行跳转,否则继续执行指令。即只有i不等于4的时候才进行条件结果中的语句。
该过程中利用cmpl与je实现操作。
3.3.3处理四则运算与复合语句
在循环条件语句中,出现了i++即加法运算,汇编码如下。
其他相关的计算的实现方法如下表:
3.3.4数组,指针,结构体:
本例的for循环中,运用了数组argv[],编译结果如下。
3.3.5处理函数
函数的调用与传参:给函数传参需要先设定寄存器,将参数传给所设的寄存器,再通过call来跳转到调用的函数开头的地址。在源代码中调用了printf、atoi、getchar、sleep和exit。
1.第一个printf转化为puts,事先将L0段的立即值传入%rdi,然后call函数跳转到puts的地址。
2.exit将立即数转入%edi中然会运行
3.第二个printf有三个参数,第一个是.LC1中的格式化字符串%eax中,后面的两个依次是%rdi,%rsi,然后跳转到printf
4.atoi,sleep函数都是实现都是实现传参数到%rdi和%edi中。
5.getchar直接运行,不需要参数
3.4 本章小结
本章重点阐述了C语言汇编过程中各种变量,过程的实现方法。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
概念:将指定的.s转化为.o,即将汇编程序转化成可重定位目标程序。
作用:将汇编代码转化为机器代码,使其在连接后能被机器识别。
4.2 在Ubuntu下汇编的命令
命令为:gcc -c -o hello.o hello.s
目录下会增加一个.o文件:
4.3 可重定位目标elf格式
在linux下生成elf格式的命令为:readelf -a hello.o >hello.elf
目录下会增加一个.elf文件:
根据以上的模型,解析.elf文件中的内容:
1.ELF头:ELF头(ELF header)以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。ELF头剩下的部分包含了帮助链接器语法分析和解释目标文件的信息,其中包括ELF头的大小、目标文件的类型(如可重定位、可执行或者共享的)、机器类型(如x86-64)、节头部表(section header table)的文件偏移,以及节头部表中条目的大小和数量。不同节的位置和大小是有节头部表描述的,其中目标文件中每个节都有一个固定大小的条目(entry)。
2.节头:记录各节名称,类型,地址,偏移量,大小,全体大小,旗标,连接,信息,对齐。
3.重定位节
real.txt保存连接时需要修改的信息。一般而言,任何外部函数或者引起全局变量的指令都需要修改。另一方面,调用本地函数的指令则不需要修改。本例中需要被重定位的有printf,puts,exit,sleepsecs,getchar,和.rodata中的.L0,.L1。
4.符号表
.symtab,一个符号表,它存放在程序中定义和引用的函数和全局变量的信息,一些程序员错误地认为必须通过-g选项来编译一个程序,才能得到符号表信息。实际上每个可重定位目标文件在.symtab中都有一张符号表(除非程序员特意用STRIP命令去掉它)。然而,和编译器中的符号表不同,.symtab符号表不包含局部变量的条目。
4.4 Hello.o的结果解析
命令:objdump -d -r hello.o > hello.txt
与hello.s的差别:
1.分支转移:在汇编代码中,分支跳转是直接以.L0等助记符表示,但在返汇编码中,分支转移表示为主函数+段内偏移。返汇编码跳转指令的操作数使用的不是段名称。因为段名称知识在汇编语言中便于编写助记符,所以在汇编成机器语言之后显然就不存在了,而是确定的地址。
2.函数调用:汇编代码中直接调用了函数名称,而反汇编码中,是call+main+偏移量。
3.访问全局变量:汇编码中用.LC0(%rip),反汇编码中为0x0(%rip)。
4.5 本章小结
本章主要分析了elf格式文件的内容,以及比较汇编码,返回编码的区别。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
概念:将各种代码和数据片段收集并合成为一个单一文件的过程,这个文件可被加载(复制)到内存并执行。链接可以执行于编译时和加载时。
作用:将大型的应用分离成更小,更好管理的模块,并可以独立的修改编译这些代码。当我们修改时,只需要重新编译,链接即可。
5.2 在Ubuntu下链接的命令
ld-o-hello-dynamic-linker/lib64/ld-linux-x86-64.so.2/user/lib/x86_linux-gnu/crt1.o/usr/lib/x86_64-linux-gnu/crti.ohello.o/usr/lib/x86_64-linux-gnu/libc.so /usr/lib/x86_64-linux-gnu/crtn.o
目录下会增加一个可执行文件:
5.3 可执行目标文件hello的格式
命令:readelf -a hello >hello1.elf
目录下多了一个.elf文件:
1.ELF头:上次的节头数为13个,这次变为25个。
2.节头
5.4 hello的虚拟地址空间
PHDR:保存程序头表
INTERP:动态链接器的路径
LOAD:可加载的程序段
DYNAMIN:保存了由动态链接器使用的信息
NOTE保存辅助信息
GNU_STACK:标志栈是否可执行
GNU_RELRO:指定重定位后需被设置成只读的内存区域
使用edb打开hello从Data Dump窗口观察hello加载到虚拟地址的状况,并查看各段信息。
在0x400000~0x401000段中,程序被载入,自虚拟地址0x400000开始,到0x400fff结束,这之间每个节的地址同5.3中的地址声明。
5.5 链接的重定位过程分析
objdump -d -r hello >hello1.txt
与hello.o生成的反汇编文件对比,hello1.txt中多了许多节。hello0.txt中只有一个.text节,而且只有一个main函数,函数地址也默认的0x000000.hello1.text中有.init,.plt,.text三节,而且每个节中有许多的函数。库函数的代码已经链接到程序中,程序各个节变的更完整,跳转的地址也具有参考性。
hello比hello.o多出的节头表:
.interp:保存ld.so的路径
.note.ABI-tag
.note.gun.build-i:编译信息表
.gun.hash:gun的扩展符号表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:初始化过的全局变量或者声明过的函数。
5.6 hello的执行流程
(1) 载入:_dl_start、_dl_init
(2)开始执行:_start、_libc_start_main
(3)执行main:_main、_printf、_exit、_sleep、
_getchar、_dl_runtime_resolve_xsave、_dl_fixup、_dl_lookup_symbol_x
(4)退出:exit
程序名称 地址
ld-2.27.so!_dl_start 0x7fb85a93aea0
ld-2.27.so!_dl_init 0x7f9612138630
hello!_start 0x400582
lib-2.27.so!__libc_start_main 0x7f9611d58ab0
hello!puts@plt 0x4004f0
hello!exit@plt 0x400530
5.7 Hello的动态链接分析
在进行动态链接前,首先进行静态链接,生成部分链接的可执行目标文件hello。此时共享库中的代码和数据没有被合并到hello中。加载hello时,动态链接器对共享目标文件中的相应模块内的代码和数据进行重定位,加载共享库,生成完全链接的可执行目标文件。
动态链接采用了延迟加载的策略,即在调用函数时才进行符号的映射。使用偏移量表GOT+过程链接表PLT实现函数的动态链接。GOT中存放函数目标地址,为每个全局函数创建一个副本函数,并将对函数的调用转换成对副本函数调用。
调用init之前的.got.plt
调用init之后的.got.plt
从图中可以看到.got.plt的条目发生变化。
5.8 本章小结
概括了链接的概念和作用。分析了hello程序的虚拟地址空间,重定位和执行过程。阐述了动态链接的原理。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
概念:一个执行中的程序实例
作用:系统中每个程序都运行在某个进程的上下文中。它是操作系统动态执行的基本单元,在传统的操作系统中,进程即使基本的分配单元,也是基本的执行单元。
6.2 简述壳Shell-bash的作用与处理流程
作用:shell是一个C语言编写的程序,它是用户使用linux的桥梁。它既是一种应用,也是一种程序设计语言。这种应用程序提供了一个界面,用户通过这个界面访问操作系统的内核服务。
处理流程:
1.从终端读入输入的程序
2.将输入的字符串切分获得的所有参数
3.如果内置命令则立刻执行,否则调用相应的程序为其分配子进程并运行
4.Shell应该接受键盘的信号并进行处理
6.3 Hello的fork进程创建过程
在终端输入命令行后,shell会处理该命令,如果判断不是内置命令,则调用fork创建子进程。通过fork函数,子进程与父进程有相同的用户级虚拟地址,拥有与父进程打开文件文件描述的相同的副本。但两者有不同的PID。
6.4 Hello的execve过程
execve在父进程中fork一个子进程,在子进程中调用一个新的程序。
6.5 Hello的进程执行
1.逻辑控制流:
如果想用调试器单步执行程序,我们会看到一系列的程序计数器(PC)的值,这些值唯一地对应于包含在程序的可执行目标文件中的指令,或是包含在运行时动态链接到程序的共享对象中的指令。这个PC值的序列叫做逻辑控制流,或者简称为逻辑流。
2.上下文切换:
上下文是内核重新启动一个被抢占进程所需的状态。内核可以决定抢占当前的进程,并重新开始一个先前被抢占的进程,这种决策成为调度,由调度器执行。在内核中调度一个新的程序运行,它会抢占当前的进程,该过程中使用的机制成为上下文切换,是一种较高层次的一场控制流来实现的。
3.时间片:
进程执行它的控制流的一部分的每一个时间段称为时间片。
开始hello运行在用户模式,收到信号后进入内核模式,运行信号的处理程序,之后再反回用户模式。在运行过程中,cpu的上下文不断地切换,使运行过程被切分成时间片,与其他进程占用cpu,实现进程的调度。
6.6 hello的异常与信号处理
可能出现的异常:
1.中断:处理器外I/O设备信号的结果,异步异常,总是返回到下一条指令
2.陷阱:一种有意的异常,使指令执行的结果,其最重要的用途是在用户程序和内核之间提供一个像过程一样的接口,称为系统调用。一般返回下一条指令。
3.故障:由错误的情况引起,非有意。如果被故障处理程序修正,则重新执行;若不能被修正,则终止程序。
4.终止:不可恢复的致命性错误。终止处理程序,并且不会控制返回给应用程序。
程序运行实例:
1.正常运行:
2.Ctrl+c终止:
3.Ctrl+z暂停,输入ps发现hello并未关闭:
4.运行过程中乱按,无关输出被缓存到stdin,并随printf指令输出到结果
6.7本章小结
本章概括了进程的概念及作用,shell-bash,fork和execve的运行过程以及hello的执行过程中异常处理。
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
1.逻辑地址:cpu的生成地址,在内部和编程时使用,并不唯一。格式是“段地址:偏移地址”。
2.物理地址:加载到寄存器的地址,内存单位的真正地址。
3.虚拟地址:保护模式下访问存储器所用的逻辑地址。
4.线性地址:逻辑地址向物理地址转化的一步。
7.2 Intel逻辑地址到线性地址的变换-段式管理
8848共设计了20位宽的地址总线,通过将段寄存器左移4位加偏移地址得到20位地址。即逻辑地址。将内存分为不同的段,每个段有寄存器对应,段寄存器有一个栈,一个代码,两个数据寄存器。
7.3 Hello的线性地址到物理地址的变换-页式管理
系统将虚拟页作为数据传输的单位。Linux下每个虚拟页大小为4kB。物理内存也被分割为物理页,MMU负责地址翻译,MMU使页表将虚拟页到物理页的映射,即虚拟地址到物理地址的映射。
7.4 TLB与四级页表支持下的VA到PA的变换
Core i7采用四级页表的层次结构。CPU产生VA,VA传输给MMU,MMU使用VPN高位作为TLBT和TLBI向TLB中寻找匹配。如果命中,则得到PA。如果TLB没有命中,MMU查询页表,CR3确定第一级页表的起始地址,VPN1确定在第一级页表中的偏移量,查询出PTE,以此类推,最终在第四级页表中找到PPN,与VPO组合成PA,添加到PLT。
7.5 三级Cache支持下的物理内存访问
使用7.4环境中获得的PA,首先取组索引对应位,向L1cache中寻找对应组。如果存在,则比较标志位,并检查对应行的有效位是否为1。如果上述条件均满足则命中。否则按顺序对L2cache、L3cache、内存进行相同操作,直到出现命中。然后向上级cache返回直到L1cache。如果有空闲块则将目标块放置到空闲块中,否则将缓存中的某个块驱逐,将目标块放到被驱逐块的原位置。
7.6 hello进程fork时的内存映射
在shell输入命令行后,内核调用fork创建子进程,为hello程序的运行创建上下文,并分配一个与父进程不同的PID。通过fork创建的子进程拥有父进程相同的区域结构、页表等的一份副本,同时子进程也可以访问任何父进程已经打开的文件。当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同,当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,因此,也就为每个进程保持了私有地址空间。
7.7 hello进程execve时的内存映射
execve函数调用驻留在内核区域的启动加载器代码,在当前进程中加载并运行包含在可执行目标文件hello中的程序,用hello程序有效地替代了当前程序。加载并运行hello需要以下几个步骤:
(1)删除已存在的用户区域,删除当前进程虚拟地址的用户部分中的已存在的区域结构。
(2)映射私有区域,为新程序的代码、数据、bss和栈区域创建新的区域结构,所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为hello文件中的.text和.data区,bss区域是请求二进制零的,映射到匿名文件,其大小包含在hello中,栈和堆地址也是请求二进制零的,初始长度为零。
(3)映射共享区域, hello程序与共享对象libc.so链接,libc.so是动态链接到这个程序中的,然后再映射到用户虚拟地址空间中的共享区域内。
(4)设置程序计数器(PC),execve做的最后一件事情就是设置当前进程上下文的程序计数器,使之指向代码区域的入口点。
7.8 缺页故障与缺页中断处理
内核中有相应的程序处理缺页故障,称为缺页处理程序,其主要步骤为:
1.检查虚拟地址是否合法,若不合法,则程序终止。
2.检查进程是否有读写或执行该区域权限,若不具有,则触发异常保护机制,程序终止。
3.内核选择一个牺牲页面,写入磁盘,重新更换新的页面。
4.控制权转移给hello进程,执行触发缺页的指令
7.9动态存储分配管理
printf函数会调用malloc,下面简述动态内存管理的基本方法与策略:
动态内存分配器维护着一个进程的虚拟内存区域,称为堆。分配器将堆视为一组不同大小的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。
分配器分为两种基本风格:显式分配器、隐式分配器。
显式分配器:要求应用显式地释放任何已分配的块。
隐式分配器:要求分配器检测一个已分配块何时不再使用,那么就释放这个块,自动释放未使用的已经分配的块的过程叫做垃圾收集。
堆中的块主要组织为两种形式:
1.隐式空闲链表(带边界标记)
在块的首尾的四个字节分别添加header和footer,负责维护当前块的信息(大小和是否分配)。由于每个块是对齐的,所以每个块的地址低位总是0,可以用该位标注当前块是否已经分配。可以利用header和footer中存放的块大小寻找当前块两侧的邻接块,方便进行空闲块的合并操作。
2.显式空闲链表
在未分配的块中添加两个指针,分别指向前一个空闲块和后一个空闲块。采用该策略,使首次适配的分配时间从块总数的线性时间减少到了空闲块数量的线性时间。
7.10本章小结
本章阐述了计算机中虚拟内存管理,物理地址,线性地址,逻辑地址的却别以及他们的变换模式。描述了段式,页式管理,在了解了内存映射的基础上重新认识共享对象,kork和execve。同时认识了动态分配的方法和原理。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:所有I/O设备被模型化为文件,所有输入输出当成文件的读写处理。
设备管理:这种将设备映射为文件的方式,允许linux内核引出一个简单的低级的应用接口,称为Unix I/O,这使得所有的输入输出都能以一种统一且一致的方式来执行。
8.2 简述Unix IO接口及其函数
Unix I/O设备执行操作:
1.打开文件:一个应用程序通过要求内核打开相关的文件,来宣告它要访问一个I/O设备。内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中的标识这个文件。内核记录有关这个打开文件的所有信息。
2.Linux在创造进程时都有打开三个文件:标准输入,标准输出和标准错误。
3.改变当前文件的位置:对于每个打开的文件,内核保持着文件位置K,初始值为0,这个文件时从开头起始的字节偏移量,应用程序能够通过执行seek,显示当前文件的位置k。
4.读写文件:一个读的操作就是从文件复制n>0个字节到内存,从当前文件位置的k开始,然后将k增加到k+n,给定一个大小为m字节的文件。当k≥m时,触发EOF。类似一个写操作就是从内存复制n>0个字节到一个文件,从当前文件的k开始,然后更新k。
5.关闭文件:当应用完成了对文件的访问后,就通知内核关闭这个文件。内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池。
Unix I/O函数:
1.int open(char* filename,int flags,mode_t mode):进程通过调用open函数打开或创建一个新文件。open函数将filename转化为一个文件描述符。flags指明了进程如何访问这个文件。mode参数指定新文件的访问权限。
2.int close(fd):fd时需要关闭文件的描述符。
3.ssize_t read(int fd,void *buf,size_t n):read函数从描述符fd的当前文件复制最多n个字节到内存位置buf。返回值为-1表示一个错位,0表示EOF。否则返回值白哦是实际传输的字节数量。
4.ssize_t write(int Fd,const void *buf,size_t n):write函数从内存位置buf复制至多n个字节到描述符fd的当前位置。
8.3 printf的实现分析
前提:printf和vsprintf代码是windows下的。
查看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;
}
首先arg获得第二个不定长参数,即输出的时候格式化串对应的值。
查看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’: //只处理%x一种情况
itoa(tmp, ((int)p_next_arg)); //将输入参数值转化为字符串保存在tmp
strcpy(p, tmp); //将tmp字符串复制到p处
p_next_arg += 4; //下一个参数值地址
p += strlen(tmp); //放下一个参数值的地址
break;
case ‘s’:
break;
default:
break;
}
}
return (p - buf); //返回最后生成的字符串的长度
}
则知道vsprintf程序按照格式fmt结合参数args生成格式化之后的字符串,并返回字串的长度。
在printf中调用系统函数write(buf,i)将长度为i的buf输出。write函数如下:
write:
mov eax, _NR_write
mov ebx, [esp + 4]
mov ecx, [esp + 8]
int INT_VECTOR_SYS_CALL
在write函数中,将栈中参数放入寄存器,ecx是字符个数,ebx存放第一个字符地址,int INT_VECTOR_SYS_CALLA代表通过系统调用syscall,查看syscall的实现:
sys_call:
call save
push dword [p_proc_ready]
sti
push ecx
push ebx
call [sys_call_table + eax * 4]
add esp, 4 * 3
mov [esi + EAXREG - P_STACKBASE], eax
cli
ret
syscall将字符串中的字节“Hello 1180200704 肖家豪”从寄存器中通过总线复制到显卡的显存中,显存中存储的是字符的ASCII码。
字符显示驱动子程序将通过ASCII码在字模库中找到点阵信息将点阵信息存储到vram中。
显示芯片会按照一定的刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
于是我们的打印字符串“Hello 1180200704 肖家豪”就显示在了屏幕上。
8.4 getchar的实现分析
异步异常-键盘中断的处理:当用户按键时,键盘接口会得到一个代表该按键的键盘扫描码,同时产生一个中断请求,终端请求抢占当前进程运行键盘中断子程序,键盘中断子程序先从键盘接口取得该按键的扫描码,然后转化为ASCII码,保存在系统的键盘缓冲区之中。getchar函数落实到底层调用了系统的read函数,通过系统调用read读取在键盘缓冲区ASCII码,直到读到回车符,然后返回整个字串,getchar进行封装,大体逻辑时读取字符串的第一个字符然后返回。
8.5本章小结
本章主要阐述了linux系统下,I/O设备的信号的传输凡是及相关函数。同时介绍了printf和getchar的实现方法。
(第8章1分)
结论
hello的艰辛历程:
1.编写:将代码带入hello.c
2.预处理:将hello.c以及其头文件对应的代码一起放入hello.i中
3.编译:将hello.i转化为汇编语言,写入hello.s
4.汇编:将hello.s转化为二进制机器语言,写入hello.o中
5.链接:将hello.c中使用的函数对应的机器语言,结合hello.o,写入hello中
6.运行:在shell中运行hello程序
7.创建子进程:shell调用fork创建子进程
8.运行程序:shell调用execve,启动加载器,映射虚拟内存,进入程序入口后载入物理内存,进入main函数。
9.执行命令:CPU为其分配时间片,在一个时间片中,hello享有CPU资源,执行自己的控制逻辑流。
10.访问内存:MMU将程序中使用的虚拟内存通过页表映射成物理地址。
11.动态申请内存:printf会调用malloc向动态内存分配器申请堆中的内存。
12.异常处理:运行途中按ctrl+z或者ctrl+c分别使程序挂起,停止。
13.结束:shell父进程祸首子进程,删除其子进程创建的数据结构。
至此,我终于写完了……我只想说,我以后一定好好学习计算机的相关课程,按考试课的标准学习!
(结论0分,缺失 -1分,根据内容酌情加分)
附件
10.hello.i:cpp根据以字符#开头的命令,修改原始的C程序。主要是将头文件,例如stdio文件插入程序文本中,得到新的C程序。
11.hello.s:编译器将C语言翻译成汇编语言,作为中间的过度阶段。
12.hello.o:汇编器将汇编语言转化为机器可读的机器语言。
13.hello:源程序中调用了printf,getchar,sleep,atoi等函数。利用连接器,将C语言函数库中相应的可连接文件与3中的文件连接,生成可执行的文件。
14.hello.elf:hello.o的可执行可连接格式。
15.hello1.elf:hello文件的elf格式。
16.hello0.txt:hello.o的反汇编代码。
17.hello1.txt:hello的返回编码。
18.a.out:可执行文件。
具体中间产物已放入压缩包中。
(附件0分,缺失 -1分)
参考文献
为完成本次大作业你翻阅的书籍与网站等
[1]https://baike.baidu.com/item/%E9%A2%84%E5%A4%84%E7%90%86/7833652?fr=aladdin.百度百科
[2]https://baike.baidu.com/item/%E7%BC%96%E8%AF%91/1258343?fr=aladdin.百度百科
[3]https://www.cnblogs.com/qingmingsang/articles/7100573.html
[4]https://baike.baidu.com/item/execve/4475693?fr=aladdin
[5]https://my.oschina.net/u/3857782/blog/1854572
[6]林来兴. 空间控制技术[M]. 北京:中国宇航出版社,1992:25-42.
[7]辛希孟. 信息技术与信息服务国际研讨会论文集:A集[C]. 北京:中国科学出版社,1999.
[8]赵耀东. 新时代的工业工程师[M/OL]. 台北:天下文化出版社,1998 [1998-09-26]. http://www.ie.nthu.edu.tw/info/ie.newie.htm(Big5).
[9]谌颖. 空间交会控制理论与方法研究[D]. 哈尔滨:哈尔滨工业大学,1992:8-13.
[10]KANAMORI H. Shaking Without Quaking[J]. Science,1998,279(5359):2063-2064.
[11]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.
[12]Randal E.Bryant.David R.O`Hallaron《深入理解计算机系统》.机械工业出版社2018年12月.
(参考文献0分,缺失 -1分)