2025春HIT CS:APP课程大作业-Hello.c的一生

摘  要

本实验围绕一个简单的 C 程序 hello.c,系统分析了其从源代码到可执行程序、再到运行结束的完整生命周期。通过分阶段操作与工具分析,深入探讨了预处理、编译、汇编、链接、加载、执行、进程调度、内存映射、系统调用、动态链接、异常处理等关键过程。实验过程中使用了 GCC、readelf、objdump、gdb等常用工具。

关键词:编译系统;ELF 文件;进程管理;虚拟内存;系统调用                         

目  录

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

P2P是指从源程序到运行进程的全过程(Program to Process),象征着hello.c从C源程序文本经过预处理,编译,汇编,链接,最后生成可执行文件的过程。

O2O则是从0到0的循环,反映了一个hello程序在内存中的声明周期。在内存中,hello程序从shell调用execve启动hello开始,shell在虚拟内存中为程序分配空间,并将其映射到物理内存。随后,hello从入口开始加载并执行,进入main函数,执行目标代码。hello执行完毕后,shell收到子进程信号并回收hello进程,回收hello占用的资源,内核也清楚hello使用的数据结构,使内存回到刚开始的状态,形成完整的程序周期。

1.2 环境与工具

硬件环境:

  1. CPU: AMD RyzenTM R9 7945HX 2.50Ghz
  2. RAM : 16.0 GB DDR5 4800MT/s

软件环境:

  1. 操作系统:Windows 11 64bit/Ubuntu 18.04.6 LTS
  2. 开发与调试工具:Visual Studio Codes,gcc,edb,objdump

1.3 中间结果

  1. hello.i        hello.c获得的预处理文本
  2. hello.s        hello.i经过编译得到的汇编文件
  3. hello.o       hello.s经过汇编得到的二进制可重定位目标文件
  4. hello.asm   hello.o反汇编得到的汇编文件
  5. hello1.asm hello反汇编得到的汇编文件

1.4 本章小结

       本章主要介绍了hello.c的P2P,O2O流程,说明了本次实验用到的软硬件平台和中间文件

第2章 预处理

2.1 预处理的概念与作用

预处理(Preprocessing)是 C 语言编译过程的第一阶段,其主要任务是对源代码中以 # 开头的 预处理指令 进行处理,生成一个中间文件(通常以 .i 为后缀),为后续编译阶段准备好纯粹的C语言代码。

  1. 宏替换
    将通过 #define 定义的宏名称替换为相应的值或表达式,例如:
  2. 头文件展开
    将头文件中的内容直接插入源代码中。比如
  3. 条件编译
    处理 #ifdef, #ifndef, #if, #else, #endif 等条件语句,根据宏是否被定义选择性地保留/丢弃代码块,用于跨平台或调试控制。
  4. 注释删除
    所有的注释内容(///* */)在预处理阶段被彻底删除,不会出现在 .i 文件中。
  5. 行拼接
    将多行宏定义中的 \ 进行行拼接,变为一行完整的指令。

2.2在Ubuntu下预处理的命令

--

2.3 Hello的预处理结果解析

经对比,hello.i被扩展为了大约3100行的C代码。其中大部分为插入的头文件内容,并去除了全部的注释

2.4 本章小结

本章介绍预处理的概念和作用,展示了Ubuntu下的预处理命令与输出结果分析

第3章 编译

3.1 编译的概念与作用

编译(Compilation)是将预处理后的 C 语言代码(.i 文件)翻译成对应的汇编语言代码(.s 文件)的过程。这个阶段由编译器(如 GCC)完成,是整个程序构建过程中的核心步骤之一。

编译的输入和输出:

  1. 输入:.i 文件(纯C语言代码,已展开宏与头文件)
  2. 输出:.s 文件(汇编语言代码,面向底层 CPU 架构)

编译阶段的主要作用:

  1. 语法分析(Syntax Analysis)
    1. 将C语言代码进行词法分析、语法分析,构建语法树(AST),检查语法错误;
    2. 保证程序符合C语言规范。
  2. 语义分析与中间代码生成
    1. 分析变量作用域、类型匹配、函数调用等;
    2. 生成中间表示(如三地址码或SSA)。
  3. 汇编代码生成
    1. 根据中间代码和目标CPU架构,生成对应的汇编语言程序(.s 文件),如 mov, add, call 等指令;
    2. 汇编代码中可清楚看到变量如何映射到寄存器、如何进行栈帧布局等。
  4. 初步优化(-O选项)
    1. 可对代码进行语句合并、死代码消除等基础优化(如使用 -Og, -O2, -O3 等选项);
    2. 在本实验中使用 -Og,以便保留调试信息。

3.2 在Ubuntu下编译的命令

3.3 Hello的编译结果解析

得到hello.s如下:

      .file "hello.c"

      .text

      .section  .rodata

      .align 8

.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"

      .text

      .globl     main

      .type      main, @function

main:

.LFB5:

      .cfi_startproc

      pushq     %rbp

      .cfi_def_cfa_offset 16

      .cfi_offset 6, -16

      movq     %rsp, %rbp

      .cfi_def_cfa_register 6

      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

      .cfi_def_cfa 7, 8

      ret

      .cfi_endproc

.LFE5:

      .size       main, .-main

      .ident     "GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0"

      .section  .note.GNU-stack,"",@progbits

3.3.1 数据

       字符串常量:section .rodata表明为只读段,包含.LC0和.LC1两个字符串常量

       局部变量:数据存在栈中,包括循环使用的i,argc,argv

3.3.2 赋值

       mov指令实现,比如movl $0, -4(%rbp)为将循环计数器置0

3.3.3算术操作

       在本程序中主要使用sub,add,lea,比如对于循环计数器自增采用:

addl $1, -4(%rbp)

3.3.4关系逻辑比较

       在本程序中主要比较大小关系,使用cmp指令,如比较argc是否为5使用:

cmpl $5, -20(%rbp)

3.3.5数组/指针/结构操作

       hello中只有一个数组为参数列表int *argv,读取参数列表使用基址寄存器%rbp与偏移量间接寻址:

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)

