机器语言是二进制数据表示的语言,机器可以直接识别;汇编语言是与机器指令一一对应的助记符,是一种低级语言,经过汇编和链接之后机器可以运行;C语言在低级语言的基础上,采用最接近人类自然语言的单词和符号来表示一组低级语言程序。
C语言的优点:提供了结构化程序设计的环境和工具,设计出来的程序可读性好,可维护性强,可靠性高;抽象程度高,远离机器语言,和计算机硬件关系不大,编程门槛低,无需懂计算机原理和计算机结构;编写出来的程序可移植性好,重用率高
缺点:相对汇编语言和机器语言来说执行效率不高
**汇编语言的优点:**能发挥计算机的特定和功能,程序紧凑,资源利用率高
缺点:代码可读性较差,难以维护;特定的汇编语言和特定的机器语言指令集一一对应,只能针对特定体系结构和处理器进行优化,可移植性差;开发效率低,时间长且单调;编程门槛高,需要了解计算机原理和计算机结构
**机器语言优点:**能利用机器指令精准的描述算法,编程质量高;所占用储存空间小;执行速度快
缺点:开发过程细节繁琐,编程门槛高;可读性差,不便于交流;严重依赖具体的计算机,可移植性差,重用性差。
##2.Hello.c 经过那些工具、步骤、生成什么类型的文件? ##

##3. 什么是程序可移植性?汇编语言可移植吗?为什么?
程序可移植性指在不同环境下,其是够具备可以被重复使用的性质。在移植到不同的系统平台使,所做的改动少,则可移植性好;否则,可移植性差。
汇编语言可移植性差。
原因:汇编语言是机器语言的主机语言,针对特定的指令系统,和特定的机器指令集一一对应;程序结构性差,不便于模块化的设计,也造成可移植性差
编译程序是将高级语言程序转换为机器级目标程序,执行时只需要启动目标程序即可,如将C、C++编译为Windows上的可执行2进制文件。
提高程序运行效率(性能)
使程序更加节省存储空间和运行空间
程序更加正确
更可靠
可移植性更好
功能更强大
使用更方便
格式符合编程规范,接口规范
更易懂(能读明白,有注释,模块化)
更美
计算机软件与硬件的界面/接口是什么?
操作系统内核是应用程序和硬件之间的媒介。它提供三个基本的抽象:
文件是对I/O设备的抽象
虚拟内存是对主存和磁盘的抽象
进程是处理器、主存和I/O设备的抽象
因为很多程序只能在一种操作系统上运行。在其他操作系统上不能运行。那怎么能够支持多重硬件平台、多种软件平台(包括操作系统)呢?
这就要用到一种新的技术——中间层语言,微软有自己的中间层语言,Java也有(叫Java字节码),凡是微软的语言比如C、C++,在运行时都要先编译成MSIL(中间层语言),然后通过微软的通用语言运行时(Common Language Runtime)来运行,而Java语言在编译成Java字节码之后要在Java虚拟机上运行,Java虚拟机通过通用语言运行时对生成的中间层代码进行解释。这样程序的可移植性就有了提高。因此我们一般编程只需面对中间层预言、虚拟机即可,这是计算机系统抽象表示的发展趋势
在编译器将源代码编译为目的码的过程中,会先将源代码转换为一个或多个的中间表述,以方便编译器进行最佳化,并产生出目的机器的机器语言。通常,中间语言的设计与一般的机器语言有三个不同之处:
每个指令代表仅有一个基本的操作。举例来说,在微处理器中出现的shift-add定址模式在中间语言不会出现。
指令集内可能不会包含控制流程的资讯。
暂存器可用的数量可能会很大,甚至没有限制。
最常见的中间语言表述形式,是三位址码(Three address code)。
这个术语也同时用来代称一些作为中间层的语言,有些高级语言不会输出为机器语言,它们仅会输出这种中间语言,而这些中间语言则会像一般语言一样,提交给编译器,编译为机器语言。这通常被用于让最佳化的过程更简单,也用于增进可移植性的能力,改进移植的方式则是利用中间语言的编译器,可以编译出许多中央处理器及操作系统可使用的机器码,例如C语言。中间语言的复杂度,通常介于高阶语言及低级语言之间,例如汇编语言。
查不到
https://blog.csdn.net/zxadcsdn/article/details/86409600
这篇博客画了一个java处理器的图
程序执行结果不仅取决于算法、程序编写,而且取决于语言处理系统、操作系统、ISA、微体系结构。

