自己总结了下操作系统面试常考题,建议背诵
主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其他进程造成影响;线程只是一个进程的不同执行路径,线程有自己的堆栈和局部变量,但是没有独立的地址空间,一个线程死掉就是整个进程死掉。所以多进程的程序比多线程的程序健壮,但是进程切换时,耗费资源较大,效率差一些。对于一些需要同时进行又要共享某些变量的并发操作,只能用线程不能用进程。
协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
进程拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度。
线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程亦由操作系统调度(标准线程是的)。
协程和线程一样共享堆,不共享栈,协程由程序员在协程的代码里显示调度。
名称 | 描述 |
---|---|
用户级线程(User-LevelThread, ULT) | 由应用程序所支持的线程实现, 内核意识不到用户级线程的实现 |
内核级线程(Kemel-LevelThread, KLT) | 内核级线程又称为内核支持的线程 |
内核级线程:切换由内核控制,当线程进行切换的时候,由用户态转化为内核态。切换完毕要从内核态返回用户态;可以很好的利用多核cpu。windows线程就是这样的。
用户级线程(轻量级):内核的切换由用户态程序自己控制内核切换,不需要内核干涉,少了进出内核态的消耗,但不能很好的利用多核Cpu,目前Linux pthread大体是这么做的。
区别:
内核态(Kernel Mode):运行操作系统程序
用户态(User Mode):运行用户程序
内核态与用户态是操作系统的两种运行级别,当程序运行在3级特权级上时,就可以称之为运行在用户态。因为这是最低特权级,是普通的用户进程运行的特权级,大部分用户直接面对的程序都是运行在用户态
当程序运行在0级特权级上时,就可以称之为运行在内核态。
运行在用户态下的程序不能直接访问操作系统内核数据结构和程序。当我们在系统中执行一个程序时,大部分时间是运行在用户态下的,在其需要操作系统帮助完成某些它没有权力和能力完成的工作时就会切换到内核态。
这两种状态的主要差别是
处于用户态执行时,进程所能访问的内存空间和对象受到限制,其所处于占有的处理机是可被抢占的。
而处于核心态执行中的进程,则能访问所有的内存空间和对象,且所占有的处理机是不允许被抢占的。
系统调用(主动)
这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作。比如fork()实际上就是执行了一个创建新进程的系统调用。
异常(被动)
当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。
外围设备的中断(被动)
由于在实际应用当中,分配内存、创建进程、线程都会设计到一些系统调用,系统调用需要导致程序从用户态切换到内核态,是非常耗时的操作。
因此,当程序中需要频繁的进行内存申请释放,进程、线程创建销毁等操作时,通常会使用内存池、进程池、线程池技术来提升程序的性能。
内存池是指程序预先从操作系统申请一块足够大内存,此后,当程序中需要申请内存的时候,不是直接向操作系统申请,而是直接从内存池中获取。
同理,当程序释放内存的时候,并不真正将内存返回给操作系统,而是返回内存池。当程序退出(或者特定时间)时,内存池才将之前申请的真正内存释放。
先启动若干数量的线程,并让这些线程都处于睡眠状态,当需要一个开辟一个线程去做具体的工作时,就会唤醒线程池中的某一个睡眠线程,让它去做具体工作。
当工作完成后,线程又处于睡眠状态,而不是将线程销毁。
(1)当一个进程从运行切换到等待状态(如:I/O请求,或者调用wait等待一个子进程的终止)
(2)当一个进程从运行状态切换到就绪状态(如:出现中断)
(3)当一个进程从等待状态切换到就绪状态(如:I/O完成)
(4)当一个进程终止时
调度只能发生在1,4为非抢占,否则为抢占。采用非抢占,一旦CPU分配给进程,其会一直运行到结束或者进入等待状态
FCFS 先到先服务算法
非抢占、平均等待时间长
SJF 最短作业优先算法
抢占/非抢占、平均等待时间最短
priority scheduling algorithm 优先级调度算法
抢占/非抢占,存在无限阻塞和饥饿问题
轮转法调度(round-robin,RR)
定义一个较小的时间单元,称为时间片(time quantum,or time slice)。将就绪队列作为循环队列
平均等待时间通常较长
普通管道PIPE
有限制,半双工:只能单向传输;只能在具有亲缘关系的进程间通信
有名管道
半双工,允许无亲缘关系进程间通信
流管道
可以双向传输,去除了第一种限制
信号量
信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
共享内存
共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
消息队列
消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。
套接口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。
临界区
在任意时刻只允许一个线程对共享资源进行访问,如果有多个线程试图访问公共资源,那么在有一个线程进入后,其他试图访问公共资源的线程将被挂起,并一直等到进入临界区的线程离开,临界区在被释放后,其他线程才可以抢占。
互斥量
只有拥有互斥对象的线程才有访问公共资源的权限,因此互斥对象只有一个,所以能保证公共资源不被多个线程访问。
信号量
允许多个线程在同一时刻访问同一资源,但是需要西安至同一时刻访问此资源的最大线程数目。
事件
通过通知操作的方式来保持线程的同步。
总结:
每个进程中访问临界资源的那段程序称为临界区,每次只准许一个进程进入临界区,进入后不允许其他进程进入。
互斥
如果进程Pi在临界区执行,那么其他进程都不能在临界区执行
前进
如果没有进程在其临界区内执行且有进程需要进入临界区,那么只有那些不在剩余区内执行的进程可以参与选择,以确定谁能下一个进入临界区,且这种选择不能无限推迟。
有限等待
从一个进程做出进入临界区的请求,知道该请求被允许,其他进程允许进入临界区的次数有上限
一组生产者进程和一组消费者进程共享一个初始为空、大小为n的缓冲区,只有缓冲区没满时,生产者才能把消息放入到缓冲区,否则必须等待;只有缓冲区不空时,消费者才能从中取出消息,否则必须等待。由于缓冲区是临界资源,它只允许一个生产者放入消息,或者一个消费者从中取出消息。
semaphore mutex=1; //临界区互斥信号量
semaphore empty=n; //空闲缓冲区
semaphore full=0; //缓冲区初始化为空
producer () { //生产者进程
while(1){
produce an item in nextp; //生产数据
P(empty); //获取空缓冲区单元
P(mutex); //进入临界区.
add nextp to buffer; //将数据放入缓冲区
V(mutex); //离开临界区,释放互斥信号量
V(full); //满缓冲区数加1
}
}
consumer () { //消费者进程
while(1){
P(full); //获取满缓冲区单元
P(mutex); // 进入临界区
remove an item from buffer; //从缓冲区中取出数据
V (mutex); //离开临界区,释放互斥信号量
V (empty) ; //空缓冲区数加1
consume the item; //消费数据
}
}
互斥
指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。请求和保持
占有并等待
进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
非抢占
进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
循环等待
指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
死锁预防
死锁避免
1 当一个顾客对资金的最大需求量不超过银行家现有的资金时就可接纳该顾客;
2 顾客可以分期贷款,但贷款的总数不能超过最大需求量;
3 当银行家现有的资金不能满足顾客尚需的贷款数额时,对顾客的贷款可推迟支付,但总能使顾客在有限的时间里得到贷款;
4 当顾客得到所需的全部资金后,一定能在有限的时间里归还所有的资金.
缺点
1 这个算法要求客户数保持固定不变,这在多道程序系统中是难以做到的。
2 这个算法保证所有客户在有限的时间内得到满足,但实时客户要求快速响应,所以要考虑这个因素。
3 由于要寻找一个安全序列,实际上增加了系统的开销。
死锁检测和恢复
特征
动态性、并发性、独立性、异步性
状态
当进程已经分配到除CPU以外的所有必要的资源,只要获得处理机便可立即执行,这时的进程状态为就绪状态。
当进程已经获得处理机,其程序正在处理机上执行,此时的进程状态为执行状态。
正在执行的进程,由于等待某个事件发生而无法执行时,便放弃处理机而处于阻塞状态。引发进程阻塞的事件有很多,例如:等待I/O,等待信号。
状态切换
处于就绪状态的进程,当进程调度程序为之分配了处理机后,该进程便由就绪状态转为执行状态。
处于执行状态的进程在其执行过程中,因分配给它的一个时间片用完而不得不让出处理机,于是进程从执行状态转为就绪状态。
正在执行的进程因等待某种事件发生而无法继续执行时,便从执行状态转为阻塞状态。
处于阻塞状态的进程,若其等待的事件已经发生,便从阻塞状态转为就绪状态。
两者都采用离散分配方式,且都要通过地址映射机构来实现地址变换。
不同点:
页是信息的物理单位,用户透明,长度固定常见512B~8KB。分页是为实现离散分配方式,以消减内存的外零头,提高内存的利用率。可以说分页仅仅是由于系统管理的需要而不是用户的需要。
段是信息的逻辑单位,用户可见,长度可变。它含有一组其意义相对完整的信息。分段的目的是为了能更好地满足用户的需要。
页的大小固定且由系统确定,把逻辑地址划分为页号和页内地址两部分,是由机器硬件实现的,因而一个系统只能有一种大小的页面。
段的长度却不固定,决定于用户所编写的程序,通常由编译程序根据信息的性质来划分。
分页的作业地址空间是一维的,即单一的线性地址空间,程序员只需利用一个记忆符,即可表示一个地址;
分段的作业地址空间则是二维的,程序员在标识一个地址时,既需给出段名,又需给出段内地址。
单一连续分配
内存在此方式下分为系统区和用户区,系统区仅提供给操作系统使用,通常在低地址部分;用户区是为用户提供的、除系统区之外的内存空间。这种方式无需进行内存保护。
这种方式的优点是简单、无外部碎片,可以釆用覆盖技术,不需要额外的技术支持。缺点是只能用于单用户、单任务的操作系统中,有内部碎片,存储器的利用率极低。
固定分区分配
它将用户内存空间划分为若干个固定大小的区域,每个分区只装入一道作业。当有空闲分区时,便可以再从外存的后备作业队列中,选择适当大小的作业装入该分区,如此循环。
这种分区方式存在两个问题:一是程序可能太大而放不进任何一个分区中,这时用户不得不使用覆盖技术来使用内存空间;二是主存利用率低,当程序小于固定分区大小时,也占用了一个完整的内存分区空间,这样分区内部有空间浪费,这种现象称为内部碎片。
动态内存分配
动态分区分配又称为可变分区分配,是一种动态划分内存的分区方法。这种分区方法不预先将内存划分,而是在进程装入内存时,根据进程的大小动态地建立分区,并使分区的大小正好适合进程的需要。
内存碎片问题
.lib
格式的文件,一般在工程的设置界面加入工程中,程序编译时会把lib
文件的代码加入你的程序中因此会增加代码大小,你的程序一运行lib
代码强制被装入你程序的运行空间,不能手动移除lib
代码。*.dll
,在程序运行时可以随意加载和移除,节省内存空间。最佳置换OPT
最佳置换算法是将未来最久不使用的页替换出去,这听起来很简单,但是无法实现。但是这种算法可以作为衡量其它算法的基准。
先进先出FIFO
优先淘汰最早进入内存的页面,亦即在内存中驻留时间最久的页面。该算法实现简单,只需把调入内存的页面根据先后次序链接成队列,设置一个指针总指向最早的页面。但该算法与进程实际运行时的规律不适应,因为在进程中,有的页面经常被访问。
最近最久未使用LRU
选择最近最长时间未访问过的页面予以淘汰,它认为过去一段时间内未访问过的页面,在最近的将来可能也不会被访问。该算法为每个页面设置一个访问字段,来记录页面自上次被访问以来所经历的时间,淘汰页面时选择现有页面中值最大的予以淘汰。
Belady奇异现象,是指采用页面置换FIFO算法时,如果对一个进程未分配它所要求的全部页面,有时就会出现分配的页面数增多,但缺页率反而提高的异常现象,这是一个违反直觉的现象。
原因是:FIFO算法的置换特征与进程访问内存的动态特征是矛盾的,即被置换的页面并不是进程不会访问的,因而FIFO并不是一个好的置换算法
Thrashing抖动现象,又叫颠簸。如果分配给进程的存储块数量小于进程所需要的最小值,进程的运行将很频繁地产生缺页中断,这种频率非常高的页面置换现象称为抖动。
原因是:进程的内存量不足。因而分配页面太少,总是缺页。