程序人生-Hello’s P2P

[复制链接]
发表于 2025-6-2 21:26:01 | 显示全部楼层 |阅读模式




计算机科学与技能学院

2025年5月

摘要

本文以Hello程序为研究对象,深入探讨了计算机系统的核心概念和运行机制 。通过对Hello程序从源代码到实行的完备生命周期分析,展示了计算机系统各条理的协同工作过程 。文章具体阐述了Hello程序的P2P(从Program到Process)和O2O(从Zero到Zero)过程,包罗预处理、编译、汇编、链接等编译系统阶段,以及历程管理、存储管理和I/O管理等操作系统功能 。 研究表明,纵然是简单的Hello程序,也涉及计算机系统的方方面面,从编译系统的源代码转换,到操作系统的历程创建与调理,再到硬件系统的指令实行与内存访问 。通过对Hello程序的深入分析,不但可以理解计算机系统的基本工作原理,还能体会到各个系统条理之间的精密接洽和协同工作机制 。 本研究对于理解计算机系统的整体架构和工作流程具有重要意义,为深入学习计算机系统提供了一个生动而全面的案例 。
关键词:Hello程序;计算机系统;编译系统;操作系统;历程管理;存储管理;I/O管理
      
     






目  录


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


ICS大作业论文

第1章 概述

1.1 Hello简介

Hello程序是计算机系统学习中的经典案例,它代码简短,功能简单,但却蕴含了计算机系统的核心概念和运行机制 。根据Hello的自白,它经历了从源代码到可实行程序,再到历程实行的完备生命周期,这个过程涵盖了计算机系统的各个层面 。
Hello的P2P(Program to Process)过程描述了从静态程序到动态历程的转变 。Hello以源代码形式(hello.c)存在,通过编辑器输入到计算机中 。随后,它经历了预处理、编译、汇编和链接等阶段,最终生成可实行文件 。当用户在Shell中实行这个程序时,操作系统为其创建历程(Process),分配资源,并在CPU上实行指令 。这一过程涉及了编译系统(预处理器、编译器、汇编器、链接器)和操作系统(历程管理、内存管理、I/O管理)的协同工作 。
Hello的O2O(Zero to Zero)过程则描述了程序从无到有再到无的完备生命周期 。Hello最初只是程序员脑海中的一个想法,通过编码变成源文件,经过编译系统处理成为可实行文件,再由操作系统加载实行成为活跃的历程 。当程序实行完毕后,历程制止,资源被接纳,Hello回归到"无"的状态 。这个循环显现了计算机系统中程序的完备生命历程 。
Hello固然简单,但它的实行过程涉及了计算机系统的方方面面:从编译系统的源代码转换,到操作系统的历程创建与调理,再到硬件系统的指令实行与内存访问 。通过对Hello的深入分析,我们可以窥见计算机系统的整体架构和工作原理 。
1.2 环境与工具

本研究接纳了以下软硬件环境和工具来深入分析Hello程序的实行过程 。
硬件环境


  • 处理器:Intel Core i7-10700K @ 3.80GHz
  • 内存:16GB DDR4
  • 存储:512GB SSD
软件环境


  • 操作系统:Ubuntu 22.04 LTS (64位)
  • 编译器:GCC 11.2.0
  • Shell:Bash 5.1.16
开发与调试工具


  • 文本编辑器:Visual Studio Code 1.76.0
  • 调试工具:GDB 12.1
  • 二进制分析工具:objdump 2.38, readelf 2.38
  • 历程监控监控工具:ps, top, htop
  • 内存分析工具:valgrind 3.18.1
  • 系统调用跟踪:strace 5.16
  • 文档编写工具:LibreOffice Writer 7.3.7
这些工具和环境为全面分析Hello程序从源代码到实行的各个阶段提供了所需的支持,使我们可以或许深入观察和理解计算机系统的工作机制 。
1.3 中间结果

在分析Hello程序的过程中,生成了以下中间结果文件:

  • hello.i:预处理后的源文件,包含展开的头文件和宏定义。
  • hello.s:编译后的汇编代码文件,展示了C代码转换为汇编指令的结果。
  • hello.o:汇编后的目的文件,包含机器码但尚未链接。
  • hello:链接后的可实行文件,可以直接运行。
  • hello_preprocessor.txt:纪录预处理命令和结果的文本文件。
  • hello_compiler.txt:纪录编译命令和结果的文本文件。
  • hello_assembler.txt:纪录汇编命令和结果的文本文件。
  • hello_linker.txt:纪录链接命令和结果的文本文件。
  • hello_objdump.txt:使用objdump工具分析目的文件和可实行文件的结果。
  • hello_readelf.txt:使用readelf工具分析ELF格式的结果。
  • hello_strace.txt:使用strace工具跟踪系统调用的结果。
  • hello_process.txt:纪录历程实行状态和资源使用环境的文本文件。
这些中间结果文件纪录了Hello程序从源代码到实行的各个阶段的具体信息,为深入理解计算机系统提供了宝贵的资料 。
1.4 本章小结

本章概述了Hello程序的基本环境、研究环境与工具,以及生成的中间结果文件 。通过Hello的自白,我们相识到它经历了从源代码到可实行程序,再到历程实行的完备生命周期,这个过程涵盖了计算机系统的各个层面 。
Hello程序固然简单,但其实行过程涉及编译系统、操作系统和硬件系统的协同工作 。通过对Hello的深入分析,我们可以窥见计算机系统的整体架构和工作原理,为后续各章节的具体讨论奠定了基础 。
接下来的章节中,我们将按照Hello程序从源代码到实行的顺序,依次探讨预处理、编译、汇编、链接、历程管理、存储管理和I/O管理等方面的内容,全面展示计算机系统的工作机制 。
      
     第2章 预处理

