目 录

第1章 概述

1.1 Hello简介

1.2 环境与工具

1.3 中间结果

1.4 本章小结

第2章 预处理

2.1 预处理的概念与作用

2.2在Ubuntu下预处理的命令

2.3 Hello的预处理结果解析

2.4 本章小结

第3章 编译

3.1 编译的概念与作用

3.2 在Ubuntu下编译的命令

3.3 Hello的编译结果解析

3.4 本章小结

第4章 汇编

4.1 汇编的概念与作用

4.2 在Ubuntu下汇编的命令

4.3 可重定位目标elf格式

4.4 Hello.o的结果解析

4.5 本章小结

第5章 链接

5.1 链接的概念与作用

5.2 在Ubuntu下链接的命令

5.3 可执行目标文件hello的格式

5.4 hello的虚拟地址空间

5.5 链接的重定位过程分析

5.6 hello的执行流程

5.7 Hello的动态链接分析

5.8 本章小结

第6章 hello进程管理

6.1 进程的概念与作用

6.2 简述壳Shell-bash的作用与处理流程

6.3 Hello的fork进程创建过程

6.4 Hello的execve过程

6.5 Hello的进程执行

6.6 hello的异常与信号处理

6.7本章小结

第7章 hello的存储管理

7.1 hello的存储器地址空间

7.2 Intel逻辑地址到线性地址的变换-段式管理

7.3 Hello的线性地址到物理地址的变换-页式管理

7.4 TLB与四级页表支持下的VA到PA的变换

7.5 三级Cache支持下的物理内存访问

7.6 hello进程fork时的内存映射

7.7 hello进程execve时的内存映射

7.8 缺页故障与缺页中断处理

7.9动态存储分配管理

7.10本章小结

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

8.2 简述Unix IO接口及其函数

8.3 printf的实现分析

8.4 getchar的实现分析

8.5本章小结

结论

附件

参考文献


第1章 概述

1.1 Hello简介

P2P过程:通过键盘输入代码保存成hello.c文件。hello.c经过cpp预处理得到hello.i,然后经ccl编译得到hello.s,在经过as汇编得到hello.o,最后通过ld链接得到可执行文件hello。至此hello由program变成process,实现P2P过程。

020过程:操作系统调用execve后映射虚拟内存,先删除当前虚拟地址的数据结构并未hello创建新的区域结构,进入程序入口后载入物理内存,再进入main函数执行代码。执行完成后,父进程回收hello进程,内核删除相关数据结构。

1.2 环境与工具

AMD Ryzen 5 4600U with Radeon Graphics 2.10 GHz1.2.2

Win10 64位

虚拟机VMware Workstation Pro12.0 Ubuntu20.04

gcc ld readelf gedit objdump edb hexedit

1.3 中间结果

hello.i:预处理生成的文本文件
hello.s:.i编译后得到的汇编语言文件
hello.o:.s汇编后得到的可重定位目标文件
hello:.o经过链接生成的可执行目标文件

1.4 本章小结

本章主要介绍了P2P、020的过程并列出实验基本信息

(第1章0.5分)


第2章 预处理

2.1预处理的概念与作用

概念:预处理器(cpp)根据以字符#开头的命令,修改原始的C程序,读取系统头文件的内容,并把它们直接插入程序文本中,得到.i扩展名文件。

作用:调用系统头文件,用实际值替换用#define 定义的字符串,简化了程序,使编译器翻译程序时更加方便

2.2在Ubuntu下预处理的命令

在命令行输入cpp hello.c > hello.i

图2.1-ubuntu下预处理

2.3 Hello的预处理结果解析

hello.i中有各种头文件:

图2.2-hello.i头文件

hello.i中的main:

图2.3-hello.i main

2.4 本章小结

本章介绍预处理过程的概念、作用,对预处理结果进行解析。

(第2章0.5分)

第3章 编译

3.1 编译的概念与作用

概念:编译器(ccl)将文本文件hello.i翻译成文本文件hello.s, 它包含一个汇编语言程序。
作用:生成语法树并转化为目标代码,使下一步转成二进制代码更加方便。

3.2 在Ubuntu下编译的命令

在命令行输入gcc -S hello.i -o hello.s

图3.1-ubuntu下编译

3.3 Hello的编译结果解析

