关注

哈工大ISC2025大作业,程序人生P2P

计算机系统

大作业

题     目  程序人生-Hellos P2P  

专       业         未来技术模块     

学     号         2023111804       

班   级         23wl017,23wlr15  

学       生           张宏硕      

指 导 教 师            吴锐         

计算机科学与技术学院

20255

摘  要

本文以“程序人生-Hello's P2P”为研究对象,通过追踪C程序(hello.c)从源码到进程终止的全生命周期,系统解析计算机系统中程序执行的底层机制与资源管理策略。研究涵盖代码编译流程(预处理、编译、汇编、链接)、进程控制、虚拟内存映射及动态库加载等核心环节,结合Ubuntu平台实验与ELF文件结构分析,揭示了程序加载、执行及内存回收的实现细节。通过探讨地址转换、页表管理及信号处理技术,阐明操作系统如何协调硬件资源,优化进程调度与异常响应。本文从理论与实践双重维度,呈现了计算机系统分层协作的复杂性及其在效率与安全间的平衡逻辑。

关键词:编译过程、进程管理、虚拟内存管理、ELF格式、动态链接                           

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

目  录

第1章 概述

1.1 Hello简介

1.2 环境与工具

1.3 中间结果

1.4 本章小结

第2章 预处理

2.1 预处理的概念与作用

2.2在Ubuntu下预处理的命令

2.3 Hello的预处理结果解析

2.4 本章小结

第3章 编译

3.1 编译的概念与作用

3.2 在Ubuntu下编译的命令

3.3 Hello的编译结果解析

3.4 本章小结

第4章 汇编

4.1 汇编的概念与作用

4.2 在Ubuntu下汇编的命令

4.3 可重定位目标elf格式

4.4 Hello.o的结果解析

4.5 本章小结

第5章 链接

5.1 链接的概念与作用

5.2 在Ubuntu下链接的命令

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

5.4 hello的虚拟地址空间

5.5 链接的重定位过程分析

5.6 hello的执行流程

5.7 Hello的动态链接分析

5.8 本章小结

第6章 hello进程管理

6.1 进程的概念与作用

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

6.3 Hello的fork进程创建过程

6.4 Hello的execve过程

6.5 Hello的进程执行

6.6 hello的异常与信号处理

6.7本章小结

第7章 hello的存储管理

7.1 hello的存储器地址空间

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

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

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

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

7.6 hello进程fork时的内存映射

7.7 hello进程execve时的内存映射

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

7.9动态存储分配管理

7.10本章小结

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

8.2 简述Unix IO接口及其函数

8.3 printf的实现分析

8.4 getchar的实现分析

8.5本章小结

结论

附件

参考文献


第1章 概述

1.1 Hello简介

Hello的P2P与020过程

P2P(Program to Process)

Hello的P2P过程始于一段C语言编写的源代码hello.c。当开发者通过GCC命令gcc -m64 -Og -no-pie -fno-stack-protector -fno-PIC hello.c -o hello触发编译流程时,计算机系统逐层展开一系列转换。首先,预处理器扫描源代码,展开所有#include指令引入的头文件(如stdio.h和unistd.h),替换宏定义,并移除注释,生成扩展后的hello.i文件。接着,编译器将预处理后的代码转换为汇编语言hello.s,其中C语言的控制结构(如for循环和if条件判断)被翻译为具体的x86-64指令,例如cmpl比较参数数量、jne跳转至错误处理逻辑,而printf和sleep函数调用则被映射为对标准库符号的引用。汇编器随后将汇编代码转换为机器码,生成可重定位目标文件hello.o,此时符号地址(如printf和sleep)尚未绑定,仅以占位符形式存在。链接器最终将hello.o与C标准库(如libc.so)及启动代码(如crt1.o)合并,解析所有符号引用,动态链接库的地址通过过程链接表(PLT)和全局偏移表(GOT)实现延迟绑定,生成可执行文件hello。

当用户在Shell中输入./hello 2023111804 张宏硕 15765000085 0时,Shell通过fork系统调用创建一个子进程,该进程继承父进程的上下文,但尚未加载新程序。随后,子进程调用execve系统调用,操作系统将hello文件加载到进程的虚拟地址空间中:代码段(.text)被映射到只读内存区域,全局变量和静态数据(如字符串常量)存入.rodata和.data段,堆栈空间则动态分配。CPU从入口函数_start开始执行,初始化寄存器并跳转至main函数,至此Hello程序正式成为一个活跃的进程,占据内存、CPU时间片等系统资源,完成从静态程序到动态进程的转化。

020(从零开始到零结束)

Hello进程的020生命周期始于操作系统的虚拟内存分配。当execve加载可执行文件时,内核为进程构建四级页表(PGD→PUD→PMD→PTE),将逻辑地址映射到物理内存,并通过TLB(快表)加速地址转换。进程启动后,命令行参数argc和argv通过寄存器%rdi和%rsi传递给main函数。程序首先检查参数数量是否为5,若不满足则调用exit(1)直接终止进程;若参数合法,则进入循环,反复输出学号、姓名和手机号,并通过sleep(atoi(argv[4]))休眠指定秒数(此处15765000085 % 5 = 0,即每次休眠0秒)。

在运行过程中,用户可能通过键盘输入信号干预进程:按下Ctrl-C会触发SIGINT信号,默认行为是终止进程,释放其占用的内存和文件描述符;按下Ctrl-Z则发送SIGTSTP信号,将进程挂起到后台,此时可通过jobs查看任务列表,或使用fg命令将其恢复至前台继续执行。若用户未中断程序,循环结束后getchar()等待输入,按回车键后main函数返回0,进程调用exit系统调用,内核回收其虚拟地址空间、关闭打开的文件,并通知父进程(Shell)子进程的退出状态。最终,Hello进程的所有痕迹(如物理内存页、进程描述符)被彻底清除,系统资源回归“零”状态,仿佛从未存在过。

这一过程中,计算机系统的核心机制——如动态链接、页式存储管理、信号处理——协同工作,既保障了进程的高效运行,又实现了资源的严格隔离与回收,完美诠释了“从零到零”的生命周期。

1.2 环境与工具

硬件环境:

处理器:处理器 AMD Ryzen 9 7945HX with Radeon Graphics,2501 Mhz

机带RAM:16.0 GB

显卡:NVIDIA GeForce RTX 4060 Laptop GPU

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

