HIT-ICS-程序人生——Hello‘s P2P

计算机系统

大作业

题     目  程序人生-Hello’s P2P   

专       业  未来技术学院人工智能领域                     

学     号   2022113616                    

班     级   22wl028                    

学       生   宋旭文                  

指 导 教 师    郑贵滨                   

计算机科学与技术学院

2024年5月

摘  要

Hello.c程序是C语言中最简单的一个程序,通常是初学者最先编写的程序之一。然而,在这个简单代码的背后却蕴含着人类的伟大智慧。本文聚焦于hello的一生,包括hello.c经过预处理、编译、汇编和链接之后,最终形成可执行程序hello,hello在进程中加载、运行和回收,分析了hello一个生命周期中的所有经历,涉及了从基于自然语言的高级程序到机器可识别的二进制代码的转换过程,以及虚拟地址到物理地址的转化过程,通过对hello的分析,揭示计算机系统底层的工作机制。

关键词:计算机系统;Linux;程序;编译;汇编;链接;进程管理;I/O管理;                           

(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分

目  录

第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的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。

1、P2P过程

程序员编写C语言源代码(.c文件),实现想要的功能。gcc通过调用预处理器cpp、编译器cc1、汇编器as、链接器ld将.c文件进行预处理、编译、汇编和链接,最终生成hello可执行目标文件,保存在磁盘中。在bash中,操作系统通过解析用户输入的命令,在磁盘中(虚拟地址空间)找到可执行文件hello并确认用户是否具有执行权限,然后将hello加载到物理内存中,最终作为进程在计算机系统中运行。

2、020过程

020 过程是 hello 可执行目标程序从运行到最后被回收的过程。在 Shell 中运行该程序时,Shell 调用 fork 函数创建子进程。创建完毕后,操作系统内核提供的 execve 函数会创建虚拟内存的映射,即 mmp,然后开始加载物理内存,进入到 main 函数当中执行相关的代码,打印出信息。在进程中,TLB、4级页表、3级 Cache,Pagefile等设计会加快程序的运行。程序运行完成后,Shell 回收子进程,操作系统内核删除相关数据结构,释放其占据的资源。至此,hello 的一生就结束了。

1.2 环境与工具

1.2.1硬件环境

图1-1 硬件环境

处理器 13th Gen Intel(R) Core(TM) i5-13500H   2.60 GHz

机带 RAM 32.0 GB (31.7 GB 可用)

系统类型    64 位操作系统, 基于 x64 的处理器

1.2.2操作系统

Windows 11、VirtualBox/Vmware 11以上;Ubuntu 18.04 LTS 64位/优麒麟 64位;

1.2.3开发工具

Vim、gcc、gdb、edb

1.3 中间结果

列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。

文件

作用

hello.c

源代码

hello.i

预处理后的代码

hello.s

汇编代码

hello.o

可重定位目标文件

hello

链接后的可执行目标文件

hello.txt

Hello的反汇编代码

表1-1 中间结果文件及作用

1.4 本章小结

本章概述了hello的P2P和O2O的过程。此外,还介绍了本实验用到的硬软件环境和开发调试工具,最后介绍了本次实验的中间结果文件。

(第1章0.5分)

第2章 预处理

2.1 预处理的概念与作用

2.1.1概念

预处理是指在编译源代码之前执行的一系列操作,如包含文件、宏替换、条件编译、删除注释等,由预处理器cpp来完成。

2.1.2作用

预处理器通过对源代码进行一系列的预处理操作,可以提高代码的灵活性、可维护性和可读性,同时也能够优化编译过程,提高编译效率。

2.2在Ubuntu下预处理的命令

Linux下使用gcc预处理的命令为:

gcc -E hello.c -o hello.i

图2-1 预处理命令

2.3 Hello的预处理结果解析

2.3.1整体

在Linux上打开文件,hello.c文件只有24行,而hello.i文件有3061行。

图2-2 hello.c和hello.i的行数

2.3.2包含其他文件

文件开头有一系列的外部库.h文件路径

图2-3 hello.i包含.h文件路径

2.3.3替换数据类型名称

接下来是一系列typedef,前面的名称是编写代码时使用的标准数据类型,而后面的那些名称就是上述引入的头文件中使用的类型定义。

图2-4 hello.i替换类型名称

2.3.4声明内部函数

中间部分是头文件内部函数的声明。

图2-5 hello.i包含头文件内部函数声明

2.3.5代码段

.c文件中的代码被放到了文件末尾。

图2-6 hello.i的代码段

2.4 本章小结

本章介绍了 hello.c 的预处理概念及作用,并分析了预处理后的文件 hello.i。

从程序员的角度来说,利用宏定义指令可以让我们轻松写出可读性更好的代码,利用条件编译指令可以让我们更加方便快捷的调试代码。

从hello程序的角度来说,hello.c 是残缺的,不完整的,预处理阶段使它健全了四肢,得以最终运行在操作系统的上下文中。

(第2章0.5分)

第3章 编译

3.1 编译的概念与作用

3.1.1概念

编译是指将源代码文件转换为目标代码文件的过程。在这个过程中,编译器cc1会对源代码进行语法分析等操作,并生成于目标平台相关的机器语言,宜宾啊计算机能够执行。

3.1.2作用

编译过程可以将高级语言转换为机器语言,生成.s文件。不仅如此,编译过程还可以优化代码、检查错误,以提高程序性能。

3.2 在Ubuntu下编译的命令

Linux下使用gcc编译的命令为:

gcc -S hello.i -o hello.s

图3-1 编译命令

3.3 Hello的编译结果解析

3.3.1常量

1、字符型常量

字符串常量存储在.LC0中

图3-2-1 hello.c中字符型常量

图3-2-2 hello.s中字符型常量

2、其他常量

直接以立即数的形式出现,例如这段代码中有一个整型常量5,在汇编代码中表示为$5。

图3-3-1 hello.c中立即数

图3-3-2 hello.s中立即数

3.3.2变量及运算

1、局部变量

在主函数中有一个局部变量i,存储在寄存器或栈中。在汇编代码中,有一条入栈指令pushq %rbp,因此可以看出,i存在栈中。

图3-4-1 hello.c中的局部变量

图3-4-2 hello.s中的局部变量

2、算术运算

在for循环中,局部变量i每循环一次就加一,在汇编代码中通过addl指令来实现。

图3-5-1 hello.c中的算术运算

图3-5-2 hello.s中的算术运算

3.3.3数组/指针操作

函数中将字符串数组argv作为第二个参数,printf函数中用到了argv[1]、argv[2]、argv[3]。在汇编代码中,%rcx、%rdx、%rax分别是第一、二、三个参数寄存器,分别存储argv[1]、argv[2]、argv[3],也就是说,%rbp-8所指向的栈空间中存的内容是argv[1]的地址,%rbp-16所指向的栈空间中存的内容是argv[2]的地址,%rbp-24所指向的栈空间中存的内容是argv[3]的地址。

图3-6-1 hello.c中的数组/指针操作

图3-6-2 hello.s中的数组/指针操作

3.3.4控制转移

在for循环中,局部变量i每加一,就需要在循环开头判断i是否小于10,如果不满足小于10的条件,即i等于10时,跳出循环。在汇编代码中采用cmpl指令比较i和立即数9的大小,如果小于等于就跳转到循环内部代码。

图3-7-1 hello.c中的控制转移

图3-7-2 hello.s中的控制转移

3.3.5函数

1、main函数

参数为 argc和 argv,是系统调用的且从 Shell 中传入,返回值设置为 0。

2、printf函数

通过设置寄存器%rsi、%rdi的值来传入和调用参数。

图3-8 printf函数的汇编代码

3、exit函数

通过设置寄存器%rsi、%rdi的值来传入和调用参数。

图3-9 exit函数的汇编代码

4、sleep函数

通过设置寄存器%rdi的值来传入和调用参数。

图3-10 sleep函数的汇编代码

5、atoi函数

将一个字符串的首地址存在寄存器%rdi中,通过%rdi来传入和调用参数,函数将该字符串转成的整数值返回到寄存器%eax中。

图3-11 atoi函数的汇编代码

6、getchar函数

无参数、无返回值。

3.4 本章小结

本章介绍了从 hello.i 文件编译成 hello.s 文件的过程,以及 .c 文件中各部分变量、常量、控制转移以及函数调用在汇编代码中相对应的样子。

(第32分)

第4章 汇编

4.1 汇编的概念与作用

汇编就是汇编器as将 hello.s 翻译成机器语言指令,把这些指令打包成可重定位目标程序的格式,并将结果保存在二进制文件 hello.o 中。

4.2 在Ubuntu下汇编的命令

在Linux中使用gcc汇编的指令为:

gcc -C hello.s -o hello.o

图4-1 汇编指令

4.3 可重定位目标elf格式

4.3.1分析hello.o的ELF格式

可重定位目标文件的elf格式如下:

图4-2 可重定位目标文件elf格式

4.3.2用readelf等列出其各节的基本信息

1、ELF头

用readelf -h hello.o指令查看ELF头。

EFL 头以 16 字节的序列 Magic 开始,这个序列描述了生成该文件的系统字的大小和字节顺序,ELF 头剩下的部分包含帮助链接器语法分析和解释目标文件的信息,其中包括 ELF 头的大小、目标文件的类型、机器类型、字节头部表的文件偏移,以及节头部表中条目的大小和数量等信息。

EFL头中还包括程序的入口点,也就是程序运行时要执行的第一条指令的地址为 0x0,通过hello.o 的反汇编代码可以看出,程序的入口点地址确实是0x0。

图4-3 hello.o的elf头

图4-4 main函数的反汇编代码

2、节头部表

用readelf -S hello.o查看ELF的节头部表。

每个节都包含了特定类型的数据,如代码、数据、符号表、重定位信息等。节头部表提供了有关这些节的元数据,包括节的位置、大小、标志等。由于这是可重定位目标文件,为了方便后续的内存映射,各个节的地址均从0开始。

它还用flags描述各个节的读写权限。

图4-5 hello.o的节头部表

3、符号表

用readelf -s hello.o指令查看.symtab节中的ELF符号表。

.symtab节存储了程序中使用的各种标识符(如变量、函数、类等)的信息,提供了这些标识符的命名、类型等,如main函数、printf函数。

图4-6 hello.o的符号表

4、重定位条目

用readelf -r hello.o指令查看重定位条目。

当hello.o中某一模块引用另一模块定义的函数或全局变量时,重定位条目告诉链接器应该如何修改代码或数据段中的地址,使其正确指向目标符号的位置,使得程序可以在运行时顺利地连接各个模块并正确访问全局变量和函数。

图4-7 hello.o的重定位条目

4.4 Hello.o的结果解析

用objdump -d -r hello.o指令查看hello.o的反汇编。

图4-8 hello.o的反汇编代码

与第3章的 hello.s进行对照来看,在 hello.s 中,分支跳转的目标位置是通过 .L1、.L2 这样的助记符来实现的,而 hello.o中,跳转的目标位置是指令的地址;在 hello.s 中,call 后面的目标函数是它的函数名,而在 hello.o 中,call 的是目标函数的相对偏移地址。

机器语言是由操作码、操作数、寻址方式和控制信息构成的。操作码是指令中用来表示操作类型的部分,指定了要执行的操作,如加法、减法、移动数据等。操作数是指令中用来表示操作对象或操作数据的部分,它可以是寄存器、内存地址或立即数等。寻址方式指定了如何找到操作数在内存中的实际位置,常见的寻址方式包括直接寻址、间接寻址、寄存器间接寻址、相对寻址等。控制信息用于控制程序的执行流程和决策。

机器语言和汇编语言是一一对应的关系,汇编语言中的每个助记符都对应着一条特定的机器语言指令,这些助记符通常与机器语言指令的操作码相对应。

4.5 本章小结

本章分析了汇编的过程,并分析了 ELF 头、节头部表、符号表以及重定位条目,同时还比较了 hello.s 和 hello.o 反汇编之后的代码的不同。

(第41分)

5章 链接

5.1 链接的概念与作用

5.1.1概念

链接是指将多个目标文件或库文件合并为一个可执行程序或共享库的过程。链接器ld是负责执行链接操作的工具。

5.1.2作用

链接的过程会解析符号引用,将代码和数据的相对地址转换成正确的绝对地址,将程序的目标文件与程序所依赖的外部函数库文件链接,节省了空间并提高了代码的重用性。

5.2 在Ubuntu下链接的命令

在Linux上使用ld的链接命令为:

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-1 链接命令

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

5.3.1hello的ELF格式

图5-2 可执行文件的elf格式

5.3.2各段信息

1、ELF头

用readelf -h hello指令查看可执行目标文件的ELF头。

程序入口分配到了地址,变成0x4010f0,文件类型从REL变成了可执行文件EXEC。

图5-3 hello的elf头

2、节头部表

用readelf -S hello指令查看节头部表。

链接器将各个文件对应的段合并,并重新分配和计算相应节的类型、位置和大小等信息。

各个节的地址也从 0变成了具体的值。可以看到 .text 节的起始地址为 0x4010f0,与程序入口地址相同,与 .text 中存放程序的机器代码符合。

图5-4 hello的节头部表

3、符号表

用readelf -s hello指令查看hello的符号表。

与hello.o的符号表相比,hello的符号表多了.dynsym动态链接节。

图5-5 hello的符号表

4、重定位条目

用readelf -r hello指令查看重定位条目。

可以看出,与hello.o的重定位条目相比,hello的重定位条目被分成两部分,代码重定位条目放在 .rela.plt 中,已初始化数据的重定位条目放在 .rela.dyn中。同时每一部分均有了准确的偏移量。

图5-6 hello的重定位条目

5.4 hello的虚拟地址空间

使用edb加载hello,查看本进程的虚拟地址空间各段信息。

可以看到,hello的虚拟地址起始为0x401000,查看hello的反汇编代码可知,起始地址0x401000对应的是init。

图5-7 init函数的反汇编代码

5.5 链接的重定位过程分析

5.5.1查看反汇编代码

用objdump -d -r hello 查看hello的反汇编代码

图5-8 hello的反汇编代码

5.5.2分析与hello.o的不同

1、新增函数

链接之后,加入了许多需要用到的库函数,如puts、printf等。

图5-9 hello反汇编代码中新增库函数

2、新增节

增加了.init节和.plt节,.init 节用于执行一些初始化操作,而 .plt 节则用于实现函数调用的延迟绑定,支持动态链接。

图5-10 hello反汇编代码中新增节

3、新增代码endbr64

可以观察到,在hello的反汇编代码中endbr64这句反复出现。endbr64 指令通常由编译器或者系统库自动生成,并且在一般情况下,程序员不需要直接操作这个指令。它是作为系统安全增强的一部分,通过编译器和操作系统的支持来实现对抗特定类型攻击的目的。

图5-11 hello反汇编代码中新增endbr64

4、函数调用与跳转

因为hello文件是已经重定位后的可执行文件,所以call和jmp语句后的目标地址都是确切的虚拟地址。

图5-12 hello反汇编代码中的函数调用与跳转

5.6 hello的执行流程

使用gdb执行hello。程序入口为0x4010f0,程序先从_start开始执行。在_start处设置一个断点,然后开始单步调试运行。程序执行完_start函数后,来到__libc_start_main,向main函数传递参数,然后才调用main函数。

图5-13 使用gdb执行hello

其调用与跳转的各个子程序名为:_start(0x4010f0),_init(0x401000),main(0x40112d),puts@plt(0x401090),exit@plt(0x4010d0),_finl(0x4011c0),sleep@plt(0x4010e0),atoi@plt(0x4010c0),printf@plt(0x4010a0)。

5.7 Hello的动态链接分析

在进行动态链接前,首先进行静态链接,生成部分链接的可执行目标文件 hello。此时共享库中的代码和数据没有被合并到 hello 中。只有在加载 hello 时,动态链接器才对共享目标文件中的相应模块内的代码和数据进行重定位,加载共享库,生成完全链接的可执行目标文件。

使用edb查看_GLOBAL_OFFSET_TABLE 的内容。

图5-14 使用edb查看_GLOBAL_OFFSET_TABLE

在dl_init前后,地址值发生改变。当调用一个动态链接库中的函数时,该地址内容值会发生变化,使其跳转到所调用的对应的函数地址处。

图5-15-1 dl_init前的地址值

图5-15-2 dl_init后的地址值

5.8 本章小结

本章详细介绍了 hello 的链接过程,比较链接后的 hello 与 hello.o 的不同,最后使用 gdb 工具逐行查看 hello 的运行过程。

(第51分)

6章 hello进程管理

6.1 进程的概念与作用

6.1.1概念

进程是计算机中正在运行的程序的实例。每个进程都有自己独立的内存空间,包括代码、数据和堆栈,以及其他系统资源的拷贝,如打开的文件、网络连接等。操作系统通过调度算法来分配处理器时间给不同的进程,从而实现多任务并发执行。

6.1.2作用

进程是操作系统中的核心概念,它使得计算机可以同时执行多个任务,并且有效地管理系统资源,从而实现高效的并发执行和多任务处理。

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

Shell 是一种命令行解释器,它是用户与操作系统内核之间的接口。Bash(Bourne Again Shell)是一种常见的 Unix/Linux 系统下的 shell 解释器。Shell-Bash使得用户能够方便地与操作系统交互,执行命令、管理环境变量、重定向输入输出、进行条件判断和循环等。

当用户在命令行中输入命令后,Shell-Bash 的处理流程大致如下:

1. 读取输入:Shell-Bash 会读取用户在命令行输入的内容,包括命令、参数和操作符等。

2. 解析命令:Shell-Bash 对输入的命令进行解析,包括识别命令名称、参数、操作符(如重定向符号、管道符号等)、变量替换等。

3. 查找命令:Shell-Bash 会在系统的执行路径中(通常是 PATH 环境变量所指定的路径列表)查找用户输入的命令。如果找到了对应的可执行文件,就准备执行该命令。

4. 创建子进程:如果找到了要执行的命令,Shell-Bash 将创建一个新的子进程来执行该命令。这样可以保持 Shell 进程的稳定性,即使子进程发生错误也不会影响到 Shell 进程本身。

5. 执行命令:在新创建的子进程中,Shell-Bash 会加载并执行对应的可执行文件。如果是内置命令(例如 cd、echo 等),则由 Shell-Bash 自己来执行,不需要创建新的子进程。

6. 等待命令执行完毕:Shell-Bash 会等待子进程执行完毕,并收集子进程的返回状态码。根据返回状态码来判断命令执行的结果是成功还是失败。

7. 处理输入输出重定向:如果命令中包含了输入输出重定向操作符(如 >、>>、<、| 等),Shell-Bash 会根据这些符号来调整命令的输入输出流。

8. 显示输出:最后,Shell-Bash 会将命令执行的结果输出到标准输出设备,供用户查看。

6.3 Hello的fork进程创建过程

Hello程序的fork进程创建过程如下:

1. 在运行Hello程序之前,操作系统会为该程序创建一个父进程。父进程是指在执行fork系统调用之前已经存在的进程。

2. 当Hello程序中遇到fork系统调用时,操作系统会创建一个新的子进程。子进程是通过复制父进程的副本来创建的。

3. 在创建子进程时,操作系统将父进程的所有资源(包括代码、数据、打开的文件、堆栈等)复制到子进程的地址空间中。

4. 然后,操作系统为子进程分配一个唯一的进程ID(PID),用于标识该进程。

5. 子进程的代码从fork系统调用的下一条语句开始执行,而父进程则继续执行原来的代码。这样,就实现了父进程和子进程的并发执行。

6. fork系统调用的返回值不同,对于父进程,fork系统调用返回子进程的PID;对于子进程,fork系统调用返回0。这样可以通过返回值来区分父进程和子进程。

7.父进程和子进程之间相互独立运行,它们有各自独立的地址空间和资源。子进程可以修改自己的地址空间和资源,而不会影响到父进程。

8. Hello程序中的父进程和子进程可以继续执行各自的逻辑,输出各自的结果。

图6-1 fork函数

6.4 Hello的execve过程

当一个程序调用execve函数时,操作系统会执行以下过程:

1. 加载可执行文件:execve函数将hello文件的路径作为参数,操作系统首先会根据路径找到对应的可执行文件hello,并将其加载到内存中。

2. 创建新的地址空间:操作系统会它创建一个新的地址空间,该地址空间将用于存储程序的代码、数据、堆栈等信息。这个新的地址空间会替换当前进程的地址空间。

3. 初始化程序的上下文:操作系统会初始化程序的上下文,包括清空寄存器、设置堆栈指针、设置程序计数器等。这些操作确保了程序在开始执行时处于一个清晰的状态。

4. 转移控制权:一旦新的地址空间和程序上下文准备就绪,操作系统会将控制权转移到新加载的程序,使其开始执行。这意味着原来的程序代码、数据和堆栈都将被替换为新程序的内容。

5. 执行hello程序:一旦控制权转移到新程序,操作系统会开始执行新程序的代码。

图6-2 execve函数流程

6.5 Hello的进程执行

6.5.1 逻辑控制流

操作系统将一个 CPU 物理控制流,分成多个逻辑控制流,每个进程独占一个逻辑控制流。当一个逻辑控制流执行的时候,其他的逻辑控制流可能会临时暂停执行。一般来说,每个逻辑控制流都是独立的。当两个逻辑控制流在时间上发生重叠,我们说是并行的。

处理器在多个进程中来回切换称为多任务,每个时间当处理器执行一段控制流称为时间片。因此多任务也指时间分片。

6.5.2 用户模式和内核模式

为了限制一个应用可以执行的指令以及它可以访问的地址空间范围,处理器用一个控制寄存器中的一个模式位来描述进程当前的特权。

用户模式:用户模式中的进程不允许执行特权指令,比如停止处理器、改变模式位,或者发起一个 I/O 操作。也不允许用户模式的进程直接引用地址空间中内核区内的代码和数据。用户程序必须通过系统调用接口间接地访问内核代码和数据。

进程从用户模式变为内核模式的唯一方法是通过诸如中断、故障或者陷入系统调用这样的异常。当异常发生时,控制传递到异常处理程序,处理器将模式从用户模式变为内核模式。处理程序运行在内核模式中,当它返回到应用程序代码时,处理器就把模式从内核模式改回到用户模式。

6.5.3 上下文切换

操作系统内核为每个进程维护一个上下文。所谓上下文就是内核重新启动一个被抢占的进程所需的状态。它由一些对象的值组成,这些对象包括通用寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构,比如描述地址空间的页表,包含有关当前进程信息的进程表,以及包含进程已打开文件的信息的文件表。

图6-3 上下文切换

6.5.4 hello 的执行

从 Shell 中运行 hello 时,它运行在用户模式。运行过程中,内核不断切换上下文,使运行过程被切分成时间片,与其他进程交替占用执行,实现进程的调度。如果在运行过程中收到信号等,那么就会进入内核模式,运行信号处理程序,之后再返回用户模式。

6.6 hello的异常与信号处理

6.6.1异常

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

  1. 中断(Interrupt):

特点:由硬件或者其他外部设备引起,打断当前程序的正常执行流程。

例子:定时器中断、I/O设备中断等。

目的:允许操作系统与外部设备进行交互,处理外部事件。

图6-4 中断

  1. 陷阱(Trap):

特点:由当前运行的程序主动触发,通常用于执行系统调用或者软中断。

例子:系统调用、断点调试等。

目的:提供一种用户空间向内核空间进行转换的手段,以便执行特权指令或请求操作系统提供服务。

  1. 故障(Fault):

特点:由当前运行的程序引起的可恢复异常,例如页面错误(Page Fault)。

例子:缺页故障、缓存未命中等。

目的:让程序能够处理一些特殊情况,例如虚拟内存的页面错误需要操作系统进行页面置换。

4. 终止(Abort):

特点:由当前运行的程序引起的不可恢复异常,导致程序被终止。

例子:除数为零、非法指令等。

目的:保护系统免受严重错误的影响,确保不良行为不会对系统造成损害。

6.6.2信号

Linux中的信号如下图:

图6-5 常见信号图

在hello运行过程中,测试其中部分信号。

SIGSTP:当 hello 在前台运行时,按下 Ctrl+z 会向它发送 SIGSTP 信号,这个进程就会暂时挂起,可以使用 fg %<pid> 命令,让它在前台继续执行:

图6-6 测试SIGSTP信号

SIGINT:当 hello 在前台运行时,按下 Ctrl+c 会向它发送 SIGINT 信号,这个进程就会被终止:

图6-7 测试SIGONT信号

在执行 hello 程序的命令后面加上 &,这样它就会在后台运行,分别使用 ps 和 jobs 来查看 hello 进程。ps命令可以监视后台其它的进程。jobs命令可显示当前shell环境中已启动的作业状态。

图6-8 查看后台运行的hello进程

当进程在后台运行时,我们用键盘发送的信号是无法发送给它,这时可以用 kill 命令来终止它:

图6-9 使用kill终止后台进程

6.7本章小结

这章讲解了 hello 如何运行在操作系统的上下文中,以及它如何受到信号的控制。

(第61分)

7章 hello的存储管理

7.1 hello的存储器地址空间

hello 进程是与其它进程共享 CPU 和主存资源的,为了更加有效地管理内存并且少出错,现代操作系统提供了一种对主存的抽象概念,叫做虚拟内存。虚拟内存是硬件异常、硬件地址翻译、主存、磁盘文件和内核软件的完美交互,它为每个进程提供了一个大的、一致的和私有的地址空间。

逻辑地址:格式为“段地址:偏移地址”,是 CPU 生成的地址,在内部和编程使用,并不唯一。

线性地址:逻逻辑地址到物理地址变换之间的中间层,逻辑地址经过段机制后转化为线性地址。

虚拟地址:保护模式下,hello 运行在虚拟地址空间中,它访问存储器所用的逻辑地址。

物理地址:加载到内存地址寄存器中的地址,内存单元的真正地址。CPU 通过地址总线的寻址,找到真实的物理内存对应地址。

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

段式管理是一种内存管理机制,它将内存划分为多个段(segment),每个段都有自己的基地址和长度。当程序使用逻辑地址访问内存时,需要经过以下步骤进行段式管理的变换:

1、逻辑地址生成:程序中产生的逻辑地址由段选择符和偏移量组成。段选择符用于指定所需访问的段,偏移量则表示相对于该段基地址的偏移量。

2、段选择符解析:根据段选择符从全局描述符表(GDT)或局部描述符表(LDT)中找到对应的段描述符。

3、段描述符解释:段描述符包含了段的基址、段限长、访问权限等信息。通过解释段描述符,可以得到段的基地址。

4、线性地址计算:将段的基地址与偏移量相加,即可得到线性地址。

图7-1 段式管理

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

页式管理是一种内存管理机制,它将内存划分为固定大小的页(page),同时将逻辑地址和物理地址分别划分为相同大小的页。当程序使用逻辑地址访问内存时,需要经过以下步骤进行页式管理的线性地址到物理地址的变换:

1、逻辑地址生成:程序中产生的逻辑地址由页号和偏移量组成,其中页号表示所在页的索引,偏移量表示相对于该页起始地址的偏移量。

2、页表查找:操作系统维护了一张页表,用于将逻辑地址中的页号映射到对应的物理页框(frame)。通过页号在页表中查找,可以得到该逻辑页对应的物理页框号。

3、物理地址计算:将页表查找得到的物理页框号与偏移量相加,即可得到物理地址。

在这个过程中,页表的查找和物理地址的计算是由硬件的内存管理单元(MMU)来完成的。MMU利用页表中的映射关系来将逻辑地址转换为物理地址,同时还会进行访问权限的检查,以确保内存访问的安全性。

图7-2 页式管理

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

在现代计算机体系结构中,虚拟地址(VA)到物理地址(PA)的变换是通过使用页表和TLB来实现的。四级页表指的是一个具有四级索引结构的页表,这种结构常见于一些现代处理器架构中。

使用TLB和四级页表进行VA到PA变换的基本步骤为:

1、虚拟地址生成:程序中产生的虚拟地址由页号和偏移量组成,其中页号表示所在页的索引,偏移量表示相对于该页起始地址的偏移量。

2、TLB查找:TLB是一个高速缓存,用于存储最近使用的页表项的映射关系。处理器首先会在TLB中查找虚拟地址对应的物理地址映射。如果TLB中存在这个映射,则可以直接得到相应的物理地址;如果TLB未命中,则需要进行额外的步骤。

3、页表查找:如果TLB未命中,处理器会根据页表的索引结构(包括四级页表)逐级查找,以获取虚拟地址对应的物理页框号。

4、物理地址计算:将页表查找得到的物理页框号与偏移量相加,即可得到最终的物理地址。

在这个过程中,TLB充当了一个高速缓存,能够加速常用地址映射的查找,从而提高了地址转换的速度。同时,四级页表的结构能够更好地组织和管理大内存空间,使得操作系统可以更灵活地管理内存。

图7-3 TLB和四级页表进行VA到PA变换

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

三级缓存是现代处理器中常见的一种层次化的缓存结构,通常包括L1缓存、L2缓存和L3缓存。物理内存访问在这个结构下的流程为:

1、L1缓存查找:当处理器需要访问一个物理内存地址时,首先会查询L1缓存,即最靠近处理器核心的缓存。如果所需数据在L1缓存中命中(即缓存中存在对应的数据块),则可以直接从L1缓存中读取或写入数据。

2、L1缓存未命中:如果在L1缓存中未命中,处理器将会继续查询L2缓存。L2缓存位于L1缓存之后,容量较大。如果所需数据在L2缓存中命中,处理器会从L2缓存中读取或写入数据,并且将这些数据同时加载到L1缓存中,以提高后续对同一数据的访问速度。

3、L2缓存未命中:如果在L2缓存中也未命中,处理器将进一步查询L3缓存。L3缓存通常位于多个处理器核心之间共享,容量更大。如果所需数据在L3缓存中命中,处理器会从L3缓存中读取或写入数据,并且将这些数据同时加载到L2和L1缓存中。

4、L3缓存未命中:如果在L3缓存中也未命中,处理器将会直接访问主存(即物理内存)。主存访问速度相对较慢,因此在此阶段可能会引发较长的访问延迟。同时,处理器会将主存中的数据加载到L3、L2和L1缓存中,以提高后续对同一数据的访问速度。

图7-4 三级Cache支持下的物理内存访问

7.6 hello进程fork时的内存映射

fork操作期间,操作系统会执行以下步骤来处理内存映射:

1、初始状态:父进程拥有一段内存空间,其中包括代码段、数据段、堆和栈等不同的区域。

2、调用fork():当父进程调用fork()创建子进程时,操作系统会复制父进程的整个地址空间,并分配给子进程独立的物理内存空间。但是,这个时候并不会立即复制父进程的整个地址空间,而是采用写时复制的方式。

3、写时复制:在写时复制的过程中,操作系统会将父进程的地址空间标记为只读,当父进程或子进程尝试修改内存内容时,操作系统会检测到这种行为,并为子进程分配新的物理页,然后将需要修改的数据复制到子进程的物理页上,使得父子进程拥有各自独立的物理内存空间。

7.7 hello进程execve时的内存映射

execve()系统调用执行过程中处理内存映射步骤如下:

1、调用execve():当进程调用execve()系统调用时,操作系统会加载新的可执行程序文件,并创建一个新的地址空间和内存映射。

2、清理旧地址空间:首先,操作系统会清理旧程序的地址空间,包括清除旧程序的代码段、数据段、堆和栈等区域所占用的内存空间。

3、加载新程序:接着,操作系统会根据新程序的可执行文件格式(如ELF格式)来创建新的地址空间,并将新程序的代码段、数据段、堆和栈等区域加载到新的地址空间中。

4、更新页表:操作系统会更新进程的页表,建立新的虚拟地址到物理地址的映射关系,以确保进程能够正确访问新程序的地址空间。

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

7.8.1缺页故障

缺页故障(Page Fault)指的是当程序试图访问虚拟内存中的一个页面,而该页面当前并不在物理内存中时所引发的异常情况。

操作系统在收到缺页故障时会进行相应的处理:

1、页表检查:首先,操作系统会检查进程的页表,判断导致缺页故障的虚拟地址对应的页面是否已经被加载到物理内存中。如果页面已经在物理内存中,那么可能是由于权限问题或其他原因导致的访问异常,操作系统将会相应地处理这些情况。

2、页面调度:如果所需页面尚未加载到物理内存中,操作系统将需要将这个页面从磁盘加载到内存中。这个过程称为页面调度(Page Replacement)。操作系统会根据一定的置换算法(如LRU、LFU等)选择一个牺牲页面,将其写回磁盘,并将需要的页面加载到空闲的物理页面中。

3、更新页表:一旦页面已经被加载到内存中,操作系统会更新进程的页表,建立新的虚拟地址到物理地址的映射关系,以便程序能够正确访问所需的页面。

7.8.2缺页中断

缺页中断(Page Fault Interrupt)是处理缺页故障的一种机制,当操作系统检测到缺页故障时,会产生一个中断,这个中断称为缺页中断。操作系统会在收到缺页中断后,根据上述步骤来处理缺页故障,确保程序能够正确访问所需的页面。

7.9动态存储分配管理

动态存储分配管理是指在程序运行时动态地为程序分配和释放内存空间的管理方式。动态存储分配管理通常由操作系统或者运行时库来实现,能够有效地利用内存资源并满足程序对内存空间动态变化的需求。

以下是一些常见的动态内存管理方法:

1、堆内存分配:堆内存分配是最常见的动态内存管理方法之一,通过调用类似malloc()、new等函数来申请一定大小的内存块,并返回该内存块的起始地址。程序员需要手动管理这些内存块的生命周期,确保及时释放不再使用的内存块,以避免内存泄漏。

2、内存池:内存池是一种预先分配一定大小的内存空间池的管理方式。程序可以从内存池中动态地分配内存块,并在使用完毕后将内存块归还给内存池而不是直接释放。内存池能够减少内存分配和释放操作的频率,提高内存分配的效率。

3、垃圾回收:垃圾回收是一种自动化的内存管理方法,通过识别和回收不再使用的内存块来释放内存空间。垃圾回收器会自动跟踪程序中的对象引用关系,并在需要时回收没有被引用的对象。常见的垃圾回收算法包括标记-清除、引用计数、复制等。

4、内存碎片整理:内存碎片整理是指对内存中的碎片进行整理和合并,以便更好地利用可用内存空间。内存碎片化是由于频繁的内存分配和释放操作导致的,通过整理内存空间可以减少碎片化,提高内存利用率。

动态内存管理还涉及到如何选择合适的内存分配策略。常见的策略包括首次适应、最佳适应、最坏适应等。不同的策略对内存分配的速度、内存碎片化程度等方面有不同的影响,需要根据具体的应用场景选择适合的策略。

7.10本章小结

本章介绍了存储器地址空间、段式管理、页式管理,VA 到 PA 的变换、物理内存访问, hello 进程 fork 时和 execve 时的内存映射、缺页故障与缺页中断处理。这些巧妙的设计使得我们的 hello 最终得以运行。

(第7 2分)

8章 hello的IO管理

8.1 Linux的IO设备管理方法

设备的模型化:文件

设备管理:unix io接口

在Linux系统中,文件被视为一个包含m个字节序列的实体,用B0、B1直到Bm-1来表示。此外,所有的I/O设备都被抽象成文件。文件分为几种类型:1、普通文件,包括文本文件(只包含ASCII码或Unicode字符)和二进制文件;2、目录,包含一组链接的文件,每个链接将文件名映射到一个文件;3、套接字,用于与另一个进程进行跨网络通信的文件。所有的输入和输出都被视作对相应文件的读和写操作。这种将设备映射为文件的方式允许Linux内核引入了一个简单、低级的应用接口,称为Unix I/O,这使得所有的输入和输出都能以一种统一且一致的方式来执行。

8.2 简述Unix IO接口及其函数

IO(Input/Output)接口是计算机系统中用于实现输入和输出操作的一种通用接口。它定义了程序与外部设备之间的数据传输规则和方式。在计算机系统中,常见的外部设备包括键盘、鼠标、显示器、打印机、硬盘等。

IO接口的主要目标是提供简单、统一的方式来进行数据的读取和写入。它通过抽象和封装底层硬件设备的细节,使得程序开发者能够更加方便地进行IO操作。常见的IO接口函数有以下几个:

1. open():打开文件或设备,并返回一个文件描述符(File Descriptor)。可以指定打开的模式(如只读、只写、读写等)和其他参数。

2. close():关闭文件或设备,释放文件描述符。

3. read():从文件或设备中读取数据。需要指定读取的字节数或缓冲区的大小。

4. write():向文件或设备写入数据。需要指定写入的字节数或要写入的数据。

5. seek():在文件或设备中移动文件指针的位置。可以用于随机访问文件或设备的不同部分。

6. flush():将缓冲区中的数据立即写入文件或设备,确保数据的及时更新。

7. ioctl():用于控制设备的特定操作,如设置设备参数、查询设备状态等。

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函数输入的参数中含有“…”,这代表可变形参,即传递参数的个数不确定。在va_list arg = (va_list)((char*)(&fmt) + 4);这句中, va_list定义为一个字符指针,(char*)(&fmt) + 4) 表示的是...中的第一个参数。fmt是一个指针,这个指针指向第一个const参数(const char *fmt)中的第一个元素。这也就说明了(char*)(&fmt) + 4) 表示的是...中的第一个参数的地址。