3.3.1 汇编指令的介绍

图3.2-Ubuntu下编译指令结果

.file 声明源文件

.text 代码段

.section

.align 对齐方式

.string 字符串
.globl 全局标识符

3.3.2 数据

1.字符串

代码中的两个字符串,在数据段中

  1. 局部变量i

在代码中,声明了一个局部变量i,编译器酱局部变量放在堆栈中,如图,放置在-4(%rbp)的位置上,占有4个字节

图3.4、3.5-局部变量

3.数组char * argv[]

argv[]作为main函数的第二个参数,是一个字符类型指针,arg[ 0]也存放在堆栈中,-32(%rbp)

图3.6-数组

3.3.3全局变量

如上图所示,hello.c中仅有一个全局函数main,

3.3.4 赋值操作

图3.7、3.8-赋值操作在汇编代码中的体现

main函数中的赋值操作i = 0,由于i为int,占4个字节,所以由movl语句实现

3.3.5 运算操作

一、加法 add实现

二、减法 sub实现

图3.9-运算操作

3.3.6 关系操作

1、hello.c中有i < 8,在汇编代码中是通过关系操作cmp实现的:cmpl $7,-4(%rbp),计算i-7,然后通过jle和0相比较实现i < 8的判断。

图3.10-判断操作(1)

  1. hello.c中有argc != 3,在汇编代码中是通过cmp实现的:cmpl $3,-20(%rbp),同时这个条指令还设置条件码,根据条件码判断是否跳转。

图3.11-判断操作(2)

3.3.7 控制转移指令

在hello.c中有jmp、jle控制转移指令,二者结合完成for循环操作,首先对i赋初值0,然后跳转到条件判断L3,判断是都符合循环条件,符合直接跳转到L4,进入循环,否则call另外的函数。

图3.12-控制转移

3.3.8 函数操作

main中调用的函数有:printf、puts、exit、sleep、getchar

一、printf函数

图3.13-printf函数

将两个参数分别放在寄存器%rsi、%rdx中,然后call调用printf函数,函数返回值存放在%eax中。

二、其他函数

图3.14-调用的其他函数

三、main函数

main函数如果有返回值,则返回值存放在寄存器%eax中,然后leave恢复栈的初始状态(调用函数之前的状态),再ret。

图3.15-main函数

3.4 本章小结

本章介绍了编译的概念和作用,由hello.c为例,分析了汇编代码中各个部分的内容和含义。

(第3章2分)

第4章 汇编

4.1 汇编的概念与作用

概念:汇编器(as)将汇编程序翻译成机器语言指令,把这些指令打包成可重定位目标程序的格式,并将结果保存在.o 目标文件中,.o 文件是一个二进制文件,它包含程序的指令编码。

作用:汇编,将代码转成二进制。

4.2 在Ubuntu下汇编的命令

在命令行输入as hello.s -o hello.o

图4.1-Ubuntu下汇编

4.3 可重定位目标elf格式

分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。

1.ELF头

用readelf -h hello.o 命令生成ELF Header 。ELF头以16字节的Magic开始,描述了生成该文件的系统的大小和字节顺序,其余部分包括帮助连接器语法分析和解释目标文件的信息,例如目标机器类型、字节头部表的文件偏移、节头大小等等。

图4.2-汇编中elf头

2.节头

通过readelf -S hello.o生成节头Section Header

表述了.o文件中的各节的信息,包括类型、位置、偏移量等等。由于是可重定位目标文件,所以每个节都从0开始,用于重定位。在文件头中得到节头表的信息,然后再使用节头表中的字节偏移信息得到各节在文件中的起始位置,以及各节所占空间的大小,同时可以观察到,代码是可执行的,但是不能写;数据段和只读数据段都不可执行,而且只读数据段也不可写。

图4.3汇编中section header

3.符号表

通过readelf -s hello.o生成符号表.symtab

可以看到存放了全局变量和引用的函数。value是每一个符号相对于目标节的起始位置偏移,size是目标的大小,type记录是数据还是函数,bind标识是全局符号还是本地符号,最后name描述符号名字。

图4.4-汇编中符号表

4.重定位节

通过readelf -r hello.o生成重定位节 .rela.text

从命名可知,重定位节是在.text中的位置的列表,包含.text 节中需要进行重定位的信息,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。需要重定位的有所引用的所有函数,还有全局变量。

