操作系统面试总结

本文详细总结了操作系统面试中的核心概念,包括线程的同步与互斥、进程与线程的区别、线程同步机制(互斥锁、信号量、自旋锁等)、进程通信方式(管道、信号、共享内存等)、死锁条件与解决方案、进程状态以及内存管理策略(分页与分段)。此外,还探讨了多进程和多线程的应用场景,以及Select/Poll/Epoll等I/O复用技术。
摘要由CSDN通过智能技术生成

本文参考书目:
操作系统:精髓与设计原理,第8版

什么是线程的同步与互斥?

互斥:指在某一时刻指允许一个进程运行其中的程序片,具有排他性和唯一性。
对于线程A和线程B来讲,在同一时刻,只允许一个线程对临界资源进行操作,即当A进入临界区对资源操作时,B就必须等待;当A执行完,退出临界区后,B才能对临界资源进行操作。

同步:指的是在互斥的基础上,实现进程之间的有序访问。假设现有线程A和线程B,线程A需要往缓冲区写数据,线程B需要从缓冲区读数据,但他们之间存在一种制约关系,即当线程A写的时候,B不能来拿数据;B在拿数据的时候A不能往缓冲区写,也就是说,只有当A写完数据(或B取走数据),B才能来读数据(或A才能往里写数据)。这种关系就是一种线程的同步关系。

(一)请分别简单说一说进程、线程、协程及它们的区别。


进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。每个进程都有自己的独立内存空间,拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度。

线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,而拥有自己独立的栈和共享的堆,栈段又叫运行时段,用来存放所有局部变量和临时变量共享堆。只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈)

协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。

协程与线程比较

  • 一个线程可以多个协程,即一个内核线程对应多个用户协程(用户进程)。
  • 进程、线程,都是有内核进行调度,有CPU时间片的概念,进行抢占式调度(有多种调度算法)。协程的调度与内核无关,完全有程序进行控制。只能进行非抢占式调度。
  • 线程进程都是同步机制,而协程则是异步。
  • 协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态

(二)线程同步的方式有哪些?

若多个线程同时访问公共数据,则极有可能出错,此时需要线程同步。

同步的几种方法:
互斥锁(Mutex)
1、互斥锁的本质:
首先需要明确一点,互斥锁实际上是一种变量,在使用互斥锁时,实际上是对这个变量进行置0置1操作并进行判断使得线程能够获得锁或释放锁。
(互斥锁的具体实现在文末讲解)
2、作用:互斥锁的作用是对临界区加以保护,以使任意时刻只有一个线程能够执行临界区的代码。实现了多线程之间的互斥。
互斥的缺点:访问无序

信号量:它允许同一时刻多个线程访问同一资源,但是需要控制同一时刻访问此资源的最大线程数量。
信号量有两种类型:
(1)二进制信号量。它只有0和1两种取值。
(2)计数信号量。它可以有更大的取值范围。
如果要用信号量来保护一段代码,使其每次只能被一个执行线程运行,就要用到二进制信号量。
如果要允许有限数目的线程执行一段指定的代码,就需要用到计数信号量。

事件
多线程同步Event,主要用于线程间的等待通知。内核对象中,事件内核对象是个最基本的对象。它们包含一个使用计数(与所有内核对象一样),一个用于指明该事件是个自动重置的事件还是一个人工重置的事件的布尔值,另一个用于指明该事件处于已通知状态还是未通知状态的布尔值。
事件能够通知一个操作已经完成。有两种不同类型的事件对象。一种是人工重置的事件,另一种是自动重置的事件。当人工重置的事件得到通知时,等待该事件的所有线程均变为可调度线程。当一个自动重置的事件得到通知时,等待该事件的线程中只有一个线程变为可调度线程。
当一个线程执行初始化操作,然后通知另一个线程执行剩余的操作时,事件使用得最多。事件初始化为未通知状态,然后,当该线程完成它的初始化操作后,它就将事件设置为已通知状态。这时,一直在等待该事件的另一个线程发现该事件已经得到通知,因此它就变成可调度线程。

自旋锁

(三)进程的通信方式有哪些? (课本P186)

进程通信:
每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程A把数据从用户空间拷到内核缓冲区,进程B再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信

主要分为:管道、系统IPC(包括消息队列、共享内存、信号)、SOCKET

管道包括命名管道、匿名管道:

  1. 管道是一个环形队列缓冲区,允许两个进程以生产者消费者模型进程通信。是一个先进先出(FIFO)队列,由一个进程写,而由另一个进程读。
  2. 管道在创建时获得一个固定大小的字节数。当一个进程试图往管道中写时,如果有足够的空间,则写请求立即被执行,否则该进程被阻塞。如果一个进程试图读取的字节数多于当前管道中的字节数,也将被阻塞。
  3. 操作系统强制实行互斥,只能有一个进程可以访问管道。
  4. 只有有血缘关系(父子关系)的进程才可以共享匿名管道,不相关的进程只能共享命名管道。
  5. 命名管道的用途主要有:(1)shell命名使用FIFO将数据从一条管道传送到另一条时,无须创建中间临时文件;(2)在客户进程和服务器进程间传送数据。

