## hello大作业

~~

hello大作业

~~

hello一生

摘 要
摘要是论文内容的高度概括,应具有独立性和自含性,即不阅读论文的全文,就能获得必要的信息。摘要应包括本论文的目的、主要内容、方法、成果及其理论与实际意义。摘要中不宜使用公式、结构式、图表和非公知公用的符号与术语,不标注引用文献编号,同时避免将摘要写成目录式的内容介绍。

关键词:hello;一生;产生到消亡; 程序员;计算机系统

论文的目的:该论文主要介绍了hello的一生,从其产生到其运行结束到消亡,介绍一个普通的c代码他一生的经历。
论文的主要内容:hello的概述、预处理、编译、汇编、链接、进程管理、存储管理、IO管理。
论文成果及其理论与实际意义:帮助一名程序员更好的了解计算机系统的更多的深层次操作,对于编写程序代码有更深层次的帮助。

目 录

第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的整个过程。
P2P过程:在linux中,hello文件经过cpp的预处理生成hello.i,ccl的编译生成hello.s,as的汇编生成可重定位的目标执行文件hello.o、ld的链接最终成为可执行目标程序hello。
在shell中输入启动命令,shell为它使用fork函数,返回两次,产生子进程,hello便从program变成为process。
020过程:
Shell调用execve函数,,为hello映射虚拟内存,hello进入程序入口后程序开始载入物理内存,之后读入代码数据段的main函数的代码执行,CPU为运行的hello分配时间片,执行逻辑控制流。当程序运行结束后,shell父进程负责回收hello进程,内核删除相关数据,hello从无到有再到无。
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
硬件环境:Intel Core i7-6700HQ x64CPU,16G RAM,256G SSD +1T HDD.
软件环境:Ubuntu18.04.1 LTS
开发与调试工具:vim,gcc,as,ld,edb,readelf,HexEdit

1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
hello.i(hello.c预处理之后的程序文本)
hello.c(hello的原文件)

hello.s(hello.i编译成汇编语言之后的程序文本)

hello.o(hello.s生成的二进制文件)

hello.o.txt(hello.o反汇编的结果)
helloo.elf(Hello.o的ELF格式)

hello(可执行的hello二进制文件)

hello.objdmp (可执行文件hello,直接用objdump反编译之后的汇编代码)

hello.elf(可执行文件hello的elf表)
1.4 本章小结
本章主要简单介绍了hello的p2p,020过程,Hello简介,列出为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
列出为编写本论文,生成的中间结果文件的名字,文件的作用等。 。
(第1章0.5分)

第2章 预处理
2.1 预处理的概念与作用
概念:预处理一般是指在程序源代码被翻译为目标代码的过程中,生成二进制代码之前的过程。典型地,由预处理器(preprocessor) 对程序源代码文本进行处理,得到的结果再由编译器核心进一步编译。预处理器cpp根据以字符#开头的命令(宏定义、条件编译),修改原始的C程序,将引用的所有库展开合并成为一个完整的文本文件。
功能:
将源文件中用#include形式声明的文件复制到新的程序中。即告诉预处理器读取头文件的内容,插入程序的文本之中。
用实际值替换用#define定义的字符串,根据#if后面的条件决定需要编译的代码。
3]
[1] [2]

2.2在Ubuntu下预处理的命令
命令:cpp hello.c > hello.i

图2.1 使用cpp命令生成hello.i文件
2.3 Hello的预处理结果解析
使用vim打开hello.i,整个hello.i程序有3188行,main函数出现在3102行开始,到3118行结束。如下图hello.i所示:

图2.2 hello.i中main函数的位置
在这之前出现的是stdio.h unistd.h stdlib.h三个头文件的依次展开,以unistd.h的展开为例,cpp到默认的环境变量下寻找unistd.h,打开/usr/include/ unistd.h 发现其中依然使用了#define语句,cpp对此递归展开,所以最终.i程序中是没有#define的。而且发现其中使用了大量的#ifdef #ifndef的语句,cpp会对条件值进行判断来决定是否执行包含其中的逻辑。stdio.h,stdlib.h类似。
2.4 本章小结
本章主要介绍了预处理的概念与作用, 在Ubuntu下预处理的命令,Hello的预处理结果解析。

(第2章0.5分)