Vsprintf函数定义为int vsprintf(char *buf, const char *fmt, va_list args)。vsprintf的作用就是格式化。它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出。

最后write函数就会把buf中的i个元素的值写到终端。

但从硬件上看,从vsprintf到最终显示器上显示出字符串对应一下过程:

从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等。字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。

8.4 getchar的实现分析

int getchar(void)

{

    static char buf[BUFSIZ];

    static char* bb = buf:

    static int n = 0;

    if (n == 0) {

        n = read(0, buf, BUFSIZ);

        bb = buf;

    }

    return (–n >= 0) ? (unsigned char)*bb++ : EOF;

}

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

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

当用户通过键盘输入字符串时,会触发键盘中断异常,导致执行键盘中断处理子程序。在处理子程序中,键盘输入的字符会被转化成对应的ASCII码,并保存到系统的键盘缓冲区中。当程序调用getchar函数时,实际上会调用read函数来从键盘缓冲区中读取字符,并将它们保存到内存中的一个缓冲区(比如buf)中。接着程序会测量该字符串的长度,假设为n,然后让字符指针bb指向buf。最后,getchar会返回buf中的第一个字符。如果测得的字符串长度n小于0,意味着出现了错误,可能是遇到了EOF(文件结束)标志,程序会报告相应的错误。

