瑞星 发表于 2024-7-17 04:49:05

计算机系统大作业:步伐人生—Hello‘s P2P

 
 
https://img-blog.csdnimg.cn/direct/d899b739ef5a43f69a5cba35396f5c07.png
计算机系统

大作业






题          目      步伐人生—Hello’s P2P   
专          业        计算机科学与技术         
 学     号          2022112588                
 班          级             2203103                   
 学          生               向宇                        
   指 导 教 师             史先俊                       



计算机科学与技术学院
2023年11月




摘  要
        本文以一个简朴的hello.c步伐开始,介绍了其在Linux下运行的完备生命周期,包括预处置处罚、编译、汇编、链接、进程管理、存储管理、I/O管理这七部分,详细介绍了步伐从被键盘输入、生存到磁盘,直到末了步伐运行结束,寂静逝去的全过程。本文通过清楚地观察hello.c的完备周期,直观地表现其生命历程。              
        关键词:hello、Linux、步伐、生命周期


目  录

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



第1章 概述

1.1 Hello简介

        ① P2P(From Program to Process)过程:
        hello的生命周期是从一个高级C语言步伐开始的,分为四个阶段:首先经过预处置处罚器cpp进行预处置处罚,生成文本文件hello.i,然后经过编译器ccl生成hello.s汇编步伐,接着经过汇编器as生成hello.o文件,末了经过链接器ld将其与引用到的库函数链接,生成可执行文件hello。再通过系统创建一个新进程并且把步伐内容加载,实现有步伐到进程的转化。
        ② O2O(From Zero-0 to Zero-0)过程:
        当步伐员在shell中运行可执行目标文件hello时,shell识别出这是一个外部命令,先调用 fork函数创建了一个新的子进程(Process),然后调用execve函数在新的子进程中加载并运行hello。运行hello还需要CPU为hello分配内存、时间片。在hello运行的过程中,CPU要访问相关数据需要MMU的假造地址到物理地址的转化,其中 TLB和四级页表为进步地址翻译的速度做出了巨大贡献,得到物理地址后三级 Cache又资助CPU快速得到需要的字节。系统的进程管理资助hello切换上下文、shell的信号处置处罚步伐使得hello在运行过程中可以处置处罚各种信号,当步伐员主动地按下Ctrl+Z大概hello运行到“return 0”;时hello所在进程将被杀死,shell会接纳它的僵死进程。

1.2 环境与工具

        硬件环境:X64 CPU;2GHz;2G RAM;256GHD Disk以上;
        软件环境:Windows10 64位; Vmware 11; Ubuntu 16.04 LTS 64位;
        开辟工具:CodeBlocks;vi/vim/gpedit+gcc;gdb;edb;readelf;objdump等。

1.3 中间结果

        得到的中间文件:
        hello.i:            hello.c经预处置处罚得到的文本文件
        hello.s:           hello.i 经编译步伐的汇编文件
        hello.o:           hello.s经得到的可重定位目标文件
        hello_elf.txt:    hello.o经readelf分析得到的文本文件
        hello_dis.txt:    hello.o经objdump反汇编得到的文本文件
        hello:               hello.o经链接得到的可执行目标文件
        hello1_elf.txt:    hello经readelf分析得到的文本文件
        hello1_dis.txt:    hello经objdump反汇编得到的文本文件

1.4 本章小结

        本章通过简朴介绍hello.c步伐一生中的P2P过程和020过程,展示了一个源步伐是如何经过预处置处罚、编译、汇编、链接等阶段,生成各种各样的中间文件,终极成为一个可执行目标文件的。本章也介绍了本次实验所用到的硬件环境、软件环境以及开辟工具等。



第2章 预处置处罚

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

        预处置处罚是C语言步伐从源代码酿成可执行步伐的第一步,主要处置处罚步伐中的预处置处罚命令。预处置处罚包括处置处罚宏定义、处置处罚特殊符号、处置处罚条件编译等。预处置处罚指令一样寻常被用来使源代码在不同的执行环境中被方便的修改大概编译。        
        预处置处罚的作用主要是使得步伐便于阅读、修改、移植和调试,让编译器在随后的文本进行编译的过程中更方便,也有利于模块化步伐设计。
        详细地说,预处置处罚有如下功能:
        ①宏睁开:预处置处罚步伐中的“#define”标识符文本,用现实值(可以是字符 串、代码等)更换用“#define”定义的字符串;
        ②文件包罗复制:预处置处罚步伐中用“#include”格式包罗的文件,将文件的内 容插入到该命令所在的位置并删除原命令,从而把包罗的文件和当前源文件毗连成一个新的源文件,这与复制粘贴类似;
        ③条件编译处置处罚:根据“#if”和“#endif”、“#ifdef”和“#ifndef”后面的条件确定需要编译的源代码。

2.2在Ubuntu下预处置处罚的命令

        预处置处罚命令:gcc -E hello.c -o hello.i
        https://img-blog.csdnimg.cn/direct/e28248a1e78940d588f2968a1ec4d923.png          https://img-blog.csdnimg.cn/direct/6d1d7d6c41964e7a9e2ca40ce90fccd9.png

2.3 Hello的预处置处罚结果解析

        原本除注释外的18行源步伐在经过预处置处罚后酿成了3092行。
        1~6行为有关源步伐的一些信息:
https://img-blog.csdnimg.cn/direct/1f46e9a9dffb41f3906236abc0ddbe94.png
        随后依次进行头文件stdio.h、unistd.h、stdlib.h的睁开。
        以stdio.h为例介绍睁开的详细过程:
        stdio.h是C语言标准库文件,cpp会到Linux系统的环境变量下探求stdio.h,打开/usr/include/stdio.h(详细见下图文件包罗信息),
https://img-blog.csdnimg.cn/direct/da7fcf8f58794955baea61eb76b178a5.png
        随后cpp发现stdio.h使用了“#define”、“#include” 等,故cpp对它们进行递归睁开更换,终极的hello.i文件中删除了原有的这部分;对于其中使用的“#ifdef”、“#ifndef”等条件编译语句,cpp会对条件值进行判定来决定是否对此部分进行包罗(终极结果详细见下图的类型定义信息和函数声明信息),
https://img-blog.csdnimg.cn/direct/8639c95e36e34861aef776368f8515ef.png
https://img-blog.csdnimg.cn/direct/b072fc34222a41b7bb31bf0041365cdf.png
        末了的部分是hello.c的源代码,由下图我们可以观察到除注释和以“#”开头的语句被删除外,其他内容保持不变:
 
https://img-blog.csdnimg.cn/direct/808a4e158e014f90a50a11a6410092d2.png

2.4 本章小结

        本章介绍了预处置处罚的概念和作用,以及预处置处罚的指令,随后分析了预处置处罚的过程与结果。通过本章的学习,相识到C 语言预处置处罚一样寻常由预处置处罚器(cpp)进行,主要完成四项工作:宏睁开、文件包罗复制、条件编译处置处罚和删除注释及多余空缺字符。



第3章 编译