软件环境:

Windows10 64位,VMwareUbuntu;

开发与调试工具:Visual Studio;Dev-c++;vim objump edb gcc readelf

1.3 中间结果

hello.i          预处理后得到的文本文件

hello.s          编译后得到的汇编语言文件

hello.o          汇编后得到的可重定位目标文件

elf-hello.o.txt     hello.o的ELF格式

dump_hello.o.txt hello.o的反汇编代码

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

elf-hello.txt      hello的ELF格式

dump_hello.txt   hello的反汇编代码

1.4 本章小结

本章首先介绍了hello的P2P,020定义和流程;然后,说明本实验的硬件和软件环境以及本实验生成的中间结果文件的名称和功能。

(第1章0.5分)


第2章 预处理

2.1 预处理的概念与作用

预处理的概念:

预处理是编译过程的第一个阶段,负责处理源代码中以 # 开头的预处理指令。这些指令在编译器正式分析代码之前被处理,主要目的是对源代码进行文本级别的替换、插入或条件筛选,生成一个“纯净”的中间文件(如 .i 文件),供后续编译阶段使用。预处理不涉及语法分析或代码优化,仅对文本进行机械式处理。

预处理的作用不仅限于简单的文本替换,它还通过条件编译指令(如#ifdef、#ifndef)实现代码的灵活控制。例如,在跨平台开发中,可以通过条件编译选择性地包含特定操作系统的实现代码,而无需修改主逻辑。此外,宏定义的合理使用能够简化重复代码,比如将常量或常用操作封装为宏,提升代码的可维护性。然而,预处理阶段也可能引发问题,例如头文件路径错误导致包含失败,或宏展开时因缺少括号引发的优先级错误(如#define SQUARE(x) x*x调用SQUARE(1+2)会错误展开为1+2*1+2)。解决这些问题需要开发者仔细检查宏定义的结构,并在头文件中使用#pragma once或#ifndef保护机制避免重复包含。

在实际项目中,预处理生成的中间文件(.i)不仅是编译的起点,也是调试的重要工具。通过查看hello.i,开发者可以验证头文件是否正确引入、宏是否按预期展开,从而快速定位语法错误或逻辑缺陷。例如,若编译时提示printf未声明,检查hello.i中是否存在stdio.h的内容即可判断是否预处理阶段出错。预处理的高效执行还显著提升了整体编译速度,因为它避免了重复解析相同的头文件,尤其是在大型项目中,这一优化尤为关键。总之,预处理通过文本级的精确处理,为程序从源代码到可执行文件的转化奠定了坚实基础,是计算机系统层次化设计中不可或缺的一环。

2.2在Ubuntu下预处理的命令

gcc -E hello.c -o hello.i

图1.Hello预处理指令

2.3 Hello的预处理结果解析

通过vim打开hello.i进行查看,和源程序进行对比,发现源程序中的hello.c程序本来的内容出现在最后。在此之前,则是stdio.h unistd.h stdlib.h的源代码的依次展开说明预处理确实对源代码有进行文本替换、文件包含、条件编译等操作。

 

图2.Hello预处理结果

2.4 本章小结

本章讲述了在linux环境下,通过使用预处理命令对C语言程序进行预处理操作,同时分析了预处理的含义和作用。利用已有的一个hello程序探索从hello.c到hello.i的过程,并结合具体的代码内容进行了处理后的结果和未处理的源代码之间的异同。我们可以发现预处理后的文件hello.i包含了标准输入输出库stdio.h的内容,以及一些宏和常量的定义。

(第2章0.5分)


第3章 编译

3.1 编译的概念与作用

编译程序所要作得工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。

在hello的一生中,编译器将文本文件 hello.i 翻译成文本文件 hello.s。

编译的过程可以达成以下效果:

1.语法分析:编译程序的语法分析器以单词符号作为输入,分析单词符号串是否形成符合语法规则的语法单位,方法分为两种:自上而下分析法和自下而上分析法。

2.中间代码:源程序的一种内部表示,或称中间语言。中间代码的作用是可使编译程序的结构在逻辑上更为简单明确,特别是可使目标代码的优化比较容易实现中间代码。

3.代码优化:指对程序进行多种等价变换,使得从变换后的程序出发,能生成更有效的目标代码。

4.目标代码:生成是编译的最后一个阶段。目标代码生成器把语法分析后或优化后的中间代码变换成目标代码。此处指汇编语言代码,须经过汇编程序汇编后,成为可执行的机器语言代码。

        

3.2 在Ubuntu下编译的命令

gcc -S hello.i -o hello.s

图3.Hello编译指令

3.3 Hello的编译结果解析

3.3.1. 程序入口与栈帧初始化

C代码逻辑:main函数开始执行时,初始化栈帧并处理命令行参数。

图4.Hello汇编入口部分

endbr64 是安全指令,防止控制流劫持攻击。

初始化栈帧,pushq %rbp 保存原基址指针,movq %rsp, %rbp 建立新栈帧,subq $32, %rsp 分配 32 字节栈空间。

将 argc 和 argv 分别存入 -20(%rbp) 和 -32(%rbp),对应 C 代码中的参数传递。

3.3.2 参数检查与错误处理

C代码逻辑:if (argc != 5)检查参数数量,若错误则输出提示并退出。

图5.Hello汇编参数检查与错误处理部分

cmpl $5, -20(%rbp) 比较 argc 与 5。

je .L2,若相等则跳转到 .L2 标签(正常流程)。

若参数错误,加载错误字符串 .LC0到 %rdi,调用 puts@PLT 输出,随后调用 exit@PLT 终止程序。编译器将 printf 优化为 puts,因为错误字符串不含格式化参数。

3.3.3 循环初始化与参数访问

C代码逻辑:for (i=0; i<10; i++)初始化循环变量i并进入循环。

  

图6.Hello汇编循环初始化与参数访问部分

movl $0, -4(%rbp) 初始化 i=0,存储到栈中 -4(%rbp)。

通过 argv 基址偏移访问参数: argv[3]:addq $24, %rax,对应 argv + 3*8。 argv[2]:addq $16, %rax,对应 argv + 2*8。 argv[1]:addq $8, %rax,对应 argv + 1*8。 参数值分别存入 %rcx、%rdx、%rsi 寄存器,为 printf 调用准备参数。

3.3.4. printf 调用与格式化输出

C 代码逻辑:printf("Hello %s %s %s\n", argv[1], argv[2], argv[3])。

图7.Hello汇编printf调用与格式化输出部分

加载格式字符串 .LC1到 %rdi。

movl $0, %eax 清空 %eax,表示无浮点参数。

call printf@PLT 调用格式化输出函数,参数通过 %rsi、%rdx、%rcx 传递。

3.3.5. sleep与atoi处理

C代码逻辑:sleep(atoi(argv[4]))将字符串参数转换为整数并休眠。

图8.Hello汇编sleep与stoi处理部分

argv[4]通过偏移+32访问,atoi@PLT将字符串转换为整数后传递给sleep@PLT。

3.3.6. 程序结束与清理

C代码逻辑:getchar()等待输入,返回0结束程序。

图9.Hello汇编程序结束语清理部分

call getchar@PLT 等待用户输入。
movl $0, %eax 设置返回值 0。
leave 恢复栈帧,等价于 movq %rbp, %rsp 和 popq %rbp。
ret 结束函数并返回。

3.4 本章小结

本章介绍了编译的概念以及过程。通过hello函数分析了c语言如何转换成为汇编代码。介绍了汇编代码如何实现变量、常量、传递参数以及分支和循环。

(第3章2分)


第4章 汇编

4.1 汇编的概念与作用

汇编概念

汇编是指将汇编语言代码(如 .s 文件)翻译成目标文件(如.o文件)的过程,翻译的结果是机器指令编码,并把这些指令打包成为一个可重定位目标文件的格式,但尚未与其它目标文件或库链接成最终可执行文件。

汇编的作用

.o文件是一个二进制文件,它包含程序的指令编码。汇编就是将高级语言转化为机器可直接识别执行的代码文件的过程,汇编器将.s 汇编程序翻译成机器语言指令,把这些指令打包成可重定位目标程序的格式,并将结果保存在.o目标文件中。

4.2 在Ubuntu下汇编的命令

as hello.s -o hello.o

图10.Hello在ubuntu下汇编的命令

4.3 可重定位目标elf格式

4.3.1 readelf命令

readelf -a hello.o > ./elf.txt

图11.用readelf指令把hello.o文件输出为txt文件

4.3.2 ELF头

包含了系统信息,编码方式,ELF头大小,节的大小和数量等等一系列信息。本文ELF头内容如下:

图12.hello.o输出的ELF头部分

4.3.3 节头目表

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

图13.hello.o输出的节头部分

4.3.4 重定位节

各个段引用的外部符号等在链接时需要通过重定位对这些位置的地址进行修改。链接器会通过重定位节的重定位条目计算出正确的地址。

hello.o需重定位:.rodata中的模式串,puts,exit,printf,slepsecs,sleep,getchar等符号。

图14.hello.o输出的重定位节部分

4.3.5 符号表

.symtab存放在程序中定义和引用的函数和全局变量的信息。

图15.hello.o输出的符号表部分

4.4 Hello.o的结果解析

objdump -d -r hello.o> dump_hello.txt  

图16.hello.o的反汇编代码

在数的表示上,hello.s中的操作数表现为十进制,而hello.o反汇编代码中的操作数为十六进制。说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。

在控制转移上,hello.s使用.L2和.LC1等段名称进行跳转,而反汇编代码使用目标代码的虚拟地址跳转。不过目前留下了重定位条目,跳转地址为零。它们将在链接之后被填写正确的位置。

在函数调用上,hello.s直接call函数名称,而反汇编代码中call的是目标的虚拟地址。但和上一条的情况类似,只有在链接之后才能确定运行执行的地址,目前目的地址是全0,并留下了重定位条目。

4.5 本章小结

这一章介绍了汇编的含义和功能。在Ubuntu系统下对hello.s文件汇编生成了hello.o同时生成了ELF格式可执行文件hello.elf。对ELF格式文件进行了文件内容和各个节的简要说明,然后对hello.o进行反汇编生成hello.asm,与hello.s进行了对比,了解汇编语言和机器语言的转换。

(第4章1分)


5链接

5.1 链接的概念与作用

什么是链接

链接是将各种不同文件(主要是可重定位目标文件)的代码和数据综合在一起,通过符号解析和重定位等过程,最终组合成一个可以在程序中加载和运行的单一的可执行目标文件的过程。

链接的作用

链接令分离编译成为可能,方便了程序的修改和编译:无需重新编译整个工程,而是仅编译修改的文件。

链接还有利于构建共享库。源程序节省空间而未编入的常用函数文件(如printf.o)进行合并,生成可以正常工作的可执行文件。

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

图17.hello.o经过ld生成可执行文件

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

sudo readelf -l hello> elf.hello.txt(不加sudo权限不够)

 

图18.readelf输出hello对应的txt文件

5.3.1  ELF文件头

hello1.elf中的ELF头以16字节序列Magic开始,剩下的部分包含帮助链接器语法分析和解释目标文件的信息。hello1.elf中基本信息未发生改变(如Magic,类别等),而类型发生改变,程序头大小和节头数量增加,并且获得了入口地址。

图18.hello对应的txt文件中程序头部分

5.3.2 节头

描述了各个节的大小、偏移量和其他属性。链接器链接时,会将各个文件的相同段合并成一个大段,并且根据这个大段的大小以及偏移量重新设置各个符号的地址

图19.hello对应的txt文件中节头部分

5.4 hello的虚拟地址空间

程序包含PHDR,INTERP,LOAD,DYNAMIC,NOTE,GNU_STACK,GNU_RELRO几个部分,其中PHDR 保存程序头表。INTERP 指定在程序已经从可执行文件映射到内存之后,必须调用的解释器。LOAD 表示一个需要从二进制文件映射到虚拟地址空间的段。其中保存了常量数据、程序的目标代码等。DYNAMIC 保存了由动态链接器使用的信息。NOTE 保存辅助信息。GNU_STACK:权限标志,用于标志栈是否是可执行。GNU_RELRO:指定在重定位结束之后哪些内存区域是需要设置只读。

观察程序头的LOAD可加载的程序段的地址为0x400000。

图20.edb运行hello后得到的Data Dump

程序从地址0x400000开始到0x401000被载入,虚拟地址从0x4000000x400f0结束,根据5.3中的节头部表,可以通过edb找到各段的信息。

5.5 链接的重定位过程分析

图21.反汇编输出为txt文件

图22.反汇编输出为txt文件的结果

5.5.1 新增函数

链接加入了在hello.c中用到的库函数,如exit、printf、sleep、getchar等函数。

图23.函数变化部分

5.5.2 新增节

hello中增加了.init和.plt节,和一些节中定义的函数。

图24.新增节变化部分

5.5.3 函数调用地址

Hello实现了调用函数时的重定位,因此在调用函数时调用的地址已经是函数确切的虚拟地址。

图25.函数调用地址部分

5.5.4 控制流跳转地址

Hello实现了调用函数时的重定位,因此在跳转时调用的地址已经是函数确切的虚拟地址。

图26.函数控制流跳转地址部分

5.5.5链接的过程

链接就是链接器(ld)将各个目标文件(各种.o文件)组装在一起,文件中的各个函数段按照一定规则累积在一起。从.o提供的重定位条目将函数调用和控制流跳转的地址填写为最终的地址。

5.6 hello的执行流程

图27.edb运行hello查找_start位置

_start 0x4010f0

_main 0x401125

_printf 0x401152

_puts 0x401090

_atoi 0x4010c0

_exit 0x401193

_sleep 0x4011ad

_getchar 0x4011bc

5.7 Hello的动态链接分析

从ELF文件中可以找到got函数的位置

图28.ELF文件标识got位置

而在Data Dump中可以发现GOT表位置在调用调用了dl_init之后字节改变了

图29.got位置变化

利用代码段和数据段的相对位置不变的原则计算变量的正确地址。而对于库函数,需要plt、got的协作。

plt初始存的是一批代码,它们跳转到got所指示的位置,然后调用链接器。初始时got里面存的都是plt的第二条指令,随后链接器修改got,下一次再调用plt时,指向的就是正确的内存地址。plt就能跳转到正确的区域

5.8 本章小结

本章讲述了关于链接的概念和作用,展示如何利用链接命令行生成可执行文件,通过edb查看hello的虚拟地址空间,对比hello与hello.o的反汇编代码,深入研究了链接的过程中重定位的过程,最后对动态链接进行了分析。

(第5章1分)


6hello进程管理

6.1 进程的概念与作用

进程概念

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

进程作用

进程提供给应用程序的关键抽象:一个独立的逻辑控制流,如同程序独占处理器;一个私有的地址空间,如同程序独占内存系统。程作为一个执行中程序的实例,系统中每个程序都运行在某个进程的上下文中。可以说,如果没有进程,体系如此庞大的计算机不可能设计出来。

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

1.命令解析:Shell 读取输入 ./hello,解析命令名称与参数,构建 argv(参数数组,如 ["hello", NULL])和 envp(环境变量数组,如 PATH=/usr/bin)。

2.进程创建:通过 fork() 复制 Shell 进程,生成子进程。

写时复制(Copy-on-Write):子进程共享父进程的物理内存页,仅在写入时创建独立副本,避免冗余内存占用。

3.程序加载:子进程调用 execve(),清空原有地址空间,按 ELF 格式将 hello 的代码段(.text)、已初始化数据(.data)、未初始化数据(.bss)映射到虚拟内存,重建堆栈。

内存布局:.text 设为只读,.data 和 .bss 可读写,堆栈动态扩展。

4.执行入口:CPU 跳转至 hello 的入口点(通常为 _start),初始化运行时环境后调用 main(),程序正式在独立进程上下文中运行。

信号与资源:Shell 父进程通过 waitpid() 监控子进程状态,捕获 SIGCHLD 信号回收资源。

整个过程通过系统调用(fork、execve)和内存管理机制,实现用户指令到系统执行的透明转换,既隔离用户程序与 Shell,又高效复用进程管理逻辑。6.3 Hello的fork进程创建过程。

6.3 Hello的fork进程创建过程

当用户在 Shell 中输入 ./hello 时,Shell 通过 fork 系统调用创建子进程以运行 hello 程序,内核按以下步骤完成进程的创建与初始化:

1.复制父进程核心结构

内核调用 dup_task_struct(),为子进程创建与父进程完全相同的 task_struct(进程描述符)、内核栈和 thread_info。此时子进程的寄存器状态、内存映射、打开文件表等与父进程完全一致,形成逻辑上的副本。这一步确保了子进程继承父进程的所有执行上下文,但后续步骤将逐步差异化。

2.检查资源限制

内核验证当前用户(即运行 Shell 的用户)的进程数是否超出资源限制(如 RLIMIT_NPROC)。若超出限制,则终止 fork 并返回错误;若未超出,继续执行。这一机制防止恶意或异常程序耗尽系统资源。

3.差异化初始化进程描述符

子进程的 task_struct 中部分字段被重置或清零。清空运行时间统计(如 utime、stime),避免继承父进程的累计值。重置信号处理表(pending、signal),确保子进程独立处理信号。设置父子进程关系,子进程的 parent 字段指向父进程的 task_struct,为后续进程树管理奠定基础。

4.设置子进程状态为可中断睡眠

子进程状态被标记为 TASK_INTERRUPTIBLE,表示其已创建但尚未被调度执行。此时子进程处于“就绪但未运行”状态,等待内核调度器分配 CPU 时间片或外部事件(如 execve 调用)唤醒。

5.更新进程权限与执行标志

调用 copy_flags() 更新子进程的 task_struct 标志位:清除超级用户权限标记(PF_SUPERPRIV),确保子进程不继承父进程的特权;同时设置 PF_FORKNOEXEC 标志,标记该进程尚未调用 exec 函数加载新程序,为后续 execve 操作提供状态依据。

6.分配唯一进程标识符(PID)

通过 alloc_pid() 为子进程分配全局唯一的 PID。PID 是进程管理的核心标识,用于资源跟踪、信号发送和父子进程通信。分配过程中需确保不与现有进程冲突,通常通过位图或哈希表高效实现。

7.共享或复制资源

根据 fork 的克隆标志参数,copy_process() 决定是否共享或复制父进程资源:共享打开的文件表(files_struct),仅递增文件描述符引用计数;通过写时复制(COW)共享父进程的地址空间,物理内存页初始为共享状态,子进程的写操作触发独立页分配;继承信号处理表及命名空间上下文,保持父子进程行为一致性。

8.完成创建并返回子进程指针

内核初始化子进程的调度器相关字段(如 sched_class、vruntime),将其加入就绪队列等待调度。copy_process() 最终返回子进程的 task_struct 指针。父进程通过 fork() 获得子进程 PID,子进程返回 0,二者开始独立执行:

父进程:继续运行,通常调用 waitpid() 等待子进程结束。

子进程:调用 execve() 加载 hello 程序,清空原有地址空间,将 .text(代码段)、.data(已初始化数据)、.bss(未初始化数据)映射到内存,跳转到 _start 入口点,最终执行 main() 函数。

6.4 Hello的execve过程

Execve的参数包括需要执行的程序(通常是argv[0])、参数argv、环境变量envp。 1. 删除已存在的用户区域(自父进程独立)。

2. 映射私有区:为Hello的代码、数据、.bss和栈区域创建新的区域结构,所有这些区域都是私有的、写时才复制的。

3. 映射共享区:比如Hello程序与标准C库libc.so链接,这些对象都是动态链接到Hello的,然后再用户虚拟地址空间中的共享区域内。

4. 设置PC:exceve做的最后一件事就是设置当前进程的上下文中的程序计数器,使之指向代码区域的入口点。

5. execve在调用成功的情况下不会返回,只有当出现错误时,例如找不到需要执行的程序时,execve才会返回到调用程序。6.5 Hello的进程执行

6.5 Hello的进程执行

在 Shell 中执行 ./hello 命令时,操作系统通过 fork 和 execve 系统调用创建新进程并加载 hello 程序。这一过程涉及进程上下文的复制、资源隔离、调度策略的执行以及用户态与核心态的动态切换,其核心目标是确保程序在独立且安全的环境中运行,同时高效利用 CPU 资源。

1.进程的创建与初始化

当用户输入命令后,Shell 首先解析命令并构建参数数组 argv 和环境变量数组 envp。随后通过 fork 系统调用创建子进程。fork 的核心操作是复制父进程的进程描述符(task_struct)、内存映射和内核栈,但通过写时复制(Copy-on-Write)技术避免立即复制物理内存,仅在子进程尝试修改内存时触发实际复制。子进程的 PID 由内核分配,确保全局唯一性,其初始状态被设为 TASK_INTERRUPTIBLE,表示已就绪但尚未被调度执行。父进程则继续运行,通常调用 waitpid 等待子进程结束。

子进程通过 execve 系统调用加载 hello 程序。execve 会清空子进程原有的地址空间,将 hello 的代码段(.text)、初始化数据段(.data)、未初始化数据段(.bss)映射到虚拟内存,并重建用户栈以传递 argv 和 envp。程序入口点设为 _start,由动态链接器(ld.so)负责加载依赖的共享库(如 libc.so),初始化全局变量,最终调用 main 函数。此时,hello 进程完全脱离父进程环境,进入独立的执行流程。

2.进程上下文与调度机制

进程的上下文是操作系统管理其执行状态的核心,包括寄存器值(如程序计数器、栈指针)、内存映射、打开文件表及内核栈信息。当 hello 进程开始执行时,其上下文被载入 CPU,程序计数器指向 main 函数的首条指令。进程的时间片由调度器动态分配,Linux 默认采用完全公平调度器(CFS),通过虚拟运行时间(vruntime)计算进程的优先级。时间片的长短取决于进程的 nice 值,普通进程默认时间片为几毫秒至几十毫秒。

调度器在以下场景触发上下文切换:

(1)时间片耗尽:若 hello 进程持续占用 CPU 至时间片结束,调度器将其移出运行队列,选择其他就绪进程执行。

(2)主动让出 CPU:当 hello 调用阻塞式系统调用(如 sleep、getchar)或等待 I/O 操作时,进程主动进入睡眠状态(TASK_INTERRUPTIBLE),释放 CPU 资源。

(3)中断触发:时钟中断(如每毫秒一次)强制暂停当前进程,检查是否需要重新调度。

例如,执行 sleep(atoi(argv[4])) 时,hello 调用 sys_nanosleep 系统调用,进程从用户态切换到核心态,内核将其标记为睡眠状态并启动定时器。调度器随后选择其他进程运行。当睡眠时间到达或收到信号(如 SIGINT)时,内核将 hello 重新加入就绪队列,等待下一次调度。

3.用户态与核心态切换

核心态可以说是拥有最高的访问权限,处理器以一个寄存器当做模式位来描述当前进程的特权。进程只有故障、中断或陷入系统调用时才会得到内核访问权限,其他情况下始终处于用户权限之中,保证了系统的安全性。进程在执行过程中频繁切换用户态和核心态,主要通过系统调用、中断和异常实现。

(1)系统调用:如 printf 调用 write 向终端输出数据,sleep 调用 nanosleep 挂起进程。此时,CPU 执行 syscall 指令,保存用户态寄存器至内核栈,切换至核心态执行内核函数,完成后恢复用户态上下文。

(2)硬件中断:时钟中断定期触发调度器更新进程时间片;键盘中断(如 Ctrl+C 发送 SIGINT)强制终止进程。中断处理程序在核心态运行,可能直接修改进程状态或调度队列。

(3)异常处理:若 hello 尝试访问非法内存地址(如空指针解引用),触发缺页异常或段错误(SIGSEGV),内核接管处理,可能终止进程或修复映射。

4.进程终止与资源回收

当 hello 的 main 函数返回或调用 exit 时,进程进入终止阶段。内核首先释放其占用的内存页、关闭打开的文件描述符,并向父进程(Shell)发送 SIGCHLD 信号。父进程通过 waitpid 系统调用读取子进程的退出状态码(如 return 0),内核随后回收进程描述符和 PID,彻底销毁进程。若父进程未及时回收,子进程会短暂处于僵尸状态(TASK_ZOMBIE),保留退出状态供父进程查询。

6.6 hello的异常与信号处理

6.6.1正常运行状态

程序连续输出十次结果

图30.正常运行状态

6.6.2 异常

如表中所示

中断

来自I/O设备的信号

异步

返回到下一条指令

陷阱

有意的异常

同步

返回到下一条指令

故障

潜在可恢复的错误

同步

可能返回当前指令

终止

不可恢复的错误

同步

不会返回

6.6.3 发送信号

1)Ctrl + C