3.3.6控制转移

       根据上文的关系逻辑比较给出的条件码与条件控制指令(xe,xne,xge,xle等)决定程序执行顺序,例如逻辑:if(argc != 5){…}有:

       cmpl      $5, -20(%rbp)

       je           .L2

.L2:

       movl      $0, -4(%rbp)

       jmp        .L3

.L3:

       cmpl      $9, -4(%rbp)

       jle          .L4

       call        getchar@PLT

       movl      $0, %eax

       leave

       .cfi         _def_cfa 7, 8

       ret

       .cfi_       endproc

3.3.7函数操作
  1. 传递参数,如argc&argv[]:
     .cfi_startproc

  pushq                          %rbp

  .cfi_def_cfa_offset    16

  .cfi_offset                   6, -16

  movq                          %rsp, %rbp

  .cfi_def_cfa_register 6

  subq                            $32, %rsp

  movl                           %edi, -20(%rbp)

  movq                          %rsi, -32(%rbp)

  1. 函数调用,如调用printf函数:
      movq                          %rax, %rsi
      leaq                            .LC1(%rip), %rdi
      movl                           $0, %eax
      call                             printf@PLT
  2. 函数返回:返回值保存在%rax,用ret返回,如:
      movl                           $0, %eax
      leave
      .cfi_def_cfa               7, 8
      ret
      .cfi_endproc
3.3.8类型转换

       类型转换在汇编中处类似于atoi函数均为隐式转换,atoi为接受字符串并返回整型值

3.4 本章小结

本章介绍了编译大致过程,解析了C语言编译产生汇编语言与源代码的关系

第4章 汇编

4.1 汇编的概念与作用

汇编(Assembly)是指将编译生成的汇编语言代码(.s 文件),转换成机器能够直接执行的机器码(即二进制指令),并生成可重定位目标文件(.o 文件,Object File)的过程。这个阶段由汇编器(Assembler)完成,是程序从文本代码向可执行形式过渡的关键步骤之一。

4.2 在Ubuntu下汇编的命令

 4.3 可重定位目标elf格式

readelf -h hello.o:

ELF 头:

  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00

  类别:                              ELF64

  数据:                              2 补码,小端序 (little endian)

  版本:                              1 (current)

  OS/ABI:                            UNIX - System V

  ABI 版本:                          0

  类型:                              REL (可重定位文件)

  系统架构:                          Advanced Micro Devices X86-64

  版本:                              0x1

  入口点地址:               0x0

  程序头起点:          0 (bytes into file)

  Start of section headers:          1192 (bytes into file)

  标志:             0x0

  本头的大小:       64 (字节)

  程序头大小:       0 (字节)

  Number of program headers:         0

  节头大小:         64 (字节)

  节头数量:         13

  字符串表索引节头: 12

readelf -S hello.o:

There are 13 section headers, starting at offset 0x4a8:

节头:

  [号] 名称              类型             地址              偏移量

       大小              全体大小          旗标   链接   信息   对齐

  [ 0]                   NULL             0000000000000000  00000000

       0000000000000000  0000000000000000           0     0     0

  [ 1] .text             PROGBITS         0000000000000000  00000040

       0000000000000099  0000000000000000  AX       0     0     1

  [ 2] .rela.text        RELA             0000000000000000  00000368

       00000000000000c0  0000000000000018   I      10     1     8

  [ 3] .data             PROGBITS         0000000000000000  000000d9

       0000000000000000  0000000000000000  WA       0     0     1

  [ 4] .bss              NOBITS           0000000000000000  000000d9

       0000000000000000  0000000000000000  WA       0     0     1

  [ 5] .rodata           PROGBITS         0000000000000000  000000e0

       0000000000000040  0000000000000000   A       0     0     8

  [ 6] .comment          PROGBITS         0000000000000000  00000120

       000000000000002a  0000000000000001  MS       0     0     1

  [ 7] .note.GNU-stack   PROGBITS         0000000000000000  0000014a

       0000000000000000  0000000000000000           0     0     1

  [ 8] .eh_frame         PROGBITS         0000000000000000  00000150

       0000000000000038  0000000000000000   A       0     0     8

  [ 9] .rela.eh_frame    RELA             0000000000000000  00000428

       0000000000000018  0000000000000018   I      10     8     8

  [10] .symtab           SYMTAB           0000000000000000  00000188

       0000000000000198  0000000000000018          11     9     8

  [11] .strtab           STRTAB           0000000000000000  00000320

       0000000000000048  0000000000000000           0     0     1

  [12] .shstrtab         STRTAB           0000000000000000  00000440

       0000000000000061  0000000000000000           0     0     1

Key to Flags:

  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),

  L (link order), O (extra OS processing required), G (group), T (TLS),

  C (compressed), x (unknown), o (OS specific), E (exclude),

  l (large), p (processor specific)

上述为节表头,记录了ELF文件各节位置,大小,偏移等信息

readelf -s hello.o:

Symbol table '.symtab' contains 17 entries:

   Num:    Value          Size Type    Bind   Vis      Ndx Name

     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND

     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS hello.c

     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1

     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3

     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4

     5: 0000000000000000     0 SECTION LOCAL  DEFAULT    5

     6: 0000000000000000     0 SECTION LOCAL  DEFAULT    7

     7: 0000000000000000     0 SECTION LOCAL  DEFAULT    8

     8: 0000000000000000     0 SECTION LOCAL  DEFAULT    6

     9: 0000000000000000   153 FUNC    GLOBAL DEFAULT    1 main

    10: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _GLOBAL_OFFSET_TABLE_

    11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND puts

    12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND exit

    13: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND printf

    14: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND atoi

    15: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND sleep

    16: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND getchar

上述为符号表,存放着源文件定义和引用的函数,全局变量,静态局部变量的信息,帮助链接器正确解析符号引用

readelf -r hello.o:

重定位节 '.rela.text' at offset 0x368 contains 8 entries:

  偏移量          信息           类型           符号值        符号名称 + 加数

000000000018  000500000002 R_X86_64_PC32     0000000000000000 .rodata - 4

00000000001d  000b00000004 R_X86_64_PLT32    0000000000000000 puts - 4

000000000027  000c00000004 R_X86_64_PLT32    0000000000000000 exit - 4

00000000005b  000500000002 R_X86_64_PC32     0000000000000000 .rodata + 2c

