哈尔滨工业大学计算机系统大作业

摘  要

本篇研究了hello.c这一C语言文件在LINUX下的整个运行周期,依次深入从预处理、编译、汇编、链接到进程管理、储存管理的完整过程,对hello的一生有了深入了解,从而加深对计算机系统的理解。

关键词:计算机系统;深入理解计算机系统;CSAPP;                           

目  录

第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:From Program to Process要运行hello.c,首先要对hello.c进行预处理、编译、汇编、链接,从而产生可执行文件,然后可以在shell中创建进程并运行。

020:From Zero-0 to Zero-0shell通过execve函数和fork函数创建子进程并将hello载入,通过内存映射等为其分配自己的运行空间,CPU在流水线上依次执行每一条指令,内存管理系统利用虚拟内存地址从TLB或高速缓存中取出相应数据,如果出现不命中则继续从其下一级缓存中读取数据。最后程序运行结束后,shell接受到相应的信号,启动信号处理机制,对该进程进行回收处理,释放其所占的内存并删除有关进程上下文(context),hello程序重新回归0

1.2 环境与工具

硬件环境:X64 CPU;2GHz;2G RAM;256GHD Disk 以上

软件环境:VMware11;Ubuntu16.04

开发与调试工具:vi/vim/gedit+gcc

1.3 中间结果

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

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

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

elf.txt:用readelf读取hello.o

dump_hello.txt:hello的反汇编代码

helloelf.txt:用readelf读取hello

hello_objdump.s:hello.o的反汇编代码

1.4 本章小结

本章对hello的一生进行了简要的介绍和描述,介绍了P2P的整个过程,介绍了本计算机的硬件环境、软件环境、开发工具,介绍了为编写本论文的中间文件的名称和其作用。

第2章 预处理

2.1 预处理的概念与作用

概念:预处理为编译做准备,处理#开头的指令,进行代码文本的替换工作。

作用:预处理器(cpp)根据以字符#开头的命令,修改原始的C程序。结果就得到了另一个C程序,通常是以.i作为文件扩展名。通过预处理指令,可以使用已经封装好的库函数,极大地提高了编程效率。

2.2在Ubuntu下预处理的命令

2.3 Hello的预处理结果解析

2.4 本章小结

本节阐述了预处理的概念和作用,并在Ubuntu下进行预处理生成了hello.i文件,对hello.i文件进行预览,更好理解了预处理的作用。

第3章 编译

3.1 编译的概念与作用

概念:编译器将hello.i翻译成文本文件hello.s。

作用:将高级语言转变为汇编语言,并检查语法错误。

3.2 在Ubuntu下编译的命令

3.3 Hello的编译结果解析

3.3.1

两个字符串都存在.rodata里

3.3.2

把argc放到栈中$rbp-20位置,argv首地址放到%rbp-32位置M[%rbp-32+16]、M[%rbp-32+8]、M[%rbp-32]分别是argv[1],argv[2],argv[3]的首地址。

3.3.3main程序中

首先定义整形i

汇编中将i保存在栈中%rbp-4的位置上

然后比较argc与4,若不相等

将.LC0中存放的字符串放入%rdi中,调用puts打印,并将%edi设为1,传参给exit,条件满足,调用exit。

若argc与4相等,

跳转至.L2

将i设初值为0然后跳转至.L3

比较i与8的大小,若小于等于则跳至.L4

进行i<9条件判断

而i++操作由进行

如果条件成立,跳转至.L4

打印.LC1并用argv[1]和argv[2]替换.LC1的两个%s

然后根据%rbp-32找到argv[3]的首地址,调用atoi函数传入argv[3]的首地址将argv[3]由字符串转换成整数,将这个整数作为参数传给sleep函数,最后将%rbp-4的值也就是i+1,也就是完成i++操作

直到i<9判断失败调用getchar

3.4 本章小结

本章介绍了汇编的概念及作用,通过hello函数分析了c语言如何转换为汇编语言及其如何实现变量,常量,传参,分支,循环。

第4章 汇编

4.1 汇编的概念与作用

概念:驱动程序运行汇编器as,将汇编语言的ASCII码文件翻译成机器语言的可重定位目标文件的过程

作用:将高级语言转化为机器可直接识别执行的代码文件

4.2 在Ubuntu下汇编的命令

4.3 可重定位目标elf格式

1.命令

   

2.ELF头

3.节头

4.重定位节

5.符号表

4.4 Hello.o的结果解析

在数的表示上,hello.s用的是十进制表示,而反汇编得到的操作数为十六进制。

控制转移上,hello.s用.L2,.L4,.L3等段名称转移,而反汇编代码使用虚拟地址转移。

函数调用时,hello.x直接call函数名称,而反汇编call目标函数的虚拟地址。

