欢迎光临
我们一直在努力

程序人生-Hello’s P2P

第1章 概述

1.1 Hello简介

P2P过程:Hello最初以ASCII文本形式存在于hello.c文件中,在经过预处理,编译,汇编,链接过程后,将创建进程,利用fork函数和execve函数创建子进程,加载可执行文件并替换地址空间。

020过程:从零开始,从磁盘中加载,进行初始化后进入指令周期,并被系统调用,进行I/O管理后归于零。

1.2 环境与工具

在编写本论文过程中,软件环境为Vmware17 Pro,使用Visual studio 2022 进行开发,GCC工具进行调试。

1.3 中间结果

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

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

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

hello.elf: hello.o的elf格式

hello:链接后得到的可执行目标文件

Hello.elf:hello的elf格式

Hello.asm: 反汇编hello得到的反汇编文件

1.4 本章小结

   本章介绍了hello程序的P2P 020过程,并介绍了在完成本论文过程中所使用的软硬件环境,开发调试工具。

第2章 预处理

2.1 预处理的概念与作用

概念:预处理是指编译器在正式编译之前,由预处理器(cpp)对源代码进行的文本处理阶段。它不分析代码的语法结构,而是进行纯文本替换和操作。

作用:将头文件内容插入源文件,定义常数或函数式宏,控制代码编译范围

2.2在Ubuntu下预处理的命令

预处理命令如下:gcc -E hello.c -o hello.i

2.3 Hello的预处理结果解析

  对比hello.c和hello.i,可知程序被处理后体积显著膨胀,在头文件中,通过studio.h引入了完整的标准输入输出体系,包含呈现典型的树状结构,形成五级嵌套的声明网络。原函数中的所有宏定义,均被实际的值替换。

2.4 本章小结

本章介绍了预处理的作用,并成功得到了hello.c的预处理文件。

第3章 编译

3.1 编译的概念与作用

概念:此处阶段的编译特指将预处理后的源代码(.i文件)转换为汇编代码(s文件)的过程

作用:将高级语言的抽象逻辑转换为底层硬件相关的汇编指令,删除冗余计算,根据目标CPU架构生成对应的指令集。

3.2 在Ubuntu下编译的命令

命令:gcc -S hello.i -o hello.s  

3.3 Hello的编译结果解析

  3.3.1数据类型处理分析

(1)字符串常量处理

– 编译器将字符串常量存储在.rodata只读数据段

– 中文字符被转换为UTF-8编码的八进制序列

(2)指针与数组处理

– 命令行参数`char *argv[]`被转换为二级指针:

  movq %rsi, -32(%rbp)

– 数组访问被优化为指针运算:

  addq $8, %rax         

  movq (%rax), %rsi      

 (3)整型变量处理

– 循环变量i(4字节int)存储在栈帧-4(%rbp)处

– 使用32位寄存器操作:

  addl $1, -4(%rbp)      

  cmpl $9, -4(%rbp)       

3.3.2关键操作实现

(1)算术运算

– 指针运算保持原始数据类型宽度:

  addq $24, %rax        

– 整型运算使用对应位宽指令:

  addl $1, -4(%rbp)   

 (2)控制流实现

– 条件跳转使用比较+条件跳转指令对:

  cmpl $5, -20(%rbp)    

  je .L2              

– 循环结构转换为底部判断模式:

  .L3:

  cmpl $9, -4(%rbp)

  jle .L4              

3.3.3系统调用封装

(1)库函数调用

