ToB企服应用市场:ToB评测及商务社交产业平台

标题: 步调人生-Hello’s P2P——哈尔滨工业大学 盘算机体系 大作业 [打印本页]

作者: 用户云卷云舒    时间: 2024-6-9 19:25
标题: 步调人生-Hello’s P2P——哈尔滨工业大学 盘算机体系 大作业


盘算机体系

大作业


题          目  步调人生-Hello’s P2P   
专          业 
学     号              
班     级               
学          生               
指 导 教 师          刘雄伟        






盘算机科学与技能学院

2024年5月

摘  要

本论文详细探究了hello步调在Linux情况下的各个执行阶段及其相干技能实现。通过对hello步调从预处置惩罚、编译、汇编、链接、历程管理、存储管理到IO管理的体系分析,全面展示了步调在Linux情况下的执行机制,为明白利用体系底层原理提供了有力支持。

关键词:盘算机体系;预处置惩罚;编译;汇编;链接;历程;存储;I/O                            










目  录


第1章 概述................................................................................................................. - 4 -
1.1 Hello简介......................................................................................................... - 4 -
1.2 情况与工具......................................................................................................... - 4 -
1.3 中间效果............................................................................................................. - 4 -
1.4 本章小结............................................................................................................. - 5 -
第2章 预处置惩罚............................................................................................................. - 6 -
2.1 预处置惩罚的概念与作用......................................................................................... - 6 -
2.2在Ubuntu下预处置惩罚的命令............................................................................. - 6 -
2.3 Hello的预处置惩罚效果解析................................................................................. - 6 -
2.4 本章小结............................................................................................................. - 8 -
第3章 编译................................................................................................................. - 9 -
3.1 编译的概念与作用............................................................................................. - 9 -
3.2 在Ubuntu下编译的命令................................................................................. - 9 -
3.3 Hello的编译效果解析..................................................................................... - 9 -
3.4 本章小结........................................................................................................... - 16 -
第4章 汇编............................................................................................................... - 17 -
4.1 汇编的概念与作用........................................................................................... - 17 -
4.2 在Ubuntu下汇编的命令............................................................................... - 17 -
4.3 可重定位目标elf格式................................................................................... - 17 -
4.4 Hello.o的效果解析........................................................................................ - 20 -
4.5 本章小结........................................................................................................... - 22 -
第5章 链接............................................................................................................... - 23 -
5.1 链接的概念与作用........................................................................................... - 23 -
5.2 在Ubuntu下链接的命令............................................................................... - 23 -
5.3 可执行目标文件hello的格式....................................................................... - 23 -
5.4 hello的捏造地址空间.................................................................................... - 29 -
5.5 链接的重定位过程分析................................................................................... - 29 -
5.6 hello的执行流程............................................................................................ - 32 -
5.7 Hello的动态链接分析................................................................................... - 32 -
5.8 本章小结........................................................................................................... - 33 -
第6章 hello历程管理........................................................................................ - 34 -
6.1 历程的概念与作用........................................................................................... - 34 -
6.2 简述壳Shell-bash的作用与处置惩罚流程......................................................... - 34 -
6.3 Hello的fork历程创建过程......................................................................... - 34 -
6.4 Hello的execve过程..................................................................................... - 35 -
6.5 Hello的历程执行........................................................................................... - 36 -
6.6 hello的非常与信号处置惩罚................................................................................ - 36 -
6.7本章小结........................................................................................................... - 41 -
第7章 hello的存储管理.................................................................................... - 42 -
7.1 hello的存储器地址空间................................................................................ - 42 -
7.2 Intel逻辑地址到线性地址的变更-段式管理............................................... - 42 -
7.3 Hello的线性地址到物理地址的变更-页式管理.......................................... - 43 -
7.4 TLB与四级页表支持下的VA到PA的变更................................................. - 43 -
7.5 三级Cache支持下的物理内存访问.............................................................. - 45 -
7.6 hello历程fork时的内存映射..................................................................... - 46 -
7.7 hello历程execve时的内存映射................................................................. - 46 -
7.8 缺页故障与缺页中断处置惩罚............................................................................... - 47 -
7.9动态存储分配管理........................................................................................... - 49 -
7.10本章小结......................................................................................................... - 50 -
第8章 hello的IO管理...................................................................................... - 51 -
8.1 Linux的IO设备管理方法.............................................................................. - 51 -
8.2 简述Unix IO接口及其函数........................................................................... - 51 -
8.3 printf的实现分析........................................................................................... - 53 -
8.4 getchar的实现分析....................................................................................... - 55 -
8.5本章小结........................................................................................................... - 55 -
结论............................................................................................................................ - 56 -
附件............................................................................................................................ - 57 -
参考文献.................................................................................................................... - 58 -



第1章 概述

1.1 Hello简介

P2P,指Program to Progress,即Hello文件整个的编译与运行过程,包罗了步调的指令与数据等信息。步调运行时,利用体系将hello.c文件加载到内存中,而且将运行中的步调实例抽象为一个历程(process)。起首由步调员使用高级语言(例如C语言)编写创建源代码hello.c文件,由预处置惩罚器处置惩罚源代码中的指令生成hello.i文件,编译器将预处置惩罚后的代码转换成汇编语言文件hello.s,再通过汇编器将汇编代码转换成呆板语言,生成目标文件hello.o,经由ld链接器将目标文件与库文件链接在一起,生成可执行文件hello。随后当打开shell输入./hello指令时,利用体系为其fork创建子历程,通过execve体系调用,加载可执行文件到内存中,实现了P2P的过程。

图1-1 P2P过程框图

020,指从0到0,从在shell中输入./hello指令,shell调用fork产生子历程,再调用execve加载历程,运行hello,为其分配内存空间与CPU时间片,加载须要的物理内存,运行时步调在CPU的分配下译码执行,在CPU流水线上执行。利用体系运行竣事后,通过exit体系调用接纳历程,释放资源,清算步调运行时占用的内存以及步调在内核中的数据布局,关闭步调打开的文件描述符,至此历程竣事,hello相干数据被删除,回归到0。
1.2 情况与工具

硬件:
处置惩罚器:AMD Ryzen 5 5600 6-Core Processor 4.60 GHz;
机带RAM:32.0 GB;
软件:Windows 10 64位 专业版;Ubuntu 18.04.6 64位;
开辟工具:VSCode、edb、gcc、gdb、readelf、HexEdit、ld。
1.3 中间效果



表1-1 得到的中间效果

文件名

功能

hello.i

预处置惩罚器修改后的源步调,用来分析预处置惩罚器的行为

hello.o

可重定位目标步调,用来分析汇编器的行为

hello.s

可重定位目标步调,用来分析汇编器的行为

hello.elf

hello.o的elf格式

hello_asm.s

hello.o的反汇编代码

hello1.elf

hello的elf格式

hello

Linux下的可执行目标步调,用来分析链接器行为

hello1.s

hello反汇编之后的可重定位文件,用来分析重定位过程


1.4 本章小结

本章对P2P和020的概念进行了简要描述,介绍了编写陈诉过程中使用的软硬件情况与开辟工具,并列出了所得到的中间效果。



第2章 预处置惩罚

2.1 预处置惩罚的概念与作用

预处置惩罚阶段,预处置惩罚器(cpp)根据以字符#开头的预编译命令(例如文件包罗(#include)、宏定义(#define)、条件编译(#ifdef)等),修改原始的C步调,同时删除源代码中的解释。此处例如hello.c中的前三行:
#include <stdio.h>

#include <unistd.h>

#include <stdlib.h>