3.1 编译的概念与作用

        编译是指对经过预处置处罚之后的源步伐代码进行分析检查,确认所有语句均符合语法规则后将其翻译成等价的中间代码或汇编代码的过程,在此处指编译器将 hello.i 翻译成 hello.s。编译通常包括以下步调:
        ①词法分析:这是编译的第一步,将源代码转换为令牌(tokens)。
        ②语法分析:这一步将上一步生成的结果(令牌流)转化为抽象语法树(AST)。
        ③语义分析:检查源代码是否符合语言的规则和约定,同时进行类型检查等。
        ④中间代码生成:将AST转化为中间代码,通常是与平台无关的代码。
        ⑤代码优化:优化中间代码,以进步生成代码的性能。
        ⑥目标代码生成:将优化后的中间代码转化为特定平台机器码,这将是终极的可执行步伐。
        编译的作用是将用高级语言誊写的源步伐转换为一条条机器指令,机器指令和汇编指令一一对应,使机器更容易明白,为汇编做准备。
     
3.2 在Ubuntu下编译的命令

编译命令:gcc -S hello.i -o hello.s
https://img-blog.csdnimg.cn/direct/b3f471b5389c433a961c45f9c920e5ba.png
3.3 Hello的编译结果解析

        编译过程是整个过程构建的核心部分,编译成功,源代码会从文本形式转换为机器语言。下面是hello.s汇编文件内容:
        3.3.1对文件信息的记载
https://img-blog.csdnimg.cn/direct/8e52dc48f20d4c18a5c61f39651e2f8f.png
        首先是记载文件相关信息的汇编代码,为之后链接过程使用。其中.file表明了源文件,.text代码段,.section .radata只读代码段,.align对齐方式为8字节对齐,.string字符串,.global全局变量,.type声明main是函数类型。
        3.3.2对局部变量的操作
https://img-blog.csdnimg.cn/direct/a7607b5cda844696b031dce194f31e49.png
https://img-blog.csdnimg.cn/direct/5a8af529b3a647269f39074f32990370.png
        局部变量存储在栈中,当进入函数main的时候,会根据局部变量的需求,在栈上申请一段空间供局部变量使用。政府部变量的生命周期结束后,会在栈上开释。可以看到在L2处局部变量i被存储在-4(%rbp)处,并赋值为0,即i的初始化。
        3.3.3对字符串常量的操作
        在main函数前,在.rodata处的.LC0和.LC1已经存储了字符串常量,标记该位置是代码是只读的。在main函数中使用字符串时,得到字符串的首地址,如下图:
https://img-blog.csdnimg.cn/direct/6caf0a58e40141829ad3ebbe9f4638dc.png
        3.3.4对立即数的操作
        立即数直接用“$+数字”表示:
https://img-blog.csdnimg.cn/direct/d1d6bad215a44b319ebd1606c68845a9.png
        3.3.5 main函数参数的通报
        在main函数的开始部分,因为后面还会使用到%rbp数组,所以先将%rbp压栈生存起来。21行将栈指针减少32位,然后分别将%rdi和%rsi的值存入栈中。
        由此我们知道,%rbp-20和%rbp-32的位置分别存了argv数组和argc的值。
https://img-blog.csdnimg.cn/direct/2cf35e3fb7bc4105a4345ae64c0872cd.png
        3.3.6对数组的操作
        对数组的操作,都是先找到数组的首地址,然后加上偏移量即可。例如在main中,调用了argv和argv,在汇编代码中,每次将%rbp-32的的值即数组首地址传%rax,然后将%rax分别加上偏移量16和8,得到了argv和argv,在分别存入对应的寄存器%rsi和%rdx作为第二个参数和第三个参数,之后调用printf函数时使用。
https://img-blog.csdnimg.cn/direct/46a8ce4d655a4e43a6db0ef7fdb4f74c.png
        调用完printf后,同样在偏移量为24时,取得argv并存入%rdi作为第一个参数在调用函数atoi使用。
        3.3.7对函数的调用与返回
        函数的前六个参数有寄存器传参,返回值存在%rax寄存器中。在函数调用时,先将相应的值存入相应的寄存器,然后使用call指令调用函数和ret指令返回函数。注意,由于函数是公用一套寄存器的,在调用一个函数之前,要先将当前函数的一些值生存起来,调用完再恢复。
        对printf函数的调用,在3.3.6中已经介绍过,取得argv数组的第二个和第三个元素放入寄存器%rsi和%rdx,然后41行取得了字符串的地址,并存入了2%rdi中作为第一个参数,这样三个参数都准备好后,用call指令调用了printf函数。
https://img-blog.csdnimg.cn/direct/3e51091f11b6463bba2190df965b8e2f.png
        对atoi函数和sleep函数的调用,先取得argv存入%rdi作为第一个参数,然后第50行call指令调用了atoi函数,接着atoi的返回值存入了%rax中,再将其存入%rdi中作为sleep的第一个参数,然后用call调用sleep函数。
https://img-blog.csdnimg.cn/direct/e662860fdea94017a36db3d5fab725e3.png
        3.3.8 for循环
        对于for循环,将循环变量存入一个寄存器中,然后当执行完一个循环体之后,更新循环变量(一样寻常是用add指令进行自增),然后用cmp指令将其与条件进行比较,满足则继续,否则退出循环。
https://img-blog.csdnimg.cn/direct/d13826270f7648d08385a01030adb27a.png
        3.3.9赋值操作
        赋值操作很简朴,用movq指令即可,例如将a寄存器的值赋值给b寄存器,用movq a b(以8字节为例)。在hello.s中很多地方都用到了赋值语句,比如说对局部变量i的赋值:
https://img-blog.csdnimg.cn/direct/4c0972b226c54af88a01cb4adcc1b8b6.png
3.4 本章小结

        本章主要介绍了编译的概念以及过程。编译步伐所做的工作,就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码表示。同时通过示例函数表现了C语言如何转换成为汇编代码。介绍了汇编代码如何实现数据、赋值、算术操作、关系操作、控制转移、函数调用、数组操作。通过本章的学习我更深刻地明白了 C 语言的数据与操作,对C语言翻译成汇编语言的过程有了更好的掌握。



第4章 汇编



4.1 汇编的概念与作用

        驱动步伐运行(或直接运行)汇编器as,将汇编语言步伐(在本章指 hello.s)翻译成机器语言指令,并将这些指令打包成可重定位目标文件(在本章中指hello.o)的过程称为汇编,hello.o 是二进制编码文件,包罗步伐的机器指令编码。
        汇编的作用是将汇编代码转换为计算机可以或许明白并执行的二进制机器代码。

4.2 在Ubuntu下汇编的命令

        汇编命令:gcc hello.s -c -o hello.o
https://img-blog.csdnimg.cn/direct/15fdc93a476448feacd3c623fe6633e9.png
4.3 可重定位目标elf格式

        4.3.1 ELF头
        读取ELF头指令:readelf -h hello.o
https://img-blog.csdnimg.cn/direct/751aea77f7e84415a7500aba6ab8a05b.png
        ELF头以16字节的序列开始,形貌了生成该文件的系统的字的巨细和字节序次,ELF头剩下的部分包罗资助链接器语法分析息争释目标文件的信息,包括ELF头的巨细、目标文件的类型(如可执行、可重定位大概共享的)、机器类型、节头部表的文件偏移以及节头部表中条目的巨细和数量。
        4.3.2节头
        命令如下:readelf -S hello.o       
