计算机系统漫游:“Hello World”的一生

摘 要

本文根据《深入理解计算机系统》的各个章节,描述了hello.c从编译到运行,再到进程回收的整个过程,并对执行中的每个步骤都分为一章详细阐述,从而加深对计算机系统的理解。

关键词:深入理解计算机系统;进程;操作系统;虚拟内存

目 录

文章目录


**
**

第1章 概述

1.1 Hello简介

Hello程序的声明周期是从一个高级C语言程序开始的,为了在系统上运行hello.c程序,每条C语句都必须被其它程序转化为一些列的机器语言指令,最终获得可执行目标文件hello,然后在shell中输入“./hello”,加载并运行可执行目标文件hello,最后回收hello进程。

Hello的P2P过程是从程序文件hello.c开始(Program),由gcc调用cpp,ccl,as,ld,经历了预处理,编译,汇编,链接这一过程(Process),最终获得可执行目标文件hello并保存在磁盘中。

Hello的020是指hello可执行目标文件从运行到最后被回收的过程。当在shell中输入“./hello”时,shell调用fork函数创建一个子进程,然后子进程调用操作系统内核提供的execve函数,将可执行目标文件hello加载到子进程的地址空间,并设置进程上下文。加载器使用mmap函数进行内存映射,将磁盘上的hello文件的内容映射到虚拟内存页中。

1.2 环境与工具

硬件环境:CPU:Intel® Core™ i7-10875H CPU @ 2.30GHz 2.30 GHz,RAM:16.0 GB

操作系统:Windows Subsystem for Linux2(Ubuntu20.04 LTS)

开发工具:vscode、vim、gcc、gdb、edb

1.3 中间结果

列出

文件 作用
Hello.c 源代码
Hello.i 预处理后的代码
Hello.s 汇编代码
Hello.o 可重定位目标文件
Hello.o.elf.txt Hello.o的ELF
Hello.o.s Hello.o反汇编后的代码
Hello 链接后的可执行目标文件
Hello.elf.txt Hello的ELF
Obj_hello.s Hello的反汇编代码

1.4 本章小结

本章介绍了hello的P2P和020过程,描述了使用的环境与工具,列出了生成的中间文件以及它们的作用。

(第1章0.5分)

**
**

第2章 预处理

2.1 预处理的概念与作用

2.1.1 预处理的概念

预处理是编译阶段之前对源代码进行的一系列文本替换和宏扩展操作。这一过程由预处理器cpp完成。

2.1.2 预处理的作用

预处理扫描带有#的命令,并对#语句进行替换。

(1)宏定义和替换

使用#define语句定义宏,然后在代码中使用这些宏,预处理器会将这些宏替换为其定义的内容。比如:

#define PI 3.14

float area = PIradiusradius

在预处理时,PI会被替换为3.14

(2)文件包含

使用#include指令包含其他文件的内容,如头文件。预处理器会将这些文件的内容插入到包含指令的位置。比如:

#include<stdio.h>

在预处理时,stdio.h文件的内容会被插入到代码中。

(3)条件编译

使用 #ifdef、#ifndef、#if、#else、#elif 和 #endif 指令控制代码的编译条件。预处理器会根据这些条件指令决定哪些代码块会被编译。比如:

#ifdef DEBUG

xxxx

#endif

在预处理阶段,如果定义了DEBUG,那么#ifdef 与 #endif之间的语句会被保留;否则,它会被忽略。

(4)宏函数

定义带参数的宏,预处理器会进行相应的替换和展开

#define SQUARE(x) ((x) * (x))

int y = SQUARE(5);

在预处理阶段,SQUARE(5)会被替换为((5) * (5))。

2.2在Ubuntu下预处理的命令

输入命令:gcc -E hello.c -o hello.i

-E表示只激活预处理阶段,最终生成预处理后的文件hello.i

img

图2-1 hello.i部分内容

2.3 Hello的预处理结果解析

源文件hello.c:

img

预处理结果hello.i:

img

(1)外部库文件

首先,开始部分有一系列的外部库.h文件路径

img

(2)数据类型名称替换

接下来是一些typedef,前面是我们编写代码时使用的标准数据类型,后面的别名就是引入的头文件中使用的类型定义。

img

(3)内部函数声明

中间部分是很多内部函数的声明,包括系统内核提供的接口的封装:

img

(4)main函数

最后是我们的main函数代码部分:

img

2.4 本章小结

本章介绍了hello.c的预处理过程,分析了预处理结果文件hello.i的各部分内容。

可以看到,hello.i文件共有3061行,并且hello.c中的main函数被放在最后。hello.c中的#include<stdio.h>经过预处理后被替换为hello.i中3000多行的stdio.h头文件内容,这体现了预处理简化代码,增加代码可读性和可维护性的作用。可以说,hello.c中还有很多残缺不全的部分,需要经过预处理,将宏定义替换和引用的头文件内容插入到代码中,补全代码,最终得到完整的代码文件hello.i

(第2章0.5分)

**
**

第3章 编译

3.1 编译的概念与作用

编译阶段将预处理的结果文件hello.i转换为汇编代码,汇编器作为这一阶段的核心,对hello.i进行了语法检验,语义分析和优化,最终生成了汇编代码文件hello.s

3.2 在Ubuntu下编译的命令

gcc -S hello.i -o hello.s

-S表示只激活到编译过程。

3.3 Hello的编译结果解析

接下来对C语言中数据类型及各种操作如何编译到汇编代码进行解析。

3.3.1 常量

(1)字符型常量

img

hello.c中的printf打印了一个字符串,这个字符串常量存放在.LC0中

img

(2)其他常量

还有一些常量以立即数出现在汇编代码中,比如if判断中有一个整数5:

img

它对应的汇编代码:

img

cmpl 比较argc与5是否相等,如果相等,则跳至.L2,;否则,将调用exit函数退出程序。

3.3.2 变量与运算

(1)局部变量

局部变量存储在寄存器或者栈中。

hello.c中有一个局部变量:

img

在汇编代码中,int i存放在栈中-4(%rbp)处:

img

(2)算术运算

在for循环中,局部变量i的值每次加1:

img

在汇编代码中,这个运算由addl指令完成:

img

3.3.3 数组/指针操作

main函数的参数中,有一个字符串数组argv:

img

根据代码:

img

在汇编代码中,%rdi向sleep函数传递参数,栈中%rbp指向的栈空间存放的内容是argv[4]的地址。

img

3.3.4 控制转移

for循环中,每次都需要比较i<10,对应的汇编代码是cmpl指令,判断i是否小于等于9,如果是,则继续执行循环体;否则,跳出循环:

img

3.3.5 函数调用与返回

(1)main函数

传入参数为argc、argv,参数从shell中传入,返回值设置为0

(2)printf函数

设置%rdi和%rsi的值来传入参数:

img

(3)exit函数

设置%rdi和%rsi的值来传入参数:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值