2022计算机系统大作业————hit

-/*******************************************************************************************************************************+++++++++++++/

计算机系统

大作业

题     目  程序人生-Hello’s P2P 

专       业                        计算机

学     号                        120L021415

班     级                        2003001

学       生                     申吴鑫

指 导 教 师                       史先俊

计算机科学与技术学院

2022年5月

摘  要

本文通过研究hello.c在linux中的各类操作,从预处理开始,经过编译、汇编、链接等一系列过程,了解其在不同操作后的变化,同时了解了linux环境下计算机内进程管理、存储管理以及IO管理的方式,全方位多层次得了解了计算机的工作模式与工作结构。

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

目  录

第1章 概述............................................................................................................. - 4 -

1.1 Hello简介...................................................................................................... - 4 -

1.2 环境与工具..................................................................................................... - 4 -

1.3 中间结果......................................................................................................... - 4 -

1.4 本章小结......................................................................................................... - 4 -

第2章 预处理......................................................................................................... - 5 -

2.1 预处理的概念与作用..................................................................................... - 5 -

2.2在Ubuntu下预处理的命令.......................................................................... - 5 -

2.3 Hello的预处理结果解析.............................................................................. - 5 -

2.4 本章小结......................................................................................................... - 5 -

第3章 编译............................................................................................................. - 6 -

3.1 编译的概念与作用......................................................................................... - 6 -

3.2 在Ubuntu下编译的命令.............................................................................. - 6 -

3.3 Hello的编译结果解析.................................................................................. - 6 -

3.4 本章小结......................................................................................................... - 6 -

第4章 汇编............................................................................................................. - 7 -

4.1 汇编的概念与作用......................................................................................... - 7 -

4.2 在Ubuntu下汇编的命令.............................................................................. - 7 -

4.3 可重定位目标elf格式.................................................................................. - 7 -

4.4 Hello.o的结果解析....................................................................................... - 7 -

4.5 本章小结......................................................................................................... - 7 -

第5章 链接............................................................................................................. - 8 -

5.1 链接的概念与作用......................................................................................... - 8 -

5.2 在Ubuntu下链接的命令.............................................................................. - 8 -

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

5.4 hello的虚拟地址空间................................................................................... - 8 -

5.5 链接的重定位过程分析................................................................................. - 8 -

5.6 hello的执行流程........................................................................................... - 8 -

5.7 Hello的动态链接分析.................................................................................. - 8 -

5.8 本章小结......................................................................................................... - 9 -

第6章 hello进程管理.................................................................................... - 10 -

6.1 进程的概念与作用....................................................................................... - 10 -

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

6.3 Hello的fork进程创建过程...................................................................... - 10 -

6.4 Hello的execve过程.................................................................................. - 10 -

6.5 Hello的进程执行........................................................................................ - 10 -

6.6 hello的异常与信号处理............................................................................. - 10 -

6.7本章小结........................................................................................................ - 10 -

第7章 hello的存储管理................................................................................ - 11 -

7.1 hello的存储器地址空间............................................................................. - 11 -

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

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

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

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

7.6 hello进程fork时的内存映射................................................................... - 11 -

7.7 hello进程execve时的内存映射............................................................... - 11 -

7.8 缺页故障与缺页中断处理........................................................................... - 11 -

7.9动态存储分配管理........................................................................................ - 11 -

7.10本章小结...................................................................................................... - 12 -

第8章 hello的IO管理.................................................................................. - 13 -

8.1 Linux的IO设备管理方法........................................................................... - 13 -

8.2 简述Unix IO接口及其函数........................................................................ - 13 -

8.3 printf的实现分析........................................................................................ - 13 -

8.4 getchar的实现分析.................................................................................... - 13 -

8.5本章小结........................................................................................................ - 13 -

结论......................................................................................................................... - 14 -

附件......................................................................................................................... - 15 -

参考文献................................................................................................................. - 16 -

第1章 概述

1.1 Hello简介

       hello程序的生命周期是从一个高级C语言程序开始的,因为这种形式能够被人读懂。然而,为了在系统上运行hello.c程序,每条C语句都必须被其他程序转化为一系列的低级 机器语言指令。然后这些指令按照一种称为“可执行目标程序”的格式打好包,并以二进制磁盘文件的形式存放起来。目标程序也称为 可执行目标文件。

       1)P2P:即Program to Progress,Hello.c在一开始只是C语言形式的文本文件,再经过CPP预处理器处理过后生成hello.i(修改了的源程序,文本),然后经过ccl编译器生成hello.s(汇编程序,文本),接着经过汇编器(as)生成hello.o(可重定位目标程序,二进制),最终经过链接器(ld)生成hello(可执行目标程序,二进制)。

 

2)020:From Zero to Zero,刚开始内存中是没有程序信息的,程序需要加载到内存中才能运行,运行可执行文件时, hello会被分配一个虚拟内存,对应着一个物理内存,里面存放着程序需要运行的信息。之后便可以执行程序,CPU和系统通过上下文切换,进程管理,处理所有的汇编指令。最终hello程序返回,会被shell回收,对应内存和虚拟内存随即释放,结束运行。

1.2 环境与工具

硬件环境:X64 CPU;2GHz;4GRAM.

软件环境:Windows10 64位;Vmware 16.0;Ubuntu 20.04.

使用工具:Codeblocks; Objdump; edb-debugger; Hexedit.

1.3 中间结果

(1)hello.c:给定的C语言源文件。

(2)hello.i:.C文件预处理生成的预处理文件。

(3)hello.s:汇编代码文件。

(4)hello.o:可重定位目标文件,属于ELF格式,可查看ELF信息结构。

(5)hello:可执行文件,属于ELF文件格式,可查看ELF信息结构。

1.4 本章小结

       计算机系统是由硬件和系统软件组成的,它们共同协作以运行应用程序。计算机内部的信息被表示为一组组的位,它们依据上下文有不同的解释方式。程序被其他程序翻译成不同的形式,开始时是ASCII文本,然后被编译器和链接器翻译成二进制可执行文件。

第2章 预处理

2.1 预处理的概念与作用

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

1)概念

预处理过程是整个编译过程的第一步。以C语言为例,预处理的输入文件是c文件和h文件,输出文件是i文件。

 

在预处理过程中,预处理器(Preprocessor)会分析预处理指令(包括#include头文件和#define宏定义等)以及去除源代码中的注释。

通常来说,编译器会直接将c文件编译成o文件,然后再去交给链接器链接为elf文件。如果希望通过命令行仅仅将c文件预处理成为i文件,只要在命令行中加上-E参数,就可以使编译器在预处理后停止下来,输出预处理后的源文件。

2)作用

1. 预处理功能是C语言特有的功能,它是在对源程序正式编译前由预处理程序完成的。程序员在程序中用预处理命令来调用这些功能。

2. 文件包含是预处理的一个重要功能,它可用来把多个源文件连接成一个源文件进行编译,结果将生成一个目标文件。

3. 条件编译允许只编译源程序中满足条件的程序段,使生成的目标程序较短,从而减少了内存的开销并提高了程序的效率。

4. 使用预处理功能便于程序的修改、阅读、移植和调试,也便于实现模块化程序设计。

2.2在Ubuntu下预处理的命令

 

相比于一开始的hello.c文件,hello.i文件的长度是其数百倍,原因在于预处理过后的文本文件会对源文件中的各类声明进行替换,具体原则如下:

一.预定义符号

主要有:

__FILE__ //表示进行编译的文件名

__LINE__ //表示文件当前的行号

__DATE__ //表示文件被编译的日期

__TIME__ //表示文件被编译的时间

__STDC__ //如果编译器遵循ANSI.C,其值为1,否则未定义

二.#define

1.#define定义标识符

2.#define定义宏

三.#undef

这条指令可以移除一个宏定义,Ex:#undef name,移除name的宏定义。

四.条件编译

五.文件包含

2.3 Hello的预处理结果解析

截取部分我们可以看到新生成的.i文件对源文件的文件头进行了大量替换。

 

2.4 本章小结

本章对hello.c的内容进行了预处理,并通过分析预处理后的结果得到了预处理的功能以及优势,对于计算机的编译过程有了更深层次的了解。

第3章 编译

3.1 编译的概念与作用

编译阶段。编译器(ccl)将文本文件hello.i翻译成文本文件hello.s,它包含一个汇编语言程序。该程序包含函数main的定义,定义中每条语句都以一种文本格式描述了一条低级机器语言指令。汇编语言是非常有用的,因为它为不同高级语言的不同编译器提供了通用的输出语言。

3.2 在Ubuntu下编译的命令

 

3.3 Hello的编译结果解析

       3.3.1 栈帧预处理

    pushq   %rbp

movq    %rsp, %rbp

subq    $32, %rsp

movl    %edi, -20(%rbp)

movq    %rsi, -32(%rbp)

main函数的两个参数为argc与字符串数组argv,分别通过%edi与%rsi保存传递。

3.3.2 条件判断

cmpl    $4, -20(%rbp)

此处为将参数argc与4进行比较,判断用户键入参数个数是否为4。

cmpl    $7, -4(%rbp)

此处为将循环变量i与7进行比较,判断程序是否满足继续循环的条件。

3.3.3控制转移

je  .L2

此处为在满足argc=4的情况下跳转至.L2处继续该程序

jmp .L3

此处为程序中直接的跳转程序,跳转至.L3处

jle .L4

此处为满足循环变量i<=7时继续循环。

3.3.4赋值及算数操作

movl    $0, -4(%rbp)

定义i=0并将其存入栈帧中。

movq    -32(%rbp), %rax

addq    $16, %rax

movq    (%rax), %rdx

movq    -32(%rbp), %rax

addq    $8, %rax

movq    (%rax), %rax

movq    %rax, %rsi

    此处应用了多个赋值以及加法操作,根据栈帧预处理我们知道argv中的字符存储在寄存器%rbp偏移32位的地址中,movq -32(%rbp), %rax,首先将存储字符串的起点地址保存在%rax当中,接着考虑到一个char类型的变量占据8位大小,%rax加上16可得到argv[2]中的字符,将argv[2]中的字符保存至%rdx当中。movq -32(%rbp), %rax

addq    $8, %rax,这两行命令可以索引到argv[1]中的字符,movq %rax, %rsi

再将其存入寄存器%rsi当中。

movq    -32(%rbp), %rax

addq    $24, %rax

movq    (%rax), %rax

    movq    %rax, %rdi

这里的几步操作与上述操作相类似,索引到argv[3]中的值,经过atoi处理后调用sleep函数实现。

    addl    $1, -4(%rbp)

    此处位对循环变量i的处理,i++。

3.3.5函数操作

Main函数中调用了puts、exit、getchar、print、atoi、sleep等函数。

3.4 本章小结

       本章通过对汇编语言的分析,具体入微地了解了编译器的工作细节,从数据处理、函数操作、控制转移等各方面了解了计算机处理程序的方式,帮助我更好地理解我书写的程序在计算机内部的工作方式,从而了解并且提高工作效率。

第4章 汇编

4.1 汇编的概念与作用

       汇编阶段:接下来,汇编器(as)将hello.s翻译成机器语言指令,把这些指令打包成一种叫做可重定向目标程序的格式,并将结果保存在目标文件hello.o当中。hello.o文件是一个二进制文件,它包含的17个字节是函数main的指令编码。

    汇编的作用:将汇编代码转换为机器指令,使其在链接后能被机器识别并执行。

4.2 在Ubuntu下汇编的命令

      

 

4.3 可重定位目标elf格式

 

  

ELF Header:

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

  Class:                             ELF64

  Data:                              2's complement, little endian

  Version:                           1 (current)

  OS/ABI:                            UNIX - System V

  ABI Version:                       0

  Type:                              REL (Relocatable file)

  Machine:                           Advanced Micro Devices X86-64

  Version:                           0x1

  Entry point address:               0x0

  Start of program headers:          0 (bytes into file)

  Start of section headers:          1240 (bytes into file)

  Flags:                             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

首先分析ELF Header中的信息,在此文件中,可看到其类型为REL,即可重定向文件,其中section头的个数为14,下面是14个Section头的详细信息:

Section Headers:

  [Nr] Name              Type             Address           Offset

       Size              EntSize          Flags  Link  Info  Align

  [ 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  00000388

       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

       000000000000002c  0000000000000001  MS       0     0     1

  [ 7] .note.GNU-stack   PROGBITS         0000000000000000  00000137

       0000000000000000  0000000000000000           0     0     1

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

       0000000000000020  0000000000000000   A       0     0     8

  [ 9] .eh_frame         PROGBITS         0000000000000000  00000158

       0000000000000038  0000000000000000   A       0     0     8

  [10] .rela.eh_frame    RELA             0000000000000000  00000448

       0000000000000018  0000000000000018   I      11     9     8

  [11] .symtab           SYMTAB           0000000000000000  00000190

       00000000000001b0  0000000000000018          12    10     8

  [12] .strtab           STRTAB           0000000000000000  00000340

       0000000000000048  0000000000000000           0     0     1

  [13] .shstrtab         STRTAB           0000000000000000  00000460

       0000000000000074  0000000000000000           0     0     1

其中Addr在此处被填充为了0的原因是,其目前并不需要被加载到内存中,在链接的时候才会被填充。

包含了文件中出现的各个节的语义,包括节的类型、位置和大小等信息。以hello.s为例,节头部表一共描述了13个不同节的位置、大小等信息。

1.text节,大小为0x92,类型为PROGBITS,标志为AX(只读可执行),偏移量为0X40,

2.rela.text节,大小为0xc0,类型为RELA,标志为I,偏移0x388 字节

3.data节,已初始化的全局变量节,类型为PROGBITS,标志为WA(可读写),偏移0xd2

4.bss节,未初始化的全局和静态变量或初始化为0的全局或静态变量,大小为0,类型为PROGBITS,标志为WA,偏移0xd2

5.rodata节,只读数据,大小为0x33,标志为A(只读),偏移0xd8

6.comment节,版本控制信息,大小为0x2c,标志为MS,偏移为0x10b

7.note.GNU_stack节:标记可执行堆栈,大小为0x0字节,类型为PROGBITS,偏移量为0x137

8. .note.gnu.propert,类型为NOTE,大小0x0字节,偏移量为0x138.

9.eh_frame节:处理异常,大小为0x38字节,类型为PROGBITS,偏移量为0x158,标志为A(表明该节的数据只读)。

10.rela.eh_frame节:.eh_frame节的重定位信息,大小为0x18字节,类型为RELA,偏移量为0x448,标志为I。

11.symtab节:一个符号表,存放在程序中定义和引用的函数和全局变量的信息。大小为0x18字节,类型为SYMTAB,偏移量为0x190

12.strtab节:一个字符串表,包括.symtab和.debug节中的符号表,以及节头,偏移量为0x340字节

13.shstrtab节:大小为0x74字节,也是一个字符串表,偏移量为0x460字节

重定位节

Relocation section '.rela.text' at offset 0x388 contains 8 entries:

  Offset          Info           Type           Sym. Value    Sym. Name + Addend

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

当汇编器生成一个目标模块时,它并不知道数据和代码最终将存放在存储器中的什么位置。它也不知道这个模块引用的任何外部定义的函数和全局变量。所以,无论何时汇编器遇到对最终位置未指定目标引用,它就会生成一个重定位条目,告诉链接器在将目标文件合并可执行文件时如何修改这个引用。代码重定位条目放在.rel.text中。已经初始化数据的重定位条目放在.rel.data中。重定位条目中描述了需重定位符号的偏移量、信息、类型、符号值及名称等。

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

存放变量信息。

4.4 Hello.o的结果解析

0000000000000000 <main>:

   0:   f3 0f 1e fa             endbr64

   4:   55                      push   %rbp

   5:   48 89 e5                mov    %rsp,%rbp

   8:   48 83 ec 20             sub    $0x20,%rsp

   c:   89 7d ec                mov    %edi,-0x14(%rbp)

   f:   48 89 75 e0             mov    %rsi,-0x20(%rbp)

  13:   83 7d ec 04             cmpl   $0x4,-0x14(%rbp)

  17:   74 16                   je     2f <main+0x2f>

  19:   48 8d 3d 00 00 00 00   lea    0x0(%rip),%rdi        # 20 <main+0x20>

            1c: R_X86_64_PC32  .rodata-0x4

  20:   e8 00 00 00 00          callq  25 <main+0x25>

            21: R_X86_64_PLT32  puts-0x4

  25:   bf 01 00 00 00          mov    $0x1,%edi

  2a:   e8 00 00 00 00          callq  2f <main+0x2f>

            2b: R_X86_64_PLT32  exit-0x4

  2f:   c7 45 fc 00 00 00 00    movl   $0x0,-0x4(%rbp)

  36:   eb 48                   jmp    80 <main+0x80>

  38:   48 8b 45 e0             mov    -0x20(%rbp),%rax

  3c:   48 83 c0 10             add    $0x10,%rax

  40:   48 8b 10                mov    (%rax),%rdx

  43:   48 8b 45 e0             mov    -0x20(%rbp),%rax

  47:   48 83 c0 08             add    $0x8,%rax

  4b:   48 8b 00                mov    (%rax),%rax

  4e:   48 89 c6                mov    %rax,%rsi

  51:   48 8d 3d 00 00 00 00   lea    0x0(%rip),%rdi        # 58 <main+0x58>

            54: R_X86_64_PC32  .rodata+0x22

  58:   b8 00 00 00 00          mov    $0x0,%eax

  5d:   e8 00 00 00 00          callq  62 <main+0x62>

            5e: R_X86_64_PLT32  printf-0x4

  62:   48 8b 45 e0             mov    -0x20(%rbp),%rax

  66:   48 83 c0 18             add    $0x18,%rax

  6a:   48 8b 00                mov    (%rax),%rax

  6d:   48 89 c7                mov    %rax,%rdi

  70:   e8 00 00 00 00          callq  75 <main+0x75>

            71: R_X86_64_PLT32  atoi-0x4

  75:   89 c7                   mov    %eax,%edi

  77:   e8 00 00 00 00          callq  7c <main+0x7c>

            78: R_X86_64_PLT32  sleep-0x4

  7c:   83 45 fc 01             addl   $0x1,-0x4(%rbp)

  80:   83 7d fc 07             cmpl   $0x7,-0x4(%rbp)

  84:   7e b2                   jle    38 <main+0x38>

  86:   e8 00 00 00 00          callq  8b <main+0x8b>

            87: R_X86_64_PLT32  getchar-0x4

  8b:   b8 00 00 00 00          mov    $0x0,%eax

  90:   c9                      leaveq

  91:   c3                      retq  

该形式的汇编代码与hello.s差别较小,主要几个差别体现在:

1)由L1、L2、L3表示的跳转标志更改为固定的地址偏移量,原因在于经过汇编操作后,汇编器为hello.s文件分配了指定的逻辑地址,因此可以采用固定偏移量进行表示。

2)全局变量的访问不能直接访问,需要访问.rodata文件中的内容,这个过程在重定位之前和函数一样,不能确定访问的地址,所以暂时仍然使用000000作为地址。

4.5 本章小结

      了解了ELF文件的内容,比较了重定向前后汇编程序的差别,了解了汇编器的工作任务以及作用。

第5章 链接

5.1 链接的概念与作用

链接阶段:hello程序调用了printf函数,它是每个C编译器都提供的标准C库中的一个函数。Printf函数存在于一个名为printf.o的单独的预编译好了的目标文件中,而这个文件必须以某种方式合并到我们的hello.o程序中,链接器(ld)就负责处理这种合并。结果就得到hello文件,它是一个可执行目标文件,可以被加载到内存中,由系统执行。

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

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

 

ELF Header:

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

  Class:                             ELF64

  Data:                              2's complement, little endian

  Version:                           1 (current)

  OS/ABI:                            UNIX - System V

  ABI Version:                       0

  Type:                              EXEC (Executable file)

  Machine:                           Advanced Micro Devices X86-64

  Version:                           0x1

  Entry point address:               0x4010f0

  Start of program headers:          64 (bytes into file)

  Start of section headers:          14208 (bytes into file)

  Flags:                             0x0

  Size of this header:               64 (bytes)

  Size of program headers:           56 (bytes)

  Number of program headers:         12

  Size of section headers:           64 (bytes)

  Number of section headers:         27

  Section header string table index: 26

可以看到hello.elf中Section headers的个数变为了27个,可以体现出链接后与原elf文本文件的变化。

    Section Headers:

  [Nr] Name              Type             Address           Offset

       Size              EntSize          Flags  Link  Info  Align

  [ 0]                   NULL             0000000000000000  00000000

       0000000000000000  0000000000000000           0     0     0

  [ 1] .interp           PROGBITS         00000000004002e0  000002e0

       000000000000001c  0000000000000000   A       0     0     1

  [ 2] .note.gnu.propert NOTE             0000000000400300  00000300

       0000000000000020  0000000000000000   A       0     0     8

  [ 3] .note.ABI-tag     NOTE             0000000000400320  00000320

       0000000000000020  0000000000000000   A       0     0     4

  [ 4] .hash             HASH             0000000000400340  00000340

       0000000000000038  0000000000000004   A       6     0     8

  [ 5] .gnu.hash         GNU_HASH         0000000000400378  00000378

       000000000000001c  0000000000000000   A       6     0     8

  [ 6] .dynsym           DYNSYM           0000000000400398  00000398

       00000000000000d8  0000000000000018   A       7     1     8

  [ 7] .dynstr           STRTAB           0000000000400470  00000470

       000000000000005c  0000000000000000   A       0     0     1

  [ 8] .gnu.version      VERSYM           00000000004004cc  000004cc

       0000000000000012  0000000000000002   A       6     0     2

  [ 9] .gnu.version_r    VERNEED          00000000004004e0  000004e0

       0000000000000020  0000000000000000   A       7     1     8

  [10] .rela.dyn         RELA             0000000000400500  00000500

       0000000000000030  0000000000000018   A       6     0     8

  [11] .rela.plt         RELA             0000000000400530  00000530

       0000000000000090  0000000000000018  AI       6    21     8

  [12] .init             PROGBITS         0000000000401000  00001000

       000000000000001b  0000000000000000  AX       0     0     4

  [13] .plt              PROGBITS         0000000000401020  00001020

       0000000000000070  0000000000000010  AX       0     0     16

  [14] .plt.sec          PROGBITS         0000000000401090  00001090

       0000000000000060  0000000000000010  AX       0     0     16

  [15] .text             PROGBITS         00000000004010f0  000010f0

       0000000000000145  0000000000000000  AX       0     0     16

  [16] .fini             PROGBITS         0000000000401238  00001238

       000000000000000d  0000000000000000  AX       0     0     4

  [17] .rodata           PROGBITS         0000000000402000  00002000

       000000000000003b  0000000000000000   A       0     0     8

  [18] .eh_frame         PROGBITS         0000000000402040  00002040

       00000000000000fc  0000000000000000   A       0     0     8

  [19] .dynamic          DYNAMIC          0000000000403e50  00002e50

       00000000000001a0  0000000000000010  WA       7     0     8

  [20] .got              PROGBITS         0000000000403ff0  00002ff0

       0000000000000010  0000000000000008  WA       0     0     8

  [21] .got.plt          PROGBITS         0000000000404000  00003000

       0000000000000048  0000000000000008  WA       0     0     8

  [22] .data             PROGBITS         0000000000404048  00003048

       0000000000000004  0000000000000000  WA       0     0     1

  [23] .comment          PROGBITS         0000000000000000  0000304c

       000000000000002b  0000000000000001  MS       0     0     1

  [24] .symtab           SYMTAB           0000000000000000  00003078

       00000000000004c8  0000000000000018          25    30     8

  [25] .strtab           STRTAB           0000000000000000  00003540

       0000000000000158  0000000000000000           0     0     1

  [26] .shstrtab         STRTAB           0000000000000000  00003698

       00000000000000e1  0000000000000000           0     0     1

区别段:

1).interp:动态链接器在操作系统中的位置不是由系统配置决定,也不是由环境参数指定,而是由 ELF 文件中的 .interp 段指定。该段里保存的是一个字符串,这个字符串就是可执行文件所需要的动态链接器的位置,常位于 /lib/ld-linux.so.2。(通常是软链接)

Program Headers:

  Type           Offset             VirtAddr           PhysAddr

                 FileSiz            MemSiz              Flags  Align

  PHDR           0x0000000000000040 0x0000000000400040 0x0000000000400040

                 0x00000000000002a0 0x00000000000002a0  R      0x8

  INTERP         0x00000000000002e0 0x00000000004002e0 0x00000000004002e0

                 0x000000000000001c 0x000000000000001c  R      0x1

      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]

  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000

                 0x00000000000005c0 0x00000000000005c0  R      0x1000

  LOAD           0x0000000000001000 0x0000000000401000 0x0000000000401000

                 0x0000000000000245 0x0000000000000245  R E    0x1000

  LOAD           0x0000000000002000 0x0000000000402000 0x0000000000402000

                 0x000000000000013c 0x000000000000013c  R      0x1000

  LOAD           0x0000000000002e50 0x0000000000403e50 0x0000000000403e50

                 0x00000000000001fc 0x00000000000001fc  RW     0x1000

  DYNAMIC        0x0000000000002e50 0x0000000000403e50 0x0000000000403e50

                 0x00000000000001a0 0x00000000000001a0  RW     0x8

  NOTE           0x0000000000000300 0x0000000000400300 0x0000000000400300

                 0x0000000000000020 0x0000000000000020  R      0x8

  NOTE           0x0000000000000320 0x0000000000400320 0x0000000000400320

                 0x0000000000000020 0x0000000000000020  R      0x4

  GNU_PROPERTY   0x0000000000000300 0x0000000000400300 0x0000000000400300

                 0x0000000000000020 0x0000000000000020  R      0x8

  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000

                 0x0000000000000000 0x0000000000000000  RW     0x10

  GNU_RELRO      0x0000000000002e50 0x0000000000403e50 0x0000000000403e50

                 0x00000000000001b0 0x00000000000001b0  R      0x1

Section to Segment mapping:

  Segment Sections...

   00    

   01     .interp

   02     .interp .note.gnu.property .note.ABI-tag .hash .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt

   03     .init .plt .plt.sec .text .fini

   04     .rodata .eh_frame

   05     .dynamic .got .got.plt .data

   06     .dynamic

   07     .note.gnu.property

   08     .note.ABI-tag

   09     .note.gnu.property

   10    

   11     .dynamic .got

多出来的Program Header Table描述了十个Segment的信息。以.interp段为例,.interp段和前面的ELFHeader、Program Header Table一起组成一个Segment(FileSiz指出总长度为0x2e0),VirtAddr列指出第一个Segment加载到虚拟地址0x00000000004002e0(注意在x86平台上后面的PhysAddr列是没有意义的)。Flg列指出第一个Segment的访问权限是可读的。最后一列Align的值0x1(4K)是x64平台的内存页面大小。在加载时要求文件中的一页对应内存中的一页。

Dynamic section如果目标文件参与动态链接,则其程序头表将包含一个类型为 PT_DYNAMIC 的元素。此段包含 .dynamic 节。特殊符号 _DYNAMIC 用于标记包含以下结构的数组的节:

Dynamic section at offset 0x2e50 contains 21 entries:

  Tag        Type                         Name/Value

 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]

 0x000000000000000c (INIT)               0x401000

 0x000000000000000d (FINI)               0x401238

 0x0000000000000004 (HASH)               0x400340

 0x000000006ffffef5 (GNU_HASH)           0x400378

 0x0000000000000005 (STRTAB)             0x400470

 0x0000000000000006 (SYMTAB)             0x400398

 0x000000000000000a (STRSZ)              92 (bytes)

 0x000000000000000b (SYMENT)             24 (bytes)

 0x0000000000000015 (DEBUG)              0x0

 0x0000000000000003 (PLTGOT)             0x404000

 0x0000000000000002 (PLTRELSZ)           144 (bytes)

 0x0000000000000014 (PLTREL)             RELA

 0x0000000000000017 (JMPREL)             0x400530

 0x0000000000000007 (RELA)               0x400500

 0x0000000000000008 (RELASZ)             48 (bytes)

 0x0000000000000009 (RELAENT)            24 (bytes)

 0x000000006ffffffe (VERNEED)            0x4004e0

 0x000000006fffffff (VERNEEDNUM)         1

 0x000000006ffffff0 (VERSYM)             0x4004cc

 0x0000000000000000 (NULL)               0x0

在hello 中,原来的.rela.text节已经没有了,说明链接的过程已经完成了对.rela.text的重定位操作。Hello中出现了6个新的重定位条目。这些重定位条目都和共享库中的函数有关,因为此时还没有进行动态链接,共享库中函数的确切地址仍是未知的,因此仍然需要重定位节,在动态链接后才能确定地址。

Relocation section '.rela.plt' at offset 0x530 contains 6 entries:

  Offset          Info           Type           Sym. Value    Sym. Name + Addend

000000404018  000100000007 R_X86_64_JUMP_SLO 0000000000000000 puts@GLIBC_2.2.5 + 0

000000404020  000200000007 R_X86_64_JUMP_SLO 0000000000000000 printf@GLIBC_2.2.5 + 0

000000404028  000400000007 R_X86_64_JUMP_SLO 0000000000000000 getchar@GLIBC_2.2.5 + 0

000000404030  000600000007 R_X86_64_JUMP_SLO 0000000000000000 atoi@GLIBC_2.2.5 + 0

000000404038  000700000007 R_X86_64_JUMP_SLO 0000000000000000 exit@GLIBC_2.2.5 + 0

000000404040  000800000007 R_X86_64_JUMP_SLO 0000000000000000 sleep@GLIBC_2.2.5 + 0

变量存储如下:

Symbol table '.dynsym' contains 9 entries:

   Num:    Value          Size Type    Bind   Vis      Ndx Name

     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND

     1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@GLIBC_2.2.5 (2)

     2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND printf@GLIBC_2.2.5 (2)

     3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.2.5 (2)

     4: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND getchar@GLIBC_2.2.5 (2)

     5: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__

     6: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND atoi@GLIBC_2.2.5 (2)

     7: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND exit@GLIBC_2.2.5 (2)

     8: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND sleep@GLIBC_2.2.5 (2)

Symbol table '.symtab' contains 51 entries:

   Num:    Value          Size Type    Bind   Vis      Ndx Name

     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND

     1: 00000000004002e0     0 SECTION LOCAL  DEFAULT    1

     2: 0000000000400300     0 SECTION LOCAL  DEFAULT    2

     3: 0000000000400320     0 SECTION LOCAL  DEFAULT    3

     4: 0000000000400340     0 SECTION LOCAL  DEFAULT    4

     5: 0000000000400378     0 SECTION LOCAL  DEFAULT    5

     6: 0000000000400398     0 SECTION LOCAL  DEFAULT    6

     7: 0000000000400470     0 SECTION LOCAL  DEFAULT    7

     8: 00000000004004cc     0 SECTION LOCAL  DEFAULT    8

     9: 00000000004004e0     0 SECTION LOCAL  DEFAULT    9

    10: 0000000000400500     0 SECTION LOCAL  DEFAULT   10

    11: 0000000000400530     0 SECTION LOCAL  DEFAULT   11

    12: 0000000000401000     0 SECTION LOCAL  DEFAULT   12

    13: 0000000000401020     0 SECTION LOCAL  DEFAULT   13

    14: 0000000000401090     0 SECTION LOCAL  DEFAULT   14

    15: 00000000004010f0     0 SECTION LOCAL  DEFAULT   15

    16: 0000000000401238     0 SECTION LOCAL  DEFAULT   16

    17: 0000000000402000     0 SECTION LOCAL  DEFAULT   17

    18: 0000000000402040     0 SECTION LOCAL  DEFAULT   18

    19: 0000000000403e50     0 SECTION LOCAL  DEFAULT   19

    20: 0000000000403ff0     0 SECTION LOCAL  DEFAULT   20

    21: 0000000000404000     0 SECTION LOCAL  DEFAULT   21

    22: 0000000000404048     0 SECTION LOCAL  DEFAULT   22

    23: 0000000000000000     0 SECTION LOCAL  DEFAULT   23

    24: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS hello.c

    25: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS

    26: 0000000000403e50     0 NOTYPE  LOCAL  DEFAULT   19 __init_array_end

    27: 0000000000403e50     0 OBJECT  LOCAL  DEFAULT   19 _DYNAMIC

    28: 0000000000403e50     0 NOTYPE  LOCAL  DEFAULT   19 __init_array_start

    29: 0000000000404000     0 OBJECT  LOCAL  DEFAULT   21 _GLOBAL_OFFSET_TABLE_

    30: 0000000000401230     5 FUNC    GLOBAL DEFAULT   15 __libc_csu_fini

    31: 0000000000404048     0 NOTYPE  WEAK   DEFAULT   22 data_start

    32: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@@GLIBC_2.2.5

    33: 000000000040404c     0 NOTYPE  GLOBAL DEFAULT   22 _edata

    34: 0000000000401238     0 FUNC    GLOBAL HIDDEN    16 _fini

    35: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND printf@@GLIBC_2.2.5

    36: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@@GLIBC_

    37: 0000000000404048     0 NOTYPE  GLOBAL DEFAULT   22 __data_start

    38: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND getchar@@GLIBC_2.2.5

    39: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__

    40: 0000000000402000     4 OBJECT  GLOBAL DEFAULT   17 _IO_stdin_used

    41: 00000000004011c0   101 FUNC    GLOBAL DEFAULT   15 __libc_csu_init

    42: 0000000000404050     0 NOTYPE  GLOBAL DEFAULT   22 _end

    43: 0000000000401120     5 FUNC    GLOBAL HIDDEN    15 _dl_relocate_static_pie

    44: 00000000004010f0    47 FUNC    GLOBAL DEFAULT   15 _start

    45: 000000000040404c     0 NOTYPE  GLOBAL DEFAULT   22 __bss_start

    46: 0000000000401125   146 FUNC    GLOBAL DEFAULT   15 main

    47: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND atoi@@GLIBC_2.2.5

    48: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND exit@@GLIBC_2.2.5

    49: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND sleep@@GLIBC_2.2.5

    50: 0000000000401000     0 FUNC    GLOBAL HIDDEN    12 _init

5.4 hello的虚拟地址空间

     [15] .text             PROGBITS         00000000004010f0  000010f0

       0000000000000145  0000000000000000  AX       0     0     16

以.text段为例,该段起始地址为00000000004010f0,

 

与在edb中的查询结果相匹配,符合段地址的描述。

5.5 链接的重定位过程分析

经过重定位后的汇编代码与重定位前主要有以下几点差别:

1.重定位后的汇编代码使用虚拟内存来代替重定位前的标记(L1、L2)。

2.重定位后代码中将会把其他各类库函数信息进行标注并体现。

5.6 hello的执行流程

跳转过程如下:

ld-2.27.so!_dl_start -> ld-2.27.so!_dl_init -> hello!_start -> ld-2.27.so!_dl_start_main    -> ld-2.27.so!_cxa_atexit -> hello!_libc_csu_init -> libc-2.27.so!setjump -> hello!printf@plt -> hello!atoi@plt -> hello!sleep@plt -> hello!getchar@plt - >hello!exit@plt

5.7 Hello的动态链接分析

在运行dl_start和dl_init之前,GOTPLT表的内容如图所示:

  

 

在运行dl_start和dl_init之后,GOTPLT表的内容如图所示:

 

动态链接的基本思想就是把程序按照模块拆分成各个相对独立部分,在程序运行时才将他们链接在一起形成一个完成的程序,而不是像静态链接把所有的模块都链接成一个单独的可执行文件。在Linux下ELF动态链接被称为动态共享库(DSO)。

动态链接的好处:

1.动态链接将共享对象放置在内存中,不仅仅节省内存,它还可以减少物理页面的换进换出,也可以提高CPU缓存的命中率,因为不同进程间的数据与指令都集中在同一个共享模块上。

2.当一个软件模块发生改变的时,只需要覆盖需要更新的文件,等程序下一次运行时自动链接更新那么,就算是跟新完成了。

3.增加程序的可扩展性和兼容性,它可以在运行时动态的加载各种程序模块,就是后来的插件(plug-in).

5.8 本章小结

本章介绍了重定位的过程,以及经过重定位后生成的ELF文件与重定位前的区别,了解了可执行文件在计算机内的执行流程,了解了动态链接的过程。

第6章 hello进程管理

6.1 进程的概念与作用

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

进程的作用:向用户提供了一种假象。 我们的程序好像是系统中当前运行的唯一程序一样,我们的程序好像是独占的使用处理器和内存。处理器好像是无间断的执行我们程序中的指令,我们程序中的代码和数据好像是系统内存中唯一的对象。

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

Linux系统中,Shell是一个交互型应用级程序,代表用户运行其他程序。

其基本功能是解释并运行用户的指令,重复如下处理过程:

1.终端进程读取用户由键盘输入的命令行。

2.分析命令行字符串,获取命令行参数,并构造传递给execve的argv向量

3.检查第一个(首个、第0个)命令行参数是否是一个内置的shell命令

4.如果不是内部命令,调用fork( )创建新进程/子进程

5.在子进程中,用步骤2获取的参数,调用execve( )执行指定程序。

6.如果用户没要求后台运行(命令末尾没有&号)否则shell使用waitpid(或wait)等待作业终止后返回。

7.如果用户要求后台运行(如果命令末尾有&号),则shell返回。

6.3 Hello的fork进程创建过程

父进程可以通过fork函数创建一个新的运行的子进程,其函数声明为:

pid_t fork(void),子进程享有与父进程相同但各自独立的上下文,包括代码、堆、数据段、共享库以及用户栈。

在父进程中,fork函数返回子进程的PID,在子进程中,fork函数返回0.

当我们在终端中输入./hello时,shell会先判断发现这个参数并不是Shell内置的命令,于是就把这条命令当作一个可执行程序的名字,它的判断显然是对的。接下了shell会执行fork函数为hello创建进程。

6.4 Hello的execve过程

首先execve函数利用参数名,调用函数hello,能取得对应文件的i节点,然后将当前进程(子进程)的i节点置换为上面操作得到的节点,释放内存页表并修改为LDT。

    具体操作如下:

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

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

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

4.设置程序计数器(PC)。exceve做的最后一件事就是设置当前进程的上下文中的程序计数器,使之指向代码区域的入口点。下一次调用这个进程时,它将从这个入口点开始执行。Linux将根据需要换入代码和数据页面。

6.5 Hello的进程执行

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

开始hello运行在用户模式,收到信号后进入内核模式,运行信号处理程序,之后再返回用户模式。运行过程中,cpu不断切换上下文,使运行过程被切分成时间片,与其他进程交替占用cpu,实现进程的调度。

6.6 hello的异常与信号处理

1.程序正常执行

 

2.通过Ctrl+Z挂起程序,输入ps查看进程:

 

3.jobs查看当前作业

 

4.fg运行前台程序

 

5.调用pstree查看进程树

systemd─┬─ModemManager───2*[{ModemManager}]

        ├─NetworkManager───2*[{NetworkManager}]

        ├─3*[VBoxClient───VBoxClient───2*[{VBoxClient}]]

        ├─VBoxClient───VBoxClient───3*[{VBoxClient}]

        ├─VBoxService───8*[{VBoxService}]

        ├─accounts-daemon───2*[{accounts-daemon}]

        ├─acpid

        ├─avahi-daemon───avahi-daemon

        ├─colord───2*[{colord}]

        ├─cron

        ├─cups-browsed───2*[{cups-browsed}]

        ├─cupsd

        ├─dbus-daemon

        ├─gdm3─┬─gdm-session-wor─┬─gdm-x-session─┬─Xorg───{Xorg}

        │      │                 │               ├─gnome-session-b─┬─ssh-agent

        │      │                 │               │                 └─2*[{gnome-+

        │      │                 │               └─2*[{gdm-x-session}]

        │      │                 └─2*[{gdm-session-wor}]

        │      └─2*[{gdm3}]

        ├─gnome-keyring-d─┬─ssh-agent

        │                 └─3*[{gnome-keyring-d}]

        ├─irqbalance───{irqbalance}

        ├─2*[kerneloops]

        ├─networkd-dispat

        ├─polkitd───2*[{polkitd}]

        ├─rsyslogd───3*[{rsyslogd}]

        ├─rtkit-daemon───2*[{rtkit-daemon}]

        ├─snapd───10*[{snapd}]

        ├─switcheroo-cont───2*[{switcheroo-cont}]

        ├─systemd─┬─(sd-pam)

        │         ├─at-spi-bus-laun─┬─dbus-daemon

        │         │                 └─3*[{at-spi-bus-laun}]

        │         ├─at-spi2-registr───2*[{at-spi2-registr}]

        │         ├─dbus-daemon

        │         ├─dconf-service───2*[{dconf-service}]

        │         ├─evolution-addre───5*[{evolution-addre}]

        │         ├─evolution-calen───8*[{evolution-calen}]

        │         ├─evolution-sourc───3*[{evolution-sourc}]

        │         ├─gjs───4*[{gjs}]

        │         ├─gnome-session-b─┬─evolution-alarm───5*[{evolution-alarm}]

        │         │                 ├─gsd-disk-utilit───2*[{gsd-disk-utilit}]

        │         │                 ├─update-notifier───3*[{update-notifier}]

        │         │                 └─3*[{gnome-session-b}]

        │         ├─gnome-session-c───{gnome-session-c}

        │         ├─gnome-shell─┬─ibus-daemon─┬─ibus-dconf───3*[{ibus-dconf}]

        │         │             │             ├─ibus-engine-lib───3*[{ibus-engi+

        │         │             │             ├─ibus-engine-sim───2*[{ibus-engi+

        │         │             │             ├─ibus-extension-───3*[{ibus-exte+

        │         │             │             └─2*[{ibus-daemon}]

        │         │             └─6*[{gnome-shell}]

        │         ├─gnome-shell-cal───5*[{gnome-shell-cal}]

        │         ├─gnome-terminal-─┬─bash───pstree

        │         │                 └─4*[{gnome-terminal-}]

        │         ├─goa-daemon───3*[{goa-daemon}]

        │         ├─goa-identity-se───2*[{goa-identity-se}]

        │         ├─gsd-a11y-settin───3*[{gsd-a11y-settin}]

        │         ├─gsd-color───3*[{gsd-color}]

        │         ├─gsd-datetime───3*[{gsd-datetime}]

        │         ├─gsd-housekeepin───3*[{gsd-housekeepin}]

        │         ├─gsd-keyboard───3*[{gsd-keyboard}]

        │         ├─gsd-media-keys───4*[{gsd-media-keys}]

        │         ├─gsd-power───3*[{gsd-power}]

        │         ├─gsd-print-notif───2*[{gsd-print-notif}]

        │         ├─gsd-printer───2*[{gsd-printer}]

        │         ├─gsd-rfkill───2*[{gsd-rfkill}]

        │         ├─gsd-screensaver───2*[{gsd-screensaver}]

        │         ├─gsd-sharing───3*[{gsd-sharing}]

        │         ├─gsd-smartcard───4*[{gsd-smartcard}]

        │         ├─gsd-sound───3*[{gsd-sound}]

        │         ├─gsd-usb-protect───3*[{gsd-usb-protect}]

        │         ├─gsd-wacom───2*[{gsd-wacom}]

        │         ├─gsd-wwan───3*[{gsd-wwan}]

        │         ├─gsd-xsettings───3*[{gsd-xsettings}]

        │         ├─gvfs-afc-volume───3*[{gvfs-afc-volume}]

        │         ├─gvfs-goa-volume───2*[{gvfs-goa-volume}]

        │         ├─gvfs-gphoto2-vo───2*[{gvfs-gphoto2-vo}]

        │         ├─gvfs-mtp-volume───2*[{gvfs-mtp-volume}]

        │         ├─gvfs-udisks2-vo───3*[{gvfs-udisks2-vo}]

        │         ├─gvfsd─┬─gvfsd-dnssd───2*[{gvfsd-dnssd}]

        │         │       ├─gvfsd-network───3*[{gvfsd-network}]

        │         │       ├─gvfsd-recent───2*[{gvfsd-recent}]

        │         │       ├─gvfsd-trash───2*[{gvfsd-trash}]

        │         │       └─2*[{gvfsd}]

        │         ├─gvfsd-fuse───5*[{gvfsd-fuse}]

        │         ├─gvfsd-metadata───2*[{gvfsd-metadata}]

        │         ├─ibus-portal───2*[{ibus-portal}]

        │         ├─ibus-x11───2*[{ibus-x11}]

        │         ├─pulseaudio───3*[{pulseaudio}]

        │         ├─snap-store───4*[{snap-store}]

        │         ├─tracker-miner-f───4*[{tracker-miner-f}]

        │         ├─xdg-desktop-por───4*[{xdg-desktop-por}]

        │         ├─xdg-desktop-por───3*[{xdg-desktop-por}]

        │         ├─xdg-document-po───5*[{xdg-document-po}]

        │         └─xdg-permission-───2*[{xdg-permission-}]

        ├─systemd-journal

        ├─systemd-logind

        ├─systemd-resolve

        ├─systemd-timesyn───{systemd-timesyn}

        ├─systemd-udevd

        ├─udisksd───4*[{udisksd}]

        ├─unattended-upgr───{unattended-upgr}

        ├─upowerd───2*[{upowerd}]

        ├─whoopsie───2*[{whoopsie}]

        └─wpa_supplicant

6.异常处理

 

在程序运行过程中随意键入字符对程序运行不会产生影响,无法识别的字符将被忽略,以无法解析符的形式存在。

6.7本章小结

通过本章了解了hello程序在进程中的运行方式,了解了各类进程相关的命令,以及面对异常进程计算机的处理方式。

第7章 hello的存储管理

7.1 hello的存储器地址空间

逻辑地址:在程序运行时由中央处理单元生成的内容的地址称为逻辑地址。该地址也称为虚拟地址。当我们谈论逻辑地址时,我们指的是CPU分配给每个进程的地址,一个进程在内存中所处的实际地址与进程认为它所处的地址是不一样的。

线性地址:线性地址(Linear Address)也叫虚拟地址(virtual address)是逻辑地址到物理地址变换之间的中间层。在分段部件中逻辑地址是段中的偏移地址,然后加上基地址就是线性地址。是一个32位无符号整数,可以用来表示高达4GB的地址,也就是,高达4294967296个内存单元。线性地址通常用十六进制数字表示,值得范围从0x00000000到0xfffffff)程序代码会产生逻辑地址,通过逻辑地址变换就可以生成一个线性地址。如果启用了分页机制,那么线性地址可以再经过变换以产生一个物理地址。如果没有启用分页机制,那么线性地址直接就是物理地址。

虚拟地址:也即是逻辑地址。

物理地址:CPU地址总线传来的地址,由硬件电路控制(现在这些硬件是可编程的了)其具体含义。物理地址中很大一部分是留给内存条中的内存的,但也常被映射到其他存储器上(如显存、BIOS等)。在没有使用虚拟存储器的机器上,虚拟地址被直接送到内存总线上,使具有相同地址的物理存储器被读写;而在使用了虚拟存储器的情况下,虚拟地址不是被直接送到内存地址总线上,而是送到存储器管理单元MMU,把虚拟地址映射为物理地址。

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

一个逻辑地址由两部份组成,段标识符和段内偏移量。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号。引号,可以理解为数组的下标——而它将会对应一个数组,它又是什么的索引呢?这就是“段描述符(segment descriptor)”,段描述符具体地址描述了一个段(对于“段”这个字眼的理解:我们可以理解为把虚拟内存分为一个一个的段。比如一个存储器有1024个字节,可以把它分成4段,每段有256个字节)。这样,很多个段描述符,就组了一个数组,叫“段描述符表”,这样,可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段,如图:

 

Intel设计的本意是,一些全局的段描述符,就放在“全局段描述符表(GDT)”中,一些局部的,例如每个进程自己的,就放在所谓的“局部段描述符表(LDT)”中。选择GDT还是LDT取决于段选择符中的T1,若T1等于0则选择GDT,反之选择LDT。GDT在内存中的地址和大小存放在CPU的gdtr控制寄存器中,而LDT则在ldtr寄存器中。

首先,给定一个完整的逻辑地址   [段选择符:段内偏移地址],

1、看段选择符的T1=0还是1,知道当前要转换是GDT中的段,还是LDT中的段,再根据相应寄存器,得到其地址和大小。我们就有了一个数组了。

2、拿出段选择符中前13位,可以在这个数组中,查找到对应的段描述符,这样,它了Base,即基地址就知道了。

3、把Base + offset,就是要转换的线性地址了。

 

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

CPU的页式内存管理单元,负责把一个线性地址,最终翻译为一个物理地址。从管理和效率的角度出发,线性地址被分为以固定长度为单位的组,称为页(page),例如一个32位的机器,线性地址最大可为4G,可以用4KB为一个页来划分,这页,整个线性地址就被划分为一个tatol_page[2^20]的大数组,共有2的20个次方个页。这个大数组我们称之为页目录。目录中的每一个目录项,就是一个地址——对应的页的地址。

 

如上图

1、分页单元中,页目录是唯一的,它的地址放在CPU的cr3寄存器中,是进行地址转换的开始点。

2、每一个进程,因为都有其独立的对应的虚似内存(页目录也是唯一的),那么它也对应了一个独立的页目录地址。——运行一个进程,需要将它的页目录地址放到cr3寄存器中。

3、每一个32位的线性地址被划分为三部份,面目录索引(10位):页表索引(10位):偏移(12位)

依据以下步骤进行转换:

1、从cr3中取出进程的页目录地址(操作系统负责在调度进程的时候,把这个地址装入对应寄存器);

2、根据线性地址前十位,在数组中,找到对应的索引项,因为引入了二级管理模式,页目录中的项,不再是页的地址,而是一个页表的地址。(又引入了一个数组),页的地址被放到页表中去了。

3、根据线性地址的中间十位,在页表(也是数组)中找到页的起始地址;

4、将页的起始地址与线性地址中最后12位相加,得到最终我们想要的物理地址;

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

首先将CPU内核发送过来的32位VA[31:0]分成三段,前两段VA[31:20]和VA[19:12]作为两次查表的索引,第三段VA[11:0]作为页内的偏移,查表的步骤如下:

⑴从协处理器CP15的寄存器2(TTB寄存器,translation table base register)中取出保存在其中的第一级页表(translation table)的基地址,这个基地址指的是PA,也就是说页表是直接按照这个地址保存在物理内存中的。

⑵以TTB中的内容为基地址,以VA[31:20]为索引值在一级页表中查找出一项(2^12=4096项),这个页表项(也称为一个描述符,descriptor)保存着第二级页表(coarse page table)的基地址,这同样是物理地址,也就是说第二级页表也是直接按这个地址存储在物理内存中的。

⑶以VA[19:12]为索引值在第二级页表中查出一项(2^8=256),这个表项中就保存着物理页面的基地址,我们知道虚拟内存管理是以页为单位的,一个虚拟内存的页映射到一个物理内存的页框,从这里就可以得到印证,因为查表是以页为单位来查的。

⑷有了物理页面的基地址之后,加上VA[11:0]这个偏移量(2^12=4KB)就可以取出相应地址上的数据了。

这个过程称为Translation Table Walk,Walk这个词用得非常形象。从TTB走到一级页表,又走到二级页表,又走到物理页面,一次寻址其实是三次访问物理内存。注意这个“走”的过程完全是硬件做的,每次CPU寻址时MMU就自动完成以上四步,不需要编写指令指示MMU去做,前提是操作系统要维护页表项的正确性,每次分配内存时填写相应的页表项,每次释放内存时清除相应的页表项,在必要的时候分配或释放整个页表。

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

处理器微架构访问Cache的方法与访问主存储器有类似之处。主存储器使用地址编码方式,微架构可以地址寻址方式访问这些存储器。Cache也使用了类似的地址编码方式,微架构也是使用这些地址操纵着各级Cache,可以将数据写入Cache,也可以从Cache中读出内容。只是这一切微架构针对Cache的操作并不是简单的地址访问操作。为简化起见,我们忽略各类Virtual Cache,讨论最基础的Cache访问操作,并借此讨论CPU如何使用TLB完成虚实地址转换,最终完成对Cache的读写操作。

 

Cache的存在使得CPU Core的存储器读写操作略微显得复杂。CPU Core在进行存储器方式时,首先使用EPN(Effective Page Number)进行虚实地址转换,并同时使用CLN(Cache Line Number)查找合适的Cache Block。这两个步骤可以同时进行。在使用Virtual Cache时,还可以使用虚拟地址对Cache进行寻址。在多数处理器微架构中,Cache由多行多列组成,使用CLN进行索引最终可以得到一个完整的Cache Block。但是在这个Cache Block中的数据并不一定是CPU Core所需要的。因此有必要进行一些检查,将Cache Block中存放的Address与通过虚实地址转换得到的PA进行地址比较(Compare Address)。如果结果相同而且状态位匹配,则表明Cache Hit。此时微架构再经过Byte Select and Align部件最终获得所需要的数据。如果发生Cache Miss,CPU需要使用PA进一步索引主存储器获得最终的数据。

7.6 hello进程fork时的内存映射

内存映射:Linux通过将一个虚拟内存区域与一个磁盘上的对象关联起来,以初始化这个内存区域的内容,这个过程称为内存映射。一个虚拟页面一旦被初始化了,它就在一个由内核维护的专门的交换文件之间换来换去。交换文件也叫交换空间或者交换区域,在任何时刻,交换空间都限制着当前运行着的进程能够分配的虚拟页面的总数。

当fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID。为了给这个新进程创建虚拟内存。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。

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

7.7 hello进程execve时的内存映射

       首先execve函数利用参数名,调用函数hello,能取得对应文件的i节点,然后将当前进程(子进程)的i节点置换为上面操作得到的节点,释放内存页表并修改为LDT。

    具体操作如下:

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

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

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

4.设置程序计数器(PC)。exceve做的最后一件事就是设置当前进程的上下文中的程序计数器,使之指向代码区域的入口点。下一次调用这个进程时,它将从这个入口点开始执行。Linux将根据需要换入代码和数据页面。

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

当CPU执行指令希望访问一个不在内存的页面时,将产生缺页中断,系统开始运行中断处理程序。此时指令计数器(PC) 的值尚未来得及增加就被压入堆栈,因此压入的断点必然是本次被中断的指令地址,而非下一条指令的地址。

缺页中断处理过程

(1) 保留进程上下文

(2)判断内存是否有空闲可用帧?若有,则获取一个帧号No,转(4) 启动I/O过程。若无,继续(3)

(3)腾出一个空闲帧,即:

(3)-1调用置换算法,选择一个淘汰页PTj。

(3)-2 PTj(S)=0 ; //驻留位置0

(3)-3 No= PTj (F); //取该页帧号

(3)-4 若该页曾修改过,则

(3)-4-1 请求外存交换区上一个空闲块B ;

(3)-4-2 PTj(D)=B ;//记录外存地址

(3)-4-3启动I/O管理程序,将该页写到外存上。

(4)按页表中提供的缺页外存位置,启动I/O,将缺页装入空闲帧No中。

(5)修改页表中该页的驻留位和内存地址。PTi(S)=1 ; PTi(F) =No。

(6)结束。

7.9动态存储分配管理

动态存储分配的几个方式:

1)malloc 函数

分配指定字节数的存储区。此存储区的初始值不确定。

2)calloc 函数

为指定数量指定长度的对象分配存储空间。该空间中的每一位(bit)都初始化为 0。

3)realloc 函数

增加或减少以前分配区的长度。这里重点说一下 realloc 函数

(1) 减少存储区的长度,这个简单直接减少就行;

(2) 增加存储区长度,如果在该存储区后有足够的空间可供扩充,则可在原存储区上向高地址方向扩充,无需移动任何原先的内容;并返回与传给他相同的指针值;

(3) 如果原存储区后没有足够的空间,则 realloc 分配另一足够大的存储区,再将原来空间的元素搬移过去,然后释放原存储区,返回新分配区的指针。新的区域,初始值不确定。

注意:realloc 的最后一个参数是存储区的长度,不是新、旧存储区长度之差。

7.10本章小结

本章通过hello程序,帮助了解了其在存储器地址空间的表示方式 ,阐述了逻辑地址到线性地址,以及线性地址到物理地址之间的转化流程,同时了解了cache、TLB、fork、execve的工作原理。

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

设备的模型化:文件,所有的输入和输出都能被当做相应文件的读和写来执行。

设备管理:unix io接口,使得所有的输入和输出都能以一种统一且一致的方式来执行。

8.2 简述Unix IO接口及其函数

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

2.Linux创建的每个进程开始时都有三个打开的文件:标准输入(描述符为0)、标准输出(描述符为1)和标准错误(描述符为2)。头文件<unistd.h>定义了常量STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO,它们可用来代替显式的描述符值。

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

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

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

函数:

打开和关闭文件:int open(char *filename, int flags, mode_t mode);

int close(int fd);

读和写文件:ssize_t read(int fd, void *buf, size_t n);

Ssize_t write(int fd, const void *buf, size_t n);

8.3 printf的实现分析

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

先看paintf函数:

int printf(const char *fmt,...)

{

    int i;

    char buf[256];

    va_list arg = (va_list)((char*)(&fmt)+4);

    i=vsprintf(buf,fmt,arg);

    write(buf,i);

    rerurn i;

}

 “…”:是可变形参的一种写法,当传递参数的个数不确定时,就可以用这种方式来表示。类似于template的写法。

通过va_list arg = (va_list)((char*)(&fmt) + 4)语句找到“…”中的第一个参数。接着调用了vsprintf函数,返回打印字符串的长度。

接下来,printf函数会调用系统IO函数:write,其作用就是从缓存buf中最多读i个字节复制到一个文件位置。

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

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

8.4 getchar的实现分析

getchar的源代码:

int getchar(void)

{

    static char buf[BUFSIZ];

    static char* bb=buf;

    static int n=0;

    if(n==0)

    {

        n=read(0,buf,BUFSIZ);

        bb=buf;

}

        return (--n>=0)?(unsigned char)*bb++:EOF;

}

异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。

8.5本章小结

通过本章,我了解了linux环境下IO设备的管理方式,IO的函数实现以及具体IO函数的内部操作。

结论

Hello经历了如下阶段:(1) 预处理:预处理器cpp将.c文件翻译成.i的文件;(2) 编译:gcc编译器将.i文件翻译成.s格式的汇编语言文件;(3) 汇编:as汇编器将.s文件转换成十六进制机器码的.o文件;(4) 链接:ld链接器将一系列.o文件链接起来形成最终的可执行文件hello;(5) 进程创建:shell为hello程序fork一个子进程;(6) 程序运行:shell调用execve函数,映射虚拟内存,载入物理内存,进入main函数;(7) 指令执行:hello和其他进程并发地运行,CPU为其分配时间片;(8) 进程回收:shell回收子进程,系统释放该进程的数据所占的内存空间。

附件

hello.i                hello的预处理结果

hello.s                hello.i的汇编结果

hello.o                hello.s翻译成的可重定位文件

hello                  可执行文件

helloo.obj             hello.o的反汇编结果

helloo.elf             hello.o的ELF格式文件

hello.obj              hello的反汇编结果

hello.elf              hello的ELF格式文件

参考文献

【1】《深入理解计算机系统》【美】 兰德尔E.布莱恩特 大卫R.奥哈拉伦 机械工业出版社

【2】Unix-Linux 网络 IO 模型简介 CSDN

【3】动态存储空间分配、管理和释放 CSDN

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值