不到断气不罢休 发表于 4 天前

哈工大盘算机体系大作业程序人生Hello’s P2P


摘  要
本报告以 “Hello's P2P:From Program to Process” 为主题,深入分析了hello.c程序在今世盘算机体系中的完备生命周期,涵盖预处置惩罚、编译、汇编、链接、进程管理、存储管理及 IO 管理等核心环节,展现了从源代码到进程运行的底层机制。

在程序转换阶段,预处置惩罚通过睁开宏定义、包含头文件等操作生成纯净 C 源码hello.i;编译阶段将其转换为汇编代码hello.s,具体解析了函数调用、条件判定等高级语法与汇编指令的映射关系;汇编阶段生成可重定位目标文件hello.o,分析了 ELF 格式中段布局与重定位信息;链接阶段通过符号解析、所在分配和重定位,将目标文件与库文件合并为可实验文件hello,并探究了动态链接机制。

进程管理方面,阐述了 Shell(Bash)通过fork创建子进程、execve加载新程序的流程,分析了进程实验中的用户态 / 内核态切换、时间片轮转调度、信号处置惩罚(如Ctrl+C触发SIGINT)及进程退出时的资源采取机制。

存储管理部分,联合 Intel x86_64 架构,解析了逻辑所在到线性所在的段式管理、线性所在到物理所在的页式管理过程,探究了 TLB 与四级页表的所在翻译机制、三级 Cache 的物理内存访问流程,以及fork时的写时复制(COW)技能和execve的内存重映射机制,还分析了缺页故障的处置惩罚流程。

IO 管理聚焦于 Linux 体系的 IO 装备管理方法,解析了printf和getchar的实现原理,展现了用户空间与内核空间通过体系调用交互的机制。

通过对hello程序全生命周期的体系性分析,本报告全面展示了盘算机体系各子体系(如处置惩罚器、内存、IO 装备)的协同工作流程,为理解程序实验机制、体系性能优化及故障调试提供了理论与实践依据。

关键词:程序生命周期;编译原理;进程管理;存储管理;体系调用。


目  录
第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's P2P:From Program to Process”为主题,深入分析一个简单的 hello.c 程序在今世盘算机体系中的编译、链接、实验与运行机制。该程序由用户提供下令行参数,在终端每隔若干秒输出指定格式的问候信息。只管程序逻辑简单,其背后体现了从源代码到终极进程运行的全流程,涉及预处置惩罚、编译、汇编、链接、进程创建、内存映射、体系调用、中断处置惩罚等核心概念。
在整个流程中,Hello程序由 Bash Shell 启动,依次经过预处置惩罚器、编译器、汇编器、链接器,再由操作体系加载到内存形成一个进程。进程通过体系调用利用 CPU、内存、IO 装备等资源,体现了“从程序到进程”的完备生命周期,是盘算机体系各子体系协作的缩影。
1.2 环境与工具

本次实验与分析主要在如下软硬件环境中举行:

操作体系:Ubuntu 22.04 LTS (64位)

编译器:GCC 11.4.0

调试器:GDB、EDB(可选)

反汇编工具:objdump, readelf

文本编辑器:Vim / VSCode

截图工具:gnome-screenshot 或 Flameshot

硬件平台:x86_64 架构,Intel Core i7-10510U,16GB RAM

1.3 中间效果

在从源文件到实验过程中,生成了如下关键中间文件:

文件名 说明
hello.i   预处置惩罚后的C源文件
hello.s  编译生成的汇编代码
hello.o  汇编生成的可重定位目标文件
hello     链接后的可实验文件
hello运行截图.png  程序运行终端截图
这些中间文件分别代表源代码转化过程中的差别阶段,对于分析其底层机制具有重要意义。
1.4 本章小结

本章介绍了 Hello 程序的配景与意义,列出了利用的工具环境,并归纳了整个过程中产生的重要中间产物。后续章节将分别深入分析每一个阶段的技能细节与体系机制,以展现一个C程序从源代码转变为可实验进程的全过程。




第2章 预处置惩罚

2.1 预处置惩罚的概念与作用