系统IPC(包括消息队列、信号、共享存储) Inter Process Communication
消息队列是消息的链表,存放在内核中并由消息队列标识符标识。
信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
共享内存虚拟内存当中共享的一块内存,这段共享内存由一个进程创建,但是多个进程可以访问。最快

套接字( socket ) : 套接口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信

线程通信的方式:

锁机制:包括互斥锁、条件变量、读写锁

  • 互斥锁提供了以排他方式防止数据结构被并发修改的方法。
  • 读写锁允许多个线程同时读共享数据,而对写操作是互斥的。
  • 条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。

信号量机制(Semaphore):包括无名线程信号量和命名线程信号量

信号机制(Signal):类似进程间的信号处理

线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。

悲观锁和乐观锁

悲观锁
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。

乐观锁
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

两种锁的适用情况

乐观锁适用于写比较少的情况下(多读场景),多写的场景下用悲观锁就比较合适

(四)什么是缓冲区溢出?有什么危害?其原因是什么?

缓冲区溢出是指当计算机向缓冲区填充数据时超出了缓冲区本身的容量,溢出的数据覆盖在合法数据上。

危害有以下两点:
程序崩溃,导致拒绝服务
跳转并且执行一段恶意代码

造成缓冲区溢出的主要原因是程序中没有仔细检查用户输入(溢出攻击)。

(五)什么是死锁?死锁产生的条件?

一组进程当中每个进程都在等待某个事件,而仅有这组被阻塞的进程当中的某个进程才能触发该事件,就称这组进程产生了死锁。

死锁产生的四个条件(有一个条件不成立,则不会产生死锁)
互斥、占有且等待、不可抢占、循环等待 (课本P175)
前三个为必要条件,最后一个为充要条件
互斥:只有一个进程使用一个资源
占有且等待:当一个进程等待时,继续占有已经分配的资源
不可抢占:不能抢占进程已经占有的资源
循环等待:存在一个闭合链,每个进程至少占有下一个进程的一个资源。

死锁的解决:银行家算法

(六)进程有哪几种状态?(课本P76)

就绪状态:进程已获得除处理机以外的所需资源,等待分配处理机资源
运行状态:占用处理机资源运行,处于此状态的进程数小于等于CPU数
阻塞状态: 进程等待某种条件,在条件满足之前无法执行
新建状态: 刚刚创建的进程,系统还没有将其加入可执行进程组
退出状态: 操作系统从可执行进程组释放的进程

(七)分页和分段有什么区别?(课本P212)

可见度:段是信息的逻辑单位,它是根据用户的需要划分的,因此段对程序员是可见的 ;页是信息的物理单位,是为了管理主存的方便而划分的,对程序员是透明的。
空间大小:段的大小不固定,有它所完成的功能决定;页大小固定,由系统决定
地址空间:段向用户提供二维地址空间;页向用户提供的是一维地址空间
共享:段是信息的逻辑单位,便于存储保护和信息的共享,页的保护和共享受到限制。

(八) 操作系统中进程调度策略有哪几种? (P264)

FCFS(先来先服务),时间片轮转,SPN,SRT,HRRN,反馈

颠簸 / 抖动

颠簸(thrashing)又称“抖动”,是指页面在内存与外存储器之间频繁地调度,以致系统用于调度页面的时间比进程实际运行所占用的时间还要长。
颠簸是由于页故障率过高而产生的结果,它将严重地影响系统的效率,甚至可能使系统全面崩溃。
通俗来讲就是一个进程的页面放入内存中,被淘汰出去后,进程又需要使用这个页面然后重新把他放入内存的过程。即你把我赶走之后,又叫我回来。

(九)说一说进程同步有哪几种机制。

互斥锁(Mutex) 同上
信号量
管程课本 Chapter 5
消息课本 Chapter 5
、会合、分布式系统、管道、匿名管道、共享内存

以下内容参照课本P138开始
什么是信号量?信号量(semaphore)的数据结构为一个值和一个指针,指针指向等待该信号量的下一个进程。信号量的值与相应资源的使用情况有关。
当它的值大于0时,表示当前可用资源的数量;
当它的值小于0时,其绝对值表示等待使用该资源的进程个数。
强信号量、弱信号量 课本P140/141
什么是PV操作
p操作(wait):申请一个单位资源,进程进入
经典伪代码
wait(S){
while(s<=0) //如果没有资源则会循环等待;
;
S-- ;
}
v操作(signal):释放一个单位资源,进程出来
signal(S){
S++ ;
}

管程:其基本思想是将共享变量和对它们的操作集中在一个模块中,操作系统或并发程序就由这样的模块构成。

锁机制:互斥锁和自旋锁(课本P189)

其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。

但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。

Fork/VFork/Clone (Unix高级编程P182)