第3章 编译
3.1 编译的概念与作用
编译的概念:编译就是把高级语言变成计算机可以识别的2进制语言,计算机只认识1和0,编译程序把人们熟悉的语言换成2进制的。
编译的作用:编译程序把一个源程序翻译成目标程序的工作过程分为五个阶段:词法分析;语法分析;语义检查和中间代码生成;代码优化;目标代码生成。主要是进行词法分析和语法分析,又称为源程序分析,分析过程中发现有语法错误,给出提示信息。编译器的构建流程:词法分析器,用于将字符串转化成内部的表示结构。 语法分析器,将词法分析得到的标记流(token)生成一棵语法树。 目标代码的生成,将语法树转化成目标代码
注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序

3.2 在Ubuntu下编译的命令
命令:gcc -S hello.i -o hello.s

图3.1 使用gcc命令生成64位的hello.s文件
3.3 Hello的编译结果解析
3.3.0 Hello.s,Hello.c代码

图3.2 hello.s代码图

图3.3 hello.c代码图

3.3.1数据
hello.s中用到的C数据类型有:整数、字符串、数组。并未用到类型和宏。
整数:
首先确认有三个整数,分别为全局变量sleepsecs,局部变量i,还有argc。

  1. int sleepsecs

图3.4 hello.s中sleepsecs的声明
Sleepsec在c程序中被声明为全局变量,且被赋值为2.5,编译器处理时,text节声明为全局变量,.data节声明该变量(align:声明对指令或者数据的存放地址进行对齐的方式),设置对齐方式为4…data节用于存放已经初始化的全局和静态C变量,设置类型为对象(.type用来指定是函数类型或是对象类型),设置字节数为4(.size声明大小),最后将sleepsecs设置为long类型其值为2.
2. int i
编译器将局部变量存储在寄存器或者栈空间中,在hello.s中编译器将i存储在栈上的空间rbp寄存器中,可以看出i占据了4个字节。
3. int argc:
它是作为第一个参数传入的。
4. 其他的整数:
其他的整数数据都是以立即数的形式出现的,直接编码在汇编代码中。
字符串:
程序中的字符串分别是:

图3.5 hello.s中声明在.LC0和.LC1段中的字符串

