程序人生-Hello’s P2P

程序人生-Hello’s P2P

摘 要
本文通过介绍hello从产生到消亡体现一个C代码从简单C源文件经过多种处理后成为可执行文件,在屏幕上显示即hello.c程序,经过预处理,编译,汇编,链接4个步骤后变为可执行文件。通过这个hello的一生能更好的了解计算机系统的更多的深层次操作,对于编写程序代码有更深层次的帮助

关键词:hello的一生,预处理,编译,汇编,链接,进程管理,存储管理,IO管理,P2P。

(摘要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的整个过程。
P2P:在linux中,hello文件经过cpp的预处理生成hello.i,ccl的编译生成hello.s,as的汇编生成可重定位的目标执行文件hello.o、ld的链接最终成为可执行目标程序hello。
020: Shell通过execve在fork产生的子进程中加载hello,先删除当前虚拟地址的用户部分已存在的数据结构,为hello的代码、数据、bss和栈区域创建新的区域结构,然后映射共享区域,设置程序计数器,使之指向代码区域的入口点,进入main函数,CPU为hello分配时间片执行逻辑控制流。hello通过Unix I/O管理来控制输出。hello执行完成后shell会回收hello进程,并且内核会从系统中删除hello所有痕迹,至此,hello完成O2O的过程。
1.2 环境与工具
硬件环境:X64 CPU;2GHz;2G RAM;256GHD Disk 以上
软件环境Windows7 64位以上;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64位/优麒麟 64位
开发工具:gcc + gedit , Codeblocks , gdb ed

1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
hello.c hello源代码
hello.i 预处理之后的文件
hello.s 汇编语言文件
hello.o 可重定位目标文件
hello 链接之后的可执行目标文件
hello1.elf hello.o的ELF格式
hello.txt hello.o反汇编
hello2.elf hello的ELF格式
hello1.txt hello反汇编

1.4 本章小结
介绍了hello的P2P,O2O过程,介绍了实验的环境工具和中间结果。
(第1章0.5分)

第2章 预处理
2.1 预处理的概念与作用
预处理又叫做预编译,是指在对C源代码文件进行词法扫描和语法分析之前所做的工作。预处理器cpp根据以字符#开头的命令,修改原始的C程序。;例如hello.c中第一行的#include<stdio.h>命令告诉预处理器读取系统头文件,通常是以.i作为文件扩展名。
预处理的作用:

  1. 删除#define,展开所有宏定义。
  2. 处理条件预编译 #if, #ifdef, #if, #elif,#endif
  3. 处理“#include”预编译指令,将包含的“.h”文件插入对应位置。这可是递归进行的,文件内可能包含其他“.h”文件。
  4. 删除所有注释。/**/,//。
  5. 添加行号和文件标识符。用于显示调试信息:错误或警告的位置。
  6. 保留#pragma编译器指令。

2.2在Ubuntu下预处理的命令
命令:gcc -E hello.c -o hello.i

2.3 Hello的预处理结果解析

经过预处理后,hello.i有3千多行,并将头文件stdio.h,unistd.h,stdlib.h的内容添加到hello.i中,并且删除所有注释
2.4 本章小结
进行hello.c的预处理,生成hello.i,将c文件改变成了统一格式,对之前代码进行了一些修正。

(第2章0.5分)

第3章 编译
3.1 编译的概念与作用
概念:编译器ccl将文本文件hello.i翻译成文本文件hello.s,它包含一个汇编语言程序。该程序包含函数main的定义。
作用:以一种低级机器语言指令为不同高级语言的不同编译器提供了通用的输出语言

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

3.3 Hello的编译结果解析
 3.3.1 数据处理

 整型变量
局部变量:int i; 编译器将局部变量存储在寄存器或者栈空间中。

	从上图可以看出编译器将i存储在栈上空间-4(%rbp)中
外部参数:main函数的传参:int argc,char *argv[];这些数据都是只会在当前的局部函数中进行读写的,外部函数没有能够正常访问到这些数据的方法。这类数据一般是在程序运行的栈中保存,寄存器中进行传递。同时在栈于寄存器中都可以对其进行修改。

 字符串
第一个字符串被编码成UTF-8格式,一个汉字在utf-8编码中占三个字节,一个\代表一个字节。
恰好对应

第二个字符串"Hello %s %s\n"
编译时这两个字符串保存在.rodata中
 3.3.2 赋值
对局部变量i的赋值:使用movl语句,对应于C程序中i=0。

 3.3.3 算术操作

	i++对应

 3.3.4 关系操作

  对应
	 

   
  对应

C源代码中的语句是i<8 而这里的语句的等效C语句确是i<=7,由于编译器会对C代码进行优化。

 3.3.5 数组操作
通过对传入的字符串数组argv进行寻址来读取参数。argv是从命令行键入的字符串的地址数组,里面按顺序存放着命令行输入的字符串在内存中的存放地址。

argv[2]地址

argv[1]地址

 3.3.6 控制转移
if分支判断语句

设置条件码,判断ZF零标志,如果最近的操作得出的结果为0,则跳到.L2中,否则顺序执行下一条语句。

for语句

for循环的控制时比较cmpl $7, -4(%rbp) ,当i大于7时跳出循环,否则进入.L4循环体内部执行

 3.3.7 函数操作
函数操作对应汇编语言中call指令。函数要提供对过程的机器支持,需要处理许多不同的属性。假设过程P调用过程Q,Q执行后返回P,这些动作包括以下机制:
传递控制 :在进入过程Q的时候,程序计数器必须被设置为Q的代码的起始地址,然后返回时,要把程序计数器设置为P中调用Q后面那条指令的地址。
传递数据 :P必须能够向Q提供一个或多个参数,Q必须能够向P返回一个值。
分配和释放内存 :在开始时,Q可能需要为局部变量分配空间,而在返回前,又必须释放这些存储空间
程序中调用函数的有:
1.main函数
由系统进行调用,并通过外部输入向其传入参数argc,argv,分别使用%rdi和%rsi存储,函数返回值为return 0,将%eax设置0返回。使用%rbp记录栈帧的底,函数分配栈帧空间在%rbp之上,程序结束时,调用leave指令,leave相当于mov %rbp,%rsp,pop %rbp,恢复栈空间为调用之前的状态,然后ret返回

2.printf函数
传递数据时将格式串信息放在rdi中,for循环中call printf时传入了 argv[1]和argc[2]的地址,其中设置%rsi为argv[1],%rdx为argv[2],具体调用过程使用call语句调用。第一个printf语句因为只需要输出相应字符串信息,所以编译器将其优化为puts,输出多个字符串时用printf

3.exit函数
将%edi设置为1,调用call exit@PLT

4.sleep函数
传入参数argv[3],再调用call sleep@PLT

5.getchar函数
调用 call getchar@PLT

3.4 本章小结
在该阶段,编译器将hello.i文件编译成更抽象更低级的hello.s汇编语言文件,为汇编阶段产生机器可识别的机器语言指令做准备。

(第3章2分)

第4章 汇编
4.1 汇编的概念与作用
概念:汇编器as将.s文本文件翻译成机器语言指令,把这些指令打包成一种叫做可重新定位目标程序的格式,并将结果保存在目标文件.o中。.o文件是一个二进制文件,它包含17个字节是函数main的指令编码。

作用:将汇编语言翻译成一条条机器语言方便机器执行该段代码。
4.2 在Ubuntu下汇编的命令
命令:gcc -c hello.s -o hello.o

4.3 可重定位目标elf格式
使用readelf -a hello.o > hello1.elf 指令获得hello.o文件的ELF格式。

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

Scetion Headers:节头部表,存储了elf表中每一个节的具体信息,包括类型,名称,偏移值等。以此为索引,能够对elf文件中每一个具体的节进行访问。

.rela.text:重定位节这个节包含了.text(具体指令)节中需要进行重定位的信息。这些信息描述的位置,在由.o文件生成可执行文件的时候需要被修改(重定位)。

.real.eh_frame:存放eh_frame节的重定位信息。

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

4.4 Hello.o的结果解析
可重定位目标文件与汇编代码的区别:
1. 跳转语句对比:在.o文件中,跳转的位置已经由符号指代变成了具体的数值。
2. 函数调用对比:汇编代码文件中的call对函数调用的语句都是直接以函数名来指代,而在.o文件中是一条重定位条目指引的信息。
3. 立即数:在.o文件当中,立即数都变为16进制。
4. 全局变量访问:在.s文件中,使用段名称+%rip,在反汇编代码中0x0(%rip),因为rodata中数据地址也是在运行时确定,故访问也需要重定位。所以在汇编成为机器语言时,将操作数设置为全0并添加重定位条目

4.5 本章小结
本章介绍了hello从hello.s到hello.o的汇编过程,通过查看hello.o的elf格式,了解到从汇编语言到机器语言的转换。

(第4章1分)

第5章 链接
5.1 链接的概念与作用
概念:链接将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载到内存并执行。链接可以执行与编译时,也就是源代码被翻译成机器代码时;也可以执行于加载时,也就是程序被加载器加载到内存并执行时;甚至可以执行与运行时,也就是由应用程序来执行。
作用:链接器合并后得到可执行文件,可以被加载到内存中,由系统执行
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.3 可执行目标文件hello的格式
命令:使用readelf -a hello > hello2.elf 命令生成hello程序的ELF格式文件。

ELF Header:描述了整个ELF表的信息

节头部表:存储了elf表中每一个节的具体信息,包括类型,名称,偏移值等。以此为索引,能够对elf文件中每一个具体的节进行访问。
Name对应各节的名字,Type对应记录了每节的类型 ,address对应了这一节的信息在虚拟内存中的存储位置。Offset对应了每一节相对于0x400000的偏移地址。Size记录了每一节的大小。Align记录了对齐位数,Info记录这一节的信息,后两位对应类型,前六位对应在symtab节中的ndx值。

5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息。
通过Data Dump查看hello程序的虚拟地址空间各段信息。可执行文件中加载的信息从0x400000处开始存放。Section Headers节中的信息可以知道这些虚拟内存中相应位置存储的信息。

Program Header:可执行文件的程序头部表,是ELF可执行文件的连续的片被映射到连续的内存段的映射关系。

5.5 链接的重定位过程分析
objdump -d -r hello > hello1.txt生成反汇编
区别:

  1. hello可执行目标文件中多出了.init段和.plt段。.init段用于初始化程序执行环境,.plt段是程序执行时的动态链接。所有的重定位条目都被修改为了确定的运行时内存地址。
  2. 程序添加了许多动态链接库中的函数。使用ld链接,定义了函数入口,初始化函数,动态链接器与动态链接共享库定义hello.o中的各种函数,将上述共享函数加入。
  3. hello.o中的相对偏移地址到了hello中变成了虚拟内存地址,hello中使用的跳转地址和函数调用地址均为虚拟内存地址。

链接过程:在使用ld命令链接的时候,指定了动态链接器为64的/lib64/ld-linux-x86-64.so.2,crt1.o、crti.o、crtn.o中主要定义了程序入口_start、初始化函数_init,_start程序调用hello.c中的main函数,libc.so是动态链接共享库,其中定义了hello.c中用到的printf、sleep、getchar、exit函数和_start中调用的__libc_csu_init,__libc_csu_fini,__libc_start_main。链接器将上述函数加入。

重定位:合并输入模块并为每个符号分配运行时地址。包括重定位节和符号定义和重定位节中的符号引用。其中在重定位节和符号中链接器将所有相同类型的节合并为同一类型的新的聚合节。连接器将运行时内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号因此全局变量都有唯一的运行时内存地址。而在重定位节中链接器依赖于hello.o中的重定位条目,修改代码节和数据节中对每个符号的引用,使得它们指向正确运行时的地址。

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

5.7 Hello的动态链接分析

编译器对于PIC使用了GOT来确定他的位置,每个GOT条目中生成了一个重定位记录,在加载时,动态链接器会重定位GOT中的每个条目,使得它包含目标正确的绝对地址,每个引用全局目标都有自己的GOT。在PIC函数调用时,编译器是无法预测这个函数运行时地址,因为定义它的共享模块在运行时可以被加载到任意位置,对此GUN编译器用延时绑定技术来解决这一问题,把函数地址的解析推迟到它实际被调用的地方。延迟绑定是通过两个数据结构之间的交互来实现,即通过GOT和PLT,如果一个目标模块调用定义在共享库中的任何函数,那么它就有自己的GOT和PLT。GOT是数据段的一部分,PLT是代码段的一部分。

相应位置信息:

通过ELF文件知道GOT表的存储位置:

在dl_init调用之前,对于每一条PIC函数调用,调用的目标地址都实际指向PLT中的代码逻辑,GOT存放的是PLT中函数调用指令的下一条指令地址。

dl_init函数执行后:

原先0x006008c0开始的global_offset表是全0的状态,在执行过_dl_init之后被赋上了相应的偏移量的值 。
5.8 本章小结
本章节主要介绍了链接的概念和作用,过查看hello的虚拟地址空间,并且对比hello.o和hello的反汇编代码,了解了链接以及是重定位的过程。
(第5章1分)

第6章 hello进程管理
6.1 进程的概念与作用
进程的概念:一个执行中的程序。系统中的每个程序都运行在某个进程的上下文中。上下文是由程序的正确运行所需的状态组成的。这个状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器
环境变量以及打开文件描述符的集合。

6.2 简述壳Shell-bash的作用与处理流程
(以下格式自行编排,编辑时删除)
Shell是一个交互型的应用级程序,它代表用户运行其他程序。Shell应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务。

处理流程:
1.读取输入的命令行。
2.解析引用并分割命令行为各个单词
3.检查命令行结构。
4.对第一个token进行别名扩展。
5.进行各种扩展。
6.引号去除。
7.搜索和执行命令。
8.返回退出状态码。
6.3 Hello的fork进程创建过程
在终端输入./hello使得hello执行,接着shell会执行fork函数。fork函数会创建与当前进程平行运行的子进程,系统将父进程的上下文包括代码,数据段,堆,共享库以及用户栈都创建一份副本,然后利用这个副本执行子进程,即当shell调用fork 时,hello可以读写shell中打开的任何文件。shell和hello进程之间最大的区别在于它们有不同的PID。fork在执行时被调用一次,但是却返回两次,一次是返回到父进程,一次是返回到新创建的子进程。

6.4 Hello的execve过程
execve在当前进程中载入并运行程序,execve 函数加载并运行可执行目标文件hello, 且带参数列表argv 和环境变量列表envp。只有当出现错误时,例如找不到filename, execve 才会返回到调用程序。所以,与fork 一次调用返回两次不同, execve 调用一次并从不返回。execve加载了hello后他调用启动代码,启动代码设置栈,启动程序运行初始化代码。系统会用execve构建的数据结构覆盖其上下文,替换成hello的上下文,然后将控制传递给新程序的主函数。execve只是简单的更换了自己所处进程的上下文,并没有改变进程的pid。

调用execve后新程序的栈结构:

6.5 Hello的进程执行
hello进程在内存中执行的过程中,当内核代表用户执行系统调用时,会发生上下文切换,比如说hello中的sleep语句执行时内核中的调度器就会执行上下文切换,将当前的上下文信息保存到内核中,恢复某个先前被抢占的进程的上下文,然后将控制传递给这个新恢复的进程。
6.6 hello的异常与信号处理
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。

异常种类:
1.中断(SIGSTP):挂起程序
2.终止(SIGINT):终止程序

正常运行:

在程序运行时按下ctrl+z:

键盘输入的ctrl+z给程序传入了一个SIGSTP信号,这个信号使程序暂时挂起。

此时可以输入ps命令查看进程:

在程序运行的时候键入ctrl+c,就会给进程发送一个终止信号:

hello-ld已经不在作业列表中:

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

按下Ctrl+Z后运行jobs命令。jobs命令列出 当前shell环境中已启动的任务状态:

kill发送信号给一个进程或多个进程。通过kill -9 10610杀死pid为10610的进程。

6.7本章小结
本章讲述了进程的概念和作用,简述壳Shell-bash的作用与处理流程,以及Hello的execve过程和hello的异常与信号处理。
(第6章1分)

第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:包含在机器语言中用来指定一个操作数或一条指令的地址。每一个逻辑地址都由一个段和偏移量组成,偏移量指明了从段开始的地方到实际地址之间的距离。在hello中hello.o里面的相对偏移地址。

线性地址:地址空间是一个非负整数地址的有序集合,如果地址空间中的整数是连续的,那么我们说它是一个线性地址空间。Linux中逻辑地址等于线性地址。因为Linux所有的段(用户代码段、用户数据段、内核代码段、内核数据段)的线性地址都是从 0x00000000 开始,长度4G,即线性地址=逻辑地址+0x00000000

虚拟地址:CPU通过生成一个虚拟地址来访问主存,这个虚拟地址在被送到内存之前先转换成适当的物理地址。

物理地址:计算机系统的主存被组织成一个由M个连续的字节大小的单元组成的数组。hello在运行时虚拟内存地址对应的物理地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
逻辑地址分成段标识符+段内偏移量,然后先判断TI字段,看看这个段描述符究竟是局部段描述符(LDT)还是全局段描述符(GDT),然后再将其组合成段描述符+地址偏移量的形式,这样就转换成线性地址了。
7.3 Hello的线性地址到物理地址的变换-页式管理
虚拟内存被组织为一个由存放在磁盘上的N个连续的字节大小的单元组成的数组。每字节都有一个唯一的虚拟地址作为到数组的索引。磁盘上数组的内容被缓存在主存中。和存储器层次结构中其他缓存一样,磁盘(较低层)上的数据被分割成块,这些块作为自盘和主存(较高层)之间的传输单元。VM系统通过将虚拟内存分割为成为虚拟页的大小固定的块来处理这个问题,对这些虚拟页的管理与调度就是页式管理。同任何缓存一样,虚拟内存系统必须有某种方法来判定一个虚拟页是否缓存在DRAM中的某个地方。如果是,系统还必须确定这个虚拟页存放在哪个物理页中。如果不命中,系统必须判断这个虚拟页存放在磁盘的那个位置,在物理内存中选择一个牺牲页,并将虚拟页从磁盘复制到DRAM中,替换这个牺牲页。

7.4 TLB与四级页表支持下的VA到PA的变换
64位计算机采用4级页表,36位VPN被封为4个9位的片,每个片被用作到一个页表的偏移量。CR3寄存器包含L1页表物理地址。VPN1提供到一个L1 PTE的偏移量,这个PTE包含L2页表的基地址。VPN2提供一个L2PTE的偏移量,以此类推。如下图所示

7.5 三级Cache支持下的物理内存访问
得到物理地址之后,先将物理地址拆分成CT(标记)+CI(索引)+CO(偏移量),然后在一级cache内部找,如果未能寻找到标记位为有效的字节(miss)的话就去二级和三级cache中寻找对应的字节,找到之后返回结果。
7.6 hello进程fork时的内存映射
当fork函数被当前进程调用时,内核为新进程创建各种数据,并分配给它一个唯一PID,为了给这个新进程创建虚拟内存,它创建了当前进程mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。
当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当着两个进程中的任一个后来进程写操作时,写时复制机制就会创建新页面,因此,也就为每个进程保持了私有地址空间的抽象概念。
7.7 hello进程execve时的内存映射

execve函数在当前进程中加载并运行新程序 a.out 的步骤

  1. 删除已存在的用户区域。删除当前进程虚拟地址的用户部分中的已存在的区域结构。
  2. 映射私有区域。为新程序的代码、数据、bss和栈区域创建新的区域结构。
  3. 映射共享区域。如果a.out程序与共享对象或目标链接,比如标准C库libc.so,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内。
  4. 设置程序计数器(PC)。execve做的最后一件事情就是设置当前进程上下文中的进程计数器,使之指向代码区域的入口点。
    下一次调度这个程序时,它将从这个入口点开始执行。Linux将根据需要换入代码和数据页面。
    7.8 缺页故障与缺页中断处理
    (以下格式自行编排,编辑时删除)
    DRAM缓存不命中称为缺页,如下图展示了在缺页之前页表的状态

页面不命中 导致缺页 缺页异常,如图对VP3 中的字的引用不命中,从而触发缺页 VM 缺页,缺页异常处理程序选择一个牺牲页 此例中就是 VP 4,导致缺页的指令重新启动 : 页面命中
7.9动态存储分配管理
在程序运行时程序员使用 动态内存分配器 比如( malloc )获得虚拟内存动态内存分配器维护着进程的一个虚拟内存区域,称为堆。对于每个进程,内核维护着一个变量brk,它指向堆的顶部。分配器将堆视为一组不同大小的块集合来维护

C标准库提供了一个称为malloc程序包的显式分配器。程序通过调用malloc函数来从堆中分配块。
void *malloc( size_t size)
成功:返回已分配块的指针,块大小至少 size 字节 对齐方式依赖编译模式:8 字节 (32 位模式) 16 (字节 64 位模式) 。 若 size == 0 , 返回 NULL ( 0 )
出错 : 返回 NULL 同时设置 errno

假设内存以字为单位,字是int类型

下图内存分配的列子:

7.10本章小结
本章介绍了hello的存储器地址空间、Intel逻辑地址到线性地址的变换-段式管理、Hello的线性地址到物理地址的变换-页式管理、TLB与四级页表支持下的VA到PA的变换、三级Cache支持下的物理内存访问、hello进程fork时的内存映射、hello进程execve时的内存映射、缺页故障与缺页中断处理以及动态存储分配管理。
(第7章 2分)

第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件
设备管理:unix io接口

一个 Linux 文件 就是一个 m 字节的序列:B 0 , B1 , … , Bk , … , Bm -1
Cool fact: 所有的 I/O 设备(网络、磁盘、终端)都被模型化为文件甚至内核也被映射为文件,允许Linux内核引出一个简单、低级的应用接口,成为Unix I/O。

File Types文件类型
 每个 Linux 文件都有一个类型( type )来表明它在系统中的角色:
普通文件 (Regular file): 包含任意数据
目录 (Directory): 包含一组链接的文件,每个链接都将一个文件名映射到一个文件
套接字 (Socket): 用来与另一个进程进行跨网络通信的文件
 其他文件类型
命名通道( Named pipes (FIFOs)
符号链接( Symbolic links)
字符和块设备( Character and block devices)
8.2 简述Unix IO接口及其函数
这种将设备优雅地映射为文件的方式,允许 Linux 内核引出一个简单、低级的应用接口,称为 Unix I/O:
1.打开和关闭文件:open() and close()
2. 读写文件:read() and write()
3. 改变当前的文件位置 (seek) 指示文件要读写位置的偏移量 lseek

 打开文件:
打开文件:通知内核,你准备好访问该文件

返回一个小的描述符数字——文件描述符。返回的描述符总是在进程中当前没有打开的最小描述符。fd == 1 说明发生错误

Linux 内核创建的每个进程都以与一个终端相关联的三个打开的文件开始:
0: 标准输入 (stdin)
1: 标准输出 (stdout)
2: 标准错误 (stderr)

 关闭文件:
通知内核不再访问文件
关闭文件时,内核的操作:
内核释放文件打开时创建的结构体
内核将描述符释放给可用描述符池,下次打开某个文件时,从该池中分配一个最小可用的描述符
进程终止时,内核操作

Reading Files读文件:
读文件从当前文件位置复制字节到内存位置,然后更新文件位置

返回值表示的是实际传送的字节数量
返回类型ssize_t是有符号整数
nbytes<0表明发生错误
同读文件一样,不足值是可能的,并非错误

Writing Files写文件
写文件从内存复制字节到当前文件位置,然后更新文件位置

返回值表示的是从内存向文件fd实际传送的字节数量
nbytes<0表明发生错误
同读文件一样,不足值是可能的,并非错误

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

其函数原型为:int printf(const char *format, …);
其函数返回值:打印出的字符格式
其调用格式为:printf("<格式化字符串>", <参量表>);

关于printf缓冲
在printf的实现中,在调用write之前先写入IO缓冲区,这是一个用户空间的缓冲。系统调用是软中断,频繁调用,需要频繁陷入内核态,这样的效率不是很高,而printf实际是向用户空间的IO缓冲写,在满足条件的情况下才会调用write系统调用,减少IO次数,提高效率。

printf在glibc中默认为行缓冲,遇到以下几种情况会刷新缓冲区,输出内容:
(1)缓冲区填满;
(2)写入的字符中有换行符\n或回车符\r;
(3)调用fflush手动刷新缓冲区;
(4)调用scanf要从输入缓冲区中读取数据时,也会将输出缓冲区内的数据刷新。

可使用setbuf(stdout,NULL)关闭行缓冲,或者setbuf(stdout,uBuff)设置新的缓冲区,uBuff为自己指定的缓冲区。也可以使用setvbuf(stdout,NULL,_IOFBF,0);来改变标准输出为全缓冲。全缓冲与行缓冲的区别在于遇到换行符不刷新缓冲区。

8.4 getchar的实现分析
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
首先,用getchar()函数进行字符的输入,并不是直接从键盘这个硬件中读取输入的字符,而是从“输入缓冲区”中得到的字符。
输入缓冲区是一个字符的队列,其中存储了所有你尚未读取的字符。
每次调用getchar函数,它就会从输入缓冲区中读出第一个字符,并把这个字符从输入缓冲区中清除。
然而,这个输入缓冲区的设计,是把所有从键盘上输入的东西都放进去的,包括你每次按的回车符‘\n’,
而getchar函数只读走了你在回车前输入的那个字符,而将回车符保留在了输入缓冲区中。
于是,第二次调用getchar时,函数就从输入缓冲区中读出了’\n’。
要解决这个问题,有两种可行的途径。
一是多加一个getchar(),过滤掉回车,但是这种方法有不足,就是如果你在调用第一个getchar时输入了多个字符,
那么,加入一个getchar并不能把所有未读取的字符过滤。如果你的本意是重新从“键盘”读取的话,最好是加一个fflush(stdin);清除输入缓冲区

8.5本章小结
本章节介绍了Linux的IO设备管理方法;简述Unix IO接口及其函数;printf的实现分析;getchar的实现分析。
(第8章1分)
结论
hello的一生可以分为以下几步:hello文件经过cpp的预处理生成hello.i,ccl的编译生成hello.s,更接近于机器的语言,as的汇编生成可重定位的目标执行文件hello.o、ld的链接最终成为可执行目标程序hello。在终端(shell)输入自己的学号、姓名和时间运行次程序。shell通过fork创建一个新的进程,然后在子进程里通过execve函数将hello程序加载到内存,最后当hello,shell会收回运行结束,shell将进程收回,hello将等待它的下一次被调用。。

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

附件
hello.c hello源代码
hello.i 预处理之后的文件
hello.s 汇编语言文件
hello.o 可重定位目标文件
hello 链接之后的可执行目标文件
hello1.elf hello.o的ELF格式
hello.txt hello.o反汇编
hello2.elf hello的ELF格式
hello1.txt hello反汇编

(附件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分)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值