计算机系统大作业——程序人生

计算机系统

大作业

题     目  程序人生-Hello’s P2P 

专       业      人工智能领域                 

学     号      2022112624                 

班     级      22WL028                 

学       生       叶祥              

指 导 教 师       郑贵滨                

计算机科学与技术学院

2024年5月

摘  要

本文主要通过利用计算机系统课程中所学习的有关程序运行全过程的知识,对预处理,编译,汇编等过程及进程管理、存储管理、IO管理等方面,对hello.c文件进行细致分析,从而更好的了解计算机系统的运行。

关键词:计算机系统,分析                         

目  录

第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:指hello.c作为源文件先通过预处理器cpp形成修改了的源程序hello.i(文本文件),再通过编译器ccl产生hello.s汇编程序(文本文件),随后通过汇编器as产生可重定位目标程序hello.o(二进制文件),最后在通过链接器链接其他.o文件,即可最终产生可执行目标程序(二进制文件)

020:用户在shell命令行中输入./hello时,系统会通过Fork函数产生一个只与父进程pid不相同的子进程,并在子进程中调用evecve,它启动加载器,丢弃原有上下文中的内容,并新建出task_struct及其目录的结构。再设置程序计数器至程序开始运行处,使程序运行。在完成一系列变量函数的调用与使用后,程序停止运行,变为僵尸进程等待回收。

1.2 环境与工具

硬件环境:

X64 CPU 3.20GHz 16G RAM 521GHD Disk

软件环境:

Windows 11 64位,Visual Basic Code,Dev-c++,XShell,XFTP

1.3 中间结果

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

1.4 本章小结

       本章总体分析了hello作为一个程序的运行全过程,并给出本文所运用的环境与工具。

第2章 预处理

2.1 预处理的概念与作用

预处理的概念:

预处理是编译器在正式编译源代码前,对代码进行初步的处理,预处理器通过读取源代码,根据预处理指令对代码或代码块进行文本替换或修改。

预处理的作用:

能使编译器更高效的处理源代码,并为编程人员提供有效的工具来管理和优化代码。

2.2在Ubuntu下预处理的命令

       命令为gcc hello.c -E -o hello.i,结果如下图所示

图1.Ubuntu下预处理命令结果展示

2.3 Hello的预处理结果解析

图2.Ubuntu下预处理文本结果展示

如图所示,代码总行数大概在3000行左右,远超最初的三四十行,且#include头文件被大量的代码替代,说明预处理过程中完成了代码的修改与替换。

2.4 本章小结

本章明确了预处理的概念及作用,给出预处理文件在ubuntu下生成的命令,并展示了预处理后的文本文件,可以发现预处理文件与源文件相比差别不大,主要是头文件的替换。

第3章 编译

3.1 编译的概念与作用

编译的概念:

编译(Compilation)是将高级编程语言编写的源代码转换为计算机可以执行的低级语言(通常是机器代码或字节码)的过程。这个过程由编译器完成,编译器是一种专门的软件工具。

编译的作用:编译是将高级编程语言转换为机器可执行代码的关键步骤。编译器不仅负责翻译代码,还执行语法和语义检查、代码优化、错误报告等功能,从而提高代码的性能、安全性和可靠性,同时增强代码的可读性和可维护性。       

3.2 在Ubuntu下编译的命令

       命令为gcc hello.i -S -o hello.s,结果如下图所示

图3.Ubuntu下编译命令结果展示

3.3 Hello的编译结果解析

图4.main函数栈的空间分配

      如图所示,main函数内部定义了一个未赋值的局部变量int i,局部变量存在栈中,生命周期与main相同,故rsp指针向下移动32位,为变量留出充足的位置。

图5.循环变量赋值

     

      如图所示,从main函数的汇编代码中可以看出,argc和argv分别存储在%edi和%rsi中,并存放在rbp向下20位的位置与向下32位的位置。

