哈尔滨工业大学 计算机系统大作业 程序人生-Hello’s P2P

计算机系统

大作业

题          目  程序人生-Hello’s P2P 

专           业  某计算机学科专业    

学      号  202211****               

班     级  *******                       

学            生  lch                     

指 导 教 师  刘宏伟                     

计算机科学与技术学院

2024年5月

摘  要

    本文深入分析了"hello"程序在计算机系统中从编写到执行的完整生命周期,揭示了操作系统在程序的预处理、编译、汇编、链接、执行过程及其存储、与IO设备交互中的关键作用和原理。

    文章首先概述了hello的编写背景和环境配置,随后阐述了hello的预处理过程,包括头文件的包含和宏定义的展开。接着文章深入探讨了编译阶段,分析了源代码如何被转换成汇编代码,并进一步解析了数据和操作的编译结果。在汇编阶段,文章解释了如何将汇编代码转换为机器代码,并讨论了可重定位目标文件的ELF格式。链接阶段则重点分析了静态链接和动态链接的区别和实现过程。文章还涵盖了程序的进程管理,包括进程创建、执行流程和信号处理。存储管理部分深入讨论了程序的内存映射、页表转换、缓存机制和动态存储分配。最后,本文探讨了Linux的IO设备管理和Unix IO接口,以及标准IO函数如printf和getchar的实现。

    文章采用了逐步分析的方法,对hello执行每个阶段进行了详细的解析和讨论,通过实际的命令行操作和系统调用展示了hello在各个阶段的具体表现和操作系统的相应处理。

    文章完整地展示了hello的一生,并深入分析了操作系统在hello执行中的工作原理。通过分析预处理、编译、汇编和链接的不同阶段,本文揭示了程序代码如何逐步转化为可执行文件,同时提供了对操作系统资源管理和程序执行机制的全面解析。

    本文对计算机系统的程序执行原理提供了详尽的解析,通过hello为深入认知和理解计算机系统提供了实际范例,有助于以后对其他应用程序及更高级知识的理解和掌握。

关键词:hello的一生;预处理;编译;汇编;链接;进程;存储;IO管理;

目  录

第1章 概述... - 4 -

1.1 Hello简介... - 4 -

1.2 环境与工具... - 4 -

1.3 中间结果... - 5 -

1.4 本章小结... - 5 -

第2章 预处理... - 6 -

2.1 预处理的概念与作用... - 6 -

2.2在Ubuntu下预处理的命令... - 6 -

2.3 Hello的预处理结果解析... - 7 -

2.4 本章小结... - 8 -

第3章 编译... - 9 -

3.1 编译的概念与作用... - 9 -

3.2 在Ubuntu下编译的命令... - 9 -

3.3 Hello的编译结果解析... - 9 -

3.4 本章小结... - 12 -

第4章 汇编... - 13 -

4.1 汇编的概念与作用... - 13 -

4.2 在Ubuntu下汇编的命令... - 13 -

4.3 可重定位目标elf格式... - 13 -

4.4 Hello.o的结果解析... - 15 -

4.5 本章小结... - 15 -

第5章 链接... - 16 -

5.1 链接的概念与作用... - 16 -

5.2 在Ubuntu下链接的命令... - 16 -

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

5.4 hello的虚拟地址空间... - 19 -

5.5 链接的重定位过程分析... - 20 -

5.6 hello的执行流程... - 21 -

5.7 Hello的动态链接分析... - 22 -

5.8 本章小结... - 23 -

第6章 hello进程管理... - 24 -

6.1 进程的概念与作用... - 24 -

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

6.3 Hello的fork进程创建过程... - 24 -

6.4 Hello的execve过程... - 25 -

6.5 Hello的进程执行... - 25 -

6.6 hello的异常与信号处理... - 25 -

6.7本章小结... - 26 -

第7章 hello的存储管理... - 27 -

7.1 hello的存储器地址空间... - 27 -

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

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

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

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

7.6 hello进程fork时的内存映射... - 29 -

7.7 hello进程execve时的内存映射... - 29 -

7.8 缺页故障与缺页中断处理... - 29 -

7.9动态存储分配管理... - 30 -

7.10本章小结... - 30 -

第8章 hello的IO管理... - 31 -

8.1 Linux的IO设备管理方法... - 31 -

8.2 简述Unix IO接口及其函数... - 32 -

8.3 printf的实现分析... - 32 -

8.4 getchar的实现分析... - 32 -

8.5本章小结... - 33 -

结论... - 34 -

附件... - 35 -

参考文献... - 36 -

第1章 概述

1.1 Hello简介

Hello的P2P过程:

  • Hello被保存为hello.c文件(Program);
  • gcc调用预处理器(cpp)进行预处理(将头文件插入到源文件中、展开宏定义等)生成hello.i文件;
  • gcc调用编译器(cc1)进行编译(将源代码转换为汇编代码)生成hello.s文件;
  • gcc调用汇编器(as)进行汇编(将汇编代码转换为二进制的机器代码)生成hello.o文件;
  • gcc调用连接器(ld)进行链接(将目标文件和其他库文件链接)生成hello可执行文件;
  • 执行该文件系统会为其生成一个进程(Process)。

