csapp大作业-程序人生

文章详细分析了Hello程序从编写到执行的全过程,涵盖预处理、编译、汇编、链接、进程管理和存储管理等多个阶段。在预处理阶段,源代码被转换为预处理文件;编译阶段,预处理文件被翻译成汇编代码;汇编阶段,汇编代码生成可重定位目标文件;链接阶段,目标文件与库函数链接生成可执行文件。此外,文章还讨论了进程的创建、执行、内存管理和IO操作,包括动态链接、异常处理、存储分配策略等。
摘要由CSDN通过智能技术生成

计算机系统

大作业

题     目  程序人生-Hello’s P2P 

专       业    网络空间安全                   

学     号    2021113454                   

班     级    2103901                   

学       生    唐思琪                 

指 导 教 师    吴锐                   

计算机科学与技术学院

2023年5月

摘  要

文章对hello程序的整个生命周期进行分析,将hello的人生分为7个阶段进行处理,对各个阶段进行分析和深度挖掘,并联系教材书本以求达到知行合一,熟练掌握。

关键词:预处理编译汇编;链接;进程;存储                           

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

1.通过编辑器编写hello.c文件,得到hello.c的源程序。

2.运行C预处理器(cpp)将hello.c进行预处理生成hello.i文件。

3.运行C编译器(ccl)将hello.i进行翻译生成汇编语言文件hello.s。

4.运行汇编器(as)将hello.s翻译成一个可重定位目标文件hello.o。

5.运行链接器ld将hello.o和系统目标文件组合起来,创建了一个可执行目标文件hello。(如图所示)

 

6.通过shell输入./hello,shell通过fork函数创建新的进程,然后调用了execve对虚拟内存进行映射,通过mmap为hello开辟一片空间。

7.中央处理器CPU从虚拟内存中的.text,.data截取代码和数据,调度器为进程规划时间片,在发生异常时触发异常处理子程序

1.2 环境与工具

硬件环境:Intel(R) Core(TM) i7-10875H CPU 2.30 GHz;16GRAM;1024Disk

软件环境:Windows11 64位;Vmware 16;Ubuntu 20.04 LTS 64位

工具:codeblocks;gdb;Objdump;HexEdito

1.3 中间结果

文件名称

功能

文件名称

功能

文件名称

功能

hello.c

源程序

hello.i

预处理后文件

hello.s

编译后的汇编文件

hello.o

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

hello

链接后的可执行文件

hello.elf

hello.o的ELF格式

hello1.txt

hello.o的反汇编代码

hello2.txt

Hello的反汇编代码

hello1.e;f

helloELF格式

1.4 本章小结

本章总体介绍了hello程序“一生”的过程,以及进行实验时的软硬件环境及开发与调试工具等基本信息。

(第1章0.5分)

第2章 预处理

2.1 预处理的概念与作用

1.   概念:预处理器根据以字符”#”开头的命令,处理hello.c源程序。

2.   作用:根据源代码中的预处理指令修改源代码,预处理从系统头文件中将头文件的源码插入到目标文件中,最终生成hello.i文件。

2.2在Ubuntu下预处理的命令

Linux中预处理hello.c文件的命令是:gcc -E -o hello.i hello.c

                  

 

 

2.3 Hello的预处理结果解析

经过预处理后,hello.c被处理成为hello.i文件。打开文件后发现hello.i文件中文件内容大大增加,且仍为可阅读的c语言程序文本文件。hello.i文件对hello.c程序中的宏进行了宏展开,该文件包含了头文件中的内容。如果代码中有#define命令还会对相应符号进行替换。

2.4 本章小结

本章介绍了预处理的相关概念和作用,进行实际操作查看了hello.i文件,是对源程序进行补充和替换

(第2章0.5分)

第3章 编译

3.1 编译的概念与作用

概念:编辑器将文本文件hello.i翻译成文本文件hello.s,即一个汇编语言程序。

作用:把源程序翻译成目标程序,进行词法分析和语法分析,分析过程中发现有语法错误,给出提示信息

注意:这儿的编译是指从 .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汇编初始部分

节名称        作用

.file           声明源文件

.text           代码节

.section.rodata   只读数据段

.globl          声明全局变量

.type           声明一个符号是函数类型还是数据类型

.size           声明大小

.string          声明一个字符串

.align           声明对指令或者数据的存放地址进行对齐的方式

3.3.2 数据

1)字符串

程序中有两个字符串,这两个字符串都在只读数据段中,如图所示:

 

hello.c中唯一的数组是作为main函数的第二个参数,数组的每个元素都是一个指向字符类型的指针。数组的起点存放在栈中-32(%rbp)的位置,被两次调用找参数传给printf函数。

 

