• 深入了解计算机系统(CS:APP)思考题


    QA1

    1.简述C、ASM、ML的关系,各自优缺点?

    机器语言是二进制数据表示的语言,机器可以直接识别;汇编语言是与机器指令一一对应的助记符,是一种低级语言,经过汇编和链接之后机器可以运行;C语言在低级语言的基础上,采用最接近人类自然语言的单词和符号来表示一组低级语言程序。

    C语言的优点:提供了结构化程序设计的环境和工具,设计出来的程序可读性好,可维护性强,可靠性高;抽象程度高,远离机器语言,和计算机硬件关系不大,编程门槛低,无需懂计算机原理和计算机结构;编写出来的程序可移植性好,重用率高
    缺点:相对汇编语言和机器语言来说执行效率不高

    **汇编语言的优点:**能发挥计算机的特定和功能,程序紧凑,资源利用率高
    缺点:代码可读性较差,难以维护;特定的汇编语言和特定的机器语言指令集一一对应,只能针对特定体系结构和处理器进行优化,可移植性差;开发效率低,时间长且单调;编程门槛高,需要了解计算机原理和计算机结构

    **机器语言优点:**能利用机器指令精准的描述算法,编程质量高;所占用储存空间小;执行速度快
    缺点:开发过程细节繁琐,编程门槛高;可读性差,不便于交流;严重依赖具体的计算机,可移植性差,重用性差。

    ##2.Hello.c 经过那些工具、步骤、生成什么类型的文件? ##

    ##3. 什么是程序可移植性?汇编语言可移植吗?为什么?
    程序可移植性指在不同环境下,其是够具备可以被重复使用的性质。在移植到不同的系统平台使,所做的改动少,则可移植性好;否则,可移植性差。

    汇编语言可移植性差。

    原因:汇编语言是机器语言的主机语言,针对特定的指令系统,和特定的机器指令集一一对应;程序结构性差,不便于模块化的设计,也造成可移植性差

    4. 编译与解释有什么区别?各举出2个语言的例子

    编译程序是将高级语言程序转换为机器级目标程序,执行时只需要启动目标程序即可,如将C、C++编译为Windows上的可执行2进制文件。

    5. 解释是将高级语言语句逐条翻译成机器指令并立即执行,不生成目标 文件,例如Java需要目标机器上安装JRE。

    6. 程序优化的目的?

    1. 提高程序运行效率(性能)

    2. 使程序更加节省存储空间和运行空间

    3. 程序更加正确

    4. 更可靠

    5. 可移植性更好

    6. 功能更强大

    7. 使用更方便

    8. 格式符合编程规范,接口规范

    9. 更易懂(能读明白,有注释,模块化)

    10. 更美

    11. 计算机软件与硬件的界面/接口是什么?
      操作系统内核是应用程序和硬件之间的媒介。它提供三个基本的抽象:

    12. 文件是对I/O设备的抽象

    13. 虚拟内存是对主存和磁盘的抽象

    14. 进程是处理器、主存和I/O设备的抽象

    8. 简述中间层语言及其运行机制 (这个应该不会考吧,没找到答案,下面这段随便看看吧)

    因为很多程序只能在一种操作系统上运行。在其他操作系统上不能运行。那怎么能够支持多重硬件平台、多种软件平台(包括操作系统)呢?

    这就要用到一种新的技术——中间层语言,微软有自己的中间层语言,Java也有(叫Java字节码),凡是微软的语言比如C、C++,在运行时都要先编译成MSIL(中间层语言),然后通过微软的通用语言运行时(Common Language Runtime)来运行,而Java语言在编译成Java字节码之后要在Java虚拟机上运行,Java虚拟机通过通用语言运行时对生成的中间层代码进行解释。这样程序的可移植性就有了提高。因此我们一般编程只需面对中间层预言、虚拟机即可,这是计算机系统抽象表示的发展趋势

    在编译器将源代码编译为目的码的过程中,会先将源代码转换为一个或多个的中间表述,以方便编译器进行最佳化,并产生出目的机器的机器语言。通常,中间语言的设计与一般的机器语言有三个不同之处:

    每个指令代表仅有一个基本的操作。举例来说,在微处理器中出现的shift-add定址模式在中间语言不会出现。

    指令集内可能不会包含控制流程的资讯。

    暂存器可用的数量可能会很大,甚至没有限制。

    最常见的中间语言表述形式,是三位址码(Three address code)。

    这个术语也同时用来代称一些作为中间层的语言,有些高级语言不会输出为机器语言,它们仅会输出这种中间语言,而这些中间语言则会像一般语言一样,提交给编译器,编译为机器语言。这通常被用于让最佳化的过程更简单,也用于增进可移植性的能力,改进移植的方式则是利用中间语言的编译器,可以编译出许多中央处理器及操作系统可使用的机器码,例如C语言。中间语言的复杂度,通常介于高阶语言及低级语言之间,例如汇编语言。

    9. 设计开发一个Java处理器是否可行?

    查不到
    https://blog.csdn.net/zxadcsdn/article/details/86409600
    这篇博客画了一个java处理器的图

    11. 程序执行结果与那些相关呢?程序编写、语言处理系统、OS、ISA-机器语言、微体系结构等

    程序执行结果不仅取决于算法、程序编写,而且取决于语言处理系统、操作系统、ISA、微体系结构。

    QA2

    1.8086 CPU内部寄存器组有哪几个?都是多少位的 ##?


    都是16位
    汇编语言中寄存器功能的解释:
    https://blog.csdn.net/a675311/article/details/53220590

    2.8086 访问存储器的地址有哪部分组成?物理地址怎么形成的?

    逻辑地址由段地址和段内偏移地址两部分组成。

    存储单元的物理地址由地址加法器生成。寻址时,CPU首先将段地址和段内偏移地址送入地址加法器,地址加法器将段地址向左偏移四位并且与段内偏移地址相加,得到一个20位的物理地址。

    3.BIU与EU是怎么完成指令执行的?

    BIU指总线接口单元,EU指执行单元。

    当指令队列中有2个空字节时,BIU自动把指令取到指令队列中,当指令队列已满,而EU无总线访问请求时,BIU进入空闲状态。

    EU从指令队列的头部取出指令,并执行该指令。在执行中,如果需要访问内存或者I/O设备,则EU请求BIU取操作数,并直到需要的操作数到来后,EU才继续操作。若BIU处于空闲态,它立即响应请求,若BIU正在取指令到指令队列,它先完成取指令操作,再响应EU的请求。

    EU在执行转移、调用,返回等指令时,指令队列中的指令被清除,BIU重新从存储器中取出指令送到指令队列,EU才继续执行指令。

    4. int x ; AX=100; AX=AX+256; x=AX;

    程序/指令中的常数在内存哪个区域/段? ##
    看不懂题目
    未初始化的静态变量和初始化为0的静态变量或者全局变量在.bss区
    已初始化的静态变量和全局变量在.data区
    局部变量在栈里

    5.全局变量x以一种什么方式在机器/汇编程序中出现?怎么访问的?至少访几次存储器 ##?

    采用内存引用的方式访问全局变量。

    6.C语言的数据类型char、short int、int、long、long long、float、double、long double、指针等在32/64位系统中占多少个字节?邮编为什么char[]?

    (实验报告里面扒下来的)

    QA3

    0. 计算机是64位的是指CPU寄存器是64位的?

    计算机是xx位的,指的是计算机CPU字的长度(字长)。
    计算机中ALU的位数 = CPU中通用寄存器的位数 = 计算机的位数

    1. 常量表达式是谁来计算的?

    编译器在编译阶段计算。

    2.0与‘0’谁大?差多少?空间呢??

    字符‘0’的ASCII码是48,则‘0’比0大,差48。

    空间和声明的类型有关,如果0声明为int, ‘0’声明为char,则数字0占用的空间较大。

    2. C源程序中的有符号常数,是怎么变成二进制补码的和进行运算的?

    什么破问题。

    3. strlen(“1234567 我想毕业\n”)=?怎么算汉字数

    strlen计算的长度是该字符串对应的编码占用的字节数,所以这个长度在不同的编码下结果也不相同。例如,gbk编码下中文字符占用2字节,UTF-8存储中文时占2~4个字节.
    计算汉字数?在UTF-8里,英文字符仍然跟ASCII编码一样,因此原先的函数库可以继续使用。而中文的编码范围一般是在0080-07FF之间,因此是2个字节表示。计算长度时可以根据
    1字节 0xxxxxxx
    2字节 110xxxxx 10xxxxxx
    3字节 1110xxxx 10xxxxxx 10xxxxxx
    4字节 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
    5字节 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
    6字节 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
    由开始的0和1的个数来判断字节数,从而计算。(我自己这么觉得,查不到一个靠谱的答案)
    https://blog.csdn.net/u014431852/article/details/48003321
    https://blog.csdn.net/tinyletero/article/details/8201465

    5.汉字是怎么输入进入源程序的(Win/Linux)?

    6. OS内核的编码决定了基于其上的系统软件与应用软件的编码要与其一致。不一致怎么办?要转换吗?谁来转换?

    要转换。

    7.当前的源程序编码是什么? 可以变吗?

    https://wenku.baidu.com/view/191f12537cd184254b353560.html这个讲的很清楚

    8. 汉字是怎么显示/打印输出的?

    ASCII码->编号->字库中点阵图编码->打印

    QA4

    1. IEEE754比整数部分10位+小数部分20位的表示方法有什么优点? 缺点呢—考虑下?

    优点:可表示数值范围较大,精度较高;不知道有啥缺点,等我问问老师

    2. float非无穷的最大值,最小值?

    float非无穷的最大值IEEE754表示是0111_1111_0111_1111_1111_1111_1111_1111
    阶码是127,尾数是2-2(-23)。所以最大值是(2-2(-23))* 2^127
    最小值符号位取1即可
    正数的最小值,IEEE754表示是0000_0000_0000_0000_0000_0000_0000_0001
    阶码是1-127 = -126,尾数是2(-23)。值是2(-149)

    3. Float的最大绝对值?最小绝对值?

    和上面重复

    4. float数 1,65536,0.4,-1,0的内存表示

    1 = 1*2^0 则frac = 000_0000_0000_0000_0000_0000, exp = 0111_1111(127)
    表示是0011_1111_1000_0000_0000_0000_0000_0000
    65536 = 2^16, 则frac = 000_0000_0000_0000_0000_0000,exp = 1000_1111
    表示是0100_0111_1000_0000_0000_0000_0000_0000

    0.4 = 0.0110_0110_0110……,涉及到舍入问题,frac = 100_1100_1100_1100_1100_1100
    exp = 0111_1101(125),表示是0011_1111_0101_1011_0110_1101_1011_0111
    -1和0略

    5. 一个数的Float形式是唯一的吗?(除了0)

    是,根据IEEE754标准,一个数字对应的格式是唯一的

    6. 每一个IEEE754编码对应的数是唯一的吗?

    不是,有些数字不能用二进制数字精确表示,涉及到舍入问题,可能不同的数字舍入之后对应的IEEE754编码完全相同。另外,当数字超出IEEE编码所能表示的范围,不同数字对应的编码都是一样的。(原因是我自己的想法)

    7. Float的阶码范围是多少?

    -126 ~ +127
    简述Float数据的浮点数密度分布?
    分布不均匀,靠近远点处它们更稠密(课本P80)

    8. C语言中除以0一定报错溢出吗?

    整数报错,浮点无穷大X/0>Y 可以

    9. int与float都占32个二进制位,Float与INT相比谁的个数多?各自是多少个?多多少? (±0、nan)

    Int表示的个数多(不知道这个表示是什么意思,我理解为精确表示)

    Int 可以表示2^32个数字(+0和-0看做不同的数)

    float可以表示232-224个数字(+0.0和-0.0看做不同的数,不算INF和NAN)

    10. Float的最大密度区间(非无穷)?Float数多少?密度多少?

    按照阶码区域写出float的最大密度区域的范围及其密度,最小密度区域及其密度(区域长度/表示的浮点个数)

    阶码是0000_0000(表示的数值范围是-1.1754942E-38 到1.1754942E-38)、(1 - (1/2)23)*2-149_(最小密度区间)、阶码是1111_1110(表示的数值范围是-3.4028235E38到-1.7014118E38和1.7014118E38到3.4028235E38)、
    (2^104 - 2^81)(最大密度区间)

    微观世界:能够区别最小的变化___2^-149_____,其10进制科学记数法为_____1.4E-45____

    宏观世界:不能区别最大的变化____2128____,其10进制科学记数法为______3.4*1038___

    11. Float的最小密度区间?Float数多少?密度多少?

    同上

    12. Float最大密度区间是最小密度区间的密度的多少倍?

    约等于2^253 - 2^230

    13. float最大的负数是多少==最小的正数是多少?

    正数的最小值,IEEE754表示是0000_0000_0000_0000_0000_0000_0000_0001
    阶码是1-127 = -126,尾数是2(-23)。值是2(-149)

    14. 怎么判断和定义浮点数的无穷大以及NaN?

    当exp部分都是1

    如果frac部分的位模式都是1表示INF

    否则,表示NAN

    浮点数的表示,越小精度越高,越大精度越低,这也基本符合数据处理的规律。太大的数据差点没啥,就是个规模而已。如人口、GDP等,没有必要到个、圆角分。

    15. Float使用注意事项? 怎么比较两个float数据?

    先判断是不是NAN,如果有一个数字是NAN,比较无意义。

    如果均是数字,则按照下面原则进行比较:

    先比较符号位,符号位是0的比符号位是1的大

    阶码从最高位进行比较,例如阶码第一位是1的数字比阶码第一位是0的数字大,如果第一位相同向后比较即可,遵从同样的规则,直到分区处大小或者阶码相同。

    如果阶码相同则比较尾数部分,同样是先比较高位之后比较低位即可。

    16. 浮点数的舍入采用什么规则?

    IEEE浮点格式定义了四种不同方式的舍入方法,默认的方式是找到最接近的匹配,而其他三种可用于计算上界和下界。向偶数舍入是默认的舍入方法。

    17. 向偶数舍入方法,尾数会不会产生溢出,如果产生溢出怎么办?阶码会不会溢出?怎么办?printf函数把浮点数按照十进制数打印%10.2f,按照什么舍入原则呢? 到底是什么?不要想当然

    向偶数舍入,尾数可能产生溢出。

    产生溢出,把阶码加一即可。

    阶码产生溢出,说明这个数字超出了浮点数可以表示的范围。

    按照十进制数字的四舍五入原则。

    18. 通用寄存器的通用是什么意思

    因为通用寄存器既可用于传送和暂存数据,也可参与算术逻辑运算,并保存运算结果。

    19. 32位CPU怎么实现64位加法

    大多数处理器都包含一个进位位和溢出位来支持位宽大于机器字长的数字的运算。
    进位位用于无符号运算,溢出位用于有符号数运算。并且,位宽较大的数字分开存储即可。(参考家庭作业2.75)

    大多数操作基本上是串行的。当在二进制电平上进行加法运算时,您需要两个输入位,并产生一个结果位和一个进位位。然后当将下一个最低有效位加入时,进位位用作输入,以此类推(

    CPU字长只是限制了它可以运行的最大时钟速度

    原文链接http://www.it1352.com/489105.html

    (*)20. 数组、结构、指针各用什么寄存器指示?

    21. 标志寄存器有什么作用?

    (课本)他们描述了最近的算术和逻辑操作的属性,可以检测这些寄存器来执行条件分支指令。

    (博客)具有以下 3 种作用。

    (1)用来存储相关指令的某些执行结果;

    (2)用来为 CPU 执行相关指令提供行为依据;

    (3)用来控制 CPU 的相关工作方式

    21. 状态标志位有哪几个?加法运算会影响那些?

    记住四个就行:
    CF:进位标志。最近的操作使最高位产生了进位。可用来检查无符号操作的溢出。

    ZF:零标志。最近的操作得出的结果为0.
    
    SF:符号标志。最近的操作得到的结果为负数。
    
    OF:溢出标志。最近的操作导致一个补码溢出一一正溢出或负溢出。
    
    • 1
    • 2
    • 3
    • 4
    • 5

    加法运算都可能改变。
    详细介绍:
    https://www.cnblogs.com/eleven24/articles/7783283.html

    22.控制标志位 IF/DF作用,怎么改变?(应该不考,毕竟书上没有提到)

    DF标记块传送是从低地址向高地址传,还是从高地址向低地址传。

    IF可以屏蔽 可屏蔽中断请求INTR

    如果外设有可屏蔽中断请求INTR,而此时CPU内IF=0,那么CPU不会响应中断

    只有可屏蔽中断请求INTR和IF有关系,

    内中断和不可屏蔽中断NMI,都不受IF的影响

    23. CF与OF是什么关系?

    CF针对无符号数(将寄存器中的操作数都看作是无符号数)

    OF针对有符号数(将寄存器中的操作数都看作是有符号数)

    24. 计算机怎么判断两个数相加是否超出了范围?

    CF位来判断无符号数运算的溢出,OF位来判断补码运算是否溢出。

    25. 怎么修改IP寄存器?

    IP寄存器的值通常不能直接被修改,需要使用call,ret,jmp等控制转移指令类修改。

    26. 32/64位的cpu中寄存器都是32/64位的?

    不是,第三章最后提到的媒体寄存器是256位的,段寄存器(有的说32位,有的说16位)

    CPU的字长 == 通用寄存器的位宽

    27. 请列出C语言的所有操作/指令,与汇编语言对比,说明汇编语言的优点

    重点:逻辑操作/位操作

    这个不会

    28. 操作数的寻址方式有哪几种?

    29. 一/二维数组采用什么寻址方式?

    比例变址寻址

    30.结构体的某一个整型成员采取什么寻址方式?

    31. 结构体的整形数组采用什么寻址方式?

    一个C生成的执行程序是多少位的是由谁来决定的?

    (A)CPU (B)OS (C)编译器 (D)源程序

    ** C **

    Q&A 9


    int a[100]; 在作为全局变量、局部变量、参数时是怎么实现对a[i]元素的访问的?

    全局变量:利用%rip+偏移量访问
    局部变量:利用%rbp+偏移量访问
    参数:先通过%rbp+偏移量得到栈中作为参数传递的a的首地址,然后利用此地址+偏移量访问


    int p;(p++)与(*p)++实现有什么不同?

    对于*(p++),是取得p+sizeof(int)地址处的值
    mov -0x8(%rbp),%rax
    add $0x4,%rax
    mov %rax,-0x8(%rbp)
    对于(*p)++,是将p地址处的值自增1
    mov -0x8(%rbp),%rax
    mov (%rax),%eax
    lea 0x1(%rax),%edx
    mov -0x8(%rbp),%rax
    mov %edx,(%rax)


    if 分支语句的汇编语言实现与C源程序的条件判断之间是 互补 关系。

    if(y>100)
    0x5555555548c9 mov -0x20(%rbp),%eax
    0x5555555548cc cmp $0x64,%eax
    0x5555555548cf 77 _ jle 0x5555555548d8
    0x5555555548d1 movl $0xffffffff,-0x20(%rbp)


    switch多分支,case条件码不规律时是怎么实现的?

    稀疏的Switch语句 使用决策树 (if-elseif-elseif-else)

    Q&A 10


    switch多分支,case条件码规律时是怎么实现的?(注意我们讲的与书上不同,看下不同在哪儿?)

    主要采用的数据结构是什么?

    跳转表

    跳转表的元素类型?(不同)

    一个代码段的地址

    跳转表的元素个数怎么计算?

    跳转元素的最大可能值-最小可能值

    跳转表的初值是什么?什么时候由谁来赋初值的?

    没看懂

    元素下标怎么计算?

    跳转元素-跳转元素最小可能值

    怎么计算跳转地址?

    当下标在跳转表中时,跳转地址=基地址+下标*8

    跳转指令是什么寻址方式?

    变址寻址


    叶子节点与非叶子节点的函数汇编语言实现有什么区别?

    叶子节点的函数无需使用push,pop等保存,传递参数,也无需使用call调用其他函数,但非叶子节点的函数需要


    递归程序的汇编语言是怎么实现的?

    递归无需特殊的处理
    栈帧意味着每个函数调用私有的存储
    保存的寄存器 、局部变量
    保存的返回地址
    寄存器保存约定:
    防止函数调用损毁其他函数(调用)的数据
    除非C代码明确地这样做(如缓冲区溢出)
    栈的使用原则:遵循 调用/返回 模式
    如 P调用Q, 然后Q 在P结束之前返回
    后入、先出
    对互递归同样有效
    P调用Q; Q调用P

    Q&A11


    静态局部变量是怎么生成汇编语言的?

    空间:全局区
    赋初值:定义该变量的函数第一次被调用时
    生命周期:整个源程序的周期


    分析全局变量、局部变量、传值的参数的生命周期

    静态全局变量:整个源程序的周期
    动态全局变量:整个源程序的周期
    静态局部变量:整个源程序的周期
    动态局部变量:定义它的函数的周期
    传值的参数:定义它的函数的周期


    简述缓冲器溢出的原理

    缓冲区溢出,简单的说就是计算机对接收的输入数据没有进行有效的检测,向缓冲区内填充数据时超过了缓冲区本身的容量,而导致数据溢出到被分配空间之外的内存空间,使得溢出的数据覆盖了其他内存空间的数据。


    怎么攻击缓冲器溢出的漏洞?

    攻击原理(3个采分点):向程序输入缓冲区写入特定的数据,例如在gets读入字符串时,使位于栈中的缓冲区数据溢出,用特定的内容覆盖栈中的内容,例如函数返回地址等,使得程序在读入字符串,结束函数gets从栈中读取返回地址时,错误地返回到特定的位置,执行特定的代码,达到攻击的目的。


    怎么防范缓冲器溢出漏洞?

    防范方法(2个采分点,有2个就算对):

    1. 代码中避免溢出漏洞:例如使用限制字符串长度的库函数
    2. 随机栈偏移:程序启动后,在栈中分配随机数量的空间,将移动整个程序使用的栈空间地址
    3. 限制可执行代码的区域
    4. 进行栈破坏检查——金丝雀

    结构体的成员在汇编语言层面是怎么操作的?

    结构体用内存块来表示
    足够大,可容纳所有字段
    字段顺序必须与声明一致
    即便其他顺序能使得内存更紧凑——也不行!
    编译器决定总的尺寸和各字段位置
    机器级程序不解读(理解)源代码中的结构体
    数组元素的地址
    每个结构体成员的偏移量(Offset)是在编译阶段确定的
    地址计算形式:r + size*idx
    结构体成员通过结构体基地址+偏移量的方式访问


    #结构体成员作为参数传输是怎么实现的?
    传递结构体基地址+偏移量处的对应成员


    #结构体成员作为返回值是怎么实现的?
    返回结构体基地址+偏移量处的对应成员

    Q&A12


    Y86-64CPU的状态存储部件主要有哪些?

    RF:程序寄存器 15个64位 %rax,%rcx,%rdx.%rbx,%rsp,%rbp,%rsi,%rdi,%r8到%r14
    CC:条件码 3个1位 ZF SF OF
    Stat:程序状态
    PC:程序计数器
    DMEM:内存


    Y86-64的寻址方式有哪几种?

    指令寻址方式:
    顺序寻址方式 PC
    跳跃寻址方式 jXX指令
    操作数寻址方式:
    寄存器寻址
    立即寻址
    间接寻址
    基址寻址


    Y86-64指令系统的算逻操作的操作数与X86-64有什么区别?

    Y86-64仅允许在寄存器数据中应用算逻操作
    X86-64则允许立即数,寄存器和内存


    Y86-64的Jxx跳转指令,寻址方式与X86-64(短/近)有什么区别?

    对目的地址进行完整编码
    和x86-64中的PC相对地址编码不一样


    rrmovq rax,rbx的机器指令是?

    2005
    0010 0000 0000 0101


    Y86-64支持过程调用的指令有哪几个?

    call
    ret


    Y86-64标志位怎么改变-副作用,怎么利用?

    算数和逻辑操作OPq设置条件码作为指令执行的副作用
    条件传送指令,条件跳转指令以条件码的值为依据


    状态码是怎么产生的?X86为什么看起来没有呢?

    AOK(1)-正常操作
    HLT(2)-执行Halt
    ADR(3)-错误的指令或数据地址
    INS(4)-无效指令
    对于Y86-64,当遇到这些异常的时候,我们就简单地让处理器停止执行指令。在更完整的设计中,处理器通常会调用一个异常处理程序,这个过程被指定用来处理遇到的某种类型的异常。就像在第8章中讲述的,异常处理程序可以被配置成不同的结果,例如,中止程序或者调用一个用户自定义的信号处理程序。

    Q&A 13


    Y86的SEQ分哪几个阶段?各阶段的主要过程是什么?

    取指:从指令存储器读取指令
    译码:读程序寄存器
    执行:计算数值或地址
    访存:读或写数据
    写回:写程序寄存器
    更新PC:更新程序计数器


    对Y86的SEQ阶段分析,12条指令的微操作实现的共同点有哪些?

    所有的指令有相同的格式,都按照6个阶段顺序执行
    每一阶段的微操作,按照顺序有3-8种
    每一条指令在各阶段执行时,根据指令类型按顺序执行不同的微操作


    Y86的硬件结构包括哪些?

    硬件单元(组合、时序逻辑)、控制逻辑、信号连接线
    组合逻辑:硬件单元间传播,不用CLK,有延迟
    计算逻辑(ALU、PCINC)
    读逻辑( PC、CC、RF、imem、dmem)
    硬件单元的时序逻辑-状态单元:都在CLK上升沿时更新。控制阻断
    时钟寄存器:PC、CC、STAT的更新
    随机访问存储器:RF写、dmem写


    怎么理解SEQ原则-从不回读?

    所谓CPU顺序实现SEQ:一个时钟变化,引发一个经过组合逻辑的流,从而执行整个指令
    一条新指令的执行,即一个CLK是把上一条指令计算结果-状态单元更新,然后其值进行组合逻辑(新指令的计算)的传播
    要控制CPU中活动的顺序,只需要用CLK控制寄存器和内存
    所有6个阶段的所有步骤的状态更新同时发生(逐级延迟)
    且只在时钟上升开始下一个周期时。
    当前时钟/指令n结束,下一时钟/指令n+1没有到上升沿前,状态单元的数据-状态并不是本指令/时钟n执行的结果,仍然是上一时钟/指令n-1的结果
    指令n的执行结果,到下一指令n+1的上升沿才更新到转状态单元


    HALT、NOP的微操作?新增C0指令Iraddq V,rB的微操作?

    $ $halt
    取指icode:ifun ← \leftarrow M1[PC]读指令字节
    译码
    执行Stat ← \leftarrow HLT
    访存
    写回
    更新PC
    $ $nop
    取指icode:ifun ← \leftarrow M1[PC]读指令字节
    valP ← \leftarrow PC+1计算下一个PC
    译码
    执行
    访存
    写回
    更新PCPC ← \leftarrow valP
    $ $iraddq V,rB
    取指icode:ifun ← \leftarrow M1[PC]读指令字节
    rA:rB ← M 1 \leftarrow M_{1} M1[PC+1]读寄存器字节
    valC ← M 8 \leftarrow M_{8} M8[PC+2]读立即数8个字节
    valP ← \leftarrow PC+10计算下一个PC
    译码valB ← \leftarrow R[rB]读操作数B
    执行valE ← \leftarrow valB+valC
    Set CC
    访存
    写回R[rB] ← \leftarrow valE
    更新PCPC ← \leftarrow valP
    注意:execute中的运算是valB OP valA,而不是valA OP valB
    M1[PC] 表示从PC开始的内存中读取一个字节的数据
    V=====valC,指令中V是加数,是ALUA的一个输入
    这些微操作都有现成的硬件结构,所以不需要改动状态单元,但控制逻辑需要稍微调整

    取指逻辑的need_valC的HCL

    need_valC:这个指令包括一个常数字吗?
    bool need_valC = icode in { IIRMOVQ,IRMMOVQ,IMRMOVQ,IJXX,ICALL };


    硬件单元:
    运算逻辑: ALU、PCINC、
    时钟寄存器:PC、CC、STAT、
    随机访问存储器:RF、IMEM、DMEM
    读操作-看成组合逻辑-有地址就有输出
    随机访问存储器:RF、imem/dmem可以用特殊的时钟电路来模拟
    时钟寄存器:PC、CC、STAT、
    写操作:需要时钟信号控制,控制阻断
    RF、DMEM、PC、CC、STAT
    控制逻辑:每一阶段每一步骤的微操作,在硬件上的映射
    在不同硬件单元之间传送数据
    操作硬件单元,使得对不同指令执行指定的微操作
    组合逻辑:不需要任何时序或控制,只要输入变化了,值就通过逻辑门网络传播
    时序逻辑:需要时钟信号控制,进行更新-把输入锁存到输出

    Q&A 14


    Y86-64的SEQ顺序结构实现的缺点是什么?

    实际使用起来太慢
    信号必须能在一个周期内传播所有的阶段,其中要经过指令内存、寄存器文件、ALU以及数据内存等
    时钟必须非常慢
    硬件单元只在时钟周期的一部分时间内被使用


    QA15

    为什么用连接器?

    • 链接器可以使得分离编译成为可能:我们可以把一个大的应用程序分解成许多小的,更好管理的模块,单独编译每一个模块。
    • 在修改的时候,只用编译被修改的模块,重新链接就行。能节省大量时间。

    连接器工作内容与步骤?

    1. 符号解析
    2. 重定位

    局部变量 链接时按照 本地符号 进行解析

    全局变量 按照 全局符号 进行解析


    链接器对局部变量、参数等符号怎么解析?

    • 对符号的解析:
      • 规则 1:不允许多个同名的强符号
      • 规则 2:若有一个强符号和多个弱符号同名,则选择强符号
      • 规则 3:如果有多个弱符号,选择任意一个

    goto L0 的地址符号的强弱?链接时怎么处理?


    有哪几种目标文件?怎么查看目标文件的各节信息?

    • 目标文件:
      • 可重定位目标文件:包含数据和代码,编译时多个可重定位目标文件生成可执行目标文件。
      • 可执行目标文件:包含数据和代码,可以被直接加载运行。
      • 共享目标文件:特殊的目标文件,可以在加载或运行时被动态的加载进内存并链接。(这个用于动态链接)
    • 查看目标文件的各节信息
      • objdump

    可执行目标文件中的重定位主要完成哪些工作?

    • 将多个单独的代码节(sections)和数据节合并为单个
      节。
    • 将符号从它们在.o文件中的相对位置重新定位到可
      执行文件中的最终绝对内存位置。
    • 用它们的新位置,更新所有对这些符号的引用。

    目标文件中的数据节有哪几种,分别有什么区别?

    • ELF头
    • 段头表/程序头表
    • .text节
    • .rodata节(只读数据)
    • .data节(数据/可读写)
    • .bss节(未初始化全局变量)
    • .symtab节(符号表)
    • .rel.text节(可重定位代码)
    • .debug节(调试信息表)
    • 节头表Section header table(每个节的偏移量、大小)

    可执行目标文件在执行时由操作系统把各节按大小在内存分配,并拷贝相应内容到内存区



    QA16

    按照从低到高地址,Linux下内存分成哪几个区?

    • text段(代码段):存放程序代码,通常只读
    • data段(数据段):存储已经初始化的全局变量和静态变量,可读可写。
    • bss段(数据段):存储未初始化的全局变量和静态变量
    • rodate(数据段):存储数据常量,只读。
    • 栈stack(数据段):存储参数变量和局部变量,从高向低地址生长。
    • 堆heap:被动态分配的内存段,从低向高生长。

    全局变量怎么实现重定位(32位/64位)?

    • 未解答

    子程序怎么实现重定位?

    • 未解答

    Linux下归档器/库管理程序是什么?

    • Idconfig(不确定)

    库管理中每个函数可以生成单独.o也可以多个函数用一个.o,对应的源程序也是这样的。


    静态连接时的目标文件生成与库文件的顺序无关。


    共享库的动态连接有哪几种方法?

    • 加载时链接:当可执行文件首次加载和运行时进行动态链接
    • 运行时链接:在程序开始运行后(通过编程指令)进行动态链接

    库打桩的应用有哪几个方面?

    • 安全
      • 监禁confinement (沙箱sandboxing)
      • 幕后加密
    • 调试
    • 监控和性能分析
      • 计算函数调用的次数
      • 刻画函数的调用位置(sites)和参数
      • Malloc 跟踪
        • 检测内存泄露
        • 生成地址痕迹(traces)

    在什么时候进行库打桩?

    • 打桩可出现在:
      • 编译时:源代码被编译时
      • 链接时间:当可重定位目标文件被静态链接来形成一个可执行目标文件时
      • 加载/运行时:当一个可执行目标文件被加载到内存中, 动态链接,然后执行时


    QA17

    从哪几方面优化程序?在底层与核心技术方面、上层与应用方面、算法与UI方面等优化重点有何区别?

    • 多个层次进行优化:
      • 算法
      • 数据表示
      • 过程
      • 循环
    • 区别:
      • 不知道

    程序的性能优化从哪几方面解决?优化性能为何一定要理解“系统”?

    • 程序是怎样被编译和执行的
    • 现代处理器+存储系统是怎么运作的
    • 怎样测量程序性能,确定“瓶颈”
    • 如何在不破坏代码模块性和通用性的前提下提高性能

    编译器能做的程序员也通常采用的一般有用的优化有哪几种?

    • 代码移动
      • 减少计算执行的频率
    • 复杂运算简化
      • 用更简单的方法替换昂贵的操作
      • 移位、加,替代乘法/除法
      • 识别乘积的顺序
    • 共享公用子表达式
      • 重用表达式的一部分
      • GCC 使用 –O1 选项实现这个优化
    • 去掉不必要的过程调用

    妨碍编译器优化的因素有哪些?为什么编译器不能优化呢?

    • 函数调用
      • 函数可能有副作用
      • 对于给定的参数,函数可能返回不同的值
    • 内存别名使用

    妨碍编译器优化的函数调用怎么进行补救或处理?

    • 使用inline内联函数
      • 用-O1是GCC这样做,但局限于单个文件之内
    • 自己做代码移动

    妨碍编译器优化的内存别名怎么进行补救或处理?

    • 移除内存别名,不需要存储中间结果。

    编译器优化的其他方法与策略?

    • 减少函数调用
    • 用临时/局部变量累计结果
    • 避免每个循环的边界检查
    • 使用指令级并行

    QA17.5 其他编译相关优化技术

    • 寄存器比内存快
    • 用快的指令
    • 用快的寻址方式
    • 位操作比算术运算快
    • 整数比浮点运算块
    • 宏比函数快
    • 简单的结构比复杂结构快
    • 算法很重要
    • C嵌入汇编快
    • 并行比串行快


    QA18

    简述现代超标量CPU的结构与执行程序特点

    • 一个周期执行多条指令。
    • 这些指令是从一个连续的指令流获取的,通常被动态调度的。

    什么容量、延迟界限、发射时间,他们之间有什么关系?

    • 容量:表示能够执行该运算的功能单元的数量

    • 发射时间:表示两个连续的同类型的运算之间需要的最小时钟周期数

    • 延迟界限:简单理解就是延迟,整数加法的延迟界限为1,整数乘法的为3,浮点加法为3,浮点乘法为5

    • 关系:

      • 同一组指令,运算单元容量越大,发射时间越短,延迟界限越短
      • 单元容量一定,延迟界限越大,发射时间越长。

    x = (x OP d[i]) OP d[i+1] 与x = x OP (d[i] OP d[i+1])哪一个并行性好?

    • x = x OP (d[i] OP d[i+1]) 的并行性更好
    • 下一个循环的操作可以早一些开始,对之前的数据没有依赖。

    采用x0 = x0 OP d[i]; x1 = x1 OP d[i+1]; 的分离累加器方法与x = x OP (d[i] OP d[i+1])的在流水线利用方面有何区别?

    • 第一种情况为两条独立的流水,第二种情况的运算全部在同一条流水线中进行。

    什么是SIMD?为什么会提高整数运算的速度?

    • SIMD全称Single Instruction Multiple Data,单指令多数据流,能够复制多个操作数,并把它们打包在大型寄存器的一组指令集。
    • 以同步方式,在同一时间内执行同一条指令。(百度答案我感觉不太对)

    对CPU分支预测怎么处理的?程序员与编译器怎么应对?

    • 对分支预测的处理:
      • 静态预测:
        • 预测分支永远不会发生(或永远会发生),因此总是顺序取下一条指令推测执行。仅当条件跳转指令被求值确实发生了跳转,则非顺序的代码地址被加载执行。
      • 动态预测
    • 避免不可预测的分支


    QA19

    以指令执行为例,说明CPU、内存、外存在数据访问与处理上的区别。

    内存MM除了存储单元外,还有哪些硬件?

    内存中的行缓冲器通常采用SRAM还是DRAM?

    • DRAM(百度没有答案,谷歌上的一本书说是RDRAM,RDRAM是一种SDRAM)

    SRAM与DRAM物理构成有何区别?

    • SRAM存储1位需要4或6个晶体管(双稳态触发器),DRAM需要一个1个电容和1个晶体管(电容的电荷)。

    DRAM芯片访问时地址信号线是怎么复用的?

    • CPU将地址A放到总线上
    • 总存储器从总线上读地址A,取出字x,然后将x放到总线上
    • CPU从总线上读入字x,并将其放入寄存器%rax
    • CPu将地址A放到总线上,主存储器读地址A并等待数据到来

    内存是4G的,此地址空间不包括BIOS的ROM存储器


    内存提高访问速度的方法有哪几种?


    为什么每出现新一代DRAM芯片,容量至少提高4倍?

    • 行地址和列地址分时复用, 每出现新一代存储器芯片,至少要增加一根地址线。每加一根地址线,则行地址和列地址各增加一位,所以行数和列数各增加一倍,因而容量至少提高到4倍。

    操作系统对硬盘访问采用逻辑方式(簇),由谁来实现此块号与物理磁面号/道号/扇区号的访问呢?

    • 由磁盘控制器维护。

    内存与硬盘间传输的块方式采用什么硬件实现?

    • DMA


    QA20

    存储访问局部性原理是什么意思?

    • 程序倾向于使用距离最近用过的指令/数据地址相近或相等的指令/数据。

    Cache访问不命中的种类?

    • 冷不命中(强制不命中):当缓存为空时,对任何数据的请求都会不命中

    • 冲突不命中:当缓存足够大,但是被引用的对象都映射到同一缓存块中,此种不命中称为冲突不命中

    • 容量不命中:当工作集(working set)的大小超过缓存的大小时,会发生容量不命中


    Cache怎么组织,其大小怎么计算?

    • Cache共有S(2* s)组,每个组有E(2*
      e)行,每行有1个有效位,t个标记为,有B(2**b)字节
    • cache大小:C = S * E * B数据字节

    程序怎么利用高速缓存访问存储器?

    • 程序会先到高速缓存中寻找所需数据。如果能够找到,直接从高速缓存中调用,不再访问内存。当cache中对应位置为空时,发生冷不命中,将从下一级存储中寻找数据并将其放入本级存储。当对应位置不为空但标记位不同是,发生冲突不命中,将从下一级存储中寻找数据并替换此处数据。

    空间局部性好的程序也常会发生Cache抖动,导致性能大降,是怎么回事?怎么解决?

    • 可能是因为cache的容量小或每组内的行数较少,关联度低,导致冲突不命中的数量增多。(不确定)

    为什么用中间位不用高位作为组/索引?

    • 如果用高位做索引,连续的存储器块会映射到相同的高速缓存块中,具有良好空间局部性的程序顺序扫描一个数组的元素时,高速缓存总保存着一个块大小的数组原内容,高速缓存使用的效率很低。
    • 用中间为作为组索引,相邻的块总是映射到不同的高速缓存行,提高高速缓存的效率。

    你的计算机CPU各级Cache多大?是哪一种?C、S、E、B各是多少?


    LRU的算法实现?命中行Counter=0,其他行的加1,淘汰计数器最大的-请设计控制逻辑(8路)。

    • 算法实现:

    I7的CPU,L2Cache为8路的2M容量,则其B= S= E=

    • 8路:8路组相连,每组有8行,E = 8
    • I7:每块64字节,B = 64
    • 总容量2M,S= 2**20 / 64 / 8 = 2 ** 11 = 2048

    全相联Cache有冲突不命中吗?为什么

    • 全相连Cache没有冲突不命中,只有容量不命中
    • 下一级存储器中的数据可对应本级cache中的任意一行,当本级cache未装满时,数据将被装入空白的行,而不会选择替换别的行。

    访问高速缓存的地址比内存的地址要短,是吗?

    • 是的
    • 以I7为例,高速缓存地址为6(块索引)+6(组索引)+35(tag)= 47位,内存地址为64位。

    QA21

    Cache的写命中策略有哪些?

    • 直写(write-through):当cache写命中时,处理器对Cache写入的同时,将数据写入到内存中,内存的数据和Cache中的数据都是同步的。(立即写入存储器)
    • 写回(write-back):当CPU对cache写命中时,只修改cache的内容不立即写入主存,只当此行被换出时才写回主存。(需要一个修改位(脏位),与内存不同时为1,表示被修改)

    Cache的写不命中策略有哪些?

    • 写分配(Write Allocate):将主存装入 Cache 中,然后更新相应单元。(好处是更多的写遵循局部性)
    • 非写分配(Not Write Allocate):直接写到主存中,不加载到缓存中。

    为什么高速缓存用不命中率而不是命中率来衡量性能?

    • 命中率相差较少时,实际性能相差很多。
    • 命中率为97%和99%的性能可相差两倍。

    怎么编写高速缓存友好的代码?

    1. 加快经常性事件:专注在核心函数和内循环上

    2. 使用内层循环的缓存不命中数量降到最低

      • 反复引用变量更好(时间局部性)
      • 步长为1的数据引用模式更好(空间局部性)
    • 关键思想:程序的局部性的这个定性概念通过对缓冲存储器的理解而量化了

    重新排列以提升程序的_____局部性

    • 重新排列以提升程序的 空间 局部性

    使用分块以提升程序的_____局部性

    • 使用块来提高 时间 局部性

    使用分块提高缓冲器命中率的基本思想与算法

    • 充分利用数据的时间局部性。

    新的计算机cache技术:指令Cache->微指令Cache、追踪缓存TraceCache等, TLB等

    • 指令Cache->微指令Cache
    • 追踪缓存TraceCache:
      • 一般一级缓存中的指令缓存都是即时解码:而追踪缓存无须每次都进行解码指令,直接做解码,这些指令称为微指令(micro-ops),12K容量能存储12000个微指令。相比可以有效地增加在高速时脉下对指令的解码能力。
    • TLB:
      • 转译后备缓冲器,也被翻译为页表缓存、转址旁路缓存,为CPU的一种缓存,由存储器管理单元用于改进虚拟地址到物理地址的转译速度。当前所有的桌面型及服务器型处理器(如 x86)皆使用TLB。TLB具有固定数目的空间槽,用于存放将虚拟地址映射至物理地址的标签页表条目。

    Q&A 22


    计算机系统的各层发生哪些异常控制?

    在硬件层,硬件检测到事件会触发控制突然转移到异常处理程序。

    在操作系统层,内核通过上下文切换,将控制从一个用户进程转移到另一个用户进程。

    在应用层,一个进程可以发送信号到另一个进程,而接收者会将控制信号突然转移到他的一个信号处理程序。

    一个程序可以通过回避通常的栈规则,并执行其他程序中任意位置的非本地跳转来对错误作出反应。


    异常处理后有哪三种返回机制?

    1)处理程序将控制返回给事件发生时正在执行的当前指令

    2)处理程序将控制返回给没有发生异常将会执行的下一条指令

    3)处理程序终止被中断的程序


    简述异常、异常号、异常处理程序、异常表的关系?

    异常在跳转到“异常处理程序”时,是通过一张“异常表”来跳转的,每一种异常都有一个异常号(非负整数),异常表会把不同的异常号关联到不同的异常处理程序,异常表在系统启动时由操作系统初始化。

    ps:

    异常是异常控制流的一种形式,是控制流中的突变,用来响应处理器状态中的某些变化,由硬件和操作系统实现

    异常表:当处理器检测到有事件发生时,它会通过跳转表,进行一个间接过程调用(异常),到异常处理程序。系统启动时操作系统分配和初始化一张异常表。

    异常号:系统中可能的某种类型的异常都分配了一个唯一的非负整数的异常号。异常号是到异常表中的索引,异常表的起始地址放在一个叫做异常表基址寄存器的特殊CPU寄存器里。*


    一些其他知识点:

    在C中通过scanf等IO函数处理IO异常。

    Linux系统调用中的功能号n就是异常号n

    系统调用是一个特殊的异常处理子程序

    异步异常需要/只需要CPU执行异常处理子程序,只需要操作系统处理即可。

    浮点除以0产生异常,导致程序终止Abort退出。

    浮点数除以0,不产生异常,程序正常执行。
    任何数除以0产生异常,导致程序终止Abort退出。
    每个进程的地址空间是整个内存空间的一部分

    QA23

    fork函数调用一次返回两次,怎么可能呀?

    当程序执行到下面的语句: pid=fork(); 的时候会复制父进程的堆栈段,所以两个进程都停留在fork函数中,等待返回。因此fork函数会返回两次,一次是在父进程中返回,另一次是在子进程中返回,这两次的返回值是不一样的。通过fork返回的值来判断当前进程是子进程还是父进程

    1)在父进程中,fork返回新创建子进程的进程ID;
    2)在子进程中,fork返回0;
    3)如果出现错误,fork返回一个负值。


    进程状态有哪几种?进程终止有几种方法?

    linux上进程有5种状态:
    1. 运行(正在运行或在运行队列中等待)

    2. 中断(休眠中, 受阻, 在等待某个条件的形成或接受到信号)

    3. 不可中断(收到信号不唤醒和不可运行, 进程必须等待直到有中断发生)

    4. 僵死(进程已终止, 但进程描述符存在, 直到父进程调用wait4()系统调用后释放)

    5. 停止(进程收到SIGSTOP, SIGSTP, SIGTIN, SIGTOU信号后停止运行运行)

      进程终止有8种

      正常终止五种:

      1.从main返回。
      2.调用exit。
      3.调用_exit或_Exit。
      4.最后一个线程从其启动例程返回。
      5.最后一个线程调用pthread_exit。
      三种异常终止:
      6.调用abort()。
      7.接到一个信号并终止。
      8.最后一个线程对取消请求作出响应。


    kill—杀死是终止进程吗?kill某进程是怎么实现的?

    kill 从字面来看,就是用来杀死进程的命令,但事实上,这个或多或少带有一定的误导性。从本质上讲,kill 命令只是用来向进程发送一个信号,至于这个信号是什么,是用户指定的。

    也就是说,kill 命令的执行原理是这样的,kill 命令会向操作系统内核发送一个信号(多是终止信号)和目标进程的 PID,然后系统内核根据收到的信号类型,对指定进程进行相应的操作。

    kill 命令的基本格式如下:

     [root@localhost ~]# kill [信号] PID 
    
    • 1

    kill 命令是按照 PID 来确定进程的,所以 kill 命令只能识别 PID,而不能识别进程名

    kill 命令是按照 PID 来确定进程的,所以 kill 命令只能识别 PID,而不能识别进程名。Linux 定义了几十种不同类型的信号,读者可以使用 kill -l 命令查看所有信号及其编号,这里仅列出几个常用的信号,如下表所示。

    信号编号信号名含义
    0EXIT程序退出时收到该信息。
    1HUP挂掉电话线或终端连接的挂起信号,这个信号也会造成某些进程在没有终止的情况下重新初始化。
    2INT表示结束进程,但并不是强制性的,常用的 “Ctrl+C” 组合键发出就是一个 kill -2 的信号。
    3QUIT退出。
    9KILL杀死进程,即强制结束进程。
    11SEGV段错误。
    15TERM正常结束进程,是 kill 命令的默认信号。

    需要注意的是,表中省略了各个信号名称的前缀 SIG,也就是说,SIGTERM 和 TERM 这两种写法都对,kill 命令都可以理解。


    (*)Kill是终止进程并回收进程吗?

    kill命令可以加许多参数(如上面的表格),其中-2 -9 -15和不添加参数的kill是不一样的

    kill不带参数 即是普通的杀死进程,回收资源,-

    2参数是低级别,可以被某些程序忽略,造成无法杀死进程。

    -9参数是强制行为,不回收资源,可能造成资源浪费。例如父进程无法被回收

    -15就是强制杀死进程,回收资源。

    fork的子进程与其父进程有什么相同与区别?

    fork之后,操作系统会复制一个与父进程完全相同的子进程,虽说是父子关系,但是在操作系统看来,他们更像兄弟关系,这2个进程共享代码空间,但是数据空间是互相独立的,子进程数据空间中的内容是父进程的完整拷贝,指令指针也完全相同,子进程拥有父进程当前运行到的位置(两进程的程序计数器pc值相同,也就是说,子进程是从fork返回处开始执行的),但有一点不同,如果fork成功,子进程中fork的返回值是0,父进程中fork的返回值是子进程的进程号,如果fork不成功,父进程会返回错误。

    fork出来的子进程,基本上除了进程号之外父进程的所有东西都有一份拷贝,基本就意味着不是全部,下面我们要说的是子进程从父进程那里继承了什么东西,什么东西没有继承。还有一点需要注意,子进程得到的只是父进程的拷贝,而不是父进程资源的本身。

    由子进程自父进程继承到:
    1.进程的资格(真实(real)/有效(effective)/已保存(saved)用户号(UIDs)和组号(GIDs))
    2.环境(environment)
    3.堆栈
    4.内存
    5.打开文件的描述符(注意对应的文件的位置由父子进程共享,这会引起含糊情况)
    6.执行时关闭(close-on-exec) 标志 (译者注:close-on-exec标志可通过fnctl()对文件描述符设置,POSIX.1要求所有目录流都必须在exec函数调用时关闭。更详细说明,参见《UNIX环境高级编程》 W. R. Stevens, 1993, 尤晋元等译(以下简称《高级编程》), 3.13节和8.9节)
    7.信号(signal)控制设定
    8.nice值 (译者注:nice值由nice函数设定,该值表示进程的优先级,数值越小,优先级越高)
    进程调度类别(scheduler class)(译者注:进程调度类别指进程在系统中被调度时所属的类别,不同类别有不同优先级,根据进程调度类别和nice值,进程调度程序可计算出每个进程的全局优先级(Global process prority),优先级高的进程优先执行)
    8.进程组号
    9.对话期ID(Session ID) (译者注:译文取自《高级编程》,指:进程所属的对话期(session)ID, 一个对话期包括一个或多个进程组, 更详细说明参见《高级编程》9.5节)
    10.当前工作目录
    11.根目录 (译者注:根目录不一定是“/”,它可由chroot函数改变)
    12.文件方式创建屏蔽字(file mode creation mask (umask))(译者注:译文取自《高级编程》,指:创建新文件的缺省屏蔽字)
    13.资源限制
    14.控制终端

    子进程所独有:
    进程号
    1.不同的父进程号(译者注:即子进程的父进程号与父进程的父进程号不同, 父进程号可由getppid函数得到)
    2.自己的文件描述符和目录流的拷贝(译者注:目录流由opendir函数创建,因其为顺序读取,顾称“目录流”)
    3.子进程不继承父进程的进程,正文(text), 数据和其它锁定内存(memory locks)(译者注:锁定内存指被锁定的虚拟内存页,锁定后,4.不允许内核将其在必要时换出(page out),详细说明参见《The GNU C Library Reference Manual》 2.2版, 1999, 3.4.2节)
    5.在tms结构中的系统时间(译者注:tms结构可由times函数获得,它保存四个数据用于记录进程使用中央处理器 (CPU:Central Processing Unit)的时间,包括:用户时间,系统时间, 用户各子进程合计时间,系统各子进程合计时间)
    6.资源使用(resource utilizations)设定为0
    8.阻塞信号集初始化为空集(译者注:原文此处不明确,译文根据fork函数手册页稍做修改)
    9.不继承由timer_create函数创建的计时器
    10.不继承异步输入和输出

    fork的子进程与其父进程同名的全局变量对应同一物理地址?

    • fork时子进程获得父进程数据空间、堆和栈的复制,所以变量的地址(当然是虚拟地址)也是一样的。
    • 每个进程都有自己的虚拟地址空间,不同进程的相同的虚拟地址显然可以对应不同的物理地址。因此地址相同(虚拟地址)而值不同没什么奇怪。

    fork子进程完全复制父进程的栈空间,也复制了页表,但没有复制物理页面,所以这时虚拟地址相同,物理地址也相同,但是会把父子共享的页面标记为“只读”,如果父子进程一直对这个页面是同一个页面,知道其中任何一个进程要对共享的页面“写操作”,这时内核会复制一个物理页面给这个进程使用,同时修改页表。而把原来的只读页面标记为“可写”,留给另外一个进程使用。

    假定父进程malloc的指针指向0x12345678, fork 后,子进程中的指针也是指向0x12345678,但是这两个地址都是虚拟内存地址 (virtual memory),经过内存地址转换后所对应的 物理地址是不一样的。所以两个进城中的这两个地址相互之间没有任何关系。

    就是说:

    子进程继承父进程的全局变量。

    子进程创建以后,可以读取原来父进程的全局变量的值。

    但是创建以后,子进程修改了变量,或者是父进程修改了变量值,互相都不影响了。

    无论是什么类型的变量,fork后父子进程中都是一样的,但两者之间没有关系,任何一个进程修改变量后,在另一个进程中都不能知道,更不能访问另一个进程中的变量,也不会对应相同的物理地址了

    但实际上,linux为了提高 fork 的效率,采用了 copy-on-write 技术,fork后,这两个虚拟地址实际上指向相同的物理地址(内存页),只有任何一个进程试图修改这个虚拟地址里的内容前,两个虚拟地址才会指向不同的物理地址(新的物理地址的内容从原物理地址中复制得到)


    (*)fork生成副本太浪费空间了,能不能先与父进程共享物理内存?特别是代码段!而且fork后还得execve覆盖掉。

    早期的UNIX的fork()实现时,就是原汁原味的复制,和它的表像一样,但是很明显,这种方法效率太低,而且造成了很大的浪费,现在大部分的UNIX实现采用了如下俩种方法来规避这种浪费

    (1)首先我们可以确定父子进程的代码段是相同的,所以代码段是没必要复制的,因此内核将代码段标记为只读,这样父子进程就可以安全的共享此代码段了。fork之后在进程创建代码段时,新子进程的进程级页表项都指向和父进程相同的物理页帧

    (2)而对于父进程的数据段,堆段,栈段中的各页,由于父子进程要相互独立,所以我们采用写实复制的技术,来最大化的提高内存以及内核的利用率。刚开始,内核做了一些设置,令这些段的页表项指向父进程相同的物理内存页。调用fork之后,内核会捕获所有父进程或子进程针对这些页面的修改企图(说明此时还没修改)并为将要修改的页面创建拷贝。系统将新的页面拷贝分配给被内核捕获的进程,还会对子进程的相应页表项做适当的调整,现在父子进程就可以分别修改各自的上述段,不再互相影响了

    fork()后产生的子进程与父进程行为是相互独立的,变量也需要是独立的,所以说物理内存中需要有两份空间,不可以共享全部内存。

    (*)fork7去掉while(1),执行后再ps,父与子进程能看到哪一个?

    (*)fork8父进程exit为什么ps看不出父进程defunct?

    一些知识点:

    并发进程是并行执行的,控制流物理上是不相交的。

    任务管理器中的进程内是串行、进程间是并行

    QA24

    简述hello从命令行到运行的过程

    1.fork

    2.execve

    3.load

    4.dl_init setjmp等到 _start-(所有C程序的入口点,在ctrl.o中)

    5.libc_start_main(系统启动函数,在libc.so,初始化执行环境 调用main执行,处理main返回值,控制返回给内核)

    6.main

    –>exit(0) 或 return 0 终止

    —>bash回收


    信号与异常的区别与联系

    1.异常

    定义:异常就是控制流中的突变,用来响应处理器状态的某些变化

    2.信号

    定义:信号是一条小消息,通知进程系统中发生了一个某种类型的时间

    区别

    信号是软件上的概念,指的是由内核 产生,并由用户态程序响应的事件。

    异常是硬件上的概念,指的是 CPU 因执行某些指令而产生的事件,比如常见的除零错误。另外,相对于中断,异常是同步的,中断是异步的;异常是由执行指令产生的,中断则是由硬件产生的。

    信号是异步的进程间通讯机制,是在软件层次上对中断机制的一种模拟

    联系

    1.硬件异常也能产生信号,例如被零除、无效内存引用等。这些条件通常先由内核硬件检测到,然后通知内核。内核将决定产生什么样的信号。


    信号的处理方法有哪几种?

    一般有三种

    1.忽略此信号

    2.执行该信号的默认处理动作
    每个信号类型都有一个预定义的默认行为
    是下面中的一种:

    1. 进程终止
    2. 进程终止并转储内存
    3. 进程停止(挂起)直到被SIGCONT信号重启
    4. 进程忽略该信号

    3.提供一个信号处理函数,要求内核处理该信号时切换到用户执行这个处理函数,这种方式称为捕捉一个信号.

    ps:信号机制

    (1)发送信号。发送信号的程序用系统调用kill( )实现;
    (2)预置对信号的处理方式。接收信号的程序用signal( )来实现对处理方式的预置;
    (3)收受信号的进程按事先的规定完成对相应事件的处理。

    ps2:信号的产生条件

    1. 用户在终端 按下某些键时,终端驱动程序会发送信号给前台进程,例如Ctrl-C产生SIGINT信 号,Ctrl-/产生SIGQUIT信号,Ctrl-Z产生SIGTSTP信号。

    2. 硬件异常产生信号,这些条件由硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了 除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE信号发送给进 程。再比如当前进程访问了非法内存地址,,MMU会产生异常,内核将这个异常解释为SIGSEGV信 号发送给进程。

    3. 一个进程调用kill(2)函数可以发送信 号给另一个进程。

    4. 可以用kill(1)命令发送信号给某个 进程,kill(1)命令也是调用kill(2)函 数实现的,如果不明确指定信号则发送SIGTERM信号,该信号的默认处理动作是终止进程。

    5. 当 内核检测到某种软件条件发生时也可以通过信号通知进程,例如闹钟超时产生SIGALRM信 号,向读端已关闭的管道写数据时产生SIGPIPE信号。

    信号的捕获过程

    img


    批处理、作业、任务、程序、进程?

    **批处理(**Batch),也称为批处理脚本顾名思义,批处理就是对某对象进行批量的处理,通常被认为是一种简化的脚本语言,它应用于DOSWindows系统中。

    **作业(job)**是计算机操作者(或是一个叫做作业调度器的程序)交给操作系统的执行单位

    任务:在多道程序或多进程环境中,要由计算机来完成的基本工作元,它是由控制程序处理的一 个或多个指令序列。

    程序是一组计算机能识别和执行的指令,

    **进程:**是程序的一次执行,进程是可以和别的计算并行执行进程是程序在一个数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单位

    程序与进程之间的区别:

    (1)进程更能真实地描述并发,而程序不能。
    (2)进程由程序和数据两部分组成,进程是竞争计算机系统有限资源的基本单位,也是进程处理机调度的基本单位。
    (3)程序是静态的概念;进程是程序在处理机上一次执行的过程,是动态的概念。
    (4)进程有生存周期,有诞生有消亡。是短暂的;而程序是相对长久的。
    (5)一个程序可以作为多个进程的运行程序;一个进程也可以运行多个程序。
    (6)进程具有创建其他进程的功能;而程序没有。

    作业与进程的区别:

    ​ 一个进程是一个程序对某个数据集的执行过程,是分配资源的基本单位。作业是用户需要计算机完成的某项任务,是要求计算机所做工作的集合。一个作业的完成要经过作业提交、作业收容、作业执行和作业完成4个阶段。而进程是对已提交完毕的程序所执行过程的描述,是资源分配的基本单位。其主要区别如下。
    (1)作业是用户向计算机提交任务的任务实体。在用户向计算机提交作业后,系统将它放入外存中的作业等待队列中等待执行。而进程则是完成用户任务的执行实体,是向系统申请分配资源的基本单位。任一进程,只要它被创建,总有相应的部分存在于内存中。
    (2)一个作业可由多个进程组成,且必须至少由一个进程组成,反过来则不成立。
    (3)作业的概念主要用在批处理系统中,像UNIX这样的分时系统中就没有作业的概念。而进程的概念则用在几乎所有的多道程序系统中。

    作业、进程和程序之间的联系:

    ​ 一个作业通常包括程序、数据和操作说明书3部分。每一个进程由PCB、程序和数据集合组成。这说明程序是进程的一部分,是进程的实体。因此,一个作业可划分为若干个进程来完成,而每一个进程有其实体————程序和数据集合。


    异步异常/中断与信号的处理有何不同?

    异常处理

    1.当发生异常时,CPU控制单元产生一个硬件出错码。

    2.CPU根据该中断吗找到中断向量表内的对应向量,根据该向量转到中断处理程序。

    3.中断处理程序处理完之后向当前进程发送一个SIG***信号。

    1. 若进程定义了相应的信号处理程序则转移到相应的程序执行,若没有,则执行内核定义的操作。

    中断处理

    1.设备产生中断

    2.PIC(可编程中断控制器)会产生一个对应的中断向量

    3.和中断向量表中的每一个中断向量进行比较,转到对应的中断处理程序

    4.中断处理程序进行保存现场,做相关处理,恢复现场

    5.内核调度,返回用户进程

    信号与中断的相似点

    (1)采用了相同的异步通信方式;
    (2)当检测出有信号或中断请求时,都暂停正在执行的程序而转去执行相应的处理程序;
    (3)都在处理完毕后返回到原来的断点;
    (4)对信号或中断都可进行屏蔽。

    信号与中断的区别

    (1)中断有优先级,而信号没有优先级,所有的信号都是平等的;
    (2)信号处理程序是在用户态下运行的,而中断处理程序是在核心态下运行;
    (3)中断响应是及时的,而信号响应通常都有较大的时间延迟。


    有的中断/异步异常会产生信号?

    硬件异常也能产生信号,例如被零除、无效内存引用等。这些条件通常先由内核硬件检测到,然后通知内核。内核将决定产生什么样的信号。
    下图为异常和函数产生信号的方式以及进程的处理过程


    信号处理程序与中断处理程序可以各自嵌套与互相嵌套?同一信号/中断同时多次 发生呢?缺省信号可嵌套中断不嵌套

    外部中断不支持嵌套处理,内部异常支持嵌套处理,原因是:内部异常程序也是有可能发生越界、越权、缺页的


    一些知识点:

    一般的故障类异常-内中断的处理是通过向发生异常的进程发送信号的机制实现异 常处理,可尽快完成在内核态的异常处理过程,因为异常处理过程越长,嵌套执 行异常的可能性越大,而异常嵌套执行会付出较大的代价。但缺页故障不是

    Linux用两个32/64位数表示信号与其阻塞 kill函数是终止进程并回收进程

    Linux处理“除法错”异常#DE(类型0)发送SIGFPE信号 Linux用信号处理同步异常,用中断处理异步异常(pushf)

    (*) 延时过程调用与中断服务例程

    QA 25

    计算机的地址空间有哪几种?怎么进行变换?

    计算机的地址空间分为物理空间和虚拟空间

    物理地址 (physical address): 放在寻址总线上的地址。放在寻址总线上,如果是读,电路根据这个地址每位的值就将相应地址的物理内存中的数据放到数据总线中传输。如果是写,电路根据这个地址每位的值就将相应地址的物理内存中放入数据总线上的内容。物理内存是以字节(8位)为单位编址的。
    虚拟地址 (virtual address): CPU启动保护模式后,程序运行在虚拟地址空间中。注意,并不是所有的“程序”都是运行在虚拟地址中。CPU在启动的时候是运行在实模式的,内核在初始化页表之前并不使用虚拟地址,而是直接使用物理地址的。

    通过MMU进行转换

    大致过程:
    1)根据VA和C2找到一级页表条目

    2)若是段描述符,返回物理地址

    3)若该条目是二级页表描述符,继续根据虚拟地址找下一个条目

    4)找到第二个页描述符,返回物理地址

    MMU怎么将[EBP+ESI*8+4]变换成虚拟地址的?

    取出%EBP,%ESI寄存器中的内容,再通过运算获取虚拟地址

    ##Linux是怎么处理Intel的分段管理机制的?
    为什么要分段管理?

    CPU内部的数据总线和段寄存器都是16位的,用他们做地址寄存器智能寻址64KB单元,但是8086的实际物理地址有1MB,如何解决这个问题呢?采用分段技术。

    什么是分段技术?

    将1MB的内存空间分成若干段,每一段的第一个地址称为段首址,并且用段首址来表示这一段的内存地址。段首址必须要能被16整除,为什么呢?这样的话,段首址就可以用16位来保存,低4位全部为零,高16位可放在段寄存器中。

    Linux如何进行分段?

    Linux的段式管理,事实上只是“哄骗”了一下硬件而已。从2.2版开始,Linux让所有的进程(或叫任务)都使用相同的逻辑地址空间,因此就没有必要使用局部描述符表LDT。但内核中也用到LDT,那只是在VM86模式中运行Wine,因为就是说在Linux上模拟运行Winodws软件或DOS软件的程序时才使用。

    Intel微处理器的段机制是从8086开始提出的, 那时引入的段机制解决了从CPU内部16位地址到20位实地址的转换。为了保持这种兼容性,386仍然使用段机制,但比以前复杂得多。因此,Linux内核的设计并没有全部采用Intel所提供的段方案,仅仅有限度地使用了一下分段机制。这不仅简化了Linux内核的设计,而且为把Linux移植到其他平台创造了条件,因为很多RISC处理器并不支持段机制。但是,对段机制相关知识的了解是进入Linux内核的必经之路。

    在 IA32 上任意给出的地址都是一个虚拟地址,即任意一个地址都是通过“选择符:偏移量”的方式给出的,这是段机制存访问模式的基本特点。所以在IA32上设计操作系统时无法回避使用段机制。一个虚拟地址最终会通过“段基地址+偏移量”的方式转化为一个线性地址。 但是,由于绝大多数硬件平台都不支持段机制,只支持分页机制,所以为了让 Linux 具有更好的可移植性,我们需要去掉段机制而只使用分页机制。但不幸的是,IA32规定段机制是不可禁止的,因此不可能绕过它直接给出线性地址空间的地址。万般无奈之下,Linux的设计人员干脆让段的基地址为0,而段的界限为4GB,这时任意给出一个偏移量,则等式为“0+偏移量=线性地址”,也就是说“偏移量=线性地址”。另外由于段机制规定“偏移量<4GB”,所以偏移量的范围为0H~FFFFFFFFH,这恰好是线性地址空间范围,也就是说虚拟地址直接映射到了线性地址,我们以后所提到的虚拟地址和线性地址指的也就是同一地址。看来,Linux在没有回避段机制的情况下巧妙地把段机制给绕过去了。

    IA32的内存寻址机制完成从逻辑地址–线性地址–物理地址的转换。其中,逻辑地址的段寄存器中的值提供段描述符,然后从段描述符中得到段基址和段界限,然后加上逻辑地址的偏移量,就得到了线性地址,线性地址通过分页机制得到物理地址。
    首先,我们要明确,分段机制是IA32提供的寻址方式,这是硬件层面的。就是说,不管你是windows还是linux,只要使用IA32的CPU访问内存,都要经过MMU的转换流程才能得到物理地址,也就是说必须经过逻辑地址–线性地址–物理地址的转换。

    ##虚拟页面有哪几种状态?
    ●未分配的:VM系统还未分配(或者创建)的页。未分配的块没有任何数据和它们相关联,因此也就不占用任何磁盘空间。

    ●缓存的:当前已缓存在物理内存中的已分配页。

    ●未缓存的:未缓存在物理内存中的已分配页。

    ##MMU怎么分配一个新的页面(VM/PM/PTE)?
    当操作系统分配一个新的虚拟内存页时,调用malloc的结果。分配过程即为在磁盘上创建空间并更新PTE中对应的页表,使它指向磁盘上这个新创建的页面

    ##多个进程的PTE项目可能存储同一内容?

    多个虚拟地址可能映射同一个物理地址,所以可能储存同一内容

    ##VM是怎么支持链接与进程创建、加载运行的?

    虚拟内存对链接的影响

    VM能够简化链接。独立的地址空间允许每个进程的内存映像使用相同的基本格式,而不管代码和数据实际存放在物理内存的何处。一个给定的Linux 系统上的每个进程都使用类似的内存格式。对于64位地址空间,代码段总是从虚拟地址0x400000开始。数据段跟在代码段之后,中间有一段符合要求的对齐空白。栈占据用户进程地址空间最高的部分,并向下生长。这样的一致性极大地简化了链接器的设计和实现,允许链接器生成完全链接的可执行文件,这些可执行文件是独立于物理内存中代码和数据的最终位置的。

    虚拟内存对fork()函数

    既然我们理解了虛拟内存和内存映射,那么我们可以清晰地知道fork函数是如何创建一个带有自己独立虚拟地址空间的新进程的。

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

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

    虚拟内存对exeve()函数:

    虚拟内存和内存映射在将程序加载到内存的过程中也扮演着关键的角色。既然已经理解了这些概念,我们就能够理解execve函数实际上是如何加载和执行程序的。假设运行在当前进程中的程序执行了如下的execve调用:

    execve("a. out", NULL, NULL);
    
    • 1

    正如在第8章中学到的,execve 函数在当前进程中加载并运行包含在可执行目标文件a.out
    中的程序,用a.out程序有效地替代了当前程序。加载并运行a.out需要以下几个步骤:

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

    映射私有区域。为新程序的代码、数据、bss和栈区域创建新的区域结构。所有这些
    新的区域都是私有的、写时复制的。代码和数据区域被映射为a.out文件中的. text和.data区。bss 区域是请求二进制零的,映射到匿名文件,其大小包含在a.out中。栈和堆区域也是请求二进制零的,初始长度为零。图9-31概括了私有区域的不同映射。

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

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

    TLB的Block是什么内容?

    PPN(物理页号)

    32/64位系统为多少字节?

    这里不介绍32/64位系统各种变量到底占多少个字节

    32位系统内存上限: 2^32=4G Bytes
    64位系统内存上限同理可以计算为128GBytes

    TLB怎么实现VA中VP的访问(VPN/PPN/TLBI/TLBT/VPO)

    如图所示

    多级页表中一级页表与末级页表的表项分别是什么,每张表是不是都必须是1K个元素(32位)?

    一级页表指向二级页表
    末级页表中表项即为PPN

    不一定

    执行MOV EAX,[EBP+ESI*8+4]最多访存几次(TLB2级Cache3级)?

    七次

    一些知识点

    Intel没有只采用物理寻址的计算机系统。

    #QA26

    关于Intel core i7处理器

    I7
    CPU的VA:48

    PA:52位,

    VPO:12

    PPO:12

    VPN:36

    PPN:40

    采用4级页表

    其页表项数依次为

    L1 PT:每个条目512GB区域

    L2 PT:每个条目1GB区域

    L3 PT:每个条目2MB区域

    L4 PT:每个条目512GB区域

    页表空间为5*512字节

    每一页表项占5字节(40位)。

    TLB采用Cache类型16组,TLB-D1为4路64条,

    则其TLBI4 位,TLBT32位。

    CacheD1为8路32K,则其64组,Block为64字节,tag为40位。

    PA中CT/CI/CO依次为40、6、6位。

    页表物理基地址为40位,5byte

    Linux的VM机制对私有写时复制对象是怎么处理的?

    由于内存映射机制,所以一个磁盘文件对象可以被多个进程共享访问,也可以被多个进程对象私有访问。如果是共享访问,那么一个进程对这个对象的修改会显示到其他进程。如果是私有访问,内核会采用写时拷贝copy on write的方式,如果一个进程要修改一个私有的写时拷贝的对象,会产生一个保护故障,内核会拷贝这个私有对象,写进程会在新的私有对象上修改,其他进程仍指向原来的私有对象。

    execve时当前进程哪些区域是请求二进制零的匿名文件映射?

    execve函数在当前的进程中加载并运行包含在可执行目标文件a.out中的程序,用a.out程序有效的替代了当前程序。加载并运行a.out需要以下几个步骤:

    删除已存在的用户区域:删除当前进程虚拟地址的用户部分中的已存在的区域结构。
    映射私有区域:为新程序的文本,数据,bss,和栈区创建新的区域结构。所有这些新的区域都是私有的,写时拷贝的。文本和数据区域被映射为a.out文件中的文本和数据区。bss区域是请求二进制零的,映射到匿名文件,其大小包含在a.out中。栈和堆区域也是请求二进制零的,初始长度为0.

    映射共享区域:如果a.out程序和共享对象(或目标)链接,比如标准C库libc.so,那么这些对象都是动态链接到这个程序的,然后在映射到用户虚拟地址空间中的共享区域内。
    设置程序计数器(PC):exevce做的最后一件事情就是设置当前进程上下文中的程序计数器,使之指向文本区域的入口点。

    QA28

    垃圾回收时堆使用的内存有向图的根节点有哪几种类型?

    根节点对应于这样一种不在堆中的位置,它们中包含指向堆中的指针。这些位置可以是寄存器、栈里的变量,或者是虚拟内存中读写数据区域内的全局变量。

    按照有向图与标记清除方法,所有的垃圾都可以回收?举例

    非也

    C程序的Mark&Sweep收集器必须是保守的,其根本原因是C语言不会用类型信息来标记内存位置。因此,像int或者float 这样的标量可以伪装成指针。例如,假设某个可达的已分配块在它的有效载荷中包含一个int,其值碰巧对应于某个其他已分配块b的有效载荷中的一个地址。对收集器而言,是没有办法推断出这个数据实际上是int而不是指针。因此,分配器必须保守地将块b标记为可达,尽管事实上它可能是不可达的。

    除了CPU与RAM,计算机还有啥呀?

    主板、电源、硬盘(ROM)、光驱、显卡(GPU)、网卡(网络设备)、声卡、调制解调器、软驱(老式电脑)、散热器、显示器、键盘、鼠标(输入输出设备)

    CPU、存储器、所有的接口等是否都可以集成到一个芯片里?

    单片机可以把微处理器、存储器和I/O接口电路等制作在一块集成电路芯片上

    删去了有关CS:APP第十章的内容

  • 相关阅读:
    github用法详解
    aliyun服务器centos8安装docker
    Transformer Fusion for Indoor RGB-D Semantic Segmentation非官方自己实现的代码
    杰理之关于 TWS 声道配置【篇】
    前端设计模式应用
    el-upload上传附件限制大小
    iapp源码集锦
    带你走进JS新特性(一)
    R语言随机波动模型SV:马尔可夫蒙特卡罗法MCMC、正则化广义矩估计和准最大似然估计上证指数收益时间序列...
    Bootstrap的一些主要作用
  • 原文地址:https://blog.csdn.net/zhousiyuan0515/article/details/126834114