都是16位
汇编语言中寄存器功能的解释:
https://blog.csdn.net/a675311/article/details/53220590
逻辑地址由段地址和段内偏移地址两部分组成。
存储单元的物理地址由地址加法器生成。寻址时,CPU首先将段地址和段内偏移地址送入地址加法器,地址加法器将段地址向左偏移四位并且与段内偏移地址相加,得到一个20位的物理地址。
BIU指总线接口单元,EU指执行单元。
当指令队列中有2个空字节时,BIU自动把指令取到指令队列中,当指令队列已满,而EU无总线访问请求时,BIU进入空闲状态。
EU从指令队列的头部取出指令,并执行该指令。在执行中,如果需要访问内存或者I/O设备,则EU请求BIU取操作数,并直到需要的操作数到来后,EU才继续操作。若BIU处于空闲态,它立即响应请求,若BIU正在取指令到指令队列,它先完成取指令操作,再响应EU的请求。
EU在执行转移、调用,返回等指令时,指令队列中的指令被清除,BIU重新从存储器中取出指令送到指令队列,EU才继续执行指令。
程序/指令中的常数在内存哪个区域/段? ##
看不懂题目
未初始化的静态变量和初始化为0的静态变量或者全局变量在.bss区
已初始化的静态变量和全局变量在.data区
局部变量在栈里
采用内存引用的方式访问全局变量。

