哈工大2022计算机系统大作业

摘  要

该大作业通过对于hello world 程序的“一生”的研究与探讨,将本学期的计算机系统学习内容进行了回顾与反思,对学习内容有了更深的理解,对于本课程的重要性有了更加深刻的认识.

关键词:计算机系统;hello world;……;                           

(摘要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简介

        通过gcc编译器对.c文件执行编译命令。

Hello的P2P过程:对hello.c的文本文件预处理生成hello.i,再由编译生成hello.s,最后通过汇编生成hello.o,最后生成可执行文件Hello。

Hello的O2O过程:Hello执行后,shell调用程序execve,通过一次进行对虚拟内存的映射和对物理内存的载入,进入主函数的执行代码,最后将结果打印于显示器上,结束进程,释放空间。

1.2 环境与工具

硬件:CPU Intel CORE i7

软件环境:Windows11 x86,Ubuntu 20.04

开发工具:Visual Studio ;vscode;codeblocks;gdb;Objdump

1.3 中间结果

hello.i :hello.c的预处理文本

hello.s :hello.i的汇编文本

hello.o :hello.s的可重定位的二进制文件

hello :hello.o的可执行文件

s.txt :hello使用objdump处理后的反汇编文本

1.4 本章小结

简述了P2P,O2O,列举了实验的环境和工具以及中间文件。

(第1章0.5分)

第2章 预处理

2.1 预处理的概念与作用

预处理的概念:预处理器(cpp)根据以字符#开头的命令,修改原始的C程序。比如 hello.c 第 1 行的 #include <stdio.h> 命令告诉预处理器读取系统头文件 stdio.h 的内容,并把它直接插入程序文本中。

预处理的作用:预处理能方便编译器的编译工作,实际上预处理从系统的头文件包中将头文件的源码插入到目标文件中生成.i文件,在编译代码第一时间将标识符替换好,成为中间码后再进行正式的编译工作

2.2在Ubuntu下预处理的命令

gcc hello.c -o hello.i -E

应截图,展示预处理过程!

2.3 Hello的预处理结果解析

文件相较原来的.c增加了很多内容,行数达到了3061行。

这是对原先.c文件中的宏的展开,头文件内容被写入该文件。

在末尾是原代码

2.4 本章小结

对预处理的概念于过程进行了理解与分析,展示了. c文件的预处理操作流程,与.i文件的结构与内容。

(第2章0.5分)

第3章 编译

3.1 编译的概念与作用

编译的概念:将高级语言转换为汇编语言,这是把高级语言转化为机器二进制代码的必经之路。

编译的作用:将高级语言翻译为更接近机器语言的汇编语言,编译使得二进制文件的生成更快。

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

3.2 在Ubuntu下编译的命令

gcc -m64 -no-pie -fno-PIC -S -o hello.s hello.i

将hello.i编译为hello.s

应截图,展示编译过程!

3.3 Hello的编译结果解析

生成hello.s文件

3.3.1文件结构分析

.file             源文件

                          .text             代码段

                          .section.rodata  存放只读变量

                          .align            对齐方式

                         

3.3.2数据

1、整数变量与整数常量与字符串常量

    1int argc

   argcmain函数的参数之一,64位编译下,由寄存器传入,进而保存在堆栈中。

         

    2int i

i是局部变量,编译器将其存放在寄存器或者栈空间中。i作为函数内部的局部变量,不占用文件实际节的空间,只存放于栈中.对i的操作是对寄存器或者栈的操作。

如图

    3if(argc!=4)           for(i=0;i<8;i++)

这两句中的4、0、8均为整数常量,以立即数形式体现如图

4、字符串

如图字符串存放于.text数据段,\XXX是utf-8,三位为汉字。

2、数组

程序中涉及的数组为char*argv[]。

这里表明了将8字节的char*型argv[]首地址存放于rsi中,

这里表明访问数组内容时是通过寻址达成的。

3.3.3赋值操作与算数操作

在开头,未赋初值地定义了

:int i;

我们知道:

1)有初始化的全局变量,该全局变量存放在data段

2)没有初始值的全局变量和初值为0的放在BSS段。

故而我们知道i存放在BSS段,在编译后的文件不占空间。

之后的赋值操作和算数操作主要是这里

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