2.1 预处理的概念与作用

预处理是编译过程的第一个阶段,在实际编译开始之前对源代码举行文本操作 。预处理器(C语言中为cpp)读取源程序文件,根据以#开头的预处理指令对源代码举行修改,生成经过预处理的中间文件(通常以.i为扩展名) 。
预处理的重要作用包罗:

  • 头文件包含:将#include指令指定的头文件内容复制到当前文件中,进步了代码的模块化和可维护性 。
  • 宏定义与展开:处理#define指令定义的宏,在预处理阶段将源代码中的宏名更换为其定义的内容 。
  • 条件编译:根据#if、#ifdef、#ifndef、#else、#elif和#endif等条件预处理指令,选择性地包含或排除某些代码段 。
  • 解释删除:删除源代码中的解释,以减少后续编译阶段的处理量 。
  • 行号指示与文件名标识:添加行号和文件名标识,用于编译错误定位和调试 。
预处理阶段不举行语法检查,只举行文本更换和处理 。它为编译器提供了一个经过开端处理的、更加规范的源代码,简化了编译器的工作 。
在Hello程序中,预处理阶段重要处理了stdio.h、unistd.h和stdlib.h三个头文件的包含,使得Hello程序可以或许调用printf、getchar、sleep、exit和atoi等函数 。
2.2 在Ubuntu下预处理的命令

在Ubuntu系统中,可以使用GCC编译器的-E选项来实行预处理操作 。对于Hello程序,预处理命令如下:
   gcc -E -m64 -Og -no-pie -fno-stack-protector -fno-PIC hello.c -o hello.i
 
  命令的各部分寄义如下 :


  • gcc:GNU编译器集合
  • -E:只实行预处理,不举行编译、汇编和链接
  • -m64:生成64位代码
  • -Og:优化级别为调试
  • -no-pie:不生成位置无关可实行文件
  • -fno-stack-protector:禁用栈保护
  • -fno-PIC:不生成位置无关代码
  • hello.c:源代码文件
  • -o hello.i:指定输出文件名为hello.i
实行该命令后,会生成预处理后的文件hello.i 。
图2-1 在Ubuntu下实行预处理命令的截图

2.3 Hello的预处理结果解析

预处理后的hello.i文件内容庞大,因为它包含了stdio.h、unistd.h和stdlib.h三个头文件的全部内容 。下面是hello.i文件的部分内容分析:

  • 文件开头:包含了一些行号指示和文件名标识,用于编译错误定位和调试 。
   # 1 "hello.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "hello.c"
 
  

  • 头文件展开:stdio.h、unistd.h和stdlib.h的内容被完备地复制到预处理后的文件中 。
   # 1 "/usr/include/stdio.h" 1 3 4
/* ... stdio.h的内容 ... */
# 2 "hello.c" 2
# 1 "/usr/include/unistd.h" 1 3 4
/* ... unistd.h的内容 ... */
# 3 "hello.c" 2
# 1 "/usr/include/stdlib.h" 1 3 4
/* ... stdlib.h的内容 ... */
# 4 "hello.c" 2
 
  

  • 解释删除:源代码中的解释已被删除 。
  • 原始代码:在全部头文件内容之后,是原始hello.c中的main函数代码,但没有解释 。
   int main(int argc,char *argv[]){
 int i;
 if(argc!=5){
  printf("用法: Hello 学号 姓名 手机号 秒数!\n");
  exit(1);
 }
 for(i=0;i<10;i++){
  printf("Hello %s %s %s\n",argv[1],argv[2],argv[3]);
  sleep(atoi(argv[4]));
 }
 getchar();
 return 0;
}
 
  通过分析预处理结果,可以看到预处理器如何将多个源文件归并成一个完备的源文件,为后续的编译阶段做好预备 。
2.4 本章小结

本章具体介绍了预处理的概念、作用以及在Ubuntu系统中的具体实现 。预处理作为编译过程的第一个阶段,通过处理头文件包含、宏定义与展开、条件编译、解释删除等操作,将源代码转换为更加规范的中间文件,为后续的编译阶段做好预备 。
在Hello程序中,预处理阶段重要处理了stdio.h、unistd.h和stdlib.h三个头文件的包含,使得程序可以或许调用printf、getchar、sleep、exit和atoi等函数 。通过分析预处理结果,我们可以清晰地看到预处理器如何将多个源文件归并成一个完备的源文件,以及如何处理行号指示和文件名标识 。
预处理固然是编译过程中相对简单的一个阶段,但它为程序员提供了强大的代码构造和条件编译工具,大大进步了代码的模块化和可维护性 。
      
     第3章 编译

3.1 编译的概念与作用

编译是将预处理后的C代码(.i文件)转换为汇编代码(.s文件)的过程 。在编译系统中,编译器是核心组件,它负责将高级语言代码转换为特定目的机器的汇编语言代码 。
编译的重要作用包罗:

  • 语法分析:检查源代码是否符合编程语言的语法规则,并构建抽象语法树(AST) 。
  • 语义分析:检查程序的语义正确性,比方范例检查、作用域分析等 。
  • 中间代码生成:将源代码转换为与机器无关的中间表示形式(IR),以便于后续优化 。
  • 代码优化:对中间代码举行各种优化,如常量折叠、死代码消除、循环优化等,以进步程序的实行效率 。
  • 目的代码生成:将优化后的中间代码转换为特定目的机器的汇编代码 。
