程序人生_大作业

计算机系统

大作业

题     目  程序人生-Hello’s P2P 

专       业      人工智能           

学     号     120E036108           

班     级       20E0361           

学       生        闻亮           

指 导 教 师        史先俊            

计算机科学与技术学院

2021年5月

摘  要

本论文简短的介绍了对于每个程序员来说最熟悉的伙伴—hello程序的一生,从预处理,编译,汇编,链接,再到进程管理,存储管理,IO管理。这短短的过程却蕴含着人生哲理!

主要在Ubuntu下进行相关操作,运用了一些Ubuntu下的操作工具,进行细致的历程分析,加深了自己对于计算机系统的理解,也对人生有了新的感触

关键词:hello程序;程序人生;计算机系统;Ubuntu                           

 

(摘要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进程创建过程  

第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.c文件,经过预处理器cpp寻以#开头的命令,将预处理成功的文件输出为hello.i。编译器将预处理好的文件生成汇编语言程序hello.s。这个过程中,可以用objdump指令反汇编查看汇编语言。再经过连接器ld链接,至此生成可执行文件hello。可以在Linux系统中内置命令行解释器shell加载运行hello程序,在经过fork后,由此完成了hello程序的p2p过程。

1.2 环境与工具

1.2.1硬件环境

AMD Ryzen 5 4600U with Radeon Graphics 2.10GHz

1.2.2软件环境

Ubuntu

1.2.3开发与调试工具

vim,gcc,as,ld,edb,readelf,objdump

1.3 中间结果

文件名称

文件作用

hello.c

Hello 源程序

hello.i

预处理生成的文件

hello.s

编译之后的汇编文件

hello.o

汇编之后的可重定位目标执行

hello.elf

hello.o 的 ELF 格式

hellooooo.s

hello.o 的反汇编代码文件

hello

hello 链接之后的可执行文件

hello 反汇编.txt

Hello 的反汇编代码文件

 

1.4 本章小结

本章总体介绍了执行运行hello程序的大致过程,并给出了这个实验的软硬件环境,以及实验过程中需要的调试工具。并对主要过程中的中间结果做出了简要分析。

(第1章0.5分)

 

第2章 预处理

2.1 预处理的概念与作用

1.概念

程序设计领域中,预处理一般是指在程序源代码被翻译为目标代码的过程中,生成二进制代码之前的过程。

 

2.作用:

预处理是指预处理器(cpp)根据以字符#开头的命令,修改原始的 C 程序,比如#include <stdio.h>命令告诉预处理器读取系统头文件 stdio.h 的内容,并把它直接插入程序文本中。结果就得到了另一个 C 程序,通常是以 .i 作为文件扩展名。

 

3.常见预处理指令

常见的预处理指令有 #endif(条件编译)、#define(宏定义)、#include(源文件包含)、#line(行控制)、#error(错误指令)、#pragma(和实现相关的杂注)以及单独的#(空指令)

2.2在Ubuntu下预处理的命令

预处理命令:gcc -E hello.c -o hello.i

图1 预处理命令

生成了hello.i文件

 

图2 预处理结果

图3 .i文件内容

2.3 Hello的预处理结果解析

在经过预处理后,hello.c文件转化为了hello.i文件。经过对hello.i文件的查看,在原有的hello.c代码的基础上,将头文件的内容引用进来,例如声明函数、定义结构体、定义变量、定义宏等内容。

原文件中的宏进行了宏展开,头文件中的内容被包含进该文件中。以 stdio.h 的展开为例,cpp 到默认的环境变量下寻找 stdio.h,打开/usr/include/stdio.h 发现其中依然使用了#define 语句,cpp 对此递归展开,所以最终.i 程序中是没有#define 的。而且发现其中使用了大量的#ifdef #ifndef 的语句,cpp 会对条件值进行判断来决定是否执行包含其中的逻辑。其他类似。

Hello.i 仍为可以阅读的 C 语言程序文本文件。

图4 hello.i部分内容

2.4 本章小结

本章介绍了hello.c经过预处理转化为hello.i的过程。此步骤是生成可执行文件的第一步处理,在此阶段完成后,即可机型下一段的汇编处理。

 

(第2章0.5分)

第3章 编译

3.1 编译的概念与作用

3.1.1概念

编译是指编译器(ccl)将文本文件hello.i翻译成文本文件hello.s的过程,它包含了一个汇编语言程序。

3.1.2作用 

编译器的基本功能是把源程序(高级语言)翻译成目标程序。

除了基本功能之外,编译程序还具备语法检查、调试措施、修改手段、覆盖处

理、目标程序优化、不同语言合用以及人机联系等重要功能。

 

3.2 在Ubuntu下编译的命令

编译命令:gcc -S hello.i -o hello.s

图5 编译命令

图6 经编译得到hello.s

3.3 Hello的编译结果解析

图7 hello.s文件内容

3.3.1整型局部变量

在hello.c程序main函数中定义了局部变量i:

图8 整型局部变量

编译处理会将它存储到栈中:

可见i存储在栈中-4(%rbp)的位置。

3.3.2数组(char指针类型)

在hello.c程序main函数参数中出现了char指针数组:

图9 char指针数组

编译处理会将它存储到栈中:

图10:压栈

首地址在%rsi传入,执行时栈会向下生长32字节,之后将数组压栈。

之后再次用到该数组时,从栈中读出即可:

图11:查看地址

起始地址为-32(%rbp),取出直接访问即可。

3.3.3字符串

在hello.c中,存在需要输出的字符串:

图12:字符串

编译处理时将这两个字符串常量提取出来,声明在.rodata节中:

3.3.4关系操作

在hello.c中,有两处涉及到了关系操作:

图13:关系操作

编译处理时使用cmp指令处理了这些操作:

其中第二条是通过i和7的比较确定i是否小于8,

3.3.5控制转移

在hello.c中,两次输出中包含常量字符串:

图14:控制转移

编译处理时,由于常量字符串被提前声明,在这里输出时使用了控制转移:

上图为argc与4的比较,如果相等则跳转到L2,在原程序里体现为不进入if语句,如果不相等,不跳转继续执行,体现为进入if语句。

上图为for循环的判断部分,当i小于等于7时,跳转到L4,L4实现的时输出语句的相关操作。

3.3.6算术操作

例如在hello.c中的变量i不断累加的过程:

图15:算术操作(1)

在编译过程中:

图16:算术操作(2)

第一个是源操作数,第二个是目标操作数,实现了将i加1的工作。

事实上,算术操作不仅只在hello.c中出现算术操作的地方存在,比如在栈的存储过程中,栈指针的变动也依赖于算术操作:

图17:算术操作(3)

上图将栈指针减小32,意为将要存储数据。

3.3.7函数操作:

 

hello.c中调用了printf()函数、exit()函数、sleep()函数:

图18:函数

printf()函数:将字符串常量的首地址存入寄存器作为参数传递,并使用call调用。

图19:call调用(1)

exit()函数:将1存入寄存器作为参数传递,并使用call调用。

图20:call调用(2)

sleep()函数:将规定位置的值存入一个新的寄存器作为参数,使用call调用。

图21:call调用(3)

3.4 本章小结

本章说明了P2P过程中的编译部分,并对生成的汇编程序中涉及到的C语言各种数据类型和各类操作做了说明。编译的过程是编译器将预处理后得到的文件进一步翻译为汇编语言的过程。

(第32分)

第4章 汇编

4.1 汇编的概念与作用

4.1.1 概念:

汇编大多是指汇编语言,汇编程序。把汇编语言翻译成机器语言的过程称为汇编。

 

4.1.2 作用:

将.s 汇编程序翻译成机器语言指令, 把这些指令打包成可重定位目标程序的格式,并将结果保存在.o 目标文件中,.o 文件是一个二进制文件。它包含程序的指令编码。

4.2 在Ubuntu下汇编的命令

命令:gcc -c hello.s -o hello.o

图22 汇编命令

4.3 可重定位目标elf格式

先使用 readelf -a hello.o > hello.elf 指令获得 hello.o 文件的 ELF 格式。

图23 hello.elf

图24 可重定位的目标文件 ELF 格式

    下面分段分析elf文件内容:开始是一段elf头,包括了机器的相关信息,例如大小端信息、负数存储信息、字的大小等。还有.o文件的相关信息,例如入口点地址、节头部的大小等。这些信息可以帮助链接器完成接下来的工作。

图25 ELF头

       (2)     节头部。如图 4-5,可以看到,节头部表描述了 hello.o 文件各个节的类型、位置和大小等信息,比如.text 节的地址是 0,偏移量是 0x40,大小是 0x92。

重定位节: 图 4-6,:重定位节.rela.text ,一个.text 节中位置的列表,包含.text 节中需要进行重定位的信息,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。其中offset是需要被修改的引用的节偏移。symbol标识被修改引用应该指向的符号。type告知链接器如何修改新的引用。addend是一个有符号常数,一些类型的重定位要使用它对被修改引用的值做偏移调整。

 

elf定义了32种不同的重定位类型,有些相当隐秘。我们只关心其中最基本的重定位类型:R_X86_64_PC32重定位一个使用32位PC相对地址的引用;R_X64_64_32重定位一个使用32位绝对地址的引用。

最后是一个符号表,存放在程序中定义和引用的函数和全局变量的信息:

4.4 Hello.o的结果解析

命令:objdump -d -r hello.o

与hello.s对比发现

1)操作数在hello.s中为十进制,在这里为十六进制

