计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 计算机
学 号 1190200903
班 级 1903012
学 生 *彤
指 导 教 师 史先俊
计算机科学与技术学院
2021年5月
摘 要
本文用计算机系统的术语,讲述一个简单的程序hello.c从头开始,经历了预处理,编译,汇编,链接,hello的进程管理……等过程,描述了一个c语言程序从创建到结束的整个历程。通过阅读本文,可以对这学期所学的csapp知识进行描述与总结。
关键词:hello的一生;深入理解计算机系统
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
第1章 概述
1.1 Hello简介
P2P:From Program to Process
Hello.c经c预处理器转化为hello.i,然后经c编译器转化为hello.s,再经汇编器转化为hello.o,最后链接器将该文件与头文件中所需的库函数链接形成可执行elf文件。在命令行中输入./hello,Shell解析命令为外部命令,调用fork函数创建一个子进程,由此将program变成一个progress。调用execve函数执行程序、加载进程,sbrk函数开辟内存空间,mmap将其映射到内存中,这就是P2P的过程。
O2O:From Zero-0 to Zero -0
CPU通过取指、译码、执行、访存、写回、更新PC等执行hello程序。首先需要将虚拟地址转换为物理地址,通过TLB和页表加速地址的翻译,高速缓存cache加快数据的传输。IO管理和Shell的进程处理和信号处理机制可以处理hello运行过程中的信号。当进程结束时,shell会回收hello进程,并且内核会从系统中删除hello所有痕迹。这就是O2O的过程。
1.2 环境与工具
硬件:Inter core i5,X64 CPU,8GRAM
软件:Windows10,VMware 15,Ubuntu 18.04
调试工具: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章 预处理
2.1 预处理的概念与作用
概念:由预处理程序(cpp)负责完成,对一个源程序进行初步处理,例如将hello.c转化为hello.i。
作用:将源文件中以“include”格式包含的文件复制到编译的源文件中,用实际值替换用“#define”定义的字符串。根据“#if”后面的条件决定需要编译的代码。
2.2在Ubuntu下预处理的命令
2.3 Hello的预处理结果解析
对比两个文件可以发现,源文件经过预处理后,由23行扩展为了3000多行,main函数被放在了最后,前面是大量的递归展开的头文件。
2.4 本章小结
第二章主要介绍了预处理的概念及作用,以及在Ubuntu下进行预处理的实操。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
概念:将经过预处理的文件进行语法分析和优化,转化为相应的汇编文件。编译通常分五个阶段,即词法分析、语法分析、语义检查和中间代码生成、代码优化、目标代码生成。
作用:将高级语言转化为机器更容易理解的汇编语言。
3.2 在Ubuntu下编译的命令
3.3 Hello的编译结果解析
3.3.1
观察.s文件,发现有许多.cfi开头的命令。即Call Frame infromation。
例如,.cfi_startproc 用在每个函数的开始,用于初始化一些内部数据结构;.cfi_endproc 在函数结束的时候使用与.cfi_startproc相配套使用。
3.3.2main函数中的参数
main函数中有两个参数argc,argv。他们都存在栈上。可以看到argc存放在%rbp-20的位置。argv存放在%rbp-32的位置。
3.3.3局部变量
main函数里声明了一个整型局部变量i,在编译过程中,局部变量存储在堆栈上。
图为将i初始化为0。
-4(%rbp)表明int型变量占四个字节。
3.3.4for循环
上面对i初始化之后,跳转到L3,L3为for循环的条件判断部分。采用cmp命令判断条件是否满足。若满足,跳转到L4,即for的循环体部分。
3.3.5判断结构
判断argc是否等于4。若相等,跳转到L2。若不等,执行if中的语句。
3.3.6只读字符串
对应hello.c中的两个printf语句。
3.4 本章小结
本章主要介绍了编译器在将hello.i文件转化为hello.s文件的过程。将hello.c中的变量,数据,操作转化成了汇编语言。通过阅读汇编语言可以对应到c语言中的变量,数据,操作等。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
概念:汇编器将汇编语言翻译成机器语言。
作用:将.s文件转化为.o文件。
4.2 在Ubuntu下汇编的命令
4.3 可重定位目标elf格式
elf格式:
Elf头 |
.text |
.rodata |
.data |
.bss |
.symtab |
.rel.text |
.rel.data |
.debug |
.line |
.strtab |
节头表 |
描述了生成该文件的系统的字的大小和字节顺序,还有帮助链接器语法分析和解释目标文件的信息。
main定义和引用的符号的信息
4.4 Hello.o的结果解析
objdump -d -r hello.o结果:
与hello.s的比较:
(1)地址不同。只读数据中,反汇编得到的结果是用$0x0代替。这是因为将来需要重定位,我们目前无法判断其将来的具体地址。除此之外,反汇编代码还在这行代码下方标注了一些重定位信息。
(2)跳转方式不同。.s在调用函数时是直接用 call+函数名的方式。而.o文件得到的反汇编代码中是在call之后用的对应命令在main函数中的偏移量,而不是具体的函数名。原因也是将来需要重定位,目前不知道具体的地址。
4.5 本章小结
在本章中,文件发生了质的变化。从文本文件变成了二进制文件。汇编器将之前得到的hello.s转化成了机器语言,更加接近了程序的“真面目”。我们使用的高级语言的各种数据、操作、功能都被转化为了一系列0,1字符串。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
概念:链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可以被加载到内存并执行。链接可以执行于编译时,也就是在源代码被编译成机器代码时;也可以执行于加载时,也就是在程序被加载器加载到内存并执行时;甚至于运行时,也就是由应用程序来执行。链接执行符号解析、重定位过程。
作用:把可重定位目标文件和命令行参数作为输入,产生一个完全链接的,可以加载运行的可执行目标文件。使得分离编译成为可能。
5.2 在Ubuntu下链接的命令
使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件
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的格式
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
节头部表:
符号表信息:
5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息。虚拟空间从0x400000开始。
.interp段,可以看出该段内保存有动态链接库的位置。
.text段地址从0x4004d0,偏移量为0x4d0,大小为0x122,对齐要求为16,故而.text段是从地址0x4004d0开始,查看0x4004d0处的值,与反汇编代码中拥有的机器码比较发现是一致的。
.rodata段地址从0x400460,偏移量为0x460,大小为0x2f,对齐要求为4,故而.rodata段是从地址0x400460开始,查看0x400460处的值。
5.5 链接的重定位过程分析
objdump -d -r hello > hello2.txt
(1)hello.o反汇编代码虚拟地址从0开始,而hello反汇编代码从0x400000开始。
(2)hello.o反汇编代码就直接是.text段,然后为main函数。而hello反汇编的结果中,由于链接过程中重定位而加入进来各种函数、数据。如开始的函数和调用的函数填充在main函数之前。所以main函数的位置发生了巨大的改变。
hello反汇编代码
5.6 hello的执行流程
函数调用如下表格所示:
名称 |
地址 |
ld-2.23.so!_dl_start |
0x00007f8dec5b79b0 |
ld-2.27.so! dl_init |
0x00007f8dec5c6740 |
hello!_start |
0x004004d0 |
ld-2.27.so!_libc_start_main |
0x00400480 |
libc-2.27.so! cxa_atexit |
0x00007f8dec226280 |
hello!_libc_csu_init |
0x00400580 |
hello!_init |
0x00400430 |
libc-2.27.so!_setjmp |
0x00007f8dec221250 |
libc-2.27.so!_sigsetjmp |
0x00007f8dec221240 |
libc-2.27.so!__sigjmp_save |
0x00007fa8dec221210 |
hello_main |
0x004004fa |
hello!puts@plt |
0x00400460 |
hello!exit@plt |
0x004004a0 |
hello!printf@plt |
0x00400470 |
hello!sleep@plt |
0x004004b0 |
hello!getchar@plt |
0x00400490 |
ld-2.23.so!_dl_runtime_resolve_avx |
0x00007f8dec5cd870 |
libc-2.27.so!exit |
0c00007f6002de35b0 |
5.7 Hello的动态链接分析
动态链接项目中,查看dl_init前后项目变化。对于动态共享链接库中PIC函数,编译器加重定位记录,等待动态链接器处理,为避免运行时修改调用模块的代码段,链接器采用延迟绑定的策略,将过程地址的绑定推迟到第一次调用该过程。动态链接器使用过程链接表PLT+全局偏移量表GOT实现函数的动态链接,GOT中存放函数目标地址,PLT使用GOT中地址跳转到目标函数。
在dl_init调用之前,对于每一条PIC函数调用,调用的目标地址都实际指向PLT中的代码逻辑,初始时每个GOT条目都指向对应的PLT条目的第二条指令。
在dl_init调用之后, 0x6008c0和0x6008c0处的两个8字节的数据分别发生改变。
和PLT联合使用时,GOT[0]和GOT[1]包含动态链接器在解析函数地址时会使用的信息。其中GOT[1]指向重定位表(依次为.plt节需要重定位的函数的运行时地址)用来确定调用的函数地址, GOT[2]是动态链接器ld-linux.so模块中的入口点。
在之后的函数调用时,首先跳转到PLT执行.plt中逻辑,第一次访问时,GOT地址为下一条指令,将函数序号压栈,然后跳转到PLT[0],在PLT[0]中将重定位表地址压栈,然后访问动态链接器,在动态链接器中使用函数序号和重定位表确定函数运行时地址,重写GOT,再将控制传递给目标函数。之后如果对同样函数调用,第一次访问跳转直接跳转到目标函数。
5.8 本章小结
本阶段完成了对hello.o的链接工作。使用Ubuntu下的链接指令可以将其转换为可执行目标文件,其中用到了rodata中的重定位条目,最终分析了程序如何实现的动态库链接。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
概念:
进程的经典定义是一个执行中程序的实例,系统的每个程序都运行在某个进程的上下文。上下文是由程序正确运行所需的状态组成的,这个状态包括存放在内存里的程序的代码和数据,它的栈,通用目的寄存器的内容,程序计数器,环境变量以及打开文件描述符的集合。
作用:
通过进程,我们会得到一种假象,好像我们的程序是当前唯一运行的程序,我们的程序独占处理器和内存,我们程序的代码和数据好像是系统内存中唯一的对象。
6.2 简述壳Shell-bash的作用与处理流程
在计算机科学中,Shell俗称壳(用来区别于核),是指“为使用者提供操作界面”的软件(命令解析器)。它类似于DOS下的command.com和后来的cmd.exe。它接收用户命令,然后调用相应的应用程序。同时它又是一种程序设计语言。作为命令语言,它交互式解释和执行用户输入的命令或者自动地解释和执行预先设定好的一连串的命令;作为程序设计语言,它定义了各种变量和参数,并提供了许多在高级语言中才具有的控制结构,包括循环和分支。
Bash是一个命令处理器,通常运行于文本窗口中,并能执行用户直接输入的命令。Bash还能从文件中读取命令,这样的文件称为脚本。和其他Unix shell 一样,它支持文件名替换(通配符匹配)、管道、here文档、命令替换、变量,以及条件判断和循环遍历的结构控制语句。包括关键字、语法在内的基本特性全部是从sh借鉴过来的。其他特性,例如历史命令,是从csh和ksh借鉴而来。总的来说,Bash虽然是一个满足POSIX规范的shell,但有很多扩展。
处理流程:
第一步:用户输入命令。
第二步:shell对用户输入命令进行解析,判断是否为内置命令。
第三步:若为内置命令,调用内置命令处理函数,否则调用execve函数创建一个子进程进行运行。
第四步:判断是否为前台运行程序,如果是,则调用等待函数等待前台作业结束;否则将程序转入后台,直接开始下一次用户输入命令。
第五步:shell应该接受键盘输入信号,并对这些信号进行相应处理
6.3 Hello的fork进程创建过程
我们在shell上输入./hello,这个不是一个内置的shell命令,所以shell会认为hello是一个可执行目标文件,通过调用某个驻留在存储器中被称为加载器的操作系统代码来运行它。
当shell运行一个程序时,父进程通过fork函数生成这个程序的进程。新创建的子进程几乎但不完全与父进程相同,包括代码、数据段、堆、共享库以及用户栈。父进程和新创建的子进程之间最大的区别在于他们有不同的PID。
6.4 Hello的execve过程
execve函数在当前进程的上下文中加载并运行一个新程序
execve函数加载并运行可执行文件filename(hello),且带参数列表argv和环境变量envp。只有当出现错误时,例如找不到filename,execve才会返回到调用程序。
当加载器运行时,它创建一个类似与图 6-2 的内存映像。在程序头部表的引导下,加载器将可执行文件的片复制到代码段和数据段,接下来,加载器跳转到程序的入口,_start函数的地址,这个函数是在系统目标文件ctrl.o中定义的,对所有的c程序都一样。_start函数调用系统启动函数,_libc_start_main,该函数定义在libc.so里,初始化环境,调用用户层的main函数,处理main函数返回值,并且在需要的时候返回给内核。
6.5 Hello的进程执行
多个流并发地执行的一般现象被称为并发。一个进程和其他进轮流运行的概念称为多任务。一个进程执行它的控制流的一部分的每一时间段叫做时间片。因此,多任务也叫做时间分片。
操作系统内核使用一种称为上下文切换的较高层形式的异常控制流来实现多任务。内核为每个进程维持一个上下文。上下文就是内核重启一个被抢占的进程所需得状态。
在执行过程中,内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程,这个决策称为调度。
hello程序与操作系统其他进程通过操作系统的调度,切换上下文,拥有各自的时间片从而实现并发运行。所以其实hello在sleep时就是这样的切换。
程序在进行一些操作时会发生内核与用户状态的不断转换。这是为了保持在适当的时候有足够的权限和不容易出现安全问题。
简单看hello sleep进程调度的过程:当调用sleep之前,如果hello程序不被抢占则顺序执行,假如发生被抢占的情况,则进行上下文切换,上下文切换是由内核中调度器完成的,当内核调度新的进程运行后,它就会抢占当前进程,并进行1)保存以前进程的上下文2)恢复新恢复进程被保存的上下文,3)将控制传递给这个新恢复的进程,来完成上下文切换。
hello初始运行在用户模式,在hello进程调用sleep之后陷入内核模式,内核处理休眠请求主动释放当前进程,并将hello进程从运行队列中移出加入等待队列,定时器开始计时,内核进行上下文切换将当前进程的控制权交给其他进程,当定时器到时时(2secs)发送一个中断信号,此时进入内核状态执行中断处理,将hello进程从等待队列中移出重新加入到运行队列,成为就绪状态,hello进程就可以继续进行自己的控制逻辑流了。
当hello调用getchar的时候,实际落脚到执行输入流是stdin的系统调用read,hello之前运行在用户模式,在进行read调用之后陷入内核,内核中的陷阱处理程序请求来自键盘缓冲区的DMA传输,并且安排在完成从键盘缓冲区到内存的数据传输后,中断处理器。此时进入内核模式,内核执行上下文切换,切换到其他进程。当完成键盘缓冲区到内存的数据传输时,引发一个中断信号,此时内核从其他进程进行上下文切换回hello进程。
6.6 hello的异常与信号处理
不停乱按,包括回车
Ctrl-Z
Ctrl-C
Ctrl-z后可以运行ps jobs pstree fg kill
6.7本章小结
本章介绍了进程的概念和作用,描述了shell如何在用户和系统内核之间建起一个交互的桥梁。讲述了shell的基本操作以及各种内核信号和命令,还总结了shell是如何fork新建子进程、execve如何执行进程、hello进程的上下文切换。
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:程序代码经过编译后出现在 汇编程序中地址。逻辑地址由选择符 (在实模式下是描述符,在保护模式下是用来选择描述符的选择符)和偏移量(偏 移部分)组成。
线性地址:逻辑地址经过段机制后转化为线性地址,为描述符:偏移量的组合 形式。分页机制中线性地址作为输入
虚拟地址:使用虚拟地址技术为进程分配的虚拟地址空间中的地址,可以在进程的页表中翻译成物理地址。
物理地址: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动态存储分配管理
动态内存管理一般由动态内存分配器维护,分配器有两种风格,显示和隐式,两者的区别在于前者要求应用显式的释放任何已分配块,而后者则自动检测一个已分配块何时不再被程序使用。不过两者都要求显式地分配空闲块。
动态内存要处理好空闲块与已分配块的区分,空闲块的管理等问题。所以通过将一些信息如该块的大小,该块是否已经被分配等等嵌入到块中。通过这种处理得到的结构被称为隐式空闲链表。
由于隐式空闲链表的某些缺点,动态内存分配器还通过显式空闲链表这种结构来管理空闲块。显式空闲链表在隐式的基础上增加了指向前驱节点和后继节点的指针,当然,其前驱和后记也是空闲块。内存分配器在malloc时需要寻找合适的空闲块,这时就有不同的策略。可以有首次适配,即从头开始搜索,选择第一次遇到的合适的块;下一次适配,略有不同,这种方法从上一次查询结束的地方开始搜索,而不是每次都从头开始搜索。
除此之外,对空闲块的搜索还包括最佳适配,还可以配合显式空闲链表实现LIFO(后进先出)的策略。
在内存分配器释放已分配块的同时,还要根据不同的空闲块的相对位置确定空闲块之间要如何合并。
7.10本章小结
本章回顾了 hello 的存储器地址空间、intel 的段式管理、hello 的页式管理,介绍了VA 到PA 的变换、物理内存访问,还介绍了hello 进程 fork 时的内存映射、execve 时的内存映射、缺页故障与缺页中断处理、动态 存储分配管理。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件
设备管理:unix io接口
8.2 简述Unix IO接口及其函数
Unix I/O接口:
1)打开文件。一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个I/O设备,内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件,内核记录有关这个打开文件的所有信息。
2)Shell创建的每个进程都有三个打开的文件:标准输入,标准输出,标准错误。
3)改变当前的文件位置:对于每个打开的文件,内核保持着一个文件位置k,初始为0,这个文件位置是从文件开头起始的字节偏移量,应用程序能够通过执行 seek,显式地将改变当前文件位置 k。
4)读写文件:一个读操作就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增加到k+n,给定一个大小为m字节的而文件,当k>=m时,触发EOF。
5)关闭文件,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中去。
Unix I/O 函数:
1.open()函数
功能描述:用于打开或创建文件,在打开或创建文件时可以指定文件的属性及用户的权限等各种参数。
函数原型:int open(const char *pathname,int flags,int perms)
参数:pathname:被打开的文件名flags:文件打开方式,
返回值:成功:返回文件描述符;失败:返回-1
2.close()函数
功能描述:用于关闭一个被打开的的文件
所需头文件: #include
函数原型:int close(int fd)
参数:fd文件描述符
函数返回值:0成功,-1出错
3.read()函数
功能描述: 从文件读取数据。
所需头文件: #include
函数原型:ssize_t read(int fd, void *buf, size_t count);
参数:fd:将要读取数据的文件描述词。buf:指缓冲区,即读取的数据会被放到这个缓冲区中去。count: 表示调用一次read操作,应该读多少数量的字符。
返回值:返回所读取的字节数;0(读到EOF);-1(出错)。
4.write()函数
功能描述: 向文件写入数据。
所需头文件: #include
函数原型:ssize_t write(int fd, void *buf, size_t count);
返回值:写入文件的字节数(成功);-1(出错)
5.lseek()函数
功能描述: 用于在指定的文件描述符中将将文件指针定位到相应位置。
所需头文件:#include ,#include
函数原型: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.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量),实现printf格式化输出。
8.4 getchar的实现分析
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章介绍了UNIX I/O设备的管理方法、UNIX I/O的接口函数。并分析的printf函数和getchar函数的实现。
(第8章1分)
结论
用计算机系统的语言,逐条总结hello所经历的过程。
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
1.编写程序:用高级语言C语言写成hello.c文件。
2.预处理:将hello.c中用到的所有外部的库(通过引用头文件实现)展开合并到一个 hello.i文件中。
3.编译:完成对hello.i的进一步处理,使之变为更接近机器语言的hello.s形式。
4.汇编:将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 父进程回收子进程,内核删除为这个进程创建的所有数据结构。
深切感悟:计算机系统这门课是我本学期最头疼的一门课。从开始的实验环境安装与配置,到后来的各个实验,对于完全不了解linux系统的我来说实在难度很大。不过好在紧跟着老师的脚步,多次试图深入理解ppt和课本,终于也有了不小的收获。虽然过程艰难,但最终也完成了各个实验。虽说书本晦涩难懂,但最终也学到了不少硬件、软件、计算机底层原理相关的知识。这门课难度较大,但重要的是使我受益匪浅。如同为我打开了一扇窗户,看到了一个新的世界。除此之外,这门课对于我以后的知识学习一定也会有深远的影响。
(结论0分,缺失 -1分,根据内容酌情加分)
附件
列出所有的中间产物的文件名,并予以说明起作用。
hello.c C语言源文件
hello.i 经过预处理得到的C语言文件
hello.s 经过编译得到的汇编语言文件
hello.o 经过汇编得到的机器语言文件
hello 经过链接得到的可执行文件
hello1.txt 对hello.o反汇编的结果,分析重定位作用和相关信息。
hello2.txt 对hello反汇编的结果,与hello.o进行对比。
(附件0分,缺失 -1分)
参考文献
为完成本次大作业你翻阅的书籍与网站等
1. 深入理解计算机系统第三版
2.https://www.cnblogs.com/pianist/p/3315801.html(介绍printf的实现和用法)
3.http://sourceware.org/binutils/docs/as/CFI-directives.html(介绍编译中用到的.cfi命令)
(参考文献0分,缺失 -1分)