000000000065  000d00000004 R_X86_64_PLT32    0000000000000000 printf - 4

000000000078  000e00000004 R_X86_64_PLT32    0000000000000000 atoi - 4

00000000007f  000f00000004 R_X86_64_PLT32    0000000000000000 sleep - 4

00000000008e  001000000004 R_X86_64_PLT32    0000000000000000 getchar - 4

重定位节 '.rela.eh_frame' at offset 0x428 contains 1 entry:

  偏移量          信息           类型           符号值        符号名称 + 加数

000000000020  000200000002 R_X86_64_PC32     0000000000000000 .text + 0

上述为重定位节.rela.text&.rela.eh_frame,记录了可重定位文件中所有重定位条目的信息,包括重定位类型偏移量等信息。这些信息用于在链接过程中计算出变量符号正确的地址,包括相对与直接寻址。

4.4 Hello.o的结果解析

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

objdump -d -r hello.o  :

0000000000000000

:

   0:    55                          push   %rbp

   1:    48 89 e5               mov    %rsp,%rbp

   4:    48 83 ec 20              sub    $0x20,%rsp

   8:    89 7d ec                mov    %edi,-0x14(%rbp)

   b:    48 89 75 e0             mov    %rsi,-0x20(%rbp)

   f:     83 7d ec 05              cmpl   $0x5,-0x14(%rbp)

  13:    74 16                je     2b

  15:    48 8d 3d 00 00 00 00       lea    0x0(%rip),%rdi        # 1c

                    18: R_X86_64_PC32 .rodata-0x4

  1c:    e8 00 00 00 00           callq  21

                    1d: R_X86_64_PLT32      puts-0x4

  21:    bf 01 00 00 00            mov    $0x1,%edi

  26:    e8 00 00 00 00           callq  2b

                    27: R_X86_64_PLT32      exit-0x4

  2b:    c7 45 fc 00 00 00 00 movl   $0x0,-0x4(%rbp)

  32:    eb 53                jmp    87

  34:    48 8b 45 e0             mov    -0x20(%rbp),%rax

  38:    48 83 c0 18             add    $0x18,%rax

  3c:    48 8b 08               mov    (%rax),%rcx

  3f:     48 8b 45 e0             mov    -0x20(%rbp),%rax

  43:    48 83 c0 10             add    $0x10,%rax

  47:    48 8b 10               mov    (%rax),%rdx

  4a:    48 8b 45 e0             mov    -0x20(%rbp),%rax

  4e:    48 83 c0 08             add    $0x8,%rax

  52:    48 8b 00               mov    (%rax),%rax

  55:    48 89 c6               mov    %rax,%rsi

  58:    48 8d 3d 00 00 00 00       lea    0x0(%rip),%rdi        # 5f

                    5b: R_X86_64_PC32 .rodata+0x2c

  5f:     b8 00 00 00 00           mov    $0x0,%eax

  64:    e8 00 00 00 00           callq  69

                    65: R_X86_64_PLT32      printf-0x4

  69:    48 8b 45 e0             mov    -0x20(%rbp),%rax

  6d:    48 83 c0 20             add    $0x20,%rax

  71:    48 8b 00               mov    (%rax),%rax

  74:    48 89 c7               mov    %rax,%rdi

  77:    e8 00 00 00 00           callq  7c

                    78: R_X86_64_PLT32      atoi-0x4

  7c:    89 c7                mov    %eax,%edi

  7e:    e8 00 00 00 00           callq  83

                    7f: R_X86_64_PLT32      sleep-0x4

  83:    83 45 fc 01              addl   $0x1,-0x4(%rbp)

  87:    83 7d fc 09              cmpl   $0x9,-0x4(%rbp)

  8b:    7e a7                  jle    34

  8d:    e8 00 00 00 00           callq  92

                    8e: R_X86_64_PLT32      getchar-0x4

  92:    b8 00 00 00 00           mov    $0x0,%eax

  97:    c9                   leaveq

  98:    c3                   retq  

相较于gcc产生的.s文件,每条指令有一个十六进制的表示形式,即机器语言编码。且反汇编中所有直接数与间接数均被改为hex格式,且跳转与函数调用由标签改为了函数入口加偏移量的形式。

4.5 本章小结

本节介绍了汇编的概念与作用,具体分析了hello.o的elf结构和反汇编的改变

第5章 链接

5.1 链接的概念与作用

链接(Linking)是将多个目标文件(.o 文件)以及相关库文件(如标准 C 库 libc.a 或 libc.so)合并成一个完整可执行文件(如 hello)的过程。这个过程通常由链接器(Linker)负责完成。

5.2 在Ubuntu下链接的命令

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

ELF 头:

  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00

  类别:                              ELF64

  数据:                              2 补码,小端序 (little endian)

  版本:                              1 (current)

  OS/ABI:                            UNIX - System V

  ABI 版本:                          0

  类型:                              EXEC (可执行文件)

  系统架构:                          Advanced Micro Devices X86-64

  版本:                              0x1

  入口点地址:               0x400550

  程序头起点:          64 (bytes into file)

  Start of section headers:          5936 (bytes into file)

  标志:             0x0

  本头的大小:       64 (字节)

  程序头大小:       56 (字节)

  Number of program headers:         8

  节头大小:         64 (字节)

  节头数量:         25

  字符串表索引节头: 24

There are 25 section headers, starting at offset 0x1730:

节头:

  [号] 名称              类型             地址              偏移量

       大小              全体大小          旗标   链接   信息   对齐

  [ 0]                   NULL             0000000000000000  00000000

       0000000000000000  0000000000000000           0     0     0

  [ 1] .interp           PROGBITS         0000000000400200  00000200

       000000000000001c  0000000000000000   A       0     0     1

  [ 2] .note.ABI-tag     NOTE             000000000040021c  0000021c

       0000000000000020  0000000000000000   A       0     0     4

  [ 3] .hash             HASH             0000000000400240  00000240

       0000000000000038  0000000000000004   A       5     0     8

  [ 4] .gnu.hash         GNU_HASH         0000000000400278  00000278

       000000000000001c  0000000000000000   A       5     0     8

  [ 5] .dynsym           DYNSYM           0000000000400298  00000298

       00000000000000d8  0000000000000018   A       6     1     8

  [ 6] .dynstr           STRTAB           0000000000400370  00000370

       000000000000005c  0000000000000000   A       0     0     1

  [ 7] .gnu.version      VERSYM           00000000004003cc  000003cc

       0000000000000012  0000000000000002   A       5     0     2

  [ 8] .gnu.version_r    VERNEED          00000000004003e0  000003e0

       0000000000000020  0000000000000000   A       6     1     8

  [ 9] .rela.dyn         RELA             0000000000400400  00000400

       0000000000000030  0000000000000018   A       5     0     8

  [10] .rela.plt         RELA             0000000000400430  00000430

       0000000000000090  0000000000000018  AI       5    19     8

  [11] .init             PROGBITS         00000000004004c0  000004c0

       0000000000000017  0000000000000000  AX       0     0     4

  [12] .plt              PROGBITS         00000000004004e0  000004e0

       0000000000000070  0000000000000010  AX       0     0     16

  [13] .text             PROGBITS         0000000000400550  00000550

       0000000000000142  0000000000000000  AX       0     0     16

  [14] .fini             PROGBITS         0000000000400694  00000694

       0000000000000009  0000000000000000  AX       0     0     4

  [15] .rodata           PROGBITS         00000000004006a0  000006a0

       0000000000000048  0000000000000000   A       0     0     8

  [16] .eh_frame         PROGBITS         00000000004006e8  000006e8

       00000000000000fc  0000000000000000   A       0     0     8

  [17] .dynamic          DYNAMIC          0000000000600e50  00000e50

       00000000000001a0  0000000000000010  WA       6     0     8

  [18] .got              PROGBITS         0000000000600ff0  00000ff0

       0000000000000010  0000000000000008  WA       0     0     8

  [19] .got.plt          PROGBITS         0000000000601000  00001000

       0000000000000048  0000000000000008  WA       0     0     8

  [20] .data             PROGBITS         0000000000601048  00001048

       0000000000000004  0000000000000000  WA       0     0     1

  [21] .comment          PROGBITS         0000000000000000  0000104c

       0000000000000029  0000000000000001  MS       0     0     1

  [22] .symtab           SYMTAB           0000000000000000  00001078

       0000000000000498  0000000000000018          23    28     8

  [23] .strtab           STRTAB           0000000000000000  00001510

       0000000000000158  0000000000000000           0     0     1

  [24] .shstrtab         STRTAB           0000000000000000  00001668

       00000000000000c5  0000000000000000           0     0     1

Key to Flags:

  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),

  L (link order), O (extra OS processing required), G (group), T (TLS),

  C (compressed), x (unknown), o (OS specific), E (exclude),

  l (large), p (processor specific)

重定位节 '.rela.dyn' at offset 0x400 contains 2 entries:

  偏移量          信息           类型           符号值        符号名称 + 加数

000000600ff0  000300000006 R_X86_64_GLOB_DAT 0000000000000000 __libc_start_main@GLIBC_2.2.5 + 0

000000600ff8  000500000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0

重定位节 '.rela.plt' at offset 0x430 contains 6 entries:

  偏移量          信息           类型           符号值        符号名称 + 加数

000000601018  000100000007 R_X86_64_JUMP_SLO 0000000000000000 puts@GLIBC_2.2.5 + 0

000000601020  000200000007 R_X86_64_JUMP_SLO 0000000000000000 printf@GLIBC_2.2.5 + 0

000000601028  000400000007 R_X86_64_JUMP_SLO 0000000000000000 getchar@GLIBC_2.2.5 + 0

000000601030  000600000007 R_X86_64_JUMP_SLO 0000000000000000 atoi@GLIBC_2.2.5 + 0

000000601038  000700000007 R_X86_64_JUMP_SLO 0000000000000000 exit@GLIBC_2.2.5 + 0

000000601040  000800000007 R_X86_64_JUMP_SLO 0000000000000000 sleep@GLIBC_2.2.5 + 0

Symbol table '.dynsym' contains 9 entries:

   Num:    Value          Size Type    Bind   Vis      Ndx Name

     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND

     1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@GLIBC_2.2.5 (2)

     2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND printf@GLIBC_2.2.5 (2)

     3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.2.5 (2)

     4: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND getchar@GLIBC_2.2.5 (2)

     5: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__

     6: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND atoi@GLIBC_2.2.5 (2)

     7: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND exit@GLIBC_2.2.5 (2)

     8: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND sleep@GLIBC_2.2.5 (2)

符号表:

Symbol table '.symtab' contains 49 entries:

   Num:    Value          Size Type    Bind   Vis      Ndx Name

     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND

     1: 0000000000400200     0 SECTION LOCAL  DEFAULT    1

     2: 000000000040021c     0 SECTION LOCAL  DEFAULT    2

     3: 0000000000400240     0 SECTION LOCAL  DEFAULT    3

     4: 0000000000400278     0 SECTION LOCAL  DEFAULT    4

     5: 0000000000400298     0 SECTION LOCAL  DEFAULT    5

     6: 0000000000400370     0 SECTION LOCAL  DEFAULT    6

     7: 00000000004003cc     0 SECTION LOCAL  DEFAULT    7

     8: 00000000004003e0     0 SECTION LOCAL  DEFAULT    8

     9: 0000000000400400     0 SECTION LOCAL  DEFAULT    9

    10: 0000000000400430     0 SECTION LOCAL  DEFAULT   10

    11: 00000000004004c0     0 SECTION LOCAL  DEFAULT   11

    12: 00000000004004e0     0 SECTION LOCAL  DEFAULT   12

    13: 0000000000400550     0 SECTION LOCAL  DEFAULT   13

    14: 0000000000400694     0 SECTION LOCAL  DEFAULT   14

    15: 00000000004006a0     0 SECTION LOCAL  DEFAULT   15

    16: 00000000004006e8     0 SECTION LOCAL  DEFAULT   16

    17: 0000000000600e50     0 SECTION LOCAL  DEFAULT   17

    18: 0000000000600ff0     0 SECTION LOCAL  DEFAULT   18

    19: 0000000000601000     0 SECTION LOCAL  DEFAULT   19

    20: 0000000000601048     0 SECTION LOCAL  DEFAULT   20

    21: 0000000000000000     0 SECTION LOCAL  DEFAULT   21

    22: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS hello.c

    23: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS

    24: 0000000000600e50     0 NOTYPE  LOCAL  DEFAULT   17 __init_array_end

    25: 0000000000600e50     0 OBJECT  LOCAL  DEFAULT   17 _DYNAMIC

    26: 0000000000600e50     0 NOTYPE  LOCAL  DEFAULT   17 __init_array_start

    27: 0000000000601000     0 OBJECT  LOCAL  DEFAULT   19 _GLOBAL_OFFSET_TABLE_

    28: 0000000000400690     2 FUNC    GLOBAL DEFAULT   13 __libc_csu_fini

    29: 0000000000601048     0 NOTYPE  WEAK   DEFAULT   20 data_start

    30: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@@GLIBC_2.2.5

    31: 000000000060104c     0 NOTYPE  GLOBAL DEFAULT   20 _edata

    32: 0000000000400694     0 FUNC    GLOBAL DEFAULT   14 _fini

    33: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND printf@@GLIBC_2.2.5

    34: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@@GLIBC_

    35: 0000000000601048     0 NOTYPE  GLOBAL DEFAULT   20 __data_start

    36: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND getchar@@GLIBC_2.2.5

    37: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__

    38: 00000000004006a0     4 OBJECT  GLOBAL DEFAULT   15 _IO_stdin_used

    39: 0000000000400620   101 FUNC    GLOBAL DEFAULT   13 __libc_csu_init

    40: 0000000000601050     0 NOTYPE  GLOBAL DEFAULT   20 _end

    41: 0000000000400580     2 FUNC    GLOBAL HIDDEN    13 _dl_relocate_static_pie

    42: 0000000000400550    43 FUNC    GLOBAL DEFAULT   13 _start

    43: 000000000060104c     0 NOTYPE  GLOBAL DEFAULT   20 __bss_start

    44: 0000000000400582   153 FUNC    GLOBAL DEFAULT   13 main

    45: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND atoi@@GLIBC_2.2.5

    46: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND exit@@GLIBC_2.2.5

    47: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND sleep@@GLIBC_2.2.5

    48: 00000000004004c0     0 FUNC    GLOBAL DEFAULT   11 _init

程序头:

Elf 文件类型为 EXEC (可执行文件)

Entry point 0x400550

There are 8 program headers, starting at offset 64

程序头:

  Type           Offset             VirtAddr           PhysAddr

                 FileSiz            MemSiz              Flags  Align

  PHDR           0x0000000000000040 0x0000000000400040 0x0000000000400040

                 0x00000000000001c0 0x00000000000001c0  R      0x8

  INTERP         0x0000000000000200 0x0000000000400200 0x0000000000400200

                 0x000000000000001c 0x000000000000001c  R      0x1

      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]

  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000

                 0x00000000000007e4 0x00000000000007e4  R E    0x200000

  LOAD           0x0000000000000e50 0x0000000000600e50 0x0000000000600e50

                 0x00000000000001fc 0x00000000000001fc  RW     0x200000

  DYNAMIC        0x0000000000000e50 0x0000000000600e50 0x0000000000600e50

                 0x00000000000001a0 0x00000000000001a0  RW     0x8

  NOTE           0x000000000000021c 0x000000000040021c 0x000000000040021c

                 0x0000000000000020 0x0000000000000020  R      0x4

  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000

                 0x0000000000000000 0x0000000000000000  RW     0x10

  GNU_RELRO      0x0000000000000e50 0x0000000000600e50 0x0000000000600e50

                 0x00000000000001b0 0x00000000000001b0  R      0x1

 Section to Segment mapping:

  段节...

   00    

   01     .interp

   02     .interp .note.ABI-tag .hash .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame

   03     .dynamic .got .got.plt .data

   04     .dynamic

   05     .note.ABI-tag

   06    

   07     .dynamic .got

5.4 hello的虚拟地址空间

  -

5.5 链接的重定位过程分析

objdump -d -r hello:

hello:     文件格式 elf64-x86-64

Disassembly of section .init:

00000000004004c0 <_init>:

  4004c0:   48 83 ec 08              sub    $0x8,%rsp

  4004c4:   48 8b 05 2d 0b 20 00    mov    0x200b2d(%rip),%rax        # 600ff8 <__gmon_start__>

  4004cb:   48 85 c0               test   %rax,%rax

  4004ce:   74 02                je     4004d2 <_init+0x12>

  4004d0:   ff d0                  callq  *%rax

  4004d2:   48 83 c4 08             add    $0x8,%rsp

  4004d6:   c3                   retq  

Disassembly of section .plt:

00000000004004e0 <.plt>:

  4004e0:   ff 35 22 0b 20 00          pushq  0x200b22(%rip)        # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>

  4004e6:   ff 25 24 0b 20 00          jmpq   *0x200b24(%rip)        # 601010 <_GLOBAL_OFFSET_TABLE_+0x10>

  4004ec:   0f 1f 40 00              nopl   0x0(%rax)

00000000004004f0 :

  4004f0:    ff 25 22 0b 20 00          jmpq   *0x200b22(%rip)        # 601018

  4004f6:    68 00 00 00 00           pushq  $0x0

  4004fb:    e9 e0 ff ff ff         jmpq   4004e0 <.plt>

0000000000400500 :

  400500:   ff 25 1a 0b 20 00           jmpq   *0x200b1a(%rip)        # 601020

  400506:   68 01 00 00 00           pushq  $0x1

  40050b:   e9 d0 ff ff ff         jmpq   4004e0 <.plt>

0000000000400510 :

  400510:   ff 25 12 0b 20 00          jmpq   *0x200b12(%rip)        # 601028

  400516:   68 02 00 00 00           pushq  $0x2

  40051b:   e9 c0 ff ff ff         jmpq   4004e0 <.plt>

0000000000400520 :

  400520:   ff 25 0a 0b 20 00           jmpq   *0x200b0a(%rip)        # 601030

  400526:   68 03 00 00 00           pushq  $0x3

  40052b:   e9 b0 ff ff ff         jmpq   4004e0 <.plt>