Hello的020过程:

  • Hello未执行时在系统中无进程(zero-0);
  • 在Shell中执行该程序,Shell调用fork函数创建一个子进程;
  • Shell调用execve和mmap函数加载程序、创建其虚拟内存的映射;
  • Shell载入物理内存,进入main函数执行代码,CPU分配时间片;
  • 存储管理(OS)通过TLB、4级页表、3级Cache,Pagefile等方式将Hello完全存放于Cache或页表等能够快速访存的部分中以加快访问速度;
  • IO管理和信号处理部分处理收到的键盘等发出的信号,并通过屏幕等外部设备输出信号;
  • 程序结束后Shell的父进程回收Hello子进程,从存储中删除Hello的相关数据、释放其资源,Hello的一生结束,不留下任何进程和数据(zero-0)。

1.2 环境与工具

硬件环境:VMware Workstation 17 Player虚拟机,内存4GB,处理器intel core i9-12900HX,硬盘30GB。

软件环境:Ubuntu22.04 64位。

开发调试工具:终端、文本编辑器、gdb、edb。

1.3 中间结果

hello.c——hello的C语言源文件。

hello.i——hello经过预处理操作后得到的预处理文件。

hello.s——hello经过编译操作得到的汇编语言文件。

hello.o——hello经过汇编操作得到的二进制机器语言文件。

hello——hello经过链接后得到的可执行文件。

1.4 本章小结

       本章主要对本文的主要任务进行了概述,对hello的简介进行了分析,并列出了为撰写本文进行hello分析过程中的环境及工具和中间结果。

第2章 预处理

2.1 预处理的概念与作用

概念:预编译器(cpp)根据以字符#开头的命令对hello.c文件进行头文件展开、宏替换、条件编译等操作并去注释,最终得到预处理文件hello.i。

作用:

  • 处理包含的头文件:对于源程序中的#include指令,读取对应头文件的内容并把它们直接插入到程序文本中;
  • 处理宏定义:对于#define指令,进行宏替换,用所定义的值替换对应的符号;
  • 处理注释:删除源程序中的注释内容,用空格代替;
  • 处理条件编译:根据条件编译指令,如#if、#elif、#else等,按条件选择符合的代码送至编译器编译,从而有选择地执行相应操作;
  • 处理一些特殊控制指令,如#error等。

2.2在Ubuntu下预处理的命令

预处理命令:gcc -E hello.c -o hello.i

图 1 预处理命令

2.3 Hello的预处理结果解析

预处理生成文件hello.i如图2、图3所示。通过hello.i与hello.c的对比可知,预处理操作后hello从hello.c文件中的24行转变为hello.i中的3092行,源文件中的注释被删除,头文件stdio.h、unistd.h和stdlib.h的内容被依次插入到了源文件中,如一系列外部库.h文件路径、使用typedef将标准数据类型替换为头文件中使用的类型定义、内部函数声明等。最后的主体代码不变。

图 2 hello.c与hello.i对照1

图 3 hello.c与hello.i对照2

2.4 本章小结

本章主要介绍了hello的预处理过程。首先本章介绍了预处理的概念和作用,通过预处理命令对hello.c文件进行预处理操作生成了hello.i文件,并对预处理生成的hello.i文件进行了分析,说明了预处理对hello.c的作用。

第3章 编译

3.1 编译的概念与作用

概念:编译器(ccl)将预处理后的文件hello.i进行词法分析、语法分析、语义分析和优化,生成汇编代码文件hello.s。

作用:对源文件进行分析和优化,检查其是否有误。将不同高级语言的文件转换为基本相同的汇编语言文件,便于机器进一步翻译理解。

3.2 在Ubuntu下编译的命令

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

图 4 编译命令

3.3 Hello的编译结果解析

       3.3.1数据

    1. 常量

      如图5中黄色部分所示,hello.c中第14行argc与常量5作比较,常量5 在hello.s中第24行编译为立即数5;hello.c中第15行printf打印的字符串在 编译后保存在.LCD0中;hello.c中第19行printf打印的字符串在编译后保存  在.LCD1中。

    1. 变量

      如图5中绿色部分所示,hello.c中第12行定义变量i作为循环变量所使用,编译后改变量被保存在栈中,具体栈地址为-4(%rbp)。

图 5 hello.c与hello.s数据对照

3.3.2操作

  1. 赋值操作

如图6中黄色部分所示,hello.c中第18行将值0赋给变量i,编译后在hello.s中第32行将立即数0转移到-4(%rbp)即保存i的栈地址中。

  1. 算数操作

如图6中绿色部分所示,hello.c中第18行的for循环每次进行i+1操作,编译后在hello.s中第56行使用addl指令向-4(%rbp)即保存i的栈地址加立即数1。

  1. 关系操作和控制操作

