操作系统面试

进程和线程

进程和线程的关系

  • 进程是 代码 在 数据集合上的一次运行活动,是系统进行 资源分配和调度的基本单位

  • 线程 是 进程中的一个实体,一个进程中至少有一个线程,进程中的 多个线程共享进程的资源

  • 操作系统在分配资源时是把资源分配给进程的,但CPU资源是被分配到线程的,即线程是CPU分配的基本单位

    进程和线程的比较

  • 进程 是资源分配的单位,线程 是 CPU 调度的单位

  • 进程 拥有一个完整的资源平台,线程 只独享必不可少的资源,如 寄存器和栈

  • 线程 同样具有 就绪、阻塞、执行 三种基本状态,同样具有状态之前的转换关系

  • 线程 能减少并发执行的时间和空间开销

线程相比进程能减少开销

  • 线程的创建时间比进程快,因为进程在创建的过程中,还需要资源管理信息,比如内存管理信息、文件管理信息。线程在创建的过程中,不会涉及这些资源管理信息,而是 共享它们
  • 线程的终止时间比进程快,因为线程释放的资源相比进程少很多
  • 同⼀个进程内的线程切换比进程切换快,因为线程具有相同的地址空间(虚拟内存共享),这意味着同⼀个进程的线程都具有同⼀个页表,那么在切换的时候不需要切换页表。而对于进程之间的切换,切换的时候要把页表给切换掉,而页表的切换过程开销比较大
  • 由于同⼀进程的 各线程间共享内存和文件资源,那么在线程之间数据传递的时候,就不需要经过内核了,这就使得线程之间的数据交互效率更⾼了

不管是时间效率,还是空间效率线程⽐进程都要⾼

进程控制块

  • 在操作系统中,⽤ 进程控制块(PCB) 数据结构来描述进程的

  • PCB 是进程存在的唯⼀标识

PCB具体包含什么信息

进程描述信息

  • 进程标识符:标识各个进程,每个进程都有一个并且唯一的标识符

  • 用户标识符:进程归属的⽤户,⽤户标识符主要为共享和保护服务

进程控制和管理信息

  • 进程当前状态,如 new、ready、running、waiting 或 blocked 等

  • 进程优先级:进程抢占 CPU 时的优先级

资源分配清单

  • 有关内存地址空间或虚拟地址空间的信息,所打开文件的列表和所使⽤的 I/O 设备信

CPU 相关信息

  • CPU 中各个寄存器的值,当进程被切换时,CPU 的状态信息都会被保存在相应的 PCB 中,以便进程重新执行时,能从断点处继续执行

进程的上下文切换

什么是进程的上下文切换

  • 各个进程之间是共享 CPU 资源的,在不同的时候进程之间需要切换,让不同的进程可以在 CPU 执行,那么这个⼀个进程切换到另⼀个进程运行 是进程的上下文切换

进程的上下文切换到底是切换什么呢?

  • 进程是由 内核管理和调度 的,所以进程的切换只能发生在 内核态
  • 进程的上下文切换不仅包含了虚拟内存、栈、全局变量等用户空间的资源,还包括了内核堆栈、寄
    存器等内核空间的资源
  • 通常,会把交换的信息保存在进程的 PCB,当要运⾏另外⼀个进程的时候,我们需要从这个进程的 PCB 取出上下文,然后恢复到 CPU 中,这使得这个进程可以继续执行
  • 在这里插入图片描述

发生进程上下文切换的场景

  • 为了保证所有进程可以得到公平调度,CPU 时间被划分为⼀段段的时间片,这些时间片再被轮流分配
    给各个进程。这样,当某个进程的时间片耗尽了,进程就从运⾏状态变为就绪状态,系统从就绪队列
    选择另外⼀个进程运行
  • 进程在系统资源不足(⽐如内存不足)时,要等到资源满⾜后才可以运行,这个时候进程也会被挂
    起,并由系统调度其他进程运行
  • 当进程通过睡眠函数 sleep 这样的⽅法将⾃⼰主动挂起时,⾃然也会重新调度
  • 当有优先级更高的进程运行时,为了保证⾼优先级进程的运行,当前进程会被挂起,由⾼优先级进程
    来运⾏
  • 发⽣硬件中断时,CPU 上的进程会被中断挂起,转⽽执行内核中的中断服务程序;