8.5本章小结

本章简单介绍了Linux下系统I/O的一些相关概念和系统级函数调用,介绍了Linux下一些接口的使用和一些读写函数,同时分析了printf函数和getchar函数的实现过程。

(第81分)

结论

至此,hello 终于走完了它的一生,让我们为它的一生做个小结:

1、程序设计者编写出它的基因——hello.c

2、预处理器完善它的基因——hello.i

3、编译器为它注入的灵魂——hello.s

4、汇编器为它的诞生做最后的准备——hello.o

5、链接器让它长出完整的躯体——hello

6、Shell 为它创建子进程,让它真正成为系统中的个体

7、加载器映射虚拟内存,给予它成长的条件

8、CPU 的逻辑控制流让它驰骋在硬件与操作系统之上

9、虚拟地址这一计算机系统最伟大的抽象为它的驰骋导航

10、malloc 的高效管理让它的驰骋拥有更广阔的天地

11、信号与异常约束它的行为,让它总是走在康庄大道之上

12、Unix I/O 打开它与程序使用者交流的窗口

13、当 hello 垂垂老矣,运行完最后一行代码,__libc_start_main 将控制转移给内核,Shell 回收子进程,内核删除与它相关的所有数据结构,它在这个世界的所有痕迹至此被抹去。

回首它的一生,惊心动魄,千难万险。其中的每个阶段无不凝结着人类最伟大的智慧!

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

附件

列出所有的中间产物的文件名,并予以说明起作用。

(附件0分,缺失 -1分)

文件

作用

hello.c

源代码

hello.i

预处理后的代码

hello.s

汇编代码

hello.o

可重定位目标文件

hello

链接后的可执行目标文件

hello.txt

Hello的反汇编代码

参考文献

为完成本次大作业你翻阅的书籍与网站等

[1]  林来兴. 空间控制技术[M]. 北京:中国宇航出版社,1992:25-42.

[2]  辛希孟. 信息技术与信息服务国际研讨会论文集:A集[C]. 北京:中国科学出版社,1999.

[3]  赵耀东. 新时代的工业工程师[M/OL]. 台北:天下文化出版社,1998 [1998-09-26]. http://www.ie.nthu.edu.tw/info/ie.newie.htm(Big5).

[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.

(参考文献0分,缺失 -1分)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值