4.5 本章小结

经过汇编器,汇编语言转换为机器语言,对可重定位目标文件elf格式研究,使用了readelf命令,接触到了elf头,节头部表,重定位节,符号表。通过对比hello.s与hello.o反汇编得到的代码理解了汇编语言到机器语言的变化。

5章 链接

5.1 链接的概念与作用

概念:链接是将不同文件的代码和数据综合到一起,通过符号解析和重定位过程,最终组合成一个可以在程序中加载和运行的单一的可执行目标文件的过程。

作用:链接使分离编译成为可能,方便了程序的修改和编译:无需重新编译整个工程,而是仅编译修改的文件。

5.2 在Ubuntu下链接的命令

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

节头

ELF头

描述了各个节的大小、偏移量和其他属性。链接器链接时,会将各个文件的相同段合并成一个大段,并且根据这个大段的大小以及偏移量重新设置各个符号的地址。

5.4 hello的虚拟地址空间

     

5.5 链接的重定位过程分析

1.新增节

2.新增函数

3.实现了调用函数时的重定位

4.控制流跳转地址

调用函数时已经是函数确切的虚拟地址

链接的过程就是链接器将各个目标文件组装在一起,文件中的各个函数段按照一定规则累积在一起。从.o提供的重定位条目将函数调用和控制流跳转的地址填写为最终的地址。

5.6 hello的执行流程

5.7 Hello的动态链接分析              

     

5.8 本章小结

本章研究了链接的过程,通过edb查看hello的虚拟地址空间,对比hello与hello.o的反汇编代码,深入研究了链接的过程中重定位的过程

6章 hello进程管理

6.1 进程的概念与作用

概念:进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

作用:进程提供给应用程序的关键抽象:一个独立的逻辑控制流,如同程序独占处理器;一个私有的地址空间,如同程序独占内存系统。可以说,如果没有进程,体系如此庞大的计算机不可能设计出来。

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

作用:Shell 应用程序提供了一个界面,用户可以通过这个界面进行系统的基本操作,访问操作系统内核的服务。

处理流程:1.从Shell终端读入输入的命令。

2.切分输入字符串,获得并识别所有的参数

3.若输入参数为内置命令,则立即执行

4.若输入参数并非内置命令,则调用相应的程序为其分配子进程并运行

5.若输入参数非法,则返回错误信息

6.处理完当前参数后继续处理下一参数,直到处理完毕

6.3 Hello的fork进程创建过程

(1)给新进程分配一个标识符

(2)在内核中分配一个PCB,将其挂在PCB表上

(3)复制它的父进程的环境(PCB中大部分的内容)

(4)为其分配资源(程序、数据、栈等)

(5)复制父进程地址空间里的内容(代码共享,数据写时拷贝)

(6)将进程置成就绪状态,并将其放入就绪队列,等待CPU调度。

6.5 Hello的进程执行

6.5.1 逻辑控制流和时间片:

进程的运行本质上是CPU不断从程序计数器 PC 指示的地址处取出指令并执行,值的序列叫做逻辑控制流。操作系统会对进程的运行进行调度,执行进程A->上下文切换->执行进程B->上下文切换->执行进程A->… 如此循环往复。 在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程,这种决策就叫做调度,是由内核中称为调度器的代码处理的。当内核选择一个新的进程运行,我们说内核调度了这个进程。在内核调度了一个新的进程运行了之后,它就抢占了当前进程,并使用上下文切换机制来将控制转移到新的进程。在一个程序被调运行开始到被另一个进程打断,中间的时间就是运行的时间片。

6.5.2 用户模式和内核模式:

用户模式的进程不允许执行特殊指令,不允许直接引用地址空间中内核区的代码和数据。

内核模式进程可以执行指令集中的任何命令,并且可以访问系统中的任何内存位置。

6.5.3 上下文:

上下文就是内核重新启动一个被抢占的进程所需要恢复的原来的状态,由寄存器、程序计数器、用户栈、内核栈和内核数据结构等对象的值构成。

6.5.4 调度的过程:

在对进程进行调度的过程,操作系统主要做了两件事:加载保存的寄存器,切换虚拟地址空间。

6.5.5 用户态与核心态转换:

为了能让处理器安全运行,需要限制应用程序可执行指令所能访问的地址范围。因此划分了用户态与核心态。

核心态可以说是拥有最高的访问权限,处理器以一个寄存器当做模式位来描述当前进程的特权。进程只有故障、中断或陷入系统调用时才会得到内核访问权限,其他情况下始终处于用户权限之中,保证了系统的安全性。

6.6 hello的异常与信号处理

异常类型与处理方式:

   

发送信号:

  1. CTRL+Z

进程收到 SIGSTP 信号, hello 进程挂起。用ps查看其进程PID,helloPID为3292