线程上下文切换

  • 当两个线程不是属于同⼀个进程,则切换的过程就跟进程上下文切换⼀样
  • 当两个线程是属于同⼀个进程,因为虚拟内存是共享的,所以在切换时,虚拟内存这些资源就保持不动,只需要切换线程的私有数据、寄存器等不共享的数据

线程的上下文切换相比进程,开销要小很多

用户态线程和内核态线程的区别

用户态线程:不需要内核支持而在用户程序中实现的线程,它的内核的切换是由用户态程序自己控制内核的切换,不需要内核的干涉。但是它不能像内核级线程一样更好的运用多核CPU

优点:

  • 同一进程中只能同时有一个线程在运行,如果有一个线程使用了系统调用而阻塞,那么整个进程都会被挂起,可以节约更多的系统资源
  • 线程的调度不需要内核直接参与,控制简单
  • 可以在不支持线程的操作系统中实现

缺点:

  • 一个用户级线程的阻塞将会引起整个进程的阻塞
  • 用户级线程不能利用系统的多重处理,仅有一个用户级线程可以被执行

内核态线程: 切换由内核控制,当线程进行切换的时候,由用户态转化为内核态。切换完毕要从内核态返回用户态。可以很好的运用多核CPU

优点:

  • 当有多个处理机时,一个进程的多个线程可以同时执行
  • 由于内核级线程只有很小的数据结构和堆栈,切换速度快,当然它本身也可以用多线程技术实现,提高系统的运行速率

缺点:

  • 线程在用户态的运行,而线程的调度和管理在内核实现,在控制权从一个线程传送到另一个线程需要用户态到内核态再到用户态的模式切换,比较占用系统资源。(就是必须要受到内核的监控)

关联性:

  • 它们之间的差别在于性能
  • 内核态线程是OS内核可感知的,用户级线程是OS内核不可感知的
  • 用户态线程的创建、撤消和调度不需要OS内核的支持
  • 用户态线程执行系统调用指令时将导致其所属进程被中断内核态线程执行系统调用指令时,只导致该线程被中断
  • 只有用户态线程的系统内,CPU调度还是以进程为单位,处于运行状态的进程中的多个线程,由用户程序控制线程的轮换运行
  • 在有内核态线程的系统内,CPU调度则以线程为单位,由OS的线程调度程序负责线程的调度
  • 用户态线程的程序实体是运行在用户态下的程序,内核态线程的程序实体则是可以运行在任何状态下的程序

进程通信

由于每个进程的用户空间都是独立的,不能相互访问,需要借助内核空间来实现进程间通信,原因很简单,每个进程都是共享⼀个内核空间

管道

匿名管道

  • 匿名管道没有名字标识,是特殊文件 只存在于内存不存在于文件系统 中,shell命令中的 「 | 」竖线 就是匿名管道,通信的数据是 无格式的流并且大小受限, 通信的方式是 单向 的,数据只能在⼀个方向上流动,如果要双向通信,需要创建两个管道。
  • 匿名管道是只能用于存在父子关系的进程间通信,匿名管道的生命周期随着进程创建而建立,随着进程终止而消失

命名管道

  • 命名管道 突破了 匿名管道 只存在父子关系进程间的通信限制,因为使⽤命名管道的前提,需要在⽂件系统创建⼀个的管道文件,那么毫⽆关系的进程就可以通过这个管道文件进⾏通信
  • 不管是匿名管道还是命名管道,进程写入的数据都是 缓存在内核 中,另⼀个进程读取数据时候⾃然也是从内核中获取,同时通信数据都遵循 先进先出 原则,不⽀持 lseek 之类的文件定位操作。

缺点:

通信方式效率低,不适合进程间频繁地交换数据

消息队列

  • 消息队列克服了 管道通信 的数据是 无格式的字节流的问题,消息队列实际上是保存在内核的消息链表,消息队列的 消息体 是可以 用户自定义的数据类型
  • 发送数据时,会被分成⼀个⼀个独立的消息体,接收数据时,要与发送方发送的消息体的数据类型保持⼀致