图4.4-汇编中重定位节

4.4 Hello.o的结果解析

以下格式自行编排,编辑时删除

objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。

说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。

图4.5-反汇编与.s对比

将objdump来的文本文件与.s相对比发现,而这并没有太大区别,反汇编来的代码除了汇编语言之外还有很多机器代码,机器语言程序的是二进制机器指令的集合,是纯粹的二进制数据表示的语言,是电脑可以真正识别的语言。通过分析可以看到二者在函数调用和跳转指令上细微的差别,这是由于汇编语言中仅仅是助记符,而在汇编成机器语言之后是确定的地址,所以二者不同;至于函数调用部分,在反汇编程序中,call之后应该是目标地址(下一条指令),但是由于调用的函数都是共享库中的函数,需要后期动态链接才能确定地址,所以在这部分就把call指令后的相对地址设为0,而.s文件中,函数调用之后直接加上函数名称,更为简单。

4.5 本章小结

在本章中,对hello.s进行了汇编,生成可重定位的目标文件,并且依次对ELF文件进行了分析,从ELF头、节头、符号表、可重定位节几个部分,还比较了.s文件与objdump来的反汇编文件的不同,学习了从汇编语言到机器语言的映射关系。

(第4章1分)

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.o hello.o /usr/lib/x86_64-linux-gnu/libc.so /usr/lib/x86_64-linux-gnu/crtn.o得到可执行程序hello

图5.1-Ubuntu下链接

5.3 可执行目标文件hello的格式

分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。

1.ELF头

通过readelf -a hello > hello.elf生成hello的elf文件

可以看出hello是一个可执行目标文件,有27个节。

图5.2-elf文件

2.节头

通过readelf -S hello生成节头

图5.3-节头

3.符号表

通过readelf -s hello生成重定位节

图5.4-符号表

4.重定位节

通过readelf -r hello生成重定位节

图5.5-重定位节

5.4 hello的虚拟地址空间

使用edb加载hello,查看hello的虚拟地址空间,可以发现,虚拟地址空间从0x7dedd000到0x7deddff0

图5.6-hello的虚拟空间

5.5 链接的重定位过程分析

以下格式自行编排,编辑时删除

objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。

结合hello.o的重定位项目,分析hello中对其怎么重定位的。

采用objdump 而来的反汇编代码已经有了确定的虚拟地址,即完成了反汇编,而hello.o中的代码的虚拟地址均为0;另外反汇编代码多了很多节:

.interp:保存ld.so的路径

.gnu.hash:GNU拓展的符号的哈希表

.dynsym:运行时/动态符号表

.rela.dyn:运行时/动态重定位表

.plt:动态链接-过程链接表

.rela.plt:.plt节的重定位条目

.init:程序初始化需要执行的代码

.fini:当程序正常终止时需要执行的代码

图5.7-重定位(1)

图5.8-重定位(2)

5.6 hello的执行流程

依次执行下列函数:

_dl_start:0x7ffff7de3630

dl-init.:0x7ffff7fe0c20

_start:0x555555555060

__libc_start_main:0x7ffff7de4f90

_setjm:0x7ffff7e03c80

main:0x555555555149

exit:0x7ffff7e07a40

图5.9-各个主要函数的地址

5.7 Hello的动态链接分析

分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。

动态链接就是把程序拆分,在运行的时候才链接到一起。对于动态共享链接库中PIC函数,编译器没有办法预测函数的运行时地址,所以需要添加重定位记录,等待动态链接器处理,然后动态链接器在程序加载的时候再解析它。GNU编译系统使用延迟绑定(lazybinding),将过程地址的绑定推迟到第一次调用该过程时。这一过程是通过GOT和PLT实现的,GOT负责存放函数目标地址,PLT使用GOT中地址跳转到目标函数。

如图,GOT起始表位置是0x404000

图5.10-动态链接项目

5.8 本章小结

在本章中介绍了链接的概念与作用,详细介绍了hello.o的ELF格式和各个节的含义,并且用edb分析了hello的虚拟地址空间、重定位过程、执行流程、动态链接过程。

(第5章1分)

6hello进程管理

6.1 进程的概念与作用