(实验报告里面扒下来的)
计算机是xx位的,指的是计算机CPU字的长度(字长)。
计算机中ALU的位数 = CPU中通用寄存器的位数 = 计算机的位数
编译器在编译阶段计算。
字符‘0’的ASCII码是48,则‘0’比0大,差48。
空间和声明的类型有关,如果0声明为int, ‘0’声明为char,则数字0占用的空间较大。
什么破问题。
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
要转换。
https://wenku.baidu.com/view/191f12537cd184254b353560.html这个讲的很清楚
ASCII码->编号->字库中点阵图编码->打印
优点:可表示数值范围较大,精度较高;不知道有啥缺点,等我问问老师
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)
和上面重复
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略
是,根据IEEE754标准,一个数字对应的格式是唯一的
不是,有些数字不能用二进制数字精确表示,涉及到舍入问题,可能不同的数字舍入之后对应的IEEE754编码完全相同。另外,当数字超出IEEE编码所能表示的范围,不同数字对应的编码都是一样的。(原因是我自己的想法)
-126 ~ +127
简述Float数据的浮点数密度分布?
分布不均匀,靠近远点处它们更稠密(课本P80)
整数报错,浮点无穷大X/0>Y 可以
Int表示的个数多(不知道这个表示是什么意思,我理解为精确表示)
Int 可以表示2^32个数字(+0和-0看做不同的数)
float可以表示232-224个数字(+0.0和-0.0看做不同的数,不算INF和NAN)
按照阶码区域写出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___
同上
约等于2^253 - 2^230
正数的最小值,IEEE754表示是0000_0000_0000_0000_0000_0000_0000_0001
阶码是1-127 = -126,尾数是2(-23)。值是2(-149)
当exp部分都是1
如果frac部分的位模式都是1表示INF
否则,表示NAN
浮点数的表示,越小精度越高,越大精度越低,这也基本符合数据处理的规律。太大的数据差点没啥,就是个规模而已。如人口、GDP等,没有必要到个、圆角分。
先判断是不是NAN,如果有一个数字是NAN,比较无意义。
如果均是数字,则按照下面原则进行比较:
先比较符号位,符号位是0的比符号位是1的大
。
阶码从最高位进行比较,例如阶码第一位是1的数字比阶码第一位是0的数字大,如果第一位相同向后比较即可,遵从同样的规则,直到分区处大小或者阶码相同。
如果阶码相同则比较尾数部分,同样是先比较高位之后比较低位即可。
IEEE浮点格式定义了四种不同方式的舍入方法,默认的方式是找到最接近的匹配,而其他三种可用于计算上界和下界。向偶数舍入是默认的舍入方法。
向偶数舍入,尾数可能产生溢出。
产生溢出,把阶码加一即可。
阶码产生溢出,说明这个数字超出了浮点数可以表示的范围。
按照十进制数字的四舍五入原则。
因为通用寄存器既可用于传送和暂存数据,也可参与算术逻辑运算,并保存运算结果。
大多数处理器都包含一个进位位和溢出位来支持位宽大于机器字长的数字的运算。
进位位用于无符号运算,溢出位用于有符号数运算。并且,位宽较大的数字分开存储即可。(参考家庭作业2.75)
大多数操作基本上是串行的。当在二进制电平上进行加法运算时,您需要两个输入位,并产生一个结果位和一个进位位。然后当将下一个最低有效位加入时,进位位用作输入,以此类推(
CPU字长只是限制了它可以运行的最大时钟速度
原文链接http://www.it1352.com/489105.html
(课本)他们描述了最近的算术和逻辑操作的属性,可以检测这些寄存器来执行条件分支指令。
(博客)具有以下 3 种作用。
(1)用来存储相关指令的某些执行结果;
(2)用来为 CPU 执行相关指令提供行为依据;
(3)用来控制 CPU 的相关工作方式
记住四个就行:
CF:进位标志。最近的操作使最高位产生了进位。可用来检查无符号操作的溢出。
ZF:零标志。最近的操作得出的结果为0.
SF:符号标志。最近的操作得到的结果为负数。
OF:溢出标志。最近的操作导致一个补码溢出一一正溢出或负溢出。
加法运算都可能改变。
详细介绍:
https://www.cnblogs.com/eleven24/articles/7783283.html
DF标记块传送是从低地址向高地址传,还是从高地址向低地址传。
IF可以屏蔽 可屏蔽中断请求INTR
如果外设有可屏蔽中断请求INTR,而此时CPU内IF=0,那么CPU不会响应中断
只有可屏蔽中断请求INTR和IF有关系,
内中断和不可屏蔽中断NMI,都不受IF的影响
CF针对无符号数(将寄存器中的操作数都看作是无符号数)
OF针对有符号数(将寄存器中的操作数都看作是有符号数)
CF位来判断无符号数运算的溢出,OF位来判断补码运算是否溢出。
IP寄存器的值通常不能直接被修改,需要使用call,ret,jmp等控制转移指令类修改。
不是,第三章最后提到的媒体寄存器是256位的,段寄存器(有的说32位,有的说16位)
CPU的字长 == 通用寄存器的位宽
重点:逻辑操作/位操作
这个不会

比例变址寻址
(A)CPU (B)OS (C)编译器 (D)源程序
** C **
全局变量:利用%rip+偏移量访问
局部变量:利用%rbp+偏移量访问
参数:先通过%rbp+偏移量得到栈中作为参数传递的a的首地址,然后利用此地址+偏移量访问
对于*(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(y>100)
0x5555555548c9 mov -0x20(%rbp),%eax
0x5555555548cc cmp $0x64,%eax
0x5555555548cf 77 _ jle 0x5555555548d8
0x5555555548d1 movl $0xffffffff,-0x20(%rbp)
稀疏的Switch语句 使用决策树 (if-elseif-elseif-else)
跳转表
一个代码段的地址
跳转元素的最大可能值-最小可能值
没看懂
跳转元素-跳转元素最小可能值
当下标在跳转表中时,跳转地址=基地址+下标*8
变址寻址
叶子节点的函数无需使用push,pop等保存,传递参数,也无需使用call调用其他函数,但非叶子节点的函数需要
递归无需特殊的处理
栈帧意味着每个函数调用私有的存储
保存的寄存器 、局部变量
保存的返回地址
寄存器保存约定:
防止函数调用损毁其他函数(调用)的数据
除非C代码明确地这样做(如缓冲区溢出)
栈的使用原则:遵循 调用/返回 模式
如 P调用Q, 然后Q 在P结束之前返回
后入、先出
对互递归同样有效
P调用Q; Q调用P
空间:全局区
赋初值:定义该变量的函数第一次被调用时
生命周期:整个源程序的周期
静态全局变量:整个源程序的周期
动态全局变量:整个源程序的周期
静态局部变量:整个源程序的周期
动态局部变量:定义它的函数的周期
传值的参数:定义它的函数的周期
缓冲区溢出,简单的说就是计算机对接收的输入数据没有进行有效的检测,向缓冲区内填充数据时超过了缓冲区本身的容量,而导致数据溢出到被分配空间之外的内存空间,使得溢出的数据覆盖了其他内存空间的数据。
攻击原理(3个采分点):向程序输入缓冲区写入特定的数据,例如在gets读入字符串时,使位于栈中的缓冲区数据溢出,用特定的内容覆盖栈中的内容,例如函数返回地址等,使得程序在读入字符串,结束函数gets从栈中读取返回地址时,错误地返回到特定的位置,执行特定的代码,达到攻击的目的。
防范方法(2个采分点,有2个就算对):
结构体用内存块来表示
足够大,可容纳所有字段
字段顺序必须与声明一致
即便其他顺序能使得内存更紧凑——也不行!
编译器决定总的尺寸和各字段位置
机器级程序不解读(理解)源代码中的结构体
数组元素的地址
每个结构体成员的偏移量(Offset)是在编译阶段确定的
地址计算形式:r + size*idx
结构体成员通过结构体基地址+偏移量的方式访问
#结构体成员作为参数传输是怎么实现的?
传递结构体基地址+偏移量处的对应成员
#结构体成员作为返回值是怎么实现的?
返回结构体基地址+偏移量处的对应成员
RF:程序寄存器 15个64位 %rax,%rcx,%rdx.%rbx,%rsp,%rbp,%rsi,%rdi,%r8到%r14
CC:条件码 3个1位 ZF SF OF
Stat:程序状态
PC:程序计数器
DMEM:内存
指令寻址方式:
顺序寻址方式 PC
跳跃寻址方式 jXX指令
操作数寻址方式:
寄存器寻址
立即寻址
间接寻址
基址寻址
Y86-64仅允许在寄存器数据中应用算逻操作
X86-64则允许立即数,寄存器和内存
对目的地址进行完整编码
和x86-64中的PC相对地址编码不一样
2005
0010 0000 0000 0101
call
ret
算数和逻辑操作OPq设置条件码作为指令执行的副作用
条件传送指令,条件跳转指令以条件码的值为依据
AOK(1)-正常操作
HLT(2)-执行Halt
ADR(3)-错误的指令或数据地址
INS(4)-无效指令
对于Y86-64,当遇到这些异常的时候,我们就简单地让处理器停止执行指令。在更完整的设计中,处理器通常会调用一个异常处理程序,这个过程被指定用来处理遇到的某种类型的异常。就像在第8章中讲述的,异常处理程序可以被配置成不同的结果,例如,中止程序或者调用一个用户自定义的信号处理程序。
取指:从指令存储器读取指令
译码:读程序寄存器
执行:计算数值或地址
访存:读或写数据
写回:写程序寄存器
更新PC:更新程序计数器
所有的指令有相同的格式,都按照6个阶段顺序执行
每一阶段的微操作,按照顺序有3-8种
每一条指令在各阶段执行时,根据指令类型按顺序执行不同的微操作
硬件单元(组合、时序逻辑)、控制逻辑、信号连接线
组合逻辑:硬件单元间传播,不用CLK,有延迟
计算逻辑(ALU、PCINC)
读逻辑( PC、CC、RF、imem、dmem)
硬件单元的时序逻辑-状态单元:都在CLK上升沿时更新。控制阻断
时钟寄存器:PC、CC、STAT的更新
随机访问存储器:RF写、dmem写
所谓CPU顺序实现SEQ:一个时钟变化,引发一个经过组合逻辑的流,从而执行整个指令
一条新指令的执行,即一个CLK是把上一条指令计算结果-状态单元更新,然后其值进行组合逻辑(新指令的计算)的传播
要控制CPU中活动的顺序,只需要用CLK控制寄存器和内存
所有6个阶段的所有步骤的状态更新同时发生(逐级延迟)
且只在时钟上升开始下一个周期时。
当前时钟/指令n结束,下一时钟/指令n+1没有到上升沿前,状态单元的数据-状态并不是本指令/时钟n执行的结果,仍然是上一时钟/指令n-1的结果
指令n的执行结果,到下一指令n+1的上升沿才更新到转状态单元
| $ $ | halt |
|---|---|
| 取指 | icode:ifun ← \leftarrow ←M1[PC]读指令字节 |
| 译码 | |
| 执行 | Stat ← \leftarrow ←HLT |
| 访存 | |
| 写回 | |
| 更新PC |
| $ $ | nop |
|---|---|
| 取指 | icode:ifun
←
\leftarrow
←M1[PC]读指令字节 valP ← \leftarrow ←PC+1计算下一个PC |
| 译码 | |
| 执行 | |
| 访存 | |
| 写回 | |
| 更新PC | PC ← \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 |
| 更新PC | PC ← \leftarrow ←valP |
| 注意:execute中的运算是valB OP valA,而不是valA OP valB | |
| M1[PC] 表示从PC开始的内存中读取一个字节的数据 | |
| V=====valC,指令中V是加数,是ALUA的一个输入 | |
| 这些微操作都有现成的硬件结构,所以不需要改动状态单元,但控制逻辑需要稍微调整 |
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
控制逻辑:每一阶段每一步骤的微操作,在硬件上的映射
在不同硬件单元之间传送数据
操作硬件单元,使得对不同指令执行指定的微操作
组合逻辑:不需要任何时序或控制,只要输入变化了,值就通过逻辑门网络传播
时序逻辑:需要时钟信号控制,进行更新-把输入锁存到输出
实际使用起来太慢
信号必须能在一个周期内传播所有的阶段,其中要经过指令内存、寄存器文件、ALU以及数据内存等
时钟必须非常慢
硬件单元只在时钟周期的一部分时间内被使用
容量:表示能够执行该运算的功能单元的数量
发射时间:表示两个连续的同类型的运算之间需要的最小时钟周期数
延迟界限:简单理解就是延迟,整数加法的延迟界限为1,整数乘法的为3,浮点加法为3,浮点乘法为5
关系:
冷不命中(强制不命中):当缓存为空时,对任何数据的请求都会不命中
冲突不命中:当缓存足够大,但是被引用的对象都映射到同一缓存块中,此种不命中称为冲突不命中
容量不命中:当工作集(working set)的大小超过缓存的大小时,会发生容量不命中
加快经常性事件:专注在核心函数和内循环上
使用内层循环的缓存不命中数量降到最低
在硬件层,硬件检测到事件会触发控制突然转移到异常处理程序。
在操作系统层,内核通过上下文切换,将控制从一个用户进程转移到另一个用户进程。
在应用层,一个进程可以发送信号到另一个进程,而接收者会将控制信号突然转移到他的一个信号处理程序。
一个程序可以通过回避通常的栈规则,并执行其他程序中任意位置的非本地跳转来对错误作出反应。
1)处理程序将控制返回给事件发生时正在执行的当前指令
2)处理程序将控制返回给没有发生异常将会执行的下一条指令
3)处理程序终止被中断的程序
异常在跳转到“异常处理程序”时,是通过一张“异常表”来跳转的,每一种异常都有一个异常号(非负整数),异常表会把不同的异常号关联到不同的异常处理程序,异常表在系统启动时由操作系统初始化。
异常是异常控制流的一种形式,是控制流中的突变,用来响应处理器状态中的某些变化,由硬件和操作系统实现
异常表:当处理器检测到有事件发生时,它会通过跳转表,进行一个间接过程调用(异常),到异常处理程序。系统启动时操作系统分配和初始化一张异常表。
异常号:系统中可能的某种类型的异常都分配了一个唯一的非负整数的异常号。异常号是到异常表中的索引,异常表的起始地址放在一个叫做异常表基址寄存器的特殊CPU寄存器里。*
在C中通过scanf等IO函数处理IO异常。
Linux系统调用中的功能号n就是异常号n
系统调用是一个特殊的异常处理子程序
异步异常需要/只需要CPU执行异常处理子程序,只需要操作系统处理即可。
浮点除以0产生异常,导致程序终止Abort退出。
浮点数除以0,不产生异常,程序正常执行。
任何数除以0产生异常,导致程序终止Abort退出。
每个进程的地址空间是整个内存空间的一部分
当程序执行到下面的语句: pid=fork(); 的时候会复制父进程的堆栈段,所以两个进程都停留在fork函数中,等待返回。因此fork函数会返回两次,一次是在父进程中返回,另一次是在子进程中返回,这两次的返回值是不一样的。通过fork返回的值来判断当前进程是子进程还是父进程
1)在父进程中,fork返回新创建子进程的进程ID;
2)在子进程中,fork返回0;
3)如果出现错误,fork返回一个负值。
运行(正在运行或在运行队列中等待)
中断(休眠中, 受阻, 在等待某个条件的形成或接受到信号)
不可中断(收到信号不唤醒和不可运行, 进程必须等待直到有中断发生)
僵死(进程已终止, 但进程描述符存在, 直到父进程调用wait4()系统调用后释放)
停止(进程收到SIGSTOP, SIGSTP, SIGTIN, SIGTOU信号后停止运行运行)
正常终止五种:
1.从main返回。
2.调用exit。
3.调用_exit或_Exit。
4.最后一个线程从其启动例程返回。
5.最后一个线程调用pthread_exit。
三种异常终止:
6.调用abort()。
7.接到一个信号并终止。
8.最后一个线程对取消请求作出响应。
kill 从字面来看,就是用来杀死进程的命令,但事实上,这个或多或少带有一定的误导性。从本质上讲,kill 命令只是用来向进程发送一个信号,至于这个信号是什么,是用户指定的。
也就是说,kill 命令的执行原理是这样的,kill 命令会向操作系统内核发送一个信号(多是终止信号)和目标进程的 PID,然后系统内核根据收到的信号类型,对指定进程进行相应的操作。
kill 命令的基本格式如下:
[root@localhost ~]# kill [信号] PID
kill 命令是按照 PID 来确定进程的,所以 kill 命令只能识别 PID,而不能识别进程名
kill 命令是按照 PID 来确定进程的,所以 kill 命令只能识别 PID,而不能识别进程名。Linux 定义了几十种不同类型的信号,读者可以使用 kill -l 命令查看所有信号及其编号,这里仅列出几个常用的信号,如下表所示。
| 信号编号 | 信号名 | 含义 |
|---|---|---|
| 0 | EXIT | 程序退出时收到该信息。 |
| 1 | HUP | 挂掉电话线或终端连接的挂起信号,这个信号也会造成某些进程在没有终止的情况下重新初始化。 |
| 2 | INT | 表示结束进程,但并不是强制性的,常用的 “Ctrl+C” 组合键发出就是一个 kill -2 的信号。 |
| 3 | QUIT | 退出。 |
| 9 | KILL | 杀死进程,即强制结束进程。 |
| 11 | SEGV | 段错误。 |
| 15 | TERM | 正常结束进程,是 kill 命令的默认信号。 |
需要注意的是,表中省略了各个信号名称的前缀 SIG,也就是说,SIGTERM 和 TERM 这两种写法都对,kill 命令都可以理解。
kill命令可以加许多参数(如上面的表格),其中-2 -9 -15和不添加参数的kill是不一样的
kill不带参数 即是普通的杀死进程,回收资源,-
2参数是低级别,可以被某些程序忽略,造成无法杀死进程。
-9参数是强制行为,不回收资源,可能造成资源浪费。例如父进程无法被回收
-15就是强制杀死进程,回收资源。
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子进程完全复制父进程的栈空间,也复制了页表,但没有复制物理页面,所以这时虚拟地址相同,物理地址也相同,但是会把父子共享的页面标记为“只读”,如果父子进程一直对这个页面是同一个页面,知道其中任何一个进程要对共享的页面“写操作”,这时内核会复制一个物理页面给这个进程使用,同时修改页表。而把原来的只读页面标记为“可写”,留给另外一个进程使用。
假定父进程malloc的指针指向0x12345678, fork 后,子进程中的指针也是指向0x12345678,但是这两个地址都是虚拟内存地址 (virtual memory),经过内存地址转换后所对应的 物理地址是不一样的。所以两个进城中的这两个地址相互之间没有任何关系。
就是说:
子进程继承父进程的全局变量。
子进程创建以后,可以读取原来父进程的全局变量的值。
但是创建以后,子进程修改了变量,或者是父进程修改了变量值,互相都不影响了。
无论是什么类型的变量,fork后父子进程中都是一样的,但两者之间没有关系,任何一个进程修改变量后,在另一个进程中都不能知道,更不能访问另一个进程中的变量,也不会对应相同的物理地址了
但实际上,linux为了提高 fork 的效率,采用了 copy-on-write 技术,fork后,这两个虚拟地址实际上指向相同的物理地址(内存页),只有任何一个进程试图修改这个虚拟地址里的内容前,两个虚拟地址才会指向不同的物理地址(新的物理地址的内容从原物理地址中复制得到)
早期的UNIX的fork()实现时,就是原汁原味的复制,和它的表像一样,但是很明显,这种方法效率太低,而且造成了很大的浪费,现在大部分的UNIX实现采用了如下俩种方法来规避这种浪费
(1)首先我们可以确定父子进程的代码段是相同的,所以代码段是没必要复制的,因此内核将代码段标记为只读,这样父子进程就可以安全的共享此代码段了。fork之后在进程创建代码段时,新子进程的进程级页表项都指向和父进程相同的物理页帧
(2)而对于父进程的数据段,堆段,栈段中的各页,由于父子进程要相互独立,所以我们采用写实复制的技术,来最大化的提高内存以及内核的利用率。刚开始,内核做了一些设置,令这些段的页表项指向父进程相同的物理内存页。调用fork之后,内核会捕获所有父进程或子进程针对这些页面的修改企图(说明此时还没修改)并为将要修改的页面创建拷贝。系统将新的页面拷贝分配给被内核捕获的进程,还会对子进程的相应页表项做适当的调整,现在父子进程就可以分别修改各自的上述段,不再互相影响了
fork()后产生的子进程与父进程行为是相互独立的,变量也需要是独立的,所以说物理内存中需要有两份空间,不可以共享全部内存。
并发进程是并行执行的,控制流物理上是不相交的。
任务管理器中的进程内是串行、进程间是并行
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.执行该信号的默认处理动作
每个信号类型都有一个预定义的默认行为
是下面中的一种:
3.提供一个信号处理函数,要求内核处理该信号时切换到用户执行这个处理函数,这种方式称为捕捉一个信号.
ps:信号机制
(1)发送信号。发送信号的程序用系统调用kill( )实现;
(2)预置对信号的处理方式。接收信号的程序用signal( )来实现对处理方式的预置;
(3)收受信号的进程按事先的规定完成对相应事件的处理。
ps2:信号的产生条件
用户在终端 按下某些键时,终端驱动程序会发送信号给前台进程,例如Ctrl-C产生SIGINT信 号,Ctrl-/产生SIGQUIT信号,Ctrl-Z产生SIGTSTP信号。
硬件异常产生信号,这些条件由硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了 除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE信号发送给进 程。再比如当前进程访问了非法内存地址,,MMU会产生异常,内核将这个异常解释为SIGSEGV信 号发送给进程。
一个进程调用kill(2)函数可以发送信 号给另一个进程。
可以用kill(1)命令发送信号给某个 进程,kill(1)命令也是调用kill(2)函 数实现的,如果不明确指定信号则发送SIGTERM信号,该信号的默认处理动作是终止进程。
当 内核检测到某种软件条件发生时也可以通过信号通知进程,例如闹钟超时产生SIGALRM信 号,向读端已关闭的管道写数据时产生SIGPIPE信号。
信号的捕获过程