https://img-blog.csdnimg.cn/direct/5ec84ca10ff6496a997ec1a54a52b03d.png
        节头部分记载了各节的名称、类型、地址、偏移量、巨细、全体巨细、旗标、链接、信息、对齐等信息。使用节头表中的字节偏移信息可以得到各节在文件中的起始位置,以及各节所占空间的巨细,这样方便重定位。
        4.3.3重定位节
        命令如下:readelf -r hello.o
https://img-blog.csdnimg.cn/direct/eca90f344aa4460c80d26333bfb72a64.png
        重定位节中包罗了.text 节中需要进行重定位的信息,我们可以发现需要重定位的函数有: .rodata, puts, exits, printf, atoi, sleep, getchar
        4.3.4符号表
        命令如下:readelf -s hello.o
https://img-blog.csdnimg.cn/direct/5aeaef74909344ddb24461a2011ee9c8.png
        符号表存放了步伐中定义和引用的函数和全局变量的信息。


4.4 Hello.o的结果解析

        反汇编指令:objdump -d -r hello.o
https://img-blog.csdnimg.cn/direct/92cea4bb536f48079afa9d51530ad14c.png
https://img-blog.csdnimg.cn/direct/1a1a75dd8c47462a96d24094f055ce48.png
        反汇编代码中,除了.s文件中已经出现过的代码,还包罗了它们对应的机器语言的代码,比如说分支转移结构中,hello.s表示为:
https://img-blog.csdnimg.cn/direct/a8da33e473244e47a7dca9495f44dc27.png
        而在hello.o中表示为:
https://img-blog.csdnimg.cn/direct/3902912b7b3a48928c98e835c15d9c2d.png
        这是因为.s文件中可以用段名称L3来进行助记,而在.o文件中则需要它的真实地址以便于下一步操作。
        而在函数调用方面,.s文件在call后可直接跟上函数名称,如 call printf@PLT,但是.o文件call后跟的是一条重定位条目指引的信息,如 call 68 <main+0x68>。

4.5 本章小结

        本章首先介绍了汇编的概念和作用,接着通过实操,对hello.s文件进行汇编,生成ELF可重定位目标文件hello.o,接着使用readelf工具,通过设置不同参数,检察了hello.o的ELF头、节头表、可重定位信息和符号表等,通太过析明白可重定位目标文件的内容。末了将其与hello.s比较,分析不同,并说明机器语言与汇编语言的一一对应关系。


  第5章 编译


5.1 链接的概念与作用

        链接是将各种代码和数据片段收集并组合成为一个单一文件的过程(在本章中,链接即是指将可重定向目标文件 hello.o与其他一些文件组合成为可执行目标文件hello),这个文件可被加载到内存并执行。在现代系统中,链接是由链接器进行的。
        链接可以实现分离编译。我们可以借助链接的优势将大型的应用步伐分解成更小、更加易于管理的模块,使得各模块之间的修改都和编译相互独立。这样当我们需要修改某一模块时只需要重新编译经过修改的模块并重新链接即可,不需要重新编译其他文件。

5.2 在Ubuntu下链接的命令

        命令如下: ld  -o  hello  -dynamic-linker  /lib64/ld-linux-x86-64.so.2 /usr/lib/x86_64-linux-gnu/crt1.o  /usr/lib/x86_64-linux-gnu/crti.o  hello.o           /usr/lib/x86_64-linux-gnu/libc.so  /usr/lib/x86_64-linux-gnu/crtn.o
https://img-blog.csdnimg.cn/direct/6ec26d935a724323a267f73f17dde20d.png

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

        5.3.1 ELF头
        命令如下:readelf -h hello
        可执行目标文件hello与可重定位文件hello.o稍有不同:前者ELF头中字段e_entry给出执行步伐时的第一条指令的地址,而后者的此字段为0。且前者比后者多了一个步伐头表,也成为段头表,是一个结构数组;还多了一个.init节,用于定义init函数,该函数用来执行可执行目标文件开始执行时的初始化工作。因为可执行目标文件不需要重定位,所以比后者少了两个.rel节。
        检察hello的ELF头,发现hello的ELF头中Type处表现的是EXEC,表示是可执行目标文件,这与hello.o不同。hello中的节的数量为30个。
https://img-blog.csdnimg.cn/direct/e143d8fedb8c4c3784481859f3755790.png
        5.3.2 节头表
        命令如下:readelf -S hello
        与.o的节头不同的是,在这里每一节都有了现实地址,说明重定向已经完成:
https://img-blog.csdnimg.cn/direct/9b1e263dff6b45319f809400ad099296.png
https://img-blog.csdnimg.cn/direct/6be9b82ee954464cae628cb14d89a6f7.png
        发现刚才提到的30个节的详细信息,在hello文件节头表中都有表现,包括巨细Size,偏移量Offset,其中Address是步伐被载入虚址地址的起始地址。
        5.3.3 步伐头表
        命令如下:readelf -l hello
https://img-blog.csdnimg.cn/direct/e41fc2976ad74c33aeb2859f5a93beef.png
https://img-blog.csdnimg.cn/direct/65cfa495b5cf40e2b566b47458773d9f.png
         检察hello的步伐头表,首先表现这是一格可执行目标文件,共有12个表项,其中有4个可装入段(Type = LOAD),VirtAddr和PhysAddr分别是假造地址和物理地址,值类似。Align是对齐方式,这里4个可装入段都是4K字节对齐。以第一个可装入段为例,表示第0x00000~0x005f0字节,映射到假造地址0x400000开头的长度为0x5f0字节的地域,按照0x1000=4KB对齐,具有只读(Flags=R)权限,是只读代码段。
        5.3.4重定位节
        命令如下:readelf -r hello
https://img-blog.csdnimg.cn/direct/4e23d04cd286454f8a4c0d135d6d5cf4.png
        5.3.5符号表
        命令如下:readelf -s hello
https://img-blog.csdnimg.cn/direct/50a1b85084224de081e33d86a21d8ef5.png
https://img-blog.csdnimg.cn/direct/a0bd62f56c384db9822946443bc4e52a.png

        5.4 hello的假造地址空间

        从5.3.3的步伐头表看,LOAD可加载的步伐段第一段的地址为0x400000,那么假造内存地址从由0x401000开始,使用edb加载hello如下图所示:
https://img-blog.csdnimg.cn/direct/3373e144351d4869958ffcf426276641.png
        由图5.3.2可以知道,.inerp段的起始地址为04002e0,用edb检察.inerp段的信息,如下图所示:
https://img-blog.csdnimg.cn/direct/a0229442b33a499dbb5390992881b83a.png
        同理,.text段的起始地址为0x4010f0,用edb检察.text段的信息,如下图所示:
https://img-blog.csdnimg.cn/direct/35ffed269e6647e79dbd60c5910b97d1.png
        同理,.rodata段的起始地址为0x402000,用edb检察.rodata段的信息,如下图所示:
https://img-blog.csdnimg.cn/direct/b0bc125c92b64b529c836975b4dfc197.png


5.5 链接的重定位过程分析

        首先,使用objdump -d -r hello对hello进行反汇编,结果如下:
https://img-blog.csdnimg.cn/direct/7e7ac4aebbb247cfb8c93c8425b95923.png
        hello的反汇编代码多了很多节,并且发现每条数据和指令都已经确定好了假造地址,不再是hello.o中的偏移量。通过链接之后,也含有了库函数的代码标识信息。
        接着,我们详细比较分析一下hello和hello.o的反汇编结果,下面两个图分别为hello.o和hello的反汇编的部分截图,别的同理。