概念:进程是程序执行时期的实例,系统中的每个程序都运行在某个进程的上下文中。上下文是由程序正确运行所需的状态组成的。包括存放在内存中的程序的代码和数据等。
作用:进程作为一种抽象,给我们提供假象:程序好像是系统中当前运行的唯一程序一样,而且好像是独占的使用处理器和内存。

6.2 简述壳Shell-bash的作用与处理流程

作用:为用户提供了一个界面以访问操作系统内核的服务。是一个交互型应用级程序。

处理流程:

1.读取输入的命令

2.对输入内容进行分割,获取参数

3.判断是否是内置命令

4.如果不是内置命令,就调用fork函数,新建一个子进程,然后execve运行程序

5.时刻监控键盘的输入信号,并对其进行相应的处理

6.3 Hello的fork进程创建过程

shell通过fork()新建一个子进程,这个子进程与父进程除了PID以外,子进程代码段、段、数据段、共享库、用户栈、任何打开文件描述符与父进程完全相同,但是同时又是独立于父进程的。他们是并发运行的独立进程。

当运行hello.c的时候,键盘输入 ./hello 120L022225 刘慧新 1,shell按照上述处理流程,解析命令,分析到hello不是内置命令,就fork子进程,然后调用execve运行hello。

6.4 Hello的execve过程

当命令行的指令不是内置命令,shell就把它当作一个需要execve运行的程序,会调用execve在当前子进程的上下文加载并运行一个新程序。创建一组新的代码、数据、堆和栈段。

图6.1-Ubuntu下进程的虚拟空间

6.5 Hello的进程执行

进程上下文信息:上下文就是内核重新启动一个被抢占的进程所需要的状态,它由 通用寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内 核数据结构等对象的值构成。

进程时间片:一个进程执行它的控制流的一部分的每一时间段叫做时间片。

在hello进程执行过程中,首先为hello分配了前文所提到的虚拟地址空间,并且hello的代码节和数据节已经存入其中;运行时首先处于用户模式,进行程序执行,当调用sleep函数后,进程进行上下文切换,保存原有上下文,加载即将执行的上下文,然后进入内核模式,,sleep结束后,恢复原有上下文,再度进行切换,将当前进程控制权移交给其他进程。这个过程中定时器从hello被挂起开始计时,当定时器发出中断信号时,内核执行信号处理,然后再次运行hello程序。

图6.2-上下文切换

6.6 hello的异常与信号处理

以下格式自行编排,编辑时删除

hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。

程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。

异常总共有四种,分别是中断、陷阱、鼓掌、终止:

同步/异步

原因

返回

中断

异步

来自I/O设备的信号

下一条指令

陷阱

同步

故意的异常

故障

潜在的可恢复的

当前指令

终止

不可恢复的

直接终止

hello执行过程中可能出现:由外部I/O设备引起的中断异常;执行到sleep函数时会引起陷阱异常。

1.正常运行:

图6.3-正常运行

2.乱按键盘:

乱按只是将屏幕的输入缓存到 stdin

图6.4-乱按键盘

3.按ctrl+c

Ctrl+c会发送SIGINT信号,该信号的处理程序是终止前台作业,所以由ps看到,hello并不在前台进程组之中

图6.5-按crtl+c

4.按ctrl+z

ctrl+z会挂起前台进程,由ps看到hello仍然在进程组中

图6.6-按ctrl+z

6.7本章小结

本章介绍了进程的概念和作用、shell的作用和一般处理流程,通过对hello例程的分析,介绍了fork、execve的执行过程,还讨论了进程执行过程中可能出现的各种异常。

(第6章1分)

7hello的存储管理

7.1 hello的存储器地址空间

逻辑地址:程序经过编译后出现在汇编代码中的地址。逻辑地址用来指定一个操作数或者是一条指令的地址。是由一个段标识符加上一个指定段内相对地址的偏移量,表示为 [段标识符:段内偏移量]。

线性地址:也叫虚拟地址,和逻辑地址类似,也是一个不真实的地址,如果逻辑地址是对应的硬件平台段式管理转换前地址的话,那么线性地址则对应了硬件也是内存的转换前地址。

虚拟地址:也就是线性地址。

物理地址:CPU 通过地址总线的寻址,找到真实的物理内存对应地址。

7.2 Intel逻辑地址到线性地址的变换-段式管理

