程序人生-Hello’s P2P

打印 上一主题 下一主题

主题 828|帖子 828|积分 2484




计算机系统


大作业



题     目   程序人生-Hello’s P2P   

专       业     工科试验班(医学类1   

学     号        2022113287       

班     级         2252003         

学       生         王瑛琦        

指 导 教 师          史先俊          


计算机科学与技术学院

2024年5月

摘  要

这篇论文以hello.c程序为基础,通过对CSAPP课程所学知识的整合与应用,深入分析了在Ubuntu虚拟机Linux系统下运行该程序的整个过程,包括hello从hello.c源代码文件经过预处理、编译、汇编、链接、执行、显示以及终止的全程,同时也展示了hello在执行过程中的进程管理、存储管理和I/O管理。通过运用Linux系统工具,本文对hello程序的生命周期进行了详尽的探讨,展现hello的奇幻生命旅程。

关键词:hello,计算机系统,linux,程序,P2P                        










目  录


第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 -
结论......................................................................................................................... - 14 -
附件......................................................................................................................... - 15 -
参考文献................................................................................................................. - 16 -



第1章 概述

1.1 Hello简介

Hello的P2P(From Program to Process)过程:
在编辑器(例如VSCode)中编写Hello代码得到hello.c程序,首先经过预处理器cpp进行预处理,生成文本文件hello.i,再经过编译器ccl生成hello.s汇编程序,然后通过汇编器as生成hello.o文件,最后经由链接器ld将其与引用到的库函数链接,生成可执行目标文件hello。再在shell中启动后,shell调用fork创建子进程,hello从program转变为process。

图 1 Hello's P2P过程

Hello的020(From Zero-0 to Zero-0)过程:
shell为hello调用execve函数,将程序映射至虚拟内存,随后进入程序入口开始将代码载入物理内存;进入main函数执行目标代码,CPU分配时间片以执行程序的逻辑控制流;程序执行完毕后,shell的父进程回收hello进程,释放所占用的内存,并删除相关数据结构。
1.2 环境与工具

硬件环境:处理器:X64 CPU;12th Gen Intel(R) Core(TM) i7-12650H ;2.30 GHz;机带:16G RAM;256G SSD Disk;1T HDD Disk
软件环境:
Windows11家庭中文版 64位;VMware Workstation PRO 17;Ubuntu 20.04.2 LTS 64位
开发/调试工具:Visual Studio 2022;vim/gpedit+gcc/as/ld/edb/readelf;VSCode
1.3 中间结果

文件名

文件的作用

hello.i

预处理后的文件

hello.s

编译之后的汇编文件

hello.o

汇编之后的可重定位目标文件

hello

链接之后的可执行目标文件

elf.txt

hello.o 的 ELF格式

hello_o_disass.s

hello.o 的反汇编代码

elf_hello.txt

hello的ELF 格式

hello_disass.s

hello 的反汇编代码

表 1 hello的中间过程

1.4 本章小结

本章概要阐述了hello的P2P与020过程,同时列举了大作业所涉及的软硬件环境和开发工具。此外,还总结了操作过程中产生的中间结果。



第2章 预处理

2.1 预处理的概念与作用

预处理,也称为预编译,在C/C++中指的是在程序编译之前,根据以字符#开头的命令(如头文件和宏定义)对原始的C程序进行修改。

预处理的作用包括:
- 将源文件中使用"include"格式包含的文件内容复制到编译的源文件中。
- 用实际值替换通过"#define"定义的字符串或宏。
- 根据"#if"后面的条件判断,决定需要编译的代码块。
2.2在Ubuntu下预处理的命令

命令:gcc -E hello.c -o hello.i
其中gcc指使用gcc编译器,-E参数指对文件进行预处理操作,-o选项用来指定输出文件为hello.i。最后就生成了hello.i文件。

图 2 预处理过程

2.3 Hello的预处理结果解析

观察发现,其中的注释已经消失。#include <stdio.h> #include <stdlib.h> #include <unistd.h>三个头文件消失,替代的是一大段代码,描述的是运行库在计算机中的位置,方便下一步翻译成汇编语言。程序的最后一部分与hello.c中的main函数完全相同。




图 3 hello.i中的库



图 4 hello.i中的hello.c代码



2.4 本章小结

这一节着重介绍了预处理的定义和实际应用,同时演示了对于hello.c文件的预处理操作,生成了名为hello.i的文本文件,并对其进行了详细分析,深入探讨了预处理的意义和作用。

第3章 编译

3.1 编译的概念与作用

编译:是利用编译器将源语言编写的程序转换成目标程序的过程,是将高级语言转换为计算机可识别的语言的操作。

其作用包括:将高级程序设计语言编写的源程序翻译成等效的计算机汇编或机器语言书写的目标程序。编译器以高级程序设计语言书写的源程序为输入,产出汇编或机器语言表示的目标程序作为输出。

注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序。
3.2 在Ubuntu下编译的命令

命令:gcc -S hello.i -o hello.s

-S为编译,-o为命令产生的文件命名。
下图为生成hello.s过程:


图 5 编译过程

3.3 Hello的编译结果解析

3.3.1 数据


  • 常量
字符串:如下两个函数中的字符串被存储在 .rodata节中


图 6 printf函数

存储如下:


图 7 字符串常量存储



  • 变量

  • 全局变量
    初始化的全局变量储存在.data节,sleepsecs全局变量被存放在.data节


图 8 sleepsecs存储位置


  • 局部变量
    局部变量存储在寄存器或栈中。程序中有局部变量int i

    在汇编代码中如下:


图 9 局部变量i存储位置

    i被存储在栈中,-4(%rbp)的位置。
3.3.2 操作


  • 算术操作
在循环操作中,使用 i++ 自增操作,每次循环结束后对 i 加1,对栈上存储i 的位置加1;

图 10 i的自增操作



  • 关系操作

  • 程序第16行中判断argc是否等于3,源代码为:

图 11 判断argc源代码

汇编代码为:

图 12 判断argc汇编代码


  • 程序第21行中判断 i 是否小于10,源代码为:

图 13 判断i源代码

    汇编代码为,汇编优化为 i <= 9:

图 14 判断i汇编代码



  • 控制转移
在使用比较关系操作进行判断后,程序将按判断结果经如下代码跳转至L2或L4,进入if语句或继续进入循环

图 15 if语句跳转


图 16 for语句跳转


  • 数组/指针/结构操作
主函数main的参数中有指针数组char *argv[],argv[0]指向输入程序的路径和名称,argv[1]和argv[2]分别表示两个字符串。


图 17 main函数参数存储


图 18 argv数组实现汇编代码


  • 函数操作

  • main函数
参数传递:传入参数argc,argv[],分别用寄存器%edi和%rsi存储
函数调用:被系统启动函数调用
函数返回:设置%eax为0,并返回

图 19 main函数返回



  • printf函数

  • call   puts@PLT
参数传递:传入字符串参数首地址
函数调用:if判断满足条件后被调用

图 20 call puts汇编代码


  • call   printf@PLT
参数传递:传入 argv[1]和argc[2]的地址
函数调用:for循环中被调用

图 21 call printf汇编代码


  • exit函数
参数传递:传入参数1
函数调用:if判断满足条件后被调用

图 22 exit函数汇编代码


  • sleep函数
参数传递:传入参数全局变量sleepsecs
函数调用:for循环中被调用

图 23 sleep函数汇编代码


  • getchar函数
函数调用:在for循环结束后被调用

图 24 getchar函数汇编代码


3.4 本章小结

本章着重介绍了编译的定义和整个过程,并对hello进行编译转换为汇编代码。通过对生成的文件进行分析,详细解释了汇编代码如何实现变量、常量、参数传递以及控制结构中的分支和循环。编译器在确认所有指令符合语法规则后,将其转换为等效的中间代码或汇编代码表示。


第4章 汇编

4.1 汇编的概念与作用

汇编的概念:汇编是经由汇编器将汇编语言转化为机器语言的步骤。其功能在于将汇编代码转换成计算机能够直接理解的机器代码,将指令整合成可移植的目标程序格式,并将转换结果保存在.o目标文件中。它是一个二进制文件,包含程序的指令编码。

汇编的作用:完成从汇编语言文件到可重定位目标文件的转化过程。

注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。
4.2 在Ubuntu下汇编的命令

命令:as hello.s -o hello.o 或 gcc -c hello.s -o hello.o

其中,##-c为汇编,-o为命令产生的文件命名



图 25 汇编过程




4.3 可重定位目标elf格式


  • 命令:readelf -a hello.o > ./elf.txt


图 26 生成elf文件


  • ELF文件头
ELF头部记录了文件的整体结构。它以一个16字节的序列开头,其中包含了生成该文件的系统的字大小和字节顺序信息。其余部分包括ELF头的大小、目标文件类型、机器类型、节头部表的文件偏移,以及节头部表中条目的大小和数量等内容。这些信息有助于链接器对目标文件进行语法分析和解释。


图 27 ELF头文件内容



  • 节头部表
描述了.o文件中出现的各个节的类型、位置、所占空间大小等信息。

图 28 节头部表内容




  • 重定位节
表述了各个段引用的外部符号等,在链接时,需要通过重定位节对这些位置的地址进行修改。链接器会通过重定位条目的类型判断该使用什么养的方法计算正确的地址值,通过偏移量等信息计算出正确的地址。

  .rel.text表示可重定位代码,它是一个.text节中位置的列表,当后续链接器把这个目标文件和其它文件组合时,需要修改这些位置。


图 29 重定位节内容




  • 符号表
    存放在程序中定义和引用的函数和全局变量的信息。

图 30 符号表内容



4.4 Hello.o的结果解析

命令:objdump -d -r hello.o > hello_o_disass.s



图 31 反汇编hello.o



图 32 反汇编文件内容


分析hello.o的反汇编,与第3章的 hello.s进行对照分析:


  • 代码左边多了机器码;
  • hello.s中的操作数为十进制,hello.o反汇编代码中的操作数为十六进制;
3、call跳转指令,在hello.s文件中,直接加上跳转函数名,在反汇编文件中,加上了跳转的相对偏移地址,函数在链接之后才能确定执行的地址,因此在.rela.text节中为其添加了重定位条目。说明机器语言的构成,与
4.5 本章小结

理解汇编的概念和作用后,进行汇编得到.o文件,然后对可重定位目标ELF格式进行分析。接着,使用objdump进行反汇编,并将其与.s文件进行比较,以更深入地理解机器语言与汇编语言之间的关系。



第5章 链接

5.1 链接的概念与作用

链接是将各种不同文件的代码和数据部分收集并组合成一个单一文件的过程。

作用:将源程序与为了节省空间而未编入的常用函数文件进行合并,生成可以正常工作的可执行文件。令分离编译成为可能,节省了大量的工作空间。

注意:这儿的链接是指从 hello.o 到hello生成过程。

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



图 33 链接操作

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



  • 命令:readelf -a hello > elf_hello.txt

图 34 生成hello的elf文件


  • ELF文件头

图 35 ELF文件头



  • 节头部表

 

  

图 36 节头部表(1)            图 37 节头部表(2)

4、程序头部表
        程序头部表,也称段头部表,如图38所示,它描述了可执行文件的连续片与连续的内存段之间的映射关系,主要包括所指向段的类型、其在ELF文件中的偏移地址、物理地址、映射到内存的虚拟地址、段的读写权限、对齐方式等。

图 38 程序头部表



5、符号表


图 39 符号表  


5.4 hello的虚拟地址空间

使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。  


图 40 用edb加载hello


图 41 虚拟地址空间信息

查看 ELF 格式文件中的 Program Headers,它告诉链接器运行时加载的内容,并提供动态链接的信息。每一个表项提供了各段在虚拟地址空间和物理地址空间的各方面的信息。

程序包含:

PHDR 保存程序头表;

INTERP 指定在程序已经从可执行文件映射到内存之后,必须调用的解释器;

LOAD 表示一个需要从二进制文件映射到虚拟地址空间的段。其中保存了常量数据、程序的目标代码等;

DYNAMIC 保存了由动态链接器使用的信息;

NOTE 保存辅助信息;

GNU_STACK,权限标志,用于标志栈是否是可执行;

GNU_RELRO,指定在重定位结束之后哪些内存区域是需要设置只读。


5.5 链接的重定位过程分析

1、命令:objdump -d -r hello > hello_disass.s


图 42 反汇编hello指令


图 43 hello反汇编代码


2、分析hello与hello.o的不同:

①在hello.o中,地址为相对偏移地址;在hello中,地址为可以由CPU直接访问的虚拟内存地址;

②hello的反汇编文件比hello.o的反汇编文件多了_init,.plt,puts@plt,printf@plt,getchar@plt,exit@plt,sleep@plt,_start,_dl_relocate_static_pie,__libc_csu_init,__libc_csu_fini,_fini等节和需要用到的库函数;

③hello.o将lea后的操作数置为0,并添加重定位条目。


3、链接的过程:

链接器将各个目标文件组装在一起,文件中的各个函数段按照一定规则累积在一起。


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

①重定位节和符号定义,连接器将所有相同类型的节合并为同一类型的新的节,然后将运行内存地址赋给这个新的节和输入模块定义的每个节以及输入模块定义的每个符号;

②重定位节中的符号引用,连接器修改代码节和数据节中对每个符号的引用,使之指向正确的运行地址。

5.6 hello的执行流程

使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。列出其调用与跳转的各个子程序名或程序地址:
子程序名

程序地址

_init

0x401000

_start

0x4010f0

__libc_csu_init

0x401190

_init

0x 401000

main

0x401125

puts@plt

0x401148

exit@plt

0x4010d0

_fini

0x4011c8


5.7 Hello的动态链接分析

   在elf文件中:



图 44 hello的elf文件内容


进入edb查看:


图 45 edb执行init前的地址



图 46 edb执行init后的地址

一开始地址的字节都为0,调用_init函数之后GOT内容产生变化,指向正确的内存地址,下一次调用跳转时可以跳转到正确位置。



5.8 本章小结

在这章中,我们了解了链接的功能,通过对hello.o进行链接获得hello文件,并对其进行了分析。通过将hello文件与hello.o的反汇编代码进行比较,更深入地理解了重定位的过程。

第6章 hello进程管理

6.1 进程的概念与作用

概念:进程是一个执行中的程序的实例。

作用:通过进程,我们会得到一种假象,好像我们的程序是当前唯一运行的程序,我们的程序独占处理器和内存,我们程序的代码和数据好像是系统内存中唯一的对象。

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

作用: shell是一个命令解释器,它解释用户输入的命令并把它们送到内核,用于用户和系统的交互。

处理流程:


  • Shell首先从命令行中找出特殊字符(元字符),在将元字符翻译成间隔符号。元字符将命令行划分成小块tokens。Shell中的元字符如下:SPACE , TAB , NEWLINE , & , ; , ( , ) ,< , > , |
  • 程序块tokens被处理,检查看他们是否是shell中所引用到的关键字。
  • 当程序块tokens被确定以后,shell根据aliases文件中的列表来检查命令的第一个单词。如果这个单词出现在aliases表中,执行替换操作并且处理过程回到第一步重新分割程序块tokens。
  • Shell对~符号进行替换。
  • Shell对所有前面带有$符号的变量进行替换。
  • Shell将命令行中的内嵌命令表达式替换成命令;他们一般都采用$(command)标记法。
  • Shell计算采用$(expression)标记的算术表达式。
  • Shell将命令字符串重新划分为新的块tokens。这次划分的依据是栏位分割符号,称为IFS。缺省的IFS变量包含有:SPACE , TAB 和换行符号。
  • Shell执行通配符* ? [ ]的替换。
  • shell把所有从处理的结果中用到的注释删除,並且按照下面的顺序实行命令的检查:A. 内建的命令B. shell函数(由用户自己定义的)C. 可执行的脚本文件(需要寻找文件和PATH路径)
  • 在执行前的最后一步是初始化所有的输入输出重定向。
  • 最后,执行命令。
6.3 Hello的fork进程创建过程

shell判断出不是内置命令后,加载可执行文件hello,通过fork创建子进程,子进程得到一份与父进程用户级虚拟地址空间相同的副本,还获得与父进程打开的文件描述符相同的副本,子进程与父进程的PID不同。fork被调用一次,但返回两次,父进程中返回子进程的PID,子进程返回0。

6.4 Hello的execve过程

execve函数在当前进程的上下文中加载并运行可执行目标文件hello,且带参数列表argv和环境变量列表envp。只有当出现错误时,例如找不到hello文件,execve才会返回到调用程序。所以与fork一次调用返回两次不同,execve调用一次并从不返回。


6.5 Hello的进程执行

内核调用hello的进程开始进行,输出Hello与之前输入的内容,然后执行sleep函数,这个函数是系统调用,它请求让调用进程休眠。内核转而执行其他进程,这时就会发生一个上下文转换。2s后,又会发生一次进程转换,恢复hello进程的上下文,继续执行hello进程,重复这个过程。

循环结束后,后面执行到getchar函数,这时读取数据需要很长的时间,所以将会发生上下文切换转而执行其他进程,当数据被读取到缓存区后,会发生中断,使内核发生上下文切换,重新执行hello进程。

一个进程执行它的控制流的一部分的每一时间段叫做时间片。内核为每个进程维持一个上下文,系统中的每个程序都运行在某个进程的上下文中,进程上下文信息就是内核重新启动一个被抢占的进程所需的状态,它由一些对象的值组成,包括寄存器、程序计数器、用户栈等。

下图为上下文切换的机制(本图源自ppt课件):

图 47 上下文切换机制


6.6 hello的异常与信号处理

hello执行过程中会出现4类异常:

类别

原因

异步/同步

返回行为

中断

来自I/O设备的信号

异步

总是返回到下一条指令

陷阱

有意的异常

同步

总是返回到下一条指令

故障

潜在可恢复的错误

同步

可能返回到当前指令

终止

不可恢复的错误

同步

不会返回


信号处理方式:


图 48 中断处理


图 49 陷阱处理



图 50 故障处理


图 51 终止处理

程序运行过程中,分别进行如下操作:

  • 回车
回车不影响正常运行,只是插入了空行。



图 52 回车运行结果


  • Ctrl-Z
进程收到SIGTSTP信号,暂时挂起,输入ps命令符查看PID发现hello的PID为5684,hello进程还没有被关闭,输入fg命令恢复后台进程。

输入kill命令后,进程被终止,输入ps命令进程hello不存在。


图 53 Ctrl-Z运行结果

输入pstree查看进程树:

图 54 进程树(部分)



  • Ctrl-C
进程收到SIGINT信号,进程结束,输入ps命令没有hello。


图 55 Ctrl-Z运行结果


  • 不停乱按
输入的字符被保存在缓冲区,被认为是命令,在程序结束后执行。


6.7本章小结

在这一部分,我们学习了hello进程的概念以及shell的功能。同时,我们分析了hello进程的执行过程,探讨了fork和execve的作用。此外,我们了解了信号处理,并深入研究了hello进程如何在内核和前端之间反复跳跃执行的情况。

第7章 hello的存储管理

7.1 hello的存储器地址空间


  • 逻辑地址:是由程序产生的与段相关的偏移地址部分。
  • 线性地址:逻辑地址经过段机制后转化为线性地址,为(描述符:偏移量)的组合形式。分页机制中线性地址作为输入。
  • 虚拟地址:有时也把逻辑地址叫做虚拟地址。
  • 物理地址:计算机系统的主存被组织成一个由M个连续的字节大小的单元组成的数组。每个字节都有唯一的物理地址。CPU通过地址总线的寻址,找到真实的物理内存对应地址。 CPU对内存的访问是通过连接着CPU和北桥芯片的前端总线来完成的。在前端总线上传输的内存地址都是物理内存地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理

一个逻辑地址由两部份组成,段标识符: 段内偏移量。可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,Base字段表示包含段的首字节的线性地址,也就是一个段的开始位置的线性地址。


  • 先检查段选择符中的TI字段,以决定段描述符保存在哪一个描述符表中;
  • 由于一个段描述符是8字节长,因此她在GDT或LDT内的相对地址是由段选择符的最高13位的值乘以8得到;
  • Base + offset,得到要转换的线性地址。
7.3 Hello的线性地址到物理地址的变换-页式管理

真正的物理地址需要经过线性地址一系列的映射才会到真正的物理地址处。一般是通过三级或者四级映射,即10-10-12或者2-9-9-12。

页表是一个存储索引号的表,每个元素是4B,对于10-10-12而言,第一个10bit用210


就可以表示就可以表示全,第二个也是如此,这样一来,一个一级页表是4 * 210


B 大小,也就是4KB的大小,一级页表都可以索引一个二级页表,一个二级页表也是4KB大小,二级页表大小是210


 * 4KB ,占了4MB。

通过每一个页表可以确定下一个表的基地址,而线性地址中的每一个部分则是对这个地址所在的表的一个查询工作。

页式管理是一种内存空间存储管理的技术,页式管理分为静态页式管理和动态页式管理。将各进程的虚拟空间划分成若干个长度相等的页(page),页式管理把内存空间按页的大小划分成片或者页面(page frame),然后把页式虚拟地址与内存地址建立一一对应页表,并用相应的硬件地址变换机构,来解决离散地址变换问题。页式管理采用请求调页或预调页技术实现了内外存存储器的统一管理。

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

先将VA中的VPN分成三段,根据TLBT和TLBI,在TLB中寻找对应的PPN,如果没有找到,即为缺页,就需要到页表中去找。

接着将VPN分成更多段(4段),CR3是对应的L1PT的物理地址,然后一步步递进往下寻址,越往下一层每个条目对应的区域越小,寻址越细致,在经过4层寻址之后找到相应的PPN然后和VPO拼接起来得到PA。
7.5 三级Cache支持下的物理内存访问

得到物理地址PA之后,根据cache大小组数的要求,将PA拆分成CT(标记)、CI(索引)、CO(偏移量),用CI位进行索引,如果匹配成功且valid值为1,则为命中,根据偏移量在L1cache中取数;如果未命中就去二级和三级cache中重复以上步骤,命中后返回结果。
7.6 hello进程fork时的内存映射

mm_struct(内存描述符):描述了一个进程的整个虚拟内存空间。

vm_area_struct(区域结构描述符):描述了进程的虚拟内存空间的一个区间。

在用fork创建虚拟内存的时候,要经历以下步骤:


  • 创建当前进程的mm_struct,vm_area_struct和页表的原样副本。
  • 两个进程的每个页面都标记为只读页面。
两个进程的每个vm_area_struct都标记为私有,这样就只能在写入时复制。
7.7 hello进程execve时的内存映射

exceve函数加载和执行程序Hello,需要以下几个步骤:

1.删除已存在的用户区域。

2.创建新的私有区域(.malloc,.data,.bss,.text)。

3.创建新的共享区域(libc.so.data,libc.so.text)。

4.设置程序计数器PC,指向代码的入口点。

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

先判断这个缺页的虚拟地址是否合法,那么遍历所有的合法区域结构,如果这个虚拟地址对所有的区域结构都无法匹配,那么就返回一个段错误(segment fault)。接着查看这个地址的权限,判断一下进程是否有读写改这个地址的权限。若都不是,就是正常缺页,则选择一个页面牺牲然后换入新的页面并更新到页表。

7.9动态存储分配管理

基本方法:维护一个虚拟内存区域“堆”,分配器将堆视为一组不同大小的块的集合来维护,每个块要么是已分配的,要么是空闲的,需要时选择一个合适的内存块进行分配。

策略:


  • 记录空闲块,可以选择隐式空闲链表,显示空闲链表,分离的空闲链表和按块大小排序建立平衡树。
  • 放置策略,可以选择首次适配,下一次适配,最佳适配。
  • 合并策略,可以选择立即合并,延迟合并。
7.10本章小结

在这一章节中,我们探讨了hello程序的存储器地址空间,介绍了Intel处理器的段式管理以及hello程序的页式管理。我们详细讲解了在特定环境下虚拟地址(VA)到物理地址(PA)的转换过程,还包括了进程fork和execve时的内存映射机制。此外,我们讨论了缺页问题和动态存储分配管理所涉及的主要内容。


第8章 hello的IO管理

8.1 Linux的IO设备管理方法

设备的模型化:文件

文件的类型:


  • 普通文件(regular file):包含任意数据的文件。
  • 目录(directory)(文件夹):包含一组链接的文件,每个链接都将一个文件名映射到一个文件。
  • 套接字(socket):用来与另一个进程进行跨网络通信的文件
  • 命名通道
  • 符号链接
  • 字符和块设备
设备管理:unix io接口


  • 打开和关闭文件
  • 读取和写入文件
  • 改变当前文件的位置
8.2 简述Unix IO接口及其函数

Unix IO接口:
Unix I/O通过统一的方式管理所有的输入输出。
首先是打开文件。应用程序请求内核打开对应的文件,以表明其希望访问某个I/O设备,内核则返回一个描述符,用于在后续操作中标识这个文件。
接下来是改变当前文件位置。对于每个打开的文件,内核维护一个文件位置k,初始值为0,表示从文件开头的字节偏移量。
随后是读写文件。读取操作会将n > 0个字节从文件复制到内存,从当前文件位置k开始,然后将k增加到k + n。类似地,写操作将n > 0个字节从内存复制到文件中,同样从当前文件位置k开始,然后更新k。
最后是关闭文件。当应用程序完成对文件的访问后,通知内核关闭该文件。内核响应后释放文件打开时创建的数据结构,并将描述符恢复到可用的描述符池中。
其函数如下:

  • 打开文件:open()函数
打开一个已经存在的文件,若文件不存在则创建一个新的文件。


  • 关闭文件:close()函数
通知内核关闭这个文件,内核会释放文件打开时创建的数据结构,将描述符恢复到描述符池中。


  • 读取文件:read()函数
从当前文件位置复制字节到内存位置,如果返回值<0则说明出现错误。


  • 写入文件:write()函数
从内存复制字节到当前文件位置,如果返回值<0则说明出现错误。


  • 改变文件位置:lseek()函数
文件开始位置为文件偏移量,应用程序通过seek操作,可设置文件的当前位置为k。

8.3 printf的实现分析

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;

}