https://img-blog.csdnimg.cn/direct/c04316e446f14a89b2436c8b3741d39e.png
https://img-blog.csdnimg.cn/direct/66e56cc25731466d8e98469dd0cf8aa9.png
 
        可以看出,在hello.o中跳转指令和call指令后为绝对地址,而在hello中已经是重定位之后的假造地址。
        接下来,以0x401148出的call指令为例,说明链接过程。先用readelf -r hello.o检察hello.o的重定位信息:
https://img-blog.csdnimg.cn/direct/b90bc125baf947019328987fa871f3b7.png
        由图可知,此处应该绑定第0x5个符号,同时链接器知道这里是相对寻址,接着检察hello.o的符号表,找到第5个符号puts,此处绑定puts的地址。
https://img-blog.csdnimg.cn/direct/2885126153ba435a8519002e709e14fc.png
        在hello中找到puts的地址为0x401090。
https://img-blog.csdnimg.cn/direct/1c506a3132a94fd3b5bddc9f7d2e2cd9.png
        当前PC的值为call指令的下一条指令的地址,也就是0x40114d。而我们要跳转到的地方为0x401090,差0x16b,因此PC需要减去0x16b,也就是加上0xff ff fe 95,由于是小端法,因此重定位目标处应该填入95 fe ff ff。
https://img-blog.csdnimg.cn/direct/7ae4fe5e82c940c3af2d4b3158fd2422.png
5.6 hello的执行流程

        使用edb单步调试运行步伐,观察其调用的函数,这里可以发如今调用main之前主要进行了初始化的工作调用了_init,在这个函数之后动态链接的重定位工作已经完成,我们可以看到在这个函数的调用之后是一系列在这个步伐中所用到的库函数(printf,exit,atoi等等),这些函数现实上在代码段并不占用现实的空间只是一个占位的符号,现实上他们的内容在共享区(高地址)处。之后调用了_start这个就是起始的地址,准备开始执行main的内容,main函数内部所调用的函数在第三章已经进行了分析,这里略过。main内部的函数,在执行main之后还会执行__libc_csu_init 、__libc_csu_fini 、_fini,终极这个步伐才结束。
        下面的表格列出了在使用edb执行hello时,从加载hello到_start,到call main,以及步伐终止的过程中调用与跳转的各个子步伐名或步伐地址:
子步伐名
子步伐地址
_abi_tag
0x400330
_init
0x401000
puts@plt
0x401030
printf@plt
0x401040
getchar@plt
0x401050
atoi@plt
0x401060
exit@plt
0x401070
sleep@plt
0x401080
_start
0x4010f0
_dl_relocate_static_pie
0x401120
main
0x401125
_fini
0x4011c0
_IO_stdin_used
0x402000
_DYNAMIC
0x403e50
_GLOBAL_OFFSET_TABLE_
0x404000
_data_start
0x404048
data_start
0x404048
_bss_start
0x40404c
_edata
0x40404c
_end
0x404050
 
5.7 Hello的动态链接分析

        动态链接器使用过程链接表PLT和全局偏移量表GOT实现函数的动态链接。通过hello节头表查询PLT和GOT的位置:        
https://img-blog.csdnimg.cn/direct/22b37324b863455ab6bf0c51f7599bad.png
        查阅节头可以得知,.got.plt起始位置为0x404000,在调用之后该处的信息发生了变革,如下图所示:
https://img-blog.csdnimg.cn/direct/4a6747e1478f46328216206ded8cd360.png
https://img-blog.csdnimg.cn/direct/9cd255859846440798e3caaa124c4191.png
        通过以上两张图的对比,可以得知,对于变量而言,我们利用代码段和数据段的相对位置不变的原则计算正确地址。对于库函数而言,需要plt、got互助,plt初始存的是一批代码,它们跳转到got所指示的位置,然后调用链接器。初始时got内里存的都是plt的第二条指令,随后链接器修改got,下一次再调用plt时,指向的就是正确的内存地址。plt就能跳转到正确的地域。

5.8 本章小结

        本章着重介绍了可重定位目标文件 hello.o 经过链接生成可执行目标文件 hello的过程。首先详细介绍并分析了链接的概念、作用及详细工作。随后验证了 hello的假造地址空间与节头部表信息的对应关系,分析了hello的执行流程。末了对hello步伐进行了动态链接分析。通过本章的实验操作,我更加深刻地明白了链接和重定位的相关概念,复习了课程第七章链接的相关知识,也相识了动态链接的过程及作用。


第6章 hello进程管理



6.1 进程的概念与作用

        6.1.1 进程的概念
        进程是指计算机中运行的步伐,是系统进行资源分配和调度的根本单元,是操作系统结构的基础。
        6.1.2 进程的作用
        每次运行步伐时,shell创建一新进程,在这个进程的上下文切换中运行这个可执行目标文件。应用步伐也可以或许创建新进程,并且在新进程的上下文中运行它 们本身的代码或其他应用步伐。 
        进程提供给应用步伐的关键抽象:一个独立的逻辑控制流,如同步伐独占处置处罚器;一个私有的地址空间,如同步伐独占内存系统。


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

        6.2.1 Shell-bash的作用
        Shell-bash是一个交互型应用级步伐,代表用户运行其他步伐。它是系统的用户界面,提供了用户与内核进行交互操作的一种接口。它吸收用户输入的命令并把它送入内核去执行。
        6.2.2 Shell-bash的处置处罚流程
        Shell首先检查命令是否是内部命令,若不是,再检查是否是一个应用步伐(比如Linux自身的应用步伐,如ls和rm,也可以是应用市肆的应用步伐,如xv)。随后shell在搜索路径里探求这些应用步伐(搜索路径就是一个能找到可执行步伐的目次列表)。假如键入的命令不是一个内部命令并且在路径里没有找到这个可执行文件,将会表现一条错误信息。假如可以或许成功找到命令,该内部命令或应用步伐将被分解为系统调用并传给Linux内核。处置处罚流程如下所示:
        ①从终端读入输入的命令;
        ②将输入字符串切分获得所有的参数;
        ③假如是内置命令则立即执行;
        ④若不是则调用相应的步伐执行;
        ⑤shell应该随时接受键盘输入信号,并对这些信号进行相应处置处罚。


6.3 Hello的fork进程创建过程

        当在shell上输入./hello命令时,命令行会首先判定是否为内置命令,假如是内置命令则立即对其进行解释。否则会调用fork创建一个新进程并在其中执行。    根据shell的处置处罚流程推断,输入命令执行hello后,shell会对我们输入的命令进行解析,认为该命令是执行当前目次下的可执行文件hello,因此shell会调用fork()创建一个子进程。子进程与父进程近似,并得到一份与父进程用户级假造空间类似且独立的副本——包括数据段、代码、共享库、堆和用户栈。父进程打开的文件,子进程也可读写。二者之间最大的不同在于PID的不同。fork函数只会被调用一次,但会返回两次,在父进程中,fork返回子进程的PID,在子进程中,fork返回0。