实模式下:逻辑地址=线性地址=物理地址
保护模式下:线性地址=段选择符+段内偏移地址

7.3 Hello的线性地址到物理地址的变换-页式管理

线性地址到物理地址需要通过页表的查询,线性地址分为两部分,虚拟页号(VPN)和虚拟页偏移量(VPO),其中VPO也是物理页便宜量,二者是相等的,现在需要将VPN通过查询转换为物理页号PPN,在结合后半部分的物理页偏移量PPO,就是物理地址。

图7.1-线性地址到物理地址

7.4 TLB与四级页表支持下的VA到PA的变换

假设前提:虚拟地址空间48位,物理地址空间52位,页表大小4KB,4级页表。TLB4路16组相联。由一个页表大小4KB,一个PTE条目8B,共512个条目,使 用 9 位二进制索引,一共 4 个页表共使用 36 位二进制索引,所以VPN共36位,因为VA共 48位,所以VPO占12位;因为TLB共16组,所以 TLBI 需 4 位,因为 VPN共36 位,所以TLBT占32位。

变换地址时,CPU产生一个虚拟地址,MMU从TLB中取出相应的PTE。如果命中,则得到对应的物理地址。如果不命中,VPN会被分成4个部分。MMU向页表中查询,CR3确定第一级页表的起始地址,VPN1确定在第一级页表中的偏移量,查询出 PTE,如果在物理内存中且权限符合,确定第二级页表的起始地址,以此类推,最终在第四级页表中查询到PPN,与VPO组合成 PA,并且向TLB中添加条目。

图7.2-多级页表

7.5 三级Cache支持下的物理内存访问

前提如下:L1 Cache 是8路64组相联,块大小为64B。由于有64组,所以组索引CI需要6 bit,块大小为64B故组内偏移CO需要6bit。因为PA共52 bit所以剩余部分CT共40 bit。

开始访问时,发送PA给L1,如果L1中没有,就向下查找L2、L3,如果找到就根据偏移量CO找到地址取出数据返回。

图7.3-三级cache

7.6 hello进程fork时的内存映射

当进程调用fork 函数时,内核为新进程创建各种数据结构,为了给这个新进程创建虚拟内存,它创建了当前进程的 mm_struct、区域结构和页表的原样副本。它将这两个进程的每个页面都标记为只 读,并将两个进程中的每个区域结构都标记为私有的写时复制。

7.7 hello进程execve时的内存映射

execve函数加载并运行hello的步骤如下:

1.删除当前的用户区域

2.映射私有区域:为新程序的代码、数据、bss和栈区域创建新的区域结构。

3.映射共享区域:hello程序与标准C库链接,这些对象动态链接到这个程序,然后再映射到用户虚拟地址空间中的共享区域内。

4.设置程序计数器。execve做得最后一件事情是设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。

图7.4-execve内存映射

7.8 缺页故障与缺页中断处理

缺页故障:当指令引用一个相应的虚拟地址,而与改地址相应的物理页面不再内存中,会触发缺页故障。

缺页故障的时候,会首先判断该虚拟地址是否合法,在判断该访问是否合法(是否有相应权限),最后当上述都合法时,选择牺牲一个现有页面,替换目标页面。

然后控制返回给引起缺页故障的指令。当指令再次执行时,相应的物理页面已经驻留在内存中,因此指令可以没有故障的运行完成。

图7.5-缺页故障

7.9动态存储分配管理

动态内存分配器维护着一个进程的虚拟内存区域,称为堆。分配器将堆视为 一组不同大小的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是已 分配的,要么是空闲的。

分配器分为两种基本风格:显式分配器、隐式分配器。

隐式分配器:要求分配器检测一个已分配块何时不再使用,那么就释放这个块,自动释放未使用的已经分配的块的过程叫做垃圾收集。
显式分配器:要求应用显式地释放任何已分配的块。

7.10本章小结

本章主要介绍了hello的存储器地址空间、段式管理和页式管理、介绍了四种地址空间的差别和地址的相互转换,介绍了VA到PA的变换、物理内存访问、三级cashe的物理内存访问以及进程fork、execve时的内存映射,缺页故障与缺页中断处理,动态存储分配管理等。

(第7章 2分)

8hello的IO管理

8.1 Linux的IO设备管理方法

