3-面试篇-操作系统
操作系统篇
1、OS
1. 操作系统的四个特性
- 并发:同一段时间内多个程序执行(注意区别并行和并发,前者是同一时刻的多个事件,后者是同一时间段内的多个事件)
- 共享:系统中的资源可以被内存中多个并发执行的进线程共同使用
- 虚拟:通过时分复用(如分时系统)以及空分复用(如虚拟内存)技术实现把一个物理实体虚拟为多个
- 异步:系统中的进程是以走走停停的方式执行的,且以一种不可预知的速度推进
2.操作系统的主要功能
1. 处理机管理:处理机分配都是以进程为单位,所以处理机管理也被看做是进程管理。包括进程控制,进程同步,进程通信和进程调度
2. 存储器管理(或者内存管理):内存分配,内存保护,地址映射,内存扩充
3. 设备管理:管理所有外围设备,包括完成用户的IO请求;为用户进程分配IO设备;提高IO设备利用率;提高IO速度;方便IO的使用
4. 文件管理:管理用户文件和系统文件,方便使用同时保证安全性。包括:磁盘存储空间管理,目录管理,文件读写管理以及文件共享和保护提供用户接口:程序接口(如API)和用户接口(如GUI)
3.异步和同步,串行,并发,并行的区别
-
同步:多个任务情况下,一个
任务A
执行结束,才可以执行另一个任务B
。只存在一个线程。 -
异步:多个任务情况下,一个
任务A
正在执行,同时可以执行另一个任务B
。任务B
不用等待任务A
结束才执行。存在多条线程
。 -
并行: 真正的异步,
多核CUP
可以同时开启多条线程供多个任务同时执行,互补干扰,如上图的并行,其实和异步图例一样。 -
并发: 是一个伪异步。在
单核CUP
中只能有一条线程,但是又想执行多个任务。这个时候,只能在一条线程上不停的切换任务,比如任务A
执行了20%
,任务A
停下来,线程让给任务B
,任务执行了30%
停下,再让任务A
执行。这样我们用的时候,由于CUP
处理速度快,你看起来好像是同时执行,其实不是的,同一时间只会执行单个任务。 -
串行:是同步线程的实现方式,就是
任务A
执行结束才能开始执行B
,单个线程只能执行一个任务,就如单行道只能行驶一辆车。
4.用户态和核心态的区别。
当一个任务(进程)执行系统调用而陷入内核代码中执行时,我们就称进程处于内核运行态(或简称为内核态)。
此时处理器处于特权级最高的(0级)内核代码中执行。
当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。
当进程在执行用户自己的代码时,则称其处于用户运行态(用户态)。
即此时处理器在特权级最低的(3级)用户代码中运行。
特权级显然是非常有效的管理和控制程序执行的手段
Intel x86架构的CPU来说一共有0~3四个特权级,0级最高,3级最低,
虽然用户态下和内核态下工作的程序有很多差别,但最重要的差别就在于特权级的不同,即权力的不同。运行在用户态下的程序不能直接访问操作系统内核数据结构和程序。
当我们在系统中执行一个程序时,大部分时间是运行在用户态下的,在其需要操作系统帮助完成某些它没有权力和能力完成的工作时就会切换到内核态。
1)用户态切换到内核态的3种方式
a. 系统调用
这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作,而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现,例如Linux的int 80h中断。
b. 异常
当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。
c. 外围设备的中断
当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。
内核态--->用户态:设置程序状态字PSW
5、什么是缓冲区溢出?有什么危害?其原因是什么?
缓冲区溢出是指当计算机向缓冲区填充数据时超出了缓冲区本身的容量,溢出的数据覆盖在合法数据上。
危害有以下两点:
- 程序崩溃,导致拒绝额服务
- 跳转并且执行一段恶意代码
造成缓冲区溢出的主要原因是程序中没有仔细检查用户输入。
6.进程调度算法
- FCFS(先来先服务,队列实现,非抢占的):先请求CPU的进程先分配到CPU
- SJF(最短作业优先调度算法):平均等待时间最短,但难以知道下一个CPU区间长度
- 优先级调度算法(可以是抢占的,也可以是非抢占的):优先级越高越先分配到CPU,相同优先级先到先服务,存在的主要问题是:低优先级进程无穷等待CPU,会导致无穷阻塞或饥饿;解决方案:老化
- 时间片轮转调度算法(可抢占的):队列中没有进程被分配超过一个时间片的CPU时间,除非它是唯一可运行的进程。如果进程的CPU区间超过了一个时间片,那么该进程就被抢占并放回就绪队列。
- 多级队列调度算法:将就绪队列分成多个独立的队列,每个队列都有自己的调度算法,队列之间采用固定优先级抢占调度。其中,一个进程根据自身属性被永久地分配到一个队列中。
- 多级反馈队列调度算法:与多级队列调度算法相比,其允许进程在队列之间移动:若进程使用过多CPU时间,那么它会被转移到更低的优先级队列;在较低优先级队列等待时间过长的进程会被转移到更高优先级队列,以防止饥饿发生。
7.分页和分段有什么区别?
7、分页和分段有什么区别(内存管理)?
段式存储管理是一种符合用户视角的内存分配管理方案。在段式存储管理中,将程序的地址空间划分为若干段(segment),如代码段,数据段,堆栈段;这样每个进程有一个二维地址空间,相互独立,互不干扰。段式管理的优点是:没有内碎片(因为段大小可变,改变段大小来消除内碎片)。但段换入换出时,会产生外碎片(比如4k的段换5k的段,会产生1k的外碎片)
页式存储管理方案是一种用户视角内存与物理内存相分离的内存分配管理方案。在页式存储管理中,将程序的逻辑地址划分为固定大小的页(page),而物理内存划分为同样大小的帧,程序加载时,可以将任意一页放入内存中任意一个帧,这些帧不必连续,从而实现了离散分离。页式存储管理的优点是:没有外碎片(因为页的大小固定),但会产生内碎片(一个页可能填充不满)。
两者的不同点:
- 目的不同:分页是由于系统管理的需要而不是用户的需要,它是信息的物理单位;分段的目的是为了能更好地满足用户的需要,它是信息的逻辑单位,它含有一组其意义相对完整的信息;
- 大小不同:页的大小固定且由系统决定,而段的长度却不固定,由其所完成的功能决定;
- 地址空间不同: 段向用户提供二维地址空间;页向用户提供的是一维地址空间;
- 信息共享:段是信息的逻辑单位,便于存储保护和信息的共享,页的保护和共享受到限制;
- 内存碎片:页式存储管理的优点是没有外碎片(因为页的大小固定),但会产生内碎片(一个页可能填充不满);而段式管理的优点是没有内碎片(因为段大小可变,改变段大小来消除内碎片)。但段换入换出时,会产生外碎片(比如4k的段换5k的段,会产生1k的外碎片)。
8、源代码是怎么变成可执行文件的,每一步的作用是什么?
预编译,词法分析,语法分析,语义分析,中间语言生成目标代码生成,汇编,链接
解答:
源代码变成可执行文件需要经历 4
个大步骤:
预处理——>编译——>汇编——>链接
1. 预处理:
由“预编译器”
负责,最终.c
文件经过预编译,变为.i
文件。
2. 编译
由编译器负责,主要又由词法分析、语法分析、语义分析、优化
和生成汇编代码
五个部分:
3. 汇编
汇编器
将汇编语言转换成机器可以执行的语言(完全由0
和1
组成).汇编文件经过汇编,变成目标文件,后缀为.o
。
4.链接 通过调用链接器ld来将多个目标文件以及所依赖的其它库文件链接起来,最后生成可执行文件。
9、 页面置换算法
- FIFO先进先出算法:在操作系统中经常被用到,比如作业调度(主要实现简单,很容易想到);
- LRU(Least recently use)最近最少使用算法:根据使用时间到现在的长短来判断;
- LFU(Least frequently use)最少使用次数算法:根据使用次数来判断;
- OPT(Optimal replacement)最优置换算法:理论的最优,理论;就是要保证置换出去的是不再被使用的页,或者是在实际内存中最晚使用的算法。
最佳置换算法:只具有理论意义的算法,用来评价其他页面置换算法。置换策略是将当前页面中在未来最长时间内不会被访问的页置换出去。
先进先出置换算法:简单粗暴的一种置换算法,没有考虑页面访问频率信息。每次淘汰最早调入的页面。
最近最久未使用算法LRU:算法赋予每个页面一个访问字段,用来记录上次页面被访问到现在所经历的时间t,每次置换的时候把t值最大的页面置换出去(实现方面可以采用寄存器或者栈的方式实现)。
时钟算法clock(也被称为是最近未使用算法NRU):页面设置一个访问位,并将页面链接为一个环形队列,页面被访问的时候访问位设置为1。页面置换的时候,如果当前指针所指页面访问为为0,那么置换,否则将其置为0,循环直到遇到一个访问为位0的页面。
改进型Clock算法:在Clock算法的基础上添加一个修改位,替换时根究访问位和修改位综合判断。优先替换访问位和修改位都是0的页面,其次是访问位为0修改位为1的页面。
最少使用算法LFU:设置寄存器记录页面被访问次数,每次置换的时候置换当前访问次数最少的。
10、虚拟内存
虚拟内存也是一种缓存思想
虚拟内存将主存看成是一个磁盘的高速缓存,主存中只保存活动区域,并根据需要在磁盘和主存之间来回传送数据。
Windows操作系统用虚拟内存来动态管理运行时的交换文件。为了提供比实际物理内存还多的内存容量以供使用,Windows操作系统占用了硬盘上的 一部分空间作为虚拟内存。当CPU有要求时,首先会读取内存中的资料。当内存容量不够用时,Windows就会将需要暂时储存的数据写入硬盘。所以,计算机的内存大小等于实际物理内存容量加上“分页文件”(就是交换文件)的大小。如果需要的话,“分页文件”会动用硬盘上所有可以使用的空间。
虚拟内存的应用与优点
虚拟内存很适合在多道程序设计系统中使用,许多程序的片段同时保存在内存中。当一个程序等待它的一部分读入内存时,可以把CPU交给另一个进程使用。虚拟内存的使用可以带来以下好处:
1、在内存中保留多个进程。由于对任何特定的进程都仅仅装入它的某些块,因此就有足够的空间来放置更多的进程。
2、进程可以比内存的全部空间还大。程序占用的内存空间的大小是程序设计中最大的限制之一。通过基于分页或分段的虚拟内存,这些分块可以按某种覆盖策略分别加载。
虚拟内存技术的实现
虚拟内存中,允许将一个作业分多次调入内存。釆用连续分配方式时,会使相当一部分内存空间都处于暂时或“永久”的空闲状态,造成内存资源的严重浪费,而且也无法从逻辑上扩大内存容量。因此,虚拟内存的实需要建立在离散分配的内存管理方式的基础上。虚拟内存的实现有以下三种方式:
- 请求分页存储管理。
- 请求分段存储管理。
- 请求段页式存储管理。
11.内存连续分配
主要是指动态分区分配时所采用的几种算法。
动态分区分配又称为可变分区分配,是一种动态划分内存的分区方法。这种分区方法不预先将内存划分,而是在进程装入内存时,根据进程的大小动态地建立分区,并使分区的大小正好适合进程的需要。因此系统中分区的大小和数目是可变的。
首次适应(First Fit)算法:空闲分区以地址递增的次序链接。分配内存时顺序查找,找到大小能满足要求的第一个空闲分区。
循环首次适应算法。该算法是由首次适应算法演变而成的。在为进程分配内存空间时,不再每次从链首开始查找,而是从上次找到的空闲分区开始查找,直至找到一个能满足需求的空闲分区,并从中划出一块来分给作业。该算法能使空闲中的内存分区分布得更加均匀,缺点是将会缺乏大的空闲分区。
最佳适应(Best Fit)算法:空闲分区按容量递增形成分区链,找到第一个能满足要求的空闲分区。
最坏适应(Worst Fit)算法:又称最大适应(Largest Fit)算法,空闲分区以容量递减的次序链接。找到第一个能满足要求的空闲分区,也就是挑选出最大的分区。
2、进程与线程
1、进程线程,区别
程序仅仅只是一堆代码而已,而进程指的是程序的运行过程。
- 进程是对运行时程序的封装,是系统进行资源调度和分配的的基本单位,实现了操作系统的并发;
- 线程是进程的实体,是CPU调度和分派的基本单位,用于保证程序的 实时性,实现进程内部的并发;
在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程
进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合)
线程才是cpu上的执行单位。
联系
- 一个进程可以由多个线程或单个线程组成
- 线程与同属一个进程的其他的线程共享进程所拥有的全部资源。
- 二者均可并发执行。
区别:
- 地址空间:
进程
拥有独立的地址空间
;线程
共享本进程的地址空间
。 - 资源拥有:
进程
是拥有系统资源
的一个独立单位
,而线程自己基本上不拥有系统资源
,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈
),和其他线程共享本进程
的相关资源如内存、I/O、cpu
等。 - 独立性: 一个进程崩溃后,在
保护模式
下不会对其他进程产生影响,但是一个线程
崩溃整个进程
都死掉。所以多进程
要比多线程
健壮。 - 系统开销: 在进程切换时,涉及到整个当前进程
CPU环境
的保存环境的设置以及新被调度运行的CPU环境
的设置,而线程切换
只需保存和设置少量的寄存器
的内容,并不涉及存储器管理
方面的操作,可见,进程切换
的开销也远大于线程切换
的开销。 - 执行过程: 每个独立的
线程
有一个程序运行
的入口、顺序执行序列
和程序
的出口。但是线程
不能够独立执行,必须依存在应用程序中,由应用程序
提供多个线程
执行控制。
2、进程通信的几种方式。
进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。
主要分为:管道、系统IPC(包括消息队列、信号量、共享内存、信号),套接字( socket )、
远程过程调用:RPC等。
其中 套接字( socket )
和远程过程调用:RPC
支持不同主机上的两个进程IPC
。
管道主要分为:普通管道PIPE 、流管道(s_pipe)、命名管道(name_pipe)
- 管道是一种半双工的通信方式,数据只能单项流动,并且只能在具有亲缘关系的进程间流动,进程的亲缘关系通常是父子进程
- 命名管道也是半双工的通信方式,它允许无亲缘关系的进程间进行通信
- 信号量是一个计数器,用来控制多个进程对资源的访问,它通常作为一种锁机制。
- 消息队列是消息的链表,存放在内核中并由消息队列标识符标识。
- 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
- 共享内存就是映射一段能被其它进程访问的内存,这段共享内存由一个进程创建,但是多个进程可以访问。
3、进程/线程的五状态模型:
运行态:该进程正在执行。
就绪态:进程已经做好了准备,只要有机会就开始执行。
阻塞态(等待态):进程在某些事情发生前不能执行,等待阻塞进程的事件完成。
新建态:刚刚创建的进程,操作系统还没有把它加入到可执行进程组中,通常是进程控制块已经创建但是还没有加载到内存中的进程。
退出态:操作系统从可执行进程组中释放出的进程,或由于自身或某种原因停止运行。
4、进程/线程间同步机制。
临界区、互斥区、事件、信号量四种方式
临界区(Critical Section)、互斥量(Mutex)、信号量(Semaphore)、事件(Event)的区别
-
临界区:Critical Section
用于线程间同步,多个线程同时访问一个资源时,首先进入这个临界区的获取使用权,其它排队,知道当前使用的线程释放临界区 -
互斥量:Mutex
可以用于进程和线程间同步,功能与临界区类似 -
信号量:Semaphores
可用于进程和线程间同步,它与临界区与互斥量的不同在于可以对资源的控制不是独享,而是可以同时使用的,它控制的是数目。
就像经典的生产者消费者模型
生产者在放货前需要知道还有多少个货物能往里放。
消费者在拿货前需要知道有多少个货物能往外拿。
生产者往里放货的时候要更新可以放货的数量以及可以拿货的数量。
消费者拿货的时候也要更新这两个数量。 -
事件:Event
事件是用来在进程和线程间同步的。就像我们人之间的交流。我把一件事做完了,告诉别人,别人收到这个消息才能接着往下做。或者他还要等第三个人把事情做完才能开 始做他的事。
3.三个经典同步问题
前面我们讲到信号量机制,下面我们讲解利用信号量及PV操作解决几个经典同步问题。
a.生产者-消费者问题
b.作者读者问题
c.哲学家进餐问题
什么是生产者和消费者模式
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
这个阻塞队列就是用来给生产者和消费者解耦的
5、线程同步方式
- 互斥量:采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问。
- 信号量:它允许同一时刻多个线程访问同一资源,但是需要控制同一时刻访问此资源的最大线程数量。
- 事件(信号):通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作。
经两位大大的指正,现已删除临界区。
6、线程的实现方式. (也就是用户线程与内核线程的区别)
线程的实现可分为两大类,用户级线程(user-levelthread,ULT)和内核级线程(kernel-levelthread,KLT)。后者又称为内核支持的线程或轻量级进程。
7、多并发任务,仅多线程能加快速度么(不能,会变慢,有线程切换的开销)
多并发任务中,CPU
只有一个,因此分配给进程的CPU资源
是一定的,多线程
只不过是轮流抢占CPU资源
而已,并不会真正提高处理速度,并且由于线程之间的切换
需要一定的开销,因此也会浪费一定的切换时间,这会导致一个任务
采用一个拥有两个线程的进程
执行所需要的时间比一个线程的进程
执行两次所需要的时间要多一些。但多线程
的作用主要在于提高了并发数量
,比如http请求
,如果是单线程
,一次只能接收一个请求,多线程
则可以同时接收多个请求。
8. 多个线程之间可以共享那些数据
- 进程代码段
- 进程的
公有数据
(利用这些共享的数据,线程很容易的实现相互之间的通讯)、 - 进程打开的
文件描述符
- 信号的处理器
- 进程的当前目录
进程用户ID
与进程组ID
。
9、线程实现方式
1、通过继承Thread类创建线程
2、通过实现Runnable接口创建线程
3.通过Callable和Future创建线程
10. 介绍几种锁,他们的用途和区别
- 互斥锁:
用于保护临界区
,确保同一时间只有一个线程访问数据。对共享资源
的访问,先对互斥量
进行加锁,如果互斥量
已经上锁,调用线程会阻塞,直到互斥量
被解锁。在完成了对共享资源
的访问后,要对互斥量
进行解锁。 - 临界区:
每个进程中访问临界资源
的那段程序称为临界区
,每次只允许一个进程进入临界区
,进入后不允许其他进程进入。 - 自旋锁:
与互斥量
类似,它不是通过休眠使进程阻塞,而是在获取锁之前一直处于循环检测保持者是否已经释放了锁(自旋)的状态
。用在以下情况:锁持有的时间短,而且线程并不希望在重新调度上花太多的成本。自旋锁
与互斥锁
的区别:线程在申请自旋锁
的时候,线程不会被挂起,而是处于忙等的状态。 - 信号量:
信号量
是一个计数器,可以用来控制多个进程对共享资源
的访问。它常作为一种锁机制
,防止某进程正在访问共享资源
时,其他进程也访问该资源。因此,主要作为进程间
以及同一进程内
不同线程之间的同步手段
。 - 读写锁(rwlock):
高级别锁
,区分读和写,符合条件时允许多个线程
访问对象。处于读锁操作
时可以允许其他线程
和本线程
的读锁
, 但不允许写锁
, 处于写锁
时则任何锁操作
都会睡眠等待;常见的操作系统
会在写锁
等待时屏蔽后续的读锁操作
以防写锁
被无限孤立而等待,在操作系统
不支持情况下可以用引用计数
加写优先等待来用互斥锁
实现。读写锁
适用于大量读少量写
的环境,但由于其特殊的逻辑使得其效率相对普通的互斥锁
和自旋锁
要慢一个数量级;值得注意的一点是按POSIX标准
在线程
申请读锁
并未释放前本线程
申请写锁是成功的,但运行后的逻辑结果是
无法预测` - 递归锁(recursivelock):
严格上讲递归锁
只是互斥锁
的一个特例,同样只能有一个线程访问该对象,但允许同一个线程在未释放其拥有的锁时反复对该锁进行加锁操作;windows
下的临界区默认是支持递归锁的,而linux
下的互斥量则需要设置参数PTHREAD_MUTEX_RECURSIVE_NP
,默认则是不支持
11.死锁
死锁是指多个进程在运行过程中,因为争夺资源而造成的一种僵局,
如果没有外力推进,处于僵局中的进程就无法继续执行。
死锁原因:
- 竞争资源:请求同一有限资源的进程数多于可用资源数
- 进程推进顺序非法:进程执行中,请求和释放资源顺序不合理,如资源等待链
导致死锁的4个必要条件:
1)互斥。一次只有一个进程可以使用一个资源。其他进程不能访问已分配给其他进程的资源。
2)占有且等待。当一个进程等待其他进程时,继续占有已经分配的资源。
3)不可抢占。不能强行抢占进程已占有的资源。
4)循环等待。存在一个封闭的进程链,使得每个进程至少占有此链中下一个进程所需要的一个资源。
死锁处理:
- 预防死锁:破坏产生死锁的4个必要条件中的一个或者多个;实现起来比较简单,但是如果限制过于严格会降低系统资源利用率以及吞吐量
- 避免死锁:在资源的动态分配中,防止系统进入不安全状态(可能产生死锁的状态)-如银行家算法
- 检测死锁:允许系统运行过程中产生死锁,在死锁发生之后,采用一定的算法进行检测,并确定与死锁相关的资源和进程,采取相关方法清除检测到的死锁。实现难度大
- 解除死锁:与死锁检测配合,将系统从死锁中解脱出来(撤销进程或者剥夺资源)。对检测到的和死锁相关的进程以及资源,通过撤销或者挂起的方式,释放一些资源并将其分配给处于阻塞状态的进程,使其转变为就绪态。实现难度大