第1章 概述
1.1 Hello简介
P2P过程:
1.编写与存储:程序员使用文本编辑器编写hello.c文件,即源代码。hello.c文件存储在硬盘(或其他存储设备)上,等待进一步的处理。
2.编译与链接:使用编译器(如GCC)对hello.c进行编译,经过预处理、编译、汇编等步骤,生成目标文件.o文件。链接器将目标文件与必要的库文件链接在一起,生成可执行文件。
3.加载与执行:
当用户在终端中输入./hello命令时,Shell会加载并执行这个程序。
操作系统(OS)为hello程序创建一个新的进程,分配必要的资源,如内存、CPU时间片等。使用fork系统调用创建新进程,并使用execve系统调用来替换新进程的内存映像为hello程序的代码和数据。由内存管理单元(MMU)和虚拟内存机制(如页表、TLB等)来管理进程的地址空间,实现虚拟地址(VA)到物理地址(PA)的映射。
进程开始执行,CPU从内存中取指令、译码、执行,并在流水线上处理。
O2O过程:
1.起始为零:程序的起始状态是源代码文件hello.c,这个文件在硬盘上占据一定的存储空间,但在内存中并没有对应的表示。
2.执行过程:程序经过编译、链接后,生成可执行文件hello,这个文件被加载到内存中,并由CPU执行。在执行过程中,程序与操作系统、硬件设备进行交互,通过IO操作(如键盘输入、屏幕输出)与用户进行交互。
3.结束为零:
程序执行完毕后,其占用的内存空间被操作系统回收,进程被销毁。程序的执行结果(如果有的话)被输出到屏幕或其他设备上,但程序本身在内存中不再存在。从这个意义上说,程序从“零”开始,经过一系列的处理和执行,最终又回到了“零”的状态。
这个过程中,计算机系统的各个组件都发挥了重要的作用,共同协作完成了从0到0的转变,以及程序的执行和结束过程。
1.2 环境与工具
硬件环境:X64 CPU;2GHz;2G RAM;256GHD Disk 以上
软件环境:Windows7 64位以上;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS
64位/优麒麟 64位
开发工具:g++,gdb,objdump,edb
1.3 中间结果
Hello.i Hello.c预处理得到的.i文件
Hello.s Hello.i编译后得到的.s文件
Hello.o Hello.s汇编后得到的.o文件
Hello Hello.o经链接后得到的可执行文件
1.4 本章小结
本章主要介绍了Hello.c文件运行的全过程,从宏观上叙述了各个阶段的流程,列举了中间文件并介绍了实验的主要环境。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
概念:预处理是进行源文件编译的第一个阶段,预处理不对源文件进行分析,而是对源文件进行文本操作,如删除源文件中的注释,在源文件中插入包含文件的内容(#include),定义符号并替换源文件中的符号等(#define),通过这些处理,将会得到编译器实际进行分析的文本。
作用:预处理能够对源程序中的特定指令进行处理,如宏定义、文件包含、条件编译等。通过#define指令可以定义宏,实现代码的复用和简化,通过#include指令可以包含其他源文件,实现代码的模块化,通过条件编译指令可以根据条件选择性地编译代码。
2.2在Ubuntu下预处理的命令
通过cpp调用预处理器:cpp hello.c hello.i
如图即得到了预处理后的hello.i文件。
2.3 Hello的预处理结果解析
如图为hello.c源代码,下面我们将分析其与hello.i的异同:
不同之处:
- hello.i中注释不再存在。可以发现hello.c中是存在注释的,但在hello.i中注释已经被去除。
- main之前的代码添加了三个头文件“stdio.h”、“unistd.h”、“stdlib.h”的内容。其中还包括了#include插入的一系列运行库的路径,如图:
代码有一些对结构体变量的定义,以及外部函数的调用等:
相同之处:
- 可以发现主函数在hello.i文件的末尾,且与hello.c中的主函数基本保持一致。下图即为hello.i中的主函数片段。
2.4 本章小结
本章简要介绍了预处理的概念及其作用,并在ubuntu中通过cpp调用预处理器:cpp hello.c hello.i进行了hello预处理的演示与预处理结果的解析。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
概念:把源程序转换为目标程序的过程
作用:
1.代码转换:编译器的首要任务是将人类可读的源代码转换为计算机可执行的机器代码。这个过程涉及词法分析、语法分析、语义分析、中间代码生成、代码优化和目标代码生成等多个阶段。
2.错误检查:编译器在转换代码的过程中会进行错误检查,包括语法错误、类型错误、未定义的变量或函数等。这些错误会在编译阶段被捕获并报告给程序员,从而避免在运行时出现不可预测的错误。
3.性能优化:编译器可以对源代码进行优化,以提高生成代码的执行效率,包括删除无用的代码、减少循环次数、改进数据访问模式等。
3.2 在Ubuntu下编译的命令
编译命令为:gcc -S hello.i -o hello.s
编译结果如下图所示,即为hello.s
3.3 Hello的编译结果解析
首先分析编译器对于数据类型的处理:
3.3.1数据
1.字符串
2.局部变量
这里局部变量i被储存在-4(%rbp)中。
3.常数
在程序的中常数以$的形式显式的写出,如$0。
3.3.2赋值
此程序中的赋值主要是对计数器i的赋值,仍以下图为例,这里是将0赋值给i。
3.3.3类型转换
这里出现了类型转换,是以调用函数的方式实现,即atoi。
3.3.4数组操作
上述代码是汇编语言中非常普遍的数组操作:首先在这里定义了数组argv[]的首地址为-32(%rbp),将地址传递给寄存器%rax后,通过%rax来实现对数组元素的访问。
3.3.5算数操作
在上图中的addq即为加法的算术操作。
3.3.6关系操作
例如此处i与9的比较:
在汇编语言中一般通过cmpl来实现比较。
3.3.7控制转移
控制转移与关系操作密切相连,往往以关系操作+控制转移的形式出现。控制转移由cmpl和条件码来实现,je代表在相等时执行跳转;jle,代表在小于等于时执行跳转。
3.4 本章小结
本章主要讲述了编译的概念与作用,介绍了编译命令,以及c语言中各种类型和操作所对应的的汇编代码。
第4章 汇编
4.1 汇编的概念与作用
概念:汇编语言翻译成机器语言的过程
作用:将人类可读的汇编指令转换为机器可以执行的二进制代码,确保了源代码可以被正确地转换为可以在特定机器上运行的程序。
4.2 在Ubuntu下汇编的命令
汇编的命令为as hello.s -o hello.o,执行如下,得到hello.o文件:
4.3 可重定位目标elf格式
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
利用readelf -a hello.o查看ELF格式:
- ELF头
2.节头
上面的节头表展示了每一节的名字/大小,类型/全体大小,地址/标志/链接/信息,偏移量/对齐方式。
3.动态节/程序头/节组
如上图所示,该文件中没有节组/程序头/动态节。
4.重定位节
如上图所示:共有8个条目,包含了重定位节的偏移量/信息/类型/符号值/符号名称与加数等信息,其中R_X86_64_PC32指静态链接内容,而R_X86_64_PLT_32则为动态链接内容。
这里的.rela.eh_frame指异常处理框架,是与重定位相关的一个异常处理单元。
5.符号表
4.4 Hello.o的结果解析
(以下格式自行编排,编辑时删除)
用objdump -d -r hello.o 查看hello.o的反汇编:
Hello.s如下进行对比:
发现通过比较反汇编的代码和hello.s,二者在语法上没有过多区别,但反汇编代码除了显示汇编代码以外,还会显示机器代码。
机器指令由操作码和操作数构成,而汇编语言是人们比较熟悉的词句直接表述CPU动作形成的语言。每一条汇编语言操作码都可以用机器二进制数据来表示,进而可以将所有的汇编语言(操作码和操作数)和二进制机器语言建立一一映射的关系,因此可以将汇编语言转化为机器语言。
1.分支转移:在分支转移中,跳转指令中直接使用段名称作为跳转对象,而在反汇编代码中可以看到跳转指令中直接引入了目标语句的地址。
2.函数调用:在汇编语句中,call指令调用的对象即调用函数的函数名,而在机器指令中,作为被计算机系统识别的语句,call指令的调用对象位于当前语句的下一条指令。这是因为 hello.c 中调用的函数都是共享库中的函数,最终需要通过动态链接器才能确定函数的运行时执行地址,在汇编成为机器语言的时候,对于这些不确定地址的函数调用,将其call指令后的相对地址设置为全0(目标地址正是下一条指令),然后在.rela.text节中为其添加重定位条目,等待静态链接的进一步确定。
4.5 本章小结
本章介绍了汇编的概念与作用,探索了一些汇编命令与elf格式。
第5章 链接
5.1 链接的概念与作用
概念:链接是将编译生成的目标文件组合成一个可执行文件的过程
作用:给目标文件中相互调用的符号生成正确的地址,再生成正确的可执行文件。
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.ohello.o/usr/lib/x86_64-linux-gnu/libc.so/usr/lib/x86_64-linux-gnu/crtn.o
执行结果如下:
5.3 可执行目标文件hello的格式
利用readelf -a hello.o查看ELF格式:
1.ELF头
2.节头
上面的节头表展示了每一节的名字/大小,类型/全体大小,地址/标志/链接/信息,偏移量/对齐方式。
3.节组
如上图所示,该文件中没有节组。
4.程序头
对比readelf的结果,发现elf头一致。
第二项是可执行的区域,地址与edb中的汇编代码地址一致
第三项是只读数据域,包括了存储的字符串等数据:
5.4 hello的虚拟地址空间
如图用edb加载hello
利用edb的memory regions选项查看内存区域:
发现第一项是只读的,考虑查看0x400000地址,发现elf头,其结果与readelf的结果一致:
5.5 链接的重定位过程分析
Hello.s如下进行对比:
发现反汇编代码中多产生了一些节,这些节是经过链接之后加入进来的。
重定位的过程具体分为两步:
1.重定位节和符号定义:在这一步中,连接器将所有相同类型的节合并成为同一类型的新的聚合节。例如,来自所有输入模块的.data节全部被合并成一个节,这个节成为输出的可执行目标文件的.data节。
2.重定位节中的符号引用:在这一步中,连接器修改代码节和数据节中对每个符号的引用,使得他们指向正确的运行时地址。
5.6 hello的执行流程
执行的子函数如下:
1.init
2.plt
3.puts@plt
4.printf@plt
5.getchar@plt
6.atoi@plt
7.exit@plt
8.sleep@plt
9._start
10.main
11._fini
5.7 Hello的动态链接分析
对于动态共享链接库中PIC函数,编译器没有办法预测函数的运行时地址,所以需要添加重定位记录,等待动态链接器处理,为避免运行时修改调用模块的代码段,链接器采用延迟绑定的策略。动态链接器使用过程链接表PLT+全局偏移量表GOT实现函数的动态链接,GOT中存放函数目标地址,PLT使用GOT中地址跳转到目标函数。
GOT的存放地址如下:
执行dl_init前的.got.plt节:
执行后这里发生了变化:
5.8 本章小结
本章介绍了链接的概念和作用,分析了链接后生成的可执行文件hello的elf格式文件,探究了hello的虚拟地址空间、重定位过程、执行过程的各种处理操作,对hello进行了动态链接分析。
第6章 hello进程管理
6.1 进程的概念与作用
概念:一个正在运行的程序或者软件
作用:
1.进程为用户提供了以下假象:
我们的程序好像是系统中当前运行的唯一程序一样,我们的程序好像是独占的使用处理器和内存,处理器好像是无间断的执行
我们程序中的指令,我们程序中的代码和数据好像是系统内存中唯一的对象。
2.方便shell程序的构造
每次用户通过向shell 输入一个可执行目标文件的名字,运行程序时, shell
就会创建一个新的进程,然后在这个新进程的上下文中运行这个可执行目标文件。应用程序也能够创建新进程,并且在这个新进程的上下文中运行它们自己的代码或其他应用程序。
3.两个关键抽象
进程提供给应用程序两个关键抽象:一个独立的逻辑控制流;一个私有的地址空间。
6.2 简述壳Shell-bash的作用与处理流程
作用:shell的作用是将我们的指令翻译给OS内核,让内核来进行处理,并把处理的结果反馈给用户。
处理流程:shell首先检查命令是否是内部命令,若不是再检查是否是一个应用程序。然后shell在搜索路径里寻找这些应用程序(搜索路径就是一个能找到可执行程序的目录列表)。如果键入的命令不是一个内部命令并且在路径里没有找到这个可执行文件,将会显示一条错误信息。如果能够成功找到命令,该内部命令或应用程序将被分解为系统调用并传给Linux内核。
6.3 Hello的fork进程创建过程
终端程序通过调用fork()函数创建一个子进程。
子进程得到与父进程完全相同但是独立的一个副本,包括代码段、段、数据段、共享库以及用户栈。子进程还获得与父进程任何打开文件描述符相同的副本,父进程和子进程最大的不同时他们的PID是不同的。父进程与子进程是并发运行的独立进程,内核能够以任意方式交替执行它们的逻辑控制流的指令。在子进程执行期间,父进程默认选项是显示等待子进程的完成。
以hello为例,在输入指令./hello 2022111266许景波 18358928859后,shell会对输入的命令进行解析,显然,我们输入的命令不是内置的shell命令,于是shell会调用fork()创建一个子进程。
6.4 Hello的execve过程
execve函数用于装载一个可执行文件以进程为单位加载到内存中,
6.5 Hello的进程执行
当创建了一个子进程之后,子进程调用exceve函数在当前子进程的上下文加载并运行一个新的程序即hello程序,加载并运行需要以下几个步骤:
1.删除已存在的用户区域。删除当前进程虚拟地址的用户部分中已存在的区域结构。
2.映射私有区域。为新程序的代码、数据、bss和栈区域创建新的区域结构。所有这些区域结构都是私有的,写时复制的。虚拟地址空间的代码和数据区域被映射为hello文件的.txt和.data区。bss区域是请求二进制零的,映射匿名文件,其大小包含在hello文件中。栈和堆区域也是请求二进制零的,初始长度为零。
3.映射共享区域。如果hello程序与共享对象链接,比如标准C库libc.so,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域。
4.设置程序计数器(PC)。exceve做的最后一件事就是设置当前进程的上下文中的程序计数器,使之指向代码区域的入口点。下一次调用这个进程时,它将从这个入口点开始执行。Linux将根据需要换入代码和数据页面。
除了一些头部信息,在加载过程中没有任何从磁盘到内存的数据复制。直到 CPU 引用一个被映射的虚拟页时才会进行复制,这时,操作系统利用它的页面调度。
6.6 hello的异常与信号处理
hello运行分析
1. 正常运行
2.Ctrl+C
发送SIGINT信号,向子进程发送SIGKILL信号使进程终止并回收
3.Ctrl+Z
产生SIGTSTP信号,使子进程被挂起
4.Ctrl+Z后ps:会显示所有的进程及其状态
5.jobs:可以显示暂停的进程
6.pstree显示全部进程的进程树
- fg:使第一个后台作业(hello)变成前台作业
8.kill
杀死进程前:
现在杀死进程
杀死进程后再看jobs:
6.7本章小结
本章阐述进程的定义与作用,同时介绍了Shell的一般处理流程和作用,并且分析了调用 fork 创建新进程,调用 execve函数 执行 hello,hello的进程执以及hello 的异常与信号处理。
第7章 hello的存储管理
7.1 hello的存储器地址空间
[1]逻辑地址(Logical Address) 是指由程式产生的和段相关的偏移地址部分。例如,你在进行C语言指针编程中,能读取指针变量本身值(&操作),实际上这个值就是逻辑地址,他是相对于你当前进程数据段的地址,不和绝对物理地址相干。只有在Intel实模式下,逻辑地址才和物理地址相等(因为实模式没有分段或分页机制,Cpu不进行自动地址转换);逻辑也就是在Intel保护模式下程式执行代码段限长内的偏移地址(假定代码段、数据段如果完全相同)。应用程式员仅需和逻辑地址打交道,而分段和分页机制对你来说是完全透明的,仅由系统编程人员涉及。应用程式员虽然自己能直接操作内存,那也只能在操作系统给你分配的内存段操作。
线性地址(Linear Address) 是逻辑地址到物理地址变换之间的中间层。程式代码会产生逻辑地址,或说是段中的偏移地址,加上相应段的基地址就生成了一个线性地址。如果启用了分页机制,那么线性地址能再经变换以产生一个物理地址。若没有启用分页机制,那么线性地址直接就是物理地址。Intel 80386的线性地址空间容量为4G(2的32次方即32根地址总线寻址)。
物理地址(Physical Address) 是指出目前CPU外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果地址。如果启用了分页机制,那么线性地址会使用页目录和页表中的项变换成物理地址。如果没有启用分页机制,那么线性地址就直接成为物理地址了。
虚拟内存(Virtual Memory)是指计算机呈现出要比实际拥有的内存大得多的内存量。因此他允许程式员编制并运行比实际系统拥有的内存大得多的程式。这使得许多大型项目也能够在具有有限内存资源的系统上实现。在Linux0.11内核中,给每个程式(进程)都划分了总容量为64MB的虚拟内存空间。因此程式的逻辑地址范围是0x0000000到0x4000000。有时我们也把逻辑地址称为 虚拟地址。因为和虚拟内存空间的概念类似,逻辑地址也是和实际物理内存容量无关的。逻辑地址和物理地址的“差距”是0xC0000000,是由于虚拟地址->线性地址->物理地址映射正好差这个值。这个值是由操作系统指定的。机理 逻辑地址(或称为虚拟地址)到线性地址是由CPU的段机制自动转换的。如果没有开启分页管理,则线性地址就是物理地址。如果开启了分页管理,那么系统程式需要参和线性地址到物理地址的转换过程。具体是通过设置页目录表和页表项进行的。
7.2 Intel逻辑地址到线性地址的变换-段式管理
段式管理是把一个程序分成若干个段进行存储,每个段都是一个逻辑实体,程序员需要知道并使用它。它的产生是与程序的模块化直接有关的。段式管理是通过段表进行的,它包括段号或段名、段起点、装入位、段的长度等。此外还需要主存占用区域表、主存可用区域表。
简要而言这个变换,就是将逻辑地址映射到线性地址,对逻辑地址进行分割成:段选择符+Offset 看段选择符的TI,如果是0,那么切换到GDT,如果是1,那么切换到LDT 利用段选择符里面的索引号在相应的表里面进行取出段描述符 对段描述符里面的Base进行取出 线性地址<=Base+Offset。
7.3 Hello的线性地址到物理地址的变换-页式管理
我们的Hello需要完成线性地址到物理地址的变换,即页式管理。
虚拟内存被组织为一个由存放在磁盘上的N个连续字节大小的单元组成的数组,它被分为一些固定大小的块,这些块称为虚拟页块。这些页块根据不同的映射状态也被划分为三种状态:未分配、为缓存、已缓存。
未分配:虚拟内存中未分配的页
未缓存:已经分配但是还没有被缓存到物理内存中的页
已缓存:分配后缓存到物理页块中的页
7.4 TLB与四级页表支持下的VA到PA的变换
页表是 PTE(页表条目)的数组,它将虚拟页映射到物理页,每个 PTE 都有一个有效位和一个 n 位地址字段,有效位表明该虚拟页是否被缓存在 DRAM 中。虚拟地址分为两个部分,虚拟页号(VPN)和虚拟页面偏移量(VPO)。其中VPN需要在PTE中查询对应,而VPO则直接对应物理地址偏移(PPO)。
7.5 三级Cache支持下的物理内存访问
物理地址由块偏移(CO)、组索引(CI)、标记(CT)三部分组成,用于进行面向Cache的组选择和行匹配。首先在一级Cache下找,若发生不命中,则到下一级缓存即二级Cache下找,若不命中则到三级Cache下访问。
7.6 hello进程fork时的内存映射
fork创建新进程,则需要为其创建虚拟内存
1.创建当前进程的mm_struct 、vm_area_struct和页表的原样副本。
2.两个进程中的每个页面都标记为只读
3.两个进程中的每个区域结构(vm_area_struct)都标记为私有的写时复制(COW)
4.在新进程中返回时,新进程拥有与调用fork的父进程相同的虚拟内存
5.随后的写操作通过写时复制机制创建新页面
7.7 hello进程execve时的内存映射
execve函数步骤如下:
1.删除已存在的用户区域
2.删除当前进程虚拟地址的用户部分中已存在的内存区域
3.映射私有区域
将目标文件hello的代码和初始化的数据映射到.text和.data区
同时,将.bss和栈映射到匿名文件
4.映射共享区域:
将libc.so等内容映射到共享库里面的映射区域.
5.设置PC,指向代码区域的入口点
7.8 缺页故障与缺页中断处理
当出现缺页故障时,即DRAM缓存不命中,此时调用缺页处理程序,内存会确定一个牺牲页,若页面被修改,则换出到磁盘,再将新的目标页替换牺牲页写入,缺页处理程序页面调入新的页面,并更新内存中的 PTE。缺页处理程序返回到原来的进程,重启导致缺页的指令。
7.9动态存储分配管理
动态内存分配器维护着一个进程的虚拟内存区域,称为堆。
分配器将堆视为一组不同大小的块(block)的集合来维护。每个块就是一个连续的虚拟内存片(chunk), 要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可用来分配空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放要么 是应用程序显式执行的,要么是内存分配器自身隐式执行的。
分配器有两种基本风格。两种风格都要求应用显式地分配块。它们的不同之处在于由哪个实体来负责释放已分配的块。
1.显式分配器(explicit allocator),要求应用显式地释放任何已分配的块。例如,C标准库提供一种叫做malloc程序包的显式分配器。C程序通过调用malloc函数来分配一个块,并通过调用free函数来释放一个块。C++中的new和delete操作符与C中的malloc和free相当。
2.隐式分配器(implicit allocator),另一方面,要求分配器检测一个已分配块何时不再被程序所使用,那么就释放这个块。隐式分配器也叫做垃圾收集器(garbage collector) ,而自动释放未使用的已分配的块的过程叫做垃圾收集(garbage collection) o 例如,诸如Lisp、ML以及Java之类的高级语言就依赖垃圾收集来释放已分配的块。
7.10本章小结
本章简述了计算机中的各类地址及他们之间的相互转换,并探究了计算机的虚拟内存系统内部的工作原理,重新认识了前面章节的一些知识,如fork、execve函数,并讲述了动态内存分配的相关知识。
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件
设备管理:unix io接口
所有的I/O设备都被模型化为文件,而所有的输入和输出都被当作相应文件的读和写来完成,这种将设备优雅的映射为文件的方式,允许Linux内核引出一个简单的,低级的应用接口,称为Unix I/O,使得所有的输入和输出都能以一种统一且一致的方式来执行。
8.2 简述Unix IO接口及其函数
IO接口Unix I/O 接口的操作[2]:
1.open和openat 打开文件
2.creat 创建文件
3.close 关闭文件
4.lseek 设置偏移量
5.read 读取数据
6.write 写入数据
8.3 printf的实现分析
(以下格式自行编排,编辑时删除)
[3]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;
}
vsprintf生成显示信息,到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将字符串中的字节从寄存器中通过总线复制到显卡的显存中,显存中存储的是字符的ASCII码。
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
异步异常-键盘中断的处理:当用户按键时,键盘接口会得到一个代表该按键 的键盘扫描码,同时产生一个中断请求,中断请求抢占当前进程运行键盘中断子 程序,键盘中断子程序先从键盘接口取得该按键的扫描码,然后将该按键扫描码 转换成ASCII码,保存到系统的键盘缓冲区之中。
getchar函数到底层调用了系统函数read,通过系统调用read读取存储在键盘缓冲区中的ASCII码直到读到回车符然后返回整个字串。
8.5本章小结
本章具体讨论了hello的IO管理,通过分析说明Linux的IO设备管理方法,UnixIO接口及其函数,printf的实现分析与getchar的实现分析,深入挖掘了hello程序背后的IO基础
结论
hello.c:编写c程序,hello.c诞生
hello.i:hello.c经过预处理阶段变为hello.i。
hello.s:hello.i经过编译阶段变为hello.s。
hello.o:hello.s经过汇编阶段变为hello.o。
hello:hello.o与可重定位目标文件和动态链接库链接成为可执行文件hello。
运行hello:在终端输入./hello 2022111266许景波 18358928859。
创建子进程:shell调用fork()函数创建一个子进程。
加载:shell调用 execve,execve 调用启动加载器,加映射虚拟内存,进入程序入口后程序开始载入物理内存,然后进入 main 函数。
终止:当子进程执行完成时,内核安排父进程回收子进程,将子进程的退出状态传递给父进程。内核删除为这个进程创建的所有数据结构。
计算机系统的设计与实现是一个既复杂又引人入胜的领域,在深入这个领域的过程中,我获得了许多深刻的感悟。
1.细节决定成败:在计算机系统中,每一个细节都可能影响到整体的性能和稳定性。一个微小的错误或疏漏都可能导致整个系统的崩溃。因此,在设计和实现过程中,必须严谨细致,对每一个细节都进行严格的测试和验证。
2.抽象与分层的智慧:计算机系统是一个复杂的系统,为了降低设计的难度和提高系统的可维护性,通常会将系统划分为多个层次,每一层都向上层提供特定的服务,并隐藏下层的复杂性。这种抽象和分层的思想不仅简化了系统的设计,也提高了系统的可复用性和可扩展性。
3.平衡的艺术:在设计和实现计算机系统时,经常需要在各种因素之间进行权衡和取舍。例如,在追求高性能的同时,可能需要牺牲一些功耗和成本;在追求易用性的同时,可能需要牺牲一些灵活性和可定制性。因此,设计者需要在这些因素之间找到一个合适的平衡点。
附件
Hello.i Hello.c预处理得到的.i文件
Hello.s Hello.i编译后得到的.s文件
Hello.o Hello.s汇编后得到的.o文件
Hello Hello.o经链接后得到的可执行文件
参考文献
为完成本次大作业你翻阅的书籍与网站等
- 逻辑地址、线性地址、物理地址和虚拟地址理解
https://blog.csdn.net/huwen415/article/details/7440772
- UNIX系统 I/O函数
哈工大CSAPP大作业 2022_哈工大csapp期末真题-CSDN博客
[3]https://www.cnblogs.com/pianist/p/3315801.html
5.重定位节
这里与先前不同的是,所有加数皆为0,代表已经完成了重定位。
6.符号表