如图所示,这两个字符串作为printf的参数。

2)局部变量

 

main函数声明了一个局部变量i,编译器进行编译的时候将局部变量i放入堆栈中。i被放置在栈中-4(%rbp)的位置,如下图所示。

3)参数argc

参数argc是作为用户传给main函数的参数。同样被放置到堆栈之中了。

4)数组char*argv[]

char*argv[]是main函数的第二个参数,数组的起始地址存放在-32(%rbp)的位置,数组中的每一个元素都是一个指向字符类型的指针,在内存中被两次调用穿给printf函数。

5)立即数

立即数直接体现在汇编代码中。

3.3.3全局函数

hello.c声明了一个函数int main(int argc,char *argv[]),通过阅读汇编代码我们发现此函数是一个全局函数,如图所示。

 

3.3.4赋值操

hello中的赋值操作主要有:i=0,而这个操作在.s文件中汇编代码主要使用mov指令来实现。mov指令根据操作数的字节大小分为:

movb:一个字节赋值,movw:两个字节赋值,

movl:四个字节赋值,movq:八个字节赋值。

3.3.5算术操作

hello.c中的算数操作主要有i++,i是int类型,在汇编代码中用addl实现此操作。

 

3.3.6关系操作

1)hello.c中 “if(argc!=4)”,这是一个条件判断语句,在进行编译时,被编译为:cmpl$4, -20(%rbp)。比较后设置条件码,根据条件码判断是否需要跳转。

 

2)hello.c源程序中的for循环条件是for(i=0;i<8;i++),该条指令被编译为cmpl$7,-4(%rbp)。同样在判断后设置条件码,为下一步的jle利用条件码跳转做准备。   

 

3.3.7控制转移指令

汇编语言中设置了条件码,然后根据条件码来进行控制程序的跳转。通过阅读hello.c 的汇编代码,我们发现有如下控制转移指令。

1)判断argc是否等于4。如果等于4,则不执行if语句;反之if不等于4,则执行后续的语句,对应的汇编代码为:

 

  1. for循环中,每次都要判断i是否小于8来决定是否继续循环。先对i进行赋初值,然后无条件跳转至判断条件的.L3中,然后判断i是否符合循环的条件,若符合则直接跳转到.L4中。这一部分的汇编代码为:

 

3.3.8函数操作

hello.c中涉及的函数操作主要有以下几个:main,printf,exit,sleep,和getchar函数。main函数的参数是argc和*argv,printf函数的参数是字符串,exit函数的参数是1,sleep函数的参数是atoi(argv[3])。所有函数的返回值都会存储在%eax寄存器中。函数的调用与传参的过程是给函数传递参数需要先设定一个寄存器,将参数传给这个设定的寄存器后,再通过call来跳转到调用的函数开头的地址。

3.3.9类型转换

hello.c中的atoi(argv[3])将字符串类型转换为整形。int、float、double、short、char之间可以进行相互转化。

3.4 本章小结

本章主要介绍了编译器处理c语言程序的基本过程,编译器分别从c语言的数据,赋值语句,类型转换,算术操作,逻辑/位操作,关系操作,控制转移与函数操作这几点进行分析,hello程序从高级的c语言变成了低阶的汇编语言

(第32分)

第4章 汇编

4.1 汇编的概念与作用

1.概念:汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程,汇编器(as)将.s汇编程序翻译成机器语言并将这些指令打包成可重定目标程序的格式存放在.o目标文件中,.o 文件是一个二进制文件,它包含程序的指令编码。

2.作用:将汇编语言翻译成机器语言,使其在链接后能够被机器识别并执行。

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

4.2 在Ubuntu下汇编的命令

gcc -c -o hello.o hello.s

 

4.3 可重定位目标elf格式

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

典型的ELF可重定位目标文件

在linux下生成hello.o文件的elf格式命令:readelf -a hello.o > hello.elf

分析hello.elf中的内容:

ELF 头:

  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00

  类别:                              ELF64

  数据:                              2 补码,小端序 (little endian)

  Version:                           1 (current)

  OS/ABI:                            UNIX - System V

  ABI 版本:                          0

  类型:                              REL (可重定位文件)

  系统架构:                          Advanced Micro Devices X86-64

  版本:                              0x1

  入口点地址:               0x0

  程序头起点:          0 (bytes into file)

  Start of section headers:          1232 (bytes into file)

  标志:             0x0

  Size of this header:               64 (bytes)

  Size of program headers:           0 (bytes)

  Number of program headers:         0

  Size of section headers:           64 (bytes)

  Number of section headers:         14

  Section header string table index: 13