2)分支转移地址表示,hello.s上为".L1"等段名称,在这里为相对偏移的地址

3)函数调用时,hello.s上为函数名,在这里call指令后是调用函数的相对偏移地址

4)访问全局变量时,hello.s上是通过".LC1(%rip)"的形式访问,在这里是以"0x0(%rip)"的形式访问,添加了重定位条目

4.5 本章小结

本章介绍了汇编过程,由 .s 变到 .o 文件。通过查看 ELF 头,里面记录了这个文件的基本信息,通过查看这张表可以得到段基址、文件节头数、符号表等等信息。然后再反汇编和原来的 hello.s 对比,分析了汇编语言与机器码的关系。

(第41分)

第5章 链接

5.1 链接的概念与作用

5.1.1 概念:

链接(linking)是将各种代码和数据片段收集并组合成为一个单一文件的过程,

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

5.2.2 作用

链接器在软件开发中扮演着一个关键的角色,因为它们使得分离编译

( separate compilation)成为可能。我们不用将一个大型的应用程序组织为一个巨大的源文件,而是可以把它分解为更小、更好管理的模块,可以独立地修改和编译这些模块。当我们改变这些模块中的一个时,只需简单地重新编译它,并重新链接应用,而不必重新编译其他文件。

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的格式

执行上述命令生成elf文件