编译阶段不但检查程序的语法和语义错误,还将高级语言的抽象概念映射到底层的机器指令,同时通过优化进步程序的实行效率 。
在Hello程序中,编译阶段将预处理后的代码转换为x86-64架构的汇编代码 。编译器将main函数、if语句、for循环以及函数调用等高级语言结构转换为对应的汇编指令序列 。
3.2 在Ubuntu下编译的命令

在Ubuntu系统中,可以使用GCC编译器的-S选项来实行编译操作,生成汇编代码 。对于Hello程序,编译命令如下:
   gcc -S -m64 -Og -no-pie -fno-stack-protector -fno-PIC hello.i -o hello.s
 
  命令的各部分寄义如下 :


  • gcc:GNU编译器集合,用于编译C程序
  • -S:只实行编译,生成汇编代码
  • -m64:生成64位代码
  • -Og:优化级别为调试
  • -no-pie:不生成位置无关可实行文件
  • -fno-stack-protector:禁用栈保护
  • -fno-PIC:不生成位置无关代码
  • hello.i:预处理后的源代码文件
  • -o hello.s:指定输出文件名为hello.s
实行该命令后,会生成汇编代码文件hello.s 。
图3-1 在Ubuntu下实行编译命令的截图

3.3 Hello的编译结果解析

编译后生成的hello.s文件包含了x86-64架构的汇编代码 。下面是hello.s文件的重要部分分析:

  • 文件头部:包含了一些汇编器指令和解释,指示目的架构和源文件信息 。
        .file   "hello.c"
     .text
     .section    .rodata
 
  

  • 字符串常量:程序中使用的字符串常量被放在只读数据段(.rodata)中 。
   .LC0:
     .string "\347\224\250\346\263\225: Hello \345\255\246\345\217\267 \345\247\223\345\220\215 \346\211\213\346\234\272\345\217\267 \347\247\222\346\225\260\357\274\201"
.LC1:
     .string "Hello %s %s %s\n"
 
  这些是UTF-8编码的中文字符串,对应于程序中的"用法: Hello 学号 姓名 手机号 秒数!"和"Hello %s %s %s\n" 。

  • main函数:程序的主函数被转换为一系列汇编指令 。
        .globl  main
     .type   main, @function
main:
     pushq   %rbp
     movq    %rsp, %rbp
     subq    $32, %rsp
     movl    %edi, -20(%rbp)
     movq    %rsi, -32(%rbp)
     cmpl    $5, -20(%rbp)
     je .L2
     leaq    .LC0(%rip), %rdi
     call    puts@PLT
     movl    $1, %edi
     call    exit@PLT
.L2:
     movl    $0, -4(%rbp)
     jmp .L3
.L4:
     movq    -32(%rbp), %rax
     addq    $24, %rax
     movq    (%rax), %rcx
     movq    -32(%rbp), %rax
     addq    $16, %rax
     movq    (%rax), %rdx
     movq    -32(%rbp), %rax
     addq    $8, %rax
     movq    (%rax), %rax
     movq    %rax, %rsi
     leaq    .LC1(%rip), %rdi
     movl    $0, %eax
     call    printf@PLT
     movq    -32(%rbp), %rax
     addq    $32, %rax
     movq    (%rax), %rax
     movq    %rax, %rdi
     call    atoi@PLT
     movl    %eax, %edi
     call    sleep@PLT
     addl    $1, -4(%rbp)
.L3:
     cmpl    $9, -4(%rbp)
     jle .L4
     call    getchar@PLT
     movl    $0, %eax
     leave
     ret
 
  这段汇编代码实现了hello.c中main函数的功能,包罗设置栈帧、生存参数、检查参数数量、循环打印问候信息、休眠、等待用户输入以及返回等。
通过分析编译结果,可以看到编译器如何将C语言的高级结构转换为高效的汇编代码 。
图3-2 Hello编译结果的部分截图

3.4 本章小结

本章具体介绍了编译的概念、作用以及在Ubuntu系统中的具体实现 。编译作为将高级语言转换为汇编语言的关键阶段,涉及语法分析、语义分析、中间代码生成、代码优化和目的代码生成等多个步骤 。
在Hello程序中,编译阶段将预处理后的C代码转换为x86-64架构的汇编代码 。通过分析编译结果,可以清晰地看到编译器如何将高级语言结构映射到底层的汇编指令,以及如何处理字符串常量和函数参数 。
编译器的工作不但仅是举行代码转换,还包罗错误检查和代码优化 。通过对Hello程序编译过程的分析,我们对编译器的工作机制有了更加深入的理解 。
      
     第4章 汇编

4.1 汇编的概念与作用

汇编是将编译生成的汇编代码(.s文件)转换为机器码,并生成可重定位目的文件(.o文件)的过程 。汇编器(Assembler)负责将汇编语言指令翻译成机器可以直接实行的二进制代码 。
汇编的重要作用包罗:

  • 指令翻译:将汇编语言指令(如mov, add, call)转换为对应的机器码 。
  • 符号解析:将汇编代码中的符号(如标签、变量名)转换为相应的地址或偏移量 。
  • 重定位信息生成:纪录需要在链接阶段举行地址修正的指令和数据位置 。
  • 目的文件生成:将翻译后的机器码和相关信息构造成特定格式的目的文件(如ELF格式) 。
汇编阶段重要是一种直接的翻译过程,不举行代码优化 。它为后续的链接阶段做预备 。
在Hello程序中,汇编阶段将x86-64汇编代码转换为机器码,并生成ELF格式的可重定位目的文件hello.o 。该文件包含了程序的机器码、数据、符号表和重定位信息,但尚未办理外部符号引用题目 。
4.2 在Ubuntu下汇编的命令