节头:

  [号] 名称              类型             地址              偏移量

       大小              全体大小          旗标   链接   信息   对齐

  [ 0]                   NULL             0000000000000000  00000000

       0000000000000000  0000000000000000           0     0     0

  [ 1] .text             PROGBITS         0000000000000000  00000040

       0000000000000092  0000000000000000  AX       0     0     1

  [ 2] .rela.text        RELA             0000000000000000  00000380

       00000000000000c0  0000000000000018   I      11     1     8

  [ 3] .data             PROGBITS         0000000000000000  000000d2

       0000000000000000  0000000000000000  WA       0     0     1

  [ 4] .bss              NOBITS           0000000000000000  000000d2

       0000000000000000  0000000000000000  WA       0     0     1

  [ 5] .rodata           PROGBITS         0000000000000000  000000d8

       0000000000000033  0000000000000000   A       0     0     8

  [ 6] .comment          PROGBITS         0000000000000000  0000010b

       0000000000000024  0000000000000001  MS       0     0     1

  [ 7] .note.GNU-stack   PROGBITS         0000000000000000  0000012f

       0000000000000000  0000000000000000           0     0     1

  [ 8] .note.gnu.propert NOTE             0000000000000000  00000130

       0000000000000020  0000000000000000   A       0     0     8

  [ 9] .eh_frame         PROGBITS         0000000000000000  00000150

       0000000000000038  0000000000000000   A       0     0     8

  [10] .rela.eh_frame    RELA             0000000000000000  00000440

       0000000000000018  0000000000000018   I      11     9     8

  [11] .symtab           SYMTAB           0000000000000000  00000188

       00000000000001b0  0000000000000018          12    10     8

  [12] .strtab           STRTAB           0000000000000000  00000338

       0000000000000048  0000000000000000           0     0     1

  [13] .shstrtab         STRTAB           0000000000000000  00000458

       0000000000000074  0000000000000000           0     0     1

Key to Flags:

  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),

  L (link order), O (extra OS processing required), G (group), T (TLS),

  C (compressed), x (unknown), o (OS specific), E (exclude),

  l (large), p (processor specific)

There are no section groups in this file.

本文件中没有程序头。

There is no dynamic section in this file.

重定位节 '.rela.text' at offset 0x380 contains 8 entries:

  偏移量          信息           类型           符号值        符号名称 + 加数

00000000001c  000500000002 R_X86_64_PC32     0000000000000000 .rodata - 4

000000000021  000c00000004 R_X86_64_PLT32    0000000000000000 puts - 4

00000000002b  000d00000004 R_X86_64_PLT32    0000000000000000 exit - 4

000000000054  000500000002 R_X86_64_PC32     0000000000000000 .rodata + 22

00000000005e  000e00000004 R_X86_64_PLT32    0000000000000000 printf - 4

000000000071  000f00000004 R_X86_64_PLT32    0000000000000000 atoi - 4

000000000078  001000000004 R_X86_64_PLT32    0000000000000000 sleep - 4

000000000087  001100000004 R_X86_64_PLT32    0000000000000000 getchar - 4

重定位节 '.rela.eh_frame' at offset 0x440 contains 1 entry:

  偏移量          信息           类型           符号值        符号名称 + 加数

000000000020  000200000002 R_X86_64_PC32     0000000000000000 .text + 0

The decoding of unwind sections for machine type Advanced Micro Devices X86-64 is not currently supported.

Symbol table '.symtab' contains 18 entries:

   Num:    Value          Size Type    Bind   Vis      Ndx Name

     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND

     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS hello.c

     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1

     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3

     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4

     5: 0000000000000000     0 SECTION LOCAL  DEFAULT    5

     6: 0000000000000000     0 SECTION LOCAL  DEFAULT    7

     7: 0000000000000000     0 SECTION LOCAL  DEFAULT    8

     8: 0000000000000000     0 SECTION LOCAL  DEFAULT    9

     9: 0000000000000000     0 SECTION LOCAL  DEFAULT    6

    10: 0000000000000000   146 FUNC    GLOBAL DEFAULT    1 main

    11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _GLOBAL_OFFSET_TABLE_

    12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND puts

    13: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND exit

    14: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND printf

    15: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND atoi

    16: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND sleep

    17: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND getchar

No version information found in this file.