fork 创造的子进程复制了父亲进程的资源(写时复制技术),包括内存的内容task_struct内容(2个进程的pid不同)。这里是资源的复制不是指针的复制。
vfork是一个过时的应用,vfork也是创建一个子进程,但是子进程共享父进程的空间。在vfork创建子进程之后,父进程阻塞,直到子进程执行了exec()或者exit()。
clone是Linux为创建线程设计的(虽然也可以用clone创建进程)。所以可以说clone是fork的升级版本,不仅可以创建进程或者线程,还可以指定创建新的命名空间(namespace)、有选择的继承父进程的内存、甚至可以将创建出来的进程变成父进程的兄弟进程

僵尸进程/孤儿进程

在unix/linux中,正常情况下,子进程是通过父进程创建的,子进程在创建新的进程。子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程 到底什么时候结束。 当一个 进程完成它的工作终止之后,它的父进程需要调用wait()或者waitpid()系统调用取得子进程的终止状态
孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作
这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init进程就会代表党和政府出面处理它的一切善后工作。因此孤儿进程并不会有什么危害
僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。
如果进程不调用wait / waitpid的话, 那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。

避免僵尸进程的三种方法

  • 一、让僵尸进程的父进程来回收,父进程每隔一段时间来查询子进程是否结束并回收,调用wait()或者waitpid(),通知内核释放僵尸进程
    如果在调用waitpid()函数时,当指定等待的子进程已经停止运行或结束了,则waitpid()会立即返回;但是如果子进程还没有停止运行或结束,则调用waitpid()函数的父进程则会被阻塞,暂停运行。
  • 二、采用信号 SIGCHLD通知处理,并在信号处理程序中调用wait函数 (Unix高级编程P190)
    子进程退出时向父进程发送SIGCHILD信号,父进程处理SIGCHILD信号。
  • 三、让僵尸进程变成孤儿进程,由init回收,就是让父亲先死(fork两次)

Exec()

系统调用exec是以新的进程去代替原来的进程,但进程的PID保持不变。因此,可以这样认为,exec系统调用并没有创建新的进程,只是替换了原来进程上下文的内容。原进程的代码段,数据段,堆栈段被新的进程所代替。

多进程与多线程的应用

1. 多进程应用场景

nginx主流的工作模式是多进程模式(也支持多线程模型)
几乎所有的web server服务器服务都有多进程的,至少有一个守护进程配合一个worker进程,例如apached,httpd等等以d结尾的进程包括init.d本身就是0级总进程,所有你认知的进程都是它的子进程;
chrome浏览器也是多进程方式。 (原因:①可能存在一些网页不符合编程规范,容易崩溃,采用多进程一个网页崩溃不会影响其他网页;而采用多线程会。②网页之间互相隔离,保证安全,不必担心某个网页中的恶意代码会取得存放在其他网页中的敏感信息。)
redis也可以归类到“多进程单线程”模型(平时工作是单个进程,涉及到耗时操作如持久化或aof重写时会用到多个进程)

2. 多线程应用场景

线程间有数据共享,并且数据是需要修改的(不同任务间需要大量共享数据或频繁通信时)。
提供非均质的服务(有优先级任务处理)事件响应有优先级。
单任务并行计算,在非CPU Bound的场景下提高响应速度,降低时延。
与人有IO交互的应用,良好的用户体验(键盘鼠标的输入,立刻响应)
案例:
桌面软件,响应用户输入的是一个线程,后台程序处理是另外的线程;

Select/Poll/Epoll

select:内核等待多个事件当中的一个或多个发生,之后才唤醒。
poll:与select类似,在处理流设备时可以提供更多信息
epoll:速度快
fd:文件描述符(file descriptor)
(1)select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制带来的性能提升。
(2)select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内部定义的等待队列)。这也能节省不少的开销

共享内存

两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间
1、共享内存允许两个或更多进程共享一个给定的存储区,因为数据不需要再客户进程和服务进程之间复制。所以这是最快的一种ipc。
2、使用共享内存时需要注意:多个进程对共享内存的同步访问
通常用信号量实现对共享内存的同步访问。

说起共享内存,一般来说会让人想起下面一些方法:
1、多线程。线程之间的内存都是共享的。更确切的说,属于同一进程的线程使用的是同一个地址空间,而不是在不同地址空间之间进行内存共享;
2、父子进程间的内存共享。父进程以MAP_SHARED|MAP_ANONYMOUS选项mmap一块匿名内存,fork之后,其子孙进程之间就能共享这块内存。这种共享内存由于受到进程父子关系的限制,一般较少使用;
3、mmap文件。mmap将一个文件或者其它对象映射进内存多个进程mmap到同一个文件,实际上就是大家在共享文件page cache中的内存。不过文件牵涉到磁盘的读写,用来做共享内存显然十分笨重,所以就有了不跟磁盘扯上关系的内存文件,也就是我们这里要讨论的tmpfs和shmem;

时间局限性和空间局部性的意义

A.最近被访问的单元,很可能在不久的将来还要被访问
B.最近被访问的单元,很可能它附近的单元也即将被访问

意义:可以利用缓存加速程序运行

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值