图6.参数存储

      字符串参数存储在.text中并进行标号

图7.字符串存储

      后续访问时,使用rip间址寻址。

图8.间接寻址

      赋值操作通过mov指令实现。

图9.赋值语句

      i++在汇编语言中用addl指令表示。

图10.算术加法

      不等号在汇编语言中,使用cmp和je的组合进行实现。cmp设置标志位,je判断标志位。

图11.不等号的比较运算

      小于号与不等号的处理形式类似。

图12.小于号的比较运算

      循环变量初始值i=0,与8比较,循环8次

图13.循环操作

      汇编代码中通过首地址并进行偏移操作,取argv中的字符串的地址。argv数组中的内容存在了栈中,取出对应的字符串的地址,并放到%rsi和%rdx中,作为printf的第二和第三个参数,最后完成输出。

图14.数组操作

      Printf函数第一次调用,由于只有一个字符串参数,被转化为puts函数进行处理,使用寄存器%rdi进行传入

图15.printf函数第一次调用

      Printf函数第二次调用,有字符串,argv[0],argv[1]三个参数传入

图16.printf函数第二次调用

      将1传入%edi中作为参数进行传递,执行exit函数

图17.exit函数调用

     

      Argv[3]作为参数传入%rdi寄存器

图18.atoi函数调用

      Atoi函数返回值存入%rax中,再作为参数给寄存器%rdi并最终传入sleep函数中

图19.sleep函数调用

      函数最终返回的过程中会执行以下操作:恢复寄存器为初始状态、恢复旧的帧指针%rbp、跳转到原有控制流的地址。一般会在结尾使用ret指令。

图20.函数返回

3.4 本章小结

本章明确了编译的概念及作用,给出编译文件在ubuntu下生成的命令,并展示了编译后的文本文件,对数据、赋值、算术操作、比较操作、数组操作、函数调用、函数返回等操作的汇编代码进行分析与思考。

第4章 汇编

4.1 汇编的概念与作用

       汇编的概念:

汇编是一种低级编程语言,它通过直接对应于计算机硬件的指令来进行编程。汇编语言通常是特定于某种计算机架构的,这意味着不同的处理器类型有不同的汇编语言。

汇编的作用:

汇编语言主要作用是高效控制硬件和性能优化。它允许程序员直接操作硬件资源,如寄存器和内存地址,从而实现高效操作和优化。在对性能要求极高的程序中,如操作系统内核、驱动程序和嵌入式系统,汇编语言可以生成更高效的代码。此外,通过编写和调试汇编代码,程序员能够深入理解计算机的底层工作机制。

4.2 在Ubuntu下汇编的命令

       指令为as hello.s – o hello.o

图21.Ubuntu下汇编指令结果展示

4.3 可重定位目标elf格式

1. elf头

       通过输入readelf -h hello.o指令实现,结果如下

图22.elf头结果展示

Elf头以一个16进制序列开始,称为魔数,用来描述文件系统字的大小及其顺序。余下的部分信息是解释器与目标文件的具体信息。

2. 节头

图23.节头结果展示

       在节头中可以看到有13个节,展示了每一个节的具体信息。表明了其名称,类型,地址等信息。

3.重定位节

       .rela.text节是text节的重定位信息,给出了偏移量、信息、寻址类型、符号的值与符号名称以及addend的数值。

‘.rela.eh_frame’节是eh_frame节的重定位信息。

图24.重定位节

      

4.符号表

       符号表展示了每个符号的编号、值、大小、类型、局部性或者全局性、是否可见、是否可被定义以及名字

图25.符号表

4.4 Hello.o的结果解析

1.  hello.asm文件有每句汇编语言的地址与编号顺序,而hello.s没有

2.  hello.asm文件中跳转利用的是代码地址,而hello.s是每个段的标号

3.  hello.asm 文件中调用函数是跳转到对应地址,而hello.s是利用函数名进行调用。

4.5 本章小结