首先仍然是elf头,给出了机器的基本信息,和之前的elf头相比,给出了程序的入口点,程序头等,表示已经完成了链接

之后为各节的信息,包括名称、类型、起始地址等。这些节已经被重定位至最终运行时的地址。

5.4 hello的虚拟地址空间

上图给出了elf文件中的程序头部表,描述了可执行文件连续的片和连续的内存段的映射关系。其中包括段:

PHDR:程序头表

INTERP:程序执行前需要调用的解释器

LOAD:程序目标代码和常量信息

DYNAMIC:动态链接器使用信息

NOTE:保存辅助信息

GNU_EH_FRAME:保存异常信息

GNU_STACK:标志栈是否可用的标志信息

GNU_RELRO:保存在重定位之后只读信息的位置

以PHDR为例,在edb中查看PHDR段内容

起始地址为0x00400040,与elf文件中一致

5.5 链接的重定位过程分析

命令:objdump -d -r hello

得到hello的汇编代码

对比本次生成的汇编代码和hello.o生成的汇编代码的不同

1)hello汇编代码中出现了更多函数,hello.o的汇编代码中只出现了main函数的名字,而hello的汇编代码中出现了_init,.plt等函数名。这体现了很多非hello.c源程序中生命的函数被链接到了hello中

2)函数调用地址变化,在hello.o汇编代码中函数的地址不确定,在hello汇编代码中地址确定下来。如.plt函数的地址为0x401020

3)数据引用地址变化,在hello.o汇编代码中很多数据的地址不确定,在hello汇编代码中地址确定下来

4)结合hello.o的重定位项目,可以得出在重定位过程中,链接器将符号解析完成后,就将代码中的所有符号引用都与一个符号定义关联。这样链接器就可以按照各个内容的具体大小来进行重定位,合并输入模块,并为每个符号分配运行时的地址

5.6 hello的执行流程

(1)ld-linux-x86-64.so!_dl_start

(2)ld-linux-x86-64.so!_dl_init

(3)hello!_start

(4)hello!__libc_csu_init

(5)hello!_init

(6)libc.so!_setjmp

(7)hello!main

(8)hello!puts@plt

(9)ld-linux-x86-64.so!_dl_runtime_resolve_xsave

(10)ld-linux-x86-64.so!_dl_fixup

(11)ld-linux-x86-64.so!_dl_lookup_symbol_x

(12)hello!exit@plt

(13)libc.so!exit

(14)hello!_fini

5.7 Hello的动态链接分析

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

动态链接库中的函数在程序执行的时候才会确定地址,所以编译器无法确定其地址。为避免运行时修改调用模块的代码段,链接器采用延迟绑定的策略。延迟绑定通过两个数据结构之间简洁但又有些复杂的交互来实现,即过程链接表(PLT)和全局偏移量表(GOT)。

    过程链接表(PLT):PLT是一个数组,其中每个条目是16字节代码。PLT [0]是一个特殊条目,它跳转到动态链接器中。每个被可执行程序调用的库函数都有它自己的PLT条目。每个条目都负责调用一个具体的函数。每个条目都负责调用一个具体的函数。

    全局偏移量表(GOT):GOT是一个数组,其中每个条目是8字节地址。和PLT联合使用时,GOT [0]和GOT [1]包含动态链接器在解析函数地址时会使用的信息。GOT [2]是动态链接器在1d-linux.so模块中的入口点。其余的每个条目对应于一个被调用的函数,其地址需要在运行时被解析。每个条目都有一个相匹配的PLT条目

