操作系统面试题总结(持续更新~)

以下题目总结于:牛客网

相关总结:

计算机网络面试题总结

数据结构与算法面试题总结

C/C++面试题总结

设计模式面试题总结

数据库面试总结

1、请你说一下进程与线程的概念,以及为什么要有进程线程,其中有什么区别,他们各自又是怎么同步的?

参考答案:

1、基本概念:
进程是对运行时程序的封装,是系统进行资源调度和分配的的基本单位,实现了操作系统的并发;

线程是进程的子任务,是 CPU调度和分派的基本单位,用于保证程序的实时性,实现进程内部的并发;

线程是操作系统可识别的最小执行和调度单位。每个线程都独自占用一个虚拟处理器:独自的寄存器组,指令计数器和处理器状态。每个线程完成不同的任务,但是共享同一地址空间(也就是同样的动态内存,映射文件,目标代码等等),打开的文件队列和其他内核资源。

2、区别:
① 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程依赖于进程而存在。

② 进程在执行过程中拥有独立的内存单元,而多个线程共享进程的内存。(资源分配给进程,同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。)

③ 进程是资源分配的最小单位,线程是 CPU调度的最小单位;

④ 系统开销: 由于在创建或撤消进程时,系统都要为之分配或回收资源,如内存空间、I/o 设备等。因此,操作系统所付出的开销将显著地大于在创建或撤消线程时的开销。类似地,在进行进程切换时,涉及到整个当前进程 CPU环境的保存以及新被调度运行的进程的 CPU环境的设置。而线程切换只须保存和设置少量寄存器的内容,并不涉及存储器管理方面的操作。可见,进程切换的开销也远大于线程切换的开销。

⑤ 通信:由于同一进程中的多个线程具有相同的地址空间,致使它们之间的同步和通信的实现,也变得比较容易。进程间通信 IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。有的系统中,线程的切换、同步和通信都无须操作系统内核的干预。

⑥ 进程编程调试简单可靠性高,但是创建销毁开销大;线程正相反,开销小,切换速度快,但是编程调试相对复杂。

⑦ 进程间不会相互影响 ;线程一个线程挂掉将导致整个进程挂掉。

⑧ 进程适应于多核、多机分布;线程适用于多核。

3、进程间通信的方式(IPC):

进程间通信主要包括管道、消息队列、信号量、信号、共享内存以及套接字 socket。
(1) 管道:
管道主要包括无名管道和命名管道。管道可用于具有亲缘关系的父子进程间的通信,有名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。

① 普通管道 PIPE:

1)它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。

2)它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。

3)它可以看成是一种特殊的文件,对于它的读写也可以使用普通的 read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。

② 命名管道 FIFO

1)FIFO 可以在无关的进程之间交换数据。

2)FIFO 有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。

(2) 系统 IPC:
① 消息队列
消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列 ID)来标记。

(消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等特点)

具有写权限的进程可以按照一定的规则向消息队列中添加新信息;有读权限的进程则可以从消息队列中读取信息;

特点:

1)消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。

2)消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。

3)消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。

② 信号量 semaphore
信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器,可以用来控制多个进程对共享资源的访问。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。

特点:

1)信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。

2)信号量基于操作系统的 P、V 操作,程序对信号量的操作都是原子操作。

3)每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。

4)支持信号量组。

③ 信号 signal
信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

④ 共享内存(Shared Memory)
它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据得更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。

特点:

1)共享内存是最快的一种 IPC,因为进程是直接对内存进行存取
2)因为多个进程可以同时操作,所以需要进行同步
3)信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问
(3) 套接字 SOCKET

socket 也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同主机之间的进程通信。

4、线程间通信的方式:

临界区:通过多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问;

互斥量:Synchronized/Lock:采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问

信号量 Semphare:为控制具有有限数量的用户资源而设计的,它允许多个线程在同一时刻去访问同一个资源,但一般需要限制同一时刻访问此资源的最大线程数目。

事件(信号),Wait/Notify:通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作。

 

2、请你说一说 linux 虚拟内存

参考答案:

为了防止不同进程同一时刻在物理内存中运行而对物理内存的争夺和践踏,采用了虚拟内存。

虚拟内存技术使得不同进程在运行过程中,它所看到的是自己独自占有了当前系统的 4G 内存。所有进程共享同一物理内存,每个进程只把自己目前需要的虚拟内存空间映射并存储到物理内存上。 事实上,在每个进程创建加载时,内核只是为进程“创建”了虚拟内存的布局,具体就是初始化进程控制表中内存相关的链表,实际上并不立即就把虚拟内存对应位置的程序数据和代码(比如.text .data 段)拷贝到物理内存中,只是建立好虚拟内存和磁盘文件之间的映射就好(叫做存储器映射),等到运行到对应的程序时,才会通过缺页异常,来拷贝数据。还有进程运行过程中,要动态分配内存,比如 malloc 时,也只是分配了虚拟内存,即为这块虚拟内存对应的页表项做相应设置,当进程真正访问到此数据时,才引发缺页异常。