如图6中蓝色部分所示,hello.c中第14行if的判断条件为argc不等于5,编译后在hello.s中第24、25行使用cmpl和je指令实现argc不等于5则不跳转操作;hello.c中第18行for循环的循环条件为i小于10,编译后转换为小于等于关系在hello.s中第58、59行使用cmpl和jle指令实现i小于等于9跳转即循环操作。

  1. 数组/指针操作

如图6中白色部分所示,hello.c中第11行main函数的参数中存在一个字符串数组,argc为输入参数的个数。第19、20行调用printf函数使用了数组argv。编译后在hello.s中第35-48行,argv[1]保存在(%rbp)-8地址的栈空间中,argv[2]保存在(%rbp)-16地址的栈空间中,argv[3]保存在(%rbp)-24地址的栈空间中;hello.c中第20行调用atoi函数使用了数组argv[4],编译后在hello.s第49-53行,arg[4]保存在(%rbp)地址栈空间中。

  1. 函数操作

如图6中红色部分所示,hello.c中调用了以下函数:

    • main函数

其传入参数为 argc和 argv,均为系统调用且参数从 Shell 中传入,返回值设置为 0;

    • printf函数

hello.c中第19行printf函数在编译后通过%rdi、%rsi、%rdx、%rcx传入参数调用,返回输出字符个数;第15行printf函数在编译后通过%rdi传入参数并调用puts函数,返回0;

    • exit函数

通过%edi传入参数1调用,无返回值;

    • sleep函数

通过%edi传入参数调用,返回0;

    • atoi函数

通过%rdi传入参数调用,返回argv[4]对应整型,保存在%rax中

    • getchar函数

无参数调用,返回输入字符的ASCII码。

图 6 hello.c与hello.s操作对照

3.4 本章小结

本章主要介绍了hello的编译过程。首先本章介绍了编译的概念和作用,通过编译命令对hello.i文件进行编译操作生成了hello.s文件,并对编译生成的hello.s文件从数据和操作两个方面进行了解析。

第4章 汇编

4.1 汇编的概念与作用

       概念:汇编器(as)将hello.s中的汇编语言指令转换为成机器语言指令,把这些指令打包成可重定位目标程序的格式,并将结果保存在二进制目标文件hello.o中。

       作用:将汇编语言代码转换为机器可执行的机器语言代码,并生成可重定向文件。

4.2 在Ubuntu下汇编的命令

gcc -c -o hello.o hello.s

图 7 汇编命令

4.3 可重定位目标elf格式

如图8中黄色方框所示,hello.o的elf格式开始为ELF头,其中包含了该文件的基本属性信息,如Magic、类别(REL可重定位文件)、数据、版本、OS/ABI、ABI版本、入口点地址(0x0)等,;

如图8、图9中绿色方框所示,该ELF文件接下来为节头部表,包括.text节、.rela.text节、.data节、.bss节、.rodata节、.comment节等,该部分还包括这些节的大小、类型、全体大小、地址、偏移量等信息,由于该文件为可重定位目标文件,当前这些节的地址均为0x0,末尾处Key to Flags描述各节的读写权限;

图 8 hello.o的ELF格式1

       如图9中黄色方框所示,接下来该ELF文件列出了.rela.text中保存的条目——重定位节,其中包括各需要重定位的符号名称(symbol)、偏移量(offset)、类型(type)、加数(addend)等信息,告诉链接器在将目标文件合并成可执行文件时如何修改这个引用;

       如图9中蓝色方框所示,最后该ELF文件列出了.symtab节中保存的条目——符号表,它保存了程序中定义和引用的函数、变量和该ELF文件中.text节和.rodata节的信息,包括名称、大小、类型、绑定等,在链接过程中用于符号解析和符号重定位。

图 9 hello.o的ELF格式2

4.4 Hello.o的结果解析

如图10所示,对比hello.o的反汇编(左)与hello.s(右)可知其有以下不同:

  • 立即数:反汇编为十六进制表示,hello.s为十进制表示;
  • 源操作数(全局变量):反汇编为rip寄存器地址加0(因为其未被重定位),hello.s为rip 寄存寄存器地址加全局保存名称(如.LC0);
  • 分支跳转目标地址:反汇编为主函数偏移地址,hello.s为助记符(如.L2);
  • 函数调用:反汇编中call指令后为调用函数的偏移地址,hello.s中call指令后为被调用函数名称。

图 10 hello.o的反汇编文件与hello.s的对照

4.5 本章小结

本章主要介绍了hello的汇编过程。首先本章介绍了汇编的概念和作用,通过汇编命令对hello.s文件进行汇编操作生成了hello.o文件,并对汇编生成的hello.o文件的ELF格式进行了分析,同时也对hello.o的反汇编文件与hello.s进行了对比解析。

5章 链接