本章主要对汇编后的可重定向文件Hello.o进行分析,理解汇编的概念与作用并给出了对应的指令,并深入分析hello.o的信息,最后将其反汇编内容与hello.s的内容进行比对并获得异同。

第5章 链接

5.1 链接的概念与作用

       链接的概念:

链接是将编译后的目标文件(object files)组合成可执行文件或库的过程。它包括解析符号、解决重定位、整合代码和数据段,以及最终生成可以在特定操作系统和硬件平台上运行的二进制文件。

链接的作用:

链接的主要作用是将多个编译后的目标文件整合成一个可以运行的可执行文件或库。它通过解析和绑定各目标文件中的符号,将未解析的函数调用和变量引用连接到对应的定义上,解决了符号解析问题。同时,链接器调整代码和数据的地址引用,确保所有引用和调用在内存中的地址正确,完成重定位操作。此外,链接器将各目标文件的代码段和数据段合并,优化和精简代码,删除未使用的符号,最终生成一个独立的可执行文件或库。

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

图26.Ubuntu下链接命令结果展示

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

1. Elf头

图27. Hello的elf头结果展示

开头与hello.o类似,都是16个字节构成的魔数,后续是ELF头的大小、目标文件类型、机器类型、节头部表的文件偏移及其中条目的信息。

2. 节头

图28.hello的节头结果展示

       表示了每一节的名字、种类、地址与偏移量

3.程序头

图29.hello的程序头结果展示

       每个程序头记录了一个内存段或为程序执行而预先准备的必要信息,包括类型、偏移量、虚拟内存地址、段的权限标志与对齐要求。

5.4 hello的虚拟地址空间

1.节地址

       查看hello的节头表,可以查看到.interp节的详细信息。

图30. .interp在节头表中的信息

    

图31.edb观察到的.interp的汇编代码

2.数据段

       如下图所示为data dump

图32. Data dump示意图

5.5 链接的重定位过程分析

1.地址类型区别

       链接之后的hello.asm中地址使用的是虚拟内存的地址,而hello.o.asm中使用的是内存的绝对地址。

图33.   hello.asm使用地址类型

图34. hello.o.asm使用地址类型

2. 函数调用

       链接之后的hello.asm中调用函数是直接声明函数名字,而hello.o.asm中使用的是函数地址

图35. hello.asm使用函数表示

图36. hello.o.asm使用函数表示

3. 重定位

图37.重定位前

图38.重定位后

图39.rodata节具体信息

       .rodata节首地址位于0x402000处且偏移量为8,从hello.o.asm中可以看出,在图中的lea命令被执行后,PC值应位于下一条命令的位置,即0x401145。则经过计算后:0x402000+8-0x401145=0xec3即重定位后lea使用的虚拟内存地址。

5.6 hello的执行流程

_dl_start

_dl_init

_cax_atexit

_new_exitfn

_libc_start_main

_libc_csu_init

Main

       {

              printf();

              atoi();

              sleep();

}(重复执行八次)

getchar

_exit

_dl_runtime_resolve_xsave

_dl_fixup

_dl_lookup_symbol_x

exit

5.7 Hello的动态链接分析

图40.‘.got’与‘.got.plt’详细信息展示

图41.链接前got的内容

      

       GOT[0]和GOT[1]存储动态链接器在解析各个函数时所需要的全部信息,GOT[2]是动态链接器在ld-linux.so模块中的入口点;其余元素分别对应一个可以被调用的函数,当第一次调用函数时,对应的内容被解析。

5.8 本章小结

本章主要介绍了链接的概念与作用,给出了在Ubuntu下的指令,并给出hello的格式如elf头,程序头表,节头表以及其对应的虚拟内存地址。并分析了链接的重定向全过程并给出hello执行过程中各部分的顺序,最后分析了hello的动态链接。

第6章 hello进程管理

6.1 进程的概念与作用