6.4 Hello的execve过程

        execve函数加载并运行可执行目标文件Hello,且带参数列表argv和环境变量列表envp。该函数的作用就是在当前进程的上下文中加载并运行一个新的步伐。只有当出现错误时,例如找不到filename,execve才会返回到调用步伐,调用成功不会返回。与fork不同,fork一次调用两次返回,execve一次调用从不返回。
        在execve加载了Hello之后,它调用启动代码。启动代码设置栈,并将控制通报给新步伐的主函数,该主函数有如下的原型:
        int main(intargc , char **argv , char *envp);
        结合假造内存和内存映射过程,可以更详细地说明exceve函数现实上是如何加载和执行步伐Hello:
        ①删除已存在的用户地域(自父进程独立)。
        ②映射私有区:为Hello的代码、数据、.bss和栈地域创建新的地域结构,所有这些地域都是私有的、写时才复制的。
        ③映射共享区:比如Hello步伐与标准C库libc.so链接,这些对象都是动态链接到Hello的,然后再用户假造地址空间中的共享地域内。
        ④设置PC:exceve做的末了一件事就是设置当前进程的上下文中的步伐计数器,使之指向代码地域的入口点。


6.5 Hello的进程执行

        6.5.1 进程上下文信息
        进程上下文信息是指内核重新启动一个被抢占的进程所需要恢复的原来的状态,由寄存器、步伐计数器、用户栈、内核栈和内核数据结构等对象的值构成。
        在内核调度了一个新的进程运行时,它就抢占当前进程,并使用一种上下文切换的机制来控制转移到新的进程。详细过程为:①生存当前进程的上下文;②恢复某个先前被抢占的进程被生存的上下文;③将控制通报给这个新恢复的进程。
https://img-blog.csdnimg.cn/direct/c657c3e3efc3408699dec63843e873be.png
        如上图所示,为进程A与进程B之间的相互切换。处置处罚器通常使用一个寄存器提供两种模式的区分,该寄存器形貌了进程当前享有的特权,当没有设置模式位时,为用户模式;设置模式为为内核模式。用户模式就是运行相应进程的代码段的内容,此时进程不答应运行特权指令,也不答应直接引用地址空间中内核区内的代码和数据;而内核模式中,进程可以运行任何指令。
        6.5.2 进程时间片
        一个进程执行它的控制流的一部分的每一个时间段叫做时间片,多任务也叫时间分片。
        6.5.3 进程调度的过程
        在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程,这种决策称为调度,是由内核中的调度器代码处置处罚的。当内核选择一个新的进程运行,我们说内核调度了这个进程。在内核调度了一个新的进程运行后,它就抢占了当前进程,并使用上下文切换机制来将控制转移到新的进程。
        hello步伐调用sleep函数休眠时,内核将通过进程调度进行上下文切换,将控制转移到其他进程。当hello步伐休眠结束后,进程调度使hello步伐重新抢占内核,继续执行。
        6.5.4 用户态与核心态的转换
        为了保证系统安全,需要限制应用步伐所能访问的地址空间范围。因而存在用户态与核心态的划分,核心态拥有最高的访问权限,而用户态的访问权限会受到一些限制。处置处罚器使用一个寄存器作为模式位来形貌当前进程的特权。进程只有故障、停止或陷入系统调用时才会得到内核访问权限,其他情况下始终处于用户权限之中,一定水平上保证了系统的安全性。

6.6 hello的异常与信号处置处罚

        6.6.1 异常的种别
        异常可以分为四类:停止、陷阱、故障和终止,下图对这些种别的属性做了小结:
https://img-blog.csdnimg.cn/direct/a56b907f121d44d0bed741e30583aad5.png
        ①停止:异步发生的。在执行hello步伐的时候,由处置处罚器外部的I/O装备的信号引起的。I/O装备通过像处置处罚器芯片上的一个引脚发信号,并将异常号放到系统总线上,来触发停止。这个异常号标识了引起停止的装备。在当前指令完成执行后,处置处罚器注意到停止引脚的电压变高了,就从系统总线读取异常号,然后调用适当的停止处置处罚步伐。在处置处罚步伐返回前,将控制返回给下一条指令。结果就像没有发生过停止一样。
        ②陷阱:陷阱是有意的异常,是执行一条指令的结果,hello执行sleep函数的时候会出现这个异常。
        ③故障:由错误引起,大概被故障处置处罚步伐修正。在执行hello时,大概出现缺页故障。
        ④终止:不可恢复的致命错误造成的结果,通常是一些硬件错误,比如DRAM大概 SRAM位被损坏时发生的奇偶错误
        6.6.2 详细异常与运行结果
        ①正常运行:
https://img-blog.csdnimg.cn/direct/7f0378a01d3a4e188fc94fafc9ea629d.png
        ②运行时输入恣意字符串:
https://img-blog.csdnimg.cn/direct/0b689657bbfe44719a1bf62ff112cf87.png
        运行时输入的恣意字符串,只是被缓存到缓冲区,待进程结束后又作为命令行进行输入。

        ③运行时输入回车:
https://img-blog.csdnimg.cn/direct/7a022de9b0974ea9b7dca5a5154d7aee.png
        getchar读回车,回车前的字符串当作shell输入的命令。

        ④运行时输入Ctrl+C:
https://img-blog.csdnimg.cn/direct/9f2e7140d95b4e1fbe0f196b56cbc914.png
        Ctrl+C:使用SIGINT信号终止前台进程。

        ⑤运行时输入Ctrl+Z:
https://img-blog.csdnimg.cn/direct/9cb1e343b2394eebaad976d87171d102.png
        Ctrl+Z:使用SIGTSTP信号,停止前台作业。

        ⑥Ctrl-Z后运行ps、jobs、pstree、fg、kill等命令
https://img-blog.csdnimg.cn/direct/fa7212cdb45448a6a0457bc3051787e7.png
https://img-blog.csdnimg.cn/direct/d65b893bc18344c3a14c69ff862967fb.png
https://img-blog.csdnimg.cn/direct/e70a1e40f7944abf9a9b98a71aa40079.png
https://img-blog.csdnimg.cn/direct/64163cfce8b54fa8bcb882c83eb3d13b.png
        Ⅰ. 首先输入Ctrl+Z,进程收到SIGTSTP信号,信号的动作是将hello挂起;
        Ⅱ. 通过ps命令看到hello进程没有被接纳,其进程号是2312;
        Ⅲ. 用jobs命令看到job ID是1,状态是“已停止”;
        Ⅳ. 接着输入pstree,以树状图形式表现所有进程;
        Ⅴ. 输入fg,使停止的进程收到SIGCONT信号,重新在前台运行;
        Ⅵ. 输入kill,-9表示给进程2380发送9号信号,即SIGKILL,杀死进程。
 
6.7本章小结

        本章主要介绍了进程的概念及作用,论述了壳shell-bash的作用与处置处罚流程,并以hello为例,分析了fork函数的执行过程,execve函数的执行过程,并对进程的创建、执行、上下文切换、用户态与内核态的转化做了详细的分析。末了给出了异常的种类以及对hello进行一些异常操作处置处罚,得到相应的结果。
        在hello步伐运行的过程中,内核对其进行进程管理,决定何时进行进程调度,在吸收到不同的异常、信号时,还要及时地进行对应的处置处罚。通过对本章内容的操作实践,我复习了CSAPP书上第八章异常控制流的相关内容,对进程、信号及异常相关概念有了更加深刻的明白。
         
 