按下Ctrl + C,Shell进程收到SIGINT信号,Shell结束并回收hello进程。

图31.Ctrl+c操作

  1. Ctrl + Z

按下Ctrl + Z,Shell进程收到SIGSTP信号,Shell显示屏幕提示信息并挂起hello进程。

图32.Ctrl+z操作

  1. 中途乱按

只是将屏幕的输入缓存到缓冲区。乱码被认为是命令。

图33.乱按操作

  1. Kill指令

输入kill命令,则可以杀死指定(进程组的)进程

6.7本章小结

本章主要讲述了计算机系统进程的概念和作用,以及Shell的概念和处理流程进行了分析,结合hello程序,对进程创建、启动和执行进行了详细介绍,最后对hello程序可能出现的异常情况进行了分析,每种信号都有不同的处理机制,对不同的shell命令,hello也有不同的响应结果。

(第6章2分)


7hello的存储管理

7.1 hello的存储器地址空间

1.逻辑地址(Logical Address)是指由程序产生的与段相关的偏移地址部分。在这里指的是hello.o中的内容。

2.线性地址(Linear Address)是逻辑地址到物理地址变换之间的中间层。程序hello的代码会产生段中的偏移地址,加上相应段的基地址就生成了一个线性地址。

7.3 虚拟地址是说CPU启动保护模式后,程序hello运行在虚拟地址空间中。注意,并不是所有的“程序”都是运行在虚拟地址中。CPU在启动的时候是运行在实模式的,Bootloader以及内核在初始化页表之前并不使用虚拟地址,而是直接使用物理地址的。