在Ubuntu系统中,可以使用GCC编译器的-c选项来实行汇编操作,生成目的文件 。对于Hello程序,汇编命令如下:
   gcc -c -m64 -Og -no-pie -fno-stack-protector -fno-PIC hello.s -o hello.o
 
  命令的各部分寄义如下 :


  • gcc:GNU编译器集合
  • -c:只实行汇编,生成目的文件
  • -m64:生成64位代码
  • -Og:优化级别为调试
  • -no-pie:不生成位置无关可实行文件
  • -fno-stack-protector:禁用栈保护
  • -fno-PIC:不生成位置无关代码
  • hello.s:汇编代码文件
  • -o hello.o:指定输出文件名为hello.o
实行该命令后,会生成目的文件hello.o 。
图4-1 在Ubuntu下实行汇编命令的截图

4.3 可重定位目的ELF格式

ELF(Executable and Linkable Format)是Linux系统中用于可实行文件、目的文件、共享库和核心转储的标准文件格式 。
ELF文件的重要部分包罗:

  • ELF头(ELF Header):包含文件的基本信息,如文件范例、目的机器范例、入口点地址等 。
  • 程序头表(Program Header Table):描述可实行文件的段信息,用于程序加载 。
  • 节头表(Section Header Table):描述文件的各个节(Section)的信息,用于链接和调试 。
  • 节(Sections):文件的实际内容,包罗代码、数据、符号表、重定位信息等 。
常见的ELF节包罗.text(机器码)、.data(已初始化的全局和静态变量)、.bss(未初始化的全局和静态变量)、.rodata(只读数据)、.symtab(符号表)、.rel.text(代码段的重定位信息)、.rel.data(数据段的重定位信息)和.strtab(字符串表) 。
可重定位目的文件(如hello.o)是ELF格式的一种,它包含了代码和数据,但其中的地址引用尚未最终确定,需要在链接阶段举行重定位 。
图4-2 ELF文件格式结构图

4.4 Hello.o的结果解析

通过使用objdump和readelf等工具,可以分析hello.o文件的内容和结构 。

  • ELF头信息:readelf -h hello.o显示hello.o是一个64位的可重定位文件(Type: REL),目的架构是x86-64。
  • 节信息:readelf -S hello.o显示hello.o包含了.text、.rodata、.symtab等多个节 。
  • 符号表:readelf -s hello.o显示hello.o定义了main函数,并引用了puts、exit、printf、atoi、sleep和getchar等外部函数,这些外部函数的地址处于未定义(UND)状态 。
  • 重定位信息:readelf -r hello.o显示了需要在链接阶段修正的地址引用,比方,调用puts函数的指令位置需要填入其实际地址 。
  • 代码段:objdump -d hello.o显示的反汇编代码中,main函数已被转换为机器码,但调用外部函数的指令(如callq)后面的地址仍然是0,需要在链接阶段填入实际地址 。
通过分析hello.o文件,我们可以看到汇编阶段如何将汇编代码转换为机器码,并生成包含代码、数据、符号表和重定位信息的ELF格式目的文件 。
图4-3 Hello.o文件分析部分截图

4.5 本章小结

本章具体介绍了汇编的概念、作用以及在Ubuntu系统中的具体实现 。汇编器将汇编语言指令翻译成机器可以直接实行的二进制代码,并生成包含代码、数据、符号表和重定位信息的ELF格式目的文件 。
在Hello程序中,汇编阶段将编译生成的x86-64汇编代码转换为机器码,并生成hello.o文件 。通过分析hello.o文件的ELF头、节信息、符号表、重定位信息和代码段,我们可以清晰地看到汇编阶段的工作结果以及为后续链接阶段预备的各种信息 。
汇编阶段生成的目的文件结构复杂,包含了丰富的信息,为链接阶段提供了须要的支持 。
      
     第5章 链接

5.1 链接的概念与作用

链接是将汇编生成的目的文件(.o文件)与库文件组合起来,生成可实行文件的过程 。链接器(Linker)负责将多个目的文件和库文件组合成一个完备的可实行程序或共享库 。
链接的重要作用包罗 :


  • 符号解析:将每个符号引用(如函数调用、变量访问)与其定义关联起来。
  • 重定位:调解代码和数据的地址,使其在最终的可实行文件中具有正确的地址。
  • 归并段:将来自差别目的文件的类似范例的段归并成单一的段。
  • 库处理:从静态库或共享库中提取所需的函数和数据。
链接可以分为静态链接和动态链接两种方式 。静态链接将全部需要的库函数复制到可实行文件中,而动态链接在可实行文件中只保存对共享库的引用,在程序运行时才加载库代码 。
在Hello程序中,链接阶段将hello.o与C标准库和系统库链接起来,解析对printf、sleep、getchar等函数的引用,并生成可实行文件hello 。
5.2 在Ubuntu下链接的命令

在Ubuntu系统中,可以使用GCC编译器来实行链接操作 。对于Hello程序,链接命令如下:
   gcc -m64 -Og -no-pie -fno-stack-protector -fno-PIC hello.o -o hello
 
  这个命令的各部分寄义如下 :


  • gcc:GNU编译器集合
  • -m64:生成64位代码
  • -Og:优化级别为调试
  • -no-pie:不生成位置无关可实行文件
  • -fno-stack-protector:禁用栈保护
  • -fno-PIC:不生成位置无关代码
  • hello.o:目的文件
  • -o hello:指定输出文件名为hello