5.1 链接的概念与作用

       概念:链接是将各种代码和数据片段(包括.o文件)收集并组合成为一个单一文件(.out文件)的过程,这个文件可以被加载(复制)到内存并执行。

       作用:链接器在软件开发中扮演着一个关键的角色,因为它使得分离编译成为可能。链接使得程序员不用将一个大型应用程序组织为一个巨大的源文件,而是可以把它分解为更小、更好管理的模块,可以独立地修改和编译这些模块。当需要改变这些模块中地一个时,只需简单地重新编译它,并重新链接应用,而不必重新编译其他文件。

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

图 11 链接命令

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

       如图12中黄色方框所示,hello的ELF格式开头为ELF头,其中包含了该文件的基本属性信息,如Magic、类别、数据、版本、OS/ABI、ABI版本、入口点地址等信息,可见文件类型变为了EXEC(可执行文件),程序入口分配地址为0x4010f0;

图 12 hello的ELF格式1

       如图12、图13中绿色方框所示,该ELF文件接下来为节头部表,包括.interp节、.gnu.hash节、.dynsym节、.rela.plt节、.init节、.text节等,该部分还包括这些节的大小、类型、全体大小、地址、偏移量等信息,可见各节地址已重新分配,末尾处Key to Flags描述各节的读写权限;

图 13 hello的ELF格式2

       如图13至图16所示,该ELF文件剩余部分包括程序头、节到段映射、动态库偏移、重定位节、符号表及其有关信息,可以看到hello的ELF文件比hello.o的ELF文件包含更多信息。

图 14 hello的ELF格式3

图 15 hello的ELF格式4

图 16 hello的ELF格式5

5.4 hello的虚拟地址空间

如图17、图18所示,hello进程的虚拟地址从0x0000000000401000开始,到0x0000000000401ff0结束。5.3中.text节起始地址为0x00000000004010f0,大小为0xd8,说明.text保存地址范围为0x00000000004010f0至0x00000000004011c7,查看该部分地址如图17中选中蓝色部分。通过节的地址和节的大小可以通过相同方式查看其余加载到虚拟地址中的节。

图 17 hello的虚拟地址空间起始地址部分

图 18 hello的虚拟地址空间结束地址部分

5.5 链接的重定位过程分析

       如图19中黄色方框所示,hello的反汇编文件中增加了节,如.init节、.ply节等;如图20中黄色方框所示,hello的反汇编文件中还增加了许多所用到的库函数如puts、printf、getchar、atoi、exit、sleep函数等。以上说明链接过程将hello中的符号通过符号解析、重定位与静态库链接,将所需符号按一定顺序添加到hello中。

图 19 hello反汇编文件与hello.o反汇编文件对照1

图 20 hello反汇编文件与hello.o反汇编文件对照2

       如图21所示,链接后hello的反汇编文件中每个节、函数都有了虚拟地址,main函数的起始地址不再为0,对全局变量的引用为rip寄存地址偏移确定的大小而不再是0。hello.o中每个引用,汇编器都产生了一个重定位条目显示在引用的后面一行,其包含了引用的偏移offset为1c、重定位类型type为R_X86_64_PC32、引用符号symbol为.rodata和加数addend-0x4,通过对应类型的重定位计算方式可以确定各全局符号的重定位地址。

 

图 21 hello反汇编文件与hello.o反汇编文件对照3

5.6 hello的执行流程

表 1 hello执行流程中的主要函数及其地址

函数名

地址

_start

0x7ffff7fe3290

_dl_start

0x7ffff7fe4030

__GI___tunables_init

0x7ffff7fda970

__GI___tunable_get_val

0x7ffff7fdad70

_dl_main

0x7ffff7fe48e0

__sigsetjmp

0x7ffff7fe9ef0

_dl_load_cache_lookup

0x7ffff7fd9710

__GI___open64_nocancel

0x7ffff7fe9ae0

__mmap64

0x7ffff7fe9ca0

__GI___pread64_nocancel

0x7ffff7fe9b90

main

0x40112d

printf

0x4010a0

atoi

0x4010c0

sleep

0x4010e0

getchar

0x4011bc

exit

0x7ffff7c455f0

 

   

图 22 执行时部分步骤

5.7 Hello的动态链接分析

通过5.3可知.got.plt地址为0x404000,大小为48。如图23,使用gdb分别在调用_dl_init前后查看该部分地址内容可见,动态链接前地址为0x404008-0x404017中的数据为0x0,在动态链接后其内容变为0xf7ffe2e0、0x00007fff、0xf7fd8d30、0x00007fff。

图 23 hello的动态链接前后对照

5.8 本章小结

本章主要介绍了hello的链接过程。首先本章介绍了链接的概念和作用,通过链接命令将hello.o文件与所需其他文件进行链接操作生成了可执行目标文件hello,并对hello的ELF格式和hello的虚拟地址空间进行了分析,同时也分析了hello链接的重定位过程、hello的执行流程和hello的动态链接。

6章 hello进程管理

6.1 进程的概念与作用

概念:进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配的基本单位,是操作系统结构的基础。

作用:系统中每一个程序都运行在某个进程的上下文中。上下文是程序正确运行所需的状态的组成的。这个状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。

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

