哈工大 csapp大作业 120L021917

计算机系统

大作业

题     目  程序人生-Hello’s P2P 

专       业        计算学部               

学     号        120L021917               

班     级        2003006               

学       生           艾浩林          

指 导 教 师         吴锐              

计算机科学与技术学院

2021年5月

摘  要

本文通过分析hello程序从C文件如何转变为可执行文件的全过程,包括预处理、编译、汇编、链接阶段,每一步如何对上一步形成的文件进行操作,形成新文件的过程。hello进程在shell执行的过程,存储管理的过程,I/O处理的过程。以这些过程的分析为例,阐明整个程序的生命周期。

关键词:hello;程序;预处理;编译;汇编;链接;进程;信号;异常;处理;内存;地址;I/O;计算机系统                             

(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分

目  录

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

根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。

1.1.1 Hello p2p: from program to process

Hello程序生命周期是由高级语言c程序开始的,而要让每条c语句都能被机器都懂并且被其他程序转化为一条低级机器语言指令,然后这些指令按照可执行目标程序格式打包好,并以二进制文件磁盘形式存放起来。在Unix中,需要从源文件到目标文件的转化:

gcc -o hello hello.c

调用编译器驱动程序,gcc编译器驱动程序读取hello.c,并把它翻译成一个可执行文件hello,而翻译过程分为四个阶段,由预处理器cpp读入hello.c文本,cpp根据#开头的命令,修改c程序,将stdio.h内容插入程序文本中得到hello.i。而后通过编译器(ccl)将文本文件hello.i每条语句翻译成低级机器语言指令成为文本文件hello.s,接下来汇编器(as)将hello.s每条语句翻译成低级机器语言指令,把这些指令打包成可重定位目标程序的格式,并将结果保存在目标文件hello.o中;最后,由于hello程序调用了printf函数(属于标准C库的一个函数),因此要将含printf函数的printf.o用链接器(ld)合并到我们hello.o程序中,结果得到hello文件(可执行文件)。

Linux系统中通过内置命令行解释器shell加载运行hello程序,为hello程序fork进程,至此,hello.c完成了P2P的过程。

1.1.2 020过程

shell首先fork一个子进程,然后通过execve加载并执行hello,映射虚拟内存,进入程序入口后将程序载入物理内存,进入 main函数执行目标代码,CPU为运行的hello分配时间片执行逻辑控制流。当程序运行结束后,shell父进程负责回收hello进程,内核删除相关数据结构。即,从0开始,以0结束,为020。

1.2 环境与工具

1.2.1 硬件环境

Windows10 位;VirtualBox/Vmware 11 以上;Ubuntu 16.04 LTS 64 位/ 优麒麟 64 位

1.2.2 软件环境

Windows7/10 64 位;VirtualBox/Vmware 11;Ubuntu 16.04 LTS 64 位/优麒麟 64 位

1.2.3 开发工具

Visual Studio 2010 64 位;CodeBlocks 64 位;vi/vim/gedit+gcc

1.3 中间结果

列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。

文件名称

作用

hello.c

hello源文件

hello.i

预处理后文本文件

hello.s

编译得到的汇编文件

hello.o

汇编后的可重定位目标文件

hello

链接后可执行文件

hello.objdump

hello可执行文件反汇编代码

hello.elf

hello的elf文件

Hello_o.objdump

hello.o(链接前)的反汇编文件

hello_o_elf.txt

hello.o的ELF格式

1.4 本章小结

              本章介绍了hello.c源程序是如何在unix系统下由gcc编译器翻译成可执行目标文件hello,并且简单介绍hello的p2p以及020过程,列举了中间产生的文件及其作用

(第1章0.5分)

第2章 预处理

2.1 预处理的概念与作用

2.1.1预处理概念

预处理是编译第一步,将源程序文本.c文件读入cpp中并开始处理,c语言预处理指令如下:

  1. 预处理的作用

预处理阶段的作用是让编译器在随后对文本进行编译的过程中,更加方便,因为访问库函数这类操作在预处理阶段已经完成,减少了编译器的工作。比如在这个hello.c文件中,预处理器cpp根据以字符#开头的命令,修改原始的c程序,告诉预处理器读取系统头文件stdio.h、unistd.h、stdlib.h等内容,并将其插入程序文本中,得到hello.i文件,进而进入下一步。

2.2在Ubuntu下预处理的命令

输入 gcc – E  hello.c -o hello.i

得到如下图

                                                图2.1通过gcc编译后得到hello.i文件

2.3 Hello的预处理结果解析

       打开hello.c文件可以发现开头有四行注释,该程序引用了三个头文件

                                                       图2.2 打开hello.c文件

打开hello.i文件如下图

                                                图2.3打开hello,i文件

可以看到,在c文件中原本注释好的内容已经被全部忽略掉了,对于stdio.h等头文件会直接将其插入到程序文件中

2.4 本章小结

本章主要介绍了预处理的内容以及作用,并在Ubuntu中演示了hello.c生成hello.i的过程,展示了预处理后hello.i与hello.c的对比图,介绍了c语言c语言预处理指令

(第2章0.5分)

第3章 编译

3.1 编译的概念与作用

3.1.1 编译概念

编译阶段是编译器(ccl)对hello.i文件进行处理,翻译成.s文件的过程。此阶段编译器会完成对代码的语法和语义的分析,生成汇编代码,并将这个代码保存在hello.s文件中。

3.1.2 编译作用

主要作用:1.扫描(词法分析)2.语法分析3.语义分析4.源代码优化(中间语言生成)5.代码生成,目标代码优化。

  1. 将源代码程序输入扫描器,将源代码的字符序列分割成一系列记号。
  2. 基于词法分析得到的一系列记号,生成语法树。
  3. 由语义分析器完成,指示判断是否合法,并不判断对错。
  4. 中间代码(语言)使得编译器分为前端和后端,前端产生与机器(或环境)无关的中间代码,编译器的后端将中间代码转换为目标机器代码,目的:一个前端对多个后端,适应不同平台。

5. 编译器后端主要包括:代码生成器:依赖于目标机器,依赖目标机器的不同字长,寄存器,数据类型等

3.2 在Ubuntu下编译的命令

gcc -S hello.i -o hello.s

如图,产生hello.s文件

                                                    图3.1

3.3 Hello的编译结果解析

  1. #include <stdio.h>  
  2. #include <unistd.h>   
  3. #include <stdlib.h>  
  4.   
  5. int main(int argc,char *argv[]){  
  6.     int i;  
  7.   
  8.     if(argc!=4){  
  9.         printf("用法: Hello 学号 姓名 秒数!\n");  
  10.         exit(1);  
  11.     }  
  12.     for(i=0;i<8;i++){  
  13.         printf("Hello %s %s\n",argv[1],argv[2]);  
  14.         sleep(atoi(argv[3]));  
  15.     }  
  16.     getchar();  
  17.     return 0;  
  18. }

函数序言:保存调用%rbp信息并且为当前函数分配栈空间

                                                        3.2

.file:文件名

.string:字符串

.globl:全局变量

.type:指定数据类型

.align:数据对齐。对齐方式:保证它后面的数据的起始位置是8的倍数

3.3.1 数据

  1. int argc

图3.3

先将%rbp压入栈中,再将栈地址%rsp保存到%rbp中,对栈地址减去32,表示压栈,movl表示将该寄存器存储argc的值保存在%rbp-20地址中

  1. char **argv

传递参数列表: argv初始被放在寄存器%rsi中, 之后又将该%rbp(及栈地址)减去32,将argv首地址放在这里.

  1. int i=0

movl,由于定义i为整数类型,所以为4字,由于栈空间是栈底在上,栈顶在下,地址由高到低排列因此要在栈地址中减去4字节,并且将i存储在该地址中

                                                                      图3.4

      1. 赋值

原c程序中有两段代码

int i;  

for(i=0;i<8;i++)

这里直接将立即数存放在%rbp地址减去4中,所以在编写程序时,如果局部变量只在循环体内部调用的话,只需在循环体内临时定义变量即可.这里i也没有赋初值,所以编译时只在for循环的时候直接将立即数赋值到寄存器中去.

  1. mov指令:指将数据从源位置复制到目的位置,不做任何变化。源操作数指定的是一个$修饰的立即数,此处在寄存器或者内存中,而目的数要指定一个位置,要么是寄存器,要么是一个内存地址

                                                  图3.5

这里有两条mov指令,分别将argc和argv保存到栈地址中

  1. 加载有效地址 leaq实际上是movq变型,它的指令形式是从内存读数据到寄存器,将有效地址写入目的操作数

如图

                                                 图3.6

      1. 算术操作

图3.7

这里体现了两种计算方式,第一种是整数算术操作,sub指令将右边的寄存器保存的值减去左边的立即数,另一种是直接在寄存器上进行运算操作,比如后面两行就是直接对寄存器的地址进行运算

 

图3.8

addl ,用一个立即数直接加上左边寄存器保存的值,联系上面分析,此处就是对循环变量i进行加一操作

      1. 类型转换

源程序中有这样一段代码:

sleep(atoi(argv[3]));  

由于sleepl函数要求参数传递整数类型,而argv是字符串类型,这里就涉及到数据类型转换了,而atoi就是将字符串nptr转化成int,在汇编指令里面观察如下:

                                                        图3.9

可以看到这里是直接调用了函数atoi进行数据类型转换

      1. 关系操作

cmp指令只是进行比较,不会对寄存器的值有任何改动,比较的意义在于将两个操作数之差来设置条件码,根据比较结果来决定下一步跳转指令

观察源代码,可以发现一共有两处比较操作:

if(argc!=4){  

for(i=0;i<8;i++){  

在观察编译语句:

图3.10

这里是将立即数4与(%rbp)-20比较,根据上面的分析可知这个地址保存的是argc,所以对应着源程序argc!=4语句

图3.11

这条语句是将7和(%rbp)-4比较,由上面分析得知这个地址保存的是i的值,而此处表达的意思是将i与7进行比较看i是否小于等于7

      1. 控制转移

                                   图3.12 jump指令

跳转指令可以将程序跳转到其他部分.而跳转条件由前面比较给出的条件码决定,通过观察图3.10和图3.11可以发现,每次cmp后都会有jump指令,这些cmp将条件码设置好后就会决定下一步程序的跳转,跳转的地方有具体的标号,比如 .L4, .L2

图3.13  L2跳转

这几段连起来解释就是:先将4和argv比较,设置条件码后进入je,相等则跳转到L2,不相等则跳过je进入下一部分

      1. 数组操作

数组操作一共出现了三处:

int main(int argc,char *argv[]){ 

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

sleep(atoi(argv[3]));

第一处只是将函数形参传进main函数中,由于传入数组时只需要传数组的首地址,所以如图,直接将寄存器保存的值存到相应栈中即可

                                                图3.14  argv数组保存

数组取址方式为首地址加上地址偏移量,偏移量大小为保存数据的大小,字符指针为8字节,所以计算方式是每次加8字节

图3.15  argv[2]读取方式

如图3.15 argv[2]读取方式是先将argv  首地址取出,在加上源地址偏移量,argv[2]是偏移两个单位,每次偏移据需要在地址加8字节,所以要对%rax保存地址值加16

图3.16  argv[1]读取方式

图3.17  argv[3]读取方式

 argv[1]和argv[3]调用方式与argv[2]类似,都是对数组首地址加上偏移量

3.3.8函数操作

hello.c中涉及的函数操作有:

  1. main函数:

传递控制:

main函数因为被调用call才能执行(被系统启动函数__libc_start_main调用),call指令将下一条指令的地址压栈,然后跳转到main函数。

传递数据:

外部调用过程向main函数传递参数argc和argv,分别使用%edi和%rsi存储,函数正常出口为return 0,将%eax设置0返回。

分配和释放内存:

使用%rbp记录栈帧的底,函数分配栈帧空间在%rbp之上。恢复栈空间为调用之前的状态,然后ret返回,ret相当pop IP。

  1. printf函数:

传递数据:

第一次printf将%rdi设置为“Usage: Hello 学号 姓名!\n”字符串的首地址。第二次printf设置%rdi为“Hello %s %s\n”的首地址,设置%rdx为argv[1],%rsi为argv[2]。

控制传递:

第一次printf因为只有一个字符串参数,所以call puts@PLT;第二次printf使用call printf@PLT。

  1. exit函数:

传递数据:将%edi设置为1。

控制传递:call exit@PLT。

  1. sleep函数:

传递数据:将%edi设置为sleepsecs。

控制传递:call sleep@PLT。

  1. getchar函数:

控制传递:call gethcar@PLT

    1. 本章小结

本章主要讲述了系统编译的作用,分析了hello.s汇编代码,从数据,赋值,类型转换到数组,控制转移等八个方面分析汇编代码的原理以及机制

(第32分)

第4章 汇编

4.1 汇编的概念与作用

4.1.1 汇编的概念

汇编指的是汇编器ad将.s文件翻译成机器语言,并将其生成后缀名为.o的可重定位目标文件的过程。

4.1.2 汇编作用

将.s文件生成机器码,使其能够被链接器ld链接生成可执行文件

4.2 在Ubuntu下汇编的命令

输入gcc -c hello.s -o hello.o

图4.1  gcc -c hello.s -o hello.o

图4.2 利用edb打开hello.o文件

4.3 可重定位目标elf格式

使用命令 readelf -a hello.o > hello_o_elf.txt,读取hello.o文件的ELF格式至hello_o_elf.txt中。

分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。

  1. ELF

ELF头以一个16字节的序列开始,描述了生成该文件的系统的字的大小和字节顺序。ELF头剩下的部分包含帮助链接器语法分析和解释目标文件的信息。其中包括ELF头的大小、目标文件的类型(如可重定位、可执行、共享的)、机器类型(如x86-64)、节头部表的文件偏移,以及节头部表中条目的大小和数量。如图4-3所示。

                                                 图4-3 hello.o ELF头

  1. 节头部表

节头部表包括节的全部信息,如图4-4所示,各个节的名称及内容如下:

节名称

包含内容

.text

已编译程序的机器代码

.rela.text

一个.text节中位置的列表,链接器链接其他文件时,需修改这些内容

.data

已初始化的全局和静态C变量

.bss

未初始化的全局和静态C变量和所有被初始化为0的全局或静态变量

.rodata

只读数据段

.comment

包含版本控制信息

.note.GNU-stack

包含注释信息,有独立的格式

.symtab

符号表,存放程序中定义和引用的函数和全局变量信息

.strtab

字符串表,包括.symtab和.debug节中的符号表以及节头部中的节名字

.shstrtab

包含节区名称

                                                 图4.4 hello.o 节头部表

  1. 重定位信息

重定位是将EFL文件中的未定义符号关联到有效值的处理过程。在hello.o中,对printf,exit等函数的未定义的引用和全局变量替换为该进程的虚拟地址空间中机器代码所在的地址。如图4-5所示。

                                                 图4.5 hello.o 重定位节

  1. 符号表

符号表(.symtab)是用来存放程序中定义和引用的函数和全局变量的信息。重定位需要引用的符号都在其中声明。如图4-6所示。

                                                 图4.6 hello.o 符号表(.symtab)

4.4 Hello.o的结果解析

输入objdump -d -r hello.o > hello_o.objdump

图4.7生成.objdump文件

打开如下

图4.8 hello_o.objdump与hello.s对比

汇编结果与反汇编区别主要在以下几个方面:

1. 立即数的引用不同,在反汇编中立即数是十六进制的,而汇编代码则是十进制。

2. 子程序的调用不同,反汇编代码中子程序的调用是通过对主函数地址的相对偏移进行的,具体原因是可重定位文件在静态链接前没有确定具体地址,所以采用相对于主函数偏移,在链接后直接为主函数确定地址就可以正常运行。而在编译代码中则是通过call直接加上函数名的方法进行的。

3. 分支跳转不同,在反汇编代码中,分支转移是主函数地址为基址,直接跳转到一个偏移地址中,而在汇编代码中则是通过 .L4、.L3这样通过段名称跳转的。

4.5 本章小结

       本章主要介绍了汇编的概念及其作用,通过演示介绍从.o文件到.elf可重定位目标文件的转换。从.o文件到.objdmp的转换展示了反汇编代码,又将汇编代码与反汇编代码的对比,展示了二者之间的区别与联系

(第41分)

5章 链接

5.1 链接的概念与作用

5.1.1 链接的概念

       链接是通过链接器(Linker)将每个模块中代码、数据(初始化全局变量、未初始化全局变量、静态变量、局部变量)完全连接形成可执行的目标文件,可执行文件可以被加载(复制)到内存并执行。

      1. 链接的作用

链接可以执行于编译时,也就是在源代码被翻译成机器代码时;也可以执行于加载时,也就是在程序被加载器加载到内存并执行时;甚至执行于运行时,也就是由应用程序来执行。在早期的计算机系统中,链接是手动执行的。在现代系统中,链接是由叫做链接器(linker)的程序自动执行的。

    1. 在Ubuntu下链接的命令

连接命令:

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

                                           图5.1链接生成hello

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

使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件

    1. 可执行目标文件hello的格式

使用readelf -a hello > hello.elf 命令,读取hello的ELF格式至hello.elf中。

                                         

图5.2 生成hello.elf过程

        图5.3 典型elf可执行目标文件构成

5.3.1 ELF 头:

 

              图5.4 hello.elf ELF头与hello_o.elf对比

对比发现两者差距不大,主要区别在入口点地址以及程序起点地址改变,由可重定位文件REL变为可执行文件EXEC,程序节头变多

5.3.2 节头表

                     图5.5 节头表

可执行文件中经过重定位每个节的地址不再是0,而是根据自身大小加上偏移量。节头部表描述不同节的位置和大小,目标文件中的每个节都有一个固定大小的节头部表条目。

5.3.3 程序头

                     图5.6 程序头

程序头部表描述了可执行文件的连续的片映射到连续的内存段的映射关系。包括目标文件的偏移、段的读写/执行权限、内存的开始地址、对齐要求、段的大小、内存中的段大小等

5.3.4 符号表

                     图5.7 符号表

符号表存放程序中定义和引用的函数和全局变量的信息,一共51条

5.3.5 重定位节

                     图5.8 重定位节

    分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。

5.4 hello的虚拟地址空间

使用edb加载hello,查看本进程的虚拟地址空间各段信息

                     图5.9 edb打开hello文件

可以看出,ELF开始地址是0x400000, 5.3中的节头部表可以获得各个节的偏移量信息,从而得知各节在虚拟地址空间中的地址。

       图5.10 edb symbols与节头对照关系

打开edb symbols窗口, 观察可发现,节头表偏移量加上起始地址0x400000就是其虚拟地址 

5.5 链接的重定位过程分析

objdump -d -r hello 分析hello与hello.o的不同

输入objdump -d -r hello > hello.objdump

       图5.11 hello.objdump生成过程

       图5.12 hello.o与hello反汇编文件对比

  1. 在hello.o中,只存在main函数的汇编指令;而在hello中,由于链接过程中发生重定位,引入了其他库的各种数据和函数,以及一些必需的启动/终止函数,因此hello中除了main函数的汇编指令外,还包括大量其他的指令
  2.  hello_o.objdump文件使用的是相对偏移地址,至于偏移起点由于地址定位不明确所以暂未确定,而hello.objdump使用的是虚拟地址,由于之前已经确定起始地址是0x400000,所以虚拟地址只需在此基础上加上原先的偏移地址即可
  3. 增加了相关函数的引用,链接器加入了hello.c用到的相关库函数,如下图.

       图5.13 相关库函数调用

重定位,通常来说把在装入时对目标程序中指令和数据地址修改的过程称为重定位。而静态重定位就是,在逻辑地址转换为物理地址的过程中,地址变换是在进程装入时一次完成的,以后不再改变。此处hello对hello.o重定位过程是链接器ld将所有链接文件中相同的节合并,并按照要求计算新的偏移地址赋值给新的节。同时链接器按链接指令的顺序搜索符号表,查找符号引用。

5.6 hello的执行流程

使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。

程序名称

程序地址

ld-2.27.so!_dl_start

0x7fce:8cc38ea0

ld-2.27.so!_dl_init

0x7fce:8cc47630

hello!_start

0x400500

libc-2.27.so!__libc_start_main

0x7fce:8c867ab0

-libc-2.27.so!__cxa_atexit      

0x7fce:8c889430

-libc-2.27.so!__libc_csu_init

0x4005c0

hello!_init

0x400488

libc-2.27.so!_setjmp

0x7fce:8c884c10

hello!main

0x400532

hello!puts@plt

0x4004b0

hello!exit@plt

0x4004e0

*hello!printf@plt

——

*hello!sleep@plt

——

*hello!getchar@plt

——

ld-2.27.so!_dl_runtime_resolve_xsave

0x7fce:8cc4e680

-ld-2.27.so!_dl_fixup

0x7fce:8cc46df0

–ld-2.27.so!_dl_lookup_symbol_x

0x7fce:8cc420b0

libc-2.27.so!exit

0x7fce:8c889128

5.7 Hello的动态链接分析

       共享库是解决静态库更新以及调用繁琐,在某个函数(比如printf)调用频率高的情况下造成内存浪费的产物,共享库在运行或加载时可以加载到任意位置,冰河一个内存的程序链接起来,这个过程就是动态链接。

hello程序对动态链接库的引用,基于数据段与代码段相对距离不变这一个机理,因此代码段中任何指令和数据段中任何变量之间的距离都是一个运行时常量。

                                                 图5.14 dl_init之前

                                                 图5.15 dl_init之后

可以看出执行dl_init后地址发生了变化。这些变化是因为编译器使用了延迟绑定技术。 Hello需要调用共享库函数,由于这些函数存放的地址可能发生变化,所以需要在程序运行时确定。

分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。

5.8 本章小结

本章主要介绍了链接的概念和作用,分析了hello.elf组成,查看了hello的虚拟地址空间,对链接重定位过程进行分析, 对比分析了hello和hello.o的反汇编代码展示了hello的执行流程并且展示了hello的动态连接分析

(第51分)

6章 hello进程管理

6.1 进程的概念与作用

      1. 进程的概念

进程是一个执行中的程序的实例,系统中的每个程序都运行在某个进程的上下文中

      1. 进程的作用
        1. 每次用户向shell输入一个可执行目标文件的名字运行时,shell就会创建一个新的进程,然后在这个进程的上下文中运行这个可执行目标文件。应用程序也能够创建新进程,并且在新进程的上下文中运行它们自己的代码或其他应用程序。
        2. 进程提供给应用进程两个关键抽象:

一个独立的逻辑控制流,好像程序可以独使用处理器。

一个私有的地址空间,好像程序独占整个内存系统

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

6.2.1 Shell-bash的作用

shell和其他软件一样都是和内核打交道,直接服务于用户。但和其他软件不同,shell主要用来管理文件和运行程序。作为命令语言,它交互式解释和执行用户输入的命令或者自动地解释和执行预先设定好的一连串的命令。

6.2.2 Shell-bash的处理流程

1. 从终端读入命令

2. 解析引用并分割命令行

3. 如果是内置命令立即执行;如果不是内置命令就认为输入的字符是一个路径,然后调用fork函数的execve,为该路径上的程序分配子进程去执行。

4. 接收输入的各种信号

5. 执行命令

6. 返回退出状态码

6.3 Hello的fork进程创建过程

       进程执行中调用一次fork,就创建一个子进程。Fork调用一次,返回两次,父进程返回子进程的PID,子进程返回0。父进程fork出子进程后,子进程得到与父进程完全相同但是独立的虚拟地址空间,包括用户栈、本地变量、堆以及全局变量值和代码段。两者并发执行。内核以任意方式交替执行它们的逻辑控制流中的指令。

      

6.4 Hello的execve过程

Execve函数加载并运行可执行目标文件hello,并且带参数列表argv和环境变量列表envp,只有当错误出现时,例如找不到文件名称,execve才会返回到调用程序

Execve执行后,将调用启动代码,启动代码设置栈,并将控制传递给新程序的主函数,覆盖当前进程的代码、数据、栈。保留PID,继承已打开的文件描述符和信号上下文。

加载并运行hello需要以下几个步骤:

        1. 删除已存在的用户区域

删除当前进程虚拟地址的用户部分中已存在的区域结构。

        1. 映射私有区域

为新程序的代码、数据、bss和栈区域创建新的区域结构。所有这些新的区域都是私有的、写时复制的。代码和数据区被映射为hello文件中的.text和.data区。bss区域是请求二进制零的,映射到匿名文件,其大小包含在hello中。栈和堆区域也是请求二进制零的,初始长度为零。

        1. 映射共享区域

如果hello程序与共享对象链接,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址中的共享区域。

        1. 设置程PC

设置当前进程上下文中的程序计数器,使之指向代码段的入口点。

6.5 Hello的进程执行

6.5.1 进程上下文信息

内核为每个进程维持一个上下文。上下文就是内核重新启动一个被强占的进程所需的状态,这个状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件    描述符的集合。

6.5.2 进程时间片

一个进程执行它的控制流的一部分的每一时间段叫做时间片。

6.5.3 进程调度

      进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前抢占了的进程,这种决策就叫调度。是由内核中称为调度器的代码处理的。党内和调度了一个新的进程运行后,他将抢占当前进程,并使用上下文切换机制来讲控制转移到新的进程

6.5.4 用户态与核心态转换

为使操作系统内核提供一个独立的逻辑控制流和一个私有的地址空间,处理器必须提供一种机制来限制应用可以执行的指令和他可以访问的地址范围。

处理器通常使用一个寄存器的一个模式位提供两种模式的区分,该寄存器描述了进程当前享有的特权,设置模式位时,进程处于内核模式,该进程可以执行指令集中的任何命令,并且可以访问系统中的任何内存位置;当没有设置模式位时,进程就处于用户模式中,用户模式的进程不允许执行特权指令,也不允许直接引用地址空间中内核区内的代码和数据。

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

这段代码中调用了sleep函数,函数的作用就是当运行到这一句的时候,程序会产生一个中断,内核会将这个进程挂起,然后运行其它程序,当内核中的计时器计时完毕后,会传一个信号给CPU,这时hello进程重新进入待执行进程队列中等待内核调度。

在执行到sleep函数的时候,切换到内核模式,将hello进程挂起,然后切换到用户模式执行其它进程。当到了2秒之后,发生一个中断,切换到内核模式,继续运行之前被挂起的进程。最后切换回用户模式,继续运行hello进程。

结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。

6.6 hello的异常与信号处理

hello执行过程中会出现的异常:

中断:信号SIGTSTP,默认行为是 停止直到下一个SIGCONT

终止:信号SIGINT,默认行为是 终止

终端输入 ./ hello 120L021917 aihaolin 1

中途乱按键盘不会影响进程

Ctrl^Z停止进程

在输入pstree获取进程树

                                                                   图6.1 获取进程树

输入ps列出当前系统中的进程(包括僵死进程)

                                                                   图6.2 获取系统进程

输入jobs获取当前shell环境中已启动的任务状态

                                                           图6.3 获取shell环境启动状态

运行时中断并且输入fg获取前台进程

                                                                   图6.4 将进程调到前台

 hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。

 程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps  jobs  pstree  fg  kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。

6.7本章小结

本章主要介绍了进程的概念及作用,描述了进程在计算机中的调用过程。介绍了shell的作用和处理流程,分析了hello程序运行时的fork和execve过程。分析了hello的进程执行和异常与信号处理过程。

(第61分)

7章 hello的存储管理

7.1 hello的存储器地址空间

  1. 逻辑地址

逻辑地址是指由程序产生的与段相关的偏移地址部分。逻辑地址由一个段(segment)和偏移量(offset)组成,偏移量指明了从段开始的地方到实际地址之间的距离。即hello.o里相对偏移地址。

  1. 线性地址

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

  1. 虚拟地址

虚拟地址是程序保护模式下,程序访问存储器所使用的逻辑地址称为虚拟地址,与实地址模式下的分段地址类似,虚拟地址也可以写为“段:偏移量”的形式,这里的段是指段选择器。就是hello里面的虚拟内存地址。虚拟地址存储在磁盘中。

  1. 物理地址

物理地址(Physical Address)是指出现在CPU外部地址总线上的寻址物   理内存的地址信号,是地址变换的最终结果地址。如果启用了分页机制,那么   hello的线性地址会使用页目录和页表中的项变换成hello的物理地址;如果没有启用分页机制,那么hello的线性地址就直接成为物理地址了。

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

一个逻辑地址由两部分组成,一共48位,包括前16位的段选择符以及后32位的段内偏移量。

每个段的首地址都存放在自己的段描述符中,而所有的段描述符都存放在一个描述符表中(描述符表分为全局描述符表 GDT 和局部描述符表 LDT)。而要想找到某个段的描述符必须通过段选择符才能找到。

段选择符是由一个16位长的字段组成,其中前13位是一个索引号。TI:0为全局描述符表(GDT),1为局部描述表(LDT)。Index指出选择描述符表中的哪个条目,RPL请求特权级。如图7-1所示:

图7-1 段选择符

被选中的段描述符先被送至描述符cache,每次从描述符cache中取32位段基址,与32位段内偏移量(有效地址)相加得到线性地址。

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

线性地址(即虚拟地址VA)到物理地址(PA)之间的转换通过分页机制完成。而分页机制是对虚拟地址内存空间进行分页。

系统将每个段分割为被称为虚拟页(VP)的大小固定的块来作为进行数据传输的单元,在linux下每个虚拟页大小为4KB,类似地,物理内存也被分割为物理页(PP/页帧),虚拟内存系统中MMU负责地址翻译,MMU使用存放在物理内存中的被称为页表的数据结构将虚拟页到物理页的映射,即虚拟地址到物理地址的映射。

如图,虚拟地址分为虚拟页号VPN和虚拟页偏移量VPO。通过页表基址寄存器PTBR+VPN在页表中获得条目PTE,一条PTE中包含有效位、权限信息、物理页号,如果有效位是0+空则代表没有在虚拟内存空间中分配该内存,如果是有效位0+非空,则代表在虚拟内存空间中分配了但是没有被缓存到物理内存中,如果有效位是1则代表该内存已经缓存在了物理内存中,可以得到其物理页号PPN,与虚拟页偏移量共同构成物理地址PA。

图7-2 使用页表的地址翻译

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

7.4.1 TLB

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

7.4.2 多级页表

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

如图7-5所示 ,CPU产生虚拟地址VA,VA传送给MMU,MMU使用前36位VPN作为TLBT(前32位)+TLBI(后4位)向TLB中匹配,如果命中,则得到PPN(40bit)与VPO(12bit)组合成PA(52bit)。

如果TLB中没有命中,MMU向页表中查询,CR3确定第一级页表的起始地址,VPN1(9bit)确定在第一级页表中的偏移量,查询出PTE,如果在物理内存中且权限符合,确定第二级页表的起始地址,以此类推,最终在第四级页表中查询到PPN,与VPO组合成PA,并且向TLB中添加条目。

如果查询PTE的时候发现不在物理内存中,则引发缺页故障。如果发现权限不够,则引发段错误。

图7-3 Core i7四级页表下地址翻译情况

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

得到了物理地址VA,首先使用物理地址的CI进行组索引,对8路的块分别匹配 CT进行标志位匹配。如果匹配成功且块的valid标志位为1,则命中hit。然后根据数据偏移量 CO取出数据并返回。如果没有匹配成功或者匹配成功但是标志位是0,则不命中(miss),向下一级缓存中查询数据(L1 cahe ->L2 cache->L3 cache->主存)。查询到数据之后,一种简单的放置策略如下:如果映射到的组内有空闲块,则直接放置,否则组内都是有效块,产生冲突(evict),则采用最近最少使用策略LFU进行替换。如图7-4所示。

图7-4 三级Cache支持下的物理内存访问

7.6 hello进程fork时的内存映射

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

当fork在hello中返回时,hello现在的虚拟内存刚好和调用shell的虚拟内存相同。当这两个进程中的任何一个进行写操作时,写时复制机制会创建新页面。因此也就为每个进程保持了私有地址空间的概念。

7.7 hello进程execve时的内存映射

execve函数调用驻留在内核区域的启动加载器代码,在当前进程中加载并运行包含在可执行目标文件hello中的程序,用hello程序有效地替代了当前程序。图7-7描述了加载器映射用户地址空间区域的模型。加载并运行hello需要以下几个步骤:

1. 删除已存在的用户区域

删除当前进程虚拟地址的用户部分中的已存在的区域结构。

2. 映射私有区域

为新程序的代码、数据、bss和栈区域创建新的区域结构,所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为hello文件中的.text和.data区,bss区域是请求二进制零的,映射到匿名文件,其大小包含在hello中,栈和堆地址也是请求二进制零的,初始长度为零。

3. 映射共享区域

hello程序与共享对象libc.so链接,libc.so是动态链接到这个程序中的,然后再映射到用户虚拟地址空间中的共享区域内。

4. 设置程序计数器(PC)

设置当前进程上下文的程序计数器,使之指向代码区域的入口点。

图7-5 加载器映射用户地址空间区域

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

  1. 缺页故障

缺页故障是一种常见的故障,当指令引用一个虚拟地址,在MMU中查找页表时发现与该地址相对应的物理地址不在内存中,因此必须从磁盘中取出时就会发生故障。故障处理流程如下。

图7-6 缺页故障处理流程

                          图7-7 缺页中断处理

  1. 缺页中断处理

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

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

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

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

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

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

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

7.9动态存储分配管理

动态内存分配器维护着一个进程的虚拟内存区域,称为堆。分配器将堆视为一组不同大小的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。

分配器分为两种基本风格:显式分配器、隐式分配器。

显式分配器(explicit allocator),要求应用显式地释放任何已分配的块。例如,C标准库提供一种叫做malloc程序包的显式分配器。C程序通过调用malloc函数来分配一个块,并通过调用 free 函数来释放一个块。

隐式分配器(implicit allocator),要求分配器检测一个已分配块何时不再被程序所使用,那么就释放这个块。隐式分配器也叫做垃圾收集器(garbage collector)

7.10本章小结

本章讨论了存储器地址空间,虚拟地址、物理地址、线性地址、逻辑地址的概念,TLB与四级页表支持下的VA到PA的变换,三级Cache支持下的物理内存访问,hello进程fork时和execve时的内存映射,缺页故障与缺页中断处理和动态存储分配管理。

(第7 2分)

8章 hello的IO管理

8.1 Linux的IO设备管理方法

8.1.1 设备的模型化:文件

一个Linux文件就是一个m字节的序列。

所有的I/O设备(网络、磁盘、终端),甚至内核都被映射为文件

8.1.2 设备管理:Unix I/O接口

所有的I/O设备(例如网络、磁盘和终端)都被模型化为文件,而所有的输入和输出都被当作对相应文件的读和写来执行。这种将设备优雅地映射为文件的方式,允许Linux内核引出一个简单、低级的应用接口,称为Unix I/O,这使得所有的输入和输出都能以一种统一且一致的方式来执行,这就是Unix I/O设备管理方法。

8.2 简述Unix IO接口及其函数

将设备优雅地映射为文件的方式,允许 Linux 内核引出一个简单、  低级的应用接口,称为 Unix I/O,这使得所有的输入和输出都能以一种统一且一致的方式来执行。

  1. Unix I/O接口统一操作
  1. 打开文件:应用程序通过要求内核打开相应的文件,想要访问一个I/O设备时,内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件,内核记录有关这个打开文件的所有信息。
  2. shell创建的每个进程都有三个打开的文件:标准输入,标准输出,标准错误。
  3. 改变当前的文件位置:对于每个打开的文件,内核保持着一个文件位置k,初始为0,这个k是从文件开头起始的字节偏移量,应用程序能够通过执行seek,显式地将改变当前文件位置。
  4. 读写文件:读操作是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增加到k+n,给定一个大小为m字节的而文件,当k>=m时,触发EOF。写操作是从内存中复制n>0个字节到一个文件,从当前文件位置k开始,然后更新k。
  5. 关闭文件:内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。
  1. Unix I/O函数

函数

功能

int open(char *filename, int flags, mode_t mode);

打开文件

int close(int fd);

关闭文件

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

读文件

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

写文件

off_t lseek(int fd, off_t offset,int whence);

在指定的文件描述符中将将文件指针定位到相应位置

ssize_t rio_readn(int fd, void *usrbuf, size_t n);

ssize_t rio_writen(int fd, void *usrbuf, size_t n);

直接在内存和文件之间传送数据

ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen);

ssize_t rio_readnb(rio_t *rp, void *usrbuf, size_t n);

内存和文件之间传送数据经过缓冲区

int stat(const char *filename, struct stat *buf);

int fstat(int fd, struct stat *buf);

检索到关于文件的信息

8.3 printf的实现分析

printf函数功能:接受字符串指针数组fmt,然后将匹配到的参数按照fmt格式输出。如下图,printf内部调用了两个函数,一个是vsprintf,一个是write。

  1. int printf(const char *fmt, ...)  
  2. {  
  3. int i;  
  4. char buf[256];  
  5.      
  6.      va_list arg = (va_list)((char*)(&fmt) + 4);  
  7.      i = vsprintf(buf, fmt, arg);  
  8.      write(buf, i);  
  9.      
  10.      return i;  
  11.     }  

图8-1 printf代码

其中,va_list是一个字符指针,arg表示 ‘…’的第一个参数。

vsprintf函数代码如图8-2所示:

  1. int vsprintf(char *buf, const char *fmt, va_list args)   
  2.    {   
  3.     char* p;   
  4.     char tmp[256];   
  5.     va_list p_next_arg = args;   
  6.      
  7.     for (p=buf;*fmt;fmt++) {   
  8.     if (*fmt != '%') {   
  9.     *p++ = *fmt;   
  10.     continue;   
  11.     }   
  12.      
  13.     fmt++;   
  14.      
  15.     switch (*fmt) {   
  16.     case 'x':   
  17.     itoa(tmp, *((int*)p_next_arg));   
  18.     strcpy(p, tmp);   
  19.     p_next_arg += 4;   
  20.     p += strlen(tmp);   
  21.     break;   
  22.     case 's':   
  23.     break;   
  24.     default:   
  25.     break;   
  26.     }   
  27.     }   
  28.      
  29.     return (p - buf);   
  30.    }   

图8-2 vsprintf代码

vsprintf的作用是格式化。接受确定输出格式的格式字符串fnt。用格式字符串对个数变化的参数进行格式化,产生格式化输出,并返回要打印的字符串的长度。

write函数代码如图8-3所示:

  write:
      mov eax, _NR_write
     mov ebx, [esp + 4]
     mov ecx, [esp + 8]
     int INT_VECTOR_SYS_CALL

图8-3 write代码

write函数中,先给寄存器传了几个参数,int INT_VECTOR_SYS_CALL表示要通过系统来调用sys_call这个函数。

sys_call函数代码如下

sys_call:
     call save
     push dword [p_proc_ready]
     sti
     push ecx
     push ebx
     call [sys_call_table + eax * 4]
     add esp, 4 * 3
     mov [esi + EAXREG - P_STACKBASE], eax
     cli
     ret

syscall函数将字符串中的字节从寄存器中通过总线复制到显卡的显存中,显存中存储的是字符的ASCII码。

字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)

8.4 getchar的实现分析

getchar源代码如下:

  1. 1.  int getchar(void)  
  2. 2.  {  
  3. 3.      static char buf[BUFSIZ];  
  4. 4.      static char* bb=buf;  
  5. 5.      static int n=0;  
  6. 6.      if(n==0)  
  7. 7.      {  
  8. 8.          n=read(0,buf,BUFSIZ);  
  9. 9.          bb=buf;  
  10. 10.     }  
  11. 11.     return (--n>=0)?(unsigned char)*bb++:EOF;  
  12. 12. }  

getchar()函数实际上是int getchar(void),所以它返回的是ASCII码,所以只要是ASCII码表里有的字符它都能读取出来。在调用getchar()函数时,编译器会依次读取用户键入缓存区的一个字符(注意这里只读取一个字符,如果缓存区有多个字符,那么将会读取上一次被读取字符的下一个字符),如果缓存区没有用户键入的字符,那么编译器会等待用户键入并回车后再执行下一步

在程序中使用getchar()函数时应当使用回车符结束数据的输入,但是当用户输入的数据都保存在缓冲区内,包括回车符,于是在运行下一个getchar()函数时仍然从缓冲区读入数据就读到了输入第一个变量时留下的回车符,导致这个getchar无法接收到第二个变量的值,从而造成各种问题。

异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,

保存到系统的键盘缓冲区。

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

8.5本章小结

本章简述了Linux的I/O设备管理机制,Unix I/O接口及函数,并简要分析了printf函数和getchar函数的实现

结论

Hello程序的执行流程如下:

1. 预处理:预处理过程扫描源代码,对其进行初步的转换,产生新的源代码提供给编译器。预处理过程读入源代码,检查包含预处理指令的语句和宏定义,并对源代码进行响应的转换。预处理过程还会删除程序中的注释和多余的空白字符。

2. 编译:将源语言经过词法分析、语法分析、语义分析以及经过一系列优化后生成汇编代码 。

3. 汇编:将高级语言转化为机器可直接识别执行的代码文件,汇编器将.s 汇编程序翻译成机器语言指令,把这些指令打包成可重定位 目标程序的格式,并将结果保存在 .o目标文件中

4. 链接:是将各种代码和数据片段收集并组合为单一文件的过程,这个文件可以被加载(复制)到内存并执行

6. 进程管理:进程提供给应用程序两个关键抽象:一个独立的逻辑控制流;一个私有的地址空间。

7. 储存管理: 当fork函数被shell调用时,内核为hello创建各种数据结构,并分配给它一个唯一的PID。execve函数调用驻留在内核区域的启动加载器代码,在当前进程中加载并运行包含在可执行目标文件hello中的程序,用hello程序有效地替代了当前程序。访问内存:MMU将程序中使用的虚拟内存地址通过页表映射成物理地址

8. IO管理:通过printf与getchar函数实现hello的IO

用计算机系统的语言,逐条总结hello所经历的过程。

这门课程由浅入深地介绍了计算机系统的联合过程。从一小段最简单的代码hello world 开始,由一个hello.c文件开始向编译汇编再到链接,储存。一步步严丝合缝,让我深刻体会到计算机系统的精妙所在。不过我目前所学的仍是凤毛麟角,期待以后对计算机有更深刻的学习与理解

你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。

(结论0分,缺失 -1分,根据内容酌情加分)

附件

列出所有的中间产物的文件名,并予以说明起作用。

文件名称

文件作用

hello.c

hello源文件

hello.i

预处理后文本文件

hello.s

编译得到的汇编文件

hello.o

汇编后的可重定位目标文件

hello

链接后可执行文件

hello.objdump

hello可执行文件反汇编代码

Hello_o.objdump

hello.o(链接前)的反汇编文件(与hello做对比)

hello.elf

hello的elf文件

hello_o_elf.txt

hello.o的ELF格式

(附件0分,缺失 -1分)

参考文献

为完成本次大作业你翻阅的书籍与网站等

[1]  [转]printf 函数实现的深入剖析 - Pianistx - 博客园  printf 函数实现的深入剖析

[2]  CSDN博客 - 专业IT技术发表平台 行缓冲——getchar()的内在原理

[3]  PKjason.Bash命令行处理流程详解

[EB/OL].https://my.oschina.net/pkjason/blog/156458,2013-08-23

[4]  Randal E. Bryant, David R. O'Hallaon. 深入理解计算机系统. 第三版. 北京市:机械工业出版社[M]. 2018 1-737

[5] Linux的jobs命令_SnailTyan的博客-CSDN博客 Linuxjobs命令

[6] getchar(计算机语言函数)_百度百科 getchar (计算机语言函数)

[7] [转]printf 函数实现的深入剖析 - Pianistx - 博客园 [转]printf 函数实现的深入剖析

(参考文献0分,缺失 -1分)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qq_52150376

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值