7.4 物理地址是指存储器以字节为单位组织,每个字节单元都有一个唯一编号。物理地址表示内存中的实际位置,因此也称为绝对地址。程序 hello 在运行时被加载到内存中后,所使用的就是这些实际的物理地址。

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

Intel处理器的逻辑地址到线性地址的变换通过段式管理实现。逻辑地址由段选择符和偏移量两部分组成,段选择符为16位,偏移量为32位(32位模式下)。段选择符的高13位是段描述符索引,用于在全局描述符表(GDT)或局部描述符表(LDT)中定位段描述符;第2位是TI标志(0选择GDT,1选择LDT);最低两位为请求特权级(RPL),用于权限检查。

段描述符是内存中的一个8字节结构,包含段的基地址(32位)、段界限(20位)及访问控制信息(如类型、特权级、是否在内存等)。基地址字段分布在描述符的不同位置,需拼接后得到完整的32位基地址。

转换时,处理器根据段选择符的TI位选择GDT或LDT,通过索引定位对应的段描述符,从中提取段基址,与逻辑地址中的偏移量相加,得到线性地址。例如,若段基址为0x1000,偏移量为0x200,则线性地址为0x1200。

段式管理的核心目的是实现内存保护与隔离:段界限限制偏移量范围,防止越界访问;特权级和类型字段控制代码与数据的访问权限。在保护模式下,段机制强制所有内存访问必须通过段描述符验证,而实模式下段基址直接由段寄存器值左移4位得到,无保护功能。此机制为多任务系统提供了基础隔离能力,确保进程无法随意访问其他段的内存空间。

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