作用:shell-bash可以实现用户交互功能,其可以读取用户输入的命令或脚本,解析命令并将其送至内核中处理相应内置命令或创建新的进程,在上下文中执行相应程序。

处理流程:

  • shell-bash从IO设备或文件中读取命令并解析,判断其是否为内置命令;
  • 若命令为内置命令则立即执行内置命令;若命令非内置命令则构造argv和envp,寻找对应可执行文件;
  • 若未找到对应可执行文件则输出一条错误信息;若找到对应可执行文件则使用fork函数创建子进程,使用execve函数在子程序的上下文中运行该可执行文件,将其相应节加载到当前进程的虚拟内存中,调用函数加载器跳转到程序的入口点地址,运行函数;
  • 可执行文件运行结束后使用waitpid函数回收子进程,清除相应虚拟内存,等待下一条指令输入。

6.3 Hello的fork进程创建过程

shell判读./hello为执行程序,因此调用fork函数创建一个新的子进程,新创建的子进程几乎但不完全与父进程相同。子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份副本,包括代码和数据段、堆、共享库以及用户栈。子进程还获得与父进程任何打开文件描述符相同的副本。父进程与新建子进程之间最大区别为具有不同的PID。

6.4 Hello的execve过程

shell在子进程创建完成后调用execve函数,在当前子进程的上下文中加载并运行hello且带参数列表argv和环境变量列表envp,此时加载器会先删除已经存在的用户区域,将hello加载到当前进程的内存空间,映射私有区域,用hello的堆、用户栈、段数据等替换原进程的相应部分,之后映射共享区域,设置程序计数器并跳转到hello的入口点即_start函数的地址,调用系统启动函数_libc_start_main,初始化执行环境,调用hello的main函数。

6.5 Hello的进程执行

hello正常执行时处于用户态,由于系统中通常有多个程序同时运行,hello刚开始运行时会抢占当前进程,并使用一种称为上下文切换的机制将控制转移到hello的进程,此后hello与其他程序并发执行,每个进程轮流运行,hello被切分为时间片与其他进程交替占用执行直到hello执行完毕。若hello运行时收到信号则将转变为内核态处理信号,处理完毕后返回用户态继续执行。

6.6 hello的异常与信号处理

hello执行过程中可能会出现中断、陷阱、故障和终止四类异常。

图 24 hello的异常与信号处理

如图24所示,hello执行过程中可能会产生以下信号与命令:

  • SIGSTP信号:按下ctrl-z向hello发送该信号,hello收到信号后会被挂起至后台,直到收到信号继续执行;
  • SIGINT信号:按下ctrl-c向hello发送该信号,hello收到信号后会被终止;
  • 按下回车:输入行换行,无其他效果;
  • ps:显示当前进程;
  • jobs:显示当前作业;
  • fg:将挂起的程序继续在前台运行;
  • kill (pid):终止PID=pid的进程;
  • pstree:输出可视化进程间的树状结构;

6.7本章小结

本章主要介绍了hello的进程管理。首先本章介绍了进程的概念和作用,并简述了壳shell-bash的作用与处理流程。此外还介绍了hello的fork进程创建过程和execve过程,并介绍了hello的进程执行过程和执行途中可能遇到的异常与收到的信号及其处理流程。

7章 hello的存储管理

7.1 hello的存储器地址空间

物理地址:计算机系统的主存被组织成一个由M个连续的字节大小的单元组成的数组,每字节都有一个唯一的标号即物理地址。hello在主存中存储的字节单元的标号即为其物理地址。

虚拟地址:CPU生成的虚拟内存中的地址,CPU通过其来访问主存,这个虚拟地址在被送到内存之间先转换成适当的物理地址。hello一般运行在虚拟空间中,其在这个虚拟空间中的地址即为虚拟地址。

逻辑地址:在机器语言指令中制定操作数或指令的地址。hello的汇编语言代码中的相对偏移地址即为逻辑地址。

线性地址:逻辑地址到物理地址变换之间的中间层。在分段部件中逻辑地址是段中的偏移地址,然后加上基地址就是线性地址。

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

逻辑地址由16位的段选择符和32位的偏移量组成,其中段选择符标识字节所在的段,偏移量指定字节在段中相对于段基地址的位置。

处理器将逻辑地址翻译为线性地址,线性地址是32位的地址。与物理地址空间一样,线性地址空间是未分段的232字节地址空间,地址范围从0到FFFFFFFFH。线性地址空间中存储着为系统定义的所有段和系统表。

为了将逻辑地址转换为线性地址,处理器执行以下操作:

①当且仅当将新的段选择子加载到段寄存器时,处理器使用段选择子中的偏移量在GDT或LDT中定位段的段描述子,并将其读入处理器。

②检查段描述子,从而检查段的访问权限和范围,以确保段是可访问的,并且偏移量在限制范围内。

③将段描述子中的段基地址与偏移量相加,形成线性地址。

逻辑地址转换为线性地址的过程大致如图25所示。

图 25 Intel逻辑地址到线性地址的变换