movl   $0, -4(%rbp)对于赋值为0

addl   $1, -4(%rbp)对于i++操作。

这里列举mov操作的指令后缀意义

  1. movb #完成1个字节的复制
  2. movw #完成2个字节的复制
  3. movl #完成4个字节的复制
  4. movq #完成8个字节的复制

同时在汇编中还有一些算数操作例如:

subq   $32, %rsp  栈指针减少开辟空间

addq   $24, %rax 使用add修改地址偏移量

以下列举常用的一些算数操作指令

3.3.4类型转换

.c源文件中的类型转换为:

sleep(atoi(argv[3]))

查看atoi函数:

int atoi(const char *__nptr)

Convert a string to an integer.

可见程序通过调用了atoi将字符串类型转换为int类型,

3.3.5关系操作与控制转移

判断参数是否为4,汇编代码为

Cmpl用来判断参数argc是否等于4,je用于判断cmpl产生的条件码,若两个操作数的值不相等则跳过“本该”执行的语句,

跳转到L2下的语句段,对应的代码:

还有一处是

这里判断i与8的大小关系对应的汇编为

可以看到设立变为了与7比较,jle用于判断cmpl产生的条件码,若后一个操作数的值小于等于前一个,则跳转到.L4——重新执行循环。

3.3.6数组/指针/结构操作

实际上在数组部分已经讲述代码数组操作的大概,这里再详细讲一下访存数组的过程

这里

movq   -32(%rbp), %rax表明将数组首地址放入%raxaddq    $16, %rax实际上是将M[argv+16]argv[2]的地址放到%rdx,同理addq    $8, %raxargv[1]的地址。

3.3.7函数操作

X86-64中,过程调用传递参数规则:第一个到第六个参数存储以下六个寄存器: %rdi、%rsi、%rdx、%rcx、%r8、%r9这六个寄存器中,剩下的参数保存在栈当中。


main函数:函数有两个参数,int argc,char *argv[],第一个参数是一个int型整数,第二个参数是一个指针,即值是一个地址,它们分别存储在寄存器%edi和%rsi中,在函数中,将参数放入栈中保存。

Main函数的调用比较复杂:main函数由系统调用,首先在运行时通过动态链接,调用libc库里的函数__libc_start_main,然后这个函数会初始化程序,执行__init,注册退出处理程序,再调用main函数。

Main函数在程序返回时设置%eax为0并且返回,对应return 0 。

printf函数

1、第一次在文中出现的地方是传入了字符串参数首地址,汇编代码如下:

实际对应的是

调用是在if判断下。

2、第二次出现是

传入了格式字符串的地址、 argv[1]、argc[2]的地址。

这是调用printf对应汇编,调用地方是在循环中。

Exit函数:传入参数1,

调用是在if段落下,调用它的汇编是

Atoi函数:传入参数argv[3]

调用是在Sleep函数中:汇编代码如下

当它返回时:它会返回一个整数即将argv[3]转化为相应整数,存储于%rax中

Sleep函数:传入参数atoi(argv[3])

在for循环中调用;

汇编为

Getchar函数

在mian中调用

汇编为

3.4 本章小结

本章介绍了编译的概念与作用,编译是将文本文件翻译成汇编语言程序,为后续将其转化为二进制机器码做准备的过程。并且以hello.i到hello.s为例,分析了编译器是如何处理C语言的各个数据类型以及各类操作,包括字符串等各类数据,赋值操作,类型转换,算术操作,逻辑/位操作,关系操作,数组/指针/结构操作,控制转移,函数操作的内容。

(第32分)

第4章 汇编

4.1 汇编的概念与作用

汇编的概念:编译完成生成hello.s文件后,驱动程序运行汇编器as,将hello.s翻译成一个可重定位目标文件hello.o,这个过程就是汇编。

汇编的作用:汇编器将.s 汇编程序翻译成机器语言指令,把这些指令打包成可重定位目标程序的格式,并将结果保存在.o目标文件中。

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

4.2 在Ubuntu下汇编的命令

命令:gcc -m64 -no-pie -fno-PIC -c hello.s -o hello.o

应截图,展示汇编过程!

4.3 可重定位目标elf格式

输入readelf -a hello.o > hello.elf,获得hello.o文件的elf格式