Displaying notes found in: .note.gnu.property

  所有者            Data size Description

  GNU                  0x00000010  NT_GNU_PROPERTY_TYPE_0

      Properties: x86 feature: IBT, SHSTK

  1. ELF头 :ELF头(ELF header)以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。ELF头剩下的部分包含了帮助链接器语法分析和解释目标文件的信息,其中包括ELF头的大小、目标文件的类型(如可重定位、可执行或者共享的)、机器类型(如x86-64)、节头部表(section header table)的文件偏移,以及节头部表中条目的大小和数量。不同节的位置和大小是有节头部表描述的,其中目标文件中每个节都有一个固定大小的条目(entry)。

2)节头:记录各节名称、类型、地址、偏移量、大小、全体大小、旗标、链接、信息、对齐。

 3)重定位节:重定位节保存的是.text节中需要被修正的信息(任何调用外部函数或者引用全局变量的指令都需要被修正),调用外部函数的指令和引用全局变量的指令需要重定位,调用局部函数的指令不需要重定位。Hello程序中需要被重定位的有printf、puts、exit、sleep、sleepseces、getchar和.rodata中的.L0和.L1。

.rela.eh_frame节是.eh_frame节的重定位信息。

  1. 符号表:.symtab,一个符号表,它存放在程序中定义和引用的函数和全局变量的信息,一些程序员错误地认为必须通过-g选项来编译一个程序,才能得到符号表信息。实际上每个可重定位目标文件在.symtab中都有一张符号表(除非程序员特意用STRIP命令去掉它)。然而,和编译器中的符号表不同,.symtab符号表不包含局部变量的条目。

4.4 Hello.o的结果解析

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

命令:objdump -d -r hello.o>hello1.txt

 

结果解析:与hello.s的差异

1)分支转移

hello.s

 

hello1.txt

 

反汇编代码跳转指令的操作数使用的不是段名称,因为段名称只是在汇编语言中便于编写的助记符,所以在汇编成机器语言之后显然不存在,而是确定的地址。

2)对函数的调用与重定位条目对应

hello.s

 

hello1.txt

 

在可重定位文件中call后面不再是函数的具体名称,而是一条重定位条目指引的信息。而在反汇编文件中可以看到,call后面直接加的是偏移量。

3)立即数变为十六进制格式

hello.s

 

hello1.txt

 

    在编译文件中,立即数全部是以16进制表示的,因为16进制与2进制之间的转换比十进制更加方便,所以都转换成了16进制。

4.5 本章小结

本章对应的主要是hello.s汇编到hello.o的过程。在本章中,我们查看了hello.o的可重定位目标文件的格式,使用反汇编查看hello.o经过反汇编过程生成的代码并且把它与hello.s进行比较,分析和阐述了从汇编语言进一步翻译成为机器语言的汇编过程。

(第41分)

第5章 链接

5.1 链接的概念与作用

概念:链接是将各种代码和数据片段收集并合成为一个单一文件的过程,这个文件可被加载(复制)到内存并执行。链接可以执行于编译时,也就是在源代码被翻译成机器代码时;也可以执行于加载时,也就是在程序被加载器加载到内存并执行时;甚至执行于运行时,也就是由应用程序来执行。

作用:链接器在软件开发过程中扮演着一个关键的角色,因为它们使得分离编译成为可能。我们不用将一个大型的应用程序组织为一个巨大的源文件,而是可以把它分解为更小、更好管理的模块,可以独立地修改和编译这些模块。当我们改变这些模块中的一个时,只需简单地重新编译它,并重新链接应用,而不必重新编译其它文件。

5.2 在Ubuntu下链接的命令

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

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 > hello1.elf

 

1)ELF头:hello的文件头和hello.o文件头的不同之处如下图标记所示,hello是一个可执行目标文件,有27个节。

2)节头:对 hello中所有的节信息进行了声明,包括大小和偏移量。

3) 重定位节.rela.text:

 4)符号表.symtab:

5.4 hello的虚拟地址空间

分析程序头LOAD可加载的程序段的地址为0x400000

 

通过edb加载hello程序,打开Data Dump查看hello加载到虚拟地址的状况,并查看各段信息:

 

在0x401000~0x402000段中,程序被载入,虚拟地址0x401000开始,到0x401ff0结束,根据5.3中的节头部表,可以通过edb找到各个节的信息,比如.txt节,虚拟地址开始于0x4010f0,大小为0x145.

5.5 链接的重定位过程分析

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

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

命令: objdump -d -r hello > hello2.txt

与hello.o的反汇编文件对比发现,hello2.txt中多了许多节。hello1.txt中只有一个.text节,而且只有一个main函数,函数地址也是默认的0x000000。hello2.txt中有.init,.plt,.text三个节,而且每个节中有很多函数。库函数的代码都已经链接到了程序中,程序各个节变的更加完整,跳转的地址也具有参考性。

 