形成线性地址后,如果不使用分页,处理器将线性地址直接映射到物理地址(即线性地址在处理器的地址总线上输出);如果线性地址空间被分页,则使用第二级地址转换将线性地址转换为物理地址。

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

VM系统通过将虚拟内存分割为称为虚拟页的大小固定的块,每个虚拟页的大小为P=2p字节。类似地,物理内存被分割为物理页(页帧),大小也为P字节。hello被分配虚拟页后存放在磁盘上,之后通过页表选择牺牲页,将虚拟内存缓存在主存物理内存中。Hello的起点一定处在主存中某一个页面位置的起点。此后执行hello时可以由用户标识找到相应的页表基址寄存器,找出hello的页表基址,由页表基址和用户虚页号找到页表中相应表项,索引存储hello的物理地址,从中读取所需信息。

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

CPU产生一个虚拟地址,MMU从TLB中取出相应的PTE。如果未命中则从L1缓存中取出相应的PTE存放在TLB中。

虚拟地址被划分为4个VPN和1个VPO,每个VPNi都是一个到第i级页表的索引(1≤i≤4)。第j级页表中的每个PTE(1≤j≤3)都指向第j+1级的某个页表的基址。第4级页表中的每个PTE包含某个物理页面的PPN,或者一个磁盘块的地址。为了构造物理地址,在能够确定PPN之前,MMU必须通过TLB访问4个PTE。另外PPO和VPO相同。

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

物理地址分为标记为(CT)、组索引(CI)和快偏移(CO),根据CI可以查找到在L1Cache中的组,将物理地址与该组中的每一行进行比较,若有效位有效且标记位相同则命中,返回所需数据;若访问完组内所有数据由都未命中,则按以上步骤依次在L2Cache、L3Cache中判断是否命中。若命中则将数据传给CPU并更新各级缓存;若仍未命中则在主存中寻找该地址,并将地址内的数据存入Cache中。

7.6 hello进程fork时的内存映射

Shell在调用fork创建子进程后,内核为子进程创建各种数据结构,并分配给它一个唯一的PID。为了给子进程创建虚拟内存,它创建了当前进程的mm_struct、区域结构和页表的原样副本。shell将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。当fork 在hello所在子进程中返回时,hello所在子进程现在的虚拟内存刚好和调用fork 时存在的虚拟内存相同。当这两个进程中的任意一个后来进行写操作时,写时复制机制就会创建新页面,因此,也就为每个进程保持了私有地址空间的抽象概念。

7.7 hello进程execve时的内存映射

shell在调用execve函数后会删除已存在的用户区域,即删除当前hello子进程虚拟地址的用户部分中的已存在的区域结构。之后会映射私有区域,为hello的代码、数据、bss和栈区域创建新的区域结构。所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为hello文件中的.text区和.data区。bss区域的请求是二进制零的,映射到匿名文件,其大小包含在hello中。栈和堆区域也是请求二进制零的,初始长度为0。之后execve会将共享对象或目标如标准C库libc.so与hello链接,再将其映射到用户虚拟地址空间中的共享区域内。最后execve设置hello进程的程序计数器,使其指向hello代码区域的入口点。

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

当处理器生成hello的虚拟地址后,将其传进MMU,MMU生成PTE地址并从高速缓存或主存中请求得到PTE,得到PTE后若有效位为0则MMU触发缺页故障,此时进程中断进入缺页处理程序。缺页处理程序确定物理内存中的牺牲页(若页面被修改则按照写回策略换出到磁盘),并将hello的页面调入,并更新内存中的PTE。处理完毕后缺页处理程序会回到原来的进程再次执行导致缺页的命令。

7.9动态存储分配管理

       动态内存分配器维护着一个进程的虚拟内存区域,称为堆。分配器将堆视为一组大小不同的块来维护,每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用,空闲块用来分配。分配器有两种基本风格即显式分配器和隐式分配器。malloc程序包为显式分配器。显式分配器要求应用显式地释放任何已分配的块。

       当printf调用malloc后,malloc函数会返回一个指针,指向大小至少为size字节的内存块,这个块会为可能包含在这个块内的任何数据对象类型作对齐。如果malloc遇到问题如程序要求的内存块比可用的虚拟内存还要大,那么就会返回NULL,并设置errno。另外,malloc不会初始化其返回的内存。

7.10本章小结

本章主要介绍了hello的存储管理。首先本章介绍了hello的存储器地址空间,并介绍了Intel逻辑地址到线性地址的变换-段式管理。此外还介绍了hello的线性地址到物理地址的变换过程即页式管理、TLB与四级页表支持下的VA到PA的变换、三级Cache支持下的物理内存访问、hello进程fork和execve时的内存映射,并介绍了遇到缺页故障时的处理方式和缺页中断处理。最后本文介绍了hello的动态存储分配管理。

8章 hello的IO管理

8.1 Linux的IO设备管理方法

一个Linux文件就是一个m个字节的序列:

B0,B1,…,Bk,…,Bm-1