**批处理(**Batch),也称为批处理脚本。顾名思义,批处理就是对某对象进行批量的处理,通常被认为是一种简化的脚本语言,它应用于DOS和Windows系统中。
**作业(job)**是计算机操作者(或是一个叫做作业调度器的程序)交给操作系统的执行单位
任务:在多道程序或多进程环境中,要由计算机来完成的基本工作元,它是由控制程序处理的一 个或多个指令序列。
程序是一组计算机能识别和执行的指令,
**进程:**是程序的一次执行,进程是可以和别的计算并行执行进程是程序在一个数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单位
程序与进程之间的区别:
(1)进程更能真实地描述并发,而程序不能。
(2)进程由程序和数据两部分组成,进程是竞争计算机系统有限资源的基本单位,也是进程处理机调度的基本单位。
(3)程序是静态的概念;进程是程序在处理机上一次执行的过程,是动态的概念。
(4)进程有生存周期,有诞生有消亡。是短暂的;而程序是相对长久的。
(5)一个程序可以作为多个进程的运行程序;一个进程也可以运行多个程序。
(6)进程具有创建其他进程的功能;而程序没有。
作业与进程的区别:
一个进程是一个程序对某个数据集的执行过程,是分配资源的基本单位。作业是用户需要计算机完成的某项任务,是要求计算机所做工作的集合。一个作业的完成要经过作业提交、作业收容、作业执行和作业完成4个阶段。而进程是对已提交完毕的程序所执行过程的描述,是资源分配的基本单位。其主要区别如下。
(1)作业是用户向计算机提交任务的任务实体。在用户向计算机提交作业后,系统将它放入外存中的作业等待队列中等待执行。而进程则是完成用户任务的执行实体,是向系统申请分配资源的基本单位。任一进程,只要它被创建,总有相应的部分存在于内存中。
(2)一个作业可由多个进程组成,且必须至少由一个进程组成,反过来则不成立。
(3)作业的概念主要用在批处理系统中,像UNIX这样的分时系统中就没有作业的概念。而进程的概念则用在几乎所有的多道程序系统中。
作业、进程和程序之间的联系:
一个作业通常包括程序、数据和操作说明书3部分。每一个进程由PCB、程序和数据集合组成。这说明程序是进程的一部分,是进程的实体。因此,一个作业可划分为若干个进程来完成,而每一个进程有其实体————程序和数据集合。
异常处理
1.当发生异常时,CPU控制单元产生一个硬件出错码。
2.CPU根据该中断吗找到中断向量表内的对应向量,根据该向量转到中断处理程序。
3.中断处理程序处理完之后向当前进程发送一个SIG***信号。
中断处理
1.设备产生中断
2.PIC(可编程中断控制器)会产生一个对应的中断向量
3.和中断向量表中的每一个中断向量进行比较,转到对应的中断处理程序
4.中断处理程序进行保存现场,做相关处理,恢复现场
5.内核调度,返回用户进程
信号与中断的相似点
(1)采用了相同的异步通信方式;
(2)当检测出有信号或中断请求时,都暂停正在执行的程序而转去执行相应的处理程序;
(3)都在处理完毕后返回到原来的断点;
(4)对信号或中断都可进行屏蔽。
信号与中断的区别
(1)中断有优先级,而信号没有优先级,所有的信号都是平等的;
(2)信号处理程序是在用户态下运行的,而中断处理程序是在核心态下运行;
(3)中断响应是及时的,而信号响应通常都有较大的时间延迟。
硬件异常也能产生信号,例如被零除、无效内存引用等。这些条件通常先由内核硬件检测到,然后通知内核。内核将决定产生什么样的信号。
下图为异常和函数产生信号的方式以及进程的处理过程