调用GOT前后,可见动态链接器解析函数的地址加入了信息中

5.8 本章小结

在本章中主要介绍了链接的概念与作用、hello 的 ELF 格式,分析了 hello 的虚拟地址空间、重定位过程、执行流程、动态链接过程。

链接过程可以发生在编译时,也可以发生在加载时,甚至可以发生在程序执行时。经过链接,ELF 可重定位的目标文件变成可执行的目标文件,链接器会将静态库代码写入程序中,以及动态库调用的相关信息,并且将地址进行重定位,从而保证寻址的正确进行。静态库直接写入代码即可,而动态链接过程相对复杂一些,涉及共享库的寻址。

链接后,程序便能够在作为进程通过虚拟内存机制直接运行。

(第51分)

 

第6章 hello进程管理

6.1 进程的概念与作用

6.1.1 概念:

进程是计算机科学中最深刻最成功的概念之一。

进程的概念主要有两点:

第一,进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。

第二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程。

进程是操作系统中最基本、重要的概念。是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律引进的一个概念,所有多道程序设计操作系统都建立在进程的基础上。

 

6.1.2 作用

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

(1)逻辑控制流 (Logical control flow)

·每个程序似乎独占地使用 CPU

·通过 OS 内核的上下文切换机制提供

(2)私有地址空间 (Private address space)

·每个程序似乎独占地使用内存系统

·OS 内核的虚拟内存机制提供

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

6.2.1 作用:

Shell 字面理解就是个“壳”,Shell 是一个用 C 语言编写的程序。是操作系统(内核)与用户之间的桥梁,充当命令解释器的作用,将用户输入的命令翻译给系统执行。 Linux 中的 shell 与 Windows 下的 DOS 一样,提供一些内建命令(shell 命令)供用户使用,可以用这些命令编写 shell 脚本来完成复杂重复性的工作。

6.2.2 处理流程:

1、         从终端或控制台读入用户输入的命令。

2、         将输入字符串切分获得所有的参数

3、         如果是内置命令则立即执行

4、         否则调用相应的程序为其分配子进程并运行

5、         shell 应该接受键盘输入信号,并对这些信号进行相应处理

6、         判断程序的执行状态,是前台还是后台。若是前台就等待进程结束;否则直接将进程放入后台执行,继续等待用户的下一次输入。

6.3 Hello的fork进程创建过程

首先运行 hello 程序,在终端输入./ hello,

接下来 shell 会判断是否是内置命令,但是她不是内置命令,所以 shell 会从文件系统中找到当前目录下的 hello 文件并执行。

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

然后 hello 将在 fork 创建的子进程中执行。

6.4 Hello的execve过程

子进程创建后,shell调用execve函数加载并运行可执行目标文件hello,且带参数列表argv和环境变量列表envp。之后当出现错误时,例如找不到hello,execve才会返回到调用程序。

在execve加载了hello后,它调用启动代码,启动代码设置栈,并将控制转移给新程序的主函数main,此时用户栈已经包含了命令行参数和环境变量,进入main函数后开始逐步运行程序。

6.5 Hello的进程执行

6.5.1 逻辑控制流和时间片

逻辑控制流:一系列程序计数器 PC 的值的序列叫做逻辑控制流,进程是轮流使用处理器的,在同一个处理器核心中,每个进程执行它的流的一部分后被抢占

(暂时挂起),然后轮到其他进程。     时间片:一个进程执行它的控制流的一部分的每一时间段叫做时间片。

6.5.2 上下文

操作系统内核使用一种称为上下文切换(conext switch)的较高层形式的异常控制流来实现多任务。

      内核为每个进程维持一个上下文(context)。 上下文就是内核重新启动一个被抢占的进程所需的状态。它由一些对象的值组成,这些对象包括通用目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构,比如描述地址空间的页表、包含有关当前进程信息的进程表,以及包含进程已打开文件的信息的文件表。

      在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程。这种决策就叫做调度(scheduling),是由内核中称为调度器(scheduler) 的代码处理的。当内核选择一个新的进程运行时,我们说内核调度了这个进程。在内核调度了一个新的进程运行后,它就抢占当前进程,并使用一种称为上下文切换的机制来将控制转移到新的进程,上下文切换

1)保存当前进程的上下文,

2)恢复某个先前被抢占的进程被保存的上下文。

3)将控制传递给这个新恢复的进程。

6.5.3 用户模式和内核模式

    为了使操作系统内核提供一个无懈可击的进程抽象,处理器必须提供一种机

制,限制一个应用可以执行的指令以及它可以访问的地址空间范围。

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

    没有设置模式位时,进程就运行在用户模式中。用户模式中的进程不允许执行特权指令(privileged instruction), 比如停止处理器、改变模式位,或者发起一个 I/O 操作。也不允许用户模式中的进程直接引用地址空间中内核区内的代码和数据。任何这样的尝试都会导致致命的保护故障。反之,用户程序必须通过系统调用接口间接地访问内核代码和数据。

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

 6.5.4 hello 的进程执行:

hello 在刚开始运行时内核为其保存一个上下文,进程在用户状态下运行。,如果没有异常或中断信号的产生,hello 将继续正常地执行。

如果 hello 程序不被抢占则顺序执行,假如发生被抢占的情况,则进行上下文切换。

hello 初始运行在用户模式,在 hello 进程调用 sleep 之后陷入内核模式。内核处理休眠请求主动释放当前进程,并将 hello 进程从运行队列中移出加入等待队列,定时器开始计时,内核进行上下文切换将当前进程的控制权交给其他进程,当定时器(2.5secs)时发送一个中断信号,中断当前正在进行的进程,进行上下文切换,恢复 hello 在休眠前的上下文信息,控制权回到 hello 继续执行。

当循环结束后,hello 调用 getchar 函数,之前 hello 运行在用户模式下,在调用 getchar 时进入内核模式,内核中的陷阱处理程序请求来自键盘缓冲区的 DMA 传输,并执行上下文切换,并把控制转移给其他进程。当完成键盘缓冲区到内存的 数据传输后,引发一个中断信号,此时内核从其他进程切换回 hello 进程,然后 hello 执行 return,进程终止。

6.6 hello的异常与信号处理

 会出现四种异常:中断、陷阱、故障、终止。

会出现的信号:SIGSTP、SIGCONT、SIGKILL、SIFGINT等。

中断是来自I/O设备的信号,异步发生,中断处理程序对其进行处理,返回后继续执行调用前待执行的下一条代码,就像没有发生过中断。

陷阱是有意的异常,是执行一条指令的结果,调用后也会返回到下一条指令,用来调用内核的服务进行操作。帮助程序从用户模式切换到内核模式。

故障是由错误情况引起的,它可能能够被故障处理程序修正。如果修正成功,则将控制返回到引起故障的指令,否则将终止程序。

终止是不可恢复的致命错误造成的结果,通常是一些硬件的错误,处理程序会将控制返回给一个abort例程,该例程会终止这个应用程序。

程序运行时通过键盘操作给信号:

(1)    运行中不停乱按,将所输内容输出到屏幕上

(2)    运行中回车,会将按键操作的输入放到缓存区中,程序运行结束执行

(3)    运行中按ctrl-c,shell父进程收到SIGINT信号,信号处理函数将hello进程终止并回收

(4)    运行中按ctrl-z,shell父进程收到SIGSTP信号,信号处理函数将hello进程挂起

(5)    ctrl-z后运行fg命令发送SIGCONT信号继续执行hello

(6)    ctrl-z后运行kill命令发送SIGKILL信号杀死hello

(7)    ctrl-z后运行ps、jobs、pstree命令,输出相关信息。ps命令输出当前系统中的进程;jobs命令输出当前已启动的任务状态;pstree命令输出进程间的树状关系

6.7本章小结

本章介绍了程序在 shell 执行及进程的相关概念。程序在 shell 中执行是通过 fork 函数及 execve 创建新的进程并执行程序。进程拥有着与父进程相同却又独立的环境,与其他系统进并发执行,拥有各自的时间片,在内核的调度下有条不紊的执行着各自的指令。

程序运行中难免遇到异常,异常分为中断、陷阱、故障和终止四类,均有对应的处理方法。操作系统提供了信号这一机制,实现了异常的反馈。这样,程序能够对不同的信号调用信号处理子程序进行处理。

(第61分)

第7章 hello的存储管理

7.1 hello的存储器地址空间

1、物理地址(physical address)

计算机系统的主存被组织成一个由 M 个连续的字节大小的单元组 成的数组,其每一个字节都被给予一个唯一的地址,这个地址称为物理地址。

用于内存芯片级的单元寻址,与处理器和 CPU 连接的地址总线相对应。

这个概念应该是这几个概念中最好理解的一个, 

2、虚拟内存(virtual memory)

CPU 启动保护模式后,程序运行在虚拟地址空间中。与物理地址相 似,虚拟内存被组织为一个存放在磁盘上的 N 个连续的字节大小的单元组成的数 组,其每个字节对应的地址成为虚拟地址。虚拟地址包括 VPO(虚拟页面偏移量)、 VPN

(虚拟页号)、TLBI(TLB 索引)、TLBT(TLB 标记)。  

计算机会呈现出要比实际拥有的内存大得多的内存量。因此他允许程式员编制并运行比实际系统拥有的内存大得多的程式。这使得许多大型项目也能够在具有有限内存资源的系统上实现

3、逻辑地址(logical address)

    是指由程式产生的和段相关的偏移地址部分,。分为两个部分,一个部分为段基址,另一个部分为段偏移量。