所有的 I/O设备(例如网络、磁盘和终端)都被模型化为文件,而所有的输入和输出都被当作对相应文件的读和写来执行。这种将设备优雅地映射为文件的方式,允许Linux内核引出一个简单、低级的应用接口,称为UnixI/O,这使得所有的输入和输出都能以一种统一且一致的方式来执行:

①打开文件。一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个I/O设备。内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件。内核记录有关这个打开文件的所有信息。应用程序只需记住这个描述符。

②Linux shell创建的每个进程开始时都有三个打开的文件:标准输入(描述符为0)、标准输出(描述符为1)和标准错误(描述符为2)。头文件<unistd.h>定义了常量 STDIN_FILENO、STDOUT_FILENO和STDER_RFILENO,它们可用来代替显式的描述符值。

③改变当前的文件位置。对于每个打开的文件,内核保持着一个文件位置k,初始为0。这个文件位置是从文件开头起始的字节偏移量。应用程序能够通过执行seek操作,显式地设置文件的当前位置为k。

④读写文件。一个读操作就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增加到k+n。给定一个大小为m字节的文件,当k≥m时执行读操作会触发一个称为end-of-file(EOF)的条件,应用程序能检测到这个条件。在文件结尾处并没有明确的“EOF符号”。

类似地,写操作就是从内存复制n>0个字节到一个文件,从当前文件位置k开始,然后更新k。

⑤关闭文件。当应用完成了对文件的访问之后,它就通知内核关闭这个文件。作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。无论一个进程因为何种原因终止时,内核都会关闭所有打开的文件并释放它们的内存资源。

8.2 简述Unix IO接口及其函数

Unix I/O (输入/输出) 接口用于文件和设备之间的数据传输。这些接口定义了一组系统调用,允许程序以统一的方式执行 I/O 操作。Unix I/O 接口的特点是它们通常执行简单的操作,并且具有很高的效率。Unix I/O接口的函数也提供了足够的灵活性,以支持复杂的 I/O 操作和高级文件系统特性。Unix I/O 接口包括以下基础函数及其功能:

  • open() - 打开一个文件,并返回一个文件描述符。文件描述符是一个非负整数,用于在后续的 I/O 操作中标识文件。
  • close() - 关闭一个文件描述符,释放与该文件相关的资源。
  • read() - 从指定的文件描述符读取数据到一个缓冲区。
  • write() - 将数据从缓冲区写入到指定的文件描述符。
  • lseek() - 改变文件描述符的文件位置指针,可以用于随机访问文件。
  • fstat() - 获取文件的状态信息,比如文件大小、权限等。
  • stat() - 类似于 fstat(), 但是它获取的是文件路径的状态信息。

8.3 printf的实现分析

       首先,printf 函数使用可变参数列表来接收不定数量的参数,并使用 va_list 来遍历这些参数。va_list 在C语言中通常定义为 char* 类型,用来指向参数列表中的元素。

       然后,在printf函数内部首先调用 vsprintf,格式化字符串 fmt 和可变参数列表 args 转换成最终的字符串,并存储在缓冲区 buf 中。vsprintf 函数遍历 fmt 中的每个字符,当遇到格式化占位符(如 %d, %s, %x 等)时,会从 args 中取出相应的参数,并按照指定格式转换成字符串。遍历完成后vsprintf 函数返回生成的字符串长度,这个长度随后被用于 write 系统调用。

       write 函数将 buf 中的数据写入到指定的输出设备(在 printf 的上下文中,这通常终端或控制台标准输出)。printf执行过程中系统调用通过中断(如 int 0x80 或 syscall)实现,这是用户态程序与内核态之间的接口。数据输出到指定设备后,显示驱动程序负责将字符转换为屏幕上可见的像素点,将ASCII字符转换为字模(fonts),然后映射到显存(VRAM)中的相应位置。

       显存存储着屏幕上每个像素点的颜色信息,显示芯片按照一定的刷新频率读取VRAM中的数据,并通过信号线将数据传输给显示器,从而在屏幕上显示图像。此外,显示信号线负责在显示芯片和显示器之间传输数据,每个像素点的RGB颜色分量通过这些信号线被传输到显示器,并最终在屏幕上渲染出图像。

8.4 getchar的实现分析

当用户按下键盘上的一个键时,键盘硬件会生成一个中断信号给CPU。CPU接收到中断信号后会暂停当前正在执行的程序,转而执行与键盘中断相关联的处理程序(中断处理子程序)。

中断处理子程序首先接收到按键的扫描码,ISR将扫描码转换为对应的ASCII码并将其保存到操作系统维护的键盘缓冲区中直到其被读取。getcahr函数通过调用read系统函数来从标准读入数据。Read函数会触发一个系统调用,请求内核从键盘缓冲区中读取数据。系统调用会检查键盘缓冲区是否有数据可读,若为空则会阻塞等待新的键盘输入到达;若有则会从缓冲区读取一个或多个字符,并将其返回给getchar函数,getchar函数再将这个字符返回给调用它的进程。如果getchar读取到回车则通常会返回。