0000000000400530 :

  400530:   ff 25 02 0b 20 00          jmpq   *0x200b02(%rip)        # 601038

  400536:   68 04 00 00 00           pushq  $0x4

  40053b:   e9 a0 ff ff ff         jmpq   4004e0 <.plt>

0000000000400540 :

  400540:   ff 25 fa 0a 20 00    jmpq   *0x200afa(%rip)        # 601040

  400546:   68 05 00 00 00           pushq  $0x5

  40054b:   e9 90 ff ff ff         jmpq   4004e0 <.plt>

Disassembly of section .text:

0000000000400550 <_start>:

  400550:   31 ed                xor    %ebp,%ebp

  400552:   49 89 d1               mov    %rdx,%r9

  400555:   5e                   pop    %rsi

  400556:   48 89 e2               mov    %rsp,%rdx

  400559:   48 83 e4 f0          and    $0xfffffffffffffff0,%rsp

  40055d:   50                          push   %rax

  40055e:   54                          push   %rsp

  40055f:    49 c7 c0 90 06 40 00        mov    $0x400690,%r8

  400566:   48 c7 c1 20 06 40 00        mov    $0x400620,%rcx

  40056d:   48 c7 c7 82 05 40 00        mov    $0x400582,%rdi

  400574:   ff 15 76 0a 20 00           callq  *0x200a76(%rip)        # 600ff0 <__libc_start_main@GLIBC_2.2.5>

  40057a:   f4                   hlt   

  40057b:   0f 1f 44 00 00            nopl   0x0(%rax,%rax,1)

0000000000400580 <_dl_relocate_static_pie>:

  400580:   f3 c3                  repz retq

0000000000400582

:

  400582:   55                          push   %rbp

  400583:   48 89 e5               mov    %rsp,%rbp

  400586:   48 83 ec 20              sub    $0x20,%rsp

  40058a:   89 7d ec                mov    %edi,-0x14(%rbp)

  40058d:   48 89 75 e0            mov    %rsi,-0x20(%rbp)

  400591:   83 7d ec 05            cmpl   $0x5,-0x14(%rbp)

  400595:   74 16                je     4005ad

  400597:   48 8d 3d 0a 01 00 00       lea    0x10a(%rip),%rdi        # 4006a8 <_IO_stdin_used+0x8>

  40059e:   e8 4d ff ff ff         callq  4004f0

  4005a3:   bf 01 00 00 00            mov    $0x1,%edi

  4005a8:   e8 83 ff ff ff         callq  400530

  4005ad:   c7 45 fc 00 00 00 00 movl   $0x0,-0x4(%rbp)

  4005b4:   eb 53                jmp    400609

  4005b6:   48 8b 45 e0             mov    -0x20(%rbp),%rax

  4005ba:   48 83 c0 18             add    $0x18,%rax

  4005be:   48 8b 08               mov    (%rax),%rcx

  4005c1:   48 8b 45 e0             mov    -0x20(%rbp),%rax

  4005c5:   48 83 c0 10             add    $0x10,%rax

  4005c9:   48 8b 10               mov    (%rax),%rdx

  4005cc:   48 8b 45 e0             mov    -0x20(%rbp),%rax

  4005d0:   48 83 c0 08             add    $0x8,%rax

  4005d4:   48 8b 00               mov    (%rax),%rax

  4005d7:   48 89 c6               mov    %rax,%rsi

  4005da:   48 8d 3d f7 00 00 00        lea    0xf7(%rip),%rdi        # 4006d8 <_IO_stdin_used+0x38>

  4005e1:   b8 00 00 00 00           mov    $0x0,%eax

  4005e6:   e8 15 ff ff ff         callq  400500

  4005eb:   48 8b 45 e0             mov    -0x20(%rbp),%rax

  4005ef:    48 83 c0 20             add    $0x20,%rax

  4005f3:    48 8b 00               mov    (%rax),%rax

  4005f6:    48 89 c7               mov    %rax,%rdi

  4005f9:    e8 22 ff ff ff         callq  400520

  4005fe:    89 c7                mov    %eax,%edi

  400600:   e8 3b ff ff ff         callq  400540

  400605:   83 45 fc 01              addl   $0x1,-0x4(%rbp)

  400609:   83 7d fc 09              cmpl   $0x9,-0x4(%rbp)

  40060d:   7e a7                  jle    4005b6

  40060f:    e8 fc fe ff ff         callq  400510

  400614:   b8 00 00 00 00           mov    $0x0,%eax

  400619:   c9                   leaveq

  40061a:   c3                   retq  

  40061b:   0f 1f 44 00 00            nopl   0x0(%rax,%rax,1)

0000000000400620 <__libc_csu_init>:

  400620:   41 57                push   %r15

  400622:   41 56                push   %r14

  400624:   49 89 d7               mov    %rdx,%r15

  400627:   41 55                push   %r13

  400629:   41 54                push   %r12

  40062b:   4c 8d 25 1e 08 20 00    lea    0x20081e(%rip),%r12        # 600e50 <_DYNAMIC>

  400632:   55                          push   %rbp

  400633:   48 8d 2d 16 08 20 00    lea    0x200816(%rip),%rbp        # 600e50 <_DYNAMIC>

  40063a:   53                          push   %rbx

  40063b:   41 89 fd                mov    %edi,%r13d

  40063e:   49 89 f6                mov    %rsi,%r14

  400641:   4c 29 e5                sub    %r12,%rbp

  400644:   48 83 ec 08              sub    $0x8,%rsp

  400648:   48 c1 fd 03              sar    $0x3,%rbp

  40064c:   e8 6f fe ff ff         callq  4004c0 <_init>

  400651:   48 85 ed               test   %rbp,%rbp

  400654:   74 20                je     400676 <__libc_csu_init+0x56>

  400656:   31 db                xor    %ebx,%ebx

  400658:   0f 1f 84 00 00 00 00 nopl   0x0(%rax,%rax,1)

  40065f:    00

  400660:   4c 89 fa                mov    %r15,%rdx

  400663:   4c 89 f6                mov    %r14,%rsi

  400666:   44 89 ef                mov    %r13d,%edi

  400669:   41 ff 14 dc               callq  *(%r12,%rbx,8)

  40066d:   48 83 c3 01             add    $0x1,%rbx

  400671:   48 39 dd               cmp    %rbx,%rbp

  400674:   75 ea                  jne    400660 <__libc_csu_init+0x40>

  400676:   48 83 c4 08             add    $0x8,%rsp

  40067a:   5b                          pop    %rbx

  40067b:   5d                          pop    %rbp

  40067c:   41 5c                pop    %r12

  40067e:   41 5d                pop    %r13

  400680:   41 5e                pop    %r14

  400682:   41 5f                  pop    %r15

  400684:   c3                   retq  

  400685:   90                          nop

  400686:   66 2e 0f 1f 84 00 00 nopw   %cs:0x0(%rax,%rax,1)

  40068d:   00 00 00