进程的概念:

       进程是计算机中程序执行的实例。它不仅包括程序的代码,还包括程序计数器、寄存器内容、变量、堆栈、数据段等执行状态信息。每个进程在操作系统中作为独立的运行单元,被分配独立的内存空间和系统资源。

进程的作用:

    进程在操作系统中扮演着至关重要的角色,通过提供资源管理、并发执行、隔离性、通信与同步以及任务调度等功能,保证计算机系统的高效、稳定和安全运行。

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

作用:

       ShellBash)是操作系统的命令解释器,充当用户与操作系统之间的桥梁。它负责解析和执行用户输入的命令,提供脚本编写和执行功能,用于自动化任务和系统管理,同时管理用户的工作环境,包括环境变量和路径设置。Shell允许用户启动、控制和管理进程,支持输入输出重定向和管道操作。

处理流程:

  1. 启动Shell:用户登录系统或在终端窗口启动Shell。Shell显示提示符,等待用户输入命令。

  1. 读取输入:Shell从终端读取用户输入的命令。用户可以输入单个命令或多个命令的组合。

  1. 解析命令:Shell对用户输入的命令进行解析,识别命令、参数、选项以及输入输出重定向符号。

  1. 查找命令:Shell查找并确定命令的位置。内部命令(如cd、echo)直接执行,外部命令则在指定的路径(由PATH环境变量定义)中查找对应的可执行文件。

  1. 执行命令:

内部命令:Shell直接执行内部命令。

外部命令:Shell创建一个子进程,通过fork和exec系统调用执行外部命令。子进程运行完成后,返回控制权给父进程(Shell)。

  1. 处理输入输出:根据用户的输入输出重定向指示,Shell将命令的标准输入、标准输出或标准错误重定向到指定的文件或设备。

  1. 等待和返回结果:Shell等待命令执行完成,获取命令的退出状态,并将结果显示给用户。如果命令在后台运行,Shell立即返回提示符而不等待命令完成。

  1. 处理脚本:如果输入的是脚本文件,Shell按照脚本中的命令顺序逐行读取并执行,直到脚本执行完毕。

6.3 Hello的fork进程创建过程

图42.hello执行结果展示

       首先对输入命令进行解析,发现命令第一个参数并不是内置命令,则找到hello程序并将其存入内存中,之后执行fork()函数创建一个子进程,二者信息几乎完全相同,区别在于两个进程的pid不同。展开新进程后,调用execve函数在当前进程的上下文加载并运行hello程序,最后hello内的main函数被调用并执行。

6.4 Hello的execve过程

1.调用execve:

当一个进程调用execve系统调用时,它提供要执行的程序路径、参数和环境变量。比如,执行一个名为hello的程序,路径是/usr/bin/hello,参数列表包括程序名和其他参数(如["hello"]),环境变量列表可以为空或包含键值对。

2. 内核检查和加载:

内核先检查给定的程序路径,确保文件存在且可执行,再检查调用进程是否有执行该文件的权限,最后确认文件格式(如ELF),以确保它是可执行文件。

3. 清理当前进程状态:

根据文件描述符的close-on-exec标志,内核关闭相应的文件描述符,再释放当前进程的用户空间内存,清理堆、栈、数据段、代码段等。

4. 加载新程序:

内核将新程序的代码段、数据段等映射到进程的地址空间中,再为新程序设置堆栈,并将参数和环境变量复制到新堆栈中。

5. 更新进程状态:

内核将程序计数器设置为新程序的入口地址,并重置CPU寄存器,使其符合新程序的运行要求。进程的标识保持不变,但进程的命令行参数和环境变量更新为新程序的。

6. 执行新程序:

内核将控制权交给新程序,从其入口地址开始执行。

6.5 Hello的进程执行

1. 进程创建:

(1)用户在命令行输入/usr/bin/hello并按下回车键。

(2)Shell(如Bash)调用fork()创建一个子进程,子进程是当前Shell进程的副本。

