HIT CSAPP 大作业

 

 

计算机系统

 

大作业

 

 

题        目     程序人生-Hello’s P2P   

专       业     自动化类/人工智能辅修  

学     号                     ***           

班     级                   1836104        

学       生                    ***      

指 导 教 师                   史先俊        

 

计算机科学与技术学院

2020年3月

 

学业不精,贻笑大方,见谅

 

摘  要

摘要:本文通过对helloworld程序从C语言代码到程序运行结束被清理的整个“生命周期”的分析,介绍程序从代码到可执行文件的预处理、编译、汇编、链接等过程和可执行文件从被加载运行到运行结束被清理的过程中的进程管理、存储管理和I/O等过程。

关键词:计算机系统;编译过程;可执行文件;进程管理;存储管理;系统I/O                           

 

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

 

目  录

 

第1章 概述

1.1 Hello简介

1.2 环境与工具

1.3 中间结果

1.4 本章小结

第2章 预处理

2.1 预处理的概念与作用

2.2在Ubuntu下预处理的命令

2.3 Hello的预处理结果解析

2.4 本章小结

第3章 编译

3.1 编译的概念与作用

3.2 在Ubuntu下编译的命令

3.3 Hello的编译结果解析

3.4 本章小结

第4章 汇编

4.1 汇编的概念与作用

4.2 在Ubuntu下汇编的命令

4.3 可重定位目标elf格式

4.4 Hello.o的结果解析

4.5 本章小结

第5章 链接

5.1 链接的概念与作用

5.2 在Ubuntu下链接的命令

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

5.4 hello的虚拟地址空间

5.5 链接的重定位过程分析

5.6 hello的执行流程

5.7 Hello的动态链接分析

5.8 本章小结

第6章 HELLO进程管理

6.1 进程的概念与作用

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

6.3 Hello的fork进程创建过程

6.4 Hello的execve过程

6.5 Hello的进程执行

6.6 hello的异常与信号处理

6.7本章小结

第7章 HELLO的存储管理

7.1 hello的存储器地址空间

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

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

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

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

7.6 hello进程fork时的内存映射

7.7 hello进程execve时的内存映射

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

7.9动态存储分配管理

7.10本章小结

第8章 HELLO的IO管理

8.1 Linux的IO设备管理方法

8.2 简述Unix IO接口及其函数

8.3 printf的实现分析

8.4 getchar的实现分析

8.5本章小结

结论

附件

参考文献

 

第1章 概述

1.1 Hello简介

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

P2P:From Program to Process ,即从源代码依次经过预处理、编译、汇编、链接最终成为可执行程序的过程。hello.c预处理得到hello.i,再经过编译得到hello.s,接着汇编成为二进制的可重定位目标程序hello.o,最后,hello.o与printf.o等一起链接生成可执行文件hello。

O2O:From Zero-0 to Zero-0,即可执行文件从被加载执行到执行完毕被回收的全过程。执行hello,操作系统shell将fork一个子进程,并在其中用execve函数加载hello,待hello执行完毕,子进程被shell回收。

1.2 环境与工具

硬件环境:

Intel® Core™ i7-7700HQ CPU @ 2.8GHz 2.8GHz

24.0GB RAM

512GB + 1TB Disk

 

软件环境:

Windows 10 专业版 64位

VMware Workstation Pro 15.5.1

Ubuntu19.10 64位

 

调试工具:

Visual Studio 2019 Enterprise 64位;  CodeBlocks 64位;  VSCode

gedit 、gcc、gdb、edb

objdump、readelf

 

 

 

 

1.3 中间结果

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

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

hello  用gcc -m64 -Og -no-pie -fno-PIC hello.c -o hello生成的可执行程序

hello.i  预处理结果

hello.s  编译结果

hello.o  汇编产生的可重定位目标文件

hello_o.elf  hello.o 的elf信息

hello_od.s  hello.o 的反汇编

a.out   hello.o链接库文件产生的可执行程序

hello.elf  a.out的elf信息

hello_d.s  a.out 的反汇编

 

 

 

1.4 本章小结

本章介绍了hello的P2P和O2O过程和实验的环境信息,列举的实验的中间文件

(第1章0.5分)

 

 

 

第2章 预处理

2.1 预处理的概念与作用

概念:预处理是指由预处理器对源代码进行一些文本性质的操作,C语言中,预处理#开头的命令对C语言的原始程序进行相应的修改。