外部中断不支持嵌套处理,内部异常支持嵌套处理,原因是:内部异常程序也是有可能发生越界、越权、缺页的
一般的故障类异常-内中断的处理是通过向发生异常的进程发送信号的机制实现异 常处理,可尽快完成在内核态的异常处理过程,因为异常处理过程越长,嵌套执 行异常的可能性越大,而异常嵌套执行会付出较大的代价。但缺页故障不是
Linux用两个32/64位数表示信号与其阻塞 kill函数是终止进程并回收进程
Linux处理“除法错”异常#DE(类型0)发送SIGFPE信号 Linux用信号处理同步异常,用中断处理异步异常(pushf)
计算机的地址空间分为物理空间和虚拟空间
物理地址 (physical address): 放在寻址总线上的地址。放在寻址总线上,如果是读,电路根据这个地址每位的值就将相应地址的物理内存中的数据放到数据总线中传输。如果是写,电路根据这个地址每位的值就将相应地址的物理内存中放入数据总线上的内容。物理内存是以字节(8位)为单位编址的。
虚拟地址 (virtual address): CPU启动保护模式后,程序运行在虚拟地址空间中。注意,并不是所有的“程序”都是运行在虚拟地址中。CPU在启动的时候是运行在实模式的,内核在初始化页表之前并不使用虚拟地址,而是直接使用物理地址的。
通过MMU进行转换
大致过程:
1)根据VA和C2找到一级页表条目
2)若是段描述符,返回物理地址
3)若该条目是二级页表描述符,继续根据虚拟地址找下一个条目
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);
正如在第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将根据需要换人代码和数据页面。
PPN(物理页号)
这里不介绍32/64位系统各种变量到底占多少个字节
32位系统内存上限: 2^32=4G Bytes
64位系统内存上限同理可以计算为128GBytes
如图所示