使用jobs查看hello后台job号为1,调用fg将其调回前台

  1. CTRL+C

hello被终止,在ps中也查不到PID,jobs中也没有显示

  1. 中途乱按

只是将屏幕的输入缓存到缓冲区。乱码被认为是命令。

4.KILL

   

挂起的进程被终止,在ps中无法查到到其PID。

6.7本章小结

本章了解了hello进程的执行过程。在hello运行过程中,内核对其调度,异常处理程序为其将处理各种异常。每种信号都有不同的处理机制,对不同的shell命令,hello也有不同的响应结果。

7章 hello的存储管理

7.1 hello的存储器地址空间

7.1.1 逻辑地址

逻辑地址(Logical Address)是指由程序产生的与段相关的偏移地址部分。在这里指的是hello.o中的内容。

7.1.2 线性地址

线性地址(Linear Address)是逻辑地址到物理地址变换之间的中间层。程序hello的代码会产生段中的偏移地址,加上相应段的基地址就生成了一个线性地址。

7.1.3 虚拟地址

CPU启动保护模式后,程序hello运行在虚拟地址空间中。注意,并不是所有的“程序”都是运行在虚拟地址中。CPU在启动的时候是运行在实模式的,Bootloader以及内核在初始化页表之前并不使用虚拟地址,而是直接使用物理地址的。

7.1.4 物理地址

放在寻址总线上的地址。放在寻址总线上,如果是读,电路根据这个地址每位的值就将相应地址的物理内存中的数据放到数据总线中传输。如果是写,电路根据这个地址每位的值就在相应地址的物理内存中放入数据总线上的内容。物理内存是以字节(8位)为单位编址的。

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

在 Intel 平台下,逻辑地址(logical address)是 selector:offset 这种形式,selector 是 CS 寄存器的值,offset 是 EIP 寄存器的值。如果用 selector 去 GDT( 全局描述符表 ) 里拿到 segment base address(段基址) 然后加上 offset(段内偏移),这就得到了 linear address。我们把这个过程称作段式内存管理。

一个逻辑地址由段标识符和段内偏移量组成。段标识符是一个16位长的字段(段选择符)。可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段。
全局的段描述符,放在“全局段描述符表(GDT)”中,一些局部的段描述符,放在“局部段描述符表(LDT)”中。
给定一个完整的逻辑地址段选择符+段内偏移地址,看段选择符的T1=0还是1,知道当前要转换是GDT中的段,还是LDT中的段,再根据相应寄存器,得到其地址和大小。拿出段选择符中前13位,可以在这个数组中,查找到对应的段描述符,就得到了其基地址。Base + offset = 线性地址。

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

页式管理是一种内存空间存储管理的技术,页式管理分为静态页式管理和动态页式管理。将各进程的虚拟空间划分成若干个长度相等的页(page),页式管理把内存空间按页的大小划分成片或者页面(page frame),然后把页式虚拟地址与内存地址建立一一对应页表,并用相应的硬件地址变换机构,来解决离散地址变换问题。页式管理采用请求调页或预调页技术实现了内外存存储器的统一管理。

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

7.4.1 翻译后备缓冲器

每次CPU产生一个虚拟地址,MMU(内存管理单元)就必须查阅一个PTE(页表条目),以便将虚拟地址翻译为物理地址。在最糟糕的情况下,这会从内存多取一次数据,代价是几十到几百个周期。如果PTE碰巧缓存在L1中,那么开销就会下降1或2个周期。然而,许多系统都试图消除即使是这样的开销,它们在MMU中包括了一个关于PTE的小的缓存,称为翻译后备缓存器(TLB)。

7.4.2 多级页表:

将虚拟地址的VPN划分为相等大小的不同的部分,每个部分用于寻找由上一级确定的页表基址对应的页表条目。

7.4.3 VA到PA的变换

处理器生成一个虚拟地址,并将其传送给MMU。MMU用VPN向TLB请求对应的PTE,如果命中,则跳过之后的几步。MMU生成PTE地址(PTEA).,并从高速缓存/主存请求得到PTE。如果请求不成功,MMU向主存请求PTE,高速缓存/主存向MMU返回PTE。PTE的有效位为零, 因此 MMU触发缺页异常,缺页处理程序确定物理内存中的牺牲页 (若页面被修改,则换出到磁盘——写回策略)。缺页处理程序调入新的页面,并更新内存中的PTE。缺页处理程序返回到原来进程,再次执行导致缺页的指令。

在多级页表的情况下,无非就是不断通过索引 – 地址 – 索引 - 地址重复四次进行寻找。

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