作用:

预处理阶段主要的工作有宏替换(将宏名替换为定义的文本内容)、文件包含(把include的文件内容复制并插入到命令位置)和条件编译(满足一定条件才对代码进行编译)。同时,预处理也会删除掉注释,只留下对编译器有用的内容。

预处理的存在,即方便了程序员对程序的调试与修改,又方便了编译器对程序进行编译。

 

预处理过后生成.i文件,其本质仍然是C语言的源文件。

 

2.2在Ubuntu下预处理的命令

gcc -E hello.c -o hello.i

2.3 Hello的预处理结果解析

 

用vscode打开hello.i,可以发现,hello.i竟然有3000余行,而原始的hello.c不过24行。这是因为hello.c中的三个#include都被展开,其对应文件的内容被复制了过来。同时,也可以发现,存在于hello.c中的注释内容并不存在于hello.i中。

2.4 本章小结

      本章介绍了预处理的概念、作用和基本命令,并展示了hello.c的预处理结果,对其进行了分析

(第2章0.5分)

 

第3章 编译

3.1 编译的概念与作用

注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序

编译是编译器将高级语言代码翻译成为汇编语言代码的过程。

作用:将高级语言代码转换为汇编语言代码,以便进一步转换为机器语言二进制代码。

 

3.2 在Ubuntu下编译的命令

gcc -S hello.i -o hello.s

3.3 Hello的编译结果解析

 

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

 

3.3.1 文本内容解析

在hello.s中,可以看到,除了汇编代码指令外,还有一些其他的成分

1.指示 以点号开头的,用来指示对汇编器、链接器和调试器有用的一些结构性信息。指示本身并不是汇编指令。

2.标签 以冒号结尾,用来确定位置。通常,用点号开始的标签是由编译器生成的临时局部标签,其它标签是函数和全局变量名称。

3.汇编指令 实际的汇编代码,CPU进行对应操作的内容

 

在main函数中,还有许多以 .cfi开头的内容,这是CFI指令,全称是Call Frame Instrctions, 即调用框架指令。CFI提供的调用框架信息, 为实现堆栈回绕(stack unwiding)或异常处理(exception handling)提供了方便, 它在汇编指令中插入指令符(directive), 以生成DWARF可用的堆栈回绕信息。

3.3.2数据类型

hello.c中涉及到到的数据类型有字符(串)、整型、数组、指针等

字符串:

 

  
 

      代码中有两处字符串,都是printf的格式化参数,它们被存放在.rodata部分,编码为utf-8编码,因此一个汉字占用三个字节。

 

      此外,还有作为输入的字符串,首地址保存在argv数组中

 

整数:

int argc 记录传入参数个数,调用main函数时,在寄存器edi中

int i 局部变量,保存在 -4(%rbp)中。

立即数 直接在汇编代码中使用

 

数组/指针:

argv是一个二维指针,指向的是一个数组,该数组的元素也是指针,每个元素是一个字符串的首地址。

 

3.3.3操作

赋值:

赋值使用的指令主要是MOV,将立即数或某个地址的值复制到目的地址,但源地址和目的地址不能都是内存地址。lea是用来载入有效地址,将一个地址赋值到目的地址中。

全局变量若赋了初值,会直接在.data里面体现。hello.c中没有全局变量。

 

指令后缀:

指令后面相应的后缀 b、w、l、q表示操作数的长度

 

算术操作:

hello.c中的算术操作只有一出i++,但在汇编代码中可以看到并不止这一处。

  用减法实现的向栈中申请空间

  i++

  rax里面是基地址,通过加法实现地址的变化

 

关系操作:

关系操作通常是通过CMP和TEST实现的。CMP本质是做减法,但是只改变标志位,TEST本质是按位运算&,同样只改变符号位。

1.if(argc!=4)判断输入参数的个数是否正确

2.for(i=0;i<8;i++) 循环边界判断 

 

控制转移:

if  for循环 都涉及控制转移,是在关系操作后依据关系操作的结果进行转移。

输入参数个数为4,转移到.L2处

 

for循环类似,i赋值后转移到边界判断,再转移到循环体

 

 

3.3.4函数调用

函数调用通过call指令调用,ret指令返回。涉及参数传递,控制转移等

64位程序中,参数传递不超过六个时用寄存器完成,当参数超过六个时则需要压栈,通过stack frame访问。call指令将下一条指令的位置压栈作为返回地址,同时程序计数器跳转到函数代码地址处。ret则将保存的返回地址赋值给程序计数器。函数的返回值保存在累加器rax中