4.3.1ELF头

包含了系统信息,编码方式,ELF头大小,节的大小和数量等等一系列信息。

4.3.2

描述了.o文件中出现的各个节的类型、位置、所占空间大小等信息。

4.3.3重定位节

1、.rela.text

    

.rela.text节包含如下信息:

1)偏移量:代表需要进行重定向的代码在.text或.data节中的偏移位置,8个字节。

2)信息:包括symbol和type两部分,其中symbol占前半部分,type占后半部分,symbol代表重定位到的目标在.symtab中的偏移量,type代表重定位的类型。

3)类型:重定位到的目标类型

4)加数:计算重定位位置的辅助信息

一个.text 节中位置的列表,包含.text 节中需要进行重定位的信息,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。

     这里,是对第一个.rodata段内容即.L0的内容,puts函数,exit函数,第二个.rodata段内容即.L1内容,printf函数,atoi函数,sleep函数,getchar函数进行重定位声明。

2、rela.eh_frame节

.eh_frame节的重定位信息。

4.3.4 符号表

.symtab存放在程序中定义和引用的函数和全局变量的信息。

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

4.4 Hello.o的结果解析

objdump -d -r hello.o > see_hello.txt

See_hello.txt中结果如图。

  1. 在数的表示上,hello.s中的操作数表现为十进制,而hello.o反汇编代码中的操作数为十六进制。
  2. 分支转移:在hello.s中,跳转指令的目标地址直接记为段名称,如.L2,.L3等。而在反汇编得到的hello.asm中,跳转的目标为具体的地址,在机器代码中体现为目标指令地址与当前指令下一条指令的地址之差。目前留下了重定位条目,跳转地址为零。它们将在链接之后被填写正确的位置。
  3.   函数调用:hello.s直接call函数名称,而反汇编代码中call的是目标的虚拟地址。与上一条的情况类似,只有在链接之后才能确定运行执行的地址,目前目的地址是全0,并留下了重定位条目。

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

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

4.5 本章小结

本章介绍了汇编的概念与作用,在Ubuntu下通过实际操作将hello.s文件转化为hello.o可重定位目标文件。我们研究了可重定位目标文件elf格式,接触了了readelf命令、elf头、节头部表、重定位节、符号表。我们对比hello.s和hello.o,了解了汇编语言与机器语言的异同之处。

(第41分)

第5章 链接

5.1 链接的概念与作用

链接的概念:链接是指通过链接器(Linker),将程序编码与数据块收集并整理成为一个单一文件,生成完全链接的可执行的目标文件的过程

链接的作用:链接令分离编译成为可能,方便了程序的修改和编译:无需重新编译整个工程,而是仅编译修改的文件。同时,链接还有利于构建共享库。源程序节省空间而未编入的常用函数文件(如printf.o)进行合并,生成可以正常工作的可执行文件。

注意:这儿的链接是指从 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

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

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

指令objdump -d -r hello > see1_hello.txt将反汇编的代码写入txt

命令:readelf -a hello > hello1.elf

  1. ELF 头

ELF头标记了这是一个可执行文件,并且给定了入口点地址。可以看出hello1.elf与hello.elf包含内容基本相同(如Magic,类别等)。类型变化为exec,程序头大小和节头数量增加。

2.节头

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

3.程序头

程序头表,给定了各个部分的具体信息,包括虚拟地址,物理地址。

  

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

5.4 hello的虚拟地址空间

打开edb,通过 data dump 查看加载到虚拟地址的程序代码。

program headers告诉链接器运行时加载的内容并提供动态链接需要的信息。

(由于我平常使用的WSL中的edb版本比较低,所以这里使用老师虚拟机中的edb,非edb其他部分还是由wsl完出(因为我的虚拟机非常地卡))。

程序包含PHDR,INTERP,LOAD,DYNAMIC,NOTE,GNU_STACK,GNU_RELRO几个部分,其中PHDR 保存程序头表。INTERP 指定在程序已经从可执行文件映射到内存之后,必须调用的解释器。LOAD 表示一个需要从二进制文件映射到虚拟地址空间的段。其中保存了常量数据、程序的目标代码等。DYNAMIC 保存了由动态链接器使用的信息。NOTE 保存辅助信息。GNU_STACK:权限标志,用于标志栈是否是可执行。GNU_RELRO:指定在重定位结束之后哪些内存区域是需要设置只读。