第7章 hello的存储管理



7.1 hello的存储器地址空间

        ①逻辑地址 (Logical Address):步伐经过编译后出如今汇编代码中的地址,用来指定一个操作数大概是一条指令的地址。是由一个段标识符加上一个指定段内相对地址的偏移量;
        ②线性地址 (Liner Address):逻辑地址向物理地址转化过程中的一步,逻辑地址经过段机制后转化为线性地址,为形貌符、偏移量的组合形式,分页机制中线性地址作为输入;
        ③假造地址 (Virtual Address):也是逻辑地址;
        ④物理地址 (Physical Address):出如今CPU外部地址总线上的寻址物理内存的地址信号,是地址变动的终极结果地址。假如启用了分页机制,那么线性地址会使用页目次和页表中的项变动成物理地址;假如没有启用分页机制,那么线性地址就直接成为物理地址了。


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

        一个步伐可以由一个主步伐、多少子步伐、符号表、栈以及数据等多少段组成。每一段都有独立、完备的逻辑意义,每一段步伐都可独立体例,且每一段的长度可以不同。段式存储管理支持用户的分段观点,具有逻辑上的清楚和完备性,它以段为单元进行存储空间的管理。
        每个作业由多少个相对独立的段组成,每个段都有一个段名,为了实现简朴,通常可用段号代替段名,段号从“0”开始,每一段的逻辑地址都从“0”开始编址,段内地址是连续的,而段与段之间的地址是不连续的。段式存储管理的逻辑地址由段号和段内地址两部分所组成,如下图所示:
https://img-blog.csdnimg.cn/direct/edf7e9196f0f4bdfa57ea449a3193511.png
        分段式存储管理是在可变分区存储管理方式的基础上发展而来的。在分段式存储管理方式中,以段为单元进行主存分配,每一个段在主存中占据一个连续空间,但各个段之间可以离散地存放在主存不同的地域中。为了使步伐能正常运行,即能从主存中正确找出每个段所在的分区位置,系统为每个进程建立一张段映射表,简称“段表”。每个段在表中占据一个表项,记载该段在主存储器中的起始地址和长度。段表实现了从逻辑段到主存空间之间的映射。
https://img-blog.csdnimg.cn/direct/661e086918794acf88efd597edf1e024.png 
        当接纳分段式存储管理的作业执行结束后,它所占据的主存空间将被接纳,接纳后的主存空间登记在空闲分区表中,可以用来装入新的作业。系统在接纳空间时同样需要检查是否存在与接纳区相邻的空闲分区,假如有,则将其合并成为一个新的空闲分区进行登记管理。
        段表存放在主存储器中,在访问一个数据或指令时至少需要访问主存两次以上。为了进步对段表的存取速度,通常增设一个相联寄存器,利用高速缓冲寄存器生存最近常用的段表项。
        段式存储管理接纳动态重定位方式装入作业,作业执行时通过硬件的地址转换机构实现从逻辑地址到物理地址的转换工作,段表的表目起到了基址寄存器和限长寄存器的作用,是硬件进行地址转换的依据。
https://img-blog.csdnimg.cn/direct/b92c23b9be6f43eaaf504f2c547a7eb8.png 
 
7.3 Hello的线性地址到物理地址的变动-页式管理

        页式存储管理是把主存储器划分成巨细相称的多少地域,每个地域称为一块,并对它们加以序次编号,如0#块、1#块等。与此对应,用户步伐的逻辑地址空间划分成巨细相称的多少页,同样为它们加以序次编号,从0开始,如第0页、第1页等。页的巨细与块的巨细相称。分页式存储管理的逻辑地址由两部分组成:页号和页内地址,其格式如下图所示:
https://img-blog.csdnimg.cn/direct/2c571000032b410097802644edc749d4.png
         在分页式存储管理系统中,答应将作业的每一页离散地存储在主存的物理块中,但系统必须可以或许保证作业的正确运行,即能在主存中找到每个页面所对应的物理块。为此,系统为每个作业建立了一张页面映像表,简称页表。页表实现了从页号到主存块号的地址映像。作业中的所有页(0~n)依次地在页表中记载了相应页在主存中对应的物理块号。页表的长度由进程或作业拥有的页面数决定。
 https://img-blog.csdnimg.cn/direct/7035bc6622c74955b217d7f32ae2154c.png
        调度步伐在选择作业后,将选中作业的页表始址送入硬件设置的页表控制寄存器中。地址转换时,只要从页表寄存器中就可找到相应的页表。当作业执行时,分页地址变动机构会主动将逻辑地址分为页号和页内地址两部分,以页号位索引检索页表,假如页表中无此页号,则产生一个“地址错”的步伐性停止事件;假如页表中有此页号,则可得到对应的主存块号,再按逻辑地址中的页内地址计算出欲访问的主存单元的物理地址。因为块的巨细相称,所以:物理地址=块号×块长+页内地址。 
 https://img-blog.csdnimg.cn/direct/77233f95843743c8b0dd4555e3005ea0.png
 
7.4 TLB与四级页表支持下的VA到PA的变动

        7.4.1 翻译后备缓存器(TLB,也叫快表)
        每次CPU产生一个假造地址,MMU(内存管理单元)就必须查阅一个PTE(页表条目)以便将假造地址翻译为物理地址。在最糟糕的情况下,这会从内存多取一次数据,代价是几十到几百个周期。假如PTE碰巧缓存在L1中,那么开销就会下降1或2个周期。许多系统都试图消除纵然是这样的时间开销,它们在MMU中包括了一个关于PTE的小缓存,也就是TLB。
        TLB是一个小的、假造寻址的缓存、其中的每一行都生存着一个由单个PTE组成的块。TLB通常具有高度的相联度,TLB的速度快于一级cache。
        TLB通过假造页号VPN部分进行索引,分为TLBT(TLB标记)和TLBI(TLB索引),这样每次MMU会从TLB中取出相应的PTE(页表条目),当TLB不掷中时,MMU又从L1缓存中取出相应的PTE,新取出的PTE会存放在TLB中此时大概会覆盖一个已存在的条目。
https://img-blog.csdnimg.cn/direct/f67488d9b3654e33bdb34df2f334f990.png
 
        7.4.2 多级页表
        使用层次结构的页表来压缩页表,形成相应的k级页表。那么假造地址被划分为VPO(假造页偏移量)以及k个VPN(假造页号),每个VPN i都是一个到第i级页表的索引,其中1≤i≤k。第j级页表中的每个PTE,1≤j≤k-1,都指向第j+1级的某个页表的基址。第k级页表中的每个PTE包罗某个物理页面的PPN,大概一个磁盘块地址。为了构造物理地址,在可以或许确定PPN之前,MMU必须访问k个PTE。对于只有一级的页表结构,PPO和VPO是类似的
 https://img-blog.csdnimg.cn/direct/174cc1b89fe843ac90c942cfe83f670f.png
 