缺点:
消息队列通信的速度不是最及时的,每次数据的写⼊和读取都需要经过用户态与内核态之间的拷贝过程

共享内存

  • 共享内存 可以解决消息队列通信中 ⽤户态与内核态之间数据拷⻉过程带来的开销,它直接分配⼀个共享空间,每个进程都可以直接访问,不需要陷⼊内核态或者系统调⽤,大大提⾼了通信的速度,

缺点:
便捷⾼效的共享内存通信,带来新的问题,多进程竞争同个共享资源会造成数据的错乱

信号量

  • 信号量保护共享资源,以确保任何时刻只能有⼀个进程访问共享资源,这种方式就是互斥访问
  • 信号量不仅可以实现访问的互斥性,还可以实现进程间的同步,信号量其实是⼀个计数器,表示的是资源个数,其值可以通过两个原⼦操作来控制,分别是 P 操作和 V 操作

信号

  • 信号 是进程间通信机制中 唯⼀的异步通信机制,信号可以在应⽤进程和内核之间直接交互,内核也可以利⽤信号来通知⽤户空间的进程发⽣了哪些系统事件
  • 信号事件的来源主要有 硬件来源(如键盘 Cltr+C )和 软件来源(如 kill 命令),⼀旦有信号发⽣,进程有三种⽅式响应信号 1. 执⾏默认操作、2. 捕捉信号、3. 忽略信号
  • 有两个信号是应⽤进程⽆法捕捉和忽略的,即 SIGKILLSEGSTOP ,这是为了⽅便能在任何时候结束或停止某个进程

Socket

  • Socket 通信 : 与不同主机的进程间通信,还可以⽤于本地主机进程间通信
  • 根据创建Socket 的类型不同,分为三种常见的通信方式,⼀个是基于 TCP 协议的通信⽅式,⼀个是基于 UDP 协议的通信⽅式,⼀个是 本地进程间通信⽅式

线程通信

同个进程下的线程之间都是共享进程的资源,只要是共享变量都可以做到线程间通信,比如全局变量,所
以对于线程间关注的不是通信⽅式,而是关注多线程竞争共享资源的问题,信号量也同样可以在线程间实
现互斥与同步

  • 互斥:可保证任意时刻只有⼀个线程访问共享资源
  • 同步:可保证线程 A 应在线程 B 之前执⾏

调度算法

页面置换算法

当 CPU 访问的页面 **不在物理内存 ** 时,便会产生一个 缺页中断,请求操作系统将 所缺页调入到物理内存

与一般中断的主要区别在于:

  • 缺页中断 在指令执行 【期间】 产生和处理中断信息,而一般中断在一条指令执行【完成】后检查和处理中断信息
  • 缺页中断 返回到该指令的开始重新执行【该指令】,而一般中断返回回到该指令的【下一个指令】执行

缺页中断的处理流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gqvL8HOu-1631464039390)(D:\Work-Space\TyporaImage\image-20210912154859443.png)]

  1. 在 CPU 里访问一条 Load M指令,然后 CPU 会找到 M 所对应的 页表项
  2. 如果该 页表项的状态位 是 【有效的】,那 CPU 就可以直接去访问 物理内存 了,如果状态位是【无效的】,则 CPU 会发送 缺页中断请求
  3. 操作系统收到了 缺页中断,则会执行缺页中断处理函数,先会 查找 该页面在磁盘中页面的位置
  4. 在换入前,需要在物理内存中找空闲页。如果找到空闲页,就把 页面换入到物理内存中
  5. 页面从磁盘换入的物理内存完成后,把页表项中的状态位修改为【有效的】
  6. 最后,CPU 重新执行导致缺页异常的指令

如果在物理内存中找不到空闲页

  • 如果找不到空闲页,说明此时内存已满。需要【页面置换算法】选择一个 物理页,如果该物理页有被修改过(脏页),则把它换出到磁盘,然后把该被 置换 出去的 页表项的状态 改成【无效的】,最后把 正在访问的页面装入到这个物理页中