实行该命令后,会生成可实行文件hello 。
图5-1 在Ubuntu下实行链接命令的截图

5.3 可实行目的文件Hello的格式

可实行目的文件hello也是ELF格式,但与可重定位目的文件hello.o相比有以下区别 :


  • 文件范例:hello是可实行文件(EXEC),而hello.o是可重定位文件(REL)。
  • 程序头表:hello包含程序头表,用于程序加载。
  • 入口点:hello有明确的入口点地址,指示程序开始实行的位置。
  • 地址分配:hello中的代码和数据已分配了假造地址。
  • 符号解析:hello中的全部符号引用都已解析。
通过readelf和objdump等工具分析hello文件 :

  • ELF头信息:readelf -h hello显示hello是一个64位可实行文件(Type: EXEC),入口点地址为0x401040 。
  • 程序头表:readelf -l hello显示了程序的段信息,包罗代码段、数据段、动态链接信息等 。其中INTERP段指定了程序解释器/lib64/ld-linux-x86-64.so.2 。
  • 节信息:readelf -S hello更具体地描述了文件的各个部分,如.text、.data、.dynamic等 。
  • 符号表:readelf -s hello列出了程序使用的外部函数,如puts、printf、sleep等,它们都来自GLIBC 。
  • 动态链接信息:readelf -d hello指示了程序运行时需要加载的共享库(libc.so.6) 。
图5-2 Hello可实行文件分析部分截图


5.4 Hello的假造地址空间

每个历程都有自己的假造地址空间,通过内存管理单元(MMU)映射到物理内存 。Hello程序的假造地址空间包罗以下重要部分 :


  • 代码段(Text Segment):存放程序的机器码,通常是只读的。在hello文件中,代码段的起始地址为0x401040 。
  • 数据段(Data Segment):存放已初始化的全局变量和静态变量 。
  • BSS段(BSS Segment):存放未初始化的全局变量和静态变量,在程序加载时被初始化为0 。
  • 堆(Heap):用于动态内存分配,从低地址向高地址增长 。
  • 栈(Stack):用于函数调用和局部变量存储,从高地址向低地址增长 。
  • 内存映射地区(Memory Mapping Segment):用于映射文件或共享库到历程的地址空间 。
size hello命令可以查看hello文件的各个段的大小 。objdump -h hello可以查看各个段的具体信息,包罗它们在假造地址空间中的位置 。
图5-3 Hello假造地址空间表示图


5.5 链接的重定位过程分析

链接的一个关键任务是重定位,即调解代码和数据的地址 。重定位过程重要包罗以下步骤 :

  • 归并输入模块:将全部输入的目的文件和库文件中的类似范例的段归并。
  • 符号解析:将每个符号引用与其定义关联起来。
  • 重定位:根据符号的实际地址,修改代码中的地址引用。
在hello.o文件中,对外部函数的调用指令后面的地址是0,在链接后的hello文件中,这条指令被修改为指向PLT(Procedure Linkage Table)的入口,这是动态链接的一部分 。
重定位信息存储在hello.o文件的.rela.text段中,readelf -r命令可以查看这些信息,其中指示了需要修改的指令位置和符号名称 。
图5-4 Hello重定位过程部分截



5.6 Hello的实行流程

当用户在Shell中输入./hello命令时,实行流程如下 :

  • Shell解析命令:Shell解析用户输入的命令 。
  • 创建历程:Shell调用fork系统调用创建一个新历程 。
  • 加载程序:新历程调用execve系统调用,将hello程序加载到内存中 。
  • 动态链接:动态链接器加载程序依赖的共享库并解析动态符号 。
  • 初始化:实行程序的初始化代码 。
  • 实行main函数:程序从main函数开始实行 。
  • 程序制止:当main函数返回或调用exit函数时,程序制止 。
strace命令可以跟踪程序实行过程中的系统调用,如execve、write、nanosleep和read 。
图5-5 Hello实行流程表示图


5.7 Hello的动态链接分析

Hello程序使用动态链接,这可以减小可实行文件的大小并共享库代码 。ldd hello命令可以查看程序依赖的共享库,如libc.so.6和动态链接器ld-linux-x86-64.so.2 。
动态链接的工作原理如下 :

  • 延迟绑定:对外部函数的调用指向PLT(Procedure Linkage Table)中的一个入口,第一次调用时由动态链接器解析函数地址。
  • GOT(Global Offset Table):存储动态链接的符号地址。
  • 重定位:动态链接器在程序加载时或第一次调用函数时修改GOT中的地址。
objdump -d hello可以查看程序的PLT和GOT,其中对puts函数的调用首先跳转到PLT中的puts@plt入口,然后通过GOT间接跳转到puts函数的实际地址。
图5-6 Hello动态链接表示图


5.8 本章小结

本章具体介绍了链接的概念、作用以及在Ubuntu系统中的具体实现 。链接器负责符号解析、重定位、段归并和库处理等任务,将多个独立的部分组合成一个完备的程序 。
在Hello程序中,链接阶段将hello.o与C标准库和系统库链接起来,生成可实行文件hello 。通过分析hello文件的结构和内容,以及探讨其假造地址空间、重定位过程、实行流程和动态链接机制,我们深入理解了程序从静态文件到动态实行的整个过程 。
      
     第6章 Hello历程管理

6.1 历程的概念与作用

历程是操作系统中最基本的实行单元,是程序的一次实行实例 。每个历程都有自己独立的地址空间、程序计数器、寄存器集合以及其他资源 。
历程的重要作用包罗 :


  • 资源隔离:每个历程拥有独立的地址空间,保障了基本的安全性。
  • 并发实行:多个历程可以并发实行,进步系统资源的利用率。
  • 模块化设计:将复杂系统分解为多个独立的历程,简化了设计和维护。
  • 容错性:一个历程的崩溃通常不会影响其他历程,进步了系统的稳定性。