puts调用:

printf调用:三个参数分别在rdi,rsi,rdx寄存器中

 

atoi:返回值在eax中

 

sleep:

getchar:无参数调用

 

3.4 本章小结

本章介绍了从编译的过程,即从高级语言到汇编语言。同时,分析了hello.c编译产生的汇编代码hello.s,解析了其中涉及到的数据类型,各种操作以及函数调用过程,分析了其实现。

(第3章2分)

 

第4章 汇编

4.1 汇编的概念与作用

注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。

汇编是将汇编语言代码翻译成为机器语言代码即二进制文件的过程。

作用:将汇编代码转化为机器语言代码,生成可重定位目标文件

4.2 在Ubuntu下汇编的命令

as hello.s -o hello.o

4.3 可重定位目标elf格式

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

 

readelf命令:

 

ELFHeader:

ELF头中以魔码开始,包含了生成该文件的系统的信息和ELF头自身的信息,

 

 

 

 

 

 

 

 

 

节头表:包含了各个节的名称、位置、大小等基本信息

 

重定位节:包括.rela.text 和 .rela.eh_frame, 包含重定位时需要的信息

链接器将可重定位文件与其他可重定位文件链接合并时,才能够最终确定数据在虚拟内存中的位置,此时需要将可重定位文件中置空的数据修改为已确定下来的地址。重定位信息包括(1)offset:需要进行重定向的代码在.text或者.data段的偏移地址,8个字节构成(2)info:包括symbol和type两部分,其中symbol占四个字节代表重定位到的目标在.symtab中的偏移量,type占四个字节,代表重定位的类型。(3)type:重定位目标的类型 (4)name:重定位目标的名称            (5)addend:计算重定位位置的辅助信息,8个字节。

 

 

符号表:存放程序中的函数和全局变量的符号相关的信息

 

4.4 Hello.o的结果解析

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

说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。

 

机器语言是由01串构成,是二进制表示的。汇编指令实际上是机器语言的助记符。机器语言包括机器指令和指令的操作数。

hello.o反汇编后,和hello.s进行汇编代码部分比较,可以发现几处不同

数字表示都转化为了16进制表示

控制跳转的跳转位置不再由标签标识而改用函数符号+偏移量

全局数据的位置也不再用标签标识,而是用全0代替,并附上重定位信息

同样,函数调用在call指令函数位置中也由全0代替,并附上重定位信息

 

4.5 本章小结

本章介绍了汇编即从汇编代码到二进制机器代码的过程,同时介绍了可重定位文件中ELF的内容和作用,并通过反汇编,比较了反汇编的内容和hello.s的汇编代码的不同之处。

(第4章1分)

 

第5章 链接

5.1 链接的概念与作用

注意:这儿的链接是指从 hello.o 到hello生成过程。

链接是将多个可重定位文件合并成为可执行文件的过程。

链接提高了编程之中的模块化,各个模块可以分别编译,提高效率。也使得构建公共函数库成可能。

 

5.2 在Ubuntu下链接的命令

ld -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 /usr/lib/gcc/x86_64-linux-gnu/9/crtbegin.o hello.o -lc /usr/lib/gcc/x86_64-linux-gnu/9/crtend.o /usr/lib/x86_64-linux-gnu/crtn.o -z relro -o a.out

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

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

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

 

ELF头,和.o的大同小异。变化的一个是类型,从可重定位目标文件变为可执行文件。另一个是具体的数值的大小。

 

 

 

 

 

 

 

节头表/段头表

 

段头表的信息内容和节头表是一致的

比较前后的节头表,可发现节的数量增多了。同时,重定位中rela.text 和 rela.eh_frame 没有了,但是又出现了.rela.plt 和 rela.dyn, 这是因为采用了动态链接,一些函数需要在程序开始运行是才链接。如果是静态链接,则没有重定位信息了。

程序头表

在程序被执行的时候告诉链接器运行时需要加载的内容并提供动态链接信息。包括每个表项提供了各段在虚拟地址空间中的大小,位置,访问权限和对齐方式

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

5.4 hello的虚拟地址空间

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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

使用Edb打开hello程序,通过Data Dump窗口查看加载到虚拟地址中的hello程序。

data dump中,数据从401000起,到4012f5结束,size为0x2f5,与5.3中程序头表提供的信息相吻合。

 