页表项的字段

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6F2eZkwk-1631464039393)(D:\Work-Space\TyporaImage\image-20210912160430062.png)]

  • 状态位: 表示该页是否有效,也就是说是否正在物理内存中,供程序访问时参考
  • 访问字段: 记录该页在一段时间被访问的次数,供页面置换算法选择出页时参考
  • 修改位: 表示该页在调入内存后是否有被修改过,由于内存中的每一页都在磁盘上保留一份副本。因此,如果没有修改,在置换该页时就不需要将该页写回到磁盘上,以减少系统的开销;如果已经被修改,则将该页重写到磁盘上,以保证磁盘中所保留的始终是最新的副本
  • 硬盘地址: 指出该页在硬盘上的地址,通常是 物理块号,供调入该页时使用

页面置换算法:当出现缺页异常,需调入新页面而内存已满时,选择被置换的物理页面

常见的页面置换算法

  • 最佳页面置换算法(OPT)
  • 先进先出置换算法(FIFO)
  • 最近最久未使用置换算法(LRU)
  • 最不常用置换算法(LFU)
  • 时钟页面置换算法(Lock)

最佳页面置换算法

  • 基本思路:置换在 【未来】最长时间不访问的页面
  • 该算法实现需要计算内存中每个逻辑页面的【下一次】访问时间,然后比较,选择未来最长时间不访问的页面
  • 实际系统中无法实现,因为程序访问页面时是动态的,无法预知每个页面在【下一次】访问前的等待时间
  • 最佳页面在置换算法的作用是为了衡量你的算法的效率,即你的算法效率越接近该算法的效率,那么说明你的算法是高效的

先进先出置换算法

  • 基本思路:既然无法预知页面在下一次访问前所需的等待时间,可以 选择在内存驻留时间很长的页面进行中置换
  • 比最佳页面置换算法 性能差

最近最久未使用的置换算法

  • 基本思路:选择最长时间没有被访问的页面进行置换
  • 虽然LRU在理论上可以实现,但代价很高。需要在内存中维护一个所有页面的链表,最近最多使用的页面在表头,最近最少使用的页面在表尾。在每次访问内存的时候都必须更新【整个链表】

最不常用置换算法

  • 基本思路: 当发生缺页中断时,选择【访问次数】最少的那个页面,将其淘汰
  • 对每个页面设置一个【访问计数器】,每当一个页面被访问时,该页面的访问计数器就累加1,。在发生缺页中断时,淘汰计算器值最小的那个页面
  • 增加一个计数器来实现,这个硬件成本比较高。另外如果要对这个计数器查找哪个页面访问次数最小,查找链表本身,如果链表长度很大,是非常耗时,效率不高
  • LFU算法只考虑了效率问题,没考虑时间问题。如果有些页面在过去时间里访问频率很高,但是现在已经没有访问了,而当前频繁访问的页面由于没有这些页面访问的次数高,在发生缺页中断时,就可能会 误伤到 当前刚开始频繁访问,但访问次数还不高的页面

解决方案

  • 可以定期减少访问的次数,比如当发生时间中断时,把过去时间访问的页面的访问次数 / 2
  • 随着时间的流失,以前的高访问次数的页面会慢慢减少,相当于加大了被置换的概率

时钟页面置换算法

  • 基本思路:把所有的页面都保存在一个类似钟面的【环形链表】中,一个表针指向最老的页面
  • 当发生 缺页中断 时,算法首先检查表针指向的页面
    • 如果访问位是 0 就淘汰该页面,并把新的页面插入这个位置,然后把表针前移一个位置
    • 如果访问位是 1 就清除访问位,并把表针前移一个位置,重复这个过程直到找到了一个访问位为 0 的页面为止

网络系统

I/O多路复用

select/poll

  • select 实现多路复用的方式是,将已连接的 Socket 都放到一个 文件描述符集合,然后调用select函数将 文件描述符集合 拷贝 到内核里,让内核来检查是否有网络事件产生
  • 检查的方式很粗暴,就是通过 遍历 文件描述符集合的方式,当检查到有事件产生后,将此Socket 标记为可读或可写,接着再把整个 文件描述符集合 拷贝 到用户态里,然后用户态还需要通过 遍历 的方式找到 可读或可写 的 Socket,然后再对其处理