hello的重定位过程:

1)重定位节和符号定义链接器将所有类型相同的节合并在一起后,这个节就作为可执行目标文件的节。然后链接器把运行时的内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号,当这一步完成时,程序中每条指令和全局变量都有唯一运行时的地址。

2)重定位节中的符号引用这一步中,连接器修改代码节和数据节中对每个符号的引用,使他们指向正确的运行时地址。

3)重定位条目当编译器遇到对最终位置未知的目标引用时,它会生成一个重定位条目。代码的重定位条目放在.rel.txt中。

5.6 hello的执行流程

①开始执行:_start、_libc_start_main

②执行main:_main、_printf、_exit、_sleep、_getchar

③退出:exit

程序名                      程序地址

_start                         0x4010f0

_libc_start_main       0x2f12271d

main                          0x401125

_printf                       0x401040

_exit                          0x401070

_sleep                         0x401080

_getchar                    0x401050

5.7 Hello的动态链接分析

共享库:共享库是一个目标模块,在加载或运行时,可以加载到任意的内存地址,并和一个在内存中的程序链接起来。这个过程被称为动态链接,是由一个叫做动态链接器的程序来执行的。共享库也称为共享目标。

动态链接的基本思想:先把程序按照模块拆分成各个相对独立的部分,在程序运行时将这些相对独立的部分链接在一起形成一个完整的程序。

动态延迟绑定:动态的链接器在正常工作的时候链接器采取了延迟绑定的连接器策略,由于静态的编译器本身无法准确的预测变量和函数的绝对运行时的地址,动态的连接器需要等待编译器在程序开始加载时在对编译器进行延迟解析,这样的延迟绑定策略叫做动态延迟绑定。

延迟绑定是通过got和plt实现的。Got是数据段的一部分,而plt是代码段的一部分。

 

.got.plt起始表的位置为0x404000。

GOT表调用dl_init前0x404008后的16个字节均为0:

调用dl_init后的.got.plt:

 

 

从图中可以看到.got.plt的条目已经发生变化。

5.8 本章小结

本章研究了链接的过程。通过edb查看hello的虚拟地址空间,对比hello与hello.o的反汇编代码,深入研究了链接的过程中重定位的过程。

(第51分)

第6章 hello进程管理

6.1 进程的概念与作用

(1)概念:进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

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

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

1)Shell-bash的作用:Linux系统中,Shell是一个交互型应用级程序,为使用者提供操作界面,接收用户命令,然后调用相应的应用程序。

(2)Shell-bash的处理流程:

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

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

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

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

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

6.3 Hello的fork进程创建过程

在终端中输入./hello 学号 姓名 1命令后,shell会处理该命令,判断出不是内置命令,则会调用fork函数创建一个新的子进程,子进程几乎但不完全与父进程相同。通过fork函数,子进程得到与父进程用户级虚拟地址空间相同的但是虚拟地址独立、PID也不相同的一份副本。

6.4 Hello的execve过程

当创建了一个子进程之后,子进程调用exceve函数在当前子进程的上下文加载并运行一个新的程序,即hello程序,需要以下步骤:

①删除已存在的用户区域。删除之前进程在用户部分中已存在的结构。

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

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

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

6.5 Hello的进程执行

6.5.1 逻辑控制流和时间片:

进程的运行本质上是CPU不断从程序计数器 PC 指示的地址处取出指令并执行,值的序列叫做逻辑控制流。操作系统会对进程的运行进行调度,执行进程A->上下文切换->执行进程B->上下文切换->执行进程A->… 如此循环往复。 在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程,这种决策就叫做调度,是由内核中称为调度器的代码处理的。当内核选择一个新的进程运行,我们说内核调度了这个进程。在内核调度了一个新的进程运行了之后,它就抢占了当前进程,并使用上下文切换机制来将控制转移到新的进程。在一个程序被调运行开始到被另一个进程打断,中间的时间就是运行的时间片。

   6.5.2用户模式和内核模式:

   Shell使得用户可以有机会修改内核,所以需要设置一些防护措施来保护内核,如限制指令的类型和可以作用的范围。

   6.5.3上下文切换

   如果系统调用因为等待某个事件发生而阻塞,那么内核可以让当前进程休眠,切换到另一个进程,上下文就是内核重新启动一个被抢占的进程所需要的状态,是一种比较高层次的异常控制流。

   6.5.4调度

   在对进程进行调度的过程,操作系统主要做了两件事:加载保存的寄存器,切换虚拟地址空间。

   6.5.5用户态与核心态转换

   为了能让处理器安全运行,需要限制应用程序可执行指令所能访问的地址范围。因此划分了用户态与核心态。

   核心态可以说是拥有最高的访问权限,处理器以一个寄存器当做模式位来描述当前进程的特权。进程只有故障、中断或陷入系统调用时才会得到内核访问权限,其他情况下始终处于用户权限之中,保证了系统的安全性。