5.5 链接的重定位过程分析

objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。

结合hello.o的重定位项目,分析hello中对其怎么重定位的。

 

hello中.text节的全局变量和函数调用都已经有了确定的虚拟地址,而不是用0代替,这是在链接的过程中将变量和函数的位置都确定下来后,根据重定位信息,将确定的地址填入了相应的位置。

hello中增加了许多被调用的函数。main函数的地址也不再从0开始

链接时,通过重定位条目,找到其需要修改的位置(段位置+偏移),将已经确定了的地址填入。

5.6 hello的执行流程

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

0x7efdb6dd1de0  0x7f4fbd5990b0

 

_start()

__libc_start_main()

__cxa_atexit_main()

__libc_csu_init()

init()

frame_dummy() & register_tm_clones ()

__setjmp() & _sigsetjmp

mian()

exit()

__call_tls_dtors()

__do_global_dtors_aux()

deregister_tm_clones()

_fini()

 

 

5.7 Hello的动态链接分析

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

通过段头表,查到.got的位置为0x403ff0

data dump查看,该位置上数据为0

运行程序,当调用0x7efdb6dd1de0完成后

可以看到0x403ff0之后的部分都发生了较大变化,几处为0的位置被填充上了相应的地址。这是程序开始运行,调用动态链接器的结果。

5.8 本章小结

本章介绍了hello.o链接生成hello(a.out)的过程。我们写的程序很少,只占最后可执行程序中很小一部分,许多是通过链接从库中合并来到。在执行main函数之前和main函数结束以后,有大量的工作需要完成。

 

(第5章1分)

 

 

第6章 hello进程管理

6.1 进程的概念与作用

概念:进程的经典定义就是一个执行中程序的实例。系统中的每个程序都运行在某个进程的上下文(context)中。上下文是由程序正确运行所需的状态组成的。这个状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。

作用:进程
提供给了应用程序两个关键抽象

一个独立的逻辑控制流,它提供一个假象,好像我们的程序独占地使用处理器。

一个私有的地址空间,它提供一个假象,好像我们的程序独占地使用内存系统。

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

shell是一个交互型的应用级程序,它代表用户运行其他程序。最早的shell是sh程序,后面出现了一些变种,比如csh、tcsh、ksh和bash。shell执行一系列的读/案值(read/evaluate)步骤,然后终止。读步骤读取来自用户的一个命令行.求值步骤解析命令行,并代表用户运行程序。

当用户键入命令,shell会进行命令解析,解析完成后会判断的一个命令参数是否为内置命令,是则释放这个命令,否则那么shell创建一个子进程,并在子进程中执行所清求的程序。如果用户请求在后台运行该程序,那么shell返回到循环的顶部,等待下一个命令行。否则,shell使用waitpid函数等待作业终止。当作业终止时,shell就开始下一轮迭代。

 

6.3 Hello的fork进程创建过程

当键入命令 ./hello srh 1180400510 2 后,shell进行命令行解析,并发现hello不是内置命令,于是shell通过fork()函数创建一个子进程。

新创建的子进程几乎但不完全与父进程相同。子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份副本,包括代码和数据段、堆、共享库以及用户栈。子进程还获得与父进程任何打开文件描述符相同的副本,这就意味着当父进程调用fork时,子进程可以读写父进程中打开的任何文件。父进程和新创建的子进程之间最大的区别在于它们有不同的PID

6.4 Hello的execve过程

为了运行hello,shell在子进程中使用execve()函数加载hello到内存中执行。在execve加载了hello之后,它调用启动代码设置栈(在上一章中已有讲述),并将控制传递给hello的主函数。execve函数是不返回的。

6.5 Hello的进程执行

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

上下丈切换:

操作系统内核使用一种称为上下丈切换〈context switch)的较高层形式的异常控制流来实现多任务。内核为每个进程维持一个上下文(context)上下文就是内核重新启动一个被抢占的进程所需的状态。它由一些对象的值组成,这些对象包括通用目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构,比如描述地址空间的页表、包含有关当前进程信息的进程表,以及包含进程已打开文件的信息的文件表。在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程。这种决策就叫做调度(scheduling),是由内核中称为调度器(scheduler)的代码处理的。当内核选择一个新的进程运行时,我们说内核调度了这个进程。在内核调度了一个新的进程运行后,它就抢占当前进程,并使用一种称为上下文切换的机制来将控制转移到新的进程,上下文切换1)保存当前进程的上下文,2)恢复某个先前被抢占的进程被保存的上下文,3)将控制传递给这个新恢复的进程。