一级页表指向二级页表
末级页表中表项即为PPN
不一定
七次
Intel没有只采用物理寻址的计算机系统。
#QA26
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
由于内存映射机制,所以一个磁盘文件对象可以被多个进程共享访问,也可以被多个进程对象私有访问。如果是共享访问,那么一个进程对这个对象的修改会显示到其他进程。如果是私有访问,内核会采用写时拷贝copy on write的方式,如果一个进程要修改一个私有的写时拷贝的对象,会产生一个保护故障,内核会拷贝这个私有对象,写进程会在新的私有对象上修改,其他进程仍指向原来的私有对象。
execve函数在当前的进程中加载并运行包含在可执行目标文件a.out中的程序,用a.out程序有效的替代了当前程序。加载并运行a.out需要以下几个步骤:
删除已存在的用户区域:删除当前进程虚拟地址的用户部分中的已存在的区域结构。
映射私有区域:为新程序的文本,数据,bss,和栈区创建新的区域结构。所有这些新的区域都是私有的,写时拷贝的。文本和数据区域被映射为a.out文件中的文本和数据区。bss区域是请求二进制零的,映射到匿名文件,其大小包含在a.out中。栈和堆区域也是请求二进制零的,初始长度为0.
映射共享区域:如果a.out程序和共享对象(或目标)链接,比如标准C库libc.so,那么这些对象都是动态链接到这个程序的,然后在映射到用户虚拟地址空间中的共享区域内。
设置程序计数器(PC):exevce做的最后一件事情就是设置当前进程上下文中的程序计数器,使之指向文本区域的入口点。
根节点对应于这样一种不在堆中的位置,它们中包含指向堆中的指针。这些位置可以是寄存器、栈里的变量,或者是虚拟内存中读写数据区域内的全局变量。
非也
C程序的Mark&Sweep收集器必须是保守的,其根本原因是C语言不会用类型信息来标记内存位置。因此,像int或者float 这样的标量可以伪装成指针。例如,假设某个可达的已分配块在它的有效载荷中包含一个int,其值碰巧对应于某个其他已分配块b的有效载荷中的一个地址。对收集器而言,是没有办法推断出这个数据实际上是int而不是指针。因此,分配器必须保守地将块b标记为可达,尽管事实上它可能是不可达的。
主板、电源、硬盘(ROM)、光驱、显卡(GPU)、网卡(网络设备)、声卡、调制解调器、软驱(老式电脑)、散热器、显示器、键盘、鼠标(输入输出设备)
单片机可以把微处理器、存储器和I/O接口电路等制作在一块集成电路芯片上