设备的模型化:文件

设备管理:unix io接口

所有的 IO 设备都被模型化为文件,而所有的输入和输出都被 当做对相应文件的读和写来执行,这种将设备优雅地映射为文件的方式,允许 Linux 内核引出一个简单低级的应用接口,称为 Unix I/O

文件的类型:

1.普通文件(regular file):包含任意数据的文件。

2.目录(directory):包含一组链接的文件,每个链接都将一个文件名映射到一个文件(他还有另一个名字叫做“文件夹”)。

3.套接字(socket):用来与另一个进程进行跨网络通信的文件

4.命名通道

5.符号链接

6.字符和块设备

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。类似一个写操作就是从内存中复制 n>0 个字节到一个文件,从当前文件位置 k 开始,然后更新 k。

(5)关闭文件,内核释放文件打开时创建的数据结构,并将这个描述符恢 复到可用的描述符池中去。

Unix I/O 函数:

(1)int open(char* filename,int flags,mode_t mode) ,进程通过调用 open 函 数来打开一个存在的文件或是创建一个新文件的。 open函数将filename 转换为一个文件描述符,并且返回描述符数字。

(2)int close(fd),fd 是需要关闭的文件的描述符,close 返回操作结果。

(3) ssize_t read(int fd,void *buf,size_t n),read 函数从描述符为 fd 的当前文 件位置赋值最多 n 个字节到内存位置 buf。返回值-1 表示一个错误,0 表示 EOF,否则返回值表示的是实际传送的字节数量。

4) ssize_t wirte(int fd,const void *buf,size_t n),write 函数从内存位置 buf 复制至多 n 个字节到描述符为 fd 的当前文件位置。

8.3 printf的实现分析

printf函数:

图8.1-printf内部

write系统函数:

图8.2-write内部

syscall:

图8.3-syscall内部

在printf中调用系统函数write(buf,i)将长度为i的buf输出,在write函数中,将栈中参数放入寄存器,ecx是字符个数,ebx存放第一个字符地址,
int INT_VECTOR_SYS_CALLA代表通过系统调用syscall。syscall将字符串中的字节从寄存器中通过总线复制到显卡的显存中,显存中存储的是字符的ASCII码。

字符显示驱动子程序将通过ASCII码在字模库中找到点阵信息将点阵信息存储到vram中。

显示芯片会按照一定的刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。

8.4 getchar的实现分析

getchar函数:

图8.4-getchar内部

异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。

getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。

8.5本章小结

本章主要介绍了 Linux 的 IO 设备管理方法、Unix IO 接口及其函数,分析了 printf 函数和 getchar 函数的实现。

(第8章1分)

结论

首先通过键盘输入代码,生成hello.c文件

预处理:将hello.c调用的所有外部的库展开合并到一个hello.i文件中

编译:将hello.i编译成汇编文件hello.s

汇编:将hello.s会变成为可重定位目标文件hello.o

链接:链接器对hello.o进行链接得到可执行文件hello,此时hello已经被操作系统加载和执行

运行hello:在终端输入命令,shell进程调用fork为hello创建一个子进程,随后调用execve启动加载器,加映射虚拟内存,进入程序入口后程序开始载入物理内存,然后进入main函数

内存使用:MMU 将程序中使用的虚拟内存地址通过页表映射成物理地址。printf会调用 malloc 向动态内存分配器申请堆中的内存

信号:在运行时输入Ctrl+c,内核会发送SIGINT信号给进程并终止前台程序。当输入Ctrl+z时,内核会发送SIGTSTP信号给进程,并将前台程序停止挂起。

终止:当子进程执行完成时,内核安排父进程回收子进程,将子进程的退出状态传递给父进程。内核删除为这个进程创建的所有 数据结构。

计算机系统的设计思想和实现都是基于抽象实现的。从最底层的信息的表示用二进制表示抽象开始,到实现操作系统管理硬件的抽象:进程是对处理器、主存和I/O设备的抽象。虚拟内存是对主存和磁盘设备的抽象。文件是对I/O设备的抽象。

通过对hello的一生的学习、梳理,深入理解了现代计算机操作系统各部分之间的协作、调用,对系统各部分的设计思想、处理方式有了基本的认识了解。

(结论0分,缺失 -1分,根据内容酌情加分)