当内核代表用户执行系统调用时,可能会发生上下文切换。如果系统调用因为等待某个事件发生而阻塞,那么内核可以让当前进程休眠,切换到另一个进程。比如,如果一个read系统调用需要访问磁盘,内核可以选择执行上下文切换,运行另外一个进程,而不是等待数据从磁盘到达。另一个示例是sleep系统调用,它显式地请求让调用进程休眠。一般而言,即使系统调用没有阻塞,内核也可以决定执行上下文切换,而不是将控制返回给调用进程。

中断也可能引发上下文切换。比如,所有的系统都有某种产生周期性定时器中断的机制,通常为每1毫秒或每10毫秒。每次发生定时器中断时,内核就能判定当前进程已经运行了足够长的时间,并切换到一个新的进程。每两次定时中断就是一个时间片。

用户态与核心态:

为了使操作系统内核提供一个无懈可击的进程抽象,处理器必须提供一种机制,限制一个应用可以执行的指令以及它可以访问的地址空间范圈。

处理器通常是用某个控制寄存器中的一个模式位(mode bit)来提供这种功能的,该寄存器描述了进程当前享有的特权。当设置了模式位时,进程就运行在内核模式中(有时叫做超级用户模式)。一个运行在内核模式的进程可以执行指令集中的任何指令,并且可以访问系统中的任何内存位置。

运行应用程序代码的进程初始时是在用户模式中的。进程从用户模式变为内核模式的唯一方法是通过诸如中断、故障或者陷入系统调用这样的异當。当异常发生时,控制传递到异常处理程序,处理器将模式从用户模式变为内核模式。处理程序运行在内核模式中,当它返回到应用程序代码时,处理器就把模式从内核模式改回到用户模式。

 

6.6 hello的异常与信号处理

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

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

 

正常运行,连续输出8次 hello srh 1180400510

 

Ctrl-Z  ps 可以看到hello进程在后台挂起

jobs命令,显示出了挂起了的进程

fg命令使进程恢复运行

 

pstree

 

使用kill命令将进程杀死

Ctrl + C终止程序

 

ctrl+c 终止程序,相应的进程也被杀死

 

滚键盘

 

6.7本章小结

本章介绍了hello程序运行过程中shell或者说操作系统的进程管理。hello是以进程的形式在计算机里运行的,计算机为其fork进程,execve加载到内存中,分配时间片,切换上下文移交控制权。又有各种信号指导hello程序作出相关反应。

(第6章1分)

 

第7章 hello的存储管理

7.1 hello的存储器地址空间

结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。

逻辑地址:程序代码的偏移地址,是经过编译后出现在汇编程序中地址。

线性地址:逻辑地址经过段机制转化后为线性地址,用于描述程序分页信息的地址。以hello为例,线性地址就是hello应该在内存的哪些块上运行。

虚拟地址:同线性地址,因为地址空间连续而被称为线性地址。

物理地址:处理器通过地址总线的寻址,找到真实的物理内存对应地址。是内存单元的真实地址。以hello为例,物理地址就是hello真正在内存上存在的地方。

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

地址变换:保护模式下,以段描述符作为下标,到GDT/LDT表查表获得段地址,段地址+偏移地址=线性地址。

段式管理:将一个程序分为若干段进行存储。每个段都是一个逻辑实体,每个段可以被存储在不同位置,程序会在段寄存器中记录各段地址,并通过偏移量找到位置。段式管理是通过段表进行的,它包括段号或段名、段起点、装入位、段的长度等。此外 还需要主存占用区域表、主存可用区域表。

 

 

 

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

先将线性地址分为 VPN(虚拟页号)+VPO(虚拟页偏移)的形式, 再将 VPN 拆分成 TLBT(TLB标记)+TLBI(TLB索引),然后去TLB 缓存里找对应的 PPN(物理页号),如果发生缺页情况则直接查找对应的 PPN,找到PPN之后,将其与VPO组合变为PPN+VPO就是生成的物理地址了。

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

 

TLB是一个小的、虚拟寻址的缓存,其中每一行都保存着一个由单个PTE组成的块,TLB通常有高度的相联度。

对于一个虚拟地址,若TLB命中,MMU从TLB中取出PTE,否则到L1缓存中寻找PTE。

 