0000000000400690 <__libc_csu_fini>:

  400690:   f3 c3                  repz retq

Disassembly of section .fini:

0000000000400694 <_fini>:

  400694:   48 83 ec 08              sub    $0x8,%rsp

  400698:   48 83 c4 08             add    $0x8,%rsp

40069c:    c3                   retq  

发现:

  1. 链接后的反汇编文件多出了.plt,puts@plt,printf@plt,getchar@plt等函数的代码。因为动态链接库将共享库中hello.c用到的函数加入了可执行文件中
  2. 链接过程中链接器解析了重定位条目并计算相对距离,修改了对应位置字节

5.6 hello的执行流程

       可以看到libc中存在_start,过程为(按调用顺序 ):

0x400550                     _start,

(Global offset table)     _libc_start_main@GLIBC_2.2.5,

0x400528                     main,

0x400500                      printf@plt,

0x400530                      exit@plt,

0x400540                      sleep@plt,

0x400510                      _getchar,

0x400530                      exit

5.7 Hello的动态链接分析

   使用 gdb 工具,对 hello 程序中的 printf 函数调用过程进行了跟踪,验证了其动态链接机制。通过如下步骤观察到:

  1. printf@plt 实际跳转到 .got.plt 表中的地址;
  2. 初始 .got.plt 中为跳转到 plt[1] 的入口;
  1. 第一次执行 printf 后,动态链接器解析并将实际地址填入 GOT;
  2. 后续对 printf 的调用则直接跳转,不再经过动态解析。

5.8 本章小结

本章介绍了连接的基本概念和功能,展示了如何链接hello.o与其他文件生成hello,并展示了其虚拟地址空间

第6章 hello进程管理

6.1 进程的概念与作用

在操作系统中,进程(Process)是资源分配和调度的基本单位。一个程序在运行时就成为一个进程,它拥有独立的地址空间、打开的文件描述符、栈、堆等资源。

在本实验中,hello 程序在执行时由 Linux 内核创建对应的进程,在运行过程中通过系统调用(如 fork()execve()sleep()getchar())与操作系统进行交互,体现了一个进程从创建到终止的完整生命周期。

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

Linux 下的 bash 是最常用的命令行 Shell,主要负责:

  1. 解析命令行输入(如 ./hello ...)
  2. 通过 fork() 创建子进程
  3. 通过 execve() 在子进程中加载并运行程序
  4. 等待子进程结束并回收资源

6.3 Hello的fork进程创建过程

bash 调用 fork() 创建一个子进程,子进程复制父进程的上下文,但拥有独立的进程ID和地址空间。

6.4 Hello的execve过程

在子进程中,bash 会调用 execve() 系统调用,加载 ELF 格式的 hello 可执行文件。此时:

  1. 原有子进程的代码和数据被清除;
  2. hello 的代码被加载到新的地址空间;
  3. 程序从 ELF 入口 _start 开始执行;
  4. argc/argv 被传递给 main() 函数。

6.5 Hello的进程执行

在运行过程中,hello 程序进入用户态,由操作系统调度执行:

  1. printf() → 调用 write() 系统调用,输出文字;
  2. sleep() → 使进程进入阻塞状态,让出 CPU;
  3. getchar() → 通过 read() 系统调用监听标准输入;
  4. 操作系统通过调度器(如 CFS)分配时间片给 hello
  5. 如果被中断(如按 Ctrl-Z),进程被挂起。

系统在用户态和内核态之间切换,执行系统调用、信号处理等。

6.6 hello的异常与信号处理

在运行过程中,hello 程序可能接收来自键盘或系统的各种信号,如:

按键

触发信号

说明

Ctrl+C

SIGINT

中断进程(终止)

Ctrl+Z

SIGTSTP

挂起进程(转后台)

kill PID

其他信号

SIGTERMSIGKILL

-

中断

-

-

陷阱

调用Syscall

-

故障

决定是否返回源程序位置

-

终止

发生硬件错误

以下为不同操作:

  1. Ctrl_Z此时jobs查看:
    可以kills杀死进程:
  2. 乱按+Enter:
  3. Ctrl_C:

 6.7本章小结

本章概述了进程的基本概念以及shell的核心功能。分析了hello进程的创建,回收,并模拟了运行中的异常。

第7章 hello的存储管理

7.1 hello的存储器地址空间

在现代操作系统与 CPU 架构中,一个进程在运行时涉及到多层次的地址转换机制。以 hello 程序为例,其执行过程中涉及以下四种地址:

1. 逻辑地址(Logical Address)

定义:由 C 语言程序或汇编代码中直接使用的地址(如变量的偏移、指令跳转目标),一般以段寄存器 + 偏移构成;

例子:main: 函数中的局部变量 i,其访问使用的是 [rbp - 4] 这类偏移值;

hello 中体现:

汇编代码中的地址如 .L3: 是逻辑地址标签;CPU 执行时先从逻辑地址转换为线性地址。

2. 线性地址(Linear Address)

定义:逻辑地址经过段表转换后的地址(在 x86-64 中段机制被“扁平化”,段基址通常为 0,所以逻辑地址 ≈ 线性地址);

例子:hello 被加载到 0x400000 虚拟地址处,main 的入口地址就是线性地址

hello 中体现:

使用 readelf -l hello 显示的 VirtAddr 就是程序的线性地址;

所有编译时定位的符号(如 printf@plt)都有确定的线性地址。

