计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业
学 号
班 级
学 生
指 导 教 师
计算机科学与技术学院
2023年4月
本文通过跟踪hello程序的生命周期来回顾对计统的学习——从它被程序员创建开始,到在系统上运行,输出简单的消息,然后终止。本文将沿着这个程序的生命周期,简要地介绍一些逐步出现的关键概念、专业术语和组成部分。本文以《深入了解计算机系统》知识主线进行总结梳理。
关键词:csapp,hello的一生
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
6.2 简述壳Shell-bash的作用与处理流程... - 32 -
6.3 Hello的fork进程创建过程... - 32 -
6.6.1hello执行过程中异常、信号、处理... - 34 -
7.2 Intel逻辑地址到线性地址的变换-段式管理... - 36 -
7.3 Hello的线性地址到物理地址的变换-页式管理... - 36 -
7.4 TLB与四级页表支持下的VA到PA的变换... - 36 -
7.5 三级Cache支持下的物理内存访问... - 36 -
7.6 hello进程fork时的内存映射... - 37 -
7.7 hello进程execve时的内存映射... - 37 -
第1章 概述
1.1 Hello简介
根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
-
-
- P2P:
-
“hello.c(Program),无意识中将我预处理、编译、汇编、链接,历经艰辛-神秘-高贵-欣喜,我-Hello一个完美的生命诞生了。你造吗?在壳(Bash)里,伟大的OS(进程管理)为我fork(Process),为我execve,为我mmap,分我时间片,让我得以在Hardware(CPU/RAM/IO)上驰骋(取指译码执行/流水线等);”这段话即描述了以下过程:
Hello.c(Program)经c预处理器转化为hello.i,然后经c编译器转化为hello.s,再经汇编器转化为hello.o,最后链接器将该文件与头文件中所需的库函数链接形成可执行elf文件。在命令行中输入./hello,Shell解析命令为外部命令,调用fork函数创建一个子进程,由此将program变成一个progress。调用execve函数执行程序、加载进程,sbrk函数开辟内存空间,mmap将其映射到内存中。
-
-
- O2O:
-
O2O的过程即说明hello从没有存在于内存,到被回收,从内存中消失的过程:
CPU通过取指、译码、执行、访存、写回、更新PC等执行hello程序。首先需要将虚拟地址转换为物理地址,通过TLB和页表加速地址的翻译,高速缓存cache加快数据的传输。IO管理和Shell的进程处理和信号处理机制可以处理hello运行过程中的信号。当进程结束时,shell会回收hello进程,并且内核会从系统中删除hello所有痕迹。
1.2 环境与工具
硬件:Inter core i5,X64 CPU, 2.5GHz,16384MB RAM,512 GB HD Disk
软件:Windows10,Virtual Box 7.0 ; Ubuntu 64-bit
调试工具:gcc ,gdb ,edb ,readelf ,objdump ,codeblocks
1.3 中间结果
Hello.i 预处理之后的文本文件
Hello.s 编译之后产生的汇编文件
Hello.ld 链接后的文件
Hello.o 可重定位的目标文件
Hello 可执行文件hello
Helloo.objdump Hello.o反汇编文件
Hello.elf Hello的ELF格式
Hello.objdump Hello的反汇编文件
1.4 本章小结
这一章对hello进行了简单的介绍,包括从程序到进程的具体流程、使用的软硬件环境和调试工具,以及在处理过程中生成的各种中间文件。
(第1章0.5分)
第2章 预处理
概念:编译过程的第一步预就是预处理,预处理结束后会产生一个后缀为(.i)的临时文件,这一步由预处理器完成。预处理器主要完成以下任务:删除所有的注释、宏扩展、文件包含。预处理器会在编译过程中删除所有注释,因为注释不属于程序代码,它们对程序的运行没有特别作用。宏是使用 #define 指令定义的一些常量值或表达式。宏调用会导致宏扩展。预处理器创建一个中间文件,其中一些预先编写的汇编级指令替换定义的表达式或常量(基本上是匹配的标记)。为了区分原始指令和宏扩展产生的程序集指令,在每个宏展开语句中添加了一个“+”号。
作用:预处理把源代码分割或处理成为特定的单位——预处理记号,用来支持语言特性(如C/C++的宏调用)。
2.2在Ubuntu下预处理的命令
图2-1 Ubuntu下预处理命令
2.3 Hello的预处理结果解析
图2-2 hello.c文件
图2-3 hello.i文件
对比两个文件可以发现,源文件经过预处理后,由23行扩展为了3000多行,main函数被放在了最后,前面是大量的递归展开的头文件。
2.4 本章小结
第二章主要介绍了预处理的概念及作用,以及在Ubuntu下进行预处理的实操。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
概念:C 中的编译阶段使用内置编译器软件将 (.i) 临时文件转换为具有汇编级指令(低级代码)的汇编文件 (.s)。为了提高程序的性能,编译器将中间文件转换为程序集文件。
汇编代码是一种简单的英文语言,用于编写低级指令(在微控制器程序中,我们使用汇编语言)。整个程序代码由编译器软件一次性解析(语法分析),并通过终端窗口告诉我们源代码中存在的任何语法错误或警告。
作用:编译后生成的.s文本文件是汇编语言程序,更容易让计算机理解,编译是将程序转换为机器指令的中间过程。
3.2 在Ubuntu下编译的命令
图3-1在ubuntu下编译命令
3.3 Hello的编译结果解析
3.3.1 头部信息和字符串常量
图3-2-1 hello.s截图
可以看到首先是对hello.c的一些基本信息.file ,.text,.section,align,即文件名、文本、节、对齐方式;
.LC0,.LC1 后缀是.string表明存储字符串常量,对应于hello.c文件中下边字符串;
图3-2-2 hello.s截图
可以看到,.LC0对应第一个字符串,.LC1对应第二个字符串。
3.3.2反汇编代码整体分析
图3-2-3 hello.s截图
.LFB6 是以图3-2-4为主体;
图3-2-4 hello.s截图
通过25行判断语句跳转到.L2,.L3,.L4,即下一部分,图3-2-5。
图3-2-5 hello.s截图
3.3.3 变量与常量分析
整型常量以立即数形式被嵌套到代码中(argc!=4中的4):
图3-2-6 hello.s截图
局部变量存储到栈上:对应于源程序局部变量i
图3-2-7 hello.s截图
3.3.4算数操作
i++,对应于add操作
图3-2-8 hello.s截图
3.3.5控制转移
图3-2-9 hello.s截图
判断i<5,用cmpl比较,在进行相应跳转jle
3.3.6数组指针操作
图3-2-10 hello.s截图
argc是参数个数,argc[]是字符数组;每一个数组的元素都是一个char*类型的指针,可以指向一个字符串,而具体来说argv[0]指向文件名,argv[1] argv[2]分别指向命令行输入的第一个和第二个参数,在汇编代码中这个指针数组的首地址被存放在rsi中,也就是存放main函数的第二个参数的寄存器。对于argv的操作,其指针数组的基地址通过寄存器存放,将基地址加上8的倍数的偏移量就可以访问指针数组的别的成员指针,而为了访问每一个指针成员指向的字符串,需要访问每一个指针指向的内存空间。
图3-2-11 hello.s截图
3.3.7函数调用
图3-2-12 hello.s截图
通过call调用函数;
图3-2-13 hello.s截图
通过leave返回。
函数一共调用了两次printf函数,第一次直接调用puts
图3-2-14 hello.s截图
第二次调用printf函数。
图3-2-15 hello.s截图
3.4 本章小结
在本章主要了解了编译的概念、作用,以及汇编代码。通过对于hello.i文件进行编译并且分析汇编代码,我们更加清楚地明白了全局变量和局部变量的区别,常量是如何进行存储的,对于非线性执行的跳转语句是如何进行的,以及在实现跳转的基础上如何进行逻辑控制、函数调用、传参,实现循环等。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
概念:使用汇编程序将程序集级代码(.s 文件)转换为机器可理解的代码(二进制/十六进制形式)。汇编程序是一个预先编写的程序,它将汇编代码转换为机器代码。它从程序集代码文件中获取基本指令,并将其转换为特定于计算机类型(称为目标代码)的二进制/十六 进制代码。生成的文件与程序集文件同名,在 DOS 中称为扩展名为 .obj 的对象文件,在 UNIX 操作系统中扩展名为 .o。
作用:从汇编指令进一步得到CPU可以执行的机器指令。
4.2 在Ubuntu下汇编的命令
图4-1 在ubuntu下汇编命令
4.3 可重定位目标elf格式
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
图4-2 hello.o重定向输出ELF格式命令
图4-3 ELF格式图解
4.3.1 ELF头
图4-4 ELF头
可以看到,其中包含了版本和系统信息、编码方式、节的大小和数量、ELF头大小和头部索引等等一系列信息。
4.3.2节头目表
图4-5 节头目表
包含了各个节的起始地址和偏移量等信息。
4.3.3重定位节
图4-6 重定位节
可以看到,重定位节中有各种引用的外部符号,给出了相应的偏移量。我们还可以看到重定位节中的外部符号包括了全局变量,以及调用的函数,其中.rodata中的数据是模式串。有了这些信息之后,在下一步进行链接,就可以通过重定位节对这些位置的地址进行重定位,使其映射到虚拟内存上。
4.3.4符号表
图4-7 符号表
其中包括了引用的函数,全局变量等等。
4.4 Hello.o的结果解析
图4-8 hello.o反汇编命令
图4-9-1 hello_disassemle.s文件截图
对两份汇编代码进行对比,我们可以发现以下的不同之处:
1.立即数进制不同
图4-9-2 hello_disassemle.s文件截图
Hello.s中的操作数采用10进制编码,而反汇编文件中的操作数都已16进制编码。
2.跳转标识不同
Hello.s中的跳转采用跳转到.L2等方式进行跳转,而反汇编文件中则是采用直接跳转,跳转位置为主函数起始地址加上偏移量。
图4-9-3 hello_disassemle.s文件截图
3.函数调用机制不同
Hello.s中的函数调用采用直接跳转到函数名的方式进行跳转,而反汇编文件中则是采用直接跳转,跳转位置为主函数起始地址加上偏移量。
4.机器指令有无
Hello.s前没有相应的机器码,反汇编代码前面有对应的机器码。
4.5 本章小结
本章介绍了汇编的概念、作用以及在Ubuntu下通过gcc指令对.s文件进行汇编的方法,通过对hello.s进行汇编得到hello.o,分析hello.o的ELF格式,并将hello.o与hello.s进行对比说明区别。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
概念:链接是将库文件包含在我们的程序中的过程。库文件是一些预定义的文件,其中包含机器语言中的函数定义,这些文件的扩展名为.lib。一些未知语句写入我们的操作系统无法理解的对象 (.o/.obj) 文件中。我们使用库文件来为对象文件中的一些未知语句赋予意义。链接过程会生成一个可执行文件,其扩展名为 .exe 在 DOS 中为 .out,在 UNIX 操作系统中为 .out。
作用:链接将汇编语言代码彻底转化为机器代码,把可重定位目标文件和命令行参数作为输入,生成可执行目标文件。
5.2 在Ubuntu下链接的命令
图5-1 ubuntu链接命令
5.3 可执行目标文件hello的格式
5.3.1ELF头
图5-2 ELF头
可以看到里面的信息有系统版本,框架,大小,起止位置,此节大小等信息。
5.3.2 节头
图5-3 节头
可以看到,节头描述了各个节的大小、偏移量和其他属性。链接器链接时,会将各个文件的相同段合并成一个大段,并且根据这个大段的大小以及偏移量重新设置各个符号的地址。
5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
虚拟空间从0x401000开始。
图5-4 edb下hello虚拟空间
5.5 链接的重定位过程分析
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
图5-5 hello的反汇编
1)hello.o反汇编代码虚拟地址从0开始,而hello反汇编代码从0x401000开始。
2)hello.o反汇编代码就直接是.text段,然后为main函数。而hello反汇编的结果中,由于链接过程中重定位而加入进来各种函数、数据。如开始的函数和调用的函数填充在main函数之前。所以main函数的位置发生了巨大的改变。
5.6 hello的执行流程
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
图5-6 函数地址
5.7 Hello的动态链接分析
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
动态链接项目中,查看dl_init前后项目变化。对于动态共享链接库中PIC函数,编译器加重定位记录,等待动态链接器处理,为避免运行时修改调用模块的代码段,链接器采用延迟绑定的策略,将过程地址的绑定推迟到第一次调用该过程。动态链接器使用过程链接表PLT+全局偏移量表GOT实现函数的动态链接,GOT中存放函数目标地址,PLT使用GOT中地址跳转到目标函数。
图5-7 .got动态链接
在dl_init调用之前,对于每一条PIC函数调用,调用的目标地址都实际指向PLT中的代码逻辑,初始时每个GOT条目都指向对应的PLT条目的第二条指令。
5.8 本章小结
本章介绍了链接的概念、作用以及在Ubuntu下通过ld指令进行链接的方法,通过对hello.o进行链接得到可执行目标文件hello,分析hello的ELF格式与动态链接的实现。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
概念:进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
作用:进程给应用程序提供一种假象:每个进程在运行时,就像是一个独立的逻辑控制流一样,独占整个CPU和内存以及其它资源。实际上在系统中可能同时运行多个进程,CPU通过快速的处理和进程切换形成并发流,造成看似同时运行的假象。为了实现这样的抽象,系统通过shell程序的合理设计以及上下文切换机制等设计来实现这样的目标。
6.2 简述壳Shell-bash的作用与处理流程
6.2.1Shell
Shell 是一个用 C 语言编写的程序,是用户使用 Linux 的桥梁。Shell 既是一种命令语言,又是一种程序设计语言,Shell 是指一种应用程序。
6.2.2功能和处理流程:
功能:Shell 应用程序提供了一个界面,用户通过这个界面访问操作系统内
核的服务。
处理流程:
1)从终端读入输入的命令。
2)将输入字符串切分获得所有的参数
3)如果是内置命令则立即执行
4)否则调用相应的程序执行
5)shell 应该接受键盘输入信号,并对这些信号进行相应处理。
6.3 Hello的fork进程创建过程
在输入命令./hello后,父进程shell会先解析命令,判断这是一个非内嵌的前台执行的命令,因此父进程调用fork函数,在创建的子进程中,虚拟空间的内容与父进程完全相同,还包括了相同的打开文件:这意味着父进程打开的文件,子进程拥有完全一样的读写权限。二者的不同之处在于其虚拟内存空间指向的物理内存空间不同,而且其进程ID,也就是PID也不同。此外,fork函数被调用一次,但是会分别在父子进程中各返回一次,共计两次,父进程返回子进程的PID,子进程返回0。
6.4 Hello的execve过程
在shell父进程创建的子进程中,子进程会调用execve函数,其参数主要是两个二级指针char **argv , char **envp,其作用主要是给出加载程序的参数。只有在加载文件出现错误的时候,例如找不到目标文件,execve函数才会返回,否则就直接执行程序,不再返回。
在execve加载了Hello之后,会为新的程序映射虚拟内存空间,并且将控制流转移到新程序的主函数中,主函数main有三个参数int argc , char **argv , char **envp,其中argv和envp分别等于传递向execve函数的参数,argc的数值等于argv指向的字符串数组的元素个数。
此时子进程还会为新的程序映射新的数据段和代码段区域,以及共享区,然后将程序计数器(PC)的值修改为新程序的代码区域入口处的地址。
6.5 Hello的进程执行
6.5.1并发、多任务、上下文与调度
多个流并发地执行的一般现象被称为并发。一个进程和其他进轮流运行的概念称为多任务。一个进程执行它的控制流的一部分的每一时间段叫做时间片。因此,多任务也叫做时间分片。操作系统内核使用一种称为上下文切换的较高层形式的异常控制流来实现多任务。内核为每个进程维持一个上下文。上下文就是内核重启一个被抢占的进程所需得状态。在执行过程中,内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程,这个决策称为调度。hello程序与操作系统其他进程通过操作系统的调度,切换上下文,拥有各自的时间片从而实现并发运行。hello在sleep时就是这样的切换。
程序在进行一些操作时会发生内核与用户状态的不断转换。这是为了保持在适当的时候有足够的权限和不容易出现安全问题。
6.5.2上下文切换
上下文切换是由内核中调度器完成的,当内核调度新的进程运行后,它就会抢占当前进程,并进行1)保存以前进程的上下文2)恢复新恢复进程被保存的上下文,3)将控制传递给这个新恢复的进程,来完成上下文切换。
6.5.3hello进程执行
hello初始运行在用户模式,在hello进程调用sleep之后陷入内核模式,内核处理休眠请求主动释放当前进程,并将hello进程从运行队列中移出加入等待队列,定时器开始计时,内核进行上下文切换将当前进程的控制权交给其他进程,当定时器到时时(2secs)发送一个中断信号,此时进入内核状态执行中断处理,将hello进程从等待队列中移出重新加入到运行队列,成为就绪状态,hello进程就可以继续进行自己的控制逻辑流。
当hello调用getchar的时候,实际落脚到执行输入流是stdin的系统调用read,hello之前运行在用户模式,在进行read调用之后陷入内核,内核中的陷阱处理程序请求来自键盘缓冲区的DMA传输,并且安排在完成从键盘缓冲区到内存的数据传输后,中断处理器。此时进入内核模式,内核执行上下文切换,切换到其他进程。当完成键盘缓冲区到内存的数据传输时,引发一个中断信号,此时内核从其他进程进行上下文切换回hello进程。
6.6 hello的异常与信号处理
6.6.1hello执行过程中异常、信号、处理
可能出现的异常种类:1.终止 2.故障
可能出现的信号:1.SIGINT信号 2.SIGSTP信号
按下ctrl-z后,shell父进程收到SIGSTP信号,信号处理程序将hello进程挂起,放到后台,停止hello程序
按下ctrl-c后,shell父进程收到SIGINT信号,由信号处理函数结束hello,并回收hello进程。
6.6.2程序运行过程
正常执行
图6-1 正常执行
按空格和回车
图6-2 空格回车
Ctrl+c
图6-3 ctrl+c
Ctrl+z
图6-4 ctrl+z
6.7本章小结
本章介绍了进程的概念和作用,分析了操作系统通过shell调用fork函数以及execve函数的过程,以及hello 程序的上下文切换、异常与信号处理。
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。
1)逻辑地址:程序代码经过编译后出现在汇编程序中地址。逻辑地址由选择符 (在实模式下是描述符,在保护模式下是用来选择描述符的选择符)和偏移量(偏移部分)组成。
2)线性地址:逻辑地址经过段机制后转化为线性地址,为描述符:偏移量的组合形式。分页机制中线性地址作为输入。
3)虚拟地址:使用虚拟地址技术为进程分配的虚拟地址空间中的地址,可以在进程的页表中翻译成物理地址。
4)物理地址:CPU 通过地址总线的寻址,找到真实的物理内存对应的地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
段式管理:把一个程序分成若干个段进行存储,将每个段看成一个逻辑实体。它与程序的模块化有关。段式管理是通过段表进行的,它包括段号或段名、段起点、装入位、段的长度等。此外还需要主存占用区域表、主存可用区域表。
7.3 Hello的线性地址到物理地址的变换-页式管理
页式管理:把物理地址空间和虚拟地址空间都划分为一个个特定大小的块,把这些块称为页。在主存上进行数据交换时以页为单位进行。
7.4 TLB与四级页表支持下的VA到PA的变换
进程的虚拟地址空间对应的物理地址存储在页表中。TLB缓存了一小部分页表信息。想要将VA变换为PA,根据VA的VPN在TLB中找对应的PA。若找到,则直接得到PPN。若没找到,则去页表中查找。如果页面已存入主存,则得到PPN,否则将页面有效位设置为1,然后重新执行上述过程。
7.5 三级Cache支持下的物理内存访问
PA包括CO、CI、CT三部分。CO由块大小决定,CI由cache的构造—有多少组决定,而剩下的CT就是标记。根据CI找到对应的组,在根据CT看是否命中。如果在一级cache中命中PA对应的块,就根据CO将要找的数据传给cpu;如果在一级cache中找不到对应的数据,就得根据相同的地址去二级,三级甚至主存中寻找数据;在二、三级cache中找到数据还需要在一级cache中根据具体情况选择是否要进行驱逐和替换。
7.6 hello进程fork时的内存映射
当shell调用fork创建hello进程时,内核为hello进程创建各种数据结构,将某个唯一的PID分配给它。同时,为了给hello进程创建其唯一的虚拟内存,内核会复制当前进程的mm_struct,区域结构和页表的原样副本。内核将两个进程中的每个页面都标记为只读,两个进程中的区域结构都标记为私有的写时复制。
7.7 hello进程execve时的内存映射
exceve函数加载和执行程序Hello,需要以下几个步骤:
1)删除已存在的用户区域。
2)映射私有区域。为Hello的代码、数据、bss和栈区域创建新的区域结构,所有这些区域都是私有的、写时复制的。
3)映射共享区域。比如Hello程序与标准C库libc.so链接,这些对象都是动态链接到Hello的,然后再用户虚拟地址空间中的共享区域内。
4)设置程序计数器(PC)。exceve做的最后一件事就是设置当前进程的上下文中的程序计数器,使之指向代码区域的入口点。
7.8 缺页故障与缺页中断处理
首先找到牺牲页,检查牺牲页此前是否发生改动,如果是,则将牺牲页写回。设置牺牲页有效位为0,设置要换入的页面有效位为1。然后重新执行导致缺页异常的指令。
7.9动态存储分配管理
Printf会调用malloc,请简述动态内存管理的基本方法与策略。
动态内存管理一般由动态内存分配器维护,分配器有两种风格,显示和隐式,两者的区别在于前者要求应用显式的释放任何已分配块,而后者则自动检测一个已分配块何时不再被程序使用。不过两者都要求显式地分配空闲块。动态内存要处理好空闲块与已分配块的区分,空闲块的管理等问题。所以通过将一些信息如该块的大小,该块是否已经被分配等等嵌入到块中。通过这种处理得到的结构被称为隐式空闲链表。
由于隐式空闲链表的某些缺点,动态内存分配器还通过显式空闲链表这种结构来管理空闲块。显式空闲链表在隐式的基础上增加了指向前驱节点和后继节点的指针,当然,其前驱和后记也是空闲块。内存分配器在malloc时需要寻找合适的空闲块,这时就有不同的策略。可以有首次适配,即从头开始搜索,选择第一次遇到的合适的块;下一次适配,略有不同,这种方法从上一次查询结束的地方开始搜索,而不是每次都从头开始搜索。除此之外,对空闲块的搜索还包括最佳适配,还可以配合显式空闲链表实现LIFO(后进先出)的策略。在内存分配器释放已分配块的同时,还要根据不同的空闲块的相对位置确定空闲块之间要如何合并。
7.10本章小结
本章分析了hello的存储管理,主要涉及内存。介绍了Intel逻辑地址到线性地址的变换-段式管理,以及TLB与多级页表支持下的VA到PA的转换,同时对三级Cache支持下的物理内存访问做了说明。简述了hello的fork和execve内存映射,了解了缺页故障与缺页中断处理程序,介绍了动态存储分配管理。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件。我们可以将所有的I/O设备都被模型化为文件,方便统一进行操作。
设备管理:unix io接口。这种将设备统一模型化为文件的方式,允许Linux内核引出一个简单、低级的应用接口,称为Unix I/O。这样,我们就可以简单地对所有的文件执行open/close,read/write或是lseek操作等等。
8.2 简述Unix IO接口及其函数
8.2.1Unix I/O接口:
1)打开文件。一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个I/O设备,内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件,内核记录有关这个打开文件的所有信息。
2)Shell创建的每个进程都有三个打开的文件:标准输入,标准输出,标准错误。
3)改变当前的文件位置:对于每个打开的文件,内核保持着一个文件位置k,初始为0,这个文件位置是从文件开头起始的字节偏移量,应用程序能够通过执行 seek,显式地将改变当前文件位置 k。
4)读写文件:一个读操作就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增加到k+n,给定一个大小为m字节的文件,当k>=m时,触发EOF。
5)关闭文件,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中去。
8.2.2Unix I/O 函数:
1)open()函数
功能描述:用于打开或创建文件,在打开或创建文件时可以指定文件的属性及用户的权限等各种参数。
函数原型:int open(const char *pathname,int flags,int perms)
参数:pathname:被打开的文件名flags:文件打开方式,
返回值:成功:返回文件描述符;失败:返回-1
2)close()函数
功能描述:用于关闭一个被打开的的文件
函数原型:int close(int fd)
参数:fd文件描述符
函数返回值:0成功,-1出错
3)read()函数
功能描述: 从文件读取数据。
函数原型:ssize_t read(int fd, void *buf, size_t count);
参数:fd:将要读取数据的文件描述词。buf:指缓冲区,即读取的数据会被放到这个缓冲区中去。count: 表示调用一次read操作,应该读多少数量的字符。
返回值:返回所读取的字节数;0(读到EOF);-1(出错)。
4)write()函数
功能描述: 向文件写入数据。
函数原型:ssize_t write(int fd, void *buf, size_t count);
返回值:写入文件的字节数(成功);-1(出错)
5)lseek()函数
功能描述: 用于在指定的文件描述符中将将文件指针定位到相应位置。
函数原型:off_t lseek(int fd, off_t offset,int whence);
参数:fd;文件描述符。offset:偏移量,每一个读写操作所需要移动的距离,单位是字节,可正可负(向前移,向后移)
返回值:成功:返回当前位移;失败:返回-1
8.3 printf的实现分析
调用了两个函数vsprintf,write。vsprintf函数的作用是将所有的参数内容格式化之后存入buf,然后返回格式化数组的长度。write函数是将buf中的i个元素写到终端的函数。从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等。(参考https://www.cnblogs.com/pianist/p/3315801.html)
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
getchar有一个int型的返回值。当程序调用getchar时,程序就等着用户按键,用户输入的字符被存放在键盘缓冲区中直到用户按回车为止(回车字符也放在缓冲区中)。
当用户键入回车之后,getchar才开始从stdio流中每次读入一个字符。getchar函数的返回值是用户输入的第一个字符的ascii码,如出错返回-1,且将用户输入的字符回显到屏幕。如用户在按回车之前输入了不止一个字符,其他字符会保留在键盘缓存区中,等待后续getchar调用读取。也就是说,后续的getchar调用不会等待用户按键,而直接读取缓冲区中的字符,直到缓冲区中的字符读完为后,才等待用户按键。
8.5本章小结
本章介绍了Unix I/O设备管理机制及方法,Unix I/O接口及函数并分析了printf和getchar函数。
(第8章1分)
结论
用计算机系统的语言,逐条总结hello所经历的过程。
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
1.编写程序:用高级语言C语言写成hello.c文件。
2.预处理:预处理器(cpp)根据以字符 # 开头的命令,修改原始的 C 程序。比如 hello.c 中第 1 行的#include <stdio.h>
命令告诉预处理器读取系统头文件 stdio.h 的内容,并把它直接插入程序文本中。结果就得到了另一个 C 程序,通常是以 .i 作为文件扩展名。
3.编译:编译器(ccl)将文本文件 hello.i 翻译成文本文件 hello.s,它包含一个汇编语言程序。
4.汇编:汇编器(as)将 hello.s 翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序的格式,并将结果保存在目标文件 hello.o 中。
5.链接:将 hello.o 与可重定位目标文件和动态链接库链接成为可执行目标程序 hello。
6.运行:在linux系统下,在终端中输入./hello的指令。
7.创建子进程:shell 调用 fork 为其创建子进程
8.运行程序:shell 调用 execve,execve 调用启动加载器,加载器为 hello 创建新的运行空间,最终调用 hello 的 main 函数。
9.执行指令:CPU 为其分配时间片,在一个时间片中,hello 享有 CPU 资源,顺序执行自己的控制逻辑流
10.对异常和信号进行处理:包括运行过程中代码段,数据段,堆栈段的缺页异常,除此之外如果运行途中键入ctr-c ,ctr-z 则调用 shell 的信号处理函数分别停止、挂起。
11.结束:shell 父进程回收子进程,内核删除为这个进程创建的所有数据结构。
感悟:任何一个程序的整个设计与运行过程很巧妙,既保持了各个部分的相对独立性,又相互关系,层层推进。
(结论0分,缺失 -1分,根据内容酌情加分)
附件
列出所有的中间产物的文件名,并予以说明起作用。
表1 中间产物及作用
中间结果文件 | 文件的作用 |
hello.i | 预处理后的文本文件 |
hello.s | 编译后汇编程序文本文件 |
hello.o | 汇编后的可重定位目标程序 |
hello | 链接后的可执行目标文件 |
hello_elf.txt | hello.o的ELF格式 |
hello_disassemble.s | 目标文件hello.o文件反汇编得到的结果 |
helloELF1.txt | 可执行文件hello的ELF格式 |
hello2.txt | 对hello反汇编的结果,与hello.o进行对比 |
(附件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分)