在页式管理中,线性地址到物理地址的转换通过多级页表实现。以32位系统为例,线性地址(如0x00401000)被划分为三部分:页目录索引(高10位)、页表索引(中间10位)和页内偏移量(低12位)。转换过程始于CR3寄存器,其中存储了当前进程的页目录基址。

首先,根据页目录索引从页目录中找到对应的页目录项(PDE)。PDE中保存了下一级页表的物理基址。例如,若页目录索引为0x0,则从CR3指向的页目录首地址取出第一个PDE。接着,根据页表索引在该页表中定位页表项(PTE),PTE中记录了目标物理页框的基地址。例如,页表索引为0x4时,从页表基址偏移0x4*4=0x10字节处读取PTE。

最终,将PTE中的物理页框基址与页内偏移量相加,得到物理地址。例如,若PTE中页框基址为0x2000,页内偏移为0x1000,则物理地址为

0x2000 + 0x1000 = 0x3000。

页式管理的核心在于内存隔离与共享:不同进程的线性地址可映射到同一物理页(共享库),或同一线性地址映射到不同物理页(进程隔离)。此外,页表项中的权限位(如读/写、用户/内核模式)确保非法访问触发缺页异常。现代系统通过TLB(快表)缓存常用映射,加速地址转换。整个过程由硬件(MMU)自动完成,对程序透明,操作系统仅需维护页表结构与处理异常。

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