如果getchar函数在执行期间发生了异步异常,操作系统会通过保存和恢复程序的状态方式来确保getchar函数能正常处理。

8.5本章小结

本章主要介绍了hello的IO管理。首先本章介绍了Linux的IO设备管理方法,并简述了Unix IO接口及其函数。另外,本文还介绍了printf和getchar函数的实现方式。

结论

本文通过一个简单的"hello"程序,探索了计算机系统内部运作方式。这不仅是对hello生命周期的追踪,更是对操作系统原理和程序执行机制的深入剖析。

本文的起点是一个看似普通的问题:hello是如何展现在我们眼前的?这个问题看似简单,但其答案却涉及了一系列复杂的步骤和过程。从编写C语言源文件,到预处理、编译、汇编,再到链接成可执行文件,每一步都是对原始代码的深入和转化。这个过程就像是一台精密运转的机器,每一步都精确而必要。

首先,hello存储于hello.c中,被预编译器(cpp)进行预处理操作,生成hello.i文件处理其包含的头文件、宏定义、注释、条件编译和特殊控制指令;其次,hello.i经过编译器(ccl)的编译处理转变为hello.s汇编语言文件;第三,hello.s被汇编器(as)进行汇编操作,生成hello.o二进制机器语言文件,并通过链接与其他有关文件一起生成hello可执行文件。最后,hello在执行过程中受到shell的管理,shell调用fork及execve等函数,同时处理出现的异常及信号。与此同时hello运行过程中会涉及到hello的内存管理与IO设备交互。最终hello返回,结束其一生。

       在这个过程中,操作系统扮演了至关重要的角色。它不仅是资源的调配者,更是hello执行的守护者。通过进程管理,操作系统确保了每个程序都能获得必要的计算资源,并能够与其他程序和谐共存。存储管理展示了操作系统如何在有限的物理内存中,为程序提供看似无限的虚拟空间。而IO管理则让我们看到了操作系统如何将外部世界的信号转化为程序可以理解的语言。

       随着对hello的逐步深入,我们不仅认识到了程序是如何被构建和执行的,更重要的是我们还学到了如何去思考和解决问题。计算机系统的设计和实现是一个复杂的过程,它要求我们既要有宏观的视角去理解整个系统的运作,也要有微观的洞察去优化每一个细节。按照顺序一步一步前进,将复杂的问题进行剖析,仔细思考每一个细节,最后在宏观上将其整体把握,难题终会迎刃而解。

在本文的最后,我们对hello的生命周期进行了全面的回顾。这次回顾不仅是对技术要点的总结,也是对计算机系统设计和实现的深切感悟。我看到了计算机系统运行的美妙,同时对其有了更深刻的理解,这吸引着我继续探索计算机世界。今后我将继续怀着一颗学徒之心,认真学习和思考每一个问题, 不断完善自我,在未来的学习和工作中取得更大的进步和成就。

附件

hello.c——hello的C语言源文件。

hello.i——hello经过预处理操作后得到的预处理文件。

hello.s——hello经过编译操作得到的汇编语言文件。

hello.o——hello经过汇编操作得到的二进制机器语言文件。

hello——hello经过链接后得到的可执行文件。

参考文献

  1. 林来兴. 空间控制技术[M]. 北京:中国宇航出版社,1992:25-42.
  2. 辛希孟. 信息技术与信息服务国际研讨会论文集:A集[C]. 北京:中国科学出版社,1999.
  3. 赵耀东. 新时代的工业工程师[M/OL]. 台北:天下文化出版社,1998 [1998-09-26]. 工業工程與工程管理學系).
  4. 谌颖. 空间交会控制理论与方法研究[D]. 哈尔滨:哈尔滨工业大学,1992:8-13.
  5. KANAMORI H. Shaking Without Quaking[J]. Science,1998,279(5359):2063-2064.
  6. CHRISTINE M. Plant Physiology: Plant Biology in the Genome Era[J/OL]. Science,1998,281:331-332[1998-09-23]. http://www.sciencemag.org/cgi/ collection/anatmorp.
  7. Randal E. Bryant等. 深入理解计算机系统(原书第三版)[M].北京:机械工业出版社,2016.7.
  8. C语言编译后生成的文件是什么?C#编译后生成的文件是什么?. C语言编译后生成的文件是什么?C#编译后生成的文件是什么?_c程序编译后-CSDN博客
  9. crt1.o, crti.o, crtbegin.o, crtend.o, crtn.o. http://t.csdnimg.cn/SAekQ
  10. _fork和execve和Linux内核的一般执行过程. http://t.csdnimg.cn/7HJ1h
  11. 逻辑地址、线性地址、物理地址和虚拟地址. https://www.cnblogs.com/diyingyun/archive/2012/01/03/2311327.html
  12. Intel IA-32架构保护模式内存管理. https://zhuanlan.zhihu.com/p/622151122
  13. printf 函数实现的深入剖析. https://www.cnblogs.com/pianist/p/3315801.html
  14. C语言getchar()函数详解. C语言 getchar()函数详解-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值