printf程序按照格式fmt结合参数args生成格式化之后的字符串,并返回字串的长度。printf用了两个外部函数,一个是vsprintf,还有一个是write。


所引用的vsprintf函数:
int vsprintf(char *buf, const char *fmt, va_list args) 


    char* p; 

    char tmp[256]; 

    va_list p_next_arg = args; 

    for (p=buf;*fmt;fmt++) { 

        if (*fmt != '%') {

            *p++ = *fmt; 

            continue

        }

        fmt++;

        switch (*fmt) { 

        case 'x': 

            itoa(tmp, *((int*)p_next_arg)); 

            strcpy(p, tmp); 

            p_next_arg += 4; 

            p += strlen(tmp); 

            break

        case 's': 

            break

        default

            break

        } 

    } 

    return (p - buf); 

}

vsprintf函数的作用是将所有的参数内容格式化之后存入buf,然后返回格式化数组的长度。

write函数是将buf中的i个元素写到终端的函数。

从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.

字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。

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

8.4 getchar的实现分析

getchar调用了一个read函数,将整个缓冲区都读到了buf里面,然后返回缓冲区的长度。如果buf长度为0,getchar才会调用read函数,否则是直接将保存的buf中的最前面的元素返回。

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

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

8.5本章小结

这章涵盖了Linux I/O设备的核心概念和管理机制,以及Unix I/O接口及其函数。我们还深入分析了printf函数和getchar函数的工作原理。