.string声明一个string类型

  1. .string “Usage: Hello \345\255\246\345\217\267 \345\247\223\345\220\215\357\274\201”
    第一个printf传入的输出参数,字符串被编码成UTF-8格式了,一个汉字在编码中占3个字节,一个\代表一个字节。
    它的.c代码是printf("Usage: Hello 学号 姓名!\n”)
  2. “Hello %s %s\n”,第二个printf传入的输出格式化参数,后两个字符串都声明了在.rodata只读数据节。

数组:
程序中的数组是charargv【】,argv是作为存放char类型指针的数组。Argv的单个元素的char的大小为8B,它的指针指向已经分配好的一片存放着字符指针的连续空间,起始地址为argv。

图3.6 计算地址取出数组argv【1】,argv【2】值
3.3.2 赋值
程序中赋值操作有两个。

  1. int sleepsecs=2.5
    sleepsecs为全局变量,在.data节中将sleep声明为值为2的long类型的数据。(在3.3.1整数数据中已经说明)
  2. 定义int i=0;
    整数类型赋值用mov指令完成。
    I为4B。用movl进行赋值操作。
    Mov根据数据类型不同使用不同的后缀。
    b (1B)
    w (2B)
    I (4B)
    q (8B)
    3.3.3 类型转换
    该程序将int类型的sleepsecs赋值为2.5,发生了隐式类型转换,将浮点数2.5转换为int类型的2.(当在double或float向int进行类型转换的时候,程序改变数值和位模式的原则是:值会向零舍入),浮点数默认类型为double,所以上述强制转化是double强制转化为int类型。遵从向零舍入的原则,将2.5舍入为2。
    若浮点数转换为整数溢出了,那么就会产生一个随机不确定的整数值。
    3.3.4算数操作
    程序中包含的算数操作:
    i++,leaq的使用
    I的自增使用addl操作,l代表4B大小数据。
    汇编中使用leaq,加载有效地址并传递给rdi。

图3.7 局部变量i的计算操作以及有效地址计算操作
3.3.5 关系操作
程序中涉及的关系运算为:
1. Argc!=3:
判断argc不等于3,hello.s中使用cmpl $3, -20(%rbp),(CMP S1,S2 S2-S1比较-设置条件码),计算出argc-3的值设置条件码,为je提供条件码进行判断是否跳转。(J根据与条件码进行跳转)。
2. I<10:
判断i小于10. cmpl $9, -4(%rbp) jle .L4,与argc!=3类似,计算i-9并设置条件码,为jle提供条件码判断是否跳转。

3.3.6 控制转移
程序中的控制转移有:

  1. if (argv!=3):当argv不等于3的时候执行的代码。结合3.3.5的关系操作,je判断ZF标志位,如果为0,说明argv=3,不执行if中的代码,跳转到.L2,否则就顺序执行下一条语句,即为if中的语句。

图3.8 if语句的编译,不满足条件跳转。
2. for(i=0;i<10;i++):使用定义的变量i计数,循环10次。编译器先无条件跳转到位于循环体L4之后的比较代码,用cmpl比较,如果i<=9,则进入L4的for循环体进行循环执行,否则循环结束。

图3.9 for循环的编译

3.3.7 函数操作
程序中涉及的函数操作:

  1. main函数:
  2. 传递控制(进行过程Q的时候,程序计数器必须设置为Q的代码的起始地址,然后在返回时,要把程序计数器设置为P中调用Q后面那条指令的地址。下面的函数同理):
    Main函数被call(系统启动函数__libc_start_main)调用才能执行,call指令将下一条指令的地址dest压栈,然后跳转到main函数。
  3. 传递数据(P必须能够向Q提供一个或多个参数,Q必须能够向P中返回一个值。):
    外部调用过程向main函数传递argc和argv,使用%rdi和%rsi存储,函数正常出口为return 0,将%eax设置为0返回。
  4. 分配和释放内存:
    使用%rbp记录栈底的帧,函数分配栈帧空间在%rbp上,程序结束,调用leave指令,恢复栈空间为之前调用的状态,然后ret返回,将下一条要执行的指令设置为dest。
  5. printf函数:
  6. 传递数据:第一次printf将%rdi设置为第一个 字符串的首地址。
    第二次设置%rdi为第二个字符串的首地址,设置%rsi为argv【1】,%rdx为argv【2】。
    2.控制传递:只输出一个字符串参数时使用puts,多个时使用printf。
  7. exit函数:将%edi设置为1,调用call exit@PLT。
  8. sleep函数:将%edi设置为sleepsecs,调用callsleep@PLT。
  9. getchar函数:调用call getchar@PLT。

此部分是重点,说明编译器是怎么处理C语言的各个数据类型以及各类操作的。应分3.3.1~ 3.3.x等按照类型和操作进行分析,只要hello.s中出现的属于大作业PPT中P4给出的参考C数据与操作,都应解析。

3.4 本章小结
本章主要阐述了编译的概念与作用,在Ubuntu下编译的命令,并且重点说明了编译器是怎么处理C语言的各个数据类型以及各类操作的。分3.3.1~ 3.3.8按照类型和操作进行分析, hello.s中C数据与操作,都进行了解析。编译器将.i的拓展程序编译为.s的汇编代码。我们的hello从c语言解构为更加低级的汇编语言。
(第3章2分)

第4章 汇编
4.1 汇编的概念与作用
汇编的概念:汇编大多是指汇编语言,汇编程序。把汇编语言翻译成机器语言的过程称为汇编。
汇编的作用:随着现代软件系统越来越庞大复杂,大量经过了封装的高级语言如C/C++,Pascal/Object Pascal也应运而生。这些新的语言使得程序员在开发过程中能够更简单,更有效率,使软件开发人员得以应付快速的软件开发的要求。而汇编语言由于其复杂性使得其适用领域逐步减小。但这并不意味着汇编已无用武之地。由于汇编更接近机器语言,能够直接对硬件进行操作,生成的程序与其他的语言相比具有更高的运行速度,占用更小的内存,因此在一些对于时效性要求很高的程序、许多大型程序的核心模块以及工业控制方面大量应用。

注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。
4.2 在Ubuntu下汇编的命令
指令:as hello.s -o hello.o

图4.1 使用as指令生成hello.o文件
4.3 可重定位目标elf格式
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。

使用readelf -a hello.o > helloo.elf 指令获得hello.o文件的ELF格式。其组成如下:

  1. ELF Header:
    以16位的序列Magic开始,描述了生成该文件的系统的字的大小和字节的顺序,剩下的部分包含帮助链接器语法分析和解释目标文件的信息,包括ELF头的大小,目标文件的类型,机器类型,字节头部表和文件偏移,以及节头部表中条目的大小和数量等信息。

图4.2 ELF Header
2.节头:包含 名称 类型 地址 大小 全体大小 旗标 链接 信息 对齐

图4.3 节头部表Section Headers
3.重定位节:包含.text节中需要重定位的信息:偏移量 信息 类型 符号值 符号名称 + 加数。当链接器把这个目标文件和其他文件组合时,需要修改这些位置。对函数进行重定位声明。

图4.4 重定位节.rela.text
重定位的分析过程:
重定位由两部分组成:
重定位段和符号定义
链接器将所有相同类型的段合并为同一类型的新的聚合段。
然后将运行时存储器地址赋给新的聚合段,赋给输入模块定义的每个段,以及赋给输入模块定义的每个符号。
此时程序中的每个指令和全局变量都有唯一的运行时存储器地址。
重定位段中的符号引用
链接器修改代码段和数据段中对每个符号的引用,使他们指向正确的运行时地址。
这一步链接器依赖于重定位条目,使得他们指向正确的运行时地址。

图4.5 通过HexEdit查看hello.o中的.rela.text节

.rela.eh_frame : 存放eh_frame节的重定位信息。
.symtab:符号表,用来存放程序中定义和引用的函数和全局变量的信息。重定位需要引用的符号都在其中声明。
4.4 Hello.o的结果解析

objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。

图4.6 hello.s与反汇编代码

机器语言的构成,与汇编语言的映射关系:
在汇编语言中,用助记符(Memoni)代替操作码,用地址符号(Symbol)或标号(Label)代替地址码。这样用符号代替机器语言的二进制码,就把机器语言变成了汇编语言。于是汇编语言亦称为符号语言

特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。

  1. 全局变量访问:在.s文件中,访问rodata(printf中的字符串),使用段名称+%rip,在反汇编代码中0+%rip,因为rodata中数据地址也是在运行时确定,故访问也需要重定位。所以在汇编成为机器语言时,将操作数设置为全0并添加重定位条目
  2. 分支转移函数的调用:
    在.s文件中,函数调用之后直接跟着函数名称,而在反汇编程序中,call的目标地址是当前地址的下一条指令。Hello.s调用的函数是共享库中的函数,最终需要通过动态链接器才能确定函数运行地址,而汇编语言中,由于地址不确定,所以将相对地址设置为0,然后在rela text节添加冲定位条目,使静态链接进一步确定。
    在.s文件,反汇编代码跳转指令操作使用的不是段名称.L3,段名称只是在汇编语言中便于编写的助记符,在汇编成机器语言就不存在了,变成了确定的地址,这是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。
    4.5 本章小结
    本章介绍了汇编的概念与作用,在Ubuntu下汇编的命令,可重定位目标elf格式,Hello.o的结果解析,即为hello.s到hello.o的汇编过程,通过使用hexedit,elf格式,objdump等手段,了解到了汇编语言到机器语言的转换。

(第4章1分)

第5章 链接
5.1 链接的概念与作用
链接的概念:把所有编译后得到的目标模块连接装配起来,再与函数库相连接成一个整体的过程叫做链接。
链接的作用:
1 对各个目标模块中没有定义的变量,在其它目标文件中找到相关的定义
2 把不同目标文件中生成的相同类型的段进行合并
3 把不同目标文件中的变量进行地质重定位

注意:这儿的链接是指从 hello.o 到hello生成过程。
5.2 在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 使用ld命令链接生成可执行程序hello
5.3 可执行目标文件hello的格式
分析hello的ELF格式:ELF 头:节头:重定位节 ‘.rela.text’ at offset 0x340 contains 8 entries: 重定位节 ‘.rela.eh_frame’ at offset 0x400 contains 1 entry:

图5.2 hello的ELF格式

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

5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。

图5.3使用edb打开hello程序,通过edb的Data Dump窗口查看加载到虚拟地址中的hello程序。
在0x400000~0x401000段中,程序被载入,自虚拟地址0x400000开始,自0x400fff结束,这之间每个节(开始 ~ .eh_frame节)的排列即开始结束同5.3中Address中声明。查看虚拟地址段0x6000000x602000,在0fff空间中,与0x4000000x401000段的存放的程序相同,在fff之后存放的是.dynamic.shstrtab节。
5.5 链接的重定位过程分析
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。

图5.4 hello的反汇编代码
Hello与hello.o的函数的个数不同,使用ld链接,定义了函数入口,初始化函数,动态链接器与动态链接共享库定义hello.o中的各种函数,将上述共享函数加入。
链接器解析重定位条目时发现重定位类型,确定elf节的相对距离,。
.rodata节:链接器解析重定位条目确定rodata与text节相对距离,直接修改call之后的值为目标地址与下一条指令的差。

5.6 hello的执行流程
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
0x7fbaa6bfaea0 = 0x00007fbaa6bfaea0
__libc_start_main(0x0000000000400532, 0x1, 0x00007fff07008e58, 0x00000000004005c0, 0x0000000000400630, 0x00007fbaa6c099a0, 0x00007fff07008e48)
qword ptr [rip + 0x200ac6] = [0x0000000000600ff0] = 0x00007fbaa6829ab0
0x40055d = 0x000000000040055d
0x4004b0 = 0x00000000004004b0
puts(<0x0000000000400644> "Usage: Hello ")
0x4004e0 = 0x00000000004004e0

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

图5.5 查看 dl_init前后代码段内容

图5.6 Dl_init前后数据段内容
在PLT中使用的jmp,执行完目标函数之后的返回地址为最近call指令下一条的指令地址,即在main中的调用完成地址。
在之后的函数调用时首先跳转到PLT实行.plt逻辑,第一次访问跳转时GOT地址为下一条指令,将函数序号压栈,然后跳转到PLT【0】,在PLT【0】中将重定位表地址压栈,然后访问动态链接器,在动态链接器中使用函数序号和重定位表确定函数运行时的地址,重写GOT,再讲控制传递给目标函数。
5.8 本章小结
在本章中主要介绍了链接的概念与作用,在Ubuntu下链接的命令,可执行目标文件hello的格式,hello的虚拟地址空间链接的重定位过程分析hello的执行流程Hello的动态链接分析。

(第5章1分)

第6章 hello进程管理
6.1 进程的概念与作用
进程的概念:主要有两点:第一,进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。第二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程。
进程的功能:进程是操作系统中最基本、重要的概念。是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律引进的一个概念,所有多道程序设计操作系统都建立在进程的基础上。

6.2 简述壳Shell-bash的作用与处理流程
壳Shell-bash的作用:Linux实质上是一个操作系统内核,一般用户不能直接使用内核,而是通过外壳程序,也就是所谓的shell来与内核进行沟通。外壳程序可以保证操作系统的安全性,抵御用户的一些不正确操作。Linux的外壳程序称作shell(命令行解释器),它能够将命令翻译给内核、将内核处理结果翻译给用户。一般我们使用的shell为bash。在解释命令的时候,bash不会直接参与解释,而是创建新进程进行命令的解释,bash只用等待结果即可,这样能保证bash进程的安全。
处理流程:shell 执行一系列的读/求值(read /evaluate ) 步骤,然后终止。读步骤读取来自用户的一个命令行。求值步骤解析命令行,并代表用户运行程序。
6.3 Hello的fork进程创建过程
Shell通过调用fork 函数创建一个新的运行的子进程。也就是Hello程序,Hello进程几乎但不完全与Shell相同。Hello进程得到与Shell用户级虚拟地址空间相同的(但是独立的)一份副本,包括代码和数据段、堆、共享库以及用户栈。Hello进程还获得与Shell任何打开文件描述符相同的副本,这就意味着当Shell调用fork 时,Hello可以读写Shell中打开的任何文件。Shell和Hello进程之间最大的区别在于它们有不同的PID。

图6.1hello的fork进程
6.4 Hello的execve过程
使用execve就是一次系统调用,首先要做的将新的可执行文件的绝对路径从调用者(用户空间)拷贝到系统空间中。在得到可执行文件路径后,就找到可执行文件打开,由于操作系统已经为可执行文件设置了一个数据结构,就初始化这个数据结构,保存一个可执行文件必要的信息。可执行文件不是真正上能够自己运行的,需要有代理人来代理。在系统内核中有一个formats队列,循环遍历这个队列,看看现在被初始化的这个数据结构是哪个代理人可以代理的。如果没有就继续查看数据结构中的信息。
execve 函数加载并运行可执行目标文件filename, 且带参数列表argv 和环境变量列表envp 。只有当出现错误时,例如找不到filename, execve 才会返回到调用程序。所以,与fork 一次调用返回两次不同, execve 调用一次并从不返回。

图6.2 hello的execve进程
6.5 Hello的进程执行
新进程的创建,首先在内存中为新进程创建一个task_struct结构,然后将父进程的task_struct内容复制其中,再修改部分数据。分配新的内核堆栈、新的PID、再将task_struct 这个node添加到链表中。然后将可执行文件装入内核的linux_binprm结构体。进程调用execve时,该进程执行的程序完全被替换,新的程序从main函数开始执行。调用execve并不创建新进程,只是替换了当前进程的代码区、数据区、堆和栈。在进程调用了exit之后,该进程并非马上就消失掉,而是留下了一个成为僵尸进程的数据结构,记载该进程的退出状态等信息供其他进程收集,除此之外,僵尸进程不再占有任何内存空间。Linux 系统中的每个程序都运行在一个进程上下文中,有自己的虚拟地址空间。当shell 运行一个程序时,父shell 进程生成一个子进程,它是父进程的一个复制。子进程通过execve 系统调用启动加载器。加载器删除子进程现有的虚拟内存段,并创建一组新的代码、数据、堆和栈段。新的栈和堆段被初始化为零。通过将虚拟地址空间中的页映射到可执行文件的页大小的片(chunk), 新的代码和数据段袚初始化为可执行文件的内容。最后,加载器跳转到_start地址,它最终会调用应用程序的main 函数。
结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。
6.6 hello的异常与信号处理
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
程序正常运行,程序执行完成后,进程被回收。

图6.3程序正常运行

程序运行时Ctrl-Z,当按下该键后,shell父进程收到SIGSTP信号,信号处理函数的逻辑是打印屏幕回显、将hello进程挂起,通过ps命令我们可以看到hello进程有没有被回收,此时他的后台job号是1,调用fg 1将其调到前台,此时shell程序首先打印hello的命令行命令,hello继续运行打印剩下的信息,之后输入字符串,程序结束,进程被回收。

图6.4程序运行时Ctrl-Z

程序运行时Ctrl-C,shell父进程收到SIFINT,信号处理函数的逻辑是结束hello,并回收hello进程。

图6.5程序运行时Ctrl-C

程序运行时乱按,乱按只会将输入缓存到stdin,当读完‘\n’后,其他字符串会被当做shell命令行输入。

图6.6程序运行时乱按

6.7本章小结
本章主要讲述了进程的概念御座用,简述了壳Shell-bash的作用与处理流程,Hello的fork进程创建过程,Hello的execve过程,hello的异常与信号处理,Hello的进程执行。Hello的一切活动由shell帮助,shell为其fork,execve,分配时间片。

(第6章1分)

第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:逻辑地址(LogicalAddress)是指由程序产生的与段相关的偏移地址部分。就是hello.o里面的相对偏移地址。
线性地址:地址空间(address space) 是一个非负整数地址的有序集合,如果地址空间中的整数是连续的,那么我们说它是一个线性地址空间(linear address space) 。就是hello里面的虚拟内存地址。
虚拟地址:CPU 通过生成一个虚拟地址(Virtual Address, VA) 。就是hello里面的虚拟内存地址。
物理地址:用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应。计算机系统的主存被组织成一个由M 个连续的字节大小的单元组成的数组。每字节都有一个唯一的物理地址。就是hello在运行时虚拟内存地址对应的物理地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
先将逻辑地址分成段选择符+段描述符的判别符(TI)+地址偏移量的形式,然后先判断TI字段,看看这个段描述符究竟是局部段描述符(ldt)还是全局段描述符(gdt),然后再将其组合成段描述符+地址偏移量的形式,这样就转换成线性地址了。

图7-1逻辑地址到线性地址的变换
7.3 Hello的线性地址到物理地址的变换-页式管理
在这个转换中要用到翻译后备缓冲器(TLB),首先我们先将线性地址分为VPN(虚拟页号)+VPO(虚拟页偏移)的形式,然后再将VPN拆分成TLBT(TLB标记)+TLBI(TLB索引)然后去TLB缓存里找所对应的PPN(物理页号)如果发生缺页情况则直接查找对应的PPN,找到PPN之后,将其与VPO组合变为PPN+VPO就是生成的物理地址了。
7.4 TLB与四级页表支持下的VA到PA的变换
36位VPN 被划分成四个9 位的片,每个片被用作到一个页表的偏移量。CR3 寄存器包含Ll页表的物理地址。VPN 1 提供到一个Ll PET 的偏移量,这个PTE 包含L2 页表的基地址。VPN 2 提供到一个L2 PTE 的偏移量,以此类推。

图7-2 TLB与四级页表支持下的VA到PA的变换
7.5 三级Cache支持下的物理内存访问
CPU发出一个虚拟地址,在TLB里面寻找。如果命中,那么将PTE发送给L1Cache,否则先在页表中更新PTE。然后再进行L1根据PTE寻找物理地址,检测是否命中的工作。这样就能完成Cache和TLB的配合工作。

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

7.6 hello进程fork时的内存映射
当fork 函数被shell调用时,内核为hello进程创建各种数据结构,并分配给它一个唯一的PID 。为了给hello进程创建虚拟内存,它创建了hello进程的mm_struct 、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。当fork 在hello进程中返回时,hello进程现在的虚拟内存刚好和调用fork 时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,因此,也就为每个进程保持了私有地址空间的抽象概念。
7.7 hello进程execve时的内存映射
删除已存在的用户区域。删除shell虚拟地址的用户部分中的已存在的区域结构。
映射私有区域。为hello的代码、数据、bss 和栈区域创建新的区域结构。所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为hello 文件中的.text和.data 区。bss 区域是请求二进制零的,映射到匿名文件,其大小包含在hello 中。栈和堆区域也是请求二进制零的,初始长度为零。图7.7 概括了私有区域的不同映射。
映射共享区域。如果hello程序与共享对象(或目标)链接,比如标准C 库libc. so, 那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内。
设置程序计数器(PC) 。execve 做的最后一件事情就是设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。
7.8 缺页故障与缺页中断处理
情况1:段错误:首先,先判断这个缺页的虚拟地址是否合法,那么遍历所有的合法区域结构,如果这个虚拟地址对所有的区域结构都无法匹配,那么就返回一个段错误(segment fault)
情况2:非法访问:接着查看这个地址的权限,判断一下进程是否有读写改这个地址的权限。
情况3:如果不是上面两种情况那就是正常缺页,那就选择一个页面牺牲然后换入新的页面并更新到页表。

7.9动态存储分配管理
Printf会调用malloc,请简述动态内存管理的基本方法与策略。
基本方法:这里指的基本方法应该是在合并块的时候使用到的方法,有最佳适配和第二次适配还有首次适配方法,首次适配就是指的是第一次遇到的就直接适配分配,第二次顾名思义就是第二次适配上的,最佳适配就是搜索完以后最佳的方案,当然这种的会在搜索速度上大有降低。
策略:这里的策略指的就是显式的链表的方式分配还是隐式的标签引脚的方式分配还是分离适配,带边界标签的隐式空闲链表分配器允许在常数时间内进行对前面块的合并。这种思想是在每个块的结尾处添加一个脚部,其中脚部就是头部的一个副本。如果每个块包括这样一个脚部,那么分配器就可以通过检查它的脚部,判断前面一个块的起始位置和状态,这个脚部总是在距当前块开始位置一个字的距离。显式空间链表就是将空闲块组织为某种形式的显式数据结构。因为根据定义,程序不需要一个空闲块的主体,所以实现这个数据结构的指针可以存放在这些空闲块的主体里面。例如,堆可以组织成一个双向空闲链表,在每个空闲块中,都包含一个前驱和后继指针,使首次适配的分配时间从块总数的线性时间减少到了空闲块数量的线性时间。为了分配一个块,必须确定请求的大小类,并且对适当的空闲链表做首次适配,查找一个合适的块。如果找到了一个,那么就(可选地)分割它,并将剩余的部分插入到适当的空闲链表中。如果找不到合适的块,那么就搜索下一个更大的大小类的空闲链表。如此重复,直到找到一个合适的块。如果空闲链表中没有合适的块,那么就向操作系统请求额外的堆内存,从这个新的堆内存中分配出一个块,将剩余部分放置在适当的大小类中。要释放一个块,我们执行合并,并将结果放置到相应的空闲链表中
7.10本章小结
本章主要介绍了hello的存储器地址空间,Intel逻辑地址到线性地址的变换-段式管理,Hello的线性地址到物理地址的变换-页式管理,TLB与四级页表支持下的VA到PA的变换,三级Cache支持下的物理内存访问,hello进程fork时的内存映射,hello进程execve时的内存映射,动态存储分配管理。只有hello要真正进行活动的时候,hello才能向MMU递交统一格式的虚拟地址来获得自己的空间,还要使用malloc申请额外的堆空间。

(第7章 2分)

第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件
设备管理:unix io接口
在设备模型中,所有的设备都通过总线相连。每一个设备都是一个文件。设备模型展示了总线和它们所控制的设备之间的实际连接。在最底层,Linux 系统中的每个设备由一个 struct device 代表,而Linux统一设备模型就是在kobject kset ktype的基础之上逐层封装起来的。设备管理则是通过unix io接口实现的。
8.2 简述Unix IO接口及其函数
linux 提供如下 IO 接口函数:
read 和 write – 最简单的读写函数;
readn 和 writen – 原子性读写操作;
recvfrom 和 sendto – 增加了目标地址和地址结构长度的参数;
recv 和 send – 允许从进程到内核传递标志;
readv 和 writev – 允许指定往其中输入数据或从其中输出数据的缓冲区;
recvmsg 和 sendmsg – 结合了其他IO函数的所有特性,并具备接受和发送辅助数据的能力。
8.3 printf的实现分析

从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。

图8.1 printf代码展示
参数中明显采用了可变参数的定义,可以看到*fmt是一个char 类型的指针,指向字符串的起始位置。

图8.2 vsprintf函数代码展示
从上面vsprintf函数可以看出,这个函数的作用是将所有的参数内容格式化之后存入buf,然后返回格式化数组的长度。write函数是将buf中的i个元素写到终端的函数。
8.4 getchar的实现分析
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章主要讲述了Linux的IO设备管理方法,简述Unix IO接口及其函数,printf的实现分析, getchar的实现分析。了解了开关读写转移文件的接口及相关函数。
(第8章1分)
结论

Hello最开始是一个程序员所编写的c代码,是.c格式的文件,首先经过cpp的预处理,将他的头文件与define拓展开来生成.i格式的文件,之后这个.i文件经过gcc的编译,生成了汇编语言形式的.s文件,经过as的汇编处理后.s文件被汇编成拥有机器代码的可重定位的.o文件,最后通过ld链接器将.o文件与其他文件链接,包括共享库的函数,生成了可执行文件hello,hello便从此刻开始可以执行。
Hello在shell中可以运行,在linuxshell的fork函数生成以及execve函数的进行下执行,其中会发生异常和信号处理,hello的存储地址分为逻辑地址,线性地址,虚拟地址以及物理地址,他经过了逻辑地址到线性地址,线性地址到物理地址的变换,最后通过页表进行访问与存储,通过动态存储进行管理。而hello中的printf与getchar函数则通过linux的IO设备进行输入与输出。
通过此次论文,我对计算机系统的设计与实现有了更深入的理解,对于计算机系统有了一个总体的轮廓,对于一个文件的从生成到执行到消亡有了一个系统性的认识,感觉到了计算机系统的伟大,它并不是晦涩难懂,而是分工合理,效率极高的工作,计算机系统是一个很伟大的设计与发明。
希望在今后能对计算机系统的哪一步分进行创新优化,创新出比python还要高度封装的函数,使我们今后的编程变得更加的简单快捷。
(结论0分,缺失 -1分,根据内容酌情加分)

附件

hello.i(hello.c预处理之后的程序文本)
hello.c(hello的原文件)

hello.s(hello.i编译成汇编语言之后的程序文本)

hello.o(hello.s生成的二进制文件)

hello.o.txt(hello.o反汇编的结果)
helloo.elf(Hello.o的ELF格式)

hello(可执行的hello二进制文件)

hello.objdmp (可执行文件hello,直接用objdump反编译之后的汇编代码)

hello.elf(可执行文件hello的elf表)
(附件0分,缺失 -1分)

参考文献
为完成本次大作业你翻阅的书籍与网站等
[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分,缺失 -1分)

你可能感兴趣的:(hello)