(3)子进程调用execve("/usr/bin/hello", args, env),用hello程序替换自身的内存空间。

2. execve系统调用:

(1)子进程进入核心态,内核执行execve系统调用。

(2)内核检查文件路径、权限和格式。

(3)内核清理子进程的当前内存空间,并加载hello程序的代码和数据。

(4)初始化新程序的堆栈、设置程序计数器、寄存器,并准备将控制权交还给新程序。

3. 调度器分配时间片:

(1)hello程序开始执行后,操作系统调度器分配一个时间片给该进程。

(2)时间片是操作系统为每个进程分配的一段CPU执行时间,确保多任务系统中各进程公平分享CPU资源。

4. 用户态执行:

(1)hello程序在用户态下执行,运行其main函数。

(2)程序调用printf函数,这是一个标准库函数,会将输出请求发送到标准  输出(通常是终端)。

5. 系统调用:printf:

(1)printf函数内部调用write系统调用,将数据写入标准输出。

(2)这触发用户态到核心态的转换,进入内核空间。

(3)内核执行实际的输出操作,将“Hello, World!”写到终端设备。

(4)完成后,内核返回用户态,继续执行hello程序。

6. 程序完成执行:

(1)hello程序执行完main函数,返回退出状态(通常是0)。

(2)调用exit系统调用,通知内核进程已完成。

(3)再次触发用户态到核心态转换,内核进行清理工作。

7. 进程终止与资源释放:

(1)内核释放进程的资源(如内存、文件描述符等)。

(2)内核将该进程标记为终止状态,并准备调度下一个进程。

8.上下文切换:

(1)调度器选择下一个准备好运行的进程(可能是另一个用户进程或内核进程)。

(2)保存当前进程的上下文信息(寄存器状态、程序计数器等)。

(3)恢复下一个进程的上下文信息。

(4)如果选择的进程是用户进程,内核从核心态切换回用户态,执行新进程的代码。

6.6 hello的异常与信号处理

1. 正常运行

图43. 正常运行结果展示

       进行10次循环输出,每次间隔两秒。输出完后按任意符号返回命令行

2. Ctrl+Z挂起

图44.Ctrl+Z挂起

       程序处于挂起状态,这是因为ctrl+z向shell壳传递SIGTSTP信号,使得程序被挂起。

3. fg恢复运行

图45. 输入fg恢复挂起程序运行

      输入fg后可将后台程序放回前台继续运行

4. Ctrl+C终止程序运行

图46.Ctrl+C终止程序运行结果展示

      按Ctrl+C可使程序运行直接终止并回到命令行模式。这是因为Ctrl+C向shell壳传递了SIGINT信号,使程序被终止,并让父进程使用Waitpid()函数等待子进程结束后,回收子进程。

5. jobs命令

图47.jobs命令结果展示

      jobs命令可以列出目前在后台挂起的程序,可以看到ctrl+c直接终止程序,并没有挂在后台,而ctrl+z将程序停止并挂在后台。

6. kill指令

图48.kill命令结果展示

      执行过程如上图所示,说明kill指令仅会向父进程传递SIGINT信号使子进程终止,而不会使得父进程使用waitpid函数等待子进程自行终止并回收。

7.pstree指令

图49.pstree指令结果展示

      先将程序挂起后输入pstree指令即可看到进程树。

  1. 乱按

图50.乱按结果展示

乱按的文本会直接输出,但并不会对程序本身的运行与输出造成影响。

6.7本章小结

       本章主要简述了进程的概念及作用,并简述壳的作用与处理流程,同时分析了fork函数与execve函数,给出了hello进程执行的过程并对各种异常情况可能导致的结果进行展示。

第7章 hello的存储管理

7.1 hello的存储器地址空间