在Linux系统中,历程由历程控制块(PCB,即task_struct)表示,包含了历程的全部信息 。历程的生命周期包罗创建、停当、运行、阻塞和制止等状态 。
当用户实行Hello程序时,Shell会创建一个新历程来实行该程序 。这个历程拥有自己的地址空间,包含了Hello程序的代码、数据和堆栈 。
图6-1 历程的基本结构和状态转换图

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

Shell是用户与操作系统内核交互的接口,它接收、解释并实行用户输入的命令 。Bash(Bourne Again SHell)是Linux系统中最常用的Shell之一 。
Shell-Bash的重要作用包罗 :


  • 命令解释:解析用户输入的命令和参数。
  • 环境管理:维护环境变量。
  • 作业控制:管理前台和后台作业。
  • 脚本实行:实行Shell脚本。
  • I/O重定向:支持输入输出重定向和管道。
Shell-Bash的处理流程如下 :

  • 初始化:读取配置文件,设置环境。
  • 显示提示符:等待用户输入。
  • 读取命令:读取用户输入的命令行。
  • 解析命令:识别命令、参数和重定向符号。
  • 查找命令:在PATH环境变量指定的目录中查找可实行文件。
  • 创建历程:使用fork系统调用创建一个新历程。
  • 实行命令:在新历程中使用execve系统调用加载并实行程序。
  • 等待完成:Shell历程等待子历程实行完毕。
  • 显示结果:显示命令实行结果,并返回步骤2。
在实行Hello程序时,Shell会解析命令,通过fork创建新历程,然后在新历程中通过execve实行hello程序 。
图6-2 Shell-Bash处理命令的流程图

6.3 Hello的fork历程创建过程

fork是Unix/Linux系统中创建新历程的系统调用 。当Shell实行Hello程序时,它会调用fork创建一个新历程,这个新历程是Shell历程的副本 。
fork系统调用的工作原理如下 :


  • 复制历程映像:创建一个新历程,其地址空间是调用历程的完备副本。
  • 复制历程控制块:新历程得到一个新的PID,但继续了父历程的大部分属性。
  • 返回值区分:在父历程中返回子历程的PID,在子历程中返回0。
  • 写时复制(Copy-On-Write):父子历程最初共享物理内存页,只有当其中一个历程尝试修改内存页时,才会创建该页的副本。
strace命令可以跟踪到fork的底层实现clone系统调用,它创建了一个新的历程 。
图6-3 Hello的fork历程创建过程表示图

6.4 Hello的execve过程

execve是Unix/Linux系统中用于加载并实行程序的系统调用 。在新历程中,execve会加载并实行Hello程序,更换当前历程的代码和数据 。
execve系统调用的工作原理如下 :


  • 验证可实行文件:检查文件是否存在、是否有实行权限以及是否为支持的格式。
  • 开释当前历程资源:开释当前历程的代码段、数据段、堆等。
  • 加载新程序:将新程序的代码段、数据段等加载到内存中。
  • 设置入口点:将程序计数器指向新程序的入口点。
  • 初始化运行环境:设置命令行参数和环境变量。
  • 开始实行:开始实行新程序的代码。
strace命令可以跟踪到execve系统调用加载并实行了Hello程序,并通报了相应的命令行参数 。
图6-4 Hello的execve过程表示图

6.5 Hello的历程实行

Hello程序加载到内存后,操作系统会为其分配CPU时间片,使其可以或许实行指令 。历程实行涉及历程调理、上下文切换和时间片分配等机制 。
Hello历程的实行阶段包罗初始化、调用main函数、参数处理、循环实行、等待输入和制止 。在实行过程中,Hello历程会在运行态、停当态和阻塞态之间转换 。比方,调用sleep函数时,历程会从运行态转为阻塞态 。
ps命令可以查看Hello历程的状态,其中S表示历程处于休眠状态(阻塞态) 。
图6-5 Hello历程状态转换表示图

6.6 Hello的异常与信号处理

在Hello程序实行过程中,大概会发生各种异常和信号,如用户按下Ctrl+C(SIGINT信号) 。操作系统通过信号机制来处理这些变乱 。
信号是用于关照历程发生某个变乱的软件制止 。每个信号都有一个默认的处理动作,历程也可以通过signal或sigaction系统调用设置自定义的信号处理函数 。
Hello程序没有设置自定义的信号处理函数,因此使用默认的信号处理动作 。比方,当用户按下Ctrl+C时,SIGINT信号的默认动作是制止历程 。
kill命令可以向Hello历程发送信号,导致历程制止 。
6.7 本章小结

本章具体介绍了Hello程序的历程管理,包罗历程的概念、Shell-Bash的处理流程、fork和execve过程、历程实行以及异常与信号处理 。
历程是操作系统中最基本的实行单元,提供了资源隔离、并发实行等重要功能 。Shell-Bash通过fork和execve来创建和实行程序 。
Hello历程在实行过程中会经历多种状态转换,操作系统通过历程调理和上下文切换等机制实现并发实行 。异常和信号机制包管了系统的稳定性和可靠性 。
通过对Hello程序历程管理的分析,我们深入理解了操作系统如何创建、调理和管理历程 。
      
     第7章 Hello的存储管理

7.1 Hello的存储器地址空间

