操作系统
1.进程、线程和协程的区别和联系
进程 | 线程 | 协程 | |
---|---|---|---|
定义 | 资源分配和拥有的基本单位 | 程序执行的基本单位 | 用户态的轻量级线程,线程内部调度的基本单位 |
切换情况 | 进程CPU环境(栈、寄存器、页表和文件句柄等)的保存以及新调度的进程CPU环境的设置 | 保存和设置程序计数器、少量寄存器和栈的内容 | 先将寄存器上下文和栈保存,等切换回来的时候再进行恢复 |
切换者 | 操作系统 | 操作系统 | 用户 |
切换过程 | 用户态->内核态->用户态 | 用户态->内核态->用户态 | 用户态(没有陷入内核) |
调用栈 | 内核栈 | 内核栈 | 用户栈 |
拥有资源 | CPU资源、内存资源、文件资源和句柄等 | 程序计数器、寄存器、栈和状态字 | 拥有自己的寄存器上下文和栈 |
并发性 | 不同进程之间切换实现并发,各自占有CPU实现并行 | 一个进程内部的多个线程并发执行 | 同一时间只能执行一个协程,而其他协程处于休眠状态,适合对任务进行分时处理 |
系统开销 | 切换虚拟地址空间,切换内核栈和硬件上下文,CPU高速缓存失效、页表切换,开销很大 | 切换时只需保存和设置少量寄存器内容,因此开销很小 | 直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快 |
通信方面 | 进程间通信需要借助操作系统 | 线程间可以直接读写进程数据段(如全局变量)来进行通信 | 共享内存、消息队列 |
2.外中断和异常有什么区别?
外中断(Interrupt)和异常(Exception)是计算机体系结构中两种不同的概念,它们有以下区别:
- 来源:
外中断是由外部设备(如键盘、鼠标、定时器等)触发的中断,通常用于与计算机外部设备进行通信和交互。
异常是由于程序执行过程中出现了某种错误或不正常情况而触发的事件,例如除零、访问非法内存、非法指令等。 - 触发时机:
外中断是在 CPU 执行指令的过程中由外部设备发出的中断请求,可以在任何时候发生,甚至在指令执行的中间。
异常是在指令执行过程中出现了错误或不正常情况时发生的,通常是由当前指令执行的结果导致的。 - 处理方式:
外中断通常由操作系统的中断处理程序进行处理,它会保存当前进程的状态,切换到相应的中断处理程序,并在处理完成后返回到原进程继续执行。
异常通常由异常处理机制(如操作系统或硬件)进行处理,它会根据异常类型执行相应的处理逻辑,可能包括异常处理程序的调用、异常信息的记录等。 - 举例:
外中断的例子包括定时器中断、IO 设备中断等。
异常的例子包括除零异常、内存访问异常、非法指令异常等。
总的来说,外中断和异常都是计算机体系结构中的重要概念,它们分别用于处理外部设备的中断请求和程序执行过程中的错误或异常情况,但在触发时机、处理方式等方面有所不同。
3.进程调度算法你了解多少?
先来先服务 first-come first-serverd(FCFS)
非抢占式的调度算法,按照请求的顺序进行调度。有利于长作业,但不利于短作业,因为短作业必须一直等待前面的长作业执行完毕才能执行,而长作业又需要执行很长时间,造成了短作业等待时间过长。
短作业优先 shortest job first(SJF)
非抢占式的调度算法,按估计运行时间最短的顺序进行调度。长作业有可能会饿死,处于一直等待短作业执行完毕的状态。因为如果一直有短作业到来,那么长作业永远得不到调度。
最短剩余时间优先 shortest remaining time next(SRTN)
最短作业优先的抢占式版本,按剩余运行时间的顺序进行调度。 当一个新的作业到达时,其整个运行时间与当前进程的剩余时间作比较。如果新的进程需要的时间更少,则挂起当前进程,运行新的进程。否则新的进程等待。
时间片轮转
将所有就绪进程按 FCFS 的原则排成一个队列,每次调度时,把 CPU 时间分配给队首进程,该进程可以执行一个时间片。当时间片用完时,由计时器发出时钟中断,调度程序便停止该进程的执行,并将它送往就绪队列的末尾,同时继续把 CPU 时间分配给队首的进程。
时间片轮转算法的效率和时间片的大小有很大关系:
因为进程切换都要保存进程的信息并且载入新进程的信息,如果时间片太小,会导致进程切换得太频繁,在进程切换上就会花过多时间。而如果时间片过长,那么实时性就不能得到保证。
优先级调度
为每个进程分配一个优先级,按优先级进行调度。为了防止低优先级的进程永远等不到调度,可以随着时间的推移增加等待进程的优先级。
多级反馈队列
一个进程需要执行 100 个时间片,如果采用时间片轮转调度算法,那么需要交换 100 次。多级队列是为这种需要连续执行多个时间片的进程考虑,它设置了多个队列,每个队列时间片大小都不同,例如 1,2,4,8,..。进程在第一个队列没执行完,就会被移到下一个队列。
这种方式下,之前的进程只需要交换 7 次。每个队列优先权也不同,最上面的优先权最高。因此只有上一个队列没有进程在排队,才能调度当前队列上的进程。
可以将这种调度算法看成是
时间片轮转调度算法和优先级调度算法的结合
。
4.Linux下进程间通信方式?
管道:
无名管道(内存文件):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程之间使用。进程的亲缘关系通常是指父子进程关系。
有名管道(FIFO文件,借助文件系统):有名管道也是半双工的通信方式,但是允许在没有亲缘关系的进程之间使用,管道是先进先出的通信方式。
共享内存:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的IPC方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与信号量,配合使用来实现进程间的同步和通信。
消息队列:消息队列是有消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
套接字:适用于不同机器间进程通信,在本地也可作为两个进程通信的方式。
信号:用于通知接收进程某个事件已经发生,比如按下ctrl + C就是信号。
信号量:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,实现进程、线程的对临界区的同步及互斥访问。
5.如果系统中具有快表后,那么地址的转换过程变成什么样了?
- CPU给出逻辑地址,由某个硬件算得页号、页内偏移量,将页号与快表中的所有页号进行比较。
- 如果找到匹配的页号,说明要访问的页表项在快表中有副本,则直接从中取出该页对应的内存块号,再将内存块号与页内偏移量拼接形成物理地址,最后,访问该物理地址对应的内存单元。因此,若快表命中,则访问某个逻辑地址仅需一次访存即可。
- 如果没有找到匹配的页号,则需要访问内存中的页表,找到对应页表项,得到页面存放的内存块号,再将内存块号与页内偏移量拼接形成物理地址,最后,访问该物理地址对应的内存单元。因此,若快表未命中,则访问某个逻辑地址需要两次访存(注意:在找到页表项后,应同时将其存入快表,以便后面可能的再次访问。但若快表已满,则必须按照-定的算法对旧的页表项进行替换)
由于查询快表的速度比查询页表的速度快很多,因此只要快表命中,就可以节省很多时间。 因为局部性原理,–般来说快表的命中率可以达到90%以上。
局部性原理:
局部性原理是计算机系统设计中的一个重要概念,它指的是在程序执行过程中,访问内存的趋势是倾向于集中在一小部分地址范围内。局部性原理分为两种类型:时间局部性和空间局部性。- 时间局部性(Temporal Locality):
时间局部性指的是,如果在程序的某个时间点访问了某个存储单元,那么在不久之后的时间内,很可能会再次访问相同的存储单元。这意味着程序倾向于重复使用最近使用过的数据或指令。时间局部性的主要原因是程序中存在循环、子程序调用等结构,导致某些数据被反复使用。
* 空间局部性(Spatial Locality):
空间局部性指的是,如果程序访问了某个存储单元,那么在不久之后的时间内,很可能会访问与该存储单元相邻的存储单元。这意味着程序倾向于顺序地访问相邻的内存位置,例如数组、矩阵等数据结构。空间局部性的主要原因是计算机系统中的缓存机制,通常会将相邻的内存位置加载到缓存中,以提高访问效率。
6.动态分区分配算法有哪几种?可以分别说说吗?
算法 | 算法思想 | 分区排列顺序 | 优点 | 缺点 |
---|---|---|---|---|
首次适应 | 从头到尾找适合的分区 | 空闲分区以地址递增次序排列 | 综合看性能最好。算法开销小,回收分区后一.般不需要对空闲分区队列重新排序 | |
最佳适应 | 优先使用更小的分区,以保留更多大分区 | 空闲分区以容量递增次序排列 | 会有更多的大分区被保留下来,更能满足大进程需求 | 会产生很多太小的、难以利用的碎片;算法开销大,回收分区后可能需要对空闲分区队列重新排序 |
最坏适应 | 优先使用更大的分区,以防止产生太小的不可用的碎片 | 空闲分区以容量递减次序排列 | 可以减少难以利用的小碎片 | 大分区容易被用完,不利于大进程;算法开销大(原因同上) |
邻近适应 | 由首次适应演变而来,每次从上次查找结束位置开始查找 | 空闲分区以地址递增次序排列(可排列成循环链表) 不 | 用每次都从低地址的小分区开始检索。算法开销小(原因同首次适应算法) | 会使高地址的大分区也被用完 |
7.虚拟技术你了解吗?
虚拟技术把一个物理实体转换为多个逻辑实体。
主要有两种虚拟技术:时(时间)分复用技术
和空(空间)分复用技术
。
多进程与多线程:多个进程能在同一个处理器上并发执行使用了时分复用技术,让每个进程轮流占用处理器,每次只执行一小个时间片并快速切换。
虚拟内存使用了空分复用技术,它将物理内存抽象为地址空间,每个进程都有各自的地址空间。地址空间的页被映射到物理内存,地址空间的页并不需要全部在物理内存中,当使用到一个没有在物理内存的页时,执行页面置换算法,将该页置换到内存中。
8.虚拟内存的目的是什么?
空分复用:
虚拟内存的目的是为了让物理内存扩充成更大的逻辑内存,从而让程序获得更多的可用内存。
为了更好的管理内存,操作系统将内存抽象成地址空间。每个程序拥有自己的地址空间,这个地址空间被分割成多个块,每一块称为一页。
这些页被映射到物理内存,但不需要映射到连续的物理内存,也不需要所有页都必须在物理内存中。当程序引用到不在物理内存中的页时,由硬件执行必要的映射,将缺失的部分装入物理内存并重新执行失败的指令。
从上面的描述中可以看出,虚拟内存允许程序不用将地址空间中的每一页都映射到物理内存,也就是说一个程序不需要全部调入内存就可以运行,这使得有限的内存运行大程序成为可能。
例如有一台计算机可以产生 16 位地址,那么一个程序的地址空间范围是 0~64K。该计算机只有 32KB 的物理内存,虚拟内存技术允许该计算机运行一个 64K 大小的程序。
9.一个C/C++程序从开始编译到生成可执行文件的完整过程,你能说出来多少?
四个过程:
(1)预编译 主要处理源代码文件中的以“#”开头的预编译指令。处理规则见下
删除所有的#define,展开所有的宏定义。
处理所有的条件预编译指令,如“#if”、“#endif”、“#ifdef”、“#elif”和“#else”。
处理“#include”预编译指令,将文件内容替换到它的位置,这个过程是递归进行的,文件中包含其他 文件。
删除所有的注释,“//”和“/**/”。
保留所有的#pragma 编译器指令,编译器需要用到他们,如:#pragma once 是为了防止有文件被重 复引用。
添加行号和文件标识,便于编译时编译器产生调试用的行号信息,和编译时产生编译错误或警告是 能够显示行号。
(2)编译 把预编译之后生成的xxx.i或xxx.ii文件,进行一系列词法分析、语法分析、语义分析及优化后,生成相应的汇编代码文件。
词法分析:利用类似于“有限状态机”的算法,将源代码程序输入到扫描机中,将其中的字符序列分割成一系列的记号。
语法分析:语法分析器对由扫描器产生的记号,进行语法分析,产生语法树。由语法分析器输出的语法树是一种以表达式为节点的树。
语义分析:语法分析器只是完成了对表达式语法层面的分析,语义分析器则对表达式是否有意义进行判断,其分析的语义是静态语义——在编译期能分期的语义,相对应的动态语义是在运行期才能确定的语义。
优化:源代码级别的一个优化过程。
目标代码生成:由代码生成器将中间代码转换成目标机器代码,生成一系列的代码序列——汇编语言表示。
目标代码优化:目标代码优化器对上述的目标机器代码进行优化:寻找合适的寻址方式、使用位移来替代乘法运算、删除多余的指令等。
(3)汇编
将汇编代码转变成机器可以执行的指令(机器码文件)。 汇编器的汇编过程相对于编译器来说更简单,没有复杂的语法,也没有语义,更不需要做指令优化,只是根据汇编指令和机器指令的对照表一一翻译过来,汇编过程有汇编器as完成。
经汇编之后,产生目标文件(与可执行文件格式几乎一样)xxx.o(Linux下)、xxx.obj(Windows下)。
(4)链接
将不同的源文件产生的目标文件进行链接,从而形成一个可以执行的程序。链接分为静态链接和动态链接:
静态链接: 函数和数据被编译进一个二进制文件。在使用静态库的情况下,在编译链接可执行文件时,链接器从库中复制这些函数和数据并把它们和应用程序的其它模块组合起来创建最终的可执行文件。 空间浪费:因为每个可执行程序中对所有需要的目标文件都要有一份副本,所以如果多个程序对同一个目标文件都有依赖,会出现同一个目标文件都在内存存在多个副本; 更新困难:每当库函数的代码修改了,这个时候就需要重新进行编译链接形成可执行程序。
- 运行速度快:但是静态链接的优点就是,在可执行程序中已经具备了所有执行程序所需要的任何东西,在执行的时候运行速度快。
动态链接: 动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。
共享库:就是即使需要每个程序都依赖同一个库,但是该库不会像静态链接那样在内存中存在多份副本,而是这多个程序在执行时共享同一份副本;
更新方便:更新时只需要替换原来的目标文件,而无需将所有的程序再重新链接一遍。当程序下一次运行时,新版本的目标文件会被自动加载到内存并且链接起来,程序就完成了升级的目标。
性能损耗:因为把链接推迟到了程序运行时,所以每次执行程序都需要进行链接,所以性能会有一定损失。
10.讲解逻辑地址转换为物理地址的基本过程
11.进程同步的四种方法
临界区
对临界资源进行访问的那段代码称为临界区。
为了互斥访问临界资源,每个进程在进入临界区之前,需要先进行检查。
同步与互斥
同步:多个进程因为合作产生的直接制约关系,使得进程有一定的先后执行关系。
互斥:多个进程在同一时刻只有一个进程能进入临界区。
信号量
信号量(Semaphore)是一个整型变量,可以对其执行 down 和 up 操作,也就是常见的 P 和 V 操作。
管程
管程是一种高级的同步机制,它将数据结构和操作封装在一起,提供了对共享资源的访问和控制。只有通过管程提供的操作才能访问共享资源,从而确保了对共享资源的互斥访问。
管程(Monitor)实质上可以看作是一个类或者一个抽象数据类型(ADT)。它封装了共享资源以及对该资源的访问和控制方法,提供了一种高级的同步机制。
12.进程通信方式
管道(pipe):允许一个进程和另一个与它有共同祖先的进程之间进行通信
命名管道(FIFO):类似于管道,但是它可以用于任何两个进程之间的通信,命名管道在文件系统中有对应的文件名。命名管道通过命令mkfifo或系统调用mkfifo来创建
消息队列(MQ):消息队列是消息的连接表,包括POSIX消息对和System V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能成该无格式字节流以及缓冲区大小受限等缺点;
信号量(semaphore):信号量主要作为进程间以及同进程不同线程之间的同步手段;
共享内存(shared memory):它使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。这是针对其他通信机制运行效率较低而设计的。它往往与其他通信机制,如信号量结合使用,以达到进程间的同步及互斥
信号(signal):信号是比较复杂的通信方式,用于通知接收进程有某种事情发生,除了用于进程间通信外,进程还可以发送信号给进程本身
内存映射(mapped memory):内存映射允许任何多个进程间通信,每一个使用该机制的进程通过把一个共享的文件映射到自己的进程地址空间来实现它Socket:它是更为通用的进程间通信机制,可用于不同机器之间的进程间通信
13.介绍一下几种典型的锁?
读写锁
- 多个读者可以同时进行读
- 写者必须互斥(只允许一个写者写,也不能读者写者同时进行)
- 写者优先于读者(一旦有写者,则后续读者必须等待,唤醒时优先考虑写者)
互斥锁
一次只能一个线程拥有互斥锁,其他线程只有等待
互斥锁是在抢锁失败的情况下主动放弃CPU进入睡眠状态直到锁的状态改变时再唤醒,而操作系统负责线程调度,为了实现锁的状态发生改变时唤醒阻塞的线程或者进程,需要把锁交给操作系统管理,所以互斥锁在加锁操作时涉及上下文的切换。互斥锁实际的效率还是可以让人接受的,加锁的时间大概100ns左右,而实际上互斥锁的一种可能的实现是先自旋一段时间,当自旋的时间超过阀值之后再将线程投入睡眠中,因此在并发运算中使用互斥锁(每次占用锁的时间很短)的效果可能不亚于使用自旋锁
条件变量
- 互斥锁一个明显的缺点是他只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,他常和互斥锁一起使用,以免出现竞态条件。当条件不满足时,线程往往解开相应的互斥锁并阻塞线程然后等待条件发生变化。一旦其他的某个线程改变了条件变量,他将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。总的来说互斥锁是线程间互斥的机制,条件变量则是同步机制。
自旋锁
- 如果进线程无法取得锁,进线程不会立刻放弃CPU时间片,而是一直循环尝试获取锁,直到获取为止。如果别的线程长时期占有锁,那么自旋就是在浪费CPU做无用功,但是自旋锁一般应用于加锁时间很短的场景,这个时候效率比较高。
14.逻辑地址VS物理地址
注意:物理地址指的是程序在内存中的地址,而不是程序在硬盘中的地址。
逻辑地址(Logical Address)和物理地址(Physical Address)是在计算机系统中用于访问内存的两种不同的地址类型,它们之间存在着映射关系。下面是它们的区别:
逻辑地址:
- 逻辑地址是由CPU产生的地址,用于访问内存中的数据和指令。它是一个虚拟地址,与实际的硬件内存地址无关。
- 在多道程序设计环境下,每个进程都有自己的逻辑地址空间。逻辑地址是相对于进程而言的,它从0开始,通常是一个连续的地址空间。
- 逻辑地址的映射是由操作系统的内存管理单元来完成的,包括地址翻译、分页机制、分段机制等。
物理地址:
- 物理地址是实际存储在计算机硬件中的地址,用于在内存中定位数据和指令的位置。它是相对于物理内存模块而言的,是真实存在的硬件地址。
- 物理地址是由内存管理单元(Memory Management Unit,MMU)根据逻辑地址的映射关系转换得到的。MMU负责将逻辑地址映射到相应的物理地址上,以便CPU可以正确地访问内存中的数据。
映射关系:
- 逻辑地址和物理地址之间存在着映射关系,这个映射关系由操作系统的内存管理单元来管理和维护。
- 当CPU发出一个逻辑地址时,MMU会根据逻辑地址的映射关系将其转换为相应的物理地址,然后用于实际的内存访问。
总的来说,逻辑地址是虚拟的地址空间,是由CPU产生的相对地址;而物理地址是真实的硬件地址,是内存中实际存储数据和指令的位置。通过逻辑地址和物理地址之间的映射关系,CPU可以正确地访问内存中的数据和指令。
15.内存交换
内存覆盖(Memory Overlay):
- 内存覆盖是一种技术,用于在内存有限的情况下运行较大的程序。当程序的内存需求超过了系统的物理内存时,操作系统会将程序的一部分加载到内存中运行,然后根据需要逐步覆盖、替换加载的程序段。
- 内存覆盖通常用于早期的计算机系统,特别是在内存容量受限的环境下。它可以让大型程序在较小的内存空间中运行,但需要手动管理内存的分段和覆盖过程。
内存交换(Memory Swapping):
- 内存交换是一种技术,用于在系统的物理内存不足时将部分内存中的数据和程序暂时移到辅助存储设备(如硬盘)中。这样可以腾出内存空间供其他程序使用。
- 当操作系统检测到物理内存不足时,它会将部分不活动的进程或者数据交换到硬盘上,以释放内存空间。当需要访问这些被交换出去的数据时,操作系统会将其再次交换回内存中,以满足程序的需求。
- 内存交换是一种动态的内存管理技术,操作系统可以根据系统的内存需求自动进行内存交换,无需用户干预。
总的来说,内存覆盖和内存交换都是用于解决内存有限的情况下运行大型程序的技术,但它们的实现方式和应用场景略有不同。内存覆盖主要用于早期计算机系统中,而内存交换则是现代操作系统中常用的内存管理技术之一。
16.如何让进程后台运行
使用后台运行命令:
- 在 Unix/Linux 系统中,可以使用 & 符号将命令放置在后台运行。例如:./my_program &。
- 实际上,这样是将命令放入到一个作业队列中了。这样可以使得该进程在后台运行,不会阻塞当前终端的使用。
使用 nohup 命令:
- nohup 命令可以让进程在后台运行,并且不受终端关闭的影响。例如:nohup ./my_program &。
- 使用 nohup 后,即使关闭终端或者注销用户,进程仍然会继续在后台运行。
使用 screen 或者 tmux:
- screen 或者 tmux 是终端复用工具,可以创建一个或多个终端窗口,并在其中运行进程。即使关闭了当前终端窗口,进程仍然会继续在后台运行。
- 具体使用方法请参考 screen 或者 tmux 的文档。
使用后台运行守护进程:
- 将需要后台运行的程序设计为守护进程,并使用系统服务管理工具(如 systemd、init.d 等)启动该守护进程。
- 守护进程是一种在后台运行的长期运行的进程,通常用于执行系统任务或服务。
17.快表在什么位置
TLB(Translation Lookaside Buffer,快表)通常位于处理器的内部,是一个高速缓存结构,用于存储最近使用的逻辑地址到物理地址的映射关系。TLB的设计旨在加速逻辑地址到物理地址的转换过程,提高系统的性能和响应速度。
18.守护进程、僵尸进程和孤儿进程
守护进程(Daemon Process):
- 守护进程是在后台运行的一种特殊进程,通常是由操作系统启动并且在后台运行的进程。它通常不与任何用户交互,而是执行一些系统任务或服务,例如网络服务、日志服务等。
- 守护进程通常在系统启动时被初始化,并且一直运行直到系统关闭。它们通常不会终止,除非出现严重的错误或者手动停止。
僵尸进程(Zombie Process):
- 僵尸进程是已经完成执行但是其父进程尚未调用
wait()
或waitpid()
函数来获取其终止状态的进程。在这种情况下,操作系统会将僵尸进程保留在进程表中,直到其父进程获取其终止状态。 - 僵尸进程不会占用系统资源,但是如果太多的僵尸进程积累,会导致系统的进程表被占满,从而影响系统的正常运行。
- 僵尸进程是已经完成执行但是其父进程尚未调用
孤儿进程(Orphan Process):
- 孤儿进程是指其父进程提前终止或者意外终止,而导致子进程成为没有父进程的进程。在这种情况下,子进程会被
init
进程(进程号为1)接管。 - 孤儿进程会继续在系统中运行,直到其自己终止或者被
init
进程接管。这样可以确保孤儿进程不会成为僵尸进程,因为init
进程会及时调用wait()
函数获取其终止状态。
- 孤儿进程是指其父进程提前终止或者意外终止,而导致子进程成为没有父进程的进程。在这种情况下,子进程会被
19.如何避免僵尸进程?
父进程调用 wait() 或 waitpid() 函数:
- 父进程可以通过调用 wait() 或 waitpid() 函数来等待子进程的退出,并获取子进程的终止状态。这样可以及时回收子进程的资源,并避免其成为僵尸进程。
- 在父进程中定期调用 wait() 或 waitpid() 函数可以确保及时处理子进程的退出。
使用信号处理函数:
- 父进程可以注册
SIGCHLD
信号的处理函数,在收到该信号时调用wait()
或waitpid()
函数来处理子进程的退出。 - 当子进程退出时,操作系统会向父进程发送 SIGCHLD 信号,父进程可以在信号处理函数中获取子进程的终止状态。
- 父进程可以注册
设置信号处理方式为忽略(SIG_IGN):
- 父进程可以将
SIGCHLD
信号的处理方式设置为忽略,这样子进程退出时可让内核把僵尸子进程转交给init进程去处理,不会产生僵尸进程。 - 但是需要注意的是,这种方式下无法获取子进程的终止状态,可能会导致资源泄漏或者无法正确处理异常情况。
- 父进程可以将
20.Linux中异常和中断的区别
在Linux系统中,异常(Exception)和中断(Interrupt)是两种不同的事件,它们的触发方式、处理方式以及产生的原因有所不同。
异常(Exception):
- 异常是指在程序执行过程中发生的意外事件或错误情况,例如除零错误、内存访问越界等。异常通常是由CPU在执行指令过程中检测到的,它是指令执行的结果与预期不符导致的。
- 在Linux系统中,异常由CPU直接检测并触发,然后将控制权转移给操作系统内核。操作系统内核会根据异常的类型和原因采取相应的处理措施,例如向用户进程发送信号、终止异常进程等。
中断(Interrupt):
- 中断是指外部设备或者硬件组件发送的信号,用于通知CPU需要处理某个事件或者执行某个操作。中断可以是来自硬件设备的信号(硬件中断),也可以是来自软件的信号(软件中断)。
- 在Linux系统中,中断通常由硬件设备发送给CPU,例如定时器中断、键盘输入中断等。当CPU接收到中断信号时,会暂停当前的执行流程,并转移到中断处理程序(中断服务程序)中执行。中断处理程序负责处理中断事件,并进行相应的处理,例如读取设备数据、响应用户输入等。
总的来说,异常是程序执行过程中的错误情况,由CPU直接检测并触发;而中断是外部设备发送的信号,用于通知CPU需要处理某个事件或者执行某个操作。异常和中断在触发方式、处理方式以及产生原因上有着明显的区别。
21.一般情况下在Linux/windows平台下栈空间的大小
Linux环境下有操作系统决定,一般是8MB,8192KB,通过
ulimit
命令查看以及修改Windows环境下由编译器决定,VC++6.0一般是1M
22.交换空间是什么
交换空间(Swap Space)是指操作系统用于扩展虚拟内存的一种技术,它将部分内存数据临时存储到磁盘上以释放物理内存空间。交换空间通常用于以下几种情况:
内存不足:当系统的物理内存不足以容纳所有的进程和数据时,操作系统会将部分不经常使用的数据存储到交换空间中,以释放物理内存空间给更重要的进程使用。
内存回收:操作系统可以使用交换空间来回收不活动的内存页面,以便将其分配给其他进程使用。这有助于优化内存的使用效率和系统的性能。
休眠和恢复:在某些情况下,操作系统可以使用交换空间来存储休眠进程的状态,以便在系统重新启动或恢复时能够快速恢复进程的状态。
交换空间通常由操作系统预先分配,并且可以是一个专门的交换分区,也可以是一个交换文件。交换空间的大小可以根据系统的配置和需求进行调整,但是过度使用交换空间可能会影响系统的性能,因为磁盘访问速度远远低于内存访问速度。因此,合理配置交换空间是系统性能优化的重要一环。
23.常见的几种磁盘调度算法
先来先服务(First-Come, First-Served,FCFS):
- FCFS 是最简单的磁盘调度算法,按照请求到达的顺序依次处理磁盘访问请求。
- 这种算法的优点是公平性,即所有的请求都能够得到处理。但是它可能会导致磁盘头在磁盘上来回移动,造成平均响应时间较长。
最短寻道时间优先(Shortest Seek Time First,SSTF):
- SSTF 算法会优先处理与磁头当前位置最接近的磁盘访问请求。
- 这种算法能够减少磁盘头的移动距离,从而缩短平均响应时间。但是它可能会导致部分请求长时间等待,造成请求饥饿现象。
电梯算法(Elevator Algorithm):
- 电梯算法模拟了电梯在多层楼间上下运动的过程,磁盘头会沿着一个方向移动,直到该方向上没有未处理的请求,然后改变方向。
- 这种算法能够有效地减少磁盘头的移动次数,从而提高磁盘访问的效率。但是它可能会导致一些请求长时间等待,特别是当磁盘请求集中在一个方向时。
扫描算法(SCAN):
- 扫描算法(也称为电梯算法的变种)从磁盘上的一个端点开始沿着一个方向移动,处理所有的请求,直到达到另一个端点,然后返回,继续处理另一侧的请求。
- 这种算法能够平衡磁盘头的移动,避免了某些请求长时间等待的情况,但是可能会导致磁盘头在两端之间频繁切换,造成磁盘头的忙碌。
24.抖动你知道是什么吗?它也叫颠簸现象
刚刚换出的页面马上又要换入内存,刚刚换入的页面马上又要换出外存,这种频繁的页面调度行为称为抖动,或颠簸。产生抖动的主要原因是进程频繁访问的页面数目高于可用的物理块数(分配给进程的物理块不够)
25.内存交换中,被换出的进程保存在哪里?
保存在磁盘中,也就是外存中。具有对换功能的操作系统中,通常把磁盘空间分为文件区和对换区两部分。文件区主要用于存放文件,主要追求存储空间的利用率,因此对文件区空间的管理采用离散分配方式;对换区空间只占磁盘空间的小部分,被换出的进程数据就存放在对换区。由于对换的速度直接影响到系统的整体速度,因此对换区空间的管理主要追求换入换出速度,因此通常对换区采用连续分配方式(学过文件管理章节后即可理解)。总之,对换区的I/O速度比文件区的更快。
26.ASCII、Unicode和UTF-8编码的区别?
ASCII
ASCII 只有127个字符,表示英文字母的大小写、数字和一些符号,但由于其他语言用ASCII 编码表示字节不够,例如:常用中文需要两个字节,且不能和ASCII冲突,中国定制了GB2312编码格式,相同的,其他国家的语言也有属于自己的编码格式。Unicode
由于每个国家的语言都有属于自己的编码格式,在多语言编辑文本中会出现乱码,这样Unicode应运而生,Unicode就是将这些语言统一到一套编码格式中,通常两个字节表示一个字符,而ASCII是一个字节表示一个字符,这样如果你编译的文本是全英文的,用Unicode编码比ASCII编码需要多一倍的存储空间,在存储和传输上就十分不划算。UTF-8
为了解决上述问题,又出现了把Unicode编码转化为可变长编码
UTF-8编码,UTF-8编码将Unicode字符按数字大小编码为1-6个字节,英文字母被编码成一个字节,常用汉字被编码成三个字节,如果你编译的文本是纯英文的,那么用UTF-8就会非常节省空间,并且ASCII码也是UTF-8的一部分。三者之间的联系
搞清楚了ASCII、Unicode和UTF-8的关系,我们就可以总结一下现在计算机系统通用的字符编码工作方式:在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,就转换为UTF-8编码:
用记事本编辑的时候,从文件读取的UTF-8字符被转换为Unicode字符到内存里,编辑完成后,保存的时候再把Unicode转换为UTF-8保存到文件。如下图
浏览网页的时候,服务器会把动态生成的Unicode内容转换为UTF-8再传输到浏览器:
27.页面置换算法
最佳置换法(OPT)
- 最佳置换算法(OPT,Optimal) :每次选择淘汰的页面将是以后永不使用,或者在最长时间内不再被访问的页面,这样可以保证最低的缺页率。
- 最佳置换算法可以保证最低的缺页率,但实际上,只有在进程执行的过程中才能知道接下来会访问到的是哪个页面。操作系统无法提前预判页面访问序列。因此,最佳置换算法是无法实现的
先进先出置换算法(FIFO)
先进先出置换算法(FIFO) :每次选择淘汰的页面是最早进入内存的页面 实现方法:把调入内存的页面根据调入的先后顺序排成一个队列,需要换出页面时选择队头页面队列的最大长度取决于系统为进程分配了多少个内存块。
Belady异常—当为进程分配的物理块数增大时,缺页次数不减反增的异常现象。
只有FIFO算法会产生Belady异常,而LRU和OPT算法永远不会出现Belady异常。另外,FIFO算法虽然实现简单,但是该算法与进程实际运行时的规律不适应,因为先进入的页面也有可能最经常被访问。因此,算法性能差
FIFO的性能较差,因为较早调入的页往往是经常被访问的页,这些页在FIFO算法下被反复调入和调出,并且有Belady现象。所谓Belady现象是指:采用FIFO算法时,如果对—个进程未分配它所要求的全部页面,有时就会出现分配的页面数增多但缺页率反而提高的异常现象。
最近最久未使用置换算法(LRU)
最近最久未使用置换算法(LRU,least recently used) :每次淘汰的页面是最近最久未使用的页面 实现方法:赋予每个页面对应的页表项中,用访问字段记录该页面自.上次被访问以来所经历的时间t(该算法的实现需要专门的硬件支持,虽然算法性能好,但是实现困难,开销大)。当需要淘汰一个页面时,选择现有页面中t值最大的,即最近最久未使用的页面。
LRU性能较好,但需要寄存器和栈的硬件支持。LRU是堆栈类算法,理论上可以证明,堆栈类算法不可能出现Belady异常。
时钟置换算法(CLOCK)
最佳置换算法性OPT能最好,但无法实现;先进先出置换算法实现简单,但算法性能差;最近最久未使用置换算法性能好,是最接近OPT算法性能的,但是实现起来需要专门的硬件支持,算法开销大。
所以操作系统的设计者尝试了很多算法,试图用比较小的开销接近LRU的性能,这类算法都是CLOCK算法的变体,因为算法要循环扫描缓冲区像时钟一样转动。所以叫clock算法。
时钟置换算法是一种性能和开销较均衡的算法,又称CLOCK算法,或最近未用算法(NRU,Not Recently Used)
简单的CLOCK算法实现方法:为每个页面设置一个访问位,再将内存中的页面都通过链接指针链接成一个循环队列。当某页被访问时,其访问位置为1。当需要淘汰-一个页面时,只需检查页的访问位。如果是0,就选择该页换出;如果是1,则将它置为0,暂不换出,继续检查下一个页面,若第- - ~轮扫描中所有页面都是1,则将这些页面的访问位依次置为0后,再进行第二轮扫描(第二轮扫描中一定会有访问位为0的页面,因此简单的CLOCK算法选择–个淘汰页面最多会经过两轮扫描)
改进型的时钟置换算法
简单的时钟置换算法仅考虑到一个页面最近是否被访问过。事实上,如果被淘汰的页面没有被修改过,就不需要执行I/O操作写回外存。只有被淘汰的页面被修改过时,才需要写回外存。
因此,除了考虑一个页面最近有没有被访问过之外,操作系统还应考虑页面有没有被修改过。在其他条件都相同时,应优先淘汰没有修改过的页面,避免I/O操作。这就是改进型的时钟置换算法的思想。修改位=0,表示页面没有被修改过;修改位=1,表示页面被修改过。
- 总结
算法规则 | 优缺点 | |
---|---|---|
OPT | 优先淘汰最长时间内不会被访问的页面 | 缺页率最小,性能最好;但无法实现 |
FIFO | 优先淘汰最先进入内存的页面 | 实现简单;但性能很差,可能出现Belady异常 |
LRU | 优先淘汰最近最久没访问的页面 | 性能很好;但需要硬件支持,算法开销大 |
CLOCK (NRU) | 循环扫描各页面 第一轮淘汰访问位=0的,并将扫描过的页面访问位改为1。若第-轮没选中,则进行第二轮扫描。 | 实现简单,算法开销小;但未考虑页面是否被修改过。 |
改进型CLOCK (改进型NRU) | 若用(访问位,修改位)的形式表述,则 第一轮:淘汰(0,0) 第二轮:淘汰(O,1),并将扫描过的页面访问位都置为0 第三轮:淘汰(O, 0) 第四轮:淘汰(0, 1) | 算法开销较小,性能也不错 |
28.Belady异常
LRU为什么不会发生Belady异常?
实页数增加 —> 能贮存的页数增加 —> 哪些页?—> 访问频率高的页
LRU当中替换的是使用频率最低的页,留下的都是使用频率高的页。当实页数增加,能够留下的高频访问的页也就更多,这直接关系到命中率的增加。
FIFO为什么会发生Belady异常?
实页数增加 —> 能贮存的页数增加 —> 哪些页?—> 后面来的页
先进先出的替换算法,完全不考虑使用频率,即使增加了实页数,多贮存的部分接下来常访问可能性也不一定大(看运气),也就并不一定能增加命中率。
29.死锁产生原因
举个例子:两个线程A和B,两个数据1和2。线程A在执行过程中,首先对资源1加锁,然后再去给资源2加锁,但是由于线程的切换,导致线程A没能给资源2加锁。线程切换到B后,线程B先对资源2加锁,然后再去给资源1加锁,由于资源1已经被线程A加锁,因此线程B无法加锁成功,当线程切换为A时,A也无法成功对资源2加锁,由此就造成了线程AB双方相互对一个已加锁资源的等待,死锁产生。
理论上认为死锁产生有以下四个必要条件,缺一不可:
互斥条件:进程对所需求的资源具有排他性,若有其他进程请求该资源,请求进程只能等待。
不剥夺条件:进程在所获得的资源未释放前,不能被其他进程强行夺走,只能自己释放。
请求和保持条件:进程当前所拥有的资源在进程请求其他新资源时,由该进程继续占有。
循环等待条件:存在一种进程资源循环等待链,链中每个进程已获得的资源同时被链中下一个进程所请求。
30.银行家算法
银行家算法是一种用于避免死锁(Deadlock)的资源分配和调度算法,最初由Dijkstra在1965年提出。它通过合理地分配和释放资源,避免了进程之间因争夺资源而导致的死锁问题。银行家算法主要用于操作系统中的进程管理和资源分配。
银行家算法的基本思想是通过检查分配资源的安全性来决定是否允许进程继续运行。它通过维护一个系统资源分配的最大需求矩阵、可用资源向量和已分配资源矩阵,来判断系统当前是否处于安全状态。
31.进程切换与线程切换的区别
进程切换与线程切换的一个最主要区别就在于进程切换涉及到虚拟地址空间
的切换而线程切换则不会。因为每个进程都有自己的虚拟地址空间,而线程是共享所在进程的虚拟地址空间的,因此同一个进程中的线程进行线程切换时不涉及虚拟地址空间的转换。
31.为什么进程切换比线程切换效率低
每个进程都有对应的页表,进程切换的时候需要切换页表,为了加快虚拟地址的地址转换效率,所以引入了TLB来缓存对应的虚拟地址和物理地址的映射。
切换页表这个操作本身是不太耗费时间的,切换之后,TLB就失效了,所以在进行地址转化的时候需要重新去查找页表,这就造成了程序运行的效率低下。
同一个进程的线程之间是共用一个页表的,所以线程之间的切换是不需要切换页表的。
TLB(translation lookaside buffer)快表,直译为转换检测缓冲区,也称页表缓冲,是一个内存管理单元,用于改进虚拟地址到物理地址转换速度的缓存。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1430797759@qq.com