C语言的预处置惩罚是编译的第一阶段,其主要作用包括:
睁开宏定义(如 #define)
处置惩罚头文件的包含(#include)
条件编译(如 #ifdef)
删除注释等
其效果是生成 .i 文件,即纯净、完备的C语言源代码,为后续编译做好准备。预处置惩罚并不会生成任何机器码,仅在源文件层面完成文本替换和组织。
2.2在Ubuntu下预处置惩罚的下令

在 Ubuntu 环境下利用 GCC 的 -E 参数举行预处置惩罚。对于 hello.c 文件,预处置惩罚下令如下:
gcc -m64 -no-pie -fno-stack-protector -fno-PIC -E hello.c -o hello.i
https://i-blog.csdnimg.cn/direct/4f31549591f3475ca5e7823bd0e30e04.png
图 1预处置惩罚下令截图
2.3 Hello的预处置惩罚效果解析

打开 hello.i 文件可以看到:#include <stdio.h> 被睁开为 stdio.h 的全部内容;
程序中的注释被完全移除;全部宏(如 NULL)已被替换;没有任何函数体、流程或行为发生改变,仅是代码“扩展”而非“转换”。
利用如下下令查看部分预处置惩罚输出并截图:
head -n 50 hello.i
https://i-blog.csdnimg.cn/direct/e40a09dd18404c7592bc848c2eb07f4b.png
图 2预处置惩罚效果截图
2.4 本章小结

本章介绍了预处置惩罚阶段的主要任务与实现方式。通过对 hello.c 举行预处置惩罚,我们得到了 .i 文件,并验证了宏睁开、注释移除与头文件替换等特性。预处置惩罚是编译阶段的基础,为后续的语法分析与代码生成提供了准备。

第3章 编译

3.1 编译的概念与作用

在 C 语言程序的构建过程中,编译阶段的任务是将预处置惩罚后的 .i 文件翻译为汇编代码 .s 文件。该过程由编译器(如 gcc 的内部前端)完成,具体操作包括词法分析、语法分析、语义分析、符号表建立、优化与汇编代码生成等。
编译器将高级语言翻译为机器相关的中间表示(IR)或直接生成汇编语言,这是程序从“源代码”向“可实验体”转换的重要桥梁。
3.2 在Ubuntu下编译的下令

在终端中输入以下下令将 .i 文件编译为汇编代码。
gcc -m64 -no-pie -fno-stack-protector -fno-PIC -S hello.i -o hello.s
https://i-blog.csdnimg.cn/direct/47ad95c756cd4e66b8579feac350fc10.png
图 3编译下令截图
3.3 Hello的编译效果解析

打开 hello.s 文件可以看到,C 语言的语句被逐行转换为汇编指令。以下是一些典型的数据类型与操作在 hello.s 中的体现与分析
3.3.1 函数调用与参数通报

源代码片段:printf("Hello %s %s %s\n", argv, argv, argv);
在 x86-64 架构中,函数参数通常通过以下寄存器通报:


[*]第1个参数 → rdi
[*]第2个参数 → rsi
[*]第3个参数 → rdx
[*]第4个参数 → rcx
[*]第5个参数 → r8
[*]第6个参数 → r9
汇编代码:
movq    -32(%rbp), %rax      ; argv 存储在栈帧偏移 -32
addq     $24, %rax
movq    (%rax), %rcx         ; argv -> rcx

movq    -32(%rbp), %rax
addq     $16, %rax
movq    (%rax), %rdx         ; argv -> rdx

movq    -32(%rbp), %rax
addq     $8, %rax
movq    (%rax), %rax
movq    %rax, %rsi           ; argv -> rsi

movl     $.LC1, %edi          ; 格式字符串 -> edi
movl     $0, %eax             ; 变参规则要求 eax=0
call printf
代码分析:

[*]argv 是一个 char* 指针数组。由于它自己作为参数传入 main,被生存为 -32(%rbp)。
[*]每次访问 argv 现实就是取 argv + i * 8,因此对应加上 8, 16, 24。
[*]加载字符串指针后依次放入 rsi、rdx、rcx,而格式字符串常量 .LC1 放入 edi。
[*]eax 被清零是为遵守浮点参数计数规则,即使当前没有通报浮点类型参数。
[*]call printf 是真正的函数调用点,它将跳转到 printf 所在位置。

本段完备展示了从 C 源代码到汇编中函数参数的拆解、所在盘算、寄存器装载与调用跳转过程,反映了今世编译器遵守的标准调用约定。
3.3.2 条件语句和跳转实现

源代码:if (argc != 5) {
    printf("用法: Hello 2023111785 姚鹏举 18645267050 0!\n");
    exit(1);
}

汇编代码:
cmpl     $5, -20(%rbp)
je   .L2
movl     $.LC0, %edi
call    puts
movl     $1, %edi
call      exit
汇编分析:

[*]argc 参数被生存为 -20(%rbp),为 32 位整数;
[*]cmpl $5, -20(%rbp):将常数 5 与 argc 举行比力;
[*]je .L2:如果相等,跳不对误处置惩罚部分,进入 .L2(主程序逻辑);
[*]若不即是 5:   
[*]将错误提示字符串 .LC0 所在装入 edi;
[*]调用 puts 打印错误信息;
[*]调用 exit(1) 退出程序。


这一段体现了 C 中条件判定的底层实现方式:比力值和目标;通过条件跳转控制实验路径;差别条件操作(如 !=, <, >=)会映射为差别跳转指令,如 jne, jl, jge 等。
3.3.3 变量定义与循环布局

源代码如下:
for(i=0;i<10;i++){
        printf("Hello %s %s %s\n",argv,argv,argv);
        sleep(atoi(argv));
    }

变量定义与赋初值
汇编代码:movl      $0, -4(%rbp)
分析:i 是一个局部变量,存放在栈帧中偏移 -4(%rbp) 处。利用 movl $0 对其举行初始化,即对应 int i = 0这一步。

循环控制布局
汇编代码:
jmp       .L3              ; 跳过循环体,先判定条件
.L4:                 ; 循环体开始
  ...                ; 循环体代码:printf + sleep
addl      $1, -4(%rbp)   ; i++
.L3:
cmpl     $9, -4(%rbp)   ; i <= 9 ?
jle  .L4             ; 若小于即是,跳回循环体
分析:
标签 .L3 是循环判定部分,.L4 是循环体开始;
条件判定利用 cmp + jle;


[*]cmpl $9, -4(%rbp) → 比力 i 和 9
[*]jle .L4 → 若 i ≤ 9,跳到循环体
自增语句 i++ 被翻译为 addl $1, -4(%rbp)
3.3.4 嵌套函数调用sleep(atoi(argv))

这是一个经典的函数嵌套调用。它的实验顺序是:

[*]先调用 atoi(argv) 把字符串转为整数;
[*]再把这个整数传入 sleep 函数,控制延时秒数。
对应汇编代码:
movq    -32(%rbp), %rax
addq     $32, %rax
movq    (%rax), %rax        ; 取 argv
movq    %rax, %rdi          ; 参数传入 rdi
call atoi                ; 调用 atoi(argv)
movl     %eax, %edi          ; 将 atoi 返回值传入 edi
call sleep               ; 调用 sleep()
分析说明:

[*]-32(%rbp) 是 argv;
[*]argv = *(argv + 4) = *(argv + 32) → 因为每个指针是8字节,偏移 4 * 8 = 32;
[*]将 argv 放入 rdi,满足 atoi(char*) 的调用规则;
[*]atoi 返回 int,效果生存在 eax;
[*]将 eax 放入 edi,传入 sleep(int);
[*]实验 call sleep。
这个过程清晰地体现了嵌套函数调用的分步处置惩罚机制:


[*]编译器不会试图把两个函数合并;
[*]它先调用内层函数,将返回值存在通用寄存器(eax);
[*]再将效果作为参数传入外层函数
这段嵌套调用展示了编译器在处置惩罚表达式求值、函数返回值利用、函数嵌套布局睁开方面的细致逻辑。嵌套函数调用被线性化为先后两次调用,每次严格利用约定的寄存器通报参数与返回值,是理解函数调用机制的关键实例。
3.3.5栈帧建立与函数返回

当程序进入 main 函数时,编译器为其生成了典型的函数入口和栈帧建立语句:
main:
  pushq      %rbp
  movq       %rsp, %rbp
  subq $32, %rsp
寄义表明:


[*]pushq %rbp:生存调用者的基址指针;
[*]movq %rsp, %rbp:建立当前函数的基准栈帧;
[*]subq $32, %rsp:为本函数的局部变量分配32字节栈空间。
函数返回语句(函数“结尾”):
call getchar
movl     $0, %eax
leave
ret
寄义表明:


[*]call getchar:等待用户输入,使程序停息;
[*]movl $0, %eax:设置返回值为 0;
[*]leave:函数清理工作,等价于:
mov %rbp, %rsp
pop %rbp


[*]ret:从函数中返回,跳转回调用该函数的位置。

3.3.6 数据布局与局部变量分析

本节从“数据”的角度,分析 hello.c 程序中涉及的变量、指针、数组和字符串常量的类型定义、存储位置以及在汇编中的体现,特别关注局部变量在栈帧中的分配和利用方式。
一、数据布局与类型维度分析

[*]整型变量 i


[*]类型:int
[*]存储位置:主函数的栈帧
[*]用途:控制 for 循环
[*]汇编操作:
movl $0, -4(%rbp)        ; i = 0
addl $1, -4(%rbp)        ; i++
cmpl $9, -4(%rbp)        ; 判定 i <= 9

[*]main 函数参数 argc 和 argv


[*]类型:int argc, char *argv[](即 char**)
[*]初始通过寄存器 %edi 和 %rsi 传入,被生存为局部变量:
movl %edi, -20(%rbp)
movq %rsi, -32(%rbp)

[*]指针数组访问(argv)


[*]类型:char* 指针
[*]表现情势:通过偏移所在访问,例如:
movq -32(%rbp), %rax
addq $8, %rax
movq (%rax), %rsi      ; 取 argv


[*]本质是数组解引用:argv = *(argv + 1)

[*]字符串常量(只读数据)


[*]示例:"Hello %s %s %s\n"、错误提示字符串
[*]编译器同一存储在 .rodata 段:
.LC1:
    .string "Hello %s %s %s\\n"


[*]汇编中作为所在利用:
movl $.LC1, %edi


二、局部变量布局与栈帧分析
变量名
类型
栈帧偏移
汇编用途形貌
i
int
-4(%rbp)
控制 for 循环,自增、比力、初始化
argc
int
-20(%rbp)
参数计数,判定是否为 5
argv
char**
-32(%rbp)
指向字符串数组,用于取出各参数
这些局部变量均分配在栈帧中,函数进入时通过以下语句建立栈帧并分配空间:
pushq %rbp
movq %rsp, %rbp
subq $32, %rsp      ; 为局部变量分配栈空间
函数退出时通过 leave 与 ret 指令规复现场并返回。
3.4 本章小结

本章从源文件经过预处置惩罚后的 .i 文件出发,具体分析了 C 程序在编译阶段被转换为汇编代码 .s 文件的全过程。通过对现实生成的 hello.s 文件举行解读,我们深入了解了编译器如何将函数调用、条件判定、循环布局、嵌套表达式等高级语法翻译为底层的汇编指令。
本章内容包括:


[*]函数调用参数的寄存器通报规则;
[*]条件语句通过 cmp 与跳转指令实现的控制流程;
[*]for 循环拆解为初始化、判定、自增与跳转标签;
[*]sleep(atoi(...)) 等嵌套函数调用的顺序拆解;
[*]栈帧的建立与清理过程;
[*]局部变量、整型参数、指针数组和字符串常量等数据布局在栈与内存中的存储与访问机制。

通过这些内容的解说,不但展现了 C 源码与汇编指令之间的映射关系,也展示了今世编译器生成高效汇编的规律性。理解这一过程是深入掌握盘算机体系工作机制、调试程序及分析性能瓶颈的重要基础。


(第3章2分)

第4章 汇编

4.1 汇编的概念与作用

汇编阶段是将 .s 汇编语言源文件翻译为 .o 机器语言目标文件的过程,它由汇编器(如 as)完成。汇编器会将每一条汇编指令转化为对应的机器指令(十六进制编码),并将符号、段等信息封装为 ELF(Executable and Linkable Format)格式的可重定位目标文件。
该目标文件尚未具备运行本事,但包含完备的代码段、数据段、符号表与重定位信息,准备进入链接阶段。该过程对深入理解指令编码与链接机制具有重要意义。
4.2 在Ubuntu下汇编的下令

利用 gcc 举行汇编的下令如下:
gcc -m64 -no-pie -fno-stack-protector -fno-PIC -c hello.s -o hello.o


[*]-c 表示仅编译为目标文件,不举行链接;
[*]-m64 指定 64 位体系;
[*]-no-pie 和 -fno-PIC 关闭位置无关代码,方便观察所在;
[*]hello.o 是生成的目标文件,二进制格式,不可直接阅读。

https://i-blog.csdnimg.cn/direct/fb1e419b0b8547249e9a99e3d4761115.png
图 4汇编下令截图
4.3 可重定位目标elf格式

目标文件 hello.o 是一种 ELF 格式文件。可以利用 readelf 查看其布局:readelf -S hello.o   
https://i-blog.csdnimg.cn/direct/cfa9ce7d077d453c93a935478e14e6e8.png
图 5 hello.o节区
重点节区解析:


[*].text
可实验代码段,包含 main 函数的全部指令,是目标文件中最关键的一部分。
[*].rodata
存储 "Hello %s %s %s\n"、"用法: Hello ..." 等字符串,是常量数据,运行时不可更改。
[*].rela.text
包含对外部函数(如 printf, sleep 等)和常量所在的重定位条目,供链接器处置惩罚。
[*].symtab / .strtab
.symtab 是符号表,记录全部变量、函数等符号的位置信息; .strtab 是用于存储这些符号名字的字符串池。
[*].eh_frame / .rela.eh_frame
提供异常处置惩罚所需的元信息,支持 gcc 的栈睁开与调试机制。
未利用段(.data / .bss)
本程序中没有利用全局变量或静态变量,因此 .data 和 .bss 段的巨细均为 0,是“空段”。
4.4 Hello.o的效果解析

利用以下下令对目标文件 hello.o 举行反汇编并查看重定位项:
objdump -d -r hello.o
该下令输出 .text 段的机器指令(十六进制)与汇编指令,并指出了需要在链接阶段修正的所在。本节将效果与前面第3章中手写的汇编源代码 hello.s 举行对照,分析汇编语言与机器语言之间的映射关系,重点探究机器语言的构成与操作数表达方式,特别是在函数调用、条件跳转等控制转移指令中的差异。

[*]函数调用
在汇编代码hello.s中,函数调用直接标上了函数的名称。而在反汇编代码中,call目标所在是当前指令的下一条指令所在。这是因为hello.c中调用的函数都是共享库(如stdio.h,stdlib.h)中的函数,如puts,exit,printf,atoi,sleep等,需要等待链接器举行链接之后才能确定相应函数的所在。因此,机器语言中,对于这种不确定所在的调用,会先将下一条指令的相对所在全部设置为0,然后在.rel.text节中为其添加重定位条目,等待链接时确定所在。
https://i-blog.csdnimg.cn/direct/e5f6aeba9b594a52abce40e49cd1b18a.png
图 6 getchar汇编
https://i-blog.csdnimg.cn/direct/c3d09e1ad8c3403abe6c4d47b4db5433.png
图 7 getchar反汇编
https://i-blog.csdnimg.cn/direct/0549ae20877d4950b599f09c22a8c106.png
图 8hello.o反汇编


[*]分支跳转
在汇编语言中利用跳转指令只需要在后面加上标识符便可以跳转到标识符所在的位置,而机器语言经过翻译直接通过长度为一个字节的PC(Program Counter,程序计数器)相对所在举行跳转。
https://i-blog.csdnimg.cn/direct/84e2110717774810b31c65a89f12d7be.png
图 9 跳转汇编代码
https://i-blog.csdnimg.cn/direct/0048bfc35bc547ccac134adc9c3d7218.png
图 10 跳转反汇编代码

[*]立刻数部分
原本十进制的立刻数都变成了二进制。输出的文件是二进制的,对于objdump来说,直接将二进制转化为十六进制比力方便,也有利于程序员以字节为单位观察代码。
https://i-blog.csdnimg.cn/direct/857cf8c5433647869e7ded6075fe2f3d.png
图 11 立刻数汇编
https://i-blog.csdnimg.cn/direct/bd57484479eb40548d2efc806e11f20a.png
图 12 立刻数反汇编
4.5 本章小结

本章对汇编的概念、作用、可重定向目标文件的布局及对应反汇编代码等举行了较为具体的介绍。经过汇编阶段,汇编语言代码转化为机器语言,生成的可重定位目标文件(hello.o)为随后的链接阶段做好了准备。完成本章内容的过程加深了我对汇编过程、ELF格式以及重定位的理解。

(第4章1分)

第5章 链接

5.1 链接的概念与作用

5.1.1 链接的概念

链接作为编译流程的收官环节,其核心功能是将编译器生成的中间目标文件整合成完备的可实验程序或共享库组件。具体而言,这一过程包含以下关键操作:
(1)符号解析:链接器依据目标文件内的符号表信息,对程序中全部函数与变量的引用关系举行解析,确保各类外部调用(如标准库函数、跨文件变量引用)精准映射到精确的内存所在空间。
(2)所在分配:链接器按照程序的虚拟内存布局规则,为代码段、数据段等差别类型的内存区域分配逻辑所在,保障程序各部分在加载至内存时能够占据精确位置,制止所在辩论。
(3)重定位处置惩罚:针对目标文件中以相对所在情势存在的代码和数据引用,链接器会根据终极的内存布局调解其所在偏移量,将相对所在转换为可直接访问的物理所在或虚拟所在。
5.1.2 链接的作用


[*]多模块整合:编译器通常将源代码拆分为多个独立的目标文件(.o 格式),每个文件对应源代码的一个逻辑模块。链接器通过合并这些目标文件,消除模块间的符号引用间隙,生成单一的可实验文件或库文件,实现程序从离散模块到完备实体的转变。
[*]符号映射与所在修正:当程序中存在跨模块的函数调用或全局变量引用时,链接器负责解析这些符号的现实定义位置,并通过重定位机制更新全部相关引用的所在值。例如,函数调用指令中的目标所在会从编译时的占位符更新为运行时的真实内存所在,确保程序实验流的精确性。
[*]库文件的差异化链接   

[*]静态链接:在编译阶段将所需的库函数代码直接嵌入目标文件,生成的可实验文件完全独立于外部库环境。这种方式的上风是程序可在无依赖环境下运行,但会导致文件体积增大且无法共享库资源。
[*]动态链接:仅在目标文件中记录库函数的引用关系,程序运行时由操作体系动态加载所需的共享库文件(如.so 格式)。该模式显著节省内存空间,支持多程序共享同一库实例,同时便于库的版本升级与维护。

[*]内存布局优化:链接器通过分析程序各模块的访问特性与依赖关系,优化内存分段布局(如将只读代码段与可写数据段分离),并移除未被利用的代码段(Dead Code Elimination),在提升程序实验服从的同时减小二进制文件体积,实现存储资源的高效利用。
   
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并回车,效果如下图所示:
https://i-blog.csdnimg.cn/direct/3dffd6d27f7e46bcb525d54fdaa0767e.png
图 13 链接下令
5.3 可实验目标文件hello的格式

 可实验目标文件的格式雷同于可重定位目标文件(hello.o)的格式,但稍有差别。ELF头中字段e_entry给出实验程序时的第一条指令的所在,而在可重定位文件中,此字段为0。可实验目标文件多了一个程序头表,也称为段头表,是一个布局数组。可实验目标文件还多了一个.init节,用于定义_init函数,该函数用来实验可实验目标文件开始实验时的初始化工作。因为可实验目标文件不需要重定位,所以比可重定位目标文件少了两个.rel节。

[*]ELF头
与第四章利用的方法相似,我们可以利用readelf查看hello文件的ELF头。https://i-blog.csdnimg.cn/direct/378d66b561ed4b19952b724ade6f5e26.png
图 14 helloELF头

        2.节头
节头形貌了各个节的巨细、偏移量和其他属性。链接器链接时,会将各个文件的相同段合并成一个大段,并且根据这个大段的巨细以及偏移量重新设置各个符号的所在。
https://i-blog.csdnimg.cn/direct/7620dc0a23ba49ebb619d4a7b3d5851d.png
图 15 hello的section头
        3.符号表
符号表中生存着定位、重定位程序中符号定义和引用的信息,全部重定位需要引用的符号都在其中声明。
https://i-blog.csdnimg.cn/direct/cd32ee134140454db33324fd3e621259.png
5.4 hello的虚拟所在空间

 在终端中输入edb并回车,打开edb界面,在File-Open中选择hello文件,点击open,界面显示如下。
https://i-blog.csdnimg.cn/direct/dcf49881307c44ab923c0ead9a5c5fc5.png
图 16 edb
可以看到hello虚拟所在空间的起始所在为0x401000,结束所在为0x401ff0。
根据5.3中的节头部表,可以通过edb找到各段的信息。
例如.text节,起首先从节头部表找到开始的虚拟所在。然后在edb中查询所在。https://i-blog.csdnimg.cn/direct/0b38f300e4b14058a89ff383fce7afc8.png
https://i-blog.csdnimg.cn/direct/cb260b3dbeea4ae08432bee8d9bb1ef4.png
图 17 .text段
.plt起始所在为0x401090,在edb中查询所在。
https://i-blog.csdnimg.cn/direct/1a32d09163974da296d8d295de095052.png
https://i-blog.csdnimg.cn/direct/50c2b00fcbb949cb86896b0694be8421.png
图 18 plt段
5.5 链接的重定位过程分析

在终端输入下令objdump -d -r hello并回车,查看hello可实验文件的反汇编条目,效果如下:
https://i-blog.csdnimg.cn/direct/3e753499cf6f47009ce4c4b592840f00.png
图 19 objdump效果
对比第四章中生成的hello.o反汇编文件和hello反汇编文件,这两份反汇编文件其实反映了程序在「未链接」和「已链接」两种差别阶段的样子,以下我们来分析差别之处。

hello反汇编代码中函数调用时不再仅仅储存call当前指令的下一条指令,而是已经完成了重定位,调用的相应函数已经有对应的明确的虚拟所在空间。
https://i-blog.csdnimg.cn/direct/5e5f6c1dd4884e6bb95636d2b8df8378.png
图 20 函数调用
hello反汇编代码中相比.o反汇编代码多出来的节都是经过链接之后加入进来的。例如.init节就是程序初始化需要实验的代码所在的节,.dynamic节是存放被ld.so调用过的 动态链接信息的节等等。https://i-blog.csdnimg.cn/direct/0645cce1af2448cabd1d2c26330f1fbb.png
图 21 .init节
重定位过程可划分为两个关键步骤:

[*]节与符号定义的重定位
链接器起首对全部输入目标文件中类型相同的节(如.text、.rodata等)举行合并,形成聚合节。随后,链接器为这些聚合节以及输入模块中定义的每个符号分配运行时内存所在。通过这一操作,程序中的每条指令、全局变量和函数符号均被赋予唯一的虚拟内存所在,为后续的所在解析奠定基础。
       2.节内符号引用的重定位
在完成所在分配后,链接器需修正代码节和数据节中对符号的引用,使其指向精确的运行时所在。这一过程依赖于可重定位目标模块中的重定位条目(Relocation Entries),这些条目记录了需要调解的符号引用位置及类型。链接器根据重定位条目中的信息,对目标文件中未解析的符号引用(如外部函数调用、全局变量访问)举行逐一修正,确保程序在运行时能够精确访问目标所在。
5.6 hello的实验流程


[*]调试环境启动
通过 EDB(Embedded Debugger)启动并调试 "hello" 可实验文件,触发程序加载流程。
[*]动态链接器初始化
程序控制权起首转移至动态链接器的初始化函数_dl_init,该函数负责解析共享库依赖、实验重定位操作,并完成运行时环境的初始化。初始化完成后,程序跳转到 "hello" 的入口点_start。
[*]C 标准库初始化
_start函数通过call指令调用动态链接库ld-2.27.so中的_libc_start_main函数。此函数作为 C 标准库的入口点,负责:   

[*]初始化栈帧和环境变量
[*]设置程序终止处置惩罚机制
[*]调用用户程序的main函数

[*]终止处置惩罚函数注册
_libc_start_main调用动态链接库中的__cxa_atexit函数,注册程序退出时需要实验的清理函数(如全局对象析构函数)。这些函数会被存入一个全局函数表中,在程序正常退出时依次调用。
[*]静态库初始化
控制权返回__libc_start_main后,调用由静态库引入的__libc_csu_init函数。该函数实验额外的初始化工作,如初始化全局变量、设置信号处置惩罚等。
[*]异常处置惩罚机制设置
再次返回__libc_start_main后,调用动态链接库中的_setjmp函数,设置非本地跳转点(用于异常处置惩罚和longjmp机制),确保程序能精确处置惩罚异常环境。
[*]进入用户代码
完成全部初始化后,__libc_start_main正式调用用户程序的main函数,通报下令行参数(argc, argv)和环境变量(envp)。
[*]条件判定与程序退出
在 EDB 调试环境中运行时,由于未提供下令行参数,main函数内部的条件判定(如if (argc < 2))被触发,程序通过exit(1)提前终止。
[*]退出流程实验
exit函数起首调用之前通过__cxa_atexit注册的清理函数,然后实验标准 I/O 缓冲区革新、文件形貌符关闭等操作,终极通过体系调用_exit终止进程。
[*]内核资源采取
操作体系内核采取进程占用的全部资源(如内存、文件形貌符、CPU 时间片等),完成程序的整个生命周期。
https://i-blog.csdnimg.cn/direct/d196d89e73b4447ebb211f7fd3953831.png
图 22 edb实验hello
5.7 Hello的动态链接分析

动态链接的基本头脑是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完备的程序,在调用共享库函数时,编译器没有办法猜测这个函数的运行时所在,因为定义它的共享模块在运行时可以加载到恣意位置。正常的方法是为该引用生成一条重定位记录,然后动态链接器在程序加载的时候再解析它,延迟绑定是通过GOT和PLT实现的。
.plt:PLT是一个数组,其中每个条目是16字节代码。PLT是一个特别条目,它跳转到动态链接器中。每个被可实验程序调用的库函数都有它自己的PLT条目。每个条目都负责调用一个具体的函数。

.got:GOT是一个数组,其中每个条目是8字节所在。和PLT联合利用时,GOT和GOT包含动态链接器在解析函数所在时会利用的信息。GOT是动态链接器在1d-linux.so模块中的入口点。别的的每个条目对应于一个被调用的函数,其所在需要在运行时被解析。每个条目都有一个相匹配的PLT条目。
一次调用某个函数时,程序不是直接调用,而是调用进入函数所在的PLT条目,第一条PLT指令通过GOT举行间接跳转,每个GOT条目初始时都指向其对应的PLT条目标第二条指令,这个间接跳转只是简单将控制传送回函数所在的PLT条目标下一条指令。之后将函数的ID压入栈中之后,函数所在的PLT条目跳转到PLT,最后PLT通过GOT间接地把动态链接器的一个参数压入栈中,然后通过GOT简介跳转进入动态链接器。动态链接器通过利用两个栈条目来确定函数的运行时位置,再将控制通报给函数。
后续调用时,则可以不消通过GOT的跳转将控制给到函数。
hello在动态毗连器加载前后的重定位是不一样的,在加载之后才举行重定位。
https://i-blog.csdnimg.cn/direct/eedfcf20e7f24644847e4f38796985d1.png
图 23 .got段源代码截图
5.8 本章小结

本章表明确链接的基本概念和作用,利用下令链接生成hello可实验文件,观察了hello文件ELF格式下的内容,利用edb观察了hello文件的虚拟所在空间利用环境,对其链接过程举行分析,最后分析白他的重定位,实验流程和动态链接。

第6章 hello进程管理

6.1 进程的概念与作用

进程是程序的一次运行实例,是操作体系资源分配和调度的基本单位。它包含程序代码、数据、内存空间和运行时状态。
作用:

[*]资源隔离:每个进程有独立的内存空间,互不干扰。
[*]资源管理:操作体系通过进程分配 CPU、内存、文件等资源。
[*]并发实验:支持多个进程同时运行,实现多任务。
[*]调度单位:进程是操作体系调度与管理的基本单位。
[*]程序实验载体:程序必须以进程情势运行才能被实验

6.2 简述壳Shell-bash的作用与处置惩罚流程

Shell(如 Bash) 是用户与操作体系之间的下令表明器,它接收用户输入的下令并传给内核实验。
作用:

[*]提供下令行接口;
[*]解析并实验用户下令;
[*]控制进程、重定向、管道等功能;
[*]支持脚本编程,主动化任务。
处置惩罚流程:

[*]用户输入下令;
[*]Shell 解析下令行(词法/语法分析);
[*]查找下令(内建或外部程序);
[*]创建子进程实验下令;
[*]显示实验效果并等待下一条下令。
6.3 Hello的fork进程创建过程

起首用户在shel1界面输入指令:./hello 2023111785姚鹏举 18645267050 0
Shell判定该指令不是内置下令,于是父进程调用fork函数创建一个新的子进程,该子进程得到与父进程用户级虚拟所在空间相同的一份副本,包括代码和数据段、堆、共享库以及用户栈。子进程与父进程最大的区别就是具有差别的PID。在父进程中,fork返回子进程的PID,而在子进程中fork返回0,返回值提供一个明确的方法来分辨程序是父进程照旧在子进程中实验。
6.4 Hello的execve过程

execve函数在当进步程的上下文中加载并运行一个程序。函数声明如下:
int execve(const char *filename, const char *argv[], const char *envp[]);
execve函数加载并运行可实验目标文件filename,且带参数列表argv和环境变量envp。只有当出现错误时,例如找不到filename,execve才会返回到调用程序。所以,与fork一次调用返回两次差别,execve调用一次并不返回。
6.5 Hello的进程实验

hello 程序从启动到退出,其实验过程可以分为以下几个关键阶段,对应操作体系为进程和 CPU 调度所提供的抽象:
6.5.1 逻辑控制流与私有所在空间

当借助 execve ("./hello", argv, envp) 启动 hello 程序时,操作体系会对当进步程的映像举行替换,并为其构建一个全新的虚拟所在空间。该空间涵盖了可实验文件的代码段、数据段、未初始化数据段(BSS)、堆区以及用户栈,同时还会映射所需的共享库。程序计数器(PC)从 main 函数的入口处开始,按顺序取出指令并实验,这一系列的指令序列就构成了进程的 “逻辑控制流”。在这个隔离的所在空间内,hello 程序看似独占了全部内存,然而现实上,内核是通过页表和虚拟内存机制在物理内存中为其分配相应资源的。
6.5.2 用户态到内核态的切换

在hello程序实验`printf("Hello 2023111785 姚鹏举 18645267050 0\n")`时,标准I/O库函数终极会触发底层的`write`体系调用。此时,处置惩罚器会从用户态主动切换至内核态,跳转至内核中`write`体系调用的实现逻辑,完成字符缓冲区到内核空间的拷贝,并通过装备驱动完成现实的输出操作。当体系调用实验完毕后,内核会将处置惩罚器状态规复为用户态,并返回到`printf`调用后的下一条指令继续实验。
6.5.3 时间切片与上下文切换

在多进程运行环境下,为实现CPU资源的公平分配,内核采用时间片轮转调度战略。每个进程被分配一个固定时长的时间片,当时间片用尽或出现更高优先级的就绪进程时,调度器会触发上下文切换。 
切换过程中,内核起首将当进步程的通用寄存器、程序计数器(PC)、栈指针等状态信息生存至进程控制块(PCB),随后加载下一个进程的上下文信息,规复其寄存器状态并从上次中断处继续实验。通过这种机制,多个进程的逻辑控制流能够在单核或多核CPU上瓜代运行,实现宏观上的并发效果。
6.5.4 休眠与信号处置惩罚

当 hello 程序调用 sleep(n) 时,它会进入可中断的睡眠状态,内核在定时器中登记一个叫醒事件,并将该进程挂起,让出 CPU 给其他就绪进程。睡眠期间,如果有信号到达(例如用户按下中断键或定时器信号),内核会提前叫醒 hello,先实验相应的信号处置惩罚程序,再根据处置惩罚效果决定是否继续睡眠或返回到用户态的后续指令。到了预定睡眠时长,内核将 hello 重新加入就绪队列,等待下一次调度。
6.5.5 进程退出

当 hello 程序实验到 return 0; 或调用 exit() 时,会再次触发一次内核切换,将退出码记录到父进程可查看的位置,并开始采取资源:取消其虚拟内存映射、关闭打开的文件形貌符、释放内核数据布局。进程控制块被标记为“已终止”,并在适当时机从就绪队列中移除。至此,hello 的整个实验生命周期才算圆满结束。
6.6 hello的异常与信号处置惩罚

异常的类别:
https://i-blog.csdnimg.cn/direct/beddd6d73ebf45caaf225d60dbf9cd98.png
        正常运行状态
https://i-blog.csdnimg.cn/direct/17f2ccb61b534dbaa8d0c499ab253ffb.png
图 24 正常运行
运行时按下Ctrl + C,Shell进程收到SIGINT信号,Shell结束并采取hello进程。https://i-blog.csdnimg.cn/direct/2cd150d71ebd4bf1a198adcaa6e46e2e.png
图 25 ctrl+c
运行时按下Ctrl + Z
按下Ctrl + Z,Shell进程收到SIGSTP信号,Shell显示屏幕提示信息并挂起hello进程。https://i-blog.csdnimg.cn/direct/4d9c084ab55648eba8b17297b11bca51.png
图 26 制止
对hello进程的挂起可由ps和jobs下令查看,可以发现hello进程确实被挂起而非被采取,且其job代号为1。https://i-blog.csdnimg.cn/direct/40ea96dda6274d67a0ed0f6c332719ae.png
图 27 查看挂起
输入kill下令,则可以杀死指定(进程组的)进程:https://i-blog.csdnimg.cn/direct/10e5d8a5543c44eca05939d5bc68c325.png
图 28 杀死进程
6.7本章小结

第6章围绕 hello 程序的进程管理睁开,从进程的基本概念、Shell bash 的启动流程,到 hello 进程的创建、替换、实验与闭幕,体系地展现了操作体系如何借助多种抽象来支持应用程序运行。起首,我们介绍了进程作为资源分配与调度的基本单位,具有独立的所在空间和控制流;随后阐述了 Bash 解析下令、通过 fork() 派生子进程并在其中实验外部程序的全过程;在 execve() 调用中,原进程映像被新程序替换,用户栈上按约定部署 argc/argv/envp;接着分析了 hello 从启动、实验 printf 和 sleep 等体系调用中的用户态/内核态切换,到内核以时间片和上下文切换实现多进程并发;还说明确信号到达时的中断叫醒与处置惩罚机制;最后,当 hello 调用 exit() 退出时,内核采取其虚拟空间和内核数据布局,完备地关闭了这一进程生命周期。

第7章 hello的存储管理

7.1 hello的存储器所在空间

逻辑所在
在有所在变更功能的盘算机中,访问指令给出的所在(操作数)叫逻辑所在,也叫相对所在。要经过寻址方式的盘算或变更才得到内存储器中的物理所在。逻辑所在是由一个段标识符加上一个指定段内相对所在的偏移量,由程序hello产生的与段相关的偏移所在部分
线性所在
线性所在是逻辑所在到物理所在变更之间的一步,程序hello的代码会产生逻辑所在,在分段部件中逻辑所在是段中的偏移所在,加上基所在就是线性所在。
虚拟所在
程序访问存储器所利用的逻辑所在称为虚拟所在。虚拟所在经过所在翻译得到物理所在。与现实物理内存容量无关,是hello中的虚拟所在
物理所在
在存储器里以字节为单位存储信息,每一个字节单元给一个唯一的存储器所在,这个所在称为物理所在,是hello的现实所在或绝对所在。
7.2 Intel逻辑所在到线性所在的变更-段式管理

在今世操作体系的内存管理架构中,段式存储管理技能通过将程序划分为多个逻辑独立的段来实现内存分配与寻址。这种管理方式借助段表实现所在映射,段表条目包含段号(或段名)、段起始所在、装入位及段长度等关键信息。程序依据功能和数据特性被划分为代码段、数据段、共享段等差别类型的逻辑块。
逻辑所在空间采用二维布局计划,由段标识符和段内偏移量两部分构成。其中,段标识符由16位的段选择符表示,前13位作为索引号指向段形貌符表中的特定条目,后3位用于存储硬件相关的访问权限和特权级信息。段形貌符表由多个段形貌符构成,每个形貌符具体定义了一个段的基所在、界限及访问控制属性。
体系中存在两种级别的段形貌符表:
1. 全局形貌符表(GDT):整个体系仅存在一个实例,存储操作体系核心组件(如内核代码段、数据段、堆栈段)的形貌符,以及各任务/程序的局部形貌符表(LDT)段形貌符。
2. 局部形貌符表(LDT):每个任务/程序拥有独立的LDT,包含该任务私有的代码段、数据段、堆栈段形貌符,以及用于实现进程间通信和特权级切换的门形貌符(如任务门、调用门)。
这种两级布局既包管了体系核心资源的全局可见性,又实现了各任务私有资源的隔离掩护,为操作体系提供了机动且安全的内存管理机制。
7.3 Hello的线性所在到物理所在的变更-页式管理

虚拟内存被组织为一个由存放在磁盘上的N个连续的字节巨细的单元构成的数组。VM体系将虚拟内存分割,称为虚拟页,雷同地,物理内存也被分割成物理页。利用页表来管理虚拟页,页表就是一个页表条目(PTE)的数组,每个PTE由一个有效位和一个位所在字段构成,有效位表明确该虚拟页当前是否被缓存在DRAM中,如果设置了有效位,那么所在字段就表示DRAM中相应的物理页的起始位置,如果发生缺页,则从磁盘读取。
MMU利用页表来实现从虚拟所在到物理所在的翻译。
https://i-blog.csdnimg.cn/direct/3b0a50d7640f49babdd65eb7c0737623.png
图 29 利用页表的所在翻译
7.4 TLB与四级页表支持下的VA到PA的变更

Core i7采用四级页表的层次布局。CPU产生虚拟所在VA,虚拟所在VA传送给MU,MMU利用VPN高位作为TLBT和TLBI,向TLB中寻找匹配。如果命中,则得到物理所在PA。如果TLB中没有命中,MMU查询页表,CR3确定第一级页表的起始所在,VPN1确定在第一级页表中的偏移量,查询出PTE,以此类推,终极在第四级页表中找到PPN,与VPO组合成物理所在PA,添加到PLT。工作原理如下:
https://i-blog.csdnimg.cn/direct/8fc8596b87c5417f9db9b3c14e65b3d6.png
图 30 corei7所在翻译概况
https://i-blog.csdnimg.cn/direct/dc3781fc6bd8435188bfb2349c1f90a7.png
图 31 多级页表
7.5 三级Cache支持下的物理内存访问

当CPU核心需要读取物理所在(PA)的数据时,起首由内存管理单元(MMU)将虚拟所在(VA)转换为物理所在(PA),随后进入多级缓存的查找流程,具体步骤如下: 
1. L1缓存(一级缓存)
作为离CPU核心近来的缓存,L1通常分为数据缓存(L1d)和指令缓存(L1i),每个核心独立拥有。其容量较小(32–64 KB),但访问速率极快(1–4个时钟周期)。CPU通过物理所在的高位部分作为索引,在L1的组相联布局中定位缓存行,并对比标签(Tag)和有效位(Valid bit)。若匹配且有效,则直接从L1读取数据(L1命中),完成访问。 
2. L2缓存(二级缓存) 
若L1未命中,CPU转而访问私有L2缓存(容量256 KB–1 MB,延迟10–15个时钟周期)。L2同样采用组相联布局,通过物理所在的索引和标签查找缓存行。若命中,L2会将数据回填到L1,再由L1交付给CPU;若未命中,则继续向L3缓存请求数据。 
3. L3缓存(三级缓存) 
多核处置惩罚器通常共享L3缓存(容量4–32 MB,延迟30–50个时钟周期),采用高相联度或全相联布局以提升命中率。CPU再次通过物理所在的索引和标签查找缓存行,若命中,数据依次回填至L2和L1,终极返回给实验单元;若未命中,则进入主存访问流程。 
4. 物理内存(DRAM)
当三级缓存均未命中时,内存控制器根据物理所在的行所在(64 B对齐)定位DRAM中的行(Row)和列(Column),实验行激活(ACTIVATE)、读(READ)或写(WRITE)操作。主存访问延迟较高(100–200纳秒),读取的数据会先装载到L3(或直接至L2/L1,依架构而定),再逐层回传至CPU,同时更新各级缓存的对应条目。 
7.6 hello进程fork时的内存映射

当 hello 进程调用 fork() 时,内核启动高效的子进程创建机制:起首为子进程分配独立的进程形貌符(task_struct),并复制父进程的内存管理核心数据布局,包括 mm_struct(记录所在空间全局信息)、虚拟内存区域(VMA)链表(形貌各内存段属性)和页表布局的镜像,确保子进程在逻辑上拥有与父进程一致的虚拟所在空间视图。

在物理内存层面,父子进程共享原始物理页面,内核通过将页面的引用计数加一,并在双方页表中将对应页表项标记为只读,激活 “写时复制”(Copy-On-Write, COW)机制。此时,两者的虚拟所在虽指向同一物理页,但任何写入操作都会触发 CPU 的页错误(Page Fault)。

当父进程或子进程首次尝试修改共享页面时,CPU 检测到页表项的只读标志,触发异常并陷入内核处置惩罚流程。内核识别到这是 COW 场景后,会为发起写操作的进程分配全新的物理内存页,将原页面内容完备拷贝至新页,并更新该进程的页表项指向新页且允许写入,而另一进程的页表项仍指向旧页(保持只读状态)。

这种机制使得父子进程在逻辑上拥有独立的所在空间,但在现实物理内存利用上,仅在发生写操作时才按需分配资源。它制止了传统复制方式对内存的大量斲丧,尤其实用于 fork 后立刻实验 execve(如启动新程序覆盖所在空间)的场景,显著提升了进程创建的服从和内存利用率。通过 COW 技能,Linux 内核在隔离性与性能之间实现了优化平衡,成为今世操作体系进程管理的核心机制之一。

https://i-blog.csdnimg.cn/direct/fa5cd3fba70740b3bf5e58af56577db5.png
图 32 进程2写了私有区域的一个页
7.7 hello进程execve时的内存映射

execve 函数调用驻留在内核区域的启动加载器代码,在当进步程中加载并运 行包含在可实验目标文件 hello 中的程序,用 hello 程序有效地替代了当前程序。 加载并运行 hello 需要以下几个步骤:
1.删除已存在的用户区域,删除当进步程虚拟所在的用户部分中的已存在的区域布局。
2.映射私有区域,为新程序的代码、数据、bss 和栈区域创建新的区域结 构,全部这些新的区域都是私有的、写时复制的。代码和数据区域被映射为 hello 文件中的.text 和.data 区,bss 区域是请求二进制零的,映射到匿名 文件,其巨细包含在 hello 中,栈和堆所在也是请求二进制零的,初始长度为零。
3.映射共享区域, hello 程序与共享对象 libc.so 链接,libc.so 是动态链 接到这个程序中的,然后再映射到用户虚拟所在空间中的共享区域内。
4.设置程序计数器(PC)。execve 做的最后一件事变就是设置当进步程 上下文的程序计数器,使之指向代码区域的入口点。
7.8 缺页故障与缺页中断处置惩罚

如果程序实验过程中发生了缺页故障,则内核调用缺页处置惩罚程序。处置惩罚程序实验如下步骤:

[*]查抄虚拟所在是否合法,如果不合法则触发一个段错误,终止这个进程。
     2.查抄进程是否有读、写或实验该区域页面的权限,如果不具有则触发掩护异常,程序终止。
     3.两步查抄都无误后,内核选择一个捐躯页面,如果该页面被修改过则将其交换出去,换入新的页面并更新页表。然后将控制转移给hello进程,再次实验触发缺页故障的指令。

https://i-blog.csdnimg.cn/direct/a45aacd74bfa418dbba1a8a824d95a4d.png
图 33 缺页异常环境处置惩罚
7.9动态存储分配管理

7.10本章小结

本章从多个角度分析了hello的动态存储管理,包括存储器所在空间、intel的段式管理、hello的页式管理,以intel Core i7在指定环境下介绍了虚拟所在VA到物理所在PA的转换、物理内存访问,分析了hello进程fork时的内存映射、hello进程、execve时的内存映射、缺页故障与缺页中断处置惩罚以及动态存储分配管理
结论

总结hello所履历的过程如下:

起首要完成hello.c的C语言源程序的编写。这就是Hello的起点。
下一步利用下令gcc -E举行预处置惩罚,Hello完成从hello.c到hello.i的成长。
下一步利用下令gcc -S举行编译,Hello完成从hello.i到hello.s的成长。
下一步利用下令gcc -c举行汇编,Hello完成从hello.s到hello.o的成长。此时的Hello已经变成一个二进制文件了。
对Hello举行链接,将Hello的好同伴们和Hello联系起来,把它和其他可重定位二进制文件合体,变成一个可以在盘算机上运行的二进制文件。
打开Shell,输入下令./hello 2023111785 姚鹏举 18645267050 0来运行Hello程序。
Shell举行第六章中报告的一系列判定:起首判定输入下令是否为内置下令。经过查抄后发现其不是内置下令,则Shell将其看成程序实验。
随机Shell调用Fork()函数为Hello创建一个进程。
shell调用execve函数,execve函数会将新创建的子进程的区域布局删除,然后将其映射到hello程序的虚拟内存,然后设置当进步程上下文中的程序计数器,使其指向hello程序的入口点。
运行hello时,内存管理单元、TLB、多级页表机制、三级cache协同工作,完成对所在的翻译和请求。
当Hello运行到printf这一步时,操作体系会调用malloc函数从堆中申请内存。
当Hello实验时,可以通过IO输入等操作向进程发送信号。例如我们从键盘输入Ctrl-c,就会发送一个SIGINT信号,使当前前台进程的作业中断;同样哦们可以利用下令jobs来查看被抢占的进程,利用下令fg %<pid>来规复对应ID的进程。
当进程实验结束后,由父进程对子进程举行采取。至此,Hello的一生结束。

附件

文件名
生成指令
作用说明
hello.c

源程序
hello.i
gcc -E hello.c -o hello.i
预处置惩罚输出的纯 C 源码
hello.s
gcc -S hello.i -o hello.s
编译阶段生成的汇编语言文件。
hello.o
gcc -c hello.s -o hello.o
汇编器输出的可重定位目标文件,包含机器码二进制、符号表和重定位信息,供链接器合并。
hello
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
终极可实验文件


参考文献

  Randal E.Bryant David R.O'Hallaron.深入理解盘算机体系(第三版).机器工业出书社.
  Kerrisk, M. (2010). The Linux Programming Interface. No Starch Press.
  GCC: The GNU Compiler Collection. (n.d.). GCC Official Documentation. Retrieved from https://gcc.gnu.org/onlinedocs/
  Intel Corporation. (2023). *Intel® 64 and IA-32 Architectures Software Developer
  Tanenbaum, A. S., & Bos, H. (2015). Modern Operating Systems (4th ed.). Pearson.
  Levine, J. R. (2000). Linkers and Loaders. Morgan Kaufmann.


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 哈工大盘算机体系大作业程序人生Hello’s P2P