在 图上的data_dump中能找到和elf文件中对应的

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

5.5 链接的重定位过程分析

我们观察hello.o的反汇编代码。可以观察到,有许多地方并没有填入正确的地址,正等待进行链接。其中R_X86_64_32表示的类型是32位直接寻址,相应地方会填入.rodata的真实地址。而R_X86_64_PLT32表示puts函数和exit函数需要通过共享库进行动态链接。

而在hello反汇编中

重定位地址已经填入了正确的信息。

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

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

5.6 hello的执行流程

0x7f36eb7c4df0                ld-2.31.so!_dl_start

0x73f36eb7df6f0               ld-2.31.so!_dl_init

0x4010f0                 hello!_start

0x7d4a2410fc0            libc-2.31.so!__libc_start_main

0x7fd4a2433e10           libc-2.31.so!__cxa_atexit

0x7fd4a2433bb0           ld-2.31.so!_dl_init

0x4011c0                 hello!_libc_csu_init

0x4010000                hello!__init

0x7fd4a242fcb0           libc-2.31.so!_setjmp

0x7f45e45aebe0            libc-2.31.so!_sigsetjmp

0x401125                hello!main

0x401090                hello!.plt+0x70

0x401040                hello!printf@plt

0x401070                hello!exit@plt

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

5.7 Hello的动态链接分析

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

链接前

链接后

plt初始存的是一批代码,它们跳转到got所指示的位置,然后调用链接器。初始时got里面存的都是plt的第二条指令,随后链接器修改got,下一次再调用plt时,指向的就是正确的内存地址。plt就能跳转到正确的区域。

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

5.8 本章小结

本章简述了链接的概念与作用,分析了我们经过链接生成的hello 文件的结构以及与之前经过链接的hello.o文件的异同,我们分析了hello文件的运行流程,使用edb探索了动态链接的过程。

(第51分)

第6章 hello进程管理

6.1 进程的概念与作用

进程的概念:进程是一个正在运行的程序的实例,系统中的每一个程序都运行在某个进程的上下文中。

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

  1. 一个独立的逻辑控制流,提供一个假象,好像程序独占地使用处理器
  2. 一个私有地址空间,提供一个假象,好像程序独占地使用内存系统

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

Shell是用户级的应用程序,代表用户控制操作系统中的任务。处理流程如下:

1) 在shell命令行中输入命令:$./hello

2) shell命令行解释器构造argv和envp;

3) 调用fork()函数创建子进程,其地址空间与shell父进程完全相同,包括只读代码段、读写数据段、堆及用户栈等

4) 调用execve()函数在当前进程(新创建的子进程)的上下文中加载并运行hello程序。将hello中的.text节、.data节、.bss节等内容加载到当前进程的虚拟地址空间

5) 调用hello程序的main()函数,hello程序开始在一个进程的上下文中运行。

6.3 Hello的fork进程创建过程

打开Shell,输入命令./hello 120L020405 dry 3,带参数执行生成的可执行文件。

运行结果如上。

fork进程的创建过程如下:首先,带参执行当前目录下的可执行文件hello,父进程会通过fork函数创建一个新的运行的子进程hello。子进程获取了与父进程的上下文,包括栈、通用寄存器、程序计数器,环境变量和打开的文件相同的一份副本。子进程与父进程的最大区别是有着跟父进程不一样的PID,子进程可以读取父进程打开的任何文件。当子进程运行结束时,父进程如果仍然存在,则执行对子进程的回收,否则就由init进程回收子进程。

6.4 Hello的execve过程

Execve的参数包括需要执行的程序(通常是argv[0])、参数argv、环境变量envp。 1) 删除已存在的用户区域(自父进程独立)。

2)为新程序建立新的区域结构,这些区域结构是私有的,写时才复制的。虚拟地址空间的代码和数据区域被映射为hello文件的.txt和.data区,bss区域是请求二进制零的,映射匿名文件,其大小包含在hello文件中。栈和堆区域也是请求二进制零的,初始长度为零。

3) 如果hello与共享对象链接,比如hello程序与标准C库libc.so链接,这些对象都是动态链接到hello的,然后再用户虚拟地址空间中的共享区域内。