这三行命令告诉预处置惩罚器读取体系头文件stdio.h、unistd.h、stdlib.h的内容,并把它直接插入步调文本中,效果就得到了另一个C步调,通常是以.i作为文件扩展名,例如我们得到的hello.i文件。
预处置惩罚使编写的步调可以更方便地复用代码,简化复杂的代码,以及根据不同的条件编译不同的代码,使代码更易读,更方便修改、调试、移植,便于步调的模块化计划。
2.2在Ubuntu下预处置惩罚的命令

gcc hello.c -m64 -no-pie -fno-stack-protector -fno-PIC -E -o hello.i


图2-1 在Ubuntu下预处置惩罚hello.c的命令

2.3 Hello的预处置惩罚效果解析

由图2-2与图2-3,对比hello.c与hello.i可见,源代码hello.c仅有24行而预处置惩罚后的hello.i有3106行,由图2-3可见,原本的源代码部分在3092行之后,在这之前是hello.c引用的头文件stdio.h、unistd.h、stdlib.h的内容的展开,但我们发现,还插入了其他的头文件,例如libc-header-start.h等。这是由于,这三个头文件中同样使用#includ命令引入了其他头文件,这些头文件同样被插入hello.i文件中。具体引入的库可见下图2-2。
同时,对比源代码hello.c与预处置惩罚效果hello.i中的源代码部分,由图2-3所示,源代码hello.c头部出现的解释,在预处置惩罚后的hello.i中已不可见,这就印证了2.1中所述的预处置惩罚器的作用,由于源代码中不含宏定义(#define)和条件编译(#ifdef),因此此处无法展示。

图2-2 hello.c的预处置惩罚效果hello.i解析



图2-3 源代码hello.c与预处置惩罚效果hello.i中的源代码部分对比

2.4 本章小结

本章重要介绍了预处置惩罚(包括文件包罗、宏定义、条件编译、删除解释等)的概念及作用,同时展示了Ubuntu体系下hello.c文件实际预处置惩罚之后得到的hello.i步调,并对预处置惩罚效果进行了解析。

第3章 编译

3.1 编译的概念与作用

编译器(ccl)将文本文件hello.i翻译成文本文件hello.s,它包罗一个汇编语言步调。该步调包罗函数main的定义,此中每条语句均以一种文本格式描述了一条低级呆板语言指令。
汇编语言为不同高级语言的不同编译器提供了通用的输出语言,汇编语言在翻译目标步调时,同时进行词法、语法分析检查,分析过程中若出现语法错误将给出提示信息。
3.2 在Ubuntu下编译的命令

gcc -S hello.i -o hello.s


图3-1 在Ubuntu下编译hello.i的命令

3.3 Hello的编译效果解析

3.3.1 数据
(1)常量
(a)数字常量:通过观察,我们可以发现,源代码中使用的数字常量均储存在.text段,包括在比较的时间使用的数字变量5,在循环的时间使用的循环比较变量等数字常量,均储存在.text 节,具体存储情况拜见图3-2;

图3-2 main函数中数字常量存储情况

(b)字符串常量:可以发现,在printf 等函数中使用的字符串常量是储存在.rotate段,为只读数据,具体存储情况见图3-3;

图3-3 main函数中字符串常量存储情况

(2)变量
(a)全局变量:代码中并无全局变量。
(b)局部变量:局部变量被存储在栈中大概直接存储在寄存器中。以下对源代码中的局部变量逐一进行分析。
一是循环变量i,由汇编代码段知,i存储在栈中地址为-4(%rbp)的位置。此处为for循环开始处.L2,初始值令i=0。在.L3循环中每次循环i均与9进行比较,每次循环i+1。


图3-4 关于hello.i中局部变量i存储情况的汇编语句

二是局部变量argc,用于存储步调运行时输入的变量个数,由汇编代码段可知,argc存储在栈中地址为-20(%rbp)的位置。

图3-5 关于hello.i中局部变量argc存储情况的汇编语句

三是局部变量argv[],用于存储步调运行时输入的变量,由汇编代码段可知,argv[]存储在栈中地址为-32(%rbp)的位置。局部变量argc和argv[]均在今后的函数中被push入栈。

图3-6 关于hello.i中局部变量argv[]存储情况的汇编语句

3.3.2 赋值
汇编代码中的变量赋值利用只有对于循环变量i在循环初始时刻令i=0的利用。
for(i=0;i<10;i++){


图3-7 关于hello.i中赋值利用的源代码语句与汇编语句

3.3.3 算术利用
汇编代码中的算术利用如下图3-8。局部变量 i为循环变量,存放在栈中地址为-4(%rbp)的位置。在循环体中每一轮的循环中都要执行i++修改这个值。
for(i=0;i<10;i++){


图3-8 关于hello.i中算术利用的源代码语句与汇编语句

3.3.4 关系利用
源代码中共出现了2处关系利用,第一处是对argc的判断,当即是5时,进行条件跳转,源代码见下图3-9。je用于判断cmpl产生的条件码,假如两个利用数不等则跳转到指定地址.L2。
    if(argc!=5){

        printf("用法: Hello 学号 姓名 手机号 秒数!\n");

        exit(1);

    }


图3-9 关于hello.i中与argc有关的关系利用的源代码语句与汇编语句

第二处是for循环中对于循环变量i的判断,源代码如下图3-10,每次循环运行完成后均将i与10进行比较,若i<10则再次进入循环,否则循环竣事,执行循环外的语句getchar()。
    for(i=0;i<10;i++){

        printf("Hello %s %s %s\n",argv[1],argv[2],argv[3]);

        sleep(atoi(argv[4]));

    }


图3-10 关于hello.i中与i有关的关系利用的源代码语句与汇编语句

3.3.5 数组/指针/布局利用
源代码中涉及argv[]数组的利用。由源代码知,在printf和atoi中传入了argv[1]、argv[2]、argv[3]、argv[4]作为参数,阅读汇编代码,如下图3-11,可见argv[]的起始地址被存放在栈中-32(%rbp)的位置,被3次调用传递参数给printf,printf函数传入argv[1]、argv[2]、argv[3],分别被存储在%rcx、%rdx、%rsi中,而argv[4]在%edi中作为atoi函数的输入。
        printf("Hello %s %s %s\n",argv[1],argv[2],argv[3]);

        sleep(atoi(argv[4]));


图3-11 关于hello.i中数组/指针/布局利用的源代码语句与汇编语句

3.3.6 控制转移利用
源代码中,控制转移重要包罗if条件语句和for循环语句两种。一是if条件语句,判断传入参数argc是否为5,假如为5则执行跳转。阅读图3-12中的相应汇编代码,可见汇编代码中使用je判断cmpl产生的条件码,假如两个利用数不等则跳转到指定地址.L2。
    if(argc!=5){


图3-12 关于hello.i中控制转移利用之if条件语句的源代码语句与汇编语句

       二是for循环语句,接纳了jump-to-middle策略,当执行完令i=0的初始化后,判断i是否小于10,假如小于10则继承进入循环。阅读图3-13中的相应汇编代码,可见汇编代码中分支利用使用cmpl产生条件码,由jle进行循环条件判定,若利用数的值小于10则跳转到循环体地址.L4。
    for(i=0;i<10;i++){


图3-13 关于hello.i中控制转移利用之for循环语句的源代码语句与汇编语句

3.3.7 函数利用
源代码中共调用了main()、printf()、exit()、atoi()、sleep()、getchar()六个函数。  在x86-64体系中,可至多通过寄存器传递6个整型(包罗整数和指针)参数。第1~6个参数依次储存在%rdi、%rsi、%rdx、%rcx、%r8、%r9这六个寄存器中,别的参数保存在栈空间中的其他位置。
表3-1 x86-64体系的参数传递次序

1

2

3

4

5

6

7

%rdi

%rsi

%rdx

%rcx

%r8

%r9

栈空间

(1)main函数:
参数传递:传入参数argc、*argv[],分别存储在寄存器%rdi和%rsi中;
函数调用:体系启动函数调用;
函数返回:在源代码中末了的返回语句为0,对应汇编语言中末了将%eax设置为0并返回。
int main(int argc,char *argv[]){

    return 0;

}


图3-13 关于hello.i中函数利用之main函数的源代码语句与汇编语句

(2)printf函数:
参数传递:第一次if条件语句中调用时,call puts只传入了字符串参数首地址;第二次for 循环语句中调用时,call printf传入数组argv[1]、argv[2]、argv[3]的地址,分别存储在%rcx、%rdx、%rsi中;
函数调用:第一次if条件语句中经判断满足条件后调用,第二次for 循环语句中循环条件满足时调用。
        printf("用法: Hello 学号 姓名 手机号 秒数!\n");

        printf("Hello %s %s %s\n",argv[1],argv[2],argv[3]);



图3-14 关于hello.i中函数利用之printf函数的源代码语句与汇编语句

(3)exit函数:
参数传递:传入的参数为1,传入到%edi寄存器中;
函数调用:当if条件语句经判断满足条件时,调用,call exit执行退出命令。
        exit(1);


图3-15 关于hello.i中函数利用之exit函数的源代码语句与汇编语句

(4)atoi函数:
参数传递:传入的参数为argv[3],将其传入%rdi寄存器中;
函数调用:在for循环语句中call atoi调用;
函数返回:将字符串输入转化为int型,返回值存放在%eax中。
        sleep(atoi(argv[4]));


图3-16 关于hello.i中函数利用之atoi函数的源代码语句与汇编语句

(5)sleep函数:
参数传递:将atoi函数的返回值参数存放在寄存器%edi中传入;
函数调用:for循环语句中被call sleep调用,将返回效果存放在寄存器%eax中。
        sleep(atoi(argv[4]));


图3-17 关于hello.i中函数利用之sleep函数的源代码语句与汇编语句

(6)getchar函数:
函数调用:getchar函数无参数传递,直接在main中call getchar调用。
    getchar();


图3-18 关于hello.i中函数利用之getchar函数的源代码语句与汇编语句

3.4 本章小结

       本章介绍了编译的概念与作用,完成了hello.i到hello.s的编译,针对汇编代码,根据hello步调main函数中重要使用的数据、赋值、算术利用、关系利用、数组/指针/布局利用、控制转移利用、函数利用等七种处置惩罚指令,具体分析了hello.s步调的编译效果。

第4章 汇编

4.1 汇编的概念与作用

       汇编器(as)将hello.s翻译为呆板语言指令,把这些指令打包成可重定位目标步调(relocatable object program)的格式,并将效果保存在目标文件hello.o文件中,hello.o文件是一个二进制文件,其包罗的字节是函数main的指令编码。
       汇编过程,将汇编语言翻译成呆板语言,使之在链接(ld)后,能够被呆板识别并执行。
4.2 在Ubuntu下汇编的命令

gcc -c -o hello.o hello.s


图4-1 在Ubuntu下汇编hello.o的命令

4.3 可重定位目标elf格式

       使用readelf列出hello.o的ELF格式各节的根本信息。
readelf -a hello.o > hello.elf


图4-2 使用readelf列出hello.o的ELF格式各节的根本信息


图4-3 典型的ELF可重定位目标文件

4.3.1 ELF头
ELF头(ELF head)以一个16字节的序列Magic开始,这个序列描述了生成该文件的体系的字的巨细和字节次序,ELF头剩下的部分包罗帮助链接器语法分析息争释目标文件的信息,此中包括ELF头巨细、目标文件范例(如可重定位、可执行或共享的)、呆板范例(如x86-64)、节头部表(section header table)的文件偏移,以及节头部表中条目的巨细和数量。

图4-3 ELF头的内容

4.3.2 节头
ELF 文件中,每个节都有一个对应的节头表,枚举和定位各个节的信息,记录各节名称、范例、地址、偏移量、巨细、全体巨细、旗标、链接、信息、对齐等信息。

图4-4 节头的内容

4.3.3 重定位节
.rela.text,一个.text节中重定位信息的列表,保存的是.text节中须要被修正的信息,包括偏移量、信息、范例、符号值、符号名称、+和加数等,当链接器把这个目标文件与其他文件组合时须要修改这些位置。任何调用外部函数大概引用全局变量的指令都须要被修正,调用外部函数的指令和引用全局变量的指令须要重定位,调用局部函数的指令不须要重定位。hello步调中,须要被重定位的有.rodata中的.L0和.L1、puts、exit、printf、atoi、sleep、getchar、.text。

图4-5 重定位节的内容

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

图4-6 符号表的内容

4.4 hello.o的效果解析

通过以下命令对hello.o进行反汇编。
objdump -d -r hello.o > hello0_asm.s


图4-7 在Ubuntu下反汇编hello.o的命令

反汇编代码如下图4-8。总体上看大抵与hello.s类似,但在以下三点有所不同。

图4-8反汇编hello.o得到的反汇编代码hello0_asm.s

(1)利用数表示:
反汇编代码hello0_asm.s中的立即数全部是以16进制表示的,而可重定位文件hello.s中仍为10进制,这是由于16进制与2进制之间的转换比十进制更方便,故呆板语言反汇编而来的反汇编代码转换为了16进制。
hello.s


hello0_asm.s


图4-9 hello.s与hello_asm0.s中利用数表示的差异

(2)分支转移
可重定位文件hello.s中,跳转指令的目标地址直接记为段名称,如.L2、.L3等,但反汇编代码hello0_asm.s中,跳转指令的利用数使用的是确定的地址,呆板代码中体现为目标指令地址与当前指令下一条指令的地址之差。由于段名称只是在汇编语言中便于编写的助记符,故汇编成呆板语言之后不存在。
hello.s


 hello0_asm.s


图4-9 hello.s与hello0_asm.s中分支转移的差异

(3)函数调用
在可重定位文件hello.s中call后面是须要调用函数的名称,而在反汇编文件hello0_asm.s中可以看到,call后面直接加入main函数相对地址偏移量,同时可以发如今hello.o的反汇编代码中,调用函数的利用数都为0,即函数的相对地址为0,由于在链接生成可执行文件hello后,才会生成其确定的地址,故此处相对地址均使用0代替。
hello.s


  hello0_asm.s


图4-10 hello.s与hello0_asm.s中函数调用的差异

4.5 本章小结

       本章介绍了汇编的概念与作用,完成了hello.s到hello.o的汇编,生成了hello.o的可重定位目标文件hello.elf,分析了可重定位目标文件的ELF头、节头部表、符号表和可重定位节,使用反汇编objdump查看hello.o经反汇编生成的代码,比较了hello.s和hello.o反汇编代码,分析了从汇编语言到呆板语言的汇编过程。



第5章 链接

5.1 链接的概念与作用

链接是将各种不同文件的代码和数据片段收集并组合成一个单一文件的编译过程,这个文件可被加载到内存并执行,由链接器(ld)负责处置惩罚这种合并。
链接器把预编译好了的多少目标文件,例如hello中的printf等函数经预编译得到的printf.o等文件合并成为一个可执行目标文件,使得分离编译成为大概,从而不必将一个大型的应用步调构造为一个巨大的源文件,而是可以把它分解为可独立修改和编译的模块。当改变这些模块中的一个时,只需简单重新编译它并重新链接即可,不必重新编译其他文件。
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-1 在Ubuntu下链接的命令

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

       使用readelf列出hello的ELF格式各节的根本信息。
readelf -a hello > hello1.elf


图5-2 使用readelf列出hello的ELF格式各节的根本信息

5.3.1 ELF头
与hello.elf中的ELF头相比,hello1.elf中的ELF头包罗的信息种类根本雷同,Magic,种别等根本信息未发生改变,而范例发生改变,由REL (可重定位文件)变为EXEC (可执行文件),步调头巨细由0字节增加到64字节,节头数量由13增加到25,而且得到了入口点地址0x400550。

图5-3 ELF头的内容

5.3.2 节头
链接器链接时,会将各个文件的雷同段合并成一个大段,而且根据这个大段的巨细以及偏移量重新设置各个符号的地址。

图5-4 节头的内容

5.3.3 步调头
步调头部分是一个布局数组,描述了可执行文件的连续的片映射到连续的内存段的映射关系,描述了可执行文件在内存中的布局和加载信息。每个步调头对应一个段,包罗段的范例、在文件中的偏移、在内存中的捏造地址、物理地址、巨细和访问权限。
PHDR:步调头表
INTERP:步调执行前须要调用的解释器
LOAD:步调目标代码和常量信息
DYNAMIC:动态链接器所使用的信息
NOTE::辅助信息
GNU_EH_FRAME:保存非常信息
GNU_STACK:使用体系栈所须要的权限信息
GNU_RELRO:保存在重定位之后只读信息的位置
可见,最开始是GNU_STACK,而LOAD从0x000e50开始,接着是PHDR,INTERP和NOTE。末了是DYNAMIC和GNU_RELRO部分。

图5-5 步调头的内容

5.3.4 Section to Segment mapping

图5-6 Section to Segment mapping的内容

5.3.5 Dynamic section
Dynamic section用于存储步调在运行时所需的动态链接信息,包罗一系列的动态入口,这些入口提供了步调在运行时获取共享库和其他动态链接信息的方式。

图5-7 Dynamic section的内容

5.3.6 重定位节
此处可见,.rela.text节消失,这表明链接过程完成了重定位利用,但确切地址仍未定,需动态链接后才能确定。

图5-8 重定位节的内容

5.3.7 符号表
符号值(Symbol Value):符号的值或地址,表示符号在内存中的位置。
符号巨细(Size):符号所占据的空间巨细。
符号范例(Type):符号的范例,如函数、对象、未定义符号等。
符号绑定(Bind):符号的绑定范例,指示符号的可见性和链接性,如局部符号、全局符号、弱符号等。
符号可见性(Vis):表示符号可见性,是否可见。
符号的节的索引(Ndx):指示了符号所属的段或节的索引。

图5-9 符号表的内容

5.4 hello的捏造地址空间

使用edb加载hello,查看本历程的捏造地址空间各段信息。由data dump部分可以看出,步调是从0x400000开始加载的,竣事在约0x400ff0位置。
.init段起始地址为0x4004c0,.text段的起始地址为0x400550,.rodata段的起始地址为0x4006a0,.data起始地址为0x601048,在edb中转到相应地址,可以看到捏造地址空间中的内容。

图5-10 使用edb查看hello的捏造地址空间各段信息

5.5 链接的重定位过程分析

使用以下指令对可执行文件hello进行反汇编。
objdump -d -r hello > hello_asm.s


图5-11 在Ubuntu下反汇编hello.o的命令


图5-12反汇编hello得到的反汇编代码hello_asm.s

与反汇编hello.o得到的hello0_asm.s比较,反汇编hello得到的hello_asm.s有三点不同。
(1)链接后函数数量增加;
链接后,反汇编hello得到的反汇编文件中,增加了.plt、puts@plt、printf@plt、getchar@plt、atoi@plt、exit@plt、sleep@plt等函数的代码段,这是由于链接器ld将共享库中源代码hello.c使用的函数加入到可执行文件中。

图5-12 链接后反汇编文件hello_asm.s中函数数量增加

(2)call函数调用指令的参数变革
在链接完成之后,hello 中的所有对于地址的访问或是引用都调用的是绝对捏造地址。这是由于 hello.o 中对于函数还未进行定位,只在.rel.text 中添加了重定位条目,而 hello 进行定位之后自然不须要重定位条目。call后的字节代码直接被链接器ld修改为目标地址与下一条指令的地址之差,指向相应的函数块代码段。
hello0_asm.s


 hello_asm.s


图5-13链接后反汇编文件hello_asm.s中call函数调用指令的参数变革

(3)跳转指令参数发生变革
链接过程进行了符号解析与重定位,符号解析包括目标文件定义和引用符号,将每个符号引用和一个符号定义关联起来,而重定位是指编译器和汇编器生成从 0 开始的代码和数据节。链接器通过把每个符号定义与一个内存位置关联起来,从而重定位这些节,然后修改所有对这些符号的引用,使得它们指向这个内存位置。链接器解析了重定位条目,盘算了相对间隔,将对应位置的字节代码修改为.plt中相应函数与下条指令的相对地址。
hello0_asm.s

 hello_asm.s

图5-14链接后反汇编文件hello_asm.s中跳转指令参数发生变革

5.6 hello的执行流程

表5-1 hello的执行流程

地址

子步调名

00000000004004c0

_init

00000000004004e0

.plt

00000000004004f0

puts@plt

0000000000400500

printf@plt

0000000000400510

getchar@plt

0000000000400520

atoi@plt

0000000000400530

exit@plt

0000000000400540

sleep@plt

0000000000400550

_start

0000000000400580

_dl_relocate_static_pie

0000000000400582

main

0000000000400620

__libc_csu_init

0000000000400690

__libc_csu_fini

0000000000400694

_fini

5.7 Hello的动态链接分析

共享库是一个目标模块,在运行和加载时可以加载到指定的内存地址,并和一个在内存中的步调链接起来,这个过程称为动态链接,当步调调用一个由共享库定义的函数时,由于编译器无法猜测此时函数的地址,GNU编译体系使用延长绑定,将过程地址的绑定推迟到第一次调用该过程时。通过.got和过程链接表.plt的协作来解析函数的地址。从elf中可以读取.got和.plt的起始地址在加载时,动态链接器会重定位.got中的每个条目,使之包罗正确的绝对地址,而.plt中的每个函数负责调用不同函数。那么,通过gdb,便可发现 dl_init后.got.plt 节发生的变革。

图5-15 从hello1.elf读取.got和.plt的起始地址





图5-16 dl_init前后.got.plt 节发生的变革

5.8 本章小结

本章中介绍了链接的概念与作用。通过在Ubuntu下链接的命令得到了hello可执行文件。通过readelf命令得到了链接后的hello可执行文件的ELF格式文本hello1.elf,据此分析了hello1.elf与hello.elf的异同,更好地掌握了链接与重定位的过程,利用edb查看了hello的捏造地址空间,并根据hello的反汇编文件hello_asm.s与hello.o的反汇编文件hello0_asm.s的比较,借助gdb调试器厘清hello的执行流程,分析动态链接前后项目内容变革,加深了对重定位与动态链接的明白。


第6章 hello历程管理

6.1 历程的概念与作用

(1)历程的概念
历程的经典定义就是一个执行中步调的实例。体系中的每个步调都运行在某个历程的上下文(context)中。上下文是由步调正确运行所需的状态构成的。这个状态包括存放在内存中的步调的代码和数据。它的栈、通用目的寄存器的内容、步调计数器、情况变量以及打开文件描述符的聚集。
(2)历程的作用
在现代体系上运行一个步调时,我们会得到一个假象,就好像我们的步调是体系中当前运行的唯一的步调一样。我们的步调好像是独占地使用处置惩罚器和内存。处置惩罚器就好像是无中断地一条接一条地执行我们步调中的指令。末了,我们步调中的代码和数据好像是体系内存中唯一的对象。这些假象都是通过历程的概念提供给我们的。
6.2 简述壳Shell-bash的作用与处置惩罚流程

(1)shell的作用
shell 是一个交互型应用步调,它代表用户运行其他步调。shell 执行一系列的读/求值(read/evaluate)步骤,然后终止。读步骤读取来自用户的一个命令行,求值步骤解析命令行,并根据解析效果运行步调。
(2)shell的处置惩罚流程:
(a)从shell终端读入输入的命令;
(b)将输入字符串切分,得到并识别所有的参数并构造传递;
(c)假如是内置命令则立即执行;
(d)否则调用fork步调为其分配子历程并运行;
(e)假如输入参数非法,则返回错误信息;
(f)命令执行完成后,shell再次显示提示符,等待用户输入下一条命令。该过程循环进行,直到用户主动退出或关闭shell会话。
6.3 Hello的fork历程创建过程

父历程通过调用 fork 函数创建一个新的运行的子历程。调用 fork 函数后,新创建的子历程险些但不完全与父历程雷同。子历程得到与父历程捏造地址空间雷同的但是独立的一份副本,包括代码、数据段、堆、共享库以及用户栈,子历程还得到与父历程任何打开文件描述符雷同的副本,这意味着当父历程调用 fork 时,子历程可以读写父历程中打开的任何文件。但父历程和子历程的PID不同。fork被调用一次,却返回两次,子历程返回0,父历程返回子历程的 PID。父历程与子历程是并发运行的独立历程,内核能够以任意方式瓜代执行它们的逻辑控制流的指令。在子历程执行期间,父历程默认选项是显示等待子历程的完成。
6.4 Hello的execve过程

当创建了一个子历程之后,子历程调用execve函数在当前子历程的上下文中加载并运行一个新的步调——hello步调。
execve:execve调用驻留在内存中被称为启动加载器的利用体系代码来执行Hello步调,加载器删除子历程现有的用户捏造内存段部分中已存在的地域布局,为新步调的代码、数据、bss和栈地域创建新的地域布局。所有这些地域布局都是私有的,写时复制的。捏造地址空间的代码和数据地域被映射为hello文件的.text和.data区。bss地域是请求二进制零的映射匿名文件,其巨细包罗在hello文件中。栈和堆地域也是请求二进制零的,初始长度为零。末了加载器设置PC 指向_start 地址,_start 最终调用hello中的main 函数。然后加载器将PC指向hello步调的起始位置(_start),即从下条指令开始执行hello步调。

图6-1 linux x86-64运行时内存映像

6.5 Hello的历程执行

用户模式和内核模式:处置惩罚器必须提供一种机制,限制一个应用可以执行的指令以及它可以访问的地址空间范围,从而使利用体系内核提供一个无懈可击的历程抽象。处置惩罚器通常是用某个控制寄存器的一个模式位来提供能这种功能的,该寄存器描述了历程当前享有的特权。当设置了模式位时,历程就运行在内核模式中,该历程可以执行指令会合的任何命令,而且可以访问体系中的任何内存位置。当没有设置模式位时,历程就处于用户模式中,用户模式的历程不允许执行特权指令,也不允许直接引用地址空间中内核区内的代码和数据。
上下文:上下文就是内核重新启动一个被抢占的历程所须要的状态,它由一些对象的值构成,这些对象包括通用目的寄存器、浮点寄存器、步调计数器、用户栈、状态寄存器、内核栈和各种内核数据布局。
上下文切换:当内核选择一个新的历程运行时,则内核调度了这个历程。在内核调度了一个新的历程运行后,它就抢占当前历程,并使用一种称为上下文切换的机制来将控制转移到新的历程:(1)保存之前历程的上下文(2)恢复某个先前被抢先的历程被保存的上下文(3)将控制传递给这个新恢复的历程。

图6-2 历程上下文切换示意图

6.6 hello的非常与信号处置惩罚

当接受到不同的非常信号时,非常处置惩罚步调将对非常信号做出相应,执行相应的代码,每种信号都有不同的处置惩罚机制,对不同的非常信号,hello也有不同的处置惩罚效果。
hello步调大概会出现的非常有:
(1)中断:在步调执行过程中大概出现外部I/O设备引起的非常,或时钟中断等。中断处置惩罚步调将控制返回给应用步调控制流中的下一条指令。

图6-3 中断处置惩罚。中断处置惩罚步调将控制返回给应用步调控制流中的下一条指令。

(2) 陷阱:陷阱是执行一条指令的效果,hello执行sleep函数时会出现这个非常。陷阱处置惩罚步调将控制返回给应用步调控制流中的下一条指令。

图6-4 陷阱处置惩罚。陷阱处置惩罚步调将控制返回给应用步调控制流中的下一条指令。

(3) 故障:大概会发生缺页故障等。根据故障是否能够被修复,故障处置惩罚步调要么重新执行引起故障的指令,要么终止。

图6-5 故障处置惩罚。根据故障是否能够被修复,故障处置惩罚步调要么重新执行引起故障的指令,要么终止。

(4) 终止:终止是不可恢复的错误,在hello执行过程大概会出现DRAM或SRAM位损坏的奇偶错误等。终止处置惩罚步调将控制传递给一个内核abort例程,该例程会终止这个应用步调。

图6-6 终止处置惩罚。终止处置惩罚步调将控制传递给一个内核abort例程,该例程会终止这个应用步调。

不停乱按,乱码会被认为是命令,不影响历程。

图6-7 不停乱按

按下Ctrl+Z,产生中断非常,它的父历程收到SIGSTP信号,步调自己这时被挂起,Ctrl+Z 后运行 ps,打印出了各历程的 pid,可以看到之前挂起的历程 hello。Ctrl+Z 后运行 jobs,打印出了被挂起历程组的 jid,可以看到之前被挂起的 hello,已被挂起的标识“已停止”,此时运行fg可使停止的hello历程继承在前台运行。

图6-8 按下Ctrl+Z及使用jobs查看历程组jid


图6-9 使用命令ps查看历程组pid


图6-10 使用命令fg继承历程运行

使用命令pstree可以将hello的所有历程以树状图情势展示。


图6-11 使用命令pstree以树状图情势展示 hello的所有历程

按下Ctrl+Z 后运行 Kill:重新执行历程,可以发现 hello 的历程号为2148,那么便可通过 kill -9 2148发送信号 SIGKILL 给历程2148,它会导致该历程被杀死。然后再运行 ps,可发现已被杀死的历程 hello。

图6-12 使用命令kill杀死历程

按下 Ctrl+C:历程收到 SIGINT 信号,竣事 hello。在 ps 中查询不到其 PID,在job 中也没有显示,可以看出 hello 已经被彻底竣事。

图6-13 按下Ctrl+C及使用ps查看历程是否被竣事

6.7本章小结

本章介绍了历程的概念与作用以及Shell-bash的根本概念,从可执行文件hello的执行过程出发,到fork历程创建、execve加载、历程执行和终止,以及通过键盘输入等过程。在hello运行过程中,内核有选择对其进行管理,决定何时进行上下文切换,须要各种各样的非常和中断等信息。步调的高效运行离不开非常、信号、历程等概念,正是这些机制支持 hello 能够顺利地在盘算机上运行。


第7章 hello的存储管理

7.1 hello的存储器地址空间

(1)物理地址:数据真正存在主存DRAM中的位置,通过地址信号线发送给主存。盘算机体系的主存被构造成一个由M个连续的字节巨细的单元构成的数组。每字节都有一个唯一的物理地址(Physical Address)。
(2)捏造地址:即线性地址,是步调员用来访问一个内存单元的整数地址。一个逻辑地址在经过段地址机制的转化后变成一个线性分页地址,它与逻辑地址类似也是一个不真实的地址。其格式可以表示为“捏造地址描述符:偏移量”。
(3)逻辑地址:“段地址:偏移地址”,是步调经过编译后出如今汇编代码中的地址,用来指定一个利用数或一条指令的地址。它由选择符和偏移量构成。linux下实质上就是捏造地址。
7.2 Intel逻辑地址到线性地址的变更-段式管理

一个逻辑地址由两部份构成,段标识符: 段内偏移量。段标识符是由一个16位长的字段构成,称为段选择符。此中前13位是一个索引号。后面3位包罗一些硬件细节,表示具体的是代码段寄存器还是栈段寄存器抑或是数据段寄存器。

图7-1 逻辑地址的构成

索引号就是“段描述符(segment descriptor)”的索引,段描述符具体地址描述了一个段。许多个段描述符,就组了一个数组,叫“段描述符表”,这样,可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,每一个段描述符由8个字节构成,Base字段表示的是包罗段的首字节的线性地址,也就是一个段的开始位置的线性地址。一些全局的段描述符,就放在“全局段描述符表(GDT)”中,一些局部的,例如每个历程自己的,就放在所谓的“局部段描述符表(LDT)”中。那究竟什么时间该用GDT,什么时间该用LDT呢?这是由段选择符中的T1字段表示的,=0,表示用GDT,=1表示用LDT,GDT在内存中的地址和巨细存放在CPU的gdtr控制寄存器中,而LDT则在ldtr寄存器中。将段描述符中的基地址与偏移量相加,得到线性地址。假如启用了分页机制,则线性地址大概还须要进一步转换为物理地址,否则线性地址就是物理地址。
7.3 Hello的线性地址到物理地址的变更-页式管理

在hello步调中,线性地址(VA)到物理地址(PA)之间的转换通过对捏造地址内存空间进行分页的分页机制完成。将各历程的捏造空间划分成多少个长度相等的页(page),页式管理把内存空间按页的巨细划分成片,然后把页式捏造地址与内存地址创建一一对应的页表。通过7.2节中的段式管理过程,可以得到了线性地址/捏造地址,记为VA。捏造地址可被分为两个部分:VPN(捏造页号)和VPO(捏造页偏移量),根据盘算机体系的特性可以确定VPN与VPO的具体位数,由于捏造内存与物理内存的页巨细雷同,因此VPO与PPO(物理页偏移量)同等。而PPN(物理页号)则需通过访问页表中的页表条目(PTE)获取,如下图所示。

图7-2 使用页表的地址翻译

若PTE有效位为1,则发生页掷中,可以直接获取到物理页号PPN,PPN与PPO共同构成物理地址。若PTE的有效位为0,说明对应捏造页没有缓存到物理内存中,产生缺页故障,调用利用体系的内核的缺页处置惩罚步调,确定牺牲页,并调入新的页面。再返回到原来的历程,再次调用导致缺页的指令。此时发生页掷中,获取到PPN,与PPO共同构成物理地址。
7.4 TLB与四级页表支持下的VA到PA的变更

我们以一个实际体系的案例来研究TLB与四级页表支持下的VA到PA的变更:一个运行Linux的Intel Core i7。固然底层的Haswell微体系布局允许完全的64位捏造和物理地址空间,而如今的(以及可预见的未来的)Core i7实现支持48位(256TB)捏造地址空间和52位(4PB)物理地址空间,还有一个兼容模式,支持32位(4GB)捏造和物理地址空间。
Intel Core i7处置惩罚器封装(processor package)包括四个核、一个大的所有核共享的L3高速缓存,以及一个DDR3内存控制器。每个核包罗一个层次布局的TLB、一个层次布局的数据和指令高速缓存,以及一组快速的点到点链路,这种链路基于QuickPath技能,是为了让一个核与其他核和外部I/O桥直接通信。TLB是捏造寻址的,是四路组相联的。LI、L2和L3高速缓存是物理寻址的,块巨细为64字节。L1和L2是8路组相联的,而L3是16路组相联的。页巨细可以在启动时被设置为4KB或4MB。Linux使用的是4KB的页。

图7-3 Intel Core i7的内存体系

Core i7接纳四级页表的层次布局。VPO与PPO有p=12位,故VPN为36位,PPN为40位。单个页表巨细4KB,PTE巨细8字节,则单个页表有512个页表条目,须要9位二进制进行索引,而四级页表则须要36位二进制进行索引,对应着36位的VPN。TLB有16组,故TLBI有t=4位,TLBT有36-4=32位。
CPU产生捏造地址VA,捏造地址VA传送给MMU,MMU使用VPN高位作为TLBT和TLBI,向TLB中寻找匹配。假如掷中,则得到物理地址PA。假如TLB中没有掷中,MMU查询页表,CR3确定第一级页表的起始地址,VPN1确定在第一级页表中的偏移量,查询出PTE,以此类推,最终在第四级页表中找到PPN,与VPO组合成物理地址PA,添加到PLT。

图7-4 Intel Core i7页表翻译的概况

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

在具备三级缓存(Cache)支持的情况下,物理内存访问可以通过缓存来进步访问速度和效率。三级缓存由L1、L2和L3三个层级构成,每个层级的缓存容量逐级增大,但访问延长也逐级增加。
当步调进行物理内存访问时,体系起首会检查L1缓存,也称为指令缓存(Instruction Cache)和数据缓存(Data Cache),来确定是否存在所需数据的副本。对Cache的访问须要把一个物理地址分为标志(CT)、组索引(CI)、块偏移(CO)三个部分。起首我们通过组索引来找到我们的地址在Cache中所对应的组号,再通过标志和Cache的有效位来判断我们的内容是否在Cache中。假如数据在L1缓存中掷中,即缓存掷中,体系可以直接从L1缓存中获取所需数据,从而制止了访问主内存的开销,进步了访问速度。
若数据在L1缓存未掷中,体系会继承检查L2缓存,也称为高速缓存(Unified Cache)。L2缓存容量比L1缓存大,但访问延长也相应增加。若数据在L2缓存中掷中,体系将从L2缓存中获取数据,制止直接访问主内存,尽管相比于L1缓存,访问速度略有下降。当数据在L2缓存未掷中时,体系将进一步检查L3缓存。
假如数据在三级缓存中都未掷中,体系将通过主内存来获取所需数据。此时会发生缓存不掷中(Cache Miss),体系将从主内存中读取数据,并将其加载到适当的缓存层级中,以供后续访问使用。

图7-5 Intel Core i7的3级Cache的概况

7.6 hello历程fork时的内存映射

Linux中除了根历程以外的历程都是由某个历程通过fork()自我复制而来的.在成功的情况下fork()调用一次,返回两次(父子历程中各返回一次,子历程中的“返回”实际上是子历程“得到新生”),通过fork()的返回值区分父子历程,而且它们也有不同的历程编号ID。
fork()会将子历程的捏造页映射到父历程的页上,有的内存区二者共享(例如共享库),更多的内存区理论上应由历程分别私有,linux使用写时复制策略,使用这些页临时在PTE上标志为只读,当此中一个历程尝试修改该页时,引发故障,内核为历程复制一份私有的页用来修改。这样既保证了内存空间的独立,又尽量通过共用页进步空间效率。
7.7 hello历程execve时的内存映射

体系调用execve将一个可执行文件加载到当前历程的上下文中,替换掉原来的步调。execve调用成功后不返回(由于原步调被杀死,原有效户地域被execve删除),也不创建新的历程。
execve实际上并不会直接把可执行文件的内容复制到物理内存,而是使用了内存映射机制,代码和数据区直接映射到可执行文件中的.text、data、.rodata等节(平凡文件的内存映射,而且写时复制),等到用的时间缺页再加载。.bss、栈、堆都将映射到匿名文件,它们不会从磁盘中加载页,而是缺页时由内核直接填充二进制0。栈和堆的初始长度都是0(空),.bss的长度由可执行文件指定。
execve会将参数列表argv[]以及情况变量列表envp[]传递给加载的步调。步调可以使用带参数的main接受它们。这两个列表内的元素都是字符串指针,以NULL结尾。argv实际上相当于用户在shell内输入的命令行按空格分隔而成的字符串们,例如命令行“./me arg1 arg2”得到的argv[]就是“me”,“arg1”,“arg2”,argv[0]为步调名。

图7-6加载器映射用户地址空间地域的方式

7.8 缺页故障与缺页中断处置惩罚

当一条访问内存的指令执行时,这条指令会将捏造地址提供给CPU,CPU会通过地址翻译单元MMU查询捏造地址对应的捏造页号(取捏造地址的高多少位)在主存页表中对应的PTE。果这个页分配但未缓存,那么MMU将触发缺页故障,即页不掷中。内核的处置惩罚步调采取页替换算法将主存里的某个物理页替换到磁盘里,并从磁盘内将目标页加载到这个物理位置,并修改页表,返回后重新执行内存访问指令。总之,MMU要通过页表得到捏造地址对应的物理页,再访问物理页上对应页偏移(即地址的低多少位)的内存单元。
示例如下,CPU引用了 VP 3中的一个字,VP 3并未缓存在DRAM中。地址翻译硬件从内存中读取PTE3,从有效位推断出VP 3未被缓存,VP3中的字的引用会不掷中,触发一个缺页非常。缺页非常调用内核中的缺页非常处置惩罚步调,缺页处置惩罚步调选择VP 4作为牺牲页,从磁盘复制VP3到内存中的PP3,更新PTE3,随后返回。当非常处置惩罚步调返回时,它会重新启动导致缺页的指令,该指令会把导致缺页的捏造地址重发送到地址翻译硬件。但是如今,VP3已经缓存在主存中了,那么页掷中也能由地址翻译硬件正常处置惩罚了。

图7-7 VM缺页(之前),VP3中的字的引用会不掷中.从而触发了缺页


图7-8 VM缺页(之后)。缺页处置惩罚步调选择VP 4作为牺牲页,并从磁盘上用VP 3的副本取代它。

7.9动态存储分配管理

动态内存分配器维护着一个称为堆的历程的捏造内存地域。分配器将堆视为一组不同巨细的块的聚集来维护。每个块就是一个连续的捏造内存片,要么是已分配的,要么是空闲的。已分配的块显式地保存为供应用步调使用。空闲块可用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放可以由应用步调显式执行或内存分配器自身隐式执行。
分配器有两种根本风格,显式分配器和隐式分配器。显示分配器要求应用显式地释放任何已分配的块;隐式分配器要求分配器检测一个已分配块何时不再使用,那么就释放这个块,主动释放未使用的已经分配的块的过程叫做垃圾收集。

图7-5 堆

C标准库提供了一个称为malloc步调包的显式分配器。步调通过调用malloc函数来从堆中分配块。malloc函数返回一个指针,指向巨细为至少size字节的内存块,这个块会为大概包罗在这个块内的任何数据对象范例做对齐。实际中,对齐依赖于编译代码在32位模式(gcc -m32)还是64位模式(默认的)中运行。在32位模式中,malloc返回的块的地址总是8的倍数。在64位模式中,该地址总是16的倍数。以下是一个C步调的16字小堆的案例。每个方框代表了一个4字节的字。粗线标出的矩形对应于已分配块(有阴影的)和空闲块(无阴影的)。初始时,堆是由一个巨细为16个字的、双字对齐的、空闲块构成的。(假设分配器返回的块是8字节双字边界对齐的)
(a)步调请求一个4字的块。malloc的响应是:从空闲块的前部切出一个4字的块,并返回一个指向这个块的第一字的指针。
(b)步调请求一个5字的块。malloc的响应是:从空闲块的前部分配一个6字的块。在本例中,malloc 在块里填充了一个额外的字,是为了保持空闲块是双字边界对齐的。
(c)步调请求一个6字的块,malloc就从空闲块的前部切出一个6 字的块。
(d)步调释放在(b)中分配的那个6字的块。留意,在调用free 返回之后,指针p2仍旧指向被释放了的块。应用有责任在它被一个新的malloc调用重新初始化之前,不再使用p2。
(e)步调请求一个2字的块。在这种情况中,malloc分配在前一步中被释放了的块的一部分,并返回一个指向这个新块的指针。

图7-5 malloc和free分配与释放块的案例

7.10本章小结

本章重要介绍了hello的存储器的地址空间,介绍了四种地址空间的差别和地址的相互转换。同时介绍了hello的四级页表的捏造地址空间到物理地址的转换。阐释了三级cache的物理内存访问、历程fork时的内存映射、execve时的内存映射、缺页故障与缺页中断处置惩罚、动态存储分配管理。

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

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

,所有的I/O设备(例如网络、磁盘和终端)都被模型化为文件,而所有的输入和输出都被当作对相应文件的读和写来执行。这种将设备优雅地映射为文件的方式,允许Linux内核引出一个简单、低级的应用接口,称为Unix I/O,这使得所有的输入和输出都能以一种同一且同等的方式来执行:打开文件;打开标准输入、标准输出、标准错误;改变当前文件的位置;读写文件;关闭文件。
设备的模型化:文件
设备管理:unix io接口
8.2 简述Unix IO接口及其函数

8.2.1 Unix I/O接口
(1)打开文件
一个应用步调通过要求内核打开相应的文件,来宣告它想要访问一个I/O设备。内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有利用中标识这个文件。内核记录有关这个打开文件的所有信息。应用步调只需记着这个描述符。Linux shell创建的每个历程开始时都有三个打开的文件:标准输入(描述符为0)、标准输出(描述符为1)和标准错误(描述符为2)。头文件<unistd.h>定义了常量STDIN_ FILENO、STDOUT_FILENO和STDERR_FILENO,它们可用来代替显式的描述符值。
(2)改变当前的文件位置
对于每个打开的文件,内核保持着一个文件位置k,初始为O。这个文件位置是从文件开头起始的字节偏移量。应用步调能够通过执行seek利用,显式地设置文件的当前位置为k。
(3)读写文件
一个读利用就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增加到k+n。给定一个巨细为m字节的文件,当k≥m时执行读利用会触发一个称为end-of-file(EOF)的条件,应用步调能检测到这个条件。在文件结尾处并没有明白的“EOF符号”。类似地,写利用就是从内存复制n>0个字节到一个文件,从当前文件位置k开始,然后更新k。
(4)关闭文件
当应用完成了对文件的访问之后,它就通知内核关闭这个文件。作为响应,内核释放文件打开时创建的数据布局,并将这个描述符恢复到可用的描述符池中。无论一个历程由于何种原因终止时,内核都会关闭所有打开的文件并释放它们的内存资源。
8.2.2 Unix I/O函数
表8-1 Unix I/O函数

函数名称

功能描述

所需头文件

函数原型

返回值

参数

open()

用于打开或创建文件,在打开或创建文件时可以指定文件的属性及用户的权限等各种参数.
#include<sys/types.h>

#include<sys/stat.h>

#include<fcntl.h>

int open(const char *pathname,int flags,int perms)
成功返回文件描述符;失败返回-1.
pathname:被打开的文件名(可包括路径名如"dev/ttyS0")flags:文件打开方式,

close()

用于关闭一个被打开的的文件.
#include <unistd.h>
int close(int fd)
0成功,-1堕落.
fd文件描述符

read()

从文件读取数据.

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

返回所读取的字节数;0(读到EOF);-1(堕落).

fd:将要读取数据的文件描述词。buf:指缓冲区,即读取的数据会被放到这个缓冲区中去。count: 表示调用一次read利用,应该读多少数量的字符。

write()

向文件写入数据.

#include <unistd.h>
ssize_t write(int fd, void *buf, size_t count);

写入文件的字节数(成功);

-1(堕落)

同read

lseek()

用于在指定的文件描述符中将将文件指针定位到相应位置.

#include <unistd.h>
#include <sys/types.h>
off_t lseek(int fd, off_t offset,int whence);

成功返回当前位移,失败返回-1

fd:文件描述符.offset:偏移量,每一个读写利用所须要移动的间隔,单元是字节,可正可负(向前移,向后移)

stat()

读取文件元数据.

#include <unistd.h>
#include <sys/stat.h>
int stat(const char *filename, struct stat *buf);

返回st_size成员包罗的文件字节数巨细,st_mode成员编码的文件访问允许位和文件范例

filename文件名;stat数据布局中的各个成员

8.3 printf的实现分析

printf函数如下:
printf(const char *fmt,…)

{

    int i;

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

    i = vsprintf(buf, fmt, arg);

    write(buf, i);

    return i;

}

其声明了一个长度1024的缓冲区,之后创建可变参数表,随后调用vsprintf将可变参数填入格式字符串进行格式化,放到缓冲区中,随后打印缓冲区内信息
vsprintf函数如下:
int vsprintf(char *buf,const char *fmt,va_list args)

{

    char *p;

    char tmp[256];

    va_listp_next_arg = args;

    for (p = buf; *fmt; fmt++)

    {

        if(*fmt != '%')

        {

            *p++ = *fmt;

            continue;

        }

        fmt++;

        switch(*fmt)

        {

            case 'x':

                itoa(tmp, *((int *)p_next_arg));

                strcpy(p, tmp);

                p_next_arg += 4;

                p += strlen(tmp);

                break;

            case 's':

                break;

            default:

                break;

        }

        return (p - buf);

    }

}

vsprintf的重要作用就是格式化。它接受确定输出格式的格式字符串fmt。用格式字符串对个数变革的参数进行格式化,产生格式化输出。
进一步对write进行追踪:
write:

     mov eax, _NR_write

     mov ebx, [esp + 4]

     mov ecx, [esp + 8]

     int INT_VECTOR_SYS_CALL

此处给eax、ebx、ecx、esp等几个寄存器传递了参数,然后以int INT_VECTOR_SYS_CALL竣事。INT_VECTOR_SYS_CALL代表通过体系调用syscall,再查看syscall的实现:
sys_call:

     call save

   

     push dword [p_proc_ready]

   

     sti

   

     push ecx

     push ebx

     call [sys_call_table + eax * 4]

     add esp, 4 * 3

   

     mov [esi + EAXREG - P_STACKBASE], eax

   

     cli

   

     ret

syscall将字符串中的字节从寄存器中通过总线复制到显卡的显存中,显存中存储的是字符的ASCII码,符显示驱动子步调:从ASCII到字模库到显示VRAM(存储每一个点的RGB颜色信息)。显示芯片按照革新频率逐行读取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;  

}

getchar标准输入里读取下一个字符,相当于getc(stdin)。返回范例为 int 型,为用户输入的 ASCII 码或 EOF。getchar 可用宏实现:#define getchar() getc(stdin)。getchar 有一个 int 型的返回值。当步调调用 getchar时,步调就等着用户按键。用户输入的字符被存放在键盘缓冲区中。直到用户按回车为止(回车字符也放在缓冲区中)。当用户键入回车之后,getchar 才开始从stdin流中每次读入一个字符。getchar 函数的返回值是用户输入的字符的 ASCII 码,若文件结尾(End-Of-File)则返回-1(EOF),且将用户输入的字符回显到屏幕。
异步非常-键盘中断的处置惩罚:键盘中断处置惩罚子步调。接受按键扫描码转成ascii码,保存到体系的键盘缓冲区。
getchar等调用read体系函数,通过体系调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结

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


结论

(1)总结hello所履历的过程
(a) 预处置惩罚:hello.c预处置惩罚到hello.i文本文件;
(b) 编译:hello.i编译到hello.s汇编文件;
(c) 汇编:hello.s汇编到二进制可重定位目标文件hello.o;
(d) 链接:hello.o链接生成可执行文件hello;
(e) 创建子历程:bash历程调用fork函数,生成子历程;
(f) 加载步调:execve函数加载运行当前历程的上下文中加载并运行新步调hello;
(g) 访问内存:hello的运行须要地址的概念,捏造地址是盘算机体系最伟大的抽象;
(h) 交互:hello的输入输出与外界交互,与linux I/O息息相干;
(i) 终止:hello最终被shell父历程接纳,内核会收回为其创建的所有信息。
(2)感悟
盘算机体系的计划与实现是一个不断演进的过程,要求我们不断学习温顺应新技能、新理念。盘算机体系这门课理论与实践联合、硬件与软件联合,我们不但能更深入地明白高级编程语言、汇编语言、盘算机构成、数据布局、算法计划、利用体系等多个领域,还能为未来的创新奠定结实的底子。让我们继承探索,不断挑战自我,为盘算机科学与技能的发展贡献气力。

附件

文件名

功能

hello.i

预处置惩罚器修改后的源步调,用来分析预处置惩罚器的行为

hello.o

可重定位目标步调,用来分析汇编器的行为

hello.s

可重定位目标步调,用来分析汇编器的行为

hello.elf

hello.o的elf格式

hello0_asm.s

hello.o的反汇编代码

hello1.elf

hello的elf格式

hello

Linux下的可执行目标步调,用来分析链接器行为

hello_asm.s

hello反汇编之后的可重定位文件,用来分析重定位过程



参考文献

[1]  深入明白盘算机体系原书第3版.
[2]  林来兴. 空间控制技能[M]. 北京:中国宇航出书社,1992:25-42.
[3]  辛希孟. 信息技能与信息服务国际研讨会论文集:A集[C]. 北京:中国科学出书社,1999.
[4]  赵耀东. 新时代的工业工程师[M/OL]. 台北:天下文化出书社,1998 [1998-09-26]. http://www.ie.nthu.edu.tw/info/ie.newie.htm(Big5).
[5]  谌颖. 空间交会控制理论与方法研究[D]. 哈尔滨:哈尔滨工业大学,1992:8-13.
[6]  KANAMORI H. Shaking Without Quaking[J]. Science,1998,279(5359):2063-2064.
[7]  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.


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4