7.5 三级Cache支持下的物理内存访问

        通用的高速缓存存储器(Cache)组织结构示意图如下所示:
 https://img-blog.csdnimg.cn/direct/fc65c2c3b900448ca4e028aabd731173.png
        当一条加载指令指示CPU从主存地址A中读一个字时,它将地址A发送到高速缓存。假如高速缓存正生存着地址A处谁人字的副本,它就立即将谁人字发回给CPU。
        根据PA、L1高速缓存的组数和块巨细确定高速缓存块偏移(CO)、组索引(CI)和高速缓存标记(CT),使用CI进行组索引,对组中每行的标记与CT进行匹配。假如匹配成功且块的valid标记位为1,则掷中,然后根据CO取出数据并返回数据给CPU。
        若未找到相匹配的行或有用位为0,则L1未掷中,继续在下一级高速缓存(L2)中进行类似过程的查找;若仍未掷中,则继续在L3高速缓存中进行查找;三级Cache均未掷中则需访问主存获取数据。
        若进行了上一步,说明至少有一级高速缓存未掷中,则需在得到数据后更新未掷中的Cache。首先判定其中是否有空闲块,若有空闲块(有用位为0),则直接将数据写入;若不存在,则需根据更换策略(如LRU、LFU策略等)驱逐一个块再写入。
https://img-blog.csdnimg.cn/direct/0f1296656d0845e6a6015402edc33a43.png
 
7.6 hello进程fork时的内存映射

        当fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID。为了给这个新进程创建假造内存,它创建了当前进程的mm_struct、地域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个地域结构都标记为私有的写时复制。
        当fork在新进程中返回时,新进程如今的假造内存刚好和调用fork时存在的假造内存类似。当这两个进程中的任何一个后来进行写操作时,写时复制机制就会创建新页面,因此,也就为每个进程保持了私有地址空间的抽象概念。


7.7 hello进程execve时的内存映射

        6.4节给出了hello的execve过程。execve函数调用驻留在内核地域的启动加载器代码,在当前进程中加载并运行包罗在可执行目标文件hello中的步伐,用hello步伐有用替代当前步伐。加载并运行hello需要以下四个步调:
        ①删除当前进程假造地址中已存在的用户地域;
        ②映射私有地域,为新步伐的代码、数据、bss和栈创建新的地域结构,所有这些新的地域都是私有的、写时复制的;
        ③映射共享地域,将hello与libc.so动态链接,然后再映射到假造地址空间中的共享地域;
        ④设置当前进程上下文步伐计数器(PC),使之指向代码地域的入口点。


7.8 缺页故障与缺页停止处置处罚

        在假造内存中DRAM缓存不掷中称为缺页。缺页异常时会调用内核中的缺页异常处置处罚步伐,详细的过程如下图所示:
https://img-blog.csdnimg.cn/direct/b0163895d800430b8c0bc2f82b4ecaa6.png
        ①处置处罚器生成一个假造地址,并把它传送给MMU;
        ②MMU生成PTE地址,并从高速缓存/主存哀求得到它;
        ③高速缓存/主存向MMU返回PTE;
        ④若PTE中的有用位是零,则MMU触发一次异常,通报CPU中的控制到操作系统内核中的缺页异常处置处罚步伐;
        ⑤缺页异常处置处罚步伐确定物理内存中的捐躯页,假如这个页面已经被修改,则把它换出到磁盘;
        ⑥缺页处置处罚步伐页面调入新的页面(内核从磁盘复制所需的假造页面到内存中),更新内存中的PTE;
        ⑦缺页处置处罚步伐返回到原来的进程中,再次执行导致缺页的指令。CPU将引起缺页的假造地址重新发送给MMU,因为假造页面已经如今在物理内存中了,所以就会掷中。


7.9动态存储分配管理

        7.9.1 动态内存分配器
        动态内存分配器维护着一个进程的假造内存地域,称为堆。堆是一个哀求二进制零的地域,它紧接在未初始化数据地域(.bss)后开始,并向上生长(向更高的地址)。对于每个进程,内核维护着一个brk指针,指向堆的顶部。
        分配器将堆视为一组不同巨细的块的聚集来维护。每个块就是一个连续的假造内存片,要么是已分配的,要么是空闲的。已分配的块表现地保留为供应用步伐使用。空闲块可用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被开释,这种开释要么是应用步伐显式执行的,要么是内存分配器自身隐式执行的。
        分配器有两种根本风格: ①显式分配器:要求应用显式地开释任何分配的块,例如C标准库提供的malloc步伐包。 ②隐式分配器:要求分配器检测一个已分配块何时不再被步伐所使用,那么就是放这个块,也被称为垃圾收集器。
        7.9.2 隐式空闲链表
        每个块是由一个字的头部、有用载荷、大概的额外添补以及一个字的尾部组成的,末了一位指明这个块是已分配的还是空闲的,详细形式如下图所示:
https://img-blog.csdnimg.cn/direct/73008f83cfad4bd5b2b52ccf2471b47a.png
        假设块的格式如上图所示,我们可以将堆组织为一个连续的已分配块和空闲块的序列,如下图所示:
https://img-blog.csdnimg.cn/direct/2e88ca41642f4a9280b7aec4bb823d0c.png 
        我们称这种结构为隐式空闲链表,是因为空闲块是通过头部中的巨细字段隐含地毗连着的。分配器可以通过遍历堆中的所有块,从而间接地遍历整个空闲块的聚集。注意,我们需要某种特殊标记的结束块,在这个示例中,就是设置了一个已分配位而巨细为零的终止头部。
        隐式空闲链表的优点是操作简朴;显著的缺点是任何操作的开销,例如放置分配的块,要求对空闲链表进行搜索,该搜索所需时间与堆中已分配块和空闲块的总数成线性关系。
        7.9.3 显式空闲链表
        显式空闲链表结构将堆组织成一个双向空闲链表,在每个空闲块的主体中,都包罗一个pred(前驱)和succ(后继)指针,其结构如下图所示:
https://img-blog.csdnimg.cn/direct/1310fc8f5f9a47b7afcfb51f65430f01.png 
        使用双向链表而不是隐式空闲链表,使首次适配的分配时间从块总数的线性时间减少到了空闲块数量的线性时间。不外,开释一个块的时间可以是线性的,也大概是个常数,这取决于空闲链表中块的排序策略。
        一种方法是用后进先出(LIFO)的序次维护链表,将新开释的块放置在链表的开始处;另一种方法是按照地址序次来维护链表,其中链表中每个块的地址都小于它后继的地址。按照地址排序的首次适配比LIFO排序的首次适配有更高的内存利用率,靠近最佳适配的利用率。
        但表现链表的缺点就是空闲块必须足够大,以包罗所需要的指针,以及头部和大概的脚部。云云便导致了更大的最小块的巨细,也潜在地进步了内部碎片的水平。


7.10本章小结

        本章介绍了hello的存储地址空间,intel的段式管理、hello的页式管理,以及在TLB和四级页表的支持下完成VA到PA的变动过程,三级Cache支持下的物理内存访问。解释了hello进程的fork与execve时的内存映射,缺页故障及其处置处罚,以及进程的动态存储分配的管理。让我对第六章存储器层次结构和第九章假造地址的知识有了更加深刻的明白。
 
 
 
第8章 hello的IO管理



8.1 Linux的IO装备管理方法

        8.1.1 装备的模型化:文件
        所有的I/O装备(例如网络、磁盘和终端)都被模型化为文件。例如:/dev/sda2文件是用户磁盘分区,/dev/tty2文件是终端。
        8.1.2 装备管理:unix io接口
        将装备模型化为文件的方式答应Linux内核引入一个简朴、低级的应用接口,称为Unix IO,这使得所有的输入和输出都能以一种统一的方式来执行。