4) 设置PC计数器,这是exceve做的最后一件事,设置当前进程的上下文中的程序计数器,使之指向代码区域的入口点。

5) execve在调用成功的情况下不会返回,只有当出现错误时,例如找不到需要执行的程序时,execve才会返回到调用程序。

6.5 Hello的进程执行

系统中每个程序都运行在某个进程的上下文中。上下文是程序正确运行所需要的状态,由内核进行维持。

一个运行多个进程的系统,进程逻辑流的执行可能是交错的。每个进程执行它的流的一部分,然后被抢占,然后轮到其他进程。一个逻辑流在时间上与另一个重叠,成为并发流。一个进程执行它的控制流的一部分时间叫做时间片。

控制寄存器利用模式位描述了当前进程享有的特权:当设置了模式位时,进程运行在内核模式中,可以执行任何命令,访问任何内存;当没有设置模式位时,进程为用户模式,不允许执行特权指令,不允许直接引用内核区的代码、数据。

在进程执行时,内核可以抢占当前进程,并重新开始一个先前被抢占了的进程。这种决策称为调度。当进程调度一个新的进程运行后,会使用上下文切换来将控制转移到新的进程。上下文切换会:1.保存当前进程的上下文。2.恢复某个先前被抢占进程的被保存的上下文。3.将控制传递给新进程。系统调用、中断可能引起上下文切换。

这是老师ppt上的。

而在hello中,在调用进程发送sleep之前,hello在当前的用户内核模式下运行,在内核中进程再次调用当前的sleep之后进程转入用户内核等待休眠模式,内核中所有正在处理等待休眠请求的应用程序主动请求释放当前正在发送处理sleep休眠请求的进程,自动将当前调用hello的进程加入正在执行等待的队列,移除或退出正在内核中执行的进程等待队列。

设置定时器,休眠的时间为自己设置的时间,当计时器时间到,发送一个中断信号。内核收到中断信号进行中断处理,hello被重新加入运行队列,等待执行,这时候hello就可以运行在自己的逻辑控制流里面了。

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

6.6 hello的异常与信号处理

这是hello程序正常运行状态。

一般而言,异常分为四类:中断、陷阱、故障、终止

异常的处理一般需要硬件和软件的结合。

常见的信号有如下五个

当按下Ctrl-C时

结果如上,ctrl+c会导致内核发送SIGINT信号到前台进程组的每个进程,默认情况是终止前台作业,可以用ps查看前台进程组,能看到没有hello进程。

按下ctrl+z

进程挂起,hello进程没有回收,而是运行在后台,使用ps命令可以看到。调用fg 1将其调到前台,首先打印命令行命令,然后把剩余info输出。

如果乱按,字符应该会被存入缓冲区,实际不影响hello运行

Kill指令和jobs指令如图。

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

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

6.7本章小结

本章简述了进程、shell的概念与作用,分析了hello程序使用fork创建子进程的过程以及使用execve加载并运行用户程序的过程,运用上下文切换、用户模式、内核模式、内核调度等知识,分析了hello进程的执行过程,最后分析了hello对于异常以及信号的处理。

(第61分)

第7章 hello的存储管理

7.1 hello的存储器地址空间

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

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

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

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

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

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

段是对程序逻辑意义上的一种划分,一组完整逻辑意义的程序被划分为一段。段的长度不确定。段描述符用于描述一个段的详细信息。段选择符用于找到对应的段描述符。流程:通过段选择符的T1字段,确定是GDT中段还是LDT中的段。

查找段描述符,获得基地址,基地址+偏移,得到线性地址

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

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

通过7.2节中的段式管理过程,可以得到了线性地址/虚拟地址,记为VA。虚拟地址可被分为两个部分:VPN(虚拟页号)和VPO(虚拟页偏移量),根据计算机系统的特性可以确定VPN与VPO的具体位数,由于虚拟内存与物理内存的页大小相同,因此VPO与PPO(物理页偏移量)一致。而PPN(物理页号)则需通过访问页表中的页表条目(PTE)获取,如下图所示。

若PTE的有效位为1,则发生页命中,可以直接获取到物理页号PPN,PPN与PPO共同组成物理地址。