结论

hello经历过程:



    • 预处理:将hello.c调用的所有外部的库拓展到hello.i文件中;
    • 编译:将hello.i编译得到汇编代码文件hello.s;
    • 汇编:将hello.s汇编成为二进制可重定位目标文件hello.o;
    • 链接:将hello.o与可重定位目标文件和动态链接库链接成为可执行目标程序hello;
    • 运行:shell中运行hello;
    • 创建子进程:shell进程调用fork函数创建子进程;
    • 运行程序:shell调用execve函数,execve调用启动加载器,映射虚拟内存,进入程序入口后程序开始载入物理内存,然后进入 main函数;
    • 执行指令:CPU为其分配时间片,在一个时间片中,hello享有CPU资源,顺序执行自己的控制逻辑流;
    • 访问内存:MMU将程序中使用的虚拟内存地址通过页表映射成物理地址;
    • 动态申请内存:printf会调用malloc向动态内存分配器申请堆中的内存;
    • 信号:如果运行途中键入Ctrl-C、Ctrl-Z则调用shell的信号处理函数分别停止、挂起;
    • 结束:shell父进程回收子进程,内核删除为这个进程创建的所有数据结构。


感悟:

计算机系统的涉及与实现远比我想象中要复杂的多得多,我们日常生活中在使用计算机的每一个看似简单的功能,其背后实现所涉及的知识原理内容都相当的复杂。Hello的一生告诉我们,计算机科学的领域,没有顺理成章,没有理所当然,一切看似容易的操作都建立在前人伟大而巧妙的构思之上。计算机领域的学习需要潜心深入、止于至善。
附件

文件名

文件的作用

hello.i

预处理后的文件

hello.s

编译之后的汇编文件

hello.o

汇编之后的可重定位目标文件

hello

链接之后的可执行目标文件

elf.txt

hello.o 的 ELF格式

hello_o_disass.s

hello.o 的反汇编代码

elf_hello.txt

hello的ELF 格式

hello_disass.s

hello 的反汇编代码


参考文献


[1] 袁春风. 计算机系统基础. 北京:机械工业出版社,2018.7(2019.8重印)
[2] Randal E. Bryant;David R. O’Hallaron. 深入理解计算机系统. 北京:机械工业出版社,2016.7(2019.3重印)
[3] 局部变量是存放在栈中,还是存放在堆栈中?_百度知道 (baidu.com)
[4] sourceware.org Git - binutils-gdb.git/commitdiff
[5] 一个简单程序从编译、链接、装载(执行)的过程 - 知乎 (zhihu.com)

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

民工心事

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表