在TLB(快表)与四级页表支持下,虚拟地址(VA)到物理地址(PA)的转换通过硬件与操作系统的协同实现。对于64位系统,虚拟地址被划分为四级页表索引(如PGD、PUD、PMD、PTE)和页内偏移量。转换时,内存管理单元(MMU)首先查询TLB——一个高速缓存,存储近期使用的虚拟页到物理页的映射。若TLB命中,直接获取物理页框基址,与偏移量拼接得到物理地址,无需访问内存中的页表,极大加速转换过程。

若TLB未命中,MMU需逐级遍历四级页表:从CR3寄存器指向的页全局目录(PGD)开始,依次用各级索引定位下一级页表的物理基址。例如,PGD索引定位页上层目录(PUD),PUD索引定位页中间目录(PMD),PMD索引定位页表项(PTE),最终PTE中保存目标物理页框基址。将基址与12位页内偏移相加,得到完整物理地址。此过程涉及四次内存访问(每级页表一次),效率较低,因此转换完成后,MMU会将新映射存入TLB,供后续快速查询。

四级页表支持超大地址空间管理,同时通过权限位(如读/写、用户/内核模式)实现内存保护。TLB则利用局部性原理,缓存热点映射,减少访存开销。操作系统负责维护页表一致性:进程切换时,需更新CR3指向新进程的PGD,并可能通过TLB刷新或地址空间标识符(ASID)隔离不同进程的映射。缺页异常由MMU触发,操作系统通过分配物理页、加载数据或更新页表响应,确保转换透明性与内存安全。TLB与四级页表的结合,在性能与灵活性间取得平衡,成为现代系统内存管理的核心机制。7.5 三级Cache支持下的物理内存访问

7.6 hello进程fork时的内存映射

当fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID,同时为这个新进程创建虚拟内存,创建当前进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记位只读,并将两个进程中的每个区域结构都标记为私有的写时复制。

当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面。

7.7 hello进程execve时的内存映射

