当前位置: 首页 > article >正文

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

摘  要

文章以C语言程序设计经典案例hello.c为研究对象,系统解析程序在计算机系统中的完整生命周期。剖析源代码通过预处理、编译、汇编、链接四阶段演化为可执行目标程序的编译系统工作机制,继而从进程视角揭示程序运行时计算机体系结构的协同运作:处理器执行指令流、存储器层次架构支撑数据存取,操作系统通过进程管理实现资源调度与状态维护。通过深度追踪hello.c从静态代码到动态进程的转化路径,构建对程序存储、加载、执行等计算机系统核心机制的完整认知框架。

在整个实验中使用泰山服务器进行。

关键词:预处理;编译;汇编;链接;进程;存储;计算机系统       

目  录

第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简介

(1)P2P过程

代码阶段:hello.c作为文本文件(高级语言程序),通过预处理(处理#include等指令)、编译(转为汇编代码hello.s)、汇编(生成机器码hello.o)、链接(合并库函数生成可执行文件hello),最终成为磁盘上的二进制程序。

  

进程阶段;

用户在Shell中输入./hello后,操作系统通过fork()创建子进程,execve()加载hello到内存,CPU开始执行指令:

存储管理:MMU将虚拟地址(VA)转为物理地址(PA),TLB和页表加速映射。

CPU调度:进程在时间片内运行(取指、译码、执行),可能因中断或时间片耗尽切换。

I/O交互:printf通过系统调用write将字符串输出到屏幕,getchar等待键盘输入。

(2)020过程

Zero→运行:

进程从“无”到被OS创建,分配资源(内存、文件描述符等),进入就绪队列。

运行→Zero:

正常终止:main返回后,OS回收内存、关闭文件,父进程(如Shell)通过wait获取退出状态。

异常终止:用户按Ctrl-C(SIGINT)或Ctrl-Z(SIGTSTP)时,OS发送信号强制结束进程,清理资源。

1.2 环境与工具

硬件环境:x64 CPU;3.20GHz;16G RAM

软件环境:Windows 11 64位;MobaXterm

开发与调试工具:gcc,objdump,readelf

1.3 中间结果

原始代码hello.c

预处理后的代码hello.i

编译后的汇编语言代码hello.s

可重定位目标文件hello.o

可执行文件hello

1.4 本章小结

本章主要介绍了hello.c程序P2P,020的过程。列出了本次实验所需的环境和工具以及过程中所生成的中间结果。

第2章 预处理

2.1 预处理的概念与作用

预处理的概念:是在数据正式处理或分析前进行的预备性操作,旨在提升数据质量、统一格式或提取关键信息,为后续步骤提供更高效的输入。

预处理的作用:缩短计算时间、提升模型精度、增强数据可靠性,是机器学习和数据分析中不可或缺的环节。例如,图像预处理(去噪、裁剪)可提高AI识别准确率,文本预处理(分词、去停用词)能优化自然语言处理效果。

2.2在Ubuntu下预处理的命令

图2.1预处理命令展示

2.3 Hello的预处理结果解析

图2.2hello.i文件部分展示

(1)头文件包含展开:

#include <stdio.h> 会被替换为stdio.h头文件的内容

#include <unistd.h> 会被替换为unistd.h头文件的内容

#include <stdlib.h> 会被替换为stdlib.h头文件的内容

图2.3头文件预处理部分截图

(2)注释处理:原始代码中的注释大作业的 hello.c 程序等会被移除

图2.4移除注释后的原程序

(3)头文件内容:

stdio.h会提供printf、getchar等函数的声明

unistd.h会提供sleep函数的声明

stdlib.h会提供exit、atoi等函数的声明

(4)预处理指令消失:所有的#include、#define等预处理指令都会被处理掉,不会出现在预处理后的代码中

2.4 本章小结

预处理是在数据正式处理或分析前进行的预备性操作,旨在提升数据质量、统一格式或提取关键信息,为后续步骤提供更高效的输入。

在linux环境下使用 gcc -E 命令对源代码进行预处理,生成预处理后的代码(.i 文件)。

对C语言程序进行预处理中头文件包含展开、注释处理、预处理指令消失生成更干净的代码供编译器使用。

第3章 编译

3.1 编译的概念与作用

注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序

编译的概念:

编译是指将预处理后的源代码(.i 文件,即经过宏展开、头文件包含、去注释等处理后的代码)转换为汇编代码(.s 文件)的过程。

输入预处理后的 C 代码(.i 文件)输出:汇编语言代码(.s 文件)

编译的作用:

将高级的C语言转换为低级语言更接近机器指令,优化代码结构,为后续汇编阶段做准备

3.2 在Ubuntu下编译的命令

图3.1编译的命令

3.3 Hello的编译结果解析

图3.2hello.s文件展示

3.3.1 基本数据类型处理

整数类型局部变量 i:

图3.3基本数据类型处理

int 为 32 位,使用 w 系列寄存器(w0~w30)。

存储在栈偏移量 44 处(sp 为栈指针)。

指针类型参数访问 argv[1]:

图3.4基本数据类型处理

3.3.2控制结构处理

条件分支(if(argc!=5))

图3.5条件分支处理

cmp 指令比较后,beq(branch if equal)实现条件跳转。

循环 (for(i=0;i<10;i++))

图3.6循环处理

通过 ble(branch if less or equal)和标签实现循环逻辑。

3.3.3 函数调用处理

printf 调用

图3.7printf调用

前 8 个参数通过 x0~x7 传递,printf 的格式字符串地址在 x0,后续参数依次为 x1~x3。

adrp 和 add 组合用于加载全局地址(位置无关代码)。

sleep 和 atoi 调用

图片同图3.7

atoi 返回的 int 存放在 w0,直接作为 sleep 的参数。

3.3.4 内存访问与栈管理

栈帧分配

图3.8栈帧分配

stp(store pair)指令保存 x29(帧指针)和 x30(返回地址),同时调整栈指针 sp。

栈空间用于存储局部变量(如 i 在 [sp, 44])和参数。

参数存储 (argc, argv)

图3.9参数存储的处理

函数入口参数 argc 和 argv 通过 w0 和 x1 传入,需保存到栈。

3.3.5 其他操作

字符串常量处理

图3.10字符串常量处理

系统调用 (getchar)

图3.11系统调用

getchar 的返回值(输入字符的 ASCII 码)通过 w0 返回。

3.4 本章小结

本章详细分析了从预处理后的 .i 文件到汇编代码 .s 的编译过程,重点解析了 hello.c 程序在 ARM64 架构下的编译结果 hello.s。通过分析汇编代码,可以清晰地看到编译器如何将高级 C 语言转换为低级的机器指令表示。

第4章 汇编

4.1 汇编的概念与作用

注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。

汇编的概念:
汇编是将汇编语言(.s 文件)转换为机器语言(二进制 .o 文件)的过程。在这一阶段,汇编器(如 as)逐行解析汇编代码,将其中的指令(如 mov、add)、标签和伪指令(如 .section)翻译为对应的机器码,并生成目标文件。目标文件包含可被计算机直接执行的二进制指令,同时保留符号表、重定位信息等元数据,以便后续链接器处理。这一过程是编译流程中连接高级语言与机器硬件的关键环节,直接生成处理器可识别的低级代码。

汇编的作用:
汇编的核心作用是将人类可读的汇编指令转换为机器可执行的二进制指令,为程序运行提供底层支持。它通过解析符号、地址和指令,生成与特定硬件架构(如 x86、ARM)匹配的目标文件,确保代码能直接在 CPU 上运行。同时,汇编阶段会处理代码分段(如 .text、.data)、解决局部符号引用,并为链接器预留重定位信息,使得多模块程序能正确合并。最终,汇编输出的目标文件是生成可执行程序或库的基础,直接影响程序的性能和硬件兼容性。

4.2 在Ubuntu下汇编的命令

图4.1汇编的命令

4.3 可重定位目标elf格式

分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。

图4.2 ELF 文件头

关键信息:文件类型(REL 表示可重定位目标文件)、架构(AArch64)。

图4.3查看所有节

关键节:

.text:存放代码。

.rodata:只读数据(如字符串常量)。

.rela.text:代码段的重定位信息。

图4.4查看符号表

关键信息:

main 是已定义的函数(FUNC,位于 .text 节)。

puts、printf 等是未定义符号(UND),需在链接时解析。

图4.5查看重定位节

关键字段:

Offset:需要修改的代码/数据在 .text 中的偏移。

Type:重定位类型(如 R_AARCH64_ADR_PREL_PG_HI21 表示 AArch64 的 PC 相对地址高 21 位)。

Sym. Name:依赖的符号(如 printf、.rodata)。

图4.6结合 objdump 查看代码与重定位

外部函数调用

如 puts、printf 的调用(R_AARCH64_CALL26 类型)需在链接时填充实际地址.

数据引用:

如 .rodata 中的字符串地址(R_AARCH64_ADR_PREL_PG_HI21)需在链接时计算。

未定义符号:

符号表中标记为 UND 的符号需由其他目标文件或库提供。

4.4 Hello.o的结果解析

以下是关键代码段的对比(以 main 函数为例):

(1) 函数入口(栈帧设置)

a9be7bfd:stp 指令的编码,操作数为 x29, x30, [sp, #-48]!

910003fd:mov 指令的编码,将 sp 的值赋给 x29。

操作数通过寄存器编号(如 x29=29, x30=30)和立即数偏移(如 #28)编码到机器码中。

(2) 条件分支与重定位

54000140:b.eq 的机器码,0140 是偏移量(计算目标地址 PC + 0x40)。

由于 hello.o 未链接,40 是临时偏移,链接后会修正为实际地址。

(3) 函数调用与重定位

adrp 和 add 的立即数(#0x0)是占位符,链接时会通过重定位修正。

bl 指令的跳转目标(puts)在重定位表(R_AARCH64_CALL26)中记录,链接时填充实际地址。

(4) 数据引用(字符串常量)

字符串以二进制形式存储在 .rodata 节,地址通过 adrp/add 组合加载。

机器语言与汇编语言的映射关系

(1) 指令编码

每条汇编指令对应固定的机器码(如 stp → a9be7bfd)。

操作数(寄存器、立即数)通过位域编码到机器码中:

例如 str w0, [x29, #28] 的 #28 被编码为 12 位有符号立即数。

(2) 分支与函数调用

分支偏移:

汇编中的标签(如 .L2)在机器码中转换为相对当前 PC 的偏移。

未链接时偏移是临时的,链接后修正(如 b.eq 40 中的 40)。

函数调用:

bl 指令的目标地址在重定位表中记录(如 R_AARCH64_CALL26 puts)。

链接时动态库的地址被填充到调用点。

图片参考第三章第四章

4.5 本章小结

本章围绕汇编过程及其在程序生成中的核心作用展开,系统性地分析了从汇编语言(.s文件)到可重定位目标文件(.o文件)的转换机制。汇编作为连接高级语言与机器硬件的关键环节,通过汇编器将人类可读的指令(如mov、bl)转换为机器码,同时处理符号引用、分段管理(如.text、.rodata)并生成重定位信息,为后续链接奠定基础。通过分析hello.o的ELF格式,我们深入理解了目标文件的结构:文件头标识其可重定位属性(REL)与架构(AArch64),节头表管理代码与数据的分区,而重定位条目(如R_AARCH64_CALL26)则记录了链接时需要修正的地址依赖(如外部函数puts的调用)。反汇编与汇编代码的对照进一步揭示了机器语言的构成逻辑——指令通过二进制编码固定化,操作数则嵌入寄存器编号或立即数,而分支与函数调用的地址在未链接时仅为临时值,需依赖重定位动态修正。这一过程凸显了工具链的协作:汇编器生成带元数据的中间文件,链接器解析重定位以绑定实际地址,最终生成可执行程序。本章内容不仅阐明了汇编的底层转换规则,也展现了程序从源码到二进制运行的全链路依赖关系,为理解计算机系统的软件-硬件协同提供了坚实基础。

5链接

5.1 链接的概念与作用

链接是将一个或多个目标文件(如 `hello.o`)及所需的库文件合并生成最终可执行程序(如 `hello`)的关键过程。在这一阶段,链接器(如 `ld`)负责解析目标文件中的符号引用,将分散的代码段和数据段按需组合,并完成地址空间的重新分配。具体到 `hello.o` 到 `hello` 的转换,链接器首先会处理重定位信息,例如修正 `hello.o` 中对 `puts`、`printf` 等外部函数的调用地址,将这些未定义符号绑定到动态库(如 `libc.so`)或静态库中的实际实现;同时合并所有输入文件的 `.text`、`.data` 等节,并确定它们在最终程序中的内存布局。此外,链接器还会添加程序头信息,设置入口点(如 `_start`),并生成动态链接所需的元数据(如 PLT/GOT 表),使程序能在加载时与操作系统交互。通过链接,原本独立的机器码片段被整合为一个完整的可执行映像,不仅解决了模块间的符号依赖问题,还优化了存储空间和运行时效率,最终生成可直接由操作系统加载执行的二进制文件 `hello`。这一过程是程序从源码到运行的最后一步,直接决定了程序能否正确启动并与系统环境无缝衔接。

5.2 在Ubuntu下链接的命令

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

使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件

图5.1链接的命令

实际链接过程隐含调用了 ld,并链接了默认的启动文件(crt1.o)和库(libc.so)。

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

分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。

图5.2 查看 ELF 文件头(架构/入口点)

关键字段:

Type: EXEC(可执行文件)

Entry point address: 0xxxxx(程序入口地址)

Machine: LoongArch(架构)

图5.3列出所有段(Segments)及内存布局

关键信息:

VirtAddr:段在内存中的虚拟地址(如 0x400000)

MemSiz:段在内存中的大小

Flags:权限(R=读,W=写,E=执行)

图5.4列出所有节(Sections)及地址

关键字段:

Addr:节的加载地址(如 .text 通常位于 0x400000 附近)

Size:节的大小

Flg:属性(A=可分配,X=可执行,W=可写)

图5.5查看动态段(依赖库)

作用:确认程序依赖的动态库(如 libc)。

5.4 hello的虚拟地址空间

图5.6虚拟地址空间各段信息

在泰山服务器中没有edb相关,缺少权限无法下载,于是使用pgrep和pmap指令查看虚拟地址空间。

通过 readelf 分析 静态ELF结构,再对比 运行时内存布局:

1. 虚拟地址空间分析(运行时)

通过 edb 或 /proc/[pid]/maps 查看运行时的内存布局,可观察到以下典型段:

代码段(.text):权限为 r-xp(可读可执行),对应程序中 main 函数的机器指令。

数据段(.data/.rodata):

.rodata(只读数据):存储字符串常量(如 "Hello %s %s %s\n"),权限为 r--p。

.data(可写数据):若程序有全局变量,权限为 rw-p。

堆(Heap):动态分配的内存(如 malloc),权限为 rw-p,地址范围由低向高增长。

栈(Stack):存储局部变量和函数调用链,权限为 rw-p,地址范围由高向低增长。

动态库映射:如 libc.so 的代码和数据段,权限为 r-xp(代码)或 rw-p(数据)。

2. 可执行文件(hello)格式分析

通过 readelf -S hello 查看静态文件中的节(Section)信息,主要包含:

.text 节:与运行时代码段对应,存储程序的指令(如 main 函数的汇编指令)。

.rodata 节:与运行时只读数据段对应,存储字符串常量(如 LC0 和 LC1)。

.data 节:若存在初始化全局变量,会在此节定义。

.bss 节:未初始化全局变量,文件中不占空间,运行时扩展为 rw-p 内存。

3. 对照分析关键差异

地址映射:

静态文件中节的地址(如 .text 的 Addr)是相对地址,运行时由加载器重定位为绝对虚拟地址(如 0xaaaaaaaaa000)。

动态链接库(如 libc.so)在文件中仅记录依赖项,运行时才映射到内存。

权限扩展:

静态文件的 .text 节标记为 AX(可分配可执行),运行时扩展为 r-xp。

.rodata 在文件中为只读(A),运行时同样为 r--p。

动态段补充:

运行时新增 [stack]、[heap] 等匿名映射段,这些在静态文件中无对应节。

4. 动态链接与重定位

PLT/GOT:

调用 printf、sleep 等函数时,静态文件通过 .rela.plt 节记录重定位项,运行时由动态链接器填充真实地址。

地址随机化(ASLR):

运行时段的基地址(如 0xaaaaaaaaa000)与静态文件中的预设地址不同,这是操作系统的安全特性。

5.5 链接的重定位过程分析

图5.7objdump指令部分截图

在程序从源文件到可执行文件的转换过程中,链接阶段起着至关重要的作用。通过分析 hello.c 编译生成的 hello.o 目标文件与最终链接生成的 hello 可执行文件,可以清晰地看到链接过程中对符号引用和地址分配的处理方式。

目标文件 hello.o 是一个可重定位文件,其中包含了程序的机器代码和数据,但尚未完成所有地址的最终确定。在 hello.o 中,对外部函数(如 printf、sleep、atoi 等)的调用以及全局数据的引用都是通过重定位项来标记的。这些重定位项指示链接器在最终生成可执行文件时需要修正的地址位置。例如,当 hello.o 调用 printf 函数时,具体的函数地址并未确定,而是由重定位项记录下需要修正的位置和方式。

链接器在生成 hello 可执行文件时,会处理这些重定位项,完成以下几个关键步骤:首先,链接器将所有目标文件的代码段、数据段等按需合并,并为它们分配最终的运行时内存地址;其次,链接器解析所有未定义的符号引用,确保它们指向正确的地址,例如将 printf 的调用指向动态链接库中的实际实现;最后,链接器生成程序头信息,指导操作系统如何加载和执行该程序。

在 hello 可执行文件中,所有的函数调用和数据访问都已完成了地址绑定。原本在 hello.o 中通过重定位项标记的位置,现在已经被替换为具体的虚拟地址或动态链接相关的跳转指令(如通过过程链接表 PLT 实现延迟绑定)。动态链接库中的函数地址会在程序加载时由动态链接器进一步解析填充。

通过这一过程,链接器将零散的目标文件整合为一个完整的可执行文件,确保了程序运行时能够正确访问所有函数和数据。从 hello.o 到 hello 的变化,本质上是从未链接的独立代码片段到地址完全解析的可执行程序的转变,这一过程依赖于重定位信息的精确指导。

5.6 hello的执行流程

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

使用gdb/edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程(主要函数)。请列出其调用与跳转的各个子程序名或程序地址。

图5.8gdb执行hello

程序加载与启动(_start)

当执行 ./hello 时,操作系统加载器(如 ld-linux)负责将程序载入内存,并跳转到入口点 _start(由链接器默认设置)。该过程涉及:

加载动态链接库(如 libc.so),解析依赖项。

初始化程序环境(栈、全局变量等)。

调用 __libc_start_main(由 _start 调用),负责:

设置 argc 和 argv。

调用全局构造函数(如 .init 段)。

最终调用 main 函数。

主函数执行(main)

main 函数的调用链如下:

参数检查

检查 argc != 5 时,调用 printf 输出用法提示,随后调用 exit(1) 终止。

循环输出

调用 printf 打印格式化字符串(参数为 argv[1]、argv[2]、argv[3])。

调用 atoi 将 argv[4] 转换为整数,传递给 sleep 函数。

等待输入

调用 getchar 等待用户输入。

返回退出

return 0 触发 main 的退出逻辑。

程序终止

main 返回后,控制权交还给 __libc_start_main,依次执行:

调用全局析构函数(如 .fini 段)。

调用 _exit 系统调用(或 exit_group),释放资源并终止进程。


图5.9main函数的地址

5.7 Hello的动态链接分析

图5.10gdb调试并设置断点

图5.11GOT初始值与PLT条目

图5.12链接后GOT项

(1)定位关键地址

使用info functions printf@plt获取PLT条目地址(如0x4005f0)

通过objdump -R hello或PLT指令计算GOT项地址(如0x411040)

(2)观察初始状态

反汇编PLT条目(disas 0x4005f0),确认其跳转逻辑

检查GOT项初始值(x/gx 0x411040),应指向PLT解析代码

(3)触发动态链接

执行到printf调用(continue或step)

动态链接器解析libc地址并更新GOT项

(4)验证绑定结果

再次检查GOT项(x/gx 0x411040),确认指向libc(如0x7ffff7e3d490)

通过info sharedlibrary验证地址在libc映射范围内

5.8 本章小结

本章主要介绍了链接的概念与作用,在泰山服务器下链接的命令,可执行目标文件hello的格式,包括ELF头、节头、重定位节、符号表,通过对比hello与hello.o进行链接的过程分析和重定位过程分析,对hello的执行流程和动态链接进行分析。

6hello进程管理

6.1 进程的概念与作用

(1)进程的概念:

进程的经典定义就是一个执行中程序的实例。

(2)进程的作用:

每次用户向shell输入一个可执行目标文件的名字,运行程序时,shell就会创建一个新的进程,然后在这个新进程的上下文中运行这个可执行目标文件。应用程序也能够创建新进程,并且在这个新进程的上下文运行它们自己的代码或其他应用程序。进程提供给应用程序两个关键抽象:

①一个独立的逻辑流,它提供一个假象,好像我们的程序独占地使用处理器。

②一个私有的地址空间,它提供一个假象,好像我们的程序独占地使用内存系统。

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

作用:Shell是信号处理的代表,负责各进程创建与程序加载运行及前后台控制,作业调用,信号发送与管理等。

shell的处理流程一般为:提示符显示;读取命令;解析命令;查找命令;创建子进程;执行命令;等待命令完成;显示输出;返回提示符

6.3 Hello的fork进程创建过程

当程序通过`fork()`系统调用创建子进程时,操作系统会生成一个与父进程高度相似但独立的新进程。子进程会获得父进程用户空间内存的完整副本,包括程序代码、数据段、堆内存、共享库以及用户栈空间,所有这些内容都是独立拷贝而非共享。此外,子进程还会继承父进程所有已打开的文件描述符,这意味着它可以像父进程一样读写相同的文件资源。父进程与子进程之间最本质的区别在于它们拥有各自独立的进程ID(PID),这是操作系统区分两个进程的关键标识。其他诸如寄存器状态、内存映射等资源在创建初期完全一致,但后续执行过程中会因各自的操作而逐渐产生差异。

6.4 Hello的execve过程

当子进程调用execve()系统调用时,操作系统会彻底重构该进程的用户空间。首先,原有的代码段、数据段、堆栈等用户区域会被完全清除。随后,内核通过虚拟内存机制将新可执行文件hello的各个段(如代码段、数据段)精确映射到进程地址空间的对应位置。同时,动态链接器ld-2.31.so等依赖的共享库也会以内存映射方式加载到进程空间。最终,处理器指令指针会跳转到hello程序的入口地址(通常是_start符号),从此开始执行新程序的指令流。这一过程彻底替换了进程的执行映像,仅保留原进程的PID和文件描述符等少量属性。

6.5 Hello的进程执行

进程调度是操作系统通过管理进程上下文和时间片实现多任务并发的核心机制。每个进程的运行都依赖于完整的上下文环境,包括内存中的代码段、数据段、栈结构、寄存器状态以及打开的资源描述符等关键信息。这些上下文构成了进程执行的现场,必须保证在调度过程中不被破坏。

当系统通过时钟中断触发调度时,会引发精确的状态转换:正在CPU上执行的进程(如hello)会因硬件中断立即从用户态切换至内核态,此时处理器特权级提升,控制权移交操作系统。内核首先将当前进程的完整上下文保存至进程控制块(PCB),包括程序计数器、寄存器堆等关键状态。随后调度器根据时间片轮转算法选择就绪队列中的下一个进程(如进程B),将其保存的上下文重新载入CPU,完成状态恢复后切换至用户态继续执行。

时间片机制是调度公平性的保障,它限定了每个进程占用CPU的最长连续时段。当进程主动调用系统调用(如sleep)时,会通过陷阱指令触发从用户态到内核态的同步转换。内核处理sleep请求时会将进程置为阻塞状态,并启动计时器。在此期间,调度器会选取其他就绪进程投入运行。当计时器中断到达时,内核在中断处理流程中解除进程阻塞状态,待下次调度时恢复其上下文继续执行。这种基于中断驱动的状态保存与恢复机制,实现了多进程间的无缝切换,既保证了系统响应速度,又维持了各进程上下文的完整性。

6.6 hello的异常与信号处理

 hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。

程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps  jobs  pstree  fg  kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。

Hello执行过程中会出现中断(异步),陷阱,故障,终止异常

中断,陷阱总是返回到下一条指令

故障可能返回到当前指令

终止不会返回

(1)在程序正常运行时,打印10次提示信息,以输入回车为标志结束程序,并回收进程。

图6.1程序正常运行

(2)按下Ctrl-Z后显示进程暂停状态,暂停进程并转入后台。

图6.2Ctrl-Z

(3)按下Ctrl-C后直接终止hello进程。

图6.3Ctrl-C

(4)按下Ctrl-Z后输入jobs -l指令,查看当前的后台进程

图6.4jobs指令

(5)随后输入pstree指令可以显示进程及其父进程(如bash)的树状结构。

图6.5pstree指令

(6)利用fg指令使进程恢复运行,继续输出Hello信息。

图6.6fg指令

(7)直接终止进程

图6.7kill指令

  1. 乱按

可以看出输入字符有效,对于程序运行没有影响。

图6.8乱按

6.7本章小结

本章深入探讨了hello程序的进程管理机制,系统性地阐述了进程的核心概念及其在计算机系统中的重要作用。通过分析shell环境的工作原理和处理流程,揭示了命令行程序的执行机制。研究重点剖析了hello程序运行过程中涉及的fork系统调用实现进程创建的完整过程,以及execve函数进行程序映像替换的关键细节。同时,本章还全面分析了程序执行期间可能触发的各类异常情况,包括中断、陷阱和故障等,并对相应的信号处理机制进行了详细说明,完整呈现了从进程创建、执行到异常处理的完整生命周期管理。

7hello的存储管理

7.1 hello的存储器地址空间

在hello程序的执行过程中,地址转换涉及四个层次的关键概念。逻辑地址是程序直接使用的段内偏移地址,如hello.c编译后生成的函数和变量地址,它由段选择符和偏移量共同构成。线性地址作为中间层,是通过将逻辑地址的偏移量与段基址相加得到的过渡形式。

操作系统通过虚拟内存机制为hello程序提供虚拟地址空间,使得程序可以透明地使用连续的内存地址,而无需关心物理内存的实际分布情况。例如,main函数中的指令地址就是虚拟地址的具体表现。最终,当程序实际访问内存时,虚拟地址会通过MMU转换为物理地址,这是真正在内存总线上传输的地址信号,对应着DRAM芯片中的具体存储单元。

物理地址直接对应硬件层面的内存访问,无论是读取指令还是写入数据,CPU都通过物理地址与内存芯片交互。这种多级地址转换机制,既保证了程序的内存访问安全性,又实现了物理内存的高效共享和管理。在hello程序从编译到运行的整个生命周期中,这些地址概念共同协作,完成了从源代码到硬件执行的无缝转换。

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

在x86架构的段式内存管理机制中,CPU通过"段选择符:偏移地址"形式的逻辑地址进行寻址。具体转换过程如下:16位的段选择符用于索引全局描述符表(GDT)或局部描述符表(LDT),从中获取对应的段描述符。该描述符包含了段的基地址、界限和访问权限等关键信息。CPU将段基地址与32位偏移地址相加,最终生成线性地址。需要特别指出的是,GDT和LDT的建立与管理完全由操作系统内核负责,这包括段的划分、权限设置以及表的维护等工作,从而确保内存访问的安全性和隔离性。这种段式管理机制构成了保护模式下内存寻址的基础层。

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

现代操作系统通过分页机制管理虚拟地址空间,将地址空间划分为固定大小的页。CPU在地址转换时,首先提取线性地址的高位作为页表索引,通过查询多级页表结构(如x86-64的四级页表)定位到对应的页表项。该页表项中存储了目标物理页框的起始地址,CPU将此物理页基址与线性地址的低位(页内偏移量)相加,最终得到实际的物理地址。这一转换过程由内存管理单元(MMU)硬件加速完成,操作系统负责维护页表内容,包括设置权限位、修改映射关系等核心功能,从而实现了虚拟内存到物理内存的高效安全映射。分页机制不仅支持灵活的内存分配,还通过页表项中的权限控制位实现了内存保护。

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

现代处理器通过TLB(Translation Lookaside Buffer)加速虚拟地址到物理地址的转换。当CPU生成虚拟地址后,MMU首先查询TLB获取页表项(PTE)。若命中,则立即完成地址转换;若未命中(TLB缺失),则需通过多级页表机制进行转换。

x86-64架构采用四级页表结构,将虚拟页号(VPN)划分为四个索引字段。MMU依次查询各级页表:

1. 使用VPN1索引PGD(页全局目录)

2. 使用VPN2索引PUD(页上级目录)

3. 使用VPN3索引PMD(页中间目录)

4. 使用VPN4索引PT(页表)

最终获取物理页号(PPN)后,与页内偏移量组合形成物理地址。多级页表通过按需分配页表结构,显著减少了内存占用,而TLB则通过缓存热点PTE来提升转换效率,二者协同工作实现了高效的内存地址转换。

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

现代计算机系统通过高速缓存(Cache)机制优化物理内存访问性能,其工作流程可分为四个关键步骤:

首先,处理器根据虚拟地址中的组索引位确定目标缓存组,将二进制索引转换为无符号整数定位具体组位置。接着进行行匹配操作,将地址标记位与组内所有缓存行的标记位逐一比对,当发现有效位为1且标记匹配时即触发缓存命中。命中后,系统利用块偏移位精确定位数据块中的目标字节,完成数据读取并返回CPU处理。

若发生缓存未命中,系统将启动补救机制:从下级存储层次(如主存)提取目标数据块,并根据放置策略将其存入缓存。对于组内存在空闲行的情况直接写入;当组已满时则触发替换机制,通常采用最近最少使用(LRU)等算法淘汰旧数据。这种分层缓存体系通过空间局部性和时间局部性原理,显著提升了内存访问效率。

7.6 hello进程fork时的内存映射

当进程调用fork()系统调用时,操作系统会执行以下精细的进程复制操作:

1. 内核首先为新进程构建完整的进程控制结构,包括分配唯一的进程标识符(PID);

2. 采用写时复制(COW)技术高效复制父进程的虚拟内存空间:

   - 完整复制mm_struct内存管理结构

   - 复制所有虚拟内存区域(vma)描述符

   - 建立与父进程相同的页表结构

3. 将所有共享内存页标记为只读权限;

4. 将所有虚拟内存区域标记为私有COW属性;

在fork()返回时,子进程拥有与父进程调用fork()时刻完全一致的虚拟内存视图。这种设计的关键优势在于:

- 初始阶段仅复制元数据,物理内存页保持共享

- 当任一进程尝试写入共享页时触发页错误异常

- 内核捕获异常后分配新物理页,执行实际复制

- 更新页表项,恢复进程的写入权限

这种写时复制机制实现了两个重要目标:

1. 极大优化了fork性能,避免不必要的内存拷贝

2. 确保进程间内存空间的隔离性,维护私有地址空间抽象

整个过程对应用程序完全透明,程序员无需关心底层的内存复制细节,只需理解fork()后两个进程将拥有独立但初始内容相同的地址空间。

7.7 hello进程execve时的内存映射

execve函数通过内核加载器在当前进程空间内加载并执行目标程序hello,其核心操作流程如下:

首先,内核会彻底清理进程现有的用户空间:解除所有原用户区域的虚拟内存映射,释放相关数据结构。随后建立新的内存布局:为程序的代码段(.text)、初始化数据(.data)、未初始化数据(.bss)以及运行时栈等区域创建私有且支持写时复制的映射。其中代码段和数据区直接映射到hello文件的对应节区,.bss和堆栈则初始化为零填充的匿名映射。

对于动态链接场景,内核会将共享库(如libc.so)映射到进程的共享内存区域,确保所有依赖库都能被正确加载。最后,内核重置进程的执行上下文,将指令指针设置为程序入口点(通常是_start符号地址),完成从旧进程到新程序的平滑转换。这种机制既保证了进程资源的彻底重置,又维持了高效的执行环境切换。

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

现代计算机系统的虚拟内存管理采用硬件与操作系统协同工作的机制。当处理器生成虚拟地址后,内存管理单元(MMU)会自动查询页表项(PTE),这一过程完全由硬件电路实现。若PTE有效位为1,则直接完成地址转换,此过程称为页面命中,全程无需操作系统介入。

当发生缺页异常时,系统进入硬件与内核协作处理流程:

1. MMU检测到无效PTE后触发缺页异常

2. CPU控制权移交操作系统缺页处理程序

3. 内核执行页面置换算法选择牺牲页

4. 若牺牲页被修改则写回磁盘

5. 从磁盘调入目标页面至物理内存

6. 更新页表项并设置有效位

7. 异常返回后重新执行触发指令

该机制通过硬件加速常规地址转换,同时利用操作系统处理异常情况,既保证了内存访问的高效性,又实现了虚拟内存的透明管理。最终,当指令重新执行时,虚拟地址能够顺利转换,完成预期的内存访问操作。

7.9动态存储分配管理

动态内存分配器管理着进程虚拟地址空间中的堆区域。在典型的内存布局中,堆是一个从.bss段末尾开始向上扩展的匿名内存区域,内核通过brk指针标记堆空间的当前顶端边界。

分配器将堆空间组织为离散的内存块集合,每个块代表一段连续的虚拟地址范围,其状态分为:

1. 已分配块:明确分配给应用程序使用的内存区域

2. 空闲块:可供分配使用的可用内存区域

内存块的生命周期遵循严格的状态转换规则:空闲块仅在被显式分配时转为已分配状态,而已分配块必须通过显式释放或自动回收机制才能重新变为空闲块。

现代分配器主要分为两大类型:

显式分配器(如malloc/free)要求程序员手动管理内存释放,这种方案控制精准但易引发内存泄漏。隐式分配器(垃圾回收器)通过引用计数、标记-清除等算法自动识别并回收不可达内存块,虽然减轻了程序员负担,但需要额外的运行时开销来跟踪对象引用关系。两种设计哲学在内存安全性和执行效率之间形成了典型权衡。

7.10本章小结

本章系统性地阐述了计算机存储管理的核心机制与实现策略。首先深入剖析了存储器地址空间的组织结构,详细讲解了Intel架构下从逻辑地址到线性地址的段式转换机制,以及线性地址到物理地址的页式映射原理。在地址转换优化方面,重点分析了TLB加速技术和四级页表结构如何协同完成虚拟地址到物理页的高效转换,并探讨了三级缓存体系对物理内存访问的性能优化。

在进程管理层面,详细解读了fork操作采用的写时复制机制和execve过程的内存映射重构策略。针对存储异常处理,深入阐述了缺页故障的触发条件与中断处理流程。最后,系统介绍了动态存储分配管理的两种范式:显式分配器的精确控制与隐式垃圾回收的自动化管理。

这些内容全面揭示了程序存储管理的底层原理,包括地址转换机制、内存映射策略、异常处理流程以及动态分配方案,为理解现代操作系统如何高效安全地管理存储器资源提供了完整的理论框架和实践指导。

8hello的IO管理

8.1 Linux的IO设备管理方法

现代操作系统采用统一的文件抽象模型来管理各类I/O设备,这一创新性设计将物理设备与标准文件操作完美融合。具体实现包含两个关键层面:

1. 设备抽象化机制

操作系统将网络接口、磁盘驱动器、终端设备等所有I/O资源都抽象为特殊的文件对象,每个设备文件都具有标准的文件属性(如inode、权限位等)。通过这种抽象,系统实现了:

- 统一的命名空间管理

- 标准化的访问控制

- 一致的读写语义

2. Unix I/O接口规范

内核基于文件抽象层提供的系统调用接口具有以下特性:

- 原子性操作保障(open/read/write/close)

- 同步/异步访问模式支持

- 设备无关的编程模型

- 统一的错误处理机制

这种设计哲学使得应用程序无需关心底层设备差异,通过标准文件操作即可实现:

- 磁盘文件的顺序/随机访问

- 网络套接字的数据收发

- 终端设备的输入输出

- 特殊设备的控制命令传递

该架构不仅简化了系统设计,还大幅提升了I/O子系统的可扩展性,新设备的接入只需实现对应的文件操作接口即可融入现有体系。这种"一切皆文件"的设计理念已成为Unix-like系统的核心特征之一。

8.2 简述Unix IO接口及其函数

Unix I/O 接口采用统一文件模型来管理所有输入输出操作,通过一组简洁而强大的系统调用实现对各类设备的访问控制。其核心函数包括 open()用于创建或打开文件描述符、read()/write()执行基础数据读写、close()释放文件资源,以及 lseek()实现随机访问定位。这些系统调用为磁盘文件、终端设备、网络套接字等提供了统一的访问抽象,所有I/O操作都通过文件描述符这个整型句柄来完成。特别地,ioctl()函数为设备特定操作提供了扩展接口,而fcntl()则支持描述符属性的动态配置。该设计通过将设备抽象为文件,使得应用程序无需关心底层硬件差异,仅通过标准文件操作就能处理包括块设备、字符设备和网络设备在内的各类I/O请求,这种"一切皆文件"的哲学既保证了接口的简洁性,又确保了系统的可扩展性,成为Unix系统持久影响力的关键特征之一。

8.3 printf的实现分析

在printf函数的实现中,其核心工作流程展现了用户态到内核态的完整I/O处理链条。该函数首先通过可变参数机制收集格式化参数,调用vsprintf进行字符串解析和格式化处理,这个关键步骤不仅完成格式转换,还精确计算输出字符串长度。随后调用write系统调用触发内核态执行,此时CPU通过中断指令(如x86的int 0x80或syscall)实现特权级切换,将控制权移交操作系统。

在内核层面,字符输出驱动负责将ASCII编码转换为显示设备可识别的字模数据,这个转换过程需要查询预先存储的字形库。转换后的像素信息被写入视频存储器(VRAM),其中每个像素点的RGB色彩值都被精确记录。显示控制器则按照固定刷新频率(如60Hz)自动扫描VRAM内容,通过视频信号线将逐像素的色彩数据实时传输至显示设备,最终完成视觉信息的呈现。这个从格式化字符串到物理像素显示的完整流水线,体现了计算机系统软硬件协同工作的精妙设计。

8.4 getchar的实现分析

getchar()函数实现了一个从标准输入流同步读取字符的完整处理链条。当这个库函数被调用时,它最终会通过read系统调用进入内核态,等待用户输入。在硬件层面,键盘控制器负责将物理按键动作转化为扫描码,并触发处理器中断机制。键盘中断服务程序作为异步事件的处理核心,首先从键盘接口寄存器获取扫描码,经过转码表将其转换为ASCII字符,并存入输入缓冲区。值得注意的是,getchar()采用行缓冲模式,只有在检测到回车键输入时才会将整行字符从内核缓冲区返回用户空间,这种设计既减少了系统调用次数,又符合终端交互的自然特性。整个处理过程涉及硬件中断响应、内核驱动处理、系统调用转换以及用户态缓冲管理等多个层级的协同工作,展现了操作系统对输入设备管理的典型实现方式。

8.5本章小结

输入输出操作表面简单,实则蕴含着复杂的系统级交互过程。从应用程序发起I/O请求到最终设备响应,整个流程涉及用户态与内核态的多次切换、硬件中断处理、设备驱动调度等多个关键环节。这种跨层级的协作机制虽然为程序员提供了简洁的接口抽象,但在执行效率方面存在固有开销,常常成为程序性能的瓶颈所在。同时,由于需要协调软件栈各层级和硬件设备的时序关系,I/O操作也成为系统稳定性的关键风险点,任何环节的异常都可能导致整个流程失败。这就要求开发者在实现功能时,必须深入理解不同I/O函数的内在特性,根据具体场景审慎选择同步/异步、缓冲/非缓冲等机制,在功能需求与性能可靠性之间取得平衡。这种对I/O操作的精确把控,往往能显著提升程序的健壮性和执行效率。

结论

1. 程序编译与链接

预处理:展开 #include 头文件,处理宏定义,生成 hello.i。

编译:将 C 代码(hello.i)翻译为汇编指令(hello.s),包含 main、printf、sleep 等函数的低级实现。

汇编:将汇编代码转换为机器码(hello.o),生成可重定位目标文件,包含逻辑地址和未解析的符号(如 printf)。

链接:动态链接器(ld)合并 hello.o 和 libc.so,解析符号地址,生成可执行文件 hello,并标记动态链接依赖。

2. 进程创建与加载

shell 解析命令:输入 ./hello 后,shell 调用 fork() 创建子进程,子进程通过 execve() 加载 hello。

fork():复制父进程(shell)的虚拟内存空间(写时复制),分配新 PID。

execve():清除原进程空间,按 ELF 格式加载 hello 的代码段(.text)、数据段(.data)、BSS 段(清零),映射共享库(如 libc.so),设置入口点为 _start。

动态链接:若首次运行,加载器 (ld.so) 解析 printf、sleep 等函数在 libc.so 中的实际地址,填充 PLT/GOT 表。

3. 程序执行与系统交互

CPU 执行流程:

_start:初始化栈帧,调用 __libc_start_main。

main 函数:

检查 argc,若参数不足,调用 printf 输出提示并触发 exit(1)。

循环 10 次:调用 printf 打印参数,通过 atoi 转换参数为整数,调用 sleep 挂起进程。

调用 getchar() 等待输入。

系统调用:

printf 通过 write 系统调用(syscall)将缓冲区数据写入标准输出(文件描述符 1)。

sleep 通过 nanosleep 系统调用挂起进程,触发进程调度。

getchar 通过 read 系统调用(文件描述符 0)等待键盘输入。

4. 硬件与内核协作

地址转换:

逻辑地址(main 中的指令地址)→ 线性地址(段式管理)→ 物理地址(MMU 查询页表,TLB 加速)。

若缺页,内核分配物理页,可能触发页面置换(如将旧页写入交换区)。

I/O 处理:

终端输出:write 系统调用将数据从用户缓冲区拷贝至内核,字符设备驱动将 ASCII 码转换为字模,写入显存(VRAM),由显卡逐帧渲染。

键盘输入:按键触发中断,键盘驱动将扫描码转为 ASCII 码,read 系统调用从内核缓冲区返回字符。

5. 进程终止

main 返回:返回值 0 传递给 __libc_start_main。

资源释放:内核关闭打开的文件描述符,释放内存页,撤销进程结构。

父进程(shell)回收:通过 wait() 获取子进程状态,显示新的提示符。

最初编写hello程序时,只觉得它不过是一个简单的打印语句,对计算机而言理应轻而易举。然而通过这次深入的剖析,我才惊觉这短短一行文字背后竟隐藏着如此精密的系统级协作 - 从预处理展开宏定义到编译器生成中间代码,从链接器解析符号到加载器构建内存映像,从CPU流水线执行到系统调用处理,最后经由显卡驱动完成像素渲染。每一个环节都像瑞士钟表里的齿轮般严丝合缝,让我深刻体会到计算机科学中"简单源于复杂"的哲学智慧。这种认识彻底改变了我对编程的认知维度:今后无论是开发应用程序还是研究系统优化,我都会铭记此刻的震撼,以体系结构的思维审视每一行代码的生命周期,在抽象与实现之间寻找创新的可能。计算机系统的精妙之处,正在于用层层精密的工程实现,最终呈现出令人惊叹的简洁之美。


附件

原始代码hello.c

预处理后的代码hello.i

编译后的汇编语言代码hello.s

可重定位目标文件hello.o

可执行文件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.

相关文章:

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

摘 要 文章以C语言程序设计经典案例hello.c为研究对象&#xff0c;系统解析程序在计算机系统中的完整生命周期。剖析源代码通过预处理、编译、汇编、链接四阶段演化为可执行目标程序的编译系统工作机制&#xff0c;继而从进程视角揭示程序运行时计算机体系结构的协同运作&…...

小程序定制开发:从需求到落地,打造企业专属数字化入口

在移动互联网时代&#xff0c;小程序已成为企业连接用户的核心载体。定制开发因能深度匹配企业需求&#xff0c;正成为各行业数字化转型的优选方案。以下从优势、流程、技术、案例四方面展开&#xff0c;助你快速掌握关键要点。 一、定制开发的核心优势 1. 高度个性化&#x…...

【C/C++】基于 Docker 容器运行的 Kafka + C++ 练手项目

文章目录 基于 Docker 容器运行的 Kafka C 练手项目1 项目目的2 项目框架3 代码4 编译运行5 功能与接口说明5.1 Producer 接口&#xff1a;producer.cpp关键调用流程参数说明 5.2 Consumer 接口&#xff1a;consumer.cpp关键调用流程消费流程中注意 5.3 工程技术点 基于 Docke…...

Linux系统管理与编程24:基础条件准备-混搭“本地+阿里云”yum源

兰生幽谷&#xff0c;不为莫服而不芳&#xff1b; 君子行义&#xff0c;不为莫知而止休。 1.添加宿主机共享文件夹 Linux虚拟机可以和宿主机共享文件夹&#xff0c;这样有利于工具文件的共享。具体操作如下&#xff1a; 1&#xff09;vmware workstation共享文件夹 虚拟机…...

新一代Python管理UV完全使用指南|附实际体验与效果对比

简介 uv是新一代的Python项目管理工具&#xff0c;具备开发一个完整项目的所有功能点&#xff1a; 功能点描述包管理完全替代pip的功能&#xff0c;支持包的安装、升级、卸载等操作虚拟环境管理内置虚拟环境创建和管理&#xff0c;无需额外安装virtualenv或venv依赖解析与锁定…...

如何在 Windows 10 PC 上获取 iPhone短信

您可以轻松地将媒体数据从 iPhone 传输到 Windows 计算机&#xff0c;并直接访问计算机上的数据。但是&#xff0c;您可以在 Windows 10 PC 上接收 iPhone 短信吗&#xff1f;有什么功能或工具支持它吗&#xff1f;如果您发现在 Windows 10 PC 上接收 iPhone 消息很困难&#x…...

STM32程序运行不了,仿真功能也异常,连断点和复位都异常了

先检查有没有出现复位引脚rst短接0的情况 在检查是否出现明明没配置该外设你却偏偏要使用的情况&#xff0c;比如串口没配置你却偏要发送&#xff0c;引脚没配置你却偏要读取 这几个可能最好的办法就是从开头一行一行注释再运行看看能不能跑起来 还可以用以下方法 检查硬…...

Linux 系统中的软链接与硬链接

目录 一、什么是软链接&#xff1f; 1. 创建软链接 2. 软链接的特性 3. 软链接的用途 二、什么是硬链接&#xff1f; 1. 创建硬链接 2. 硬链接的特性 3. 硬链接的用途 4. 目录硬链接的特殊性 ​编辑 三、软链接与硬链接的区别 1. inode 编号 2. 路径依赖 3. 删除行…...

Python爬虫第22节- 结合Selenium识别滑动验证码实战

目录 一、引言 二、滑动验证码原理与反爬机制 2.1 验证码原理 2.2 反爬机制 三、工程实战&#xff1a;滑动验证码识别全流程 3.1 工程准备 3.1.1 环境依赖 3.1.2 目标网站与验证码识别案例 3.2 核心破解流程 3.2.1 自动化打开网页与登录 3.2.2 获取验证码图片&#…...

【C/C++】chrono简单使用场景

chrono使用场景举例 1 输出格式化字符串 示例代码 auto now std::chrono::system_clock::now(); auto t std::chrono::system_clock::to_time_t(now); auto ms std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000;std::ostrin…...

Escrcpy(安卓手机投屏软件) v1.29.6 中文绿色版

在数字设备日益普及的今天&#xff0c;用户对于设备的控制和管理需求也在不断增加。对于Android设备用户来说&#xff0c;Escrcpy这款强大的工具无疑是一个福音。它不仅提供了直观的图形化界面&#xff0c;让用户能够轻松显示和控制自己的Android设备&#xff0c;还以完全免费开…...

Oracle MOVE ONLINE 实现原理

Oracle MOVE ONLINE 实现原理 Oracle 的 MOVE ONLINE 操作是一种在线重组表的技术&#xff0c;允许在不中断业务的情况下重新组织表数据。以下是其实现原理的详细分析&#xff1a; 基本概念 MOVE ONLINE 是 Oracle 12c 引入的特性&#xff0c;用于替代传统的 ALTER TABLE ..…...

Linux:深入理解网络层

网络层在复杂的网络环境中确定一个合适的路径.传输到指定的网络中 一、网络层的理解 问题1&#xff1a;为什么要有网络层的概念呢&#xff1f;&#xff1f; ——>我们先来讲一个故事&#xff1a; 假设我在学校里被誉为数学大神&#xff0c;是因为我的数学有考满分的能力&…...

【设计模式】简单工厂模式,工厂模式,抽象工厂模式,单例,代理,go案例区分总结

工厂模式三种类型&#xff1a; 一、简单工厂模式&#xff08;Simple Factory&#xff09; 定义&#xff1a; 用一个工厂类&#xff0c;根据传入的参数决定创建哪一种具体产品类实例。 面试说法&#xff1a; 由一个统一的工厂创建所有对象&#xff0c;增加新产品时需要修改工…...

Linux_编辑器Vim基本使用

✨✨ 欢迎大家来到小伞的大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;LInux_st 小伞的主页&#xff1a;xiaosan_blog 制作不易&#xff01;点个赞吧&#xff01;&#xff01;谢谢喵&#xff01;&a…...

vue展示修改前后对比,并显示修改标注diff

动态父组件 <template><el-buttontype"primary"size"small"plainclick"showDiffDialog(subItem)">查看修改内容</el-button><TextDiffDialogv-model:visible"diffDialogVisible":before"currentDiffItem?.…...

LiveWallpaperMacOS:让你的 Mac 桌面动起来

随着桌面美化需求的不断提升,用户对于桌面壁纸的要求已经不再局限于静态图片。越来越多的 Mac 用户希望桌面能像 Windows 一样,拥有动态壁纸,展现个性、提升体验。LiveWallpaperMacOS 正是这样一款让你的 Mac 桌面焕发活力的开源项目。 本文将详细介绍 LiveWallpaperMacOS …...

[预训练]Encoder-only架构的预训练任务核心机制

原创文章1FFN前馈网络与激活函数技术解析&#xff1a;Transformer模型中的关键模块2Transformer掩码技术全解析&#xff1a;分类、原理与应用场景3【大模型技术】Attention注意力机制详解一4Transformer核心技术解析LCPO方法&#xff1a;精准控制推理长度的新突破5Transformer模…...

07-后端Web实战(部门管理)

5. 修改部门 对于任何业务的修改功能来说&#xff0c;一般都会分为两步进行&#xff1a;查询回显、修改数据。 5.1 查询回显 5.1.1 需求 当我们点击 "编辑" 的时候&#xff0c;需要根据ID查询部门数据&#xff0c;然后用于页面回显展示。 5.1.2 接口描述 参照参照…...

mysql ACID 原理

序言&#xff1a;ACID 是一组数据库设计原则&#xff0c;他是业务数据和关键业务程序的可靠性保障。 1、atomicity&#xff08;原子性&#xff09; 依赖如下能力 autocommit commit rollback2、一致性 2.1 double write buffer 1、定义&#xff1a;double write buffer 是…...

[Rust_1] 环境配置 | vs golang | 程序运行 | 包管理

目录 Rust 环境安装 GoLang和Rust 关于Go 关于Rust Rust vs. Go&#xff0c;优缺点 GoLang的优点 GoLang的缺点 Rust的优点 Rust的缺点 数据告诉我们什么&#xff1f; Rust和Go的主要区别 (1) 性能 (2) 并发性 (3) 内存安全性 (4) 开发速度 (5) 开发者体验 Ru…...

二十五、面向对象底层逻辑-SpringMVC九大组件之HandlerMapping接口设计

一、引言&#xff1a;MVC架构的交通枢纽 在Spring MVC框架中&#xff0c;HandlerMapping接口扮演着"请求导航仪"的关键角色&#xff0c;它决定了HTTP请求如何被路由到对应的Controller处理器。作为MVC模式的核心组件之一&#xff0c;HandlerMapping在请求处理的生命…...

构建安全高效的邮件网关ngx_mail_ssl_module

一、快速上手&#xff1a;最小配置示例 worker_processes auto;mail {server {# 监听 IMAP over TLSlisten 993 ssl;protocol imap;# TLS 协议与密码套件ssl_protocols TLSv1.2 TLSv1.3;ssl_ciphers HIGH:!aNULL:!MD5;# 证书与私钥ssl_…...

HUAWEI交换机配置镜像口验证(eNSP)

技术术语&#xff1a; 流量观察口&#xff1a;就是我们常说的镜像口&#xff0c;被观察的流量的引流目的端口 流量源端口&#xff1a;企业生产端口&#xff0c;作为观察口观察对象。 命令介绍&#xff1a; [核心交换机]observe-port [观察端口ID或编号&#xff08;数字&am…...

前端vue3实现图片懒加载

场景和指令用法 场景:电商网站的首页通常会很长&#xff0c;用户不一定能访问到页面靠下面的图片&#xff0c;这类图片通过懒加载优化手段可以做到只有进入视口区域才发送图片请求 核心原理:图片进入视口才发送资源请求 首先&#xff1a;我们需要定义一个全局的指令&#x…...

网站每天几点更新,更新频率是否影响网站收录

1. 每天几点更新网站最合适&#xff1f;总怕时间选错影响收录&#xff1f; 刚开始搞网站的时候&#xff0c;是不是老纠结啥时候更新合适&#xff1f;早上刚上班&#xff1f;半夜没人的时候&#xff1f;选不对时间&#xff0c;总担心搜索引擎爬虫来了没抓到新内容&#xff0c;影…...

主流Markdown编辑器的综合评测与推荐

根据2025年最新资料&#xff0c;结合功能特性、用户体验和技术适配性&#xff0c;以下是对主流Markdown编辑器的综合评测与推荐&#xff1a; 一、核心对比维度与评估方法 功能完整性&#xff1a;支持数学公式、流程图、代码高亮等复杂格式。跨平台兼容性&#xff1a;Windows/m…...

计算机网络-MPLS VPN应用场景与组网

上一篇文章我们通过一个基础实验实现了企业分支间的MPLS VPN互联&#xff0c;如果还不理解的可以多看几遍前面的文章或者多敲下实验。今天来学习几种常见的MPLS VPN应用场景与这些场景下MPLS VPN的部署方法。 一、MPLS VPN典型应用 目前&#xff0c;MPLS VPN的主要应用包括企…...

AugmentFree:解除 AugmentCode 限制的终极方案 如何快速清理vscode和AugmentCode缓存—windows端

AugmentFree1.0工具包&#xff1a;解除 AugmentCode 免费试用限制的终极方案 Augment VIP 是一个专为 VS Code 用户设计的实用工具包&#xff0c;旨在帮助用户管理和清理 VS Code 数据库&#xff0c;解除 AugmentCode 免费试用账户的限制。 augment从根源上解决免费额度限制问…...

WPF【11_7】WPF实战-重构与美化(ViewModel的嵌套与分解、海量数据不要Join)

11-12 【重构】ViewModel的嵌套与分解 目前我们的代码中有一个不易发现的致命问题&#xff0c;如果工作中这样写代码大概率会被打回去重做。那么这个问题是什么呢&#xff1f; --\ViewModels\MainViewModel.cs 视图模型中的 LoadCustomers() 方法&#xff0c;考虑一下在这里我…...