3. 虚拟地址(Virtual Address)

定义:从进程的视角看到的地址空间。通常情况下,线性地址 = 虚拟地址;

例子:在 GDB 中执行 info proc mappings 或查看 /proc//maps 得到的地址;

hello 中体现:

程序被加载到如 0x400000(.text 段),0x601000(.data 段);

用户使用 malloc() 时返回的堆地址也是虚拟地址。

4. 物理地址(Physical Address)

定义:实际访问主存时的内存地址,仅由操作系统和 MMU(内存管理单元)知道;

例子:程序中的虚拟地址如 0x601000,可能被映射到物理内存中地址 0x1234ABCD;

hello 中体现

看不到物理地址,操作系统通过四级页表将虚拟地址转换为物理地址;

若页面不在内存,还可能触发缺页异常(Page Fault)。

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

Intel x86-64 使用段式管理 + 页式管理结合的方式进行地址转换:

  1. 每条指令访问的地址叫做逻辑地址(logical address);
  2. 逻辑地址由段寄存器(如 cs, ds) + 偏移组成;
  3. 段寄存器选择段描述符,用于确定段的基址和界限;
  4. 在 64 位下,段机制被简化,大多数段基址为 0,仅保留 CS/SS 功能。

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

操作系统采用分页机制实现虚拟地址 → 物理地址映射:

  1. 虚拟地址被划分为页号 + 页内偏移;
  2. 页号作为索引,查页表得到对应的物理页框;
  3. 页内偏移保持不变,构成物理地址;
  4. hello 程序中的每个段都被分页映射到物理内存中。

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

为了加速地址转换,CPU 使用TLB(Translation Lookaside Buffer)缓存最近访问的页表项。流程如下:

  1. CPU 首先查 TLB;
  2. 如果命中 → 快速获得物理地址;
  3. 如果未命中 → 触发页表查找;
  4. 页表查找结果写入 TLB。

hello 的执行涉及大量 TLB 操作,特别是在进行 sleep()getchar() 阻塞时会发生上下文切换,可能导致 TLB 刷新。

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

现代 CPU 中,访问物理地址时会通过多级 Cache(缓存)加速:

级别

容量

作用

L1

极小

指令/数据分离

L2

中等

核心专用

L3

较大

多核共享

每一级缓存都是下一级缓存的缓存,cpu从L1开始向下顺序查找

hello 中大量使用 printf(),涉及访问 .rodata 常量区,以及函数调用栈,Cache 命中率对性能影响明显。

7.6 hello进程fork时的内存映射

执行 fork() 时,Linux 并不会立刻复制父进程的所有内存,而是采用写时复制(COW, Copy-on-Write)机制:

  1. 子进程与父进程共享物理页框;
  2. 所有页表项设为只读;
  3. 任一进程对某页写入 → 触发缺页异常;
  4. 内核复制该页,修改页表,解除共享。

7.7 hello进程execve时的内存映射

fork() 创建子进程后,execve() 被调用加载 hello 程序,此时:

  1. 内核清空原有地址空间;
  2. 加载 ELF 文件中的 .text.data.bss 段;
  3. 映射共享库(如 libc.so);
  4. 建立栈空间,传入 argc/argv
  5. 设置指令指针 IP 指向 _start,准备执行。

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

缺页中断(Page Fault)是指访问虚拟地址时,若页表项无效或不在物理内存中,CPU 将触发中断:

  1. 内核根据地址判断是否合法;
  2. 若合法,分配新页框,加载数据;
  3. 若非法,触发 SIGSEGV 信号,终止进程。

hello 中的 getchar() 调用 read(),等待用户输入,此时可能进入休眠状态,一旦用户输入,唤醒并访问输入缓冲区 → 若该页尚未加载 → 触发缺页中断 → 映射页 → 继续执行。

7.9动态存储分配管理

程序中 printf() 实际调用了 malloc() 分配内部缓冲区。C 库使用堆区管理内存,分配器采用以下机制:

  1. 使用 brk()mmap() 扩展堆;
  2. 使用空闲链表管理碎片;
  3. 分配策略常见有:首次适配、最佳适配、边界标记法等。

7.10本章小结

本章分析了 hello 程序在执行过程中涉及的存储管理机制,包括段式管理、页表映射、TLB、Cache 层级结构以及 execve 加载过程和 page fault 异常处理。通过本章学习,我系统理解了虚拟地址空间的布局与动态演化过程来观测和调试进程的内存状态。

结论

通过本课程,从一个极其简单的 C 程序出发,系统性地学习并实践了程序从源代码到最终运行所经历的完整过程,包括:预处理、编译、汇编、链接、加载、执行与退出。

在此过程中,不仅掌握了 GCC 编译工具链的使用方法,还熟练应用了诸如 readelf、objdump、gdb、strace、ps、pmap 等 Linux 下的重要调试和分析工具,深入理解了 ELF 文件结构、进程地址空间、系统调用、动态链接机制、虚拟内存与页表映射、信号传递与异常处理等计算机系统底层机制。

尤其重要的是,本课程从“用户视角”转向“系统视角”,以操作系统和硬件运行机制的角度重新审视程序的执行过程。程序不再只是“运行了就完事”,而是一个动态、复杂、结构化的过程,背后蕴含着大量隐含的系统调用、数据结构和资源调度。

这份课程不仅让我巩固了《计算机系统》课程中的理论知识,也让我体会到系统思维的重要性。只有理解了底层机制,未来我在软件开发、系统设计或性能优化等方面才能更得心应手。

附件

  1. hello.i        hello.c获得的预处理文本
  2. hello.s        hello.i经过编译得到的汇编文件
  3. hello.o       hello.s经过汇编得到的二进制可重定位目标文件
  4. hello.asm   hello.o反汇编得到的汇编文件
  5. hello1.asm hello反汇编得到的汇编文件

参考文献

[1] Randal E. Bryant, David R. O'Hallaron. Computer Systems: A Programmer's Perspective (3rd Edition). Pearson, 2015.(中文版:《深入理解计算机系统》)

[2] man7.org. ELF - Executable and Linkable Format [EB/OL]. https://man7.org/linux/man-pages/man5/elf.5.html

你可能感兴趣的:(学习方法)