使用多级页表,只有一级页表需要常驻内存中,其他页表可以根据需要创建、调入或调出。虚拟地址被分割为多个VPN和一个VPO,四级页表中,需要访问四个页表取得PPN再和PPO连接获得物理地址。

 

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

得到了物理地址,只需要在 cache 寻找即可。将物理地址分为 CT(标记)+CI(索引)+CO(偏移量),然后在一级 cache 内部找,不命中再去二级 cache 里找,再不命中到三级cache中找,还不命中就去存储器找。找到之后则加入 cache,并返回结果。受益与局部性,通常命中的概率比较大,平均读取时间较快。

 

 

 

 

 

 

 

7.6 hello进程fork时的内存映射

Linux通过将一个虚拟内存区域与一个磁盘上的对象(object)关联起来,以初始化这个虚拟内存区域的内容,这个过程称为内存映射(memory mapping)。虚拟内存区域可以映射到两种类型的对象中的一种。

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

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

 

7.7 hello进程execve时的内存映射

execve函数在当前进程中加载并运行包含在可执行目标文件hello中的程序,用hello程序有效地替代了当前程序。execve会:

删除已存在的用户区域。

映射私有区域。为新程序的各种东西创建区域,都是私有的、写时复制的。为代码段和数据段分配虚拟页,并标记为无效(即未被缓存)。每个页面被初次引用时,虚拟内存系统才会按照需要自动地调入数据页。

映射共享区域。若 a.out 与共享对象链接,那么这些对象都是动态连接到这个

程序的,然后再映射到用户虚拟地址空间中的共享区域内。

设置程序计数器。使之指向代码区域的入口点。

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

假设MMU在试图翻译某个虚拟地址A时,触发了一个缺页.这个异常导致控制转移到内核的缺页处理程序,处理程序随后就执行下面的步骤:

1)确认虚拟地址A是否合法,即确认A是否在某个区域结构定义的区域内。如果这个指令是不合法的,那么缺页处理程序就触发一个段错误,从而终止这个进程。

2)确认内存访冋是否合法,即确认进程是否有读、写或者执行这个区域内页面的权限。若访问不合法,则触发保护异常,终止程序。

3)执行完以上两项,则确认了操作合法,将把对应页面加载并更新页表,具体 如下:选择一个牺牲页面,如果这个牺牲页面被修改过,那么就将它交换 出去,换入新的页面并更新页表。当缺页处理程序返回时,CPU 重新启动 引起缺页的指令,这次 MMU 就能正常翻译 A,而不会产生缺页中断。

7.9动态存储分配管理

请简述动态内存管理的基本方法与策略。

动态内存分配器维护着一个进程的虚拟内存区域,称为堆(heap)。系统之间细节不同,但是不失通用性,假设堆是一个请求二进制零的区域,它紧接在未初始化的数据区域后开始,并向上生长(向更高的地址).对于每个进程,内核维护着一个变量brk(读做“break”),它指向堆的顶部。

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

显式分配器 (explicit allocator),要求应用显式地释放任何已分配的块。例如,C标准库提供一种叫做inalloc程序包的显式分配器。C程序通过调用malloc函数来分配一个块,并通过调用free函数来释放一个块.C++中的new和delete操作符与C中的malloc和free相当.

隐式分配器(implicit allocator),另一方面,要求分配器检测一个已分配块何时不再被程序所使用,那么就释放这个块。隐式分配器也叫做垃圾收集S(garbage collec-tor), 而自动释放未使用的已分配的块的过程叫做垃圾收集(garbage collection)

 

7.10本章小结

本章节从四种地址开始,介绍了段式管理和页式管理,和地址转换,TLB和多级页表以及三级cache支持下访问储存器内容。分析了程序运行时fork和execve的内存映射工作,缺页故障发生时缺页中断处理程序的工作内容。最后介绍了动态存储分配管理的两种分配器。

(第7章 2分)

 

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

设备的模型化:文件

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

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

 

设备管理:unix io接口

打开和关闭文件 open close

读取和写入文件 read write

改变当前文件的位置

读取文件信息

8.2 简述Unix IO接口及其函数

1.open函数

进程是通过调用open函数来打开一个已存在的文件或者创建一个新文件

open函数将filename转换为一个文件描述符,并且返回描述符数字.返回的描述符总是在进程中当前没有打开的最小描述符。flags指明了进程打算如何访问这个文件:只读、只写或可读可写

 