当进程调用execve执行新程序(如hello)时,操作系统首先清空当前进程的原有内存映射,包括代码段、数据段、堆、栈等,但保留文件描述符、信号处理等部分上下文。接着,内核解析目标可执行文件(如ELF格式),读取其程序头表,按需将代码段、数据段等映射到进程的虚拟地址空间。代码段通常以只读且可执行的方式映射,数据段和BSS段(未初始化数据)则以读写权限加载,BSS段的内容初始化为零。

动态链接器(如ld-linux.so)随后被加载到内存,负责解析并映射依赖的共享库(如libc.so),这些库通过页表映射到进程地址空间,可能采用延迟绑定(PLT/GOT)机制,仅在首次调用时加载实际函数地址。堆空间通过调整程序中断点(brk)初始化,栈则由内核自动分配,用于存放命令行参数、环境变量及函数调用帧。

内存映射过程中,操作系统通过页表设置权限位(读/写/执行),确保代码不可篡改、数据不可执行等安全策略。若系统支持按需分页,物理内存的分配会延迟到首次访问时触发缺页异常,由内核填充实际页框。最终,进程从新程序的入口点(如_start)开始执行,完成从旧映像到hello的彻底替换。这一过程通过内存映射的精细管理,实现了进程资源的高效重构与隔离。7.8 缺页故障与缺页中断处理

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

缺页故障与缺页中断处理是现代操作系统中内存管理的核心机制之一,其本质在于通过动态管理物理内存资源,实现虚拟内存系统的高效运行。当程序访问虚拟地址时,若对应的物理页面尚未加载到内存中或访问权限不符,处理器会触发缺页中断,操作系统随即介入处理这一异常,确保程序能够继续执行。这一过程涉及硬件与软件的紧密协作,涵盖中断响应、页面调度、权限验证及内存分配等多个环节。

缺页故障的触发条件主要分为几种情况:一是程序访问的虚拟地址对应的页表项标记为“不存在”(即该页面尚未被加载到物理内存中);二是页面虽存在于内存中,但当前访问的权限与页表项中定义的权限不符(例如试图写入只读页面);三是多级页表遍历时发现中间页表项无效。例如,当程序通过malloc动态分配内存后首次访问该区域时,操作系统可能尚未为其分配物理页框,此时访问会触发缺页中断。类似地,若进程尝试执行代码段以外的内存区域(如数据段被错误标记为不可执行),也会因权限错误引发缺页故障。

当缺页中断发生时,处理器的控制权会立即转移到操作系统的中断处理程序。操作系统首先保存当前进程的上下文(如寄存器状态、程序计数器等),随后通过分析触发中断的虚拟地址及错误类型,确定具体故障原因。例如,通过读取CR2寄存器(在x86架构中存储导致缺页的虚拟地址),操作系统可以定位到引发问题的内存地址。接下来,系统需验证该地址是否属于进程合法的地址空间范围。若地址非法(如越界访问),操作系统会向进程发送信号(如SIGSEGV)终止其运行;若地址合法,则进入页面调度流程。

对于“页面不存在”的情况,操作系统需要从磁盘(如交换分区或文件系统)中加载所需页面到物理内存。这一过程可能涉及复杂的I/O操作。首先,操作系统需确定该页面在磁盘中的位置。对于文件映射的页面(如共享库或内存映射文件),系统通过虚拟地址对应的文件偏移定位数据;对于匿名页面(如堆或栈空间),则需从交换分区中读取。随后,系统需分配一个空闲的物理页框。若物理内存已满,则需根据页面置换算法(如LRU、FIFO或时钟算法)选择一个“牺牲页面”换出到磁盘。页面置换算法的选择直接影响系统性能——LRU(最近最少使用)算法通过历史访问记录淘汰最久未使用的页面,但实现复杂度较高;而时钟算法通过循环扫描页表项,平衡了效率与开销,成为许多系统的折中选择。

在页面加载完成后,操作系统需更新页表项,将虚拟地址映射到新的物理页框,并设置正确的权限标志(如可读、可写、可执行)。同时,若系统支持TLB(快表),还需刷新或更新相关缓存条目,确保后续访问能直接通过TLB加速转换。例如,在x86架构中,可通过invlpg指令显式失效特定虚拟地址的TLB条目。完成这些操作后,操作系统恢复进程的上下文,重新执行引发缺页的指令,此时物理页面已就绪,指令得以正常执行。

权限错误引发的缺页故障处理则更为严格。例如,若进程试图修改只读页面(如代码段),操作系统需判断是否为合法操作。某些情况下,这可能是程序错误(如缓冲区溢出);另一些情况下,可能是写时复制(Copy-on-Write)机制的触发点。例如,在fork系统调用创建子进程时,父子进程共享物理页面,并标记为只读。当任一进程尝试写入共享页面时,会触发缺页中断,操作系统此时为写入进程分配新物理页框,复制原页面内容,并更新页表以解除共享关系。这种机制显著减少了进程复制的内存开销,体现了缺页处理的灵活性。

缺页中断处理的性能优化是系统设计的关键。频繁的缺页会导致大量磁盘I/O和上下文切换,严重降低系统吞吐量。因此,操作系统通过多种策略减少缺页率:其一,预取(Prefetching)机制根据程序访问模式提前加载可能需要的页面;其二,页面缓存(Page Cache)将频繁访问的磁盘文件页面保留在内存中,减少重复加载;其三,透明大页(Transparent Huge Pages)通过合并小页为大页,减少页表项数量及TLB失效频率。此外,现代处理器和操作系统支持延迟加载(Lazy Allocation),仅在页面首次被访问时分配物理内存,避免了不必要的初始化开销。

缺页处理还与内存安全密切相关。例如,操作系统通过缺页中断检测堆栈溢出(当访问超出栈空间的地址时),或防止进程越界访问其他进程的内存区域。在支持虚拟化的环境中,缺页处理机制还被扩展用于虚拟机监控程序(Hypervisor)管理客户机的内存访问,确保隔离性与资源分配公平性。

7.9动态存储分配管理

动态储存分配管理使用动态内存分配器(如malloc)来进行。动态内存分配器维护着一个进程的虚拟内存区域,称为堆。分配器将堆视为一组不同大小的块的集合。

每个块就是一个连续的虚拟内存页,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配的状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。动态内存分配主要有两种基本方法与策略:

7.9.1 带边界标签的隐式空闲链表分配器管理

带边界标记的隐式空闲链表的每个块是由一个字的头部、有效载荷、(可能的)额外填充以及一个字的尾部组成。