4、线性地址(linear address)

地址空间是一个非负整数地址的有序集合,而如果此时地址空间中 的整数是连续的,则我们称这个地址空间为线性地址空间。   

其中,由逻辑地址翻译得到的线性地址即为虚拟地址

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

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

段式管理是实现逻辑地址到线性地址转换机制的基础,段的特征有段基址、段限长、段属性。这三个特征存储在段描述符中,用以实现从逻辑地址到线性地址的转换。段描述符存储在段描述符表中,通常,我们使用段选择符定位段描述符在这个表中的位置。每个逻辑地址由16位的段选择符和32位的偏移量组成。

 

段基址规定了线性地址空间中段的开始地址。在保护模式下,段基址长32位。因为基址长度和寻址地址的长度相同,所以段基址可以是0-4GB范围内的任意地址。

和一个段有关的信息需要8个字节来描述,这就是段描述符。为了存放这些描述符,需要在内存中开辟出一段空间。在这段空间里所有的描述符都在一起集中存放,这就构成了一个描述符表,描述符表分为两种,GDT和LDT。

一些全局的段描述符,就放在"全局段描述符表(GDT)"中,一些局部的,例如每个进程自己的段描述符,就放在的"局部段描述符表(LDT)"中。

介绍一个完整的变换过程,给出一个完整的逻辑地址[段选择符:段内偏移地址]。首先看段选择符判断当前转换时GDT中的段还是LDT中的段,再根据相应寄存器得到其地址和大小。之后拿出段选择符中的前13位,在对应地址中查找到对应的段描述符,这样就知道了基址。根据基址和偏移量结合,就得到了所求的线性地址。

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

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

分页机制是实现虚拟存储的关键,位于线性地址与物理地址的变换之间设置。虚拟内存被组织为一个由存放在磁盘上的N个连续的字节大小的单元组成的数组。每字节都有一个唯一的虚拟地址,作为到数组的索引。磁盘上数组的内容被缓存在主存中。和存储器层次结构中其他缓存一样,磁盘上的数据被分割成块,这些块作为磁盘和主存之间的传输单元。VM系统通过将虚拟内存分割为称为虚拟页为大小固定的块来处理这个问题。每个虚拟页的大小固定。类似地,物理内存被分割为物理页,大小与虚拟页相同。

同任何缓存一样,虚拟内存系统必须用某种方法来判定一个虚拟页是否缓存在DRAM中的某个地方。如果是,系统还必须确定这个虚拟页存放在哪个物理页中。如果不命中,系统必须判断这个虚拟页存放在磁盘的哪个位置,在物理内存中选择一个牺牲页,并将虚拟页从磁盘复制到DRAM,替换这个牺牲页。

页表是一个存放在物理内存中的数据结构,将虚拟页映射到物理页。每次地址翻译硬件将一个虚拟地址转换为物理地址时读取页表。操作系统负责维护页表中的内容,以及再磁盘与DRAM之间来回传送页。

内存分页管理的基本原理是将整个内存区域划分成固定大小的内存页面。程序申请使用内存时就以内存页位单位进行分配。转换通过两个表,页目录表PDE(也叫一级目录)和二级页表PTE。进程的虚拟地址需要首先通过其局部段描述符变换为CPU整个线性地址空间中的地址,然后再使用页目录表和页表PTE映射到实际物理地址上。

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

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

每次CPU产生一个虚拟地址,MMU就必须查阅一个PTE,以便将虚拟地址翻译为物理地址。为了降低时间开销,MMU中包括了一个关于PTE的小的缓存,称为翻译后备缓冲器。

TLB是一个小的、虚拟寻址的缓存,其中每一行都保存着一个由单个PTE组成的块,下述为一个从TLB中获取物理地址的过程:

(1)CPU产生一个虚拟地址。

(2)MMU从TLB中取出相应的PTE。

(3)MMU将这个虚拟地址翻译成一个物理地址,并且将它发送到高速缓存/主存。

(4)高速缓存/主存将所请求的数据字返回给CPU。

下图给出了含TLB的虚拟地址翻译流程:

Inter Core i7实现支持48位虚拟地址空间和52位物理地址空间,使用4KB的页。X64 CPU上的PTE为64位,所以每个页表一共有512个条目。512个PTE条目需要9位VPN定位。再四级页表的条件下,一共需要36位VPN,因为虚拟地址空间是48位,故低12位是VPO。TLB四路组联,共有16组,需要4位TLBI,故VPN的低4位是TLBI,高32位是TLBT。

CPU产生虚拟地址VA,VA传送给MMU,MMU使用前36位VPN作为TLBT+TLBI向TLB中匹配,如果命中,则得到40位PPN+12位VPO组合成52位物理地址PA。如果没有命中,MMU向页表中查询,CR3确定第一级页表的起始地址,9位VPN1确定在第一级页表中的偏移量,查询出第一部分PTE,以此类推最终在四级页表都访问完后获得PPN,与VPO结合获得PA,并向TLB中更新。

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

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