使用PLT(过程链接表)实现动态链接:

  call printf@PLT

  call sleep@PLT

  • 类型转换处理
  •  atoi转换显示栈操作:

      movq (%rax), %rdi    

      call atoi@PLT      

      movl %eax, %edi

    3.3.4   完整数据流示例

    C代码片段:

    printf("Hello %s %s %s\\n",argv[1],argv[2],argv[3]);

    对应汇编实现:

    movq -32(%rbp), %rax   

    addq $8, %rax          

    movq (%rax), %rsi      

    addq $16, %rax         

    movq (%rax), %rdx     

    addq $24, %rax        

    movq (%rax), %rcx      

    leaq .LC1(%rip), %rd

    call printf@PLT

    该实现展示了编译器如何:

    1. 维护指针的正确偏移计算

    2. 按调用约定安排参数寄存器

    3. 处理可变参数函数的特殊要求

    3.4 本章小结

    本章介绍了编译的概念与作用,得到了hello.i编译后的文件hello.s,并对其进行了解析。

    第4章 汇编

    4.1 汇编的概念与作用 

    概念:汇编是将汇编语言程序转换为机器语言二进制程序的过程,由汇编器完成。这一过程是编译流程中的关键步骤,实现从人类可读的助记符到计算机可执行指令的最终转换。

    作用:将汇编代码转换为机器代码,记录标签,函数名等生成符号链表供连接器使用,标记需要在链接阶段确定的地址,将代码、数据分类储存在目标文件的特定节区。

    4.2 在Ubuntu下汇编的命令

    指令:gcc -c hello.s -o hello.o

    4.3 可重定位目标elf格式

    利用指令readelf -S hello.o分析hello.o的ELF格式,结果如下:

  • 代码与数据节区
  •  节区编号    名称   类型      大小     标志    对齐      作用

    [1]       .text  PROGBITS   0xa3     AX     1     存储程序机器指令

    [3]      .data  PROGBITS   0x0      WA    1     已初始化全局数据

    [4]       .bss   NOBITS     0x0     WA    1     未初始化全局数据

     [5]     .rodata PROGBITS   0x40      A     8     只读数据

     节区编号    名称    类型      大小     链接目标     作用

     [2]      .rela.text   RELA     0xc0    11→.symtab  代码段重定位条目

    [10]     .rela.eh_frame RELA   0x18     11→.symtab  异常帧重定位条目

    [11]     .symtab     SYMTAB  0x108  12→.strtab      符号表          

    [12]     .strtab      STRTAB    0x32             符号名称字符串表

    重定位类型:

    类型名                     触发场景                修正方式

     R_X86_64_PLT32         调用外部函数        替换为PLT条目相对地址        

    R_X86_64_PC32      访问.rodata中的字符串  计算目标与PC的偏移量         |

    R_X86_64_32              绝对地址引用        直接替换为符号最终地址        

    4.4 Hello.o的结果解析

    以下是对helllo.o进行反汇编的结果

    可见该结果与hello.s中使用的汇编代码是一样的,但是夹杂了一些机器代码。每一条汇编语言都可以用机器二进制数据来表示,汇编语言中的操作和操作数和机器语言进行一一对应,使机器能够理解代码的含义并进行执行。

    4.5 本章小结

      本章介绍了从hello.s到hello.o的过程,解释了hello.o的ELF形式,并对hello.o进行了反汇编以进行解释,找到机器代码和汇编代码的区别。

    第5章 链接

    5.1 链接的概念与作用

    概念:由编译器将源代码编译生成的中间文件,包含机器代码和符号表。

    作用: 将多个目标文件和所需的库合并,解析它们之间的符号引用,生成一个完整的、可加载执行的文件。

    5.2 在Ubuntu下链接的命令

    命令为

       

    ld-ohello-dynamic-linker/lib64/ld-linux-x86-64.so.2/usr/lib/x86_64-linux-gnu/crt1.o/usr/lib/x86_64-linux-gnu/crti.ohello.o/usr/lib/x86_64-linux-gnu/libc.so/usr/lib/x86_64-linux-gnu/crtn.o

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

    对hello文件的ELF头进行分析:

    查看所有段:

    5.4 hello的虚拟地址空间

    在gdb中查看:

        

    关键段:0x400000-0x401000:代码段(对应readelf -l 中的LOAD段)。

    0x600000-0x601000:只读数据段。

    0x601000-0x602000:可读写数据段。

    [heap]和 [stack]:动态分配的堆和栈空间。  

    5.5 链接的重定位过程分析

    得到的结果如下:

    hello.o中,符号地址并未全部解析,存在重定位项,无动态链接信息,而在hello中,符号地址已解析为实际虚拟地址,重定位项已处理,存在动态链接信息

    5.6 hello的执行流程

    入口地址

    main函数地址

    终止点地址

    5.7 Hello的动态链接分析

      未运行时观察PLT项:

    0x404020(GOT)初始指向PLT中的解析例程。

    运行后观察GOT项的变化

    5.8 本章小结

    本章介绍了链接的作用,解释了程序如何进行重定位的操作,说明了链接的工作原理。

    第6章 hello进程管理

    6.1 进程的概念与作用

    概念:进程是程序的执行实例,是操作系统进行资源分配和调度的基本单位。每个进程拥有独立的地址空间、文件描述符、寄存器状态等资源。  

    作用:资源隔离、并发执行、系统管理

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

     作用:是用户与操作系统内核的交互接口,解释并执行用户输入的命令。  

    流程:1. 读取输入:通过`readline库获取用户命令。  

    2. 解析命令:分割为命令名和参数。  

    3. 执行命令:  

       内置命令直接执行。  

       外部程序:通过fork()创建子进程,execve()加载程序。  

    4. 等待结果:父进程(Shell)通过waitpid()等待子进程结束。  

    6.3 Hello的fork进程创建过程

    1. 调用fork():  

       Shell调用fork()创建子进程,复制父进程的PCB、文件描述符等。  

       子进程获得独立的PID,返回值为0(父进程返回子进程PID)。  

    2. 内存复制:  

       采用写时复制(Copy-On-Write)技术,初始共享物理内存,修改时再复制。  

    3. 执行hello:  

       子进程调用execve()加载hello的代码段和数据段,替换原有内存空间。  

    6.4 Hello的execve过程

    1. 参数传递:  

       Shell将argv[]传递给execve。  

    2. 加载程序:  

       – 内核读取ELF头部,映射代码段和数据段到内存。  

       – 动态链接器析共享库依赖。  

    3. 设置入口点:  

       跳转到_start(程序入口),最终调用main()。  

    6.5 Hello的进程执行

    1. 时间片分配:  

       默认时间片10ms,通过时钟中断触发调度。  

    2. 用户态→内核态:  

       发生系统调用或中断时,CPU切换到内核态。  

    3. 上下文切换:  

       保存当前进程寄存器状态到PCB,加载下一个进程的PCB。  

    6.6 hello的异常与信号处理

     Ctrl+Z,发送一个SIGTSTP信号,导致挂起进程

    Ctrl+C,发送一个SIGINT信号,导致进程终止

    回车,会导致进程被读取

    6.7本章小结

    在本章中,探讨了进程管理的实现方式,并且了解了shell如何进行信号的处理,理解了shell是如何作用的。

    第7章 hello的存储管理

    7.1 hello的存储器地址空间

    1. 逻辑地址:  

       程序员可见的地址。  

    2. 线性地址:  

       现代Linux默认平坦模式,逻辑地址直接作为线性地址。  

    3. 虚拟地址:  

       用户态看到的地址,通过页表映射到物理地址。  

    4. 物理地址:  

       由MMU通过页表转换得到。  

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

    1. 段选择子:  

       16位寄存器(CS/DS等)指向GDT(全局描述符表)条目。  

    2. 段描述符:  

       包含段基址和界限。  

    3. 线性地址计算:   

       线性地址 = 段基址 + 逻辑地址偏移量  

  • Linux简化处理:  
  • 所有段基址设为0,逻辑地址=线性地址)。  

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

    1. 虚拟地址划分:  

       [47:39] PML4索引 → [38:30] PDPT索引 → [29:21] PD索引 → [20:12] PT索引 → [11:0] 页内偏移  

    2. 查表过程:  

       CR3寄存器指向PML4表基址,逐级索引找到物理页框号。  

    3. 物理地址生成:    

       物理地址 = PFN × 4KB + 页内偏移  

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

    1. TLB查找:  

       用虚拟地址高位作为Tag查询TLB,命中则直接获取PFN。  

    2. TLB未命中:  

       遍历四级页表,更新TLB缓存。    

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

    1. CPU访问0x7fa030(物理地址)。  

    2. 依次查询L1→L2→L3 Cache,未命中则访问DRAM。  

    3. 缓存行大小通常为64B,对齐访问提升效率。

    7.6 hello进程fork时的内存映射

    1. 共享物理页:  

       父进程和子进程的页表项指向相同的物理页,标记为只读。  

    2. 触发复制:  

       任一进程尝试写入时,触发缺页异常,内核分配新物理页并复制内容。    

    7.7 hello进程execve时的内存映射

    1. 释放旧空间:  

       清除非共享的内存区域(堆、栈等)。  

    2. 加载新程序:  

       映射hello的代码段到0x400000,数据段到0x601000。  

    3. 动态链接

       加载ld-linux和libc.so到共享库区域。  

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

    1. 异常触发:CPU陷入内核态,保存上下文。  

    2. 查VMA:确认地址合法性。  

    3. 分配物理页:  

       文件映射:从磁盘读取(如代码段);  

       匿名映射:清零新页(如堆内存)。  

    4. 更新页表:建立虚拟→物理映射,返回用户态继续执行。  

    7.9本章小结

    本章讨论了地址转换,内存映射,动态内存等一系列的问题,有助于了解程序运行的本质。

    第8章 hello的IO管理

    8.1 Linux的IO设备管理方法

    设备的模型化:文件

    Linux将所有IO设备抽象为文件,通过文件系统统一管理。设备文件存放在/dev目录下,分为:  

    字符设备(如键盘):按字符流传输,支持顺序访问。  

    块设备如磁盘):按数据块传输,支持随机访问。  

    设备管理:Unix IO接口  

    通过统一的文件操作接口管理设备,屏蔽硬件差异,实现“一切皆文件”的哲学。  

    8.2 简述Unix IO接口及其函数

    Unix IO接口提供以下核心函数:  

    1. open:打开设备文件,返回文件描述符。   

       int open(const char *pathname, int flags);  

    2. read:从设备读取数据。   

       ssize_t read(int fd, void *buf, size_t count);  

    3. write:向设备写入数据。  

       ssize_t write(int fd, const void *buf, size_t count);   

    4. close:关闭设备文件。   

       int close(int fd);

    5. ioctl:控制设备的特定功能(如设置串口波特率)。  

       int ioctl(int fd, unsigned long request, …);  

    8.3 printf的实现分析

    1. 格式化字符串:  

       printf调用vsprintf,将可变参数按格式字符串转换为字符串,存入缓冲区。  

    2. 系统调用:  

       调用write系统函数,通过陷阱指令进入内核态。  

    3. 字符显示驱动:  

       – 从ASCII码查询字模库(如位图字体),生成像素点阵。  

       – 将像素信息写入显存(VRAM),存储每个像素的RGB值。  

    4. 屏幕刷新:  

       显示芯片按刷新频率逐行读取VRAM,通过信号线输出到显示器。  

    8.4 getchar的实现分析  

    1. 键盘中断:  

       按键触发硬件中断,CPU跳转到键盘中断处理程序。  

    2. 扫描码转换:  

       中断程序将键盘扫描码转换为ASCII码,存入内核缓冲区。  

    3. 系统调用:  

       getchar调用read系统函数,从缓冲区读取ASCII码:  

       – 若缓冲区为空,则阻塞等待输入。  

       – 遇到回车符时返回数据。

    8.5本章小结

    本章探讨了Linux系统的IO设备管理办法,Linux系统通过文件抽象IO设备,简化管理,并解释了两种函数的底层逻辑。

    结论

    Hello程序在计算机系统中的执行过程

    从源代码到屏幕输出,hello程序经历了以下关键步骤:  

    1. 源代码编译

    预处理:  

      #include <stdio.h>等指令被展开,生成纯C代码。  

    编译:  

      编译器将C代码转换为汇编代码,仍为人类可读文本。  

    汇编:  

      汇编器将汇编代码转换为机器指令,生成可重定位目标文件。  

    2. 链接  

    链接器合并hello.o与libc.so,解析符号引用,生成可执行文件。  

    关键点:动态链接库在运行时加载,静态链接库直接嵌入可执行文件。  

    3. 加载与执行

    Shell调用execve:  

      Shell通过fork创建子进程,子进程调用execve加载a.out,替换为hello的代码段、数据段。  

    虚拟内存映射:  

      操作系统为进程分配虚拟地址空间,映射代码、数据、堆栈等段(通过页表管理物理内存)。  

    4. 运行

    printf的执行:  

      1. 调用vsprintf格式化字符串,存入用户态缓冲区。  

      2. 触发write系统调用,切换到内核态。  

      3. 内核检查文件描述符,调用显示驱动。  

    5. 显示输出(

    字符渲染:  

      1. 驱动从字模库获取ASCII对应的像素矩阵,写入显存(VRAM)。  

      2. 显卡控制器逐帧扫描VRAM,通过视频信号(如HDMI)输出到显示器。

    计算机系统设计与实现的深切感悟  

    1. 计算机系统的分层抽象, 从高级语言到机器指令,每一层隐藏底层细节。  

    2. 硬件与软件的协同,硬件为操作系统提供基础功能。  

    3.安全与隔离,进程间通过虚拟内存隔离,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.

    赞(0)
    未经允许不得转载:171主机测评 » 程序人生-Hello’s P2P
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址