6.6 hello的异常与信号处理

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

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

6.6.1hello的正常运行状态

 

6.6.2按下CTRL-Z

 

输入ctrl-z默认结果是挂起前台的作业,hello进程并没有回收,而是运行在后台下,用ps命令可以看到,hello进程并没有被回收。此时他的后台 job 号是 1,调用 fg 1 将其调到前台,此时 shell 程序首先打印 hello 的命令行命令, hello 继续运行打印剩下的 8 条 info,之后输入字串,程序结束,同时进程被回收,如下图:

 

6.6.3按下CTRL-C

 

在键盘上输入Ctrl+c会导致内核发送一个SIGINT信号到前台进程组的每个进程,默认情况是终止前台作业,用ps查看前台进程组发现没有hello进程,如图所示。

6.6.4不停乱按

 

无关输入被缓存到stdin,并随着printf指令被输出到结果。

6.7本章小结

本章了解了hello进程的执行过程。在hello运行过程中,内核对其调度,异常处理程序为其将处理各种异常。每种信号都有不同的处理机制,对不同的shell命令,hello也有不同的响应结果。

(第61分)

第7章 hello的存储管理

7.1 hello的存储器地址空间

1)逻辑地址:程序经过编译后出现在汇编代码中的地址。逻辑地址用来指定一个操作数或者是一条指令的地址。是由一个段标识符加上一个指定段内相对地址的偏移量,表示为 段标识符:段内偏移量。

2)线性地址:逻辑地址向物理地址转化过程中的一步,逻辑地址经过段机制后转化为线性地址,为描述符:偏移量的组合形式,分页机制中线性地址作为输入。

3)虚拟地址:就是线性地址。

4)物理地址:CPU通过地址总线的寻址,找到真实的物理内存对应地址。CPU对内存的访问是通过连接着CPU和北桥芯片的前端总线来完成的。在前端总线上传输的内存地址都是物理内存地址。

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

在 Intel 平台下,逻辑地址(logical address)是 selector:offset 这种形式,selector 是 CS 寄存器的值,offset 是 EIP 寄存器的值。如果用 selector 去 GDT( 全局描述符表 ) 里拿到 segment base address(段基址) 然后加上 offset(段内偏移),这就得到了 linear address。我们把这个过程称作段式内存管理。

    一个逻辑地址由段标识符和段内偏移量组成。段标识符是一个16位长的字段(段选择符)。可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段。

    全局的段描述符,放在“全局段描述符表(GDT)”中,一些局部的段描述符,放在“局部段描述符表(LDT)”中。

    给定一个完整的逻辑地址段选择符+段内偏移地址,看段选择符的T1=0还是1,知道当前要转换是GDT中的段,还是LDT中的段,再根据相应寄存器,得到其地址和大小。拿出段选择符中前13位,可以在这个数组中,查找到对应的段描述符,就得到了其基地址。Base + offset = 线性地址。

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

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

系统将虚拟页作为进行数据传输的单元。Linux下每个虚拟页大小为4KB。物理内存也被分割为物理页, MMU(内存管理单元)负责地址翻译,MMU使用页表将虚拟页到物理页的映射,即虚拟地址到物理地址的映射。

n位的虚拟地址包含两个部分:一个p位的虚拟页面偏移(VPO),一个n-p位的虚拟页号(VPN),MMU利用VPN选择适当的PTE,根据PTE,我们知道虚拟页的信息,如果虚拟页是已缓存的,那直接将页表条目的物理页号和虚拟地址的VPO串联起来就得到一个相应的物理地址。VPO和PPO是相同的。如果虚拟页是未缓存的,会触发一个缺页故障。调用一个缺页处理子程序将磁盘的虚拟页重新加载到内存中,然后再执行这个导致缺页的指令。

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

7.4.1 翻译后备缓冲器

   每次CPU产生一个虚拟地址,MMU(内存管理单元)就必须查阅一个PTE(页表条目),以便将虚拟地址翻译为物理地址。在最糟糕的情况下,这会从内存多取一次数据,代价是几十到几百个周期。如果PTE碰巧缓存在L1中,那么开销就会下降1或2个周期。然而,许多系统都试图消除即使是这样的开销,它们在MMU中包括了一个关于PTE的小的缓存,称为翻译后备缓存器(TLB)。

    7.4.2 多级页表:

将虚拟地址的VPN划分为相等大小的不同的部分,每个部分用于寻找由上一级确定的页表基址对应的页表条目。

    7.4.3 VA到PA的变换

处理器生成一个虚拟地址,并将其传送给MMU。MMU用VPN向TLB请求对应的PTE,如果命中,则跳过之后的几步。MMU生成PTE地址(PTEA).,并从高速缓存/主存请求得到PTE。如果请求不成功,MMU向主存请求PTE,高速缓存/主存向MMU返回PTE。PTE的有效位为零, 因此 MMU触发缺页异常,缺页处理程序确定物理内存中的牺牲页 (若页面被修改,则换出到磁盘——写回策略)。缺页处理程序调入新的页面,并更新内存中的PTE。缺页处理程序返回到原来进程,再次执行导致缺页的指令。在多级页表的情况下,无非就是不断通过索引 – 地址 – 索引 - 地址重复四次进行寻找。

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

获得物理地址之后,先取出组索引对应位,在L1中寻找对应组。如果存在,则比较标志位,相等后检查有效位是否为1.如果都满足则命中取出值传给CPU,否则按顺序对L2cache、L3cache、内存进行相同操作,直到出现命中。然后再一级一级向上传,如果有空闲块则将目标块放置到空闲块中,否则将缓存中的某个块驱逐,将目标块放到被驱逐块的位置。

7.6 hello进程fork时的内存映射

在shell输入命令行后,内核调用fork创建子进程,为hello程序的运行创建上下文,并分配一个与父进程不同的PID。同时为这个新进程创建虚拟内存,创建当前进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记位只读,并将两个进程中的每个区域结构都标记为私有的写时复制。

当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面。

7.7 hello进程execve时的内存映射

execve 函数调用驻留在内核区域的启动加载器代码,在当前进程中加载并运 行包含在可执行目标文件 hello 中的程序,用 hello 程序有效地替代了当前程序。 它可能会自动覆盖当前进程中的所有虚拟地址和空间,删除当前进程虚拟地址的所有用户虚拟和部分空间中的已存在的代码共享区域和结构,但它不会自动创建一个新的代码共享进程。

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

  1. 删除当前进程虚拟地址中已存在的用户区域
  2. 映射私有区域,为新程序的代码、数据、bss和栈创建新的区域结构。
  3. 映射共享区域,将hello与libc.so动态链接,然后再映射到虚拟地址空间中的共享区域。

    4. 设置当前进程上下文程序计数器(PC),使之指向代码区域的入口点。

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

页面命中完全是由硬件完成的,而处理缺页是由硬件和操作系统内核协作完成的,在指令请求一个虚拟地址时,MMU中查找页表,如果这时对应得物理地址没有存在主存的内部,我们必须要从磁盘中读出数据。在虚拟内存的习惯说法中,DRAM缓存不命中成为缺页。在发生缺页后系统会调用内核中的一个缺页处理程序,选择一个页面作为牺牲页面。具体流程如下

  1. 处理器生成一个虚拟地址,并将它传送给MMU
  2. MMU生成PTE地址,并从高速缓存/主存请求得到它
  3. 高速缓存/主存向MMU返回PTE
  4. PTE中的有效位是0,所以MMU出发了一次异常,传递CPU中的控制到操作系统内核中的缺页异常处理程序。
  5. 缺页处理程序确认出物理内存中的牺牲页,如果这个页已经被修改了,则把它换到磁盘。
  6. 缺页处理程序页面调入新的页面,并更新内存中的PTE。
  7. 缺页处理程序返回到原来的进程,再次执行导致缺页的命令。CPU将引起缺页的虚拟地址重新发送给MMU。因为虚拟页面已经换存在物理内存中,所以就会命中。

7.9动态存储分配管理

动态内存管理的基本方法与策略:

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

分配器将堆视为一组不同大小的块的集合来维护。每个块就是一个个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显示地保留为供应用程序使用。空闲块可用来分配。空闲块保持空闲,直到它显示地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显示执行的。要么是内存分配器自身隐式执行的。分配器有两种基本风格。两种风格都要求应用显示地分配块。他们的不同之处在于由哪个实体来负责释放已分配的块。

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

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

①隐式空闲链表的堆块格式:

 

②隐式空闲链表的带边界标记的堆块格式:

 

使用边界标记的堆块的格式其中头部和脚部分别存放了当前内存块的大小与是否已分配的信息。通过这种结构,隐式动态内存分配器会对堆进行扫描,通过头部和脚部的结构实现查找。