2.close函数

关闭描述符为fd的文件

 

 

 

 

 

3.read和write

read函数从描述符为fd的当前文件位置复制最多n个字节到内存位置buf,返回值-1则表示一个错误,而返回值0表示EOF。否则,返回值表示的是实际传送的字节数量。

write函数从内存位置buf复制至多n个字节到描述符fd的当前文件位置。

 

4 stat和fstat

应用程序能够通过调用stat和fstat函数,检索到关于文件的信息(有时也称为文
件的无數据(metadata))。

 

8.3 printf的实现分析

https://www.cnblogs.com/pianist/p/3315801.html

printf的函数体

…是token,可变形参的一种写法,当传递参数的个数不确定时,就可以用这种方式来表示。

va_list 是字符指针类型,即arg是一个char*类型变量,指向…中第一个参数

 

 

 

i = vsprintf(buf, fmt, arg);

write(buf, i);

i是write的字节数量,那么vsprintf返回的是输出的字节长度

看vsprintf的代码

该函数传入格式字符串和参数列表。对于每一个以%开头的格式符,与参数列 表中的参数相匹配,将答案加入buf中,最后返回答案的长度。

最后调用write函数写入文件。

write:

     mov eax, _NR_write

     mov ebx, [esp + 4]

     mov ecx, [esp + 8]

     int INT_VECTOR_SYS_CALL

write将参数放入寄存器,调用syscall

syscall它将结果字符串中的字节从寄存器中通过总线复制到显卡的显存中,将字节翻译并找到字符的点阵信息储存到vram中,显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。

 

8.4 getchar的实现分析

异步异常-键盘中断的处理:当按下键盘时会产生一个中断请求,然后运行键

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

getchar调用read系统函数,read将整个缓存区读到buf里面,取出第一个字符作为getchar的结果。

8.5本章小结

本章介绍了Linux对设备的管理方法和文件模型,介绍了unix IO的接口及相应函数,并分析了printf和getchar函数的实现。

(第8章1分)

 

结论

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

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

 

hello 程序很简单,但从源代码到可执行文件在,再丛被加载执行到运行结束被清理的这个过程却是非常丰富多彩的。这个过程涉及到整个计算机系统软件硬件的协同处理,各种机制的相互配合。

总结一下,hello 的一生经历了:

1. hello 的源代码保存为 hello.c。

2. 预处理:将 hello.c 经过预处理器解析以#开头的预处理命令修改文本,得到 hello.i

3. 编译:将hello.i通过编译器翻译成汇编程序 hello.s。

4. 汇编:将汇编程 hello.s通过汇编器翻译成二进制可重定位目标文件hello.o

5. 链接:将 hello.o 与各种库链接称为可执行文件hello

6. 从shell输入命令,运行hello

7. shell通过fork和execve 创建子进程并加载hello。加载中为hello分配虚拟地址空间和页表。进入程序入口初始化程序,转入main函数

8.系统在hello的时间片里执行hello的指令

9. hello访问内存中的数据。hello有自己的虚拟地址空间,当有访存操作时,MMU 把虚拟地址翻译成物理地址,通过三级 cache 访问内存。

10. 程序运行时可能会有许多异常,比如 ctrl+z/c,hello 中的 sleep 函数,会触发上下文切换,hello 会在用户态和内核态切换。

11. printf、getchar 函数会调用系统I/O从缓冲区读取字符进行处理。

12. 进程结束,shell回收子进程,hello进程消失。

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

 

附件

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

hello  用gcc -m64 -Og -no-pie -fno-PIC hello.c -o hello生成的可执行程序

hello.i  预处理结果

hello.s  编译结果

hello.o  汇编产生的可重定位目标文件

hello_o.elf  hello.o 的elf信息

hello_od.s  hello.o 的反汇编

a.out   hello.o链接库文件产生的可执行程序

hello.elf  a.out的elf信息

hello_d.s  a.out 的反汇编

 

 

 

 

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

 

参考文献

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

[1]  C语言的预处理详解 https://blog.csdn.net/czc1997/article/details/81079498

[2]  X86汇编调用框架浅析与CFI简介 https://blog.csdn.net/permike/article/

details/41550991

[3]  printf 函数实现的深入剖析 https://www.cnblogs.com/pianist/p/3315801.html

[4]  .深入了解计算机系统(中文第三版)

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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值