隐式空闲链表:空闲块通过头部的大小字段隐含地连接着。分配器遍历堆中所有的块,间接地遍历整个空闲块的集合。

当一个应用请求一个k字节的块时,分配器搜索空闲链表,查找一个足够大的可以放置所请求块的空闲块。分配器有三种放置策略:首次适配、下一次适配和最佳适配。分配器在面对释放一个已分配块时,可以合并相邻的空闲块,其中一种简单的方式,是利用隐式空闲链表的边界标记来进行合并。

7.9.2 显式空间链表管理

显式空闲链表是将堆的空闲块组织成一个双向链表,在每个空闲块中,都包含一个前驱与一个后继指针。进行内存管理。在显式空闲链表中。可以采用后进先出的顺序维护链表,将最新释放的块放置在链表的开始处,也可以采用按照地址顺序来维护链表,其中链表中每个块的地址都小于它的后继地址,在这种情况下,释放一个块需要线性时间的搜索来定位合适的前驱。

7.10本章小结

本章系统阐述了计算机系统中内存管理的核心机制。从hello进程的存储器地址空间布局(代码段、数据段、堆栈等)出发,深入解析了Intel处理器的地址转换流程:段式管理通过段选择符与描述符将逻辑地址转为线性地址,页式管理再通过四级页表与TLB将线性地址映射为物理地址,实现内存隔离与高效访问。进程控制方面,fork通过写时复制优化子进程创建,execve彻底重构内存映射以加载新程序,动态存储分配管理则依托堆空间的malloc/free机制。缺页中断处理与TLB协同保障按需分页的灵活性,而权限控制与异常机制共同维护内存安全。这些层次化的设计(硬件MMU与操作系统协作)不仅支撑了进程间隔离与资源共享,更在性能与安全性间达成精妙平衡,构成现代计算机内存体系的基石。

(第7章 2分)


8hello的IO管理

8.1 Linux的IO设备管理方法

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

设备的模型化:文件

设备管理:unix io接口

8.2 简述Unix IO接口及其函数

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

8.3 printf的实现分析

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

[转]printf 函数实现的深入剖析 - Pianistx - 博客园

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

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

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

8.4 getchar的实现分析

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

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

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

8.5本章小结

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

(第8章 选做 0分)

结论

程序 hello 的执行全过程

  1. 预处理

使用预处理器(如 cpp)对 hello.c 进行展开,将源文件中引用的头文件和宏定义嵌入代码中,生成预处理结果文件 hello.i。

  1. 编译

编译器将 hello.i 转换为汇编语言代码,生成汇编文件 hello.s,完成从高级语言到低级指令的转换。

  1. 汇编

汇编器将汇编代码翻译为机器码,生成可重定位目标文件 hello.o,这是一个中间产物,尚不能独立执行。

  1. 链接

链接器将 hello.o 与标准库和其他依赖目标文件链接,形成可执行文件 hello。此过程还涉及重定位和符号解析。

  1. 运行命令

用户在 shell 中输入命令 ./hello 2023111804 zhanghongshuo,shell 开始解析并准备执行该程序。

  1. 创建进程

shell 识别该命令不是内建指令,调用 fork() 创建一个子进程以执行该程序。

  1. 加载程序

子进程通过 execve() 系统调用,将当前地址空间替换为 hello 程序的映像,映射代码段、数据段、共享库等到虚拟地址空间,并跳转到主函数入口开始执行。

  1. 执行指令

操作系统调度器为子进程分配 CPU 时间片,在时间片内,CPU 执行 hello 程序的指令流。

  1. 访问内存

程序运行过程中,使用的虚拟地址通过 MMU 和页表映射到物理地址,完成对主存的访问。

  1. 信号管理

若用户按下 Ctrl+c,内核向前台进程发送 SIGINT 信号终止它;按下 Ctrl+z,则发送 SIGTSTP 信号使其挂起kill会直接杀死程序

  1. 终止进程

程序执行完毕后正常退出,子进程终止,内核回收其资源并将退出状态传递给父进程。

感悟:

说点实在的,好难呀,我可能以后都不会想学计算机了,不过还是对这些知识点的理解深化了很多,这个实验比课本更生动的让我学习知识点。人类能做出来这么复杂抽象的机器真是厉害,不知道我以后在某个方面能不能追赶上伟人们的脚步。

(结论0分,缺失-1分)


附件

hello.c 源文件

hello.i          预处理后得到的文本文件

hello.s          编译后得到的汇编语言文件

hello.o          汇编后得到的可重定位目标文件

elf-hello.o.txt     hello.o的ELF格式

dump_hello.o.txt hello.o的反汇编代码

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

elf-hello.txt      hello的ELF格式

dump_hello.txt   hello的反汇编代码

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


参考文献

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

  1. Randal E.Bryant David R.O'Hallaron.深入理解计算机系统(第三版).机械工业出版社,2016.
  2. https://www.cnblogs.com/buddy916/p/10291845.html
  3. linux2.6 内存管理——逻辑地址转换为线性地址(逻辑地址、线性地址、物理地址、虚拟地址) - 刁海威 - 博客园
  4. Ubuntu系统预处理、编译、汇编、链接指令_ubuntu 对话语料库怎么预处理-CSDN博客
  5. https://zhuanlan.zhihu.com/p/676243873
  6. https://blog.csdn.net/weixin_57760758/article/details/129770877
  7. execve 继承了什么?映射、信号、文件描述符、优先级、ptrace_execveat signal-CSDN博客
  8. https://zhuanlan.zhihu.com/p/43238579
  9. 操作系统的内存管理——页式、段式管理、段页式管理-CSDN博客
  10. 逻辑地址、物理地址、虚拟地址_虚拟地址 逻辑地址-CSDN博客
  11. 预处理详解(完结篇)-CSDN博客
  12. https://blog.csdn.net/weixin_50502862/article/details/129381511
  13. 地址空间与内存管理:动态重定位、私有空间与存储策略-CSDN博客
  14. linux2.6 内存管理——逻辑地址转换为线性地址(逻辑地址、线性地址、物理地址、虚拟地址) - 刁海威 - 博客园
  15. Linux下 可视化 反汇编工具 EDB 基本操作知识_linuxedb教程-CSDN博客
  16. https://blog.csdn.net/taotongning/article/details/106382171

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

转载自CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/2301_80391083/article/details/148217525

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--