1.逻辑地址

       逻辑地址是程序员在编写程序时使用的地址。它是指程序内部使用的变量、函数和指令地址。逻辑地址是相对于某个基地址的偏移量,也被称为虚拟地址的一部分。对于一个正在运行的程序,这些地址是在编译和链接过程中生成的。

2.线性地址

       线性地址是由逻辑地址通过段地址转换后的地址。在现代计算机系统中,段机制已经不常使用,但在保护模式下,逻辑地址首先被转换为段内偏移,然后通过段寄存器的基址加上这个偏移量,形成线性地址。

3.虚拟地址

       虚拟地址通常被用作逻辑地址的同义词。它是程序运行时看到的地址空间,是一个连续的地址空间,由操作系统提供。每个进程有自己独立的虚拟地址空间,使得进程之间互不干扰。

4.物理地址

       物理地址是实际存在于计算机内存中的地址。它是由内存控制器和硬件直接使用的地址。虚拟地址通过页表映射到物理地址,这个映射由操作系统管理。

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

在Intel的段式内存管理模型中,逻辑地址到线性地址的转换是通过段机制实现的。这种机制在x86架构的保护模式下被广泛使用。

段式管理将内存分为若干段,每个段有一个基地址和一个限制(长度)。每个段都描述了一个连续的地址空间范围,段寄存器用于保存段选择符,而段选择符用于索引段描述符表(GDT或LDT),段描述符包含了段的基地址、段的限制和访问权限等信息。

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

在现代计算机系统中,页式管理是常用的内存管理方式,用于将线性地址转换为物理地址。这种机制使得操作系统可以高效地管理内存,并提供虚拟内存的支持。

在页式管理中,内存被划分为固定大小的页和页框。线性地址通过页表映射到物理地址。每个进程都有自己的页表,由操作系统维护和管理。

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

在现代操作系统中,为了更高效地管理内存和提高地址转换的速度,使用了多级页表和转换后备缓冲(TLB)。在四级页表支持下,虚拟地址到物理地址的转换涉及多级页表查找。通过TLB缓存最近使用的地址映射,可以大幅提高转换速度,减少多级页表查找的开销。这种机制使得操作系统能够高效地管理内存,提供虚拟内存的支持,同时确保系统的安全性和稳定性。

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

在现代计算机系统中,为了提高处理器访问内存的速度,采用了多级缓存(Cache)架构。通常包括L1L2L3三级缓存。这些缓存层次结构在处理器和物理内存之间提供了一个快速的数据访问路径。

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

7.6 hello进程fork时的内存映射

在 Unix 系统中,当一个进程调用 fork 函数时,操作系统会创建一个新的进程(子进程),它是调用进程(父进程)的一个拷贝。这个拷贝包括父进程的所有内存,包括代码段、数据段、堆和栈。然而,现代操作系统通过一种称为写时复制的技术来优化这一过程。

当 fork 被调用时,操作系统会执行以下步骤:

  1. 创建新的进程控制块(PCB): 操作系统为子进程创建一个新的进程控制块,其中包含子进程的 PID 和其他进程管理信息。

  1. 复制父进程的虚拟内存空间: 子进程获得与父进程相同的虚拟内存空间布局,包括代码段、数据段、堆和栈。但是,这些虚拟地址映射到相同的物理页面。

  1. 设置页面为只读(共享): 操作系统将共享的物理页面标记为只读。如果父进程和子进程尝试写入这些页面,将触发页面错误。

4.  处理写时复制: 当页面错误发生时,操作系统会复制该物理页面,并将写操作重定向到新的副本。这一过程确保父进程和子进程在写操作时拥有独立的页面。

7.7 hello进程execve时的内存映射

在 Unix 系统中,当一个进程调用 execve 时,操作系统会用一个新程序替换当前进程的执行内容,而这个过程会涉及到内存映射的重新设置。execve 调用是系统调用的一部分,用于执行一个新程序。

当一个进程调用 execve 时,操作系统会进行以下步骤来重新映射内存:

  1. 卸载旧的内存映射:

当前进程的内存映射会被释放,包括代码段、数据段、堆和栈。也就是说,旧的程序代码和数据不再保留在内存中。

  1. 加载新程序的可执行文件:

操作系统从文件系统中加载新程序的可执行文件,这通常是一个 ELF(Executable and Linkable Format)文件或其他可执行格式的文件。

  1. 创建新的内存映射:

根据新程序的可执行文件的格式和内容,操作系统会重新建立内存映射。具体包括以下几个部分:

代码段(text segment):这是新程序的机器指令所在的内存区域,通常是只读的。

数据段(data segment):包含已初始化的全局变量和静态变量。

BSS段(Block Started by Symbol):包含未初始化的全局变量和静态变量,通常被初始化为零。

堆(heap):用于动态分配内存。

栈(stack):用于函数调用和局部变量。

  1. 设置程序入口点:

操作系统会将程序计数器(程序的入口点)设置为新程序的入口地址,通常是程序的 main 函数的起始位置。

  1. 传递参数和环境变量:

操作系统会将调用 execve 时提供的参数和环境变量复制到新程序的栈上,以便新程序可以访问这些信息。

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

缺页故障和缺页中断处理是操作系统内存管理中的重要概念。缺页故障发生在进程试图访问不在物理内存中的虚拟内存页面时。操作系统通过缺页中断处理机制来解决这个问题,将所需页面加载到物理内存中。

缺页故障可能是因为页面不存在于物理内存中、页面在交换空间中、非法访问。当 CPU 尝试访问一个虚拟内存地址时,如果地址未被映射到物理内存页面,内存管理单元(MMU)会生成一个缺页故障。MMU 将这个异常通知操作系统,触发缺页中断处理机制。

缺页中断处理是操作系统对缺页故障的响应过程,首先操作系统保存当前进程的 CPU 状态,以便在处理完缺页中断后能够恢复进程继续执行。随后检查缺页的原因,确定所需页面是否在交换空间中,或者是非法访问。如果页面在交换空间中,操作系统找到该页面在磁盘上的位置并从磁盘读取页面数据并加载到物理内存中。如果页面从未分配,操作系统为该页面分配一个新的物理内存页面并初始化新的页面。更新页表,将虚拟页面映射到新分配的物理页面,同时设置适当的访问权限。最后恢复进程的 CPU 状态,并继续执行被中断的指令。

7.9动态存储分配管理

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

  1. 基本方法

(1)堆内存分配:

malloc/free:在C语言中,malloc用于分配指定大小的内存块,返回指向该内存块的指针。free用于释放之前分配的内存块,以便操作系统可以重用这块内存。

new/delete:在C++中,new运算符用于分配内存并调用构造函数,delete运算符用于销毁对象并释放内存。

(2)内存池(Memory Pool):

内存池是一种预先分配一大块内存,然后按需分配和释放小块内存的方法。它减少了频繁的系统调用开销,并可以通过集中管理内存来提高性能和减少碎片。

  1. 动态内存管理策略

(1)首次适配:

在空闲链表中找到第一个大小足够的空闲块进行分配。这种方法简单高效,但可能会导致空闲内存碎片化。

(2)最佳适配:

在空闲链表中找到最接近所需大小的空闲块进行分配。这种方法可以减少内存碎片,但查找过程可能较慢。

(3)最差适配:

在空闲链表中找到最大的空闲块进行分配。这种方法旨在避免产生太多的小碎片,但实际效果可能因具体情况而异。

(4)次适配:

类似于首次适配,但从上次分配结束的位置开始查找。它试图分散内存碎片,但可能会在链表末尾产生较大的碎片。

7.10本章小结

       本章主要概述了hello的存储管理,包括存储器的地址空间,段式管理与页式管理,以及cache的内存访问规则,并讲述了应用fork和execve函数时系统的内存映射,给出缺页故障的概念并概括了解决方案——缺页中断处理。

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