CPU发送一条虚拟地址,随后MMU按照7.4所述的操作获得了物理地址PA。根据cache大小组数的要求,将PA分为CT(标记位)CI(组索引),CO(块偏移)。根据CI寻找到正确的组,依次与每一行的数据比较,有效位有效且标记位一致则命中。如果命中,直接返回想要的数据。如果不命中,就依次去L2,L3,主存判断是否命中,命中时将数据传给CPU同时更新各级cache的储存。

7.6 hello进程fork时的内存映射

当fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID,同时为这个新进程创建虚拟内存,创建当前进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记位只读,并将两个进程中的每个区域结构都标记为私有的写时复制。

当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面。

7.7 hello进程execve时的内存映射

1)在bash中的进程中执行了如下的execve调用:execve("hello",NULL,NULL);

2)execve函数在当前进程中加载并运行包含在可执行文件hello中的程序,用hello替代了当前bash中的程序。

3)删除已存在的用户区域。

4)映射私有区域

5)映射共享区域

6)设置程序计数器(PC)

最后,exceve设置当前进程的上下文中的程序计数器到代码区域的入口点。

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

处理器生成一个虚拟地址,并将它传送给MMU

MMU生成PTE地址,并从高速缓存/主存请求得到它

高速缓存/主存向MMU返回PTE

PTE中的有效位是0,所以MMU出发了一次异常,传递CPU中的控制到操作系统内核中的缺页异常处理程序。

缺页处理程序确认出物理内存中的牺牲页,如果这个页已经被修改了,则把它换到磁盘。

缺页处理程序页面调入新的页面,并更新内存中的PTE

缺页处理程序返回到原来的进程,再次执行导致缺页的命令。CPU将引起缺页的虚拟地址重新发送给MMU。因为虚拟页面已经换存在物理内存中,所以就会命中。

7.9动态存储分配管理

动态储存分配管理使用动态内存分配器(如malloc)来进行。动态内存分配器维护着一个进程的虚拟内存区域,称为堆。分配器将堆视为一组不同大小的块的集合。每个块就是一个连续的虚拟内存页,要么是已分配的,要么是空闲的。

已分配的块显式地保留为供应用程序使用。

空闲块保持空闲,直到它显式地被应用所分配。

一个已分配的块保持已分配的状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。动态内存分配主要有两种基本方法与策略:

7.9.1 带边界标签的隐式空闲链表分配器管理

带边界标记的隐式空闲链表的每个块是由一个字的头部、有效载荷、(可能的)额外填充以及一个字的尾部组成。

隐式空闲链表:空闲块通过头部的大小字段隐含地连接着。分配器遍历堆中所有的块,间接地遍历整个空闲块的集合。

当一个应用请求一个k字节的块时,分配器搜索空闲链表,查找一个足够大的可以放置所请求块的空闲块。分配器有三种放置策略:首次适配、下一次适配和最佳适配。分配器在面对释放一个已分配块时,可以合并相邻的空闲块,其中一种简单的方式,是利用隐式空闲链表的边界标记来进行合并。

7.9.2 显式空间链表管理

显式空闲链表是将堆的空闲块组织成一个双向链表,在每个空闲块中,都包含一个前驱与一个后继指针。进行内存管理。在显式空闲链表中。可以采用后进先出的顺序维护链表,将最新释放的块放置在链表的开始处,也可以采用按照地址顺序来维护链表,其中链表中每个块的地址都小于它的后继地址,在这种情况下,释放一个块需要线性时间的搜索来定位合适的前驱。

7.10本章小结

本章介绍了存储器地址空间、段式管理、页式管理,VA 到 PA 的变换、物理内存访问, hello 进程fork时和execve 时的内存映射、缺页故障与缺页中断处理、包括隐式空闲链表和显式空闲链表的动态存储分配管理。

8章 hello的IO管理

8.1 Linux的IO设备管理方法

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

8.2 简述Unix IO接口及其函数

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

8.3 printf的实现分析

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

8.4 getchar的实现分析

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

异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。

getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。

8.5本章小结

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

(第81分)

结论

hello.c预处理到hello.i

hello.i编译到hello.s

hello.s汇编到hello.o

hello.o链接生成可执行文件hello

bash进程调用fork函数,生成子进程

execve函数加载运行当前进程的上下文并运行新程序hello

hello最终被Shell父进程回收,内核回收创建的所有信息

这门课程的学习让我意识到不能忽略底层,只盯着顶层的实现!

附件

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

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

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

elf.txt:用readelf读取hello.o

dump_hello.txt:hello的反汇编代码

helloelf.txt:用readelf读取hello

hello_objdump.s:hello.o的反汇编代码

参考文献

 [1] 《深入理解计算机系统》

[2]  进程的创建过程(fork函数)_lyl194458的博客-CSDN博客

[3]  EDB的简单使用_ciahi的博客-CSDN博客.

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值