每个历程都有自己的假造地址空间,通过内存管理单元(MMU)映射到物理内存 。Hello程序的存储器地址空间包罗以下重要部分 :


  • 代码段(Text Segment):存放程序的机器码,通常是只读的。
  • 数据段(Data Segment):存放已初始化的全局和静态变量。
  • BSS段(BSS Segment):存放未初始化的全局和静态变量。
  • 堆(Heap):用于动态内存分配。
  • 栈(Stack):用于函数调用和局部变量存储。
  • 内存映射地区(Memory Mapping Segment):用于映射文件或共享库。
size命令可以查看Hello程序各个段的大小 。pmap命令可以查看Hello历程的内存映射,显示了程序各个段和加载的共享库的假造地址空间布局 。
7.2 Intel逻辑地址到线性地址的变更-段式管理

在Intel x86-64架构中,地址转换的第一阶段是段式管理,它将逻辑地址转换为线性地址 。逻辑地址由段选择子和段内偏移量组成 。
在64位模式下,段式管理被简化,段基地址被设置为0,因此逻辑地址和线性地址在数值上是相称的 。但段仍然用于访问控制,如区分代码段和数据段 。
gdb可以查看Hello程序实行时的段寄存器值,如cs(代码段)和ss(栈段)。
图7-1 Intel x86-64架构中的段式管理

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

页式管理是地址转换的第二阶段,负责将线性地址转换为物理地址 。在x86-64架构中,线性地址被分为多个部分,用于在多级页表中查找 。
地址转换过程如下 :

  • CPU生成一个线性地址。
  • MMU使用CR3寄存器找到页目录指针表的基地址。
  • 通过多级页表查找,最终找到页的地址。
  • 将页内偏移与页的地址相加,得到最终的物理地址 。
/proc/pid/maps文件可以查看Hello历程的内存映射,显示了各个段的地址范围、访问权限和对应的文件
图7-2 x86-64架构中的页式管理


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

为了加速地址转换,当代处理器使用转换后备缓冲区(TLB)来缓存迩来使用的地址转换结果 。当CPU需要访问内存时,首先检查TLB,如果掷中,则直接获取物理地址,避免遍历页表 。如果TLB未掷中,则需要遍历页表,并将结果添加到TLB中 。
在x86-64架构中,四级页表结构包罗页全局目录(PGD)、页上级目录(PUD)、页中间目录(PMD)和页表(PT) 。
perf工具可以测量TLB的未掷中率,Hello程序的TLB未掷中率较低,表明大多数内存访问都能在TLB中找到匹配的条目
图7-3 TLB与四级页表支持下的地址转换过程


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

为了减少内存访问延迟,处理器使用多级缓存(Cache)来存储迩来访问的数据和指令 。典型的处理器有三级缓存:L1、L2和L3 。
当CPU需要访问内存时,会依次检查L1、L2和L3缓存,如果都未掷中,才访问主内存 。缓存的工作原理基于局部性原理,并以缓存行为单元存储数据 。
perf工具可以测量缓存的未掷中率,Hello程序的缓存未掷中率较低,表明大多数内存访问都能在缓存中找到匹配的数据 。
图7-5 三级Cache支持下的内存访问过程

7.6 Hello历程fork时的内存映射

当Shell实行Hello程序时,它会调用fork系统调用创建一个新历程 。为了进步效率,当代操作系统使用写时复制(COW)技能,父子历程最初共享物理内存页,只有当其中一个历程尝试修改内存页时,才会创建该页的副本 。
/proc/pid/maps文件可以显示fork前后的内存映射,子历程继续了父历程的内存映射 。
图7-5 Hello历程fork时的内存映射过程


7.7 Hello历程execve时的内存映射

在fork之后,子历程会调用execve来加载并实行Hello程序,更换当前历程的代码和数据 。execve会开释当前历程的大部分资源,并创建新的内存映射来加载新程序 。
strace命令可以跟踪execve系统调用及其后续的内存管理操作,如brk和mmap 。/proc/pid/maps文件可以显示execve前后历程的内存映射已经完全改变。
图7-7 Hello历程execve时的内存映射过程

7.8 缺页故障与缺页制止处理

在假造内存系统中,当历程访问一个尚未加载到物理内存的页面时,会触发缺页故障(Page Fault) 。操作系统会捕捉这个异常,并实行缺页制止处理程序 。
如果访问是正当的,操作系统会分配物理页面,从磁盘加载数据,并更新页表 。如果访问是非法的,则向历程发送SIGSEGV信号 。
perf工具可以测量缺页故障的次数,Hello程序的缺页故障次数相对较少 。
图7-8 缺页故障与缺页制止处理过程

7.9 动态存储分配管理

动态存储分配是指在程序运行时根据需要分配和开释内存,重要通过malloc、free等函数实现 。
C库提供的内存分配器通过brk或mmap等系统调用向操作系统请求内存,并在堆地区举行管理 。
固然Hello程序没有显式使用动态内存分配,但C运行时库和标准库函数大概会在内部使用 。strace可以跟踪到brk系统调用调解堆的大小。valgrind工具可以检测内存泄漏,并显示Hello程序没有内存泄漏 。
图7-9 动态存储分配管理过程

7.10 本章小结

本章具体介绍了Hello程序的存储管理,包罗假造地址空间、段式管理、页式管理、TLB、多级缓存、fork和execve时的内存映射、缺页故障处理以及动态存储分配管理 。
存储管理是操作系统的核心功能之一,通过假造内存技能为历程提供独立的地址空间 。处理器通过TLB和多级缓存来加速内存访问 。
fork和execve涉及复杂的内存映射操作 。缺页故障和动态存储分配是假造内存系统和程序运行时内存管理的重要机制 。
通过对Hello程序存储管理的分析,我们深入理解了操作系统如何为历程分配和管理内存资源,以及处理器如何加速内存访问 。
      
     第8章 Hello的IO管理