设备的模型化:文件

在 Linux 系统中,设备被模型化为文件。这种设计理念称为“万物皆文件”。这种方法的核心思想是将设备抽象为文件系统中的文件,使用户和应用程序能够通过标准的文件操作接口对设备进行操作。这些文件可以是用于顺序读写操作的设备,例如键盘、串口、终端等。也可以是用于随机读写操作的设备,例如硬盘、光驱等。

这些设备文件通常位于 /dev 目录下,例如/dev/sda表示一个硬盘设备。/dev/ttyS0:表示一个串口设备。

设备管理:unix io接口

Unix I/O 接口是 Unix 操作系统提供的一套用于文件和设备操作的标准接口。这些接口通过一组系统调用使用户程序能够对文件、设备以及其他 I/O 资源进行读写和控制操作。

8.2 简述Unix IO接口及其函数

Linux 提供了统一的 Unix I/O 接口,使得对设备的操作类似于对文件的操作。主要的 Unix I/O 接口函数包括:

  1. open:打开文件或设备。
  2. read:从文件或设备读取数据。
  3. write:向文件或设备写入数据。
  4. close:关闭文件或设备。
  5. ioctl:对设备进行控制操作,通常用于设备特定的操作。

通过这些接口,Linux 将设备操作统一抽象为文件操作,简化了设备管理的复杂性。应用程序不需要关心底层硬件的实现细节,只需使用标准的文件操作接口即可完成对设备的读写和控制。

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

8.3 printf的实现分析

图51.printf函数

va_list是一个字符指针的重定义,即:typedef char* va_list,(char*)((&fmt) + 4 )作为第一个参数与栈的结构紧密相关,&fmt存放在栈中,后续的字符型指针存于栈中,由于指针大小位四个字节,所以+4可得到第一个参数。(32位系统指针大小4个字节,64位系统指针大小8个字节)

图52.vsprintf函数

      vsprintf函数将所有参数信息格式化后,返回格式化数组的长度,write函数将第i个参数信息写入终端。然后执行字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。最后显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)

8.4 getchar的实现分析

       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;

}

它可以读取缓冲区内的单个字符,在输入了大量字符后,存放于缓冲区,使用getchar函数后会从缓冲区中读取一个字符并读入。

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

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

8.5本章小结

本章总体介绍了I/O的相关内容,并分析了其管理方法,常用接口与函数,并对printf与getchar函数进行了细致的分析。

结论

       至此分析完了hello在系统由一个初始的c语言文件逐步变成了一个可执行文件,最后运行完成的一个总体过程,下面对整个过程进行一个总结。

  1. 编写c语言源代码,生成.c文件
  2. 通过预处理将头文件与部分源代码结构进行替换获得.i文件
  3. 将.i文件进行汇编处理获得汇编语言文件.s
  4. 进一步通过汇编处理获得可重定向文件.o
  5. 通过链接处理获得可执行文件hello,至此可以被运行
  6. 调用fork函数创建一个子进程,为程序的运行加载提供虚拟内存等必要信息。
  7. 调用execve函数启动加载器映射虚拟内存,之后继续载入物理内存并进入main函数
  8. 通过TLB等工具实现虚拟内存和物理内存之间的翻译,进而访问内存
  9. 通过IO设备实现输入输出,与外界进行交互
  10. 最终hello被其父进程回收,内核收回为其创建的所有信息

附件

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

1.  hello.c: 源程序

2. hello.i :预处理后的程序

  1. hello.s: 编译后的汇编程序
  1. hello.o:汇编后的可重定位目标文件
  1. elf.txt:hello.o的ELF文件
  1. dump_hello.txt:hello.o的反汇编代码
  1. hello:hello.o链接后的可执行目标文件(不包含可调试选项)
  1. hello1:hello.o链接后的可执行目标文件(包含可调试选项)
  1. hello_objdump.s: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分)

  • 20
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值