若PTE的有效位为0,说明对应虚拟页没有缓存到物理内存中,产生缺页故障,调用操作系统的内核的缺页处理程序,确定牺牲页,并调入新的页面。再返回到原来的进程,再次调用导致缺页的指令。此时发生页命中,获取到PPN,与PPO共同组成物理地址。

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

按照上述模式,每次CPU产生一个虚拟地址并且发送给地址管理单元,MMU必须找到一个PTE用来将虚拟地址翻译为物理地址。为了消除这种操作带来的大量时间开销,MMU中被设计了一个关于PTE的小的缓存,称为翻译后备缓冲器。这就是TLB,例如当每次cpu发现需要重新翻译一个虚拟地址时,它就必须发送一个vpn得到虚拟地址mmu,发送一个vpo位得到一个l1高速缓存.

corei7采用四级页表层次结构,每个四级页表进程都有自己私有的页表层次结构,这种设计方法从两个基本方面减少了对内存的需求,如果一级页表的pte全部为空,那么二级页表就不会继续存在,从而为进程节省了大量的内存,而且也只有一级页表才会有需要总是在一个内存中。四级页表的层次结构操作流程如下:36位虚拟地址被寄存器划分出来组成四个9位的片,每个片被寄存器用作到一个页表的偏移量。cr3寄存器内储存了一个l1页表的一个物理起始基地址,指向第一级页表的一个起始和最终位置,这个地址是页表上下文的一部分信息。vpn1提供了到一个l1pet的偏移量,这个pte寄存器包含一个l2页表的起始基地址.vpn2提供了到一个l2pte的偏移量,一共四级,逐级以此层次类推。

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时的内存映射

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

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

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

3)若hello程序与共享对象或目标(如标准C库libc.so)链接,则将这些对象动态链接到hello程序,然后再映射到用户虚拟地址空间中的共享区域内。                                   

    4)最后execve函数设置当前进程上下文的程序计数器,将之指向代码区域的入口点。

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

缺页故障:当指令引用一个相应的虚拟地址,而与该地址相应的物理页面不再内存中,会触发缺页故障。通过查询页表PTE可以知道虚拟页在磁盘的位置。    然后缺页处理程序从指定的位置加载页面到物理内存中,并更新PTE。然后控制返回给引起缺页故障的指令。当指令再次执行时,相应的物理页面已经驻留在内存中,因此指令可以没有故障的运行完成。

7.9动态存储分配管理

动态内存分配器维护者一个进程的虚拟内存区域,称为堆。分配器将堆视为一组不同的大小的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。分配器有两种基本风格。已分配的块显式地保留为供应用程序使用。空闲块保持空闲,直到它显式地被应用所分配。具体而言,分配器分为两种基本风格:显式分配器、隐式分配器。

1) 显式分配器:要求应用显示的释放任何已分配的块。例如C标准库提供一个叫做malloc程序包的显示分配器。

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

7.9.1隐式链表

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

7.9.2显式链表

在每个空闲块中,都包含一个前驱(pred)与后继(succ)指针,从而减少了搜索与适配的时间。

7.9.3带边界标记的合并

采取使用边界标记的堆块的格式,在堆块的末尾为其添加一个脚部,其为头部的副本。添加脚部之后,分配器就可以通过检查前面一个块的脚部,判断前面一个块的起始位置和状态。通过双界标记,我们可以在常数时间内完成空闲块的合并,减小性能消耗。

7.9.4分离的空闲链表

维护多个空闲链表,其中,每个链表的块具有相同的大小。将所有可能的块大小分成一些等价类,从而进行分离存储。

7.10本章小结

本章简述了系统对于hello的存储管理,介绍了intel段式、页式管理,分析了程序的虚拟地址逐步翻译为物理地址的过程,分析程序运行过程中fork,execve函数进行的内存映射,说明了系统对于缺页异常的处理以及动态存储的分配。

(第7 2分)

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

Linux将文件所有的I/O设备都模型化为文件,甚至内核也被映射为文件。这种将设备映射为文件的方式,允许Linux内核引出一个简单、低级的应用接口,称为Unix I/O。Linux就是基于Unix I/O实现对设备的管理

8.2 简述Unix IO接口及其函数

1、接口

1)打开文件:一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个 I/O 设备,内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件,内核记录有关这个打开文件的所有信息,应用程序只需要记住这个描述符。