对于select这种方式,需要进行 2次 【遍历】文件描述符集合,一次是在内核态里,一次是在用户态里,而且还会发生 2次【拷贝】文件描述符集合,先从用户空间传入内核空间,由内核修改后,再传出到用户空间里

select 使用固定长度的 BitsMap,表示文件描述符集合,而且所支持的文件描述符的个数是有限的,在 Linux 系统中,由内核中的 FD_SETSIZE 限制,默认最大值为 1024,只能听见 0~1023的文件描述符

poll 不再用 BitsMap 来存储所关注的文件描述符,取而代之用 动态数组,以 链表形式来组织突破了select的文件描述符个数限制,当然还会受到系统文件描述符限制。

select 和 poll并没有太大的本质区别:

  • 都是使用**「线性结构」**存储进程关注的Socket集合,因此都需要遍历文件描述符集合来找到可读或可写的Socket,时间复杂度为O(n)
  • 也需要在用户态与内核态之间拷贝文件描述符集合,这种方式随着并发数.上来,性能的损耗会呈指数级增长

epoll

epoll通过两个方面,很好解决了 select/poll的问题

第一点:

  • epoll 在内核里使用 红黑树来跟踪进程所有待检测的文件描述字,把需要监控的socket通过epoll_ ctl() 函数加入内核中的红黑树里
  • 红黑树是个高效的数据结构,增删查一般时间复杂度是O(logn),通过对这棵红黑树进行操作,这样就不需要像select/poll每次操作时都传入整个socket集合,只需要传入一个待检测的socket,减少了内核和用户空间大量的数据拷贝和内存分配

第二点:

  • epoll 使用事件驱动的机制,内核里 维护了一个链表来记录就绪事件,当某个socket有事件发生时,通过回调函数内核会将其加入到这个就绪事件列表中
  • 当用户调用 epoll_wait() 函数时, 只会返回有事件发生的文件描述符的个数,不需要像select/poll那样轮询扫描整个socket集合,大大提高了检测的效率

设备管理

设备控制器

输入输出设备可分为两大类:块设备字符设备

  • 块设备:把数据存储在固定大小的块中,每个块有自己的地址。硬盘、USB是常见的设备
  • 字符设备: 以 字符 为单位发送或接收一个字符流,字符设备是不可寻址的,也没有任何寻道操作。鼠标是常见的字符设备

块设备通常传输的数据量非常大,于是控制器设立了一个可读写的 数据缓冲区

  • CPU 写入数据到 控制器的缓冲区 时,当缓冲区的数据囤够了一部分,才会发给设备
  • CPU 从控制器的缓冲区读取数据时,也需要缓冲区囤够了一部分,才拷贝到内存

键盘敲入字母时发什么了什么?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a2ezJnc0-1631464039396)(D:\Work-Space\TyporaImage\image-20210903165951849.png)]

  • 当用户输入了键盘字符,键盘控制器 就会产生扫描码数据,并将其缓冲在键盘控制器的寄存器里,紧接着键盘控制器通过 总线 给CPU发送 中断请求
  • CPU 收到中断请求后,操作系统会 保存被中断进程的 CPU 上下文,然后调用键盘的 中断处理程序
  • 键盘的中断处理程序是在 键盘驱动程序 初始化时注册的,那键盘 中断处理函数 的功能就是从键盘控制器的寄存器的缓冲区读取扫描码,再根据扫描码找到用户在键盘输入的字符,如果输入的字符是显示字符,那就会把扫描码翻译成对应显示字符的 ASCII码,比如用户在键盘输入的是字母A,是显示字符,于是就会把扫描码翻译成 A字符的 ASCII码
  • 得到了显示字符的 ASCII码后,就会把 ASCII码放到 【读缓冲区队列】,接下来就是要把显示字符显示屏幕了,显示设备的驱动程序会定时从 【读缓冲区队列】读取数据放到 【写缓冲区队列】,最后把 【写缓冲区队列】的数据一个一个写入到显示设备的控制器的寄存器中的数据缓冲区,最后将这些数据显示在屏幕里。显示出结果后,恢复被中断进程的上下文
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值