摘 要
本文按照哈尔滨工业大学计算机系统课程大作业要求,以hello程序为例,从预处理编译开始,到IO管理结束,通过在Linux下用各种工具对hello程序进行分析,详细的分析了hello从程序到进程的过程,以及从程序开始从磁盘加载进入内存到程序结束退出内存的整个过程,分析了其中操作系统的处理方式,以此来加强自己对计算机系统的认识,并希望能够帮助到其它想要了解计算机系统的同学。
关键词:计算机系统;Linux;程序编译;进程管理;内存映射;虚拟内存;
第1章 概述
1.1 Hello简介
P2P(From Program to Progress):程序员敲击键盘得到程序(Program)hello.c,预处理得到hello.i,编译得到hello.s,汇编得到可重定向文件hello.o,再与其它用到的可重定向文件一起链接形成了最终的可执行程序hello.out,再bash-shell中加载程序,bash-shell会fork形成两个进程,在子进程中指向execve装载并运行hello.out,此时hello才成为了进程(Progress)。
020(From Zero to Zero):hello.out在bash-shell运行execve时与虚拟内存建立映射关系,CPU通过MMU把VA转化成PA,通过缺页处理的方式把程序从虚拟内存中加载到物理内存中,之后又加载到L3,L2,L1Cache,而bash-shell会在hello开始运行后,执行waitpid等待hello执行结束,执行结束后回收hello在内存中的所有痕迹。
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
硬件:X64 CPU;2.90GHz;32G RAM;512G SSD
软件环境:Windows 11 家庭中文版;Vmware 15.5;Ubuntu 21.04 64位
开发调试工具:VS code;gcc;gdb;
1.3 中间结果
文件名 | 描述 |
---|---|
hello.c | 源程序 |
hello.i | hello.c预处理得到的预编译处理文件 |
hello.s | hello.i编译得到的汇编文件 |
hello.o | hello.s汇编之后的可重定位目标文件 |
hello.o.elf | readelf -a hello.o |
hello.o.objdump | objdump -r -d hello.o |
testprintf.c | 测试printf调用的测试程序 |
testprintf.s | testprintf.c编译得到的文件 |
tty.c | getchar源码来自鸿蒙仓库 kernel/linux/linux-5.10/arch/x86/boot/tty.c |
dprintf.c | printf源码来自鸿蒙仓库 device/board/talkweb/niobe407/liteos_m/bsp/src/dprintf.c |
1.4 本章小结
从程序到进程,对于用户来讲,就一个双击或是一个指令的简单操作,但是其背后的操作是非常复杂的,从Program到Progress离不开编译器,也离不开shell;计算机可以长时间稳定运行也离不开从Zero到Zero的内存管理策略;我们可以流畅的运行多个程序,离不开虚拟内存的管理方案,离不开巧妙的多级缓存机制。
第2章 预处理
2.1 预处理的概念与作用
预处理(或称预编译)是指在进行编译的第一遍扫描(词法扫描和语法分析)之前所作的工作。预处理指令指示在程序正式编译前就由编译器进行的操作,可放在程序中任何位置。
当对一个源文件进行编译时,系统将自动引用预处理程序对源程序中的预处理部分作处理,处理完毕自动进入对源程序的编译。C语言提供多种预处理功能,主要处理#开始的预编译指令,如宏定义(#define)、文件包含(#include)、条件编译(#ifdef)等。合理使用预处理功能编写的程序便于阅读、修改、移植和调试,也有利于模块化程序设计。
2.2在Ubuntu下预处理的命令
在Ubuntu下,使用gcc的-E参数对程序进行预处理,还可以添加-o参数让结果重定向输入到某以特定文件中,下面例子为将hello.c预处理后,结果重定向输出到hello.i文件中。
2.3 Hello的预处理结果解析
预处理过程中,会把用#include所引入的头文件的实际地址解析出来,同时也会解析该头文件所用#include引入的头文件,一直递归解析下去。如下图所示,hello.c中用到了stdio.h,stdio.h中又用到了libc-header-start.h……
除了头文件解析工作以外,还会把程序里用#define,#ifdef等等指令,根据实际逻辑进行对程序的删减,因此在预处理后得到的文件中是不含#ifdef,#elif等等指令的。如图所示,左边的为stdio.h,右边为hello.i文件中的,snprintf,vsnprintf几乎原封不动保留了,而vasprintf因为#if条件为假,所以在预处理时直接被删除了。
2.4 本章小结
在C语言中,源代码通过gcc的-E参数可以选择只开启预编译选项生成预编译处理完成的.i文件。预编译过程中,会解析程序用到所有头文件的绝对路径,除此之外还会根据预处理器指令将不需要的代码删除等一系列操作。
第3章 编译
3.1 编译的概念与作用
把预处理的文件进行一系列的语法分析并且进行优化后生成的相应的汇编指令叫做编译,其作用为hello.i文件生成hello.s文件,方便下一步生成二进制文件。
3.2 在Ubuntu下编译的命令
gcc hello.i -S -o hello.c
3.3 Hello的编译结果解析
3.3.1 局部变量int
3.3.2 字符串常量
printf中写的格式化字符串被当作局部的字符串常量处理,globl项说明了该常量作用范围为main函数内。
3.3.3 算术操作符 ++
3.3.4 关系操作符 <
i<8在进行编译时,被解析成了i<=7,或许是为了减小编码长度,在这个例子中可能没有体现出来,但是如果8换成256,则无法用一个字节存储,而换成小于等于255就刚好可以用一个字节表示。
3.3.5 关系运算符 !=