MMU 发送物理地址 PA 给 L1 缓存,L1 缓存从物理地址中抽取出缓存偏移 CO、缓存组索引 CI 以及缓存标记 CT。高速缓存根据 CI 找到缓存中的一组,并通过 CT 判断是否已经缓存地址对应的数据,若缓存命中,则根据偏移量直接从缓存中读取数据并返回;若缓存不命中,则继续从 L2、L3 缓存中查询,若仍未命中,则从主存中读取数据。

7.6 hello进程fork时的内存映射

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

虚拟内存和内存映射解释了 fork 函数如何为每个新进程提供私有的虚拟地址空间. 

为新进程创建虚拟内存

创建当前进程的的 mm_struct, vm_area_struct 和页表的原样副本. 

两个进程中的每个页面都标记为只读

两个进程中的每个区域结构(vm_area_struct)都标记为私有的写时复制(COW)在新进程中返回时,新进程拥有与调用 fork 进程相同的虚拟内存随后的写操作通过写时复制机制创建新页面

7.7 hello进程execve时的内存映射

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

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

2、创建新的区域结构私有的、写时复制

代码和初始化数据映射到.text 和.data 区(目标文件提供)

.bss 和栈堆映射到匿名文件 ,栈堆的初始长度 0

3、共享对象由动态链接映射到本进程共享区域

4、设置 PC,指向代码区域的入口点

Linux 根据需要换入代码和数据页面

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

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

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

缺页中断处理:缺页处理程序是系统内核中的代码,选择一个牺牲页面,如果这个牺牲页面被修改过,那么就将它交换出去,换入新的页面并更新页表。当缺页处理程序返回时,CPU重新启动引起缺页的指令,这条指令再次发送VA到MMU,这次 MMU 就能正常翻译 VA 了。

7.9动态存储分配管理

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

    分配器分为两种:显式分配器和隐式分配器。显式分配器要求应用显式地释放人设已分配地块。隐式分配器要求分配器检测一个已分配块何时不再被程序所使用,那么就释放这个块。隐式分配器也叫做垃圾回收器,而自动释放未使用的已经分配的块的过程叫做垃圾收集。

    malloc使用的是显式分配器,通过free函数释放已分配的块。

    下面分别介绍两种分配器:

    (1)隐式空闲链表分配器。我们可以将堆组织为一个连续的已分配块和空闲块的序列,空闲块是通过头部中的大小字段隐含地连接着的,这种结构为隐式空闲表。分配器可以通过遍历堆中所有的块,从而间接地遍历整个空闲块地集合。一个块是由一个字的头部、有效载荷、可能的填充和一个字的脚部,其中脚部就是头部的一个副本。头部编码了这个块的大小以及这个块是已分配还是空闲的。分配器就可以通过检查它的头部和脚部,判断前后块的起始位置和状态。

    (2)显示空闲链表分配器。将堆组成一个双向空闲链表,在每个空闲块中,都包含一个pred和succ指针。一种方法是用后进先出(LIFO)的顺序来维护链表,将新释放的块放置在链表的开始处。使用LIFO的顺序和首次适配的放置策略,分配器会最先检查最近使用过的块。在这种情况下,释放一个块可以在常数时间内完成。如果使用了边界标记,那么合并也可以在常数时间内完成。另一种方法是按照地址顺序来维护链表,其中链表中每个块的地址都小于它后继的地址,在这种情况下,释放一个块需要线性时间的搜索来定位合适的前驱。平衡点在于,按照地址地址排序的首次适配比LIFO排序的首次适配有更高的内存利用率,接近最佳适配的利用。一般而言,显式链表的缺点是空闲块必须足够大,以包含所有需要的指针,以及头部和可能的脚部。这就导致了更大的最小块大小,也潜在的提高了内部碎片的程度。

7.10本章小结

虚拟内存是对主存的一个抽象,访存时地址需要从逻辑地址翻译到虚拟地址并进一步翻译成物理地址。

        现代系统通过将虚拟内存片和磁盘上的文件片关联起来,来初始化虚拟内存片,这个过程称为内存映射。

操作系统通过地址的页式管理来实现对磁盘的缓存、内存管理、内存保护等功能。

虚拟内存为便捷的加载、进程管理提供了可能。程序运行过程中往往涉及动态

内存分配,动态内存分配通过动态内存分配器完成

 

(第7 2分)

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

设备的模型化:文件

设备管理:unix io 接口 

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

8.2 简述Unix IO接口及其函数

Unix I/O接口的统一操作:

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

(2)Linux shell创建的每个进程开始时都有三个打开的文件:标准输入、标准输出和标准错误。

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

