计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 物联网工程
学 号 2022112708
班 级 2237301
学 生 朱思泉
指 导 教 师 吴锐
计算机科学与技术学院
2024年5月
Hello是程序员的初恋,却又常常被人抛弃。可是当你回过头去深入了解hello的P2P时,hello的复杂一生似乎又伴随着你对计算机系统的学习过程。失去的hello又成了心头那份飘摇的白月光。
关键词:P2P;计算机系统;Linux;
目 录
第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 -
参考文献....................................................................................... - 16 -
第1章 概述
1.1 Hello简介
根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
P2P: From Program to Process
源程序hello.c经处理器(cpp)修改转化为hello.i文本,然后经过编译器(ccl)翻译成hello.s这一汇编程序文本,再用汇编器(as)将hello.s翻译成机器语言指令,将这些指令打包成可重定位目标程序的格式,并将结果保存在目标文件hello.o中。最后因为该程序调用了printf这一函数,需要将printf.o通过链接器(ld)与和hello.o进行链接,最终得到可执行程序hello文件。此时在shell内调用相关命令,为其fork进程。
020: From Zero-0 to Zero-0
shell通过execve在fork产生的子进程中加载hello,先删除当前虚拟地址的用户部分已存在的数据结构,为hello的代码段、数据、bss以及栈区域创建新的区域结构,然后映射虚拟内存,设置程序计数器,使之指向代码区域的入口点,进入程序入口后程序开始载入物理内存,而后进入main函数,CPU为hello分配时间片执行逻辑控制流。hello通过Unix I/O管理来控制输出。hello执行完成后shell父进程会回收hello进程,并且内核会从系统中删除hello所有痕迹,即“赤条条来去”。
1.2 环境与工具
硬件环境:Intel(R) Core(TM) i5-8300H X64 CPU;2.30GHz;8.00GB RAM;256GHD Disk以上
软件环境:Windows10 64位;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64位
开发工具:CodeBlocks;vi/vim/gpedit+gcc;Visual Studio 2010 64位以上;GDB/OBJDUMP;DDD/EDB等
1.3 中间结果
中间文件 | 说明 |
hello.o | 预处理 |
hello.s | 编译 |
hello.o | 汇编 |
hello | 链接后可执行文件 |
hello_elf.txt | Elf文件 |
hello.c | 原文件 |
1.4 本章小结
本章简要介绍了大作业的背景
第2章 预处理
2.1 预处理的概念与作用
预处理的概念:
预处理一般是指在程序源代码被翻译为目标代码的过程中,生成二进制代码之前的过程。预处理器(cpp)根据以字符#开头的命令,修改原始的C程序,读取头文件stdio.h的内容,并把它直接插入程序文本中。
预处理的作用:
1.以字符#开头的命令,告诉预处理器读取系统头文件stdio.h的内容,并将其直接插入程序文本中。
2.用实际值替换用#define 定义的字符串
3.根据#if 后面的条件决定需要编译的代码
2.2在Ubuntu下预处理的命令
键入gcc -m64 -no-pie -fno-PIC -E -o hello.i hello.c
图2.2.1 运行命令及打开hello.i文件
2.3 Hello的预处理结果解析
图2.3.1 hello.i文件首部
hello.c一共有三个头文件,分别为#include <stdio.h>、#include <unistd.h> 、#include <stdlib.h>,故在预处理过程中对这三个文件进行了读取插入操作.从首部文件内容展示可以看到许多形如usr/include/的语句,这是cpp读取头文件进行的操作.
图2.3.2 hello.i文件尾部
图2.3.3 原hello.c文本
将图2.3.2与图2.3.3进行对比,并不能看出不同,这说明预处理的过程只是对头文件进行操作的,main函数的部分不作修改.
2.4 本章小结
预处理是hello一生p2p过程中的第一步,放在整个过程中也是相当重要的.我们在这一章节使用ubuntu展示了对于hello.c文件的预处理过程与预处理结果,对后续工作有着一定的帮助作用.
第3章 编译
3.1 编译的概念与作用
概念:编译,就是把代码转化为汇编指令的过程,并把把预处理完的文件进行一系列语法分析及优化后生成相应的汇编文件。编译程序把一个源程序翻译成目标程序的工作过程分为五个阶段:词法分析;语法分析;语义检查和中间代码生成;代码优化;目标代码生成。主要是进行词法分析和语法分析,又称为源程序分析,分析过程中发现有语法错误,给出提示信息。
作用: 编译器(ccl)将文本文件hello.i翻译成文本文件hello.s,它包含一个汇编语言程序,该程序包含函数main的定义,其中每条语句都以一种文本格式描述了一条低级语言指令。编译主要作用除了是将文本文件hello.i翻译成文本文件hello.s之外,还在出现语法错误时给出提示信息。
3.2 在Ubuntu下编译的命令
gcc -m64 -no-pie -fno-PIC -S -o hello.s hello.i
图3.2.1 输入命令并生成hello.s文件
3.3 Hello的编译结果解析
3.3.1 数据
1.字符串
分别为:
"用法: Hello 学号 姓名秒数!\n"
与"Hello %s %s %s\n"
图3.3.1
2.变量
i
图3.3.2
通过addl $1, -4(%rbp)递增i实现循环
Argc及argv
图3.3.3
通过movl %edi, -20(%rbp)存储参数argc的值,通过movq %rsi, -32(%rbp)存储参数argv的值
2.赋值
图3.3.4
汇编语句中采用了movl指令进行赋值操作
3.算数操作
图3.3.5
在main的循环控制中用到addl
4.关系操作
图3.3.6
图3.3.7
分别对应if(argc!=5)和for(i=0;i<10;i++)中i<10的比较
5.数组指针结构
图3.3.8
argv地址存放在存放在栈中-32(%rbp)
6.转移控制
图3.3.9
If语句用来比较跳转
7.函数操作
图3.3.10
3.4 本章小结
本章展示hello.i -> hello.s,并对编译结果的内容进行多层面分析,有助于更充分的理解程序.
第4章 汇编
4.1 汇编的概念与作用
概念:是把作为中间结果的汇编代码通过汇编器(as)翻译成机器代码,即目标代码的过程,由.s文件得到.o文件
作用:从汇编指令进一步得到CPU可以执行的机器指令
4.2 在Ubuntu下汇编的命令
gcc -m64 -no-pie -fno-PIC -c -o hello.o hello.s
图4.2.1
4.3 可重定位目标elf格式
键入readelf -a hello.o>hello_elf.txt查看elf格式
4.3.1 elf头
图4.3.1 elf头
Elf头定义了ELF魔数、文件机器字节长度、数据存储方式、版本、运行平台等。
数据结构如下所示
#define EI_NIDENT 16
typedef struct {
unsigned char e_ident[EI_NIDENT];
Elf64_Half e_type;
Elf64_Half e_machine;
Elf64_Word e_version;
Elf64_Addr e_entry;
Elf64_Off e_phoff;
Elf64_Off e_shoff;
Elf64_Word e_flags;
Elf64_Half e_ehsize;
Elf64_Half e_phentsize;
Elf64_Half e_phnum;
Elf64_Half e_shentsize;
Elf64_Half e_shnum;
Elf64_Half e_shstrndx;
} Elf64_Ehdr;
4.3.2 节头
节头描述了ELF的各个段的信息,ELF文件的段结构就是由段表决定的。编译器、链接器和装载器都是依靠段表来定位和访问各个段的属性的
图4.3.2 节头
数据结构如下
typedef struct {
Elf64_Word sh_name;
Elf64_Word sh_type;
Elf64_Xword sh_flags;
Elf64_Addr sh_addr;
Elf64_Off sh_offset;
Elf64_Xword sh_size;
Elf64_Word sh_link;
Elf64_Word sh_info;
Elf64_Xword sh_addralign;
Elf64_Xword sh_entsize;
} Elf64_Shdr;
4.3.3 重定位节
图4.3.3 重定位节
4.3.4 符号表
图4.3.4 符号表
4.4 Hello.o的结果解析
objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
图4.4.1 hello.o反汇编
hello.s是由汇编语言组成的,相对于计算机能识别的机器级指令,汇编代码仍是抽象语言;而反汇编得到的代码不仅仅有汇编代码,还有机器语言代码。汇编语言可以映射到机器语言。
不同点:
- 操作数上,hello.s中采用的是十进制,反汇编代码中采用的是十六进制
- 分支转移上,hello.s中用段名称来跳转,反汇编代码用具体的地址实现
4.5 本章小结
本章对hello.s进行汇编,得到了hello.o可重定位目标文件,同时分析hello.o的ELF格式,并将hello.o与hello.s进行对比说明区别。
第5章 链接
5.1 链接的概念与作用
概念:链接是处理可重定位文件,把它们的各种符号引用和符号定义转换为可执行文件中的合适信息(一般是虚拟内存地址)的过程。链接又分为静态链接和动态链接,前者是程序开发阶段程序员用ld(gcc实际上在后台调用了ld)静态链接器手动链接的过程,而动态链接则是程序运行期间系统调用动态链接器(ld-linux.so)自动链接的过程。
作用:链接将汇编语言代码彻底转化为机器代码,把可重定位目标文件和命令行参数作为输入,生成可执行目标文件。
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
图5.2.1 链接过程
5.3 可执行目标文件hello的格式
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
图5.3.1 elf头
图5.3.2 节头
图5.3.3 重定位节
图5.3.4 符号表
5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
图5.4.1 Data Dump
由图可知,虚拟空间从0x400000开始
图5.4.2 symbolviewer
与5.3对照发现一致
5.5 链接的重定位过程分析
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
图5.5.1 hello
图5.5.2 hello.o
进行分析比较可以发现二者的差异。
- 就main段看,hello反汇编的代码有确定的虚拟地址,而hello.o反汇编代码中代码的虚拟地址均为0
- Hello反汇编代码与hello.o相比,多了许多节
图5.5.3 更多的节示意
hello重定位的过程:
- 合并相同的节
- 对定义符号进行重定位(确定地址)
- 对引用符号进行重定位(确定地址)
5.6 hello的执行流程
子程序名 ld -2.33.so!_dl_start | 程序地址 0x7fac8adba093 |
ld-2.33.so!_dl_init | 0x7fac8adba0c5 |
hello!_start | 0x4010b8 |
libc-2.33so!__libc_start_main | 0x7f6fe58bd540 |
hello!printf@plt | 0x401024 |
hello!sleep@plt | 0x4010f7 |
hello!getchar@plt | 0x401090 |
libc-2.33.so!exit | 0x7f6fe58b40e0 |
5.7 Hello的动态链接分析
对于动态共享链接库中PIC函数,编译器添加重定位记录,等待动态链接器处理,同时,链接器采用延迟绑定的方法。
5.7.1 在dl_init后,部分数据信息发生变动
5.8 本章小结
在本章中主要介绍了链接的概念与作用,并且详细阐述了hello.o是怎么链接成为一个可执行目标文件的过程,详细介绍了hello.o的ELF格式和各个节的含义,并且分析了hello的虚拟地址空间、重定位过程、执行流程、动态链接过程
第6章 hello进程管理
6.1 进程的概念与作用
概念:进程是操作系统对一个正在运行的程序的一种抽象。
作用:进程可以使CPU并行完成任务,并且高效利用内存和CPU。
6.2 简述壳Shell-bash的作用与处理流程
Shell是用户和操作系统之间完成交互式操作的一个接口程。bash是Linux操作系统的默认shell程序。
Shell是“提供使用者使用界面”的软件,是一个命令行解释器,作用是:保护内核同时帮助使用者向计算机传递交互信息。
处理流程:
1.用户输入命令或程序代码。
2.Shell解析输入信息。
3.若为内置命令,调用内置命令处理函数,否则调用fork( )创建子进程。
4.若为前台运行程序,调用wait()直至前台作业结束,如果为后台命令则,则不等待。
6.3 Hello的fork进程创建过程
一个进程,包括代码、数据和分配给进程的资源。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,包括代码段、段、数据段、共享库以及用户栈,但是两者pid不同。在子进程执行期间,父进程默认选项是显示等待子进程的完成。
以hello为例,在shell输入./hello时,由于不是内置命令,shell就调用fork()来创建一个子进程以执行hello
6.4 Hello的execve过程
通过调用execve(),进程能够以全新程序来替换当前运行的程序。再次过程中,将丢弃旧有程序,进程的栈、数据以及堆段会被新程序所替换。以hello为例,shell的fork子程序调用execve函数,execve函数在当前子进程的上下文中加载运行可执行文件hello,创建一个hello的映像复制的hello拥有相同的PID。只有当出现错误时,例如找不到hello,execve才会返回到调用程序。
6.5 Hello的进程执行
进程是操作系统对一个正在运行的程序的一种抽象。刚开始运行时内核为其保存一个上下文,进程在用户状态下运行。循环结束后,hello 调用 getchar 函数,之前 hello 运行在用户模式下,再调用 getchar 时进入内核模式,执行上下文切换,把控制转移给其他进程。完成键盘输入后,内核从其他进程切换回 hello 进程,最终 hello执行 return终止进程。
6.6 hello的异常与信号处理
可能出现的异常种类:1.终止, 2.故障
可能出现的信号:1.SIGINT信号 2.SIGSTP信号
按下ctrl-z后,shell父进程收到SIGSTP信号,信号处理程序将hello进程挂起,放到后台,停止hello程序
按下ctrl-c后,shell父进程收到SIGINT信号,由信号处理函数结束hello,并回收hello进程
图6.6.1 乱按
图6.6.2 ctrl+c
图6.6.3 ctrl+z及相关命令
6.7本章小结
本章简述了进程的概念与作用及壳Shell-bash的作用与处理流程,分析了hello的fork进程与execve过程,以及其上下文的进程执行和异常信号处理。
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:访问指令给出的地址 (操作数) 叫逻辑地址,也叫相对地址,由选择符和偏移量组成。要经过寻址方式的计算或变换才得到内存储器中的物理地址。
线性地址:线性地址或也叫虚拟地址,逻辑地址经过段机制后转化为线性地址,是一个不真实的地址,线性地址对应硬件页式内存的转换前地址。
虚拟地址:虚拟地址即线性地址,虚拟地址是对物理地址的映射。
物理地址:真实的物理内存对应地址。CPU通过地址总线的寻址,找到真实的物理内存对应地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
实模式下,逻辑地址CS: EA通过CS * 16 + EA转换为物理地址。
保护模式下,以段描述符作为下标,到GDT/LDT表中查表获得段地址,将段地址+偏移地址转换为线性地址。
图7.2.1 段选择符
7.3 Hello的线性地址到物理地址的变换-页式管理
由逻辑地址得到的线性地址一共 32 位。前 10 位是页目录索引,中间 10 位是页表索引,最后 12 位是业内偏移量。由 CR3 寄存器得到页目录基地址,再得到页目录项,由页目录项得到页表基地址,通过基地址得到页表项,最后得到物理地址
图7.3.1 页式管理
7.4 TLB与四级页表支持下的VA到PA的变换
TLB是一个小的、虚拟寻址的缓存,其每一行都保存着一个由单个PTE组成的块。用于组选择和行匹配的索引和标记字段是从虚拟地址的虚拟页号中提取出来的。
图7.4.1 虚拟地址中用以访问TLB的组成部分
图7.4.2 k级页表的地址翻译
若是四级页表,虚拟地址被划分成为 4 VPN 和 1 个 VPO。每个 VPN i 都是一个到第 i 级页表的索引,其中 1⩽𝑖⩽4. j 级页表中的每个 PTE,1⩽𝑗⩽3都指向第 j+1 级的某个页表的基址。第 k 级页表中的每个 PTE 包含某个物理页面的 PPN,或者一个磁盘块的地址。为了构造物理地址,在能够确定 PPN 之前,MMU 必须访问k个PTE。
7.5 三级Cache支持下的物理内存访问
产生虚拟地址请求时,首先去TLB寻找,若命中则在MMU获取;没有命中则根据多级页表,得到物理地址,接下去三级cache一级级寻找。
7.6 hello进程fork时的内存映射
当 fork 函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的 PID。为了给这个新进程创建虚拟内存,它创建了当前进程的 mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。
当 fork 在新进程中返回时,新进程现在的虚拟内存刚好和调用 fork 时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,因此,也就为每个进程保持了私有地址空间的抽象概念。
7.7 hello进程execve时的内存映射
execve 函数在当前进程中加载并运行包含在可执行目标文件 a.out 中的程序,用 a.out 程序有效地替代了当前程序。加载并运行 a.out 需要以下几个步骤:
1.删除已存在的用户区域。删除当前进程虚拟地址的用户部分中的已存在的区域结构。
2.映射私有区域。为新程序的代码、数据、bss 和栈区域创建新的区域结构。所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为 a.out 文件中的. text 和. data 区。bss 区域是请求二进制零的,映射到匿名文件,其大小包含在 a.out 中。栈和堆区域也是请求二进制零的,初始长度为零。图 9-31 概括了私有区域的不同映射。
3.映射共享区域。如果 a.out 程序与共享对象(或目标)链接,比如标准 C 库 libc.so,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内。
4.设置程序计数器(PC)。execve 做的最后一件事情就是设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。
图7.7.1 加载器如何映射用户地址空间
7.8 缺页故障与缺页中断处理
假设MMU试图翻译虚拟地址A触发缺页后,处理程序执行下列步骤:
- 判断虚拟地址A是否合法,若不合法,缺页程序触发段错误终止这一进程;
- 判断试图进行的内存访问是否合法若不合法,缺页处理程序会触发一个保护异常,从而终止这个进程;
- 内核知道了这个缺页是由于对合法的虚拟地址进行合法的操作造成的。此时选择一个牺牲页面,使MMU能正常翻译A
图7.8.1 缺页处理
7.9动态存储分配管理
动态存储分配管理由动态内存分配器完成。动态内存分配器维护着一个进程的虚拟内存区域,称为堆。堆是一个请求二进制零的区域,它紧接在未初始化的数据区后开始,并向上生长(向更高的地址)。分配器将堆视为一组不同大小的块的集合来维护。
每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可以用来分配。空闲块保持空闲,直到它显示地被应用程序所分配。
一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。
动态内存分配器从堆中获得空间,将对应的块标记为已分配,回收时将堆标记为未分配。而分配和回收的过程中,往往涉及到分割、合并等操作。
动态内存分配器的目标是在对齐块的基础上,尽可能地提高吞吐率及空间占用率,即减少因为内存分配造成的碎片。其实现常见的数据结构有隐式空闲链表、显式空闲链表、分离空闲链表,常见的放置策略有首次适配、下一次适配和最佳适配。
7.10本章小结
本章分析了hello的存储管理,介绍了段式管理与页式管理,以及TLB与多级页表支持下的VA到PA的转换,同时对三级Cache支持下的物理内存访问进行了说明。简要说明了hello的fork和execve内存映射,了解了缺页故障与缺页中断处理程序,介绍了动态存储分配管理。
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件
文件包括:普通文件、目录、套接字、命名通道、符号链接以及字符和块设备。
图8.1.1 Linux 目录层次的一部分。尾部有斜杠表示是目录
设备管理:unix io接口
操作包括:打开和关闭文件、读取和写入文件以及改变当前文件的位置。
8.2 简述Unix IO接口及其函数
1.打开文件——open():一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个 I/O 设备。内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件。内核记录有关这个打开文件的所有信息。应用程序只需记住这个描述符。
2.关闭文件——close():当应用完成了对文件的访问之后,它就通知内核关闭这个文件。作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。无论一个进程因为何种原因终止时,内核都会关闭所有打开的文件并释放它们的内存资源。
3.读写文件——read()write():一个读操作就是从文件复制 n > 0 个字节到内存,从当前文件位置 k 开始,然后将 k 增加到 k+n。给定一个大小为 m 字节的文件,当k⩾m时执行读操作会触发一个称为 end-of-file(EOF)的条件,应用程序能检测到这个条件。在文件结尾处并没有明确的 “EOF 符号”。
8.3 printf的实现分析
函数printf的实现过程调用了vsprintf和write函数。
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章简述IO设备管理方法及Unix I/O函数。同时分析了printf和getchar两个函数的实现。
结论
Hello作为每个程序员的初恋,作为一个普通的程序,却走过了复杂的一生,包括预处理、编译、汇编、链接、创建进程、运行程序、执行指令、访问内存、异常信号、Unix I/O以及进程结束。Hello不仅是初恋,更是那白月光,一路走来,却发现它在你的学习中贯穿始终,闪闪发光。
附件
中间文件 | 说明 |
hello.o | 预处理 |
hello.s | 编译 |
hello.o | 汇编 |
hello | 链接后可执行文件 |
hello_elf.txt | Elf文件 |
hello.c | 原文件 |
参考文献
[1] Linux四级页表及其原理 - 简书 (jianshu.com)
[2] 本电子书信息 | 深入理解计算机系统(CSAPP) (gitbook.io)
[3] ELF 接头 (linuxfoundation.org).
计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 物联网工程
学 号 2022112708
班 级 2237301
学 生 朱思泉
指 导 教 师 吴锐
计算机科学与技术学院
2024年5月
摘 要
Hello是程序员的初恋,却又常常被人抛弃。可是当你回过头去深入了解hello的P2P时,hello的复杂一生似乎又伴随着你对计算机系统的学习过程。失去的hello又成了心头那份飘摇的白月光。
关键词:P2P;计算机系统;Linux;
目 录
第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 -
参考文献....................................................................................... - 16 -
第1章 概述
1.1 Hello简介
根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
P2P: From Program to Process
源程序hello.c经处理器(cpp)修改转化为hello.i文本,然后经过编译器(ccl)翻译成hello.s这一汇编程序文本,再用汇编器(as)将hello.s翻译成机器语言指令,将这些指令打包成可重定位目标程序的格式,并将结果保存在目标文件hello.o中。最后因为该程序调用了printf这一函数,需要将printf.o通过链接器(ld)与和hello.o进行链接,最终得到可执行程序hello文件。此时在shell内调用相关命令,为其fork进程。
020: From Zero-0 to Zero-0
shell通过execve在fork产生的子进程中加载hello,先删除当前虚拟地址的用户部分已存在的数据结构,为hello的代码段、数据、bss以及栈区域创建新的区域结构,然后映射虚拟内存,设置程序计数器,使之指向代码区域的入口点,进入程序入口后程序开始载入物理内存,而后进入main函数,CPU为hello分配时间片执行逻辑控制流。hello通过Unix I/O管理来控制输出。hello执行完成后shell父进程会回收hello进程,并且内核会从系统中删除hello所有痕迹,即“赤条条来去”。
1.2 环境与工具
硬件环境:Intel(R) Core(TM) i5-8300H X64 CPU;2.30GHz;8.00GB RAM;256GHD Disk以上
软件环境:Windows10 64位;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64位
开发工具:CodeBlocks;vi/vim/gpedit+gcc;Visual Studio 2010 64位以上;GDB/OBJDUMP;DDD/EDB等
1.3 中间结果
中间文件 | 说明 |
hello.o | 预处理 |
hello.s | 编译 |
hello.o | 汇编 |
hello | 链接后可执行文件 |
hello_elf.txt | Elf文件 |
hello.c | 原文件 |
1.4 本章小结
本章简要介绍了大作业的背景
第2章 预处理
2.1 预处理的概念与作用
预处理的概念:
预处理一般是指在程序源代码被翻译为目标代码的过程中,生成二进制代码之前的过程。预处理器(cpp)根据以字符#开头的命令,修改原始的C程序,读取头文件stdio.h的内容,并把它直接插入程序文本中。
预处理的作用:
1.以字符#开头的命令,告诉预处理器读取系统头文件stdio.h的内容,并将其直接插入程序文本中。
2.用实际值替换用#define 定义的字符串
3.根据#if 后面的条件决定需要编译的代码
2.2在Ubuntu下预处理的命令
键入gcc -m64 -no-pie -fno-PIC -E -o hello.i hello.c
图2.2.1 运行命令及打开hello.i文件
2.3 Hello的预处理结果解析
图2.3.1 hello.i文件首部
hello.c一共有三个头文件,分别为#include <stdio.h>、#include <unistd.h> 、#include <stdlib.h>,故在预处理过程中对这三个文件进行了读取插入操作.从首部文件内容展示可以看到许多形如usr/include/的语句,这是cpp读取头文件进行的操作.
图2.3.2 hello.i文件尾部
图2.3.3 原hello.c文本
将图2.3.2与图2.3.3进行对比,并不能看出不同,这说明预处理的过程只是对头文件进行操作的,main函数的部分不作修改.
2.4 本章小结
预处理是hello一生p2p过程中的第一步,放在整个过程中也是相当重要的.我们在这一章节使用ubuntu展示了对于hello.c文件的预处理过程与预处理结果,对后续工作有着一定的帮助作用.
第3章 编译
3.1 编译的概念与作用
概念:编译,就是把代码转化为汇编指令的过程,并把把预处理完的文件进行一系列语法分析及优化后生成相应的汇编文件。编译程序把一个源程序翻译成目标程序的工作过程分为五个阶段:词法分析;语法分析;语义检查和中间代码生成;代码优化;目标代码生成。主要是进行词法分析和语法分析,又称为源程序分析,分析过程中发现有语法错误,给出提示信息。
作用: 编译器(ccl)将文本文件hello.i翻译成文本文件hello.s,它包含一个汇编语言程序,该程序包含函数main的定义,其中每条语句都以一种文本格式描述了一条低级语言指令。编译主要作用除了是将文本文件hello.i翻译成文本文件hello.s之外,还在出现语法错误时给出提示信息。
3.2 在Ubuntu下编译的命令
gcc -m64 -no-pie -fno-PIC -S -o hello.s hello.i
图3.2.1 输入命令并生成hello.s文件
3.3 Hello的编译结果解析
3.3.1 数据
1.字符串
分别为:
"用法: Hello 学号 姓名秒数!\n"
与"Hello %s %s %s\n"
图3.3.1
2.变量
i
图3.3.2
通过addl $1, -4(%rbp)递增i实现循环
Argc及argv
图3.3.3
通过movl %edi, -20(%rbp)存储参数argc的值,通过movq %rsi, -32(%rbp)存储参数argv的值
2.赋值
图3.3.4
汇编语句中采用了movl指令进行赋值操作
3.算数操作
图3.3.5
在main的循环控制中用到addl
4.关系操作
图3.3.6
图3.3.7
分别对应if(argc!=5)和for(i=0;i<10;i++)中i<10的比较
5.数组指针结构
图3.3.8
argv地址存放在存放在栈中-32(%rbp)
6.转移控制
图3.3.9
If语句用来比较跳转
7.函数操作
图3.3.10
3.4 本章小结
本章展示hello.i -> hello.s,并对编译结果的内容进行多层面分析,有助于更充分的理解程序.
第4章 汇编
4.1 汇编的概念与作用
概念:是把作为中间结果的汇编代码通过汇编器(as)翻译成机器代码,即目标代码的过程,由.s文件得到.o文件
作用:从汇编指令进一步得到CPU可以执行的机器指令
4.2 在Ubuntu下汇编的命令
gcc -m64 -no-pie -fno-PIC -c -o hello.o hello.s
图4.2.1
4.3 可重定位目标elf格式
键入readelf -a hello.o>hello_elf.txt查看elf格式
4.3.1 elf头
图4.3.1 elf头
Elf头定义了ELF魔数、文件机器字节长度、数据存储方式、版本、运行平台等。
数据结构如下所示
#define EI_NIDENT 16
typedef struct {
unsigned char e_ident[EI_NIDENT];
Elf64_Half e_type;
Elf64_Half e_machine;
Elf64_Word e_version;
Elf64_Addr e_entry;
Elf64_Off e_phoff;
Elf64_Off e_shoff;
Elf64_Word e_flags;
Elf64_Half e_ehsize;
Elf64_Half e_phentsize;
Elf64_Half e_phnum;
Elf64_Half e_shentsize;
Elf64_Half e_shnum;
Elf64_Half e_shstrndx;
} Elf64_Ehdr;
4.3.2 节头
节头描述了ELF的各个段的信息,ELF文件的段结构就是由段表决定的。编译器、链接器和装载器都是依靠段表来定位和访问各个段的属性的
图4.3.2 节头
数据结构如下
typedef struct {
Elf64_Word sh_name;
Elf64_Word sh_type;
Elf64_Xword sh_flags;
Elf64_Addr sh_addr;
Elf64_Off sh_offset;
Elf64_Xword sh_size;
Elf64_Word sh_link;
Elf64_Word sh_info;
Elf64_Xword sh_addralign;
Elf64_Xword sh_entsize;
} Elf64_Shdr;
4.3.3 重定位节
图4.3.3 重定位节
4.3.4 符号表
图4.3.4 符号表
4.4 Hello.o的结果解析
objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
图4.4.1 hello.o反汇编
hello.s是由汇编语言组成的,相对于计算机能识别的机器级指令,汇编代码仍是抽象语言;而反汇编得到的代码不仅仅有汇编代码,还有机器语言代码。汇编语言可以映射到机器语言。
不同点:
- 操作数上,hello.s中采用的是十进制,反汇编代码中采用的是十六进制
- 分支转移上,hello.s中用段名称来跳转,反汇编代码用具体的地址实现
4.5 本章小结
本章对hello.s进行汇编,得到了hello.o可重定位目标文件,同时分析hello.o的ELF格式,并将hello.o与hello.s进行对比说明区别。
第5章 链接
5.1 链接的概念与作用
概念:链接是处理可重定位文件,把它们的各种符号引用和符号定义转换为可执行文件中的合适信息(一般是虚拟内存地址)的过程。链接又分为静态链接和动态链接,前者是程序开发阶段程序员用ld(gcc实际上在后台调用了ld)静态链接器手动链接的过程,而动态链接则是程序运行期间系统调用动态链接器(ld-linux.so)自动链接的过程。
作用:链接将汇编语言代码彻底转化为机器代码,把可重定位目标文件和命令行参数作为输入,生成可执行目标文件。
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
图5.2.1 链接过程
5.3 可执行目标文件hello的格式
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
图5.3.1 elf头
图5.3.2 节头
图5.3.3 重定位节
图5.3.4 符号表
5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
图5.4.1 Data Dump
由图可知,虚拟空间从0x400000开始
图5.4.2 symbolviewer
与5.3对照发现一致
5.5 链接的重定位过程分析
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
图5.5.1 hello
图5.5.2 hello.o
进行分析比较可以发现二者的差异。
- 就main段看,hello反汇编的代码有确定的虚拟地址,而hello.o反汇编代码中代码的虚拟地址均为0
- Hello反汇编代码与hello.o相比,多了许多节
图5.5.3 更多的节示意
hello重定位的过程:
- 合并相同的节
- 对定义符号进行重定位(确定地址)
- 对引用符号进行重定位(确定地址)
5.6 hello的执行流程
子程序名 ld -2.33.so!_dl_start | 程序地址 0x7fac8adba093 |
ld-2.33.so!_dl_init | 0x7fac8adba0c5 |
hello!_start | 0x4010b8 |
libc-2.33so!__libc_start_main | 0x7f6fe58bd540 |
hello!printf@plt | 0x401024 |
hello!sleep@plt | 0x4010f7 |
hello!getchar@plt | 0x401090 |
libc-2.33.so!exit | 0x7f6fe58b40e0 |
5.7 Hello的动态链接分析
对于动态共享链接库中PIC函数,编译器添加重定位记录,等待动态链接器处理,同时,链接器采用延迟绑定的方法。
5.7.1 在dl_init后,部分数据信息发生变动
5.8 本章小结
在本章中主要介绍了链接的概念与作用,并且详细阐述了hello.o是怎么链接成为一个可执行目标文件的过程,详细介绍了hello.o的ELF格式和各个节的含义,并且分析了hello的虚拟地址空间、重定位过程、执行流程、动态链接过程
第6章 hello进程管理
6.1 进程的概念与作用
概念:进程是操作系统对一个正在运行的程序的一种抽象。
作用:进程可以使CPU并行完成任务,并且高效利用内存和CPU。
6.2 简述壳Shell-bash的作用与处理流程
Shell是用户和操作系统之间完成交互式操作的一个接口程。bash是Linux操作系统的默认shell程序。
Shell是“提供使用者使用界面”的软件,是一个命令行解释器,作用是:保护内核同时帮助使用者向计算机传递交互信息。
处理流程:
1.用户输入命令或程序代码。
2.Shell解析输入信息。
3.若为内置命令,调用内置命令处理函数,否则调用fork( )创建子进程。
4.若为前台运行程序,调用wait()直至前台作业结束,如果为后台命令则,则不等待。
6.3 Hello的fork进程创建过程
一个进程,包括代码、数据和分配给进程的资源。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,包括代码段、段、数据段、共享库以及用户栈,但是两者pid不同。在子进程执行期间,父进程默认选项是显示等待子进程的完成。
以hello为例,在shell输入./hello时,由于不是内置命令,shell就调用fork()来创建一个子进程以执行hello
6.4 Hello的execve过程
通过调用execve(),进程能够以全新程序来替换当前运行的程序。再次过程中,将丢弃旧有程序,进程的栈、数据以及堆段会被新程序所替换。以hello为例,shell的fork子程序调用execve函数,execve函数在当前子进程的上下文中加载运行可执行文件hello,创建一个hello的映像复制的hello拥有相同的PID。只有当出现错误时,例如找不到hello,execve才会返回到调用程序。
6.5 Hello的进程执行
进程是操作系统对一个正在运行的程序的一种抽象。刚开始运行时内核为其保存一个上下文,进程在用户状态下运行。循环结束后,hello 调用 getchar 函数,之前 hello 运行在用户模式下,再调用 getchar 时进入内核模式,执行上下文切换,把控制转移给其他进程。完成键盘输入后,内核从其他进程切换回 hello 进程,最终 hello执行 return终止进程。
6.6 hello的异常与信号处理
可能出现的异常种类:1.终止, 2.故障
可能出现的信号:1.SIGINT信号 2.SIGSTP信号
按下ctrl-z后,shell父进程收到SIGSTP信号,信号处理程序将hello进程挂起,放到后台,停止hello程序
按下ctrl-c后,shell父进程收到SIGINT信号,由信号处理函数结束hello,并回收hello进程
图6.6.1 乱按
图6.6.2 ctrl+c
图6.6.3 ctrl+z及相关命令
6.7本章小结
本章简述了进程的概念与作用及壳Shell-bash的作用与处理流程,分析了hello的fork进程与execve过程,以及其上下文的进程执行和异常信号处理。
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:访问指令给出的地址 (操作数) 叫逻辑地址,也叫相对地址,由选择符和偏移量组成。要经过寻址方式的计算或变换才得到内存储器中的物理地址。
线性地址:线性地址或也叫虚拟地址,逻辑地址经过段机制后转化为线性地址,是一个不真实的地址,线性地址对应硬件页式内存的转换前地址。
虚拟地址:虚拟地址即线性地址,虚拟地址是对物理地址的映射。
物理地址:真实的物理内存对应地址。CPU通过地址总线的寻址,找到真实的物理内存对应地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
实模式下,逻辑地址CS: EA通过CS * 16 + EA转换为物理地址。
保护模式下,以段描述符作为下标,到GDT/LDT表中查表获得段地址,将段地址+偏移地址转换为线性地址。
图7.2.1 段选择符
7.3 Hello的线性地址到物理地址的变换-页式管理
由逻辑地址得到的线性地址一共 32 位。前 10 位是页目录索引,中间 10 位是页表索引,最后 12 位是业内偏移量。由 CR3 寄存器得到页目录基地址,再得到页目录项,由页目录项得到页表基地址,通过基地址得到页表项,最后得到物理地址
图7.3.1 页式管理
7.4 TLB与四级页表支持下的VA到PA的变换
TLB是一个小的、虚拟寻址的缓存,其每一行都保存着一个由单个PTE组成的块。用于组选择和行匹配的索引和标记字段是从虚拟地址的虚拟页号中提取出来的。
图7.4.1 虚拟地址中用以访问TLB的组成部分
图7.4.2 k级页表的地址翻译
若是四级页表,虚拟地址被划分成为 4 VPN 和 1 个 VPO。每个 VPN i 都是一个到第 i 级页表的索引,其中 1⩽𝑖⩽4. j 级页表中的每个 PTE,1⩽𝑗⩽3都指向第 j+1 级的某个页表的基址。第 k 级页表中的每个 PTE 包含某个物理页面的 PPN,或者一个磁盘块的地址。为了构造物理地址,在能够确定 PPN 之前,MMU 必须访问k个PTE。
7.5 三级Cache支持下的物理内存访问
产生虚拟地址请求时,首先去TLB寻找,若命中则在MMU获取;没有命中则根据多级页表,得到物理地址,接下去三级cache一级级寻找。
7.6 hello进程fork时的内存映射
当 fork 函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的 PID。为了给这个新进程创建虚拟内存,它创建了当前进程的 mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。
当 fork 在新进程中返回时,新进程现在的虚拟内存刚好和调用 fork 时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,因此,也就为每个进程保持了私有地址空间的抽象概念。
7.7 hello进程execve时的内存映射
execve 函数在当前进程中加载并运行包含在可执行目标文件 a.out 中的程序,用 a.out 程序有效地替代了当前程序。加载并运行 a.out 需要以下几个步骤:
1.删除已存在的用户区域。删除当前进程虚拟地址的用户部分中的已存在的区域结构。
2.映射私有区域。为新程序的代码、数据、bss 和栈区域创建新的区域结构。所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为 a.out 文件中的. text 和. data 区。bss 区域是请求二进制零的,映射到匿名文件,其大小包含在 a.out 中。栈和堆区域也是请求二进制零的,初始长度为零。图 9-31 概括了私有区域的不同映射。
3.映射共享区域。如果 a.out 程序与共享对象(或目标)链接,比如标准 C 库 libc.so,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内。
4.设置程序计数器(PC)。execve 做的最后一件事情就是设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。
图7.7.1 加载器如何映射用户地址空间
7.8 缺页故障与缺页中断处理
假设MMU试图翻译虚拟地址A触发缺页后,处理程序执行下列步骤:
- 判断虚拟地址A是否合法,若不合法,缺页程序触发段错误终止这一进程;
- 判断试图进行的内存访问是否合法若不合法,缺页处理程序会触发一个保护异常,从而终止这个进程;
- 内核知道了这个缺页是由于对合法的虚拟地址进行合法的操作造成的。此时选择一个牺牲页面,使MMU能正常翻译A
图7.8.1 缺页处理
7.9动态存储分配管理
动态存储分配管理由动态内存分配器完成。动态内存分配器维护着一个进程的虚拟内存区域,称为堆。堆是一个请求二进制零的区域,它紧接在未初始化的数据区后开始,并向上生长(向更高的地址)。分配器将堆视为一组不同大小的块的集合来维护。
每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可以用来分配。空闲块保持空闲,直到它显示地被应用程序所分配。
一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。
动态内存分配器从堆中获得空间,将对应的块标记为已分配,回收时将堆标记为未分配。而分配和回收的过程中,往往涉及到分割、合并等操作。
动态内存分配器的目标是在对齐块的基础上,尽可能地提高吞吐率及空间占用率,即减少因为内存分配造成的碎片。其实现常见的数据结构有隐式空闲链表、显式空闲链表、分离空闲链表,常见的放置策略有首次适配、下一次适配和最佳适配。
7.10本章小结
本章分析了hello的存储管理,介绍了段式管理与页式管理,以及TLB与多级页表支持下的VA到PA的转换,同时对三级Cache支持下的物理内存访问进行了说明。简要说明了hello的fork和execve内存映射,了解了缺页故障与缺页中断处理程序,介绍了动态存储分配管理。
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件
文件包括:普通文件、目录、套接字、命名通道、符号链接以及字符和块设备。
图8.1.1 Linux 目录层次的一部分。尾部有斜杠表示是目录
设备管理:unix io接口
操作包括:打开和关闭文件、读取和写入文件以及改变当前文件的位置。
8.2 简述Unix IO接口及其函数
1.打开文件——open():一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个 I/O 设备。内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件。内核记录有关这个打开文件的所有信息。应用程序只需记住这个描述符。
2.关闭文件——close():当应用完成了对文件的访问之后,它就通知内核关闭这个文件。作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。无论一个进程因为何种原因终止时,内核都会关闭所有打开的文件并释放它们的内存资源。
3.读写文件——read()write():一个读操作就是从文件复制 n > 0 个字节到内存,从当前文件位置 k 开始,然后将 k 增加到 k+n。给定一个大小为 m 字节的文件,当k⩾m时执行读操作会触发一个称为 end-of-file(EOF)的条件,应用程序能检测到这个条件。在文件结尾处并没有明确的 “EOF 符号”。
8.3 printf的实现分析
函数printf的实现过程调用了vsprintf和write函数。
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章简述IO设备管理方法及Unix I/O函数。同时分析了printf和getchar两个函数的实现。
结论
Hello作为每个程序员的初恋,作为一个普通的程序,却走过了复杂的一生,包括预处理、编译、汇编、链接、创建进程、运行程序、执行指令、访问内存、异常信号、Unix I/O以及进程结束。Hello不仅是初恋,更是那白月光,一路走来,却发现它在你的学习中贯穿始终,闪闪发光。
附件
中间文件 | 说明 |
hello.o | 预处理 |
hello.s | 编译 |
hello.o | 汇编 |
hello | 链接后可执行文件 |
hello_elf.txt | Elf文件 |
hello.c | 原文件 |
参考文献
[1] Linux四级页表及其原理 - 简书 (jianshu.com)
[2] 本电子书信息 | 深入理解计算机系统(CSAPP) (gitbook.io)