CSAPP程序人生
Posted Immortal351
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CSAPP程序人生相关的知识,希望对你有一定的参考价值。
计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 计算学部
学 号 7203610225
班 级 20xxxxxxx
学 生 wlh
指 导 教 师 刘宏伟
计算机科学与技术学院
2021年5月
在本文中,我们将介绍hello程序在linux下的生成,运行及回收的过程。通过一些工具研究hello.c的预处理,编译,汇编,链接的程序生成过程,以及计算机系统对hello可执行目标程序的进程,存储以及I/O管理,了解程序运行过程的关键。
关键词:hello;预处理;编译;汇编;链接;进程管理;存储管理;I/O管理
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
2.2在Ubuntu下预处理的命令.......................................................................... - 5 -
3.2 在Ubuntu下编译的命令............................................................................. - 6 -
4.2 在Ubuntu下汇编的命令............................................................................. - 7 -
5.2 在Ubuntu下链接的命令............................................................................. - 8 -
5.3 可执行目标文件hello的格式.................................................................... - 8 -
6.2 简述壳Shell-bash的作用与处理流程..................................................... - 10 -
6.3 Hello的fork进程创建过程..................................................................... - 10 -
6.6 hello的异常与信号处理............................................................................ - 10 -
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 -
8.1 Linux的IO设备管理方法.......................................................................... - 13 -
8.2 简述Unix IO接口及其函数....................................................................... - 13 -
第1章 概述
1.1 Hello简介
Hello的p2p:程序员在代码编辑器上写下高级语言的代码,然后经过预处理,编译,汇编与链接过程生成一个hello的可执行文件,完成从program到process的过程。
Hello的020:在系统的shell程序中执行./hello命令,shell会调用fork函数创建一个子进程,为其分配虚拟内存空间,然后调用execve()加载进程,执行hello的程序。程序执行结束后,父进程将子进程回收,释放内存,hello也就在内存中消失。
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
硬件环境:X64 Intel(R) Core(TM) i7-10750H CPU @ 2.60GHz RAM:16GB
软件环境:windows10 linux ubuntu 20.04
开发工具:dev codeblocks gcc,as,ld,vim,edb,gdb
1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
hello.c:源程序
hello.i:预处理后的文本文件
hello.s:编译后汇编程序文本文件
hello.o:汇编后的可重定位目标程序(二进制文件)
hello:链接后的可执行目标文件
1.4 本章小结
本章主要介绍了hello的p2p和020的过程,以及执行程序的一些中间结果。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
预处理的概念:预处理也称为预编译,它为编译做预备工作,主要进行代码文本的替换工作,用于处理#开头的指令,其中预处理器产生编译器的输出。经过预处理器处理的源程序会有所不同,在预处理阶段所进行的工作只是纯粹的替换和展开,没有任何计算功能。
预处理的作用:预处理阶段是预处理器(cpp)根据以字符#开头的命令,修改原始的C程序。如hello.c中第一行#include <stdio.h>命令告诉预处理器读取系统头文件stdio.h中的内容,并把它直接插入程序文本中,得到另一个以.i为扩展名的另一个C程序。
2.2在Ubuntu下预处理的命令
预处理命令:gcc -E hello.c -o hello.i
2.3 Hello的预处理结果解析
通过将hello.c进行预处理得到一个代码长很多的一个文件,在预处理的过程中,程序的头文件的内容写入程序源代码中,其他部分保持不变。
(以下格式自行编排,编辑时删除)
2.4 本章小结
本章我们了解了预处理的一些概念及用法,中间文件hello.i的大致内容。
(第2章0.5分)
3.1 编译的概念与作用
编译的概念:将高级语言代码转化为汇编语言代码,存储在hello.s中。
编译的作用:通过字词分析将高级语言代码转化为汇编代码,同时在转化的过程中可以对代码的语法进行检查,对一些操作进行简化,对源程序进行一些优化。
注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序
3.2 在Ubuntu下编译的命令
gcc -S hello.i -o hello.s
3.3 Hello的编译结果解析
3.3.1变量
全局变量
在代码的起始位置声明了2个全局变量的字符串,作为printf的输出部分。
在主函数中传入两个参数(在C语言代码中可以看到),编译器将两个参数分别放入寄存器%edi和%rsi中,
局部变量:在主函数中定义一个局部变量i作为计数器。在循环控制中,将i的值赋给%eax。
3.3.2控制转移
1.if条件判断控制转移
在主函数中,将第一个参数(存放在%edi中)与常数4比较,不满足相等则跳转。
2.for循环控制跳转
将-4(%rbp)作为计数器,在循环开始之前赋值为0,在条件判断未终止循环后加1,跳转至循环开始处继续执行循环内的语句。
3.3.3数组,指针操作
在主函数的循环中,使用了数组中的内容。主函数传入的第二个参数为数组的地址,即数组第一个元素的地址。在printf中需要输出数组的第二个和第三个元素,于是将栈指针中的内容赋给%rax,通过%rax的移动找到对应的元素,然后将数组第二个元素赋给%rdi,第三个元素赋给%rsi,它们分别为printf中的第一个和第二个参数。然后调用printf函数进行输出。
3.3.4函数调用
在主函数中,判断控制中出现一个调用printf的函数,函数不需要参数。在循环内部调用printf函数(具体分析见数组访问的那部分)。同时也使用了sleep和atoi函数,均不需要传入参数。
3.3.5算数操作与关系操作
在条件判断中存在判断参数与4是否相等控制跳转。在循环中,对计数器的循环+1属于算数操作,同时对于循环终止条件的判断也用到关系操
此部分是重点,说明编译器是怎么处理C语言的各个数据类型以及各类操作的。应分3.3.1~ 3.3.x等按照类型和操作进行分析,只要hello.s中出现的属于大作业PPT中P4给出的参考C数据与操作,都应解析。
3.4 本章小结
在本章中,我们对于hello.s中的汇编代码进行详细的解读,对于其中的一些指令以及控制跳转有所了解。
(第3章2分)
4.1 汇编的概念与作用
汇编的概念:汇编器将hello.s翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序的格式,并将结果保存在目标文件hello.o中。
汇编的作用:将汇编代码转化成机器可以理解并执行的文件。
注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。
4.2 在Ubuntu下汇编的命令
gcc hello.s -c -o hello.o
4.3 可重定位目标elf格式
指令:readelf -a hello.o >helloref.txt
1.ELF头
ELF头以一个16字节的序列开始,这个序列描述了生成该文件的系统 的字的大小和字节顺序,ELF 头剩下的部分包含帮助链接器语法分析和解 释目标文件的信息,其中包括 ELF 头的大小、目标文件的类型、机器类型、 字节头部表(section header table)的文件偏移,以及节头部表中条目的大小和数量等信息。
2.节头部表
在节头部表中可以看到各个节的类型,大小,地址,偏移量等信息。
3.重定位节
汇编器把对所有不确定最终位置的对外部数据或外部函数的引用放进重定位条目里。在链接时,链接器循着这些条目信息更改条目位置数据的引用位置。
R_X86_64_PC32 :重定位一个使用32位PC相对地址的引用。
R_X86_64_32 :重定位一个使用32位PC绝对地址的引用。
通过重定位算法根据不同类型,计算出指令的具体位置,即可执行目标函数。
4.符号表
符号表存放在程序中定义和引用的函数和全局变量的信息。其中,name是字符串表中的字节偏移,指向符号的以null结尾的字符串名字;value是符号的地址。对于可定位目标文件来说,value是距定义目标的节的起始位置的偏移。对于可执行目标文件来说,该值是一个绝对运行的地址。size是目标的大小(以字节为单位),type通常要么是数据要么是函数。bind字段表明符号是本地的还是全局的。
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
4.4 Hello.o的结果解析
反汇编代码图
汇编代码图
机器语言是有二进制的机器指令集构成的,在反汇编图中可以看到机器指令与汇编语句是一一对应的。在机器语言中立即数是用16进制数表示,而在汇编语言中用10进制表示。机器语言的分支转移使用的是相对地址,汇编语言则使用类似L2的代码段来完成转移。机器语言的函数调用也使用的是函数的相对地址,汇编语言中只有call+函数名。
objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。
4.5 本章小结
本章研究了汇编语言到机器语言的转换,通过对hello.o文件的展开了解了汇编的过程,也将汇编与反汇编代码比较,对于汇编有更深入的认识。
(以下格式自行编排,编辑时删除)
(第4章1分)
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
使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件
5.3 可执行目标文件hello的格式
1.ELF头
在ELF头表中列出了文件的基本信息,包括程序的入口地址。
2.节头表
在节头表中可以看到各段的地址及大小。
3.程序头
4.符号表(仅展示部分)
符号表中保存着定位、重定位程序中符号定义和引用的信息
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
5.4 hello的虚拟地址空间
图为在symbolview中查看各段地址信息(仅展示部分)
在图中可以看到各段的起始地址与5.3中看到的基本一致,并且增加了一些外部函数的地址和一些其他信息。
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
5.5 链接的重定位过程分析
hello与hello.o的不同:
1.链接后hello中增加了在hello.c中的库函数以及.init和.plt节,和一些节中定义的函数。
2.hello中函数的地址为实际可运行地址(虚拟地址),而hello.o中函数的地址用起始地址加偏移量得到,需要通过重定位获得运行地址。
3.在hello中因为不需要重定位,也就没有重定位条目。在hello.o中需要重定位条目获得函数地址。
链接的过程:链接器将各个目标文件组装在一起,将文件中的函数整理排列,确定其可运行地址。
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
5.6 hello的执行流程
1.从加载hello到_start
ld -2.27.so!_dl_start
ld-2.27.so!_dl_init
hello!start
2.到call main
libc-2.27.so!_libc_start_main
libc-2.27.so!_al_fixup
libc-2.27.so!_libc_csu_init
libc-2.27.so!_setjmp
hello!main
3.到结束
hello!puts@plt
hello!exit@plt
ld-2.27.so!_dl_fixup
ld-2.27.so!exit
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
5.7 Hello的动态链接分析
由于编译器无法预测函数的运行时地址,所以需要添加重定位记录。为避免运行时修改调用模块的代码段,链接器采用延迟绑定的策略,使用全局偏移量表和 过程链接表(PLT+GOT)实现函数的动态链接。PLT 使用 GOT 中的地址跳到目标函数,在加载时,连接器会重定位GOT,使得他包含目标的正确的绝对地址。
在节头部表中可以查到.got.plt的起始地址为0x404000
dl.init前
di.init后
可以看到在di.init后增加了两个地址,即动态链接函数的地址。
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
5.8 本章小结
本章我们通过对链接后的文件hello进行详细分析,了解了链接的过程以及重定位,对比链接前与链接后的区别,分析动态链接过程,对链接有更深入的了解。
(第5章1分)
6.1 进程的概念与作用
进程的概念:进程是一个执行中程序的实例,系统的每个程序都运行在某个进程的上下文中,上下文是由程序正确运行所需的状态组成的。包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。
进程的作用:每次用户通过向shell 输人一个可执行目标文件的名字,运行程序时,shell 就会创建一个新的进程,然后在这个新进程的上下文中运行这个可执行目标文件。应用程序也能够创建新进程,并且在这个新进程的上下文中运行它们自己的代码或其他应用程序。
6.2 简述壳Shell-bash的作用与处理流程
作用:Shell是指UNIX系统下的一个命令解析器;主要用于用户和系统的交互。Shell可以逐行获取用户发出的指令并执行。
处理流程:
1.对用户输入的命令行根据特殊符号进行分解,获得命令的参数
2.对参数进行判断,如果是内置命令就立即执行
3. 否则调用相应的程序为其分配子进程并运行。
4.执行完一个命令,shell返回循环顶部,等待下一个命令的输入。
6.3 Hello的fork进程创建过程
1. 在终端输入./hello。它不是shell的内置命令,shell会从文件系统中找到当前目录下的hello文件并执行。
2. Shell调用fork函数,创建一个子进程。子进程除了进程号之外与父进程相同。子进程得到与父进程用户级虚拟地址空间相同的一份副本,包括代码和数据段、堆、共享库以及用户栈。子进程还获得与父进程任何打开文件描述符相同的副本,当父进程调用fork时,子进程可以读写父进程中的任何文件。
3. 当子进程运行结束时,父进程如果仍然存在,则执行对子进程的回收,否则就由init进程回收子进程。
6.4 Hello的execve过程
1.调用函数fork创建新的子进程之后,子进程会调用execve函数,在当前进程的上下文中加载并运行一个新程序hello。
2. execve函数加载并运行可执行目标文件filename,且带参数列表argv 和环境变量列表envp。
3. 然后通过跳转到程序的第一条指令或入口点来运行该程序。
6.5 Hello的进程执行
上下文信息:上下文就是内核重新启动一个被抢占的进程所需要的状态,它由通用寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构等对象的值构成。
进程时间片:一个进程执行它的控制流的一部分的每一时间段叫做时间片。
用户模式和内核模式:处理器通常是用某个控制寄存器中的一个模式位来提供这种功能的,该寄存器描述了进程当前享有的特权。当设置了模式位时,进程就运行在内核模式中(有时叫做超级用户模式)。一个运行在内核模式的进程可以执行指令集中的任何指令,并且可以访问系统中的任何内存位置。
Hello开始在用户模式运行,内核保存上下文,然后调用printf函数,系统从用户模式切换到内核模式,执行完printf函数后再切换回用户模式。当调用sleep函数后,进程休眠,内核进行上下文切换,调用其他进程运行。等到休眠的时间到时,系统发生中断,再次进行上下文切换,转换到hello进程原先运行的位置,继续运行。
结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。
6.6 hello的异常与信号处理
1.程序正常运行,按下回车键进程结束并回收进程
2.在程序运行时按回车键,程序继续正常运行,但输出结束后接收回车的信号终止并回收进程。
3. 3.按下Ctrl + C,Shell进程收到SIGINT信号,Shell提前结束并回收hello进程。
4.按下Ctrl + Z,Shell进程收到SIGSTP信号,Shell显示屏幕提示信息并挂起hello进程。
5.输入ps和jobs
6.输入pstree查看进程树(部分)
7.输入fg1后程序从挂起处继续运行
8.输入kill杀死进程,挂起后无法继续运行
9.不停乱按,则将输入字符当做下一次输入的命令行,程序正常运行。
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
6.7本章小结
本章概括了进程的概念和作用,简述了shell-bash的作用以及处理流程。介绍了hello的fork进程创建过程与execve过程。我们对hello程序执行来了解进程运行以及对异常处理的一些操作与原理。
(第6章1分)
7.1 hello的存储器地址空间
逻辑地址:逻辑地址是指由程序产生的与段相关的偏移地址部分,逻辑地址由选择符和偏移量两部分组成。经过寻址方式的计算或变换得到内存储器中的实际有效地址即物理地址。
线性地址:由逻辑地址加上基地址产生线性地址。
虚拟地址:虚拟地址即线性地址。
物理地址:是指出目前CPU外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果地址。
结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。
7.2 Intel逻辑地址到线性地址的变换-段式管理
一个逻辑地址由两部分组成,段标识符和段内偏移量。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号。后面3位包含一些硬件细节。可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段。线性地址为段首地址与逻辑地址中的偏移量组成。
7.3 Hello的线性地址到物理地址的变换-页式管理
页式管理是一种内存空间存储管理的技术,页式管理分为静态页式管理和动态页式管理。将各进程的虚拟空间划分成若干个长度相等的页,页式管理把内存空间按页的大小划分成片或者页面,然后把页式虚拟地址与内存地址建立一一对应页表,并用相应的硬件地址变换机构,来解决离散地址变换问题。页式管理采用请求调页或预调页技术实现了内外存存储器的统一管理。当给定了线性地址后,首先以其最高k位(通过页目录表确定k)作为索引,查看页目录表的对应项,里面存放的是页表的物理地址。以中间若干位(通过页表确定)作为索引,在页表中找到物理页的物理起始地址。物理页基地址加上线性地址种剩余低位的偏移量,就能找到线性地址最终对应的物理内存单元。
7.4 TLB与四级页表支持下的VA到PA的变换
虚拟地址被划分为4个VPN和一个VPO.每个VPNi都是一个到第i级页表的索引。第j级页表中的每个PTE,都指向第j+1级的某个页表的基址。第k级页表中的每个PTE包含某个物理页面的PPN,或者一个磁盘块的地址。为了构造物理地址,在能够确定PPN之前,MMU必须访问k个PTE。MMU将这个虚拟地址翻译成一个物理地址,并且将它发送给高速缓存/主存中。高速缓存或主存将所请求的数据字返回给cpu。(以下格式自行编排,编辑时删除)
7.5 三级Cache支持下的物理内存访问
在通过虚拟地址转换得到物理地址后,cpu将对物理地址进行访问。先在高速缓存L1进行索引,如果匹配成功且块的valid标志位为1,则命中,取出相应的数据后返回。若未命中则继续向L2,L3主存继续访问。
7.6 hello进程fork时的内存映射
当fork 函数被shell调用时,内核为hello进程创建各种数据结构,并分配给它一个唯一的PID 。为了给hello进程创建虚拟内存,它创建了hello进程的mm_struct 、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。
当fork在hello进程中返回时,hello进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,因此,也就为每个进程保持了私有地址空间的抽象概念。
7.7 hello进程execve时的内存映射
execve函数在当前进程中加载并运行包含在可执行目标文件hello中的程序,用hello有效替代当前程序。加载并运行hello需要以下几个步骤:
1.删除已存在的用户区域,删除当前进程虚拟地址的用户部分中的已存在的区域结构。
2.映射私有区域。为hello程序的代码、数据、bss和栈区域创建新的区域结构,所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为hello文件中的.text和.data段,bss是请求二进制零的,映射到匿名文件,大小包含在hello中,栈和堆也是请求二进制零的,初始长度为0。
3.映射共享区域。如果hello程序与共享对象链接,如libc.so,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内。
4.设置程序计数器。execve做的最后一件事就是设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。下一次调度这个进程时将从这个入口点开始执行。
7.8 缺页故障与缺页中断处理
缺页异常会调用内核中的缺页异常处理程序,该程序会选择一个牺牲页,如果牺牲页已经被更改,那就先将其存回到磁盘中。找到了要存储的页后,内核会从磁盘中将需要访问的内存,并且将PTE中的信息更新,这样就成功的将一个物理地址缓存在了页表中。当异常处理返回的时候,CPU会重新执行访问虚拟内存的操作,这个时候就可以正常的访问,不会发生缺页现象了。
7.9动态存储分配管理
动态储存分配管理使用动态内存分配器来进行。动态内存分配器维护着一个进程的虚拟内存区域,称为堆。分配器将堆视为一组不同大小的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可以用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配的状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。动态内存分配主要有两种基本方法与策略:
1.带边界标签的隐式空闲链表分配器管理
带边界标记的隐式空闲链表的每个块是由一个字的头部、有效载荷、可能的额外填充以及一个字的尾部组成的。隐式空闲链表:在隐式空闲链表中,因为空闲块是通过头部中的大小字段隐含地连接着的。分配器可以通过遍历堆中所有的块,从而间接地遍历整个空闲块的集合。其中,一个设置了已分配的位而大小为零的终止头部将作为特殊标记的结束块。当一个应用请求一个k字节的块时,分配器搜索空闲链表,查找一个足够大的可以放置所请求块的空闲块。分配器有三种放置策略:首次适配、下一次适配合最佳适配。分配完后可以分割空闲块减少内部碎片。同时分配器在面对释放一个已分配块时,可以合并空闲块,其中便利用隐式空闲链表的边界标记来进行合并。
2.显示空间链表管理
显式空闲链表是将空闲块组织为某种形式的显式数据结构。因为根据定义,程序不需要一个空闲块的主体,所以实现这个数据结构的指针可以存放在这些空闲块的主体里面。如,堆可以组织成一个双向链表,在每个空闲块中,都包含一个前驱与一个后继指针。显式空闲链表:在显式空闲链表中。可以采用后进先出的顺序维护链表,将最新释放的块放置在链表的开始处,也可以采用按照地址顺序来维护链表,其中链表中每个块的地址都小于它的后继地址,在这种情况下,释放一个块需要线性时间的搜索来定位合适的前驱。
Printf会调用malloc,请简述动态内存管理的基优化程序性能(CSAPP:5)
【前言】虽然现在没有接触过大型项目,但是工作了会注重性能。学习一下,应该能更好更快的理解别人写的经典优秀的代码。结合CSAPP和自己的理解,总结一下。
一、程序优化综述
1、高效程序的特点
(1)适当的算法和数据结构。方法和数据的组织形式无疑是最关键的,是优化的基础;
(2)代码能够被编译器转化成高效的可执行代码。需要深入了解使用的编译器的优化方法,和常见的优化策略;
(3)运用现代并行编程技术。多核以及硬件支持提供更大的加速可能,例如GPU;
2、优化程序的一般步骤
(1)消除不必要的工作,例如消除不必要的函数调用(加大栈区负担),条件测试和内存引用;
(2)利用处理器提供的指令级并行能力,同时执行多条指令;指令级并行介于线程级并发和单指令多数据并行之间,一个处理器可实现多指令执行,例如流水线技术;
(3)优化关键路径。就是反复执行的数据和代码;
二、优化编译器的能力和局限性
我们可以使用-O1、-O2、-O3来指定编译器的优化级别,级别越高可能会增加程序的规模。注意一点,优化级别高一般比级别低的性能好,肯定比原始未优化的好!但是,编译器在优化时会考虑安全问题,如果优化一定会在安全范围内优化,有一个条件不满足就会放弃某模块的优化。
例如:
1 { 2 *y += *x; 3 *y += *x; 4 } 5 //上面的代码会被优化为下面吗? 6 { 7 *y += 2* *x; 8 }
不会,虽然适用一般情况y=y+2x。但是,当x=y时:y=4x 不是3x。编译器会考虑所有特殊情况,保证安全。还有一种情况,是内存别名使用的时候两个变量值不同,但是指针地址有可能相同。也不会优化.
函数调用也会妨碍优化,此时就是用内联函数优化了。避免频繁出栈入栈。
以上是关于CSAPP程序人生的主要内容,如果未能解决你的问题,请参考以下文章
CSAPP Lab:Shell Lab——理解进程控制的秘密
Linux运维Ubuntu Server的无密码开机自动登录
Linux运维Ubuntu Server的无密码开机自动登录