使用双向链表而不是隐式空闲链表,使首次适配的分配时间从块总数的线性时间减少到了空闲块数量的线性时间。维护链表的顺序有:后进先出(LIFO),将新释放的块放置在链表的开始处,使用LIFO的顺序和首次适配的放置策略,分配器会最先检查最近使用过的块,在这种情况下,释放一个块可以在线性的时间内完成,如果使用了边界标记,那么合并也可以在常数时间内完成。按照地址顺序来维护链表,其中链表中的每个块的地址都小于它的后继的地址,在这种情况下,释放一个块需要线性时间的搜索来定位合适的前驱。平衡点在于,按照地址排序首次适配比LIFO排序的首次适配有着更高的内存利用率,接近最佳适配的利用率。

7.10本章小结

本章介绍了存储器地址空间、段式管理、页式管理,VA 到 PA 的变换、物理内存访问, hello 进程fork时和execve 时的内存映射、缺页故障与缺页中断处理、包括隐式空闲链表和显式空闲链表的动态存储分配管理。

(第7 2分)

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

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

设备的模型化:文件

设备管理:unix io接口

8.2 简述Unix IO接口及其函数

Unix IO接口:

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

2.Linux Shell创建的每个进程都有三个打开的文件:标准输入(描述符为0),标准输出(描述符为1),标准错误(描述符为2)。

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

4.读写文件。一个读操作就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增加到k+n,给定一个大小为m字节的而文件,当k>=m时,触发EOF。类似一个写操作就是从内存中复制n>0个字节到一个文件,从当前文件位置k开始,然后更新k。

5.关闭文件。当应用完成了对文件的访问之后,它就通知内核关闭这个文件,作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。无论一个进程因为何种原因终止时,内核都会关闭所有打开的文件并释放他们的内存资源。

Unix I/O函数:

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

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

2.int close(fd)

进程通过调用close函数关闭一个打开的文件,fd是需要关闭的文件的描述符。

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

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

4.    ssize_t wirte(int fd,const void *buf,size_t n)

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

8.3 printf的实现分析

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

 

 

 

 

vsprintf函数将所有的参数内容格式化之后存入buf,返回格式化数组的长度。write函数将buf中的i个元素写到终端。从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。

8.4 getchar的实现分析

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

当用户键入回车之后,getchar才开始从stdio流中每次读入一个字符。getchar函数的返回值是用户输入的第一个字符的ascii码,如出错返回-1,且将用户输入的字符回显到屏幕。如用户在按回车之前输入了不止一个字符,其他字符会保留在键盘缓存区中,等待后续getchar调用读取。也就是说,后续的getchar调用不会等待用户按键,而直接读取缓冲区中的字符,直到缓冲区中的字符读完为后,才等待用户按键。

异步异常-键盘中断的处理:当用户按键时触发键盘终端,操作系统将控制转移到键盘中断处理子程序,中断处理程序执行,接受按键扫描码转成ascii码,保存到系统的键盘缓冲区,显示在用户输入的终端内。当中断处理程序执行完毕后,返回到下一条指令运行。

8.5本章小结

本章介绍了Linux的IO设备管理方法,Unix IO接口及其函数,分析了printf函数和getchar函数的实现。

(第81分)

结论

用计算机系统的语言,总结hello的一生:

   

预处理

hello.c预处理到hello.i文本文件

编译

hello.i编译到hello.s汇编文件

汇编

hello.s汇编到二进制可重定位目标文件hello.o

链接

hello.o链接生成可执行文件hello

创建子进程

bash进程调用fork函数,生成子进程

加载程序

execve函数加载运行当前进程的上下文中加载并运行新程序hello

访问内存

hello的运行需要地址的概念,虚拟地址是计算机系统最伟大的抽象

交互

hello的输入输出与外界交互,与linux I/O息息相关

终止

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

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

附件

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

hello.c                  源程序

hello.i                   预处理后文件

hello.s                  编译后的汇编文件

hello.o                  汇编后的可重定位目标执行文件

hello                     链接后的可执行文件

hello.elf                 hello.o的ELF格式

hello1.txt               hello.o的反汇编

hello2.txt               hello的反汇编代码

hello1.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.

[7]  深入理解计算机系统原书第3版-文字版.pdf

[8]  https://www.cnblogs.com/diaohaiwei/p/5094959.html 内存管理

[9]  https://www.cnblogs.com/pianist/p/3315801.html printf函数的实现分析

[10]  https://blog.csdn.net/drshenlei/article/details/4261909 内存地址的转换

[11]  https://blog.csdn.net/yueyansheng2/article/details/78860040 fork创建子进程

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值