8.1 Linux的IO装备管理方法

Linux操作系统接纳"统统皆文件"的哲学,将全部I/O装备都抽象为文件,通过文件系统接口举行访问 。这种方法简化了装备访问的编程模子,应用程序可以使用类似的系统调用(如open、read、write、close)来访问差别范例的装备 。
Linux的I/O装备管理条理包罗用户空间、假造文件系统(VFS)、装备驱动程序和硬件抽象层 。装备分为字符装备、块装备和网络装备三类 。
在Hello程序中,固然没有直接操作装备文件,但它使用了标准输入输出,这也是通过文件描述符实现的 。
图8-1 Linux I/O装备管理架构

8.2 简述Unix IO接口及其函数

Unix/Linux系统提供了一组同一的I/O接口函数,用于文件和装备的操作,这些函数是高级I/O库函数的底层实现 。
重要的Unix I/O接口函数包罗 :


  • open
  • read
  • write
  • close
  • lseek
  • ioctl
  • select/poll/epoll
  • mmap
在Hello程序中,printf和getchar函数最终会调用write和read系统调用来实现I/O操作 。strace命令可以跟踪到这些系统调用 。
图8-2 Unix I/O接口函数调用关系

8.3 printf的实现分析

printf是C标准库中的一个格式化输出函数,它接受一个格式字符串和可变数量的参数,将格式化后的结果输出到标准输出。
printf的实现涉及参数处理、格式化、缓冲管理、输出和系统调用等多个条理 。在Hello程序中,printf函数用于打印问候信息 。
strace命令可以观察到printf函数最终调用的write系统调用,将格式化后的字符串写入到标准输出(文件描述符1) 。
图8-3 printf函数的实现流程

8.4 getchar的实现分析

getchar是C标准库中的一个函数,用于从标准输入(stdin)读取一个字符 。
getchar的实现涉及缓冲管理、系统调用、字符处理和返回值等多个条理 。在Hello程序中,getchar函数用于等待用户输入,阻塞程序直到用户输入一个字符 。
strace命令可以观察到getchar函数最终调用的read系统调用,从标准输入(文件描述符0)读取数据 。8.5 本章小结
本章具体介绍了Hello程序的I/O管理,包罗Linux的I/O装备管理方法、Unix I/O接口及其函数、printf的实现分析和getchar的实现分析 。
Linux接纳"统统皆文件"的装备管理哲学,通过同一的文件系统接口举行访问 。Unix/Linux系统提供了一组同一的I/O接口函数,作为高级I/O库函数的底层实现 。
printf和getchar函数分别通过write和read系统调用来实现输出和输入操作 。通过对Hello程序I/O管理的分析,我们深入理解了计算机系统的I/O机制和高级I/O库函数的实现原理 。
      
     结论

通过对Hello程序从源代码到实行过程的全面分析,我们深入探讨了计算机系统的各个条理,包罗编译系统、操作系统和硬件架构 。Hello程序的生命周期涵盖了计算机系统的核心概念和机制,从编译过程到实行过程,再到内存管理和I/O操作 。
本次研究得出的重要结论包罗:

  • 抽象条理的重要性:计算机系统由多个抽象条理构成,每个条理都隐藏了下层的复杂性。
  • 接口与实现的分离:各个组件通过定义良好的接口举行交互,进步了系统的灵活性。
  • 性能优化的多条理性性能优化涉及编译器、操作系统、内存条理结构和硬件设计等多个条理。
  • 安全与保护机制的须要性:假造内存、特权级别等机制对于保障系统的安全性和稳定性至关重要。
  • 标准化与兼容性的代价:标准化和兼容性促进了技能的创新和应用的普及。
本研究加深了我们对计算机系统的理解,并造就了分析复杂系统的能力 。通过将抽象概念与具体实例相结合,我们可以或许更好地理解理论知识在实际系统中的应用 。
      
     参考文献

[1]   Randal E.Bryant David R.O'Hallaron.深入理解计算机系统(第三版).机械工业出版社,2016.
[2]   https://www.cnblogs.com/buddy916/p/10291845.html
[3]   [转]printf 函数实现的深入剖析 - Pianistx - 博客园
[4]   printf背后的故事 - Florian - 博客园.
[5]   linux2.6 内存管理——逻辑地址转换为线性地址(逻辑地址、线性地址、物理地址、假造地址) - 刁海威 - 博客园
[6]   https://blog.csdn.net/spfLinux/article/details/54427494

附件列表


  • hello.c - Hello程序源代码
  • hello.i - Hello程序预处理后的文件
  • hello.s - Hello程序编译后的汇编代码
  • hello.o - Hello程序汇编后的目的文件
  • hello - Hello程序链接后的可实行文件
  • preprocess.sh - 预处理脚本
  • compile.sh - 编译脚本
  • assemble.sh - 汇编脚本
  • link.sh - 链接脚本
  • run.sh - 运行脚本
  • objdump_hello.txt - 使用objdump分析hello可实行文件的结果
  • readelf_hello.txt - 使用readelf分析hello可实行文件的结果
  • strace_hello.txt - 使用strace跟踪hello程序实行的系统调用
  • size_hello.txt - 使用size命令查看hello文件各段大小的结果
  • nm_hello.txt - 使用nm命令查看hello文件符号表的结果

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
继续阅读请点击广告

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

×
登录参与点评抽奖,加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表