(4)读写文件。一个读操作就是从文件复制n个字节到内存,从当前文件位置k开始,然后将k增加到k+n。给定一个大小为m字节的文件,当k≥m时执行读操作会触发一个称为end-of-file的条件,应用程序能够检测到这个条件。在文件结尾处并没有明确的"EOF符号"。

(5)关闭文件。当应用程序完成了对文件的访问后,它就通知内核关闭这个文件。作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。

 

Unix I/O函数接口提供了以下函数共应用程序调用:

(1)open:进程是通过调用open函数来打开一个已存在的文件或者创建一个新文件的。open将filename转换为一个文件描述符,并且放回描述符数字。

(2)close:进程通过调用close函数关闭一个打开的文件。关闭一个已关闭的描述符会出错。

(3)read:应用程序通过read函数来执行输入。read函数从描述符为fd的当前文件位置复制最多n个字节到内存位置buf。返回值-1表示一个错误;返回值0表示EOF;否则,返回值表示的是实际传扫的字节数量。

(4)write:应用程序通过write函数来执行输出。write函数从内存位置buf复制至多n个字节到描述符fd的当前文件位置。

8.3 printf的实现分析

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

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

研究 printf 的实现,首先来看看 printf 函数的函数体

在形参列表里有这么一个 token:... 

这个是可变形参的一种写法。 当传递参数的个数不确定时,就可以用这种方式来表示。 很显然,我们需要一种方法,来让函数体可以知道具体调用时参数的个数。

(char*)(&fmt) + 4) 表示的是...中的第一个参数的地址

i = vsprintf(buf, fmt, arg); 让我们来看看 vsprintf(buf, fmt, arg)是什么函数。 

vsprintf 返回的是要打印出来的字符串的长度

write,顾名思义:写操作,把 buf 中的 i 个元素的值写到终端。

所以说:vsprintf 的作用就是格式化。它接受确定输出格式的格式字符串 fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出。

接着就轮到 write 系统函数了,在 Linux 下,write 函数的第一个参数为 fd,也 就是描述符,而 1 代表的就是标准输出。查看 write 函数的汇编实现可以发现,它首先给寄存器传递了几个参数,然后执行 int INT_VECTOR_SYS_CALL,代表通过系统调用 syscall,syscall 将寄存器中的字节通过总线复制到显卡的显存中。显示芯片按照刷新频率逐行读取 vram,并通过信号线向液晶显示器传输每一个点

(RGB 分量)。由此 write 函数显示一个已格式化的字符串

8.4 getchar的实现分析

bb是缓冲区的开始,int变量n初始化为0,只有在n为0的情况下从缓冲区读入BUFSIZ个字节。返回时如果n大于0,那么返回缓冲区的第一个字符。否则返回EOF。

异步异常-键盘中断的处理:当用户按键时,键盘接口会得到一个代表该按键的键盘扫描码,同时产生一个中断请求。键盘中断服务程序先从键盘接口取得按键的扫描码,然后根据其扫描码判断用户所按的键并作相应的处理,最后通知中断控制器本次中断结束并实现中断返回。

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

8.5本章小结

I/O 时在主存和外部设备之间复制数据的过程。在 Linux 中,I/O 的实现是通过 Unix I/O 函数来执行的。Linux 把所有的 I/O 设备模型化为文件,并提供统一的

Unix I/O 接口,这使得所有的输入输出都能以一种统一且一致的方式来执行。

 

(第81分)

结论

(1)编写程序:源程序hello.c在编译器中完成。

(2)预处理:预处理器(cpp)将修改源程序,生成hello.i文件。

(3)编译:编译器(ccl)将hello.i文件翻译为汇编文件hello.s。

(4)汇编:汇编器(as)将hello.s文件翻译为二进制机器语言,生成可重定位目标文件hello.o。

(5)链接:链接器(ld)将可重定位目标文件hello.o和其他目标文件链接成为可执行文件hello。

(6)创建进程:shell进程调用fork函数为hello创建新进程,并调用execve函数运行hello。

(7)访问内存:通过MMU将需要访问的虚拟地址转化为物理地址,并通过缓存系统访问内存。

(8)动态申请内存:hello运行过程中可能会通过malloc函数动态申请堆中的内存。

(9)异常:hello运行过程中可能会产生各种异常和信号,系统会针对出现的异常和收到的信号做出反应。

(10)终止:hello运行结束后被父进程回收,内核删除相关数据。

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

附件

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

文件名称

文件作用

hello.c

Hello 源程序

hello.i

预处理生成的文件

hello.s

编译之后的汇编文件

hello.o

汇编之后的可重定位目标执行

hello.elf

hello.o 的 ELF 格式

hellooooo.s

hello.o 的反汇编代码文件

hello

hello 链接之后的可执行文件

hello 反汇编.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分)

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值