2)改变当前的文件位置: 对于每个打开的文件,内核保持着一个文件位置k,初始为0,这个文件位置是从文件开头起始的字节偏移量,应用程序能够通过执行seek,显式地将改变当前文件位置k。

3) 读写文件:进行复制操作并改变文件位置k的值。

4) 关闭文件:内核释放相应数据结构,将描述符恢复到可用的描述符池中

2、函数

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

进程通过调用open函数来打开一个存在的文件或是创建一个新文件的。open函数将filename转换为一个文件描述符,并且返回描述符数字,返回的描述符总是在进程中当前没有打开的最小描述符,flags参数指明了进程打算如何访问这个文件,mode参数指定了新文件的访问权限位。

2)int close(fd)

fd是需要关闭的文件的描述符,close返回操作结果。

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

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

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

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

8.3 printf的实现分析

printf函数的函数体

在形参列表里有这么一个token:... 这个是可变形参的一种写法。当传递参数的个数不确定时,就可以用这种方式来表示。

再进一步查看windows系统下的vsprintf函数体:

va_list的定义:typedef char *va_list,说明它是一个字符指针,其中 (char*)(&fmt) + 4) 即arg表示的是...中的第一个参数。

再看一看vprintf函数

其中vsprintf(buf, fmt, arg)函数能够返回我们想要打印的字符串的长度并对我们的格式化字符串进行解析。当获取到字符串的长度后,我们便能够将字符串打印出来。

从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.

字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。

显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。

8.4 getchar的实现分析

当程序调用getchar时,程序等待用户按键,用户输入的字符被存放在键盘缓冲区中直到用户按回车(回车也在缓冲区中)。

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

getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。在调用read时,它发送一个中断信号,内核抢占这个进程,用户输入字符串,键入回车后(字符串和回车都保存在缓冲区内),再次发送信号,内核重新调度这个进程,getchar从缓冲区读入字符。

8.5本章小结

本章介绍了LinuxI/O设备的管理方法,UnixI/O接口和函数,并且分析了printfgetchar函数是如何通过UnixI/O函数实现其功能的。

(第81分)

结论

hello程序一生如下:

0.初始时,hello.c是存储在磁盘上的一个文本文件。

1.hello.c经过编译器的预处理生成hello.i文本文件

2.hello.i通过编译器将合法指令翻译成等价汇编代码,生产hello.s汇编文件

3.hello.s通过汇编器as汇编到二进制可重定位目标文件hello.o

4.hello.o由链接器ld链接生成可执行文件hello,至此,hello成为了一个可以运行的程序。

5.通过在shell上运行./hello 120L020405 dry 3,shell调用fork函数,生成子进程;

6.execve函数加载运行当前进程的上下文中加载并运行新程序hello,这一过程中,涉及到了虚拟内存,内存映射。

7. hello运行过程中,可能遇到各种异常,收到各种信号,这些需要异常处理程序和信号处理程序。

8. hello最终被shell父进程回收,内核会收回为其创建的所有信息

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

附件

hello.i                             预处理得到的文本文件

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

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

hello.elf                       用readelf读取hello.o得到的ELF格式文件

hello                           hello.o经过链接得到的可执行文件

hello1.elf                    用readelf读取hello得到的ELF格式文件

see_hello.txt                       用objdump生成的hello.o的汇编文件

see1_hello.txt                      用objdump生成的hello的汇编文件

     

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

参考文献

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

[1]  Randal E.Bryant, David O'Hallaron. 深入理解计算机系统[M]. 机械工业出版社.2018.4

[2]  C语言编译过程中*.i *.s *.o *.out 等文件是什么?_大石斑�的博客-CSDN博客_.i文件.

(123条消息) C语言编译过程中*.i *.s *.o *.out 等文件是什么?_大石斑�的博客-CSDN博客_.i文件

[3]  Top (The C Preprocessor) (gnu.org)

[4]  [转]printf 函数实现的深入剖析 - Pianistx - 博客园 (cnblogs.com) [5] 

[转]printf 函数实现的深入剖析 - Pianistx - 博客园 (cnblogs.com)

[5]  Linux链接命令

(123条消息) Linux链接命令_佳期如顭的博客-CSDN博客_linux链接命令

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值