请求分页系统、请求分段系统和请求段页式系统都是针对虚拟内存的,通过请求实现内存与外存的信息置换。

虚拟内存的好处:

  1. 扩大地址空间;
  2. 内存保护:每个进程运行在各自的虚拟内存地址空间,互相不能干扰对方。虚存还对特定的内存地址提供写保护,可以防止代码或数据被恶意篡改。
  3. 公平内存分配。采用了虚存之后,每个进程都相当于有同样大小的虚存空间。
  4. 当进程通信时,可采用虚存共享的方式实现。
  5. 当不同的进程使用同样的代码时,比如库文件中的代码,物理内存中可以只存储一份这样的代码,不同的进程只需要把自己的虚拟内存映射过去就可以了,节省内存
  6. 虚拟内存很适合在多道程序设计系统中使用,许多程序的片段同时保存在内存中。当一个程序等待它的一部分读入内存时,可以把 CPU 交给另一个进程使用。在内存中可以保留多个进程,系统并发度提高。
  7. 在程序需要分配连续的内存空间的时候,只需要在虚拟内存空间分配连续空间,而不需要实际物理内存的连续空间,可以利用碎片。

内存的代价:

  1. 虚存的管理需要建立很多数据结构,这些数据结构要占用额外的内存
  2. 虚拟地址到物理地址的转换,增加了指令的执行时间。
  3. 页面的换入换出需要磁盘 I/O,这是很耗时的
  4. 如果一页中只有一部分数据,会浪费内存。

 

3、请你说一下操作系统中程序的内存结构

参考答案:

    一个程序本质上都是由 BSS 段、data 段、text 段三个组成的。可以看到一个可执行程序在存储(没有调入内存)时分为代码段数据区未初始化数据区三部分。

  • BSS 段(未初始化数据区):通常用来存放程序中未初始化的全局变量和静态变量的一块内存区域。BSS 段属于静态分配,程序结束后静态变量资源由系统自动释放。
  • 数据段:存放程序中已初始化的全局变量的一块内存区域。数据段也属于静态内存分配。
  • 代码段:存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域属于只读。在代码段中,也有可能包含一些只读的常数变量。

text 段和 data 段在编译时已经分配了空间,而 BSS 段并不占用可执行文件的大小,它是由链接器来获取内存的。

bss 段(未进行初始化的数据)的内容并不存放在磁盘上的程序文件中。其原因是内核在程序开始运行前将它们设置为 0。需要存放在程序文件中的只有正文段和初始化数据段。

data 段(已经初始化的数据)则为数据分配空间,数据保存到目标文件(.obj)中。
数据段包含经过初始化的全局变量以及它们的值。BSS 段的大小从可执行文件中得到,然后链接器得到这个大小的内存块,紧跟在数据段的后面。当这个内存进入程序的地址空间后全部清零。包含数据段和 BSS 段的整个区段此时通常称为数据区。

可执行程序在运行时又多出两个区域:栈区和堆区。

栈区:由编译器自动释放,存放函数的参数值、局部变量等。每当一个函数被调用时,该函数的返回类型和一些调用的信息被存放到栈中。然后这个被调用的函数再为他的自动变量和临时变量在栈上分配空间。每调用一个函数一个新的栈就会被使用。栈区是从高地址位向低地址位增长的,是一块连续的内存区域,最大容量是由系统预先定义好的,申请的栈空间超过这个界限时会提示溢出,用户能从栈中获取的空间较小。

堆区:用于动态分配内存,位于 BSS 和栈中间的地址区域。由程序员申请分配和释放。堆是从低地址位向高地址位增长,采用链式存储结构。频繁的 malloc/free 造成内存空间的不连续,产生碎片。当申请堆空间时库函数是按照一定的算法搜索可用的足够大的空间。因此堆的效率比栈要低的多。

 

4、请你说一说操作系统中的缺页中断

参考答案:

缺页中断什么时候会触发?

    malloc等内存分配函数,在分配时只是建立了进程虚拟地址空间,并没有分配虚拟内存对应的物理内存。当进程访问这些没有建立映射关系的虚拟内存时,处理器自动触发一个缺页异常。

缺页中断:在请求分页系统中,可以通过查询页表中的状态位来确定所要访问的页面是否存在于内存中。每当所要访问的页面不在内存是,会产生一次缺页中断,此时操作系统会根据页表中的外存地址在外存中找到所缺的一页,将其调入内存。

缺页本身是一种中断,与一般的中断一样,需要经过 4 个处理步骤:

1、保护 CPU 现场。
2、分析中断原因。
3、转入缺页中断处理程序进行处理。
4、恢复 CPU 现场,继续执行。

但是缺页中断是由于所要访问的页面不存在于内存时,由硬件所产生的一种特殊的中断,因此,与一般的中断存在区别:

1、在指令执行期间产生和处理缺页中断信号。
2、一条指令在执行期间,可能产生多次缺页中断。
3、缺页中断返回是,执行产生中断的一条指令,而一般的中断返回是,执行下一条指令。

 

5、请你回答一下 fork 和 和 vfork  的区别

参考答案:

fork 和 vfork 的区别:
1. fork( )的子进程拷贝父进程的数据段和代码段;vfork( )的子进程与父进程共享数据段

2. fork( )的父子进程的执行次序不确定;vfork( )保证子进程先运行,在调用 exec 或 exit 之前与父进程数据是共享的,在它调用 exec 或 exit 之后父进程才可能被调度运行。

3. vfork( )保证子进程先运行,在它调用 exec 或 exit 之后父进程才可能被调度运行。如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。

4.当需要改变共享数据段中变量的值,则拷贝父进程。

补充:

① fork 的基础知识:
fork:创建一个和当前进程映像一样的进程可以通过 fork( )系统调用:
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
成功调用 fork( )会创建一个新的进程,它几乎与调用 fork( )的进程一模一样,这两个进程都会继续运行。在子进程中,成功的 fork( )调用会返回 0。在父进程中 fork( )返回子进程的 pid。如果出现错误,fork( )返回一个负值。

最常见的 fork( )用法是创建一个新的进程,然后使用 exec( )载入二进制映像,替换当前进程的映像。这种情况下,派生(fork)了新的进程,而这个子进程会执行一个新的二进制可执行文件的映像。这种“派生加执行”的方式是很常见的。

在早期的 Unix 系统中,创建进程比较原始。当调用 fork 时,内核会把所有的内部数据结构复制一份,复制进程的页表项,然后把父进程的地址空间中的内容逐页的复制到子进程的地址空间中。但从内核角度来说,逐页的复制方式是十分耗时的。现代的 Unix 系统采取了更多的优化,例如 Linux,采用了写时复制的方法,而不是对父进程空间进程整体复制。

② vfork 的基础知识:

在实现写时复制之前,Unix 的设计者们就一直很关注在 fork 后立刻执行 exec 所造成的地址空间的浪费。BSD的开发者们在 3.0 的 BSD系统中引入了 vfork( )系统调用。
#include <sys/types.h>
#include <unistd.h>
pid_t vfork(void);

除了子进程必须要立刻执行一次对 exec 的系统调用,或者调用_exit( )退出,对 vfork( )的成功调用所产生的结果和 fork( )是一样的。vfork( )会挂起父进程直到子进程终止或者运行了一个新的可执行文件的映像。通过这样的方式,vfork( )避免了地址空间的按页复制。在这个过程中,父进程和子进程共享相同的地址空间和页表项。实际上 vfork( )只完成了一件事:复制内部的内核数据结构。因此,子进程也就不能修改地址空间中的任何内存。

vfork( )是一个历史遗留产物,Linux 本不应该实现它。需要注意的是,即使增加了写时复制,vfork( )也要比fork( )快,因为它没有进行页表项的复制。然而,写时复制的出现减少了对于替换 fork( )争论。实际上,直到2.2.0 内核,vfork( )只是一个封装过的 fork( )。因为对 vfork( )的需求要小于 fork( ),所以 vfork( )的这种实现方式是可行的。

③ 写时复制

Linux 采用了写时复制的方法,以减少 fork 时对父进程空间进程整体复制带来的开销。

写时复制是一种采取了惰性优化方法来避免复制时的系统开销。它的前提很简单:如果有多个进程要读取它们自己的那部门资源的副本,那么复制是不必要的。每个进程只要保存一个指向这个资源的指针就可以了。只要没有进程要去修改自己的“副本”,就存在着这样的幻觉:每个进程好像独占那个资源。从而就避免了复制带来的负担。如果一个进程要修改自己的那份资源“副本”,那么就会复制那份资源,并把复制的那份提供给进程。不过其中的复制对进程来说是透明的。这个进程就可以修改复制后的资源了,同时其他的进程仍然共享那份没有修改过的资源。所以这就是名称的由来:在写入时进行复制。

写时复制的主要好处在于:如果进程从来就不需要修改资源,则不需要进行复制。惰性算法的好处就在于它们尽量推迟代价高昂的操作,直到必要的时刻才会去执行。

在使用虚拟内存的情况下,写时复制(Copy-On-Write)是以页为基础进行的。所以,只要进程不修改它全部的地址空间,那么就不必复制整个地址空间。在 fork( )调用结束后,父进程和子进程都相信它们有一个自己的地址空间,但实际上它们共享父进程的原始页,接下来这些页又可以被其他的父进程或子进程共享。

写时复制在内核中的实现非常简单。与内核页相关的数据结构可以被标记为只读和写时复制。如果有进程试图修改一个页,就会产生一个缺页中断。内核处理缺页中断的方式就是对该页进行一次透明复制。这时会清除页面的 COW 属性,表示着它不再被共享。现代的计算机系统结构中都在内存管理单元(MMU)提供了硬件级别的写时复制支持,所以实现是很容易的。

在调用 fork( )时,写时复制是有很大优势的。因为大量的 fork 之后都会跟着执行 exec,那么复制整个父进程地址空间中的内容到子进程的地址空间完全是在浪费时间:如果子进程立刻执行一个新的二进制可执行文件的映像,它先前的地址空间就会被交换出去。写时复制可以对这种情况进行优化。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值