8.2 简述Unix IO接口及其函数

        8.2.1 Unix IO接口
        ① 打开文件。一个应用步伐通过要求内核打开相应的文件,来宣告它想要访问一个I/O装备,内核返回一个小的非负整数,叫做形貌符,它在后续对此文件的所有操作中标识这个文件,内核记载有关这个打开文件的所有信息。应用步伐只需记取这个形貌符。
        ② Linux Shell创建的每个进程都有三个打开的文件:标准输入、标准输出、标准错误。
        ③ 改变当前的文件位置。对于每个打开的文件,内核保持着一个文件位置k,初始为0,这个文件位置是从文件开头起始的字节偏移量,应用步伐可以或许通过执行 seek,显式地将改变当前文件位置k。
        ④ 读写文件。一个读操作就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增长到k+n。给定一个巨细为m字节的文件,当k>=m时,执行读操作会触发EOF,应用步伐能检测到它。类似地,写操作就是从内存中复制n>0个字节到一个文件,从当前文件位置k开始,然后更新k。
        ⑤ 关闭文件。内核开释文件打开时创建的数据结构,并将这个形貌符恢复到可用的形貌符池中去。
        8.2.2 Unix IO函数
        ① int open(char* filename,int flags,mode_t mode),进程通过调用open函数来打开一个存在的文件或是创建一个新文件的。open函数将filename转换为一个文件形貌符,并且返回形貌符数字,返回的形貌符总是在进程中当前没有打开的最小形貌符,flags参数指明了进程打算如何访问这个文件,mode参数指定了新文件的访问权限位。
        ② int close(fd),fd是需要关闭的文件的形貌符,close返回操作结果。
        ③ ssize_t read(int fd,void *buf,size_t n),read函数从形貌符为fd的当前文件位置赋值最多n个字节到内存位置buf。返回值-1表示一个错误,0表示EOF,否则返回值表示的是现实传送的字节数量。
        ④ ssize_t wirte(int fd,const void *buf,size_t n),write函数从内存位置buf复制至多n字节到形貌符为fd的当前文件位置。
        ⑤ lseek函数:off_t lseek(int fd, off_t offset, int whence),应用步伐表现地修改当前文件的位置。
        ⑥ stat函数:int stat(const char *filename,struct stat *buf),以文件名作为输入,并填入一个stat数据结构的各个成员。


8.3 printf的实现分析

        printf的函数体如下图所示:
 https://img-blog.csdnimg.cn/direct/a0c29d4005cd4171882beb191dffe2f2.png
        在代码的3~5行中,第三行目的是让argv指向第一个字符串;第二句的作用是格式化,并返回要打印的字符串的长度,第三句的作用是调用write函数将buf的前i个字符输出到终端,调用了unix I/O。
        从vsprintf生成表现信息,到write系统函数,到陷阱-系统调用int 0x80或syscall等。字符表现驱动子步伐:从ASCII到字模库到表现vram(存储每一个点的RGB颜色信息)。表现芯片按照刷新频率逐行读取vram,并通过信号线向液晶表现器传输每一个点(RGB分量)。


8.4 getchar的实现分析

        getchar的函数体如下图所示:
https://img-blog.csdnimg.cn/direct/35b3ddcd125a4ca19c3f25e883a46ee6.png
        getchar函数内部调用了read函数,通过系统调用read读取存储在键盘缓冲区的ASCII码,直到读到回车符才返回。不外read函数每次会把所有的内容读进缓冲区,假如缓冲区原来非空,则不会调用read函数,而是简简朴单的返回缓冲区中最前面的元素
        异步异常-键盘停止的处置处罚:当用户按键时,键盘接口会得到一个代表该按键的键盘扫描码,同时产生一个停止哀求,停止哀求抢占当前进程运行键盘停止子步伐,键盘停止子步伐先从键盘接口取得该按键的扫描码,然后将该按键扫描码转换成ASCⅡ码,生存到系统的键盘缓冲区中。


8.5本章小结

        本章介绍了Linux的I/O装备的根本概念和管理方法,以及Unix I/O接口及其函数。末了分析了printf函数和getchar函数的工作过程。本章内容让我对Unix IO 接口在Linux系统中的紧张作用有了更加深入的明白,同时也相识了作为异步异常之一的键盘停止处置处罚的工作过程。



结论

一、对hello所经历历程的总结

        hello步伐这一生:
        ①hello.c经预处置处罚产生hello.i文本文件;
        ②hello.i经编译产生hello.s汇编文件;
        ③hello.s经汇编产生二进制可重定位目标文件hello.o;
        ④hello.o经链接产生可执行文件hello;
        ⑤在终端输入“./hello 2022112588 向宇 1”,shell经过检查后发现其不是内置命令,则将其当作步伐执行;
        ⑥bash进程调用fork,生成子进程;
        ⑦execve函数加载运行当前进程的上下文中加载并运行新步伐
        ⑧运行hello时,内存管理单元MMU、翻译后备缓冲器TLB、多级页表机制、三级cache协同工作,完成对地址的翻译和哀求。
        ⑨调用sleep函数后进程休眠进入停止状态,调用完成后,内核再次进行上下文切换重新执行hello进程。
        ⑩hello的输入输出与外界交互,与linux I/O息息相关;
        ⑪hello终极被shell父进程接纳,内核会收回为其创建的所有信息。


二、对计算机系统这门课的感悟

        计算机系统这门课的知识点很多,比较抽象,学习难度总体偏大,不外很有趣。通过学习计算机系统(CSAPP)这门课,我认识到计算机专业不只只是写代码这么简朴,还涉及许多计算机底层的知识,远比我想象的要复杂;通过动手完成课程的五个实验和一个大作业,让我计算机系统有了更加深入的明白,对课堂上那天抽象、艰涩难懂的知识点可以或许记忆得更加牢固。总之,计算机系统这门课让我学到了很多,也为我未来学习操作系统、数据库等其他计算机专业课程奠定了雄厚的基础。


附件

        本次大作业的中间产物如下图所示:
https://img-blog.csdnimg.cn/direct/d5180f6915144edab8a4f1ce8ef42f45.png
        ①hello.c:源步伐
        ②hello.i:预处置处罚后的文本文件
        ③hello.s:编译后的汇编文件
        ④hello.o:汇编后的可重定位目标文件
        ⑤hello:链接后的可执行目标文件


参考文献


 林来兴. 空间控制技术. 北京:中国宇航出书社,1992:25-42.
 辛希孟. 信息技术与信息服务国际研讨会论文集:A集. 北京:中国科学出书社,1999.
 赵耀东. 新期间的工业工程师. 台北:天下文化出书社,1998 . http://www.ie.nthu.edu.tw/info/ie.newie.htm(Big5).
 谌颖. 空间交会控制理论与方法研究. 哈尔滨:哈尔滨工业大学,1992:8-13.
  KANAMORI H. Shaking Without Quaking. Science,1998,279(5359):2063-2064.
  CHRISTINE M. Plant Physiology: Plant Biology in the Genome Era. Science,1998,281:331-332. http://www.sciencemag.org/cgi/ collection/anatmorp.

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 计算机系统大作业:步伐人生—Hello‘s P2P