[操作系统]常见面试题--无序版

操作系统64位与32位的区别

内存寻址能力:64位操作系统通常最大可支持16EB的物理内存,32位由于地址空间的限制,最多只有4GB的物理内存
49aa9e63bf46602b175f49747ad27018.png
运算性能:64位CPU有更宽的通用寄存器和更大的指令集,一次可以处理更多数据

进程和线程的区别

  • 进程是系统资源分配和调度的最小单元,线程是CPU调度和分配的最小单位
  • 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程
  • 进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率
  • 资源分配给进程,同一进程的所有线程共享该进程的所有资源
  • 进程的创建、销毁、切换的开销大于线程
  • 线程不能独立执行,必须依存在进程中

线程快在哪?

  • 线程创建时有些资源不需要自己管理,直接从进程取用,线程只需要管理寄存器和栈的声明周期
  • 同一进程内多个线程共享数据,所以进程数据传输可以用零拷贝技术,不需要经过内核
  • 进程使用一个虚拟内存跟页表,然后多个线程共用这些虚拟内存,如果同进程内两个线程进行上下文切换比进程快很多

线程分为用户态线程和内核态线程,二者有什么区别?

用户态线程工作在用户空间,内核态线程工作在内核空间。用户态线程调度完全由进程负责,通常就是由进程的主线程负责。相当于进程主线程的延展,使用的是操作系统分配给进程主线程的时间片段。内核线程由内核维护,由操作系统调度。

用户态线程无法跨核心,一个进程的多个用户态线程不能并发,阻塞一个用户态线程会导致进程的主线程阻塞,直接交出执行权限。这些都是用户态线程的劣势。内核线程可以独立执行,操作系统会分配时间片段。因此内核态线程更完整,也称作轻量级进程。内核态线程创建成本高,切换成本高,创建太多还会给调度算法增加压力,因此不会太多。

实际操作中,往往结合两者优势,将用户态线程附着在内核态线程中执行。

说说协程

为了处理高并发、多连接下的数据读写,以往有两种解决方案。

  • 多进程:存在频繁调度切换的问题,同时HIA存在每个进程资源不共享的问题,需要额外引入进程间通信机制来解决。
  • 多线程:高并发场景的大量IO等待会导致多线程被频繁挂起和切换,非常消耗系统资源,同时多线程访问共享资源存在竞争问题

协程是一种比线程更加轻量级的微线程,一个线程可以拥有多个协程
协程运行在线程之上,当一个协程执行完成之后,可以选择主动出让,让另一个协程运行在当前线程之上。

协程并没有增加线程 数量,只是在线程的基础之上通过分时复用的方式运行多个协程,而且协程的切换在用户态完成,切换的代价比线程从用户态到内核态的代价小很多。

因此,使用协程替换线程适用于有大量IO操作业务的情况下。一是降低了系统内存,二是减少了系统切换开销,因此系统的性能也会提升。
由于在协程中调用阻塞IO的方法会导致在该线程之上的所有协程都陷入阻塞。因此协程只有和异步IO结合起来才能发挥出最大的威力

进程通信

进程的用户地址空间是相互独立的,不可以互相访问,但内核空间是进程都共享的,所以进程之间要通信必须通过内核。进程间通信主要有管道、消息队列、共享内存、信号量、信号、Socket编程

  • 管道分为匿名管道和有名管道,管道是半双工通信的。管道的本质就是内核在内存中开辟了一个缓冲区,这个缓冲区与管道文件相关联,对管道文件的操作,被内核转换成对这块缓冲区的操作。

    • 匿名管道只能用于父子进程间的通信,有名管道只要可以访问路径就可以通过有名
      信号是进程通信机制中唯一的异步通信机制,它可以在任何时候发送信号给某个进程。通过发送指定信号来通知进程某个异步事件的发送,以迫使进程执行信号处理程序。信号处理完毕后,被中断进程将恢复执行。用户、内核和进程都能生成和发送信号。
      信号的来源有硬件来源和软件来源,常见的有Ctrl+9 1111
  • Socket
    用于跨网络与不同主机上的进程进行通信,当然也可以完成同主机上的进程通信
    Socket的本质是一个编程接口(API)
    是应用层与TCP/IP协议的网络通信的抽象层。它对TCP/IP进行了封装,它把复杂的TCP/IP协议簇隐藏在Socket接口后面。对于用户来说,只需要进行一组简单的API就可以实现网络的连接C产生SIGINT信号,表示终止该进程;软件来源如kill -管道进行通信

    • 管道这种进程通信方式虽然使用简单,但是效率比较低,不适合进程间频繁地交换数据,并且管道只能传输无格式的字节流
  • 消息队列的本质就是存放在内存中的消息的链表,而消息本质上是用户自定义的数据结构。如果进程从消息队列中读取了某个消息,这个消息就会从消息队列中删除

    • 消息队列可以实现消息的随机查询,不一定要以先进先出的次序读取消息,也可以按消息类型读取,比有名管道的先进先出更有优势
    • 用户进程写入数据到消息队列时,会发生从用户态拷贝数据到内核态,用户进程读也会发生拷贝。如果数据量较大,,使用消息队列会造成频繁系统调用,会降低性能
  • 共享内存可以避免消息队列的拷贝消息、进行系统调用,允许不相干的进程将同一段物理内存连接到它们各自的地址空间中,使得这些进程可以访问同一个物理内存,这个物理内存就成为共享内存。如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。

    • 共享内存仅在建立共享内存区域时需要系统调用,一旦建立共享内存,所有的访问都可作为常规内存访问,无需借助内核。这样,数据就不需要在进程之间来回拷贝,所以这是最快的一种进程通信方式。但是却容易造成数据冲突
  • 信号量,信号量是 一个整型计数器,主要用于实现进程间的互斥与同步,而不是用于缓存进程间通信的数据,保证共享内存在任何时刻只有一个进程在访问(保证互斥),并使得进程们能够按照某个特定的程序访问共享内存(同步)

    • 利用PV操作实现
    • P操作:将信号量值减一,表示申请占用一个资源。如果结果小于 0,表示已经没有可用资源,则执行 P 操作的进程被阻塞。如果结果大于等于 0,表示现有的资源足够你使用,则执行 P 操作的进程继续执行。
    • V操作:将信号量值加一,表示释放一个资源,即使用完资源后归还资源。
  • 信号

信号信号量完全不同!!!

信号是进程通信机制中唯一的异步通信机制,它可以在任何时候发送信号给某个进程。通过发送指定信号来通知进程某个异步事件的发送,以迫使进程执行信号处理程序。信号处理完毕后,被中断进程将恢复执行。用户、内核和进程都能生成和发送信号。
信号的来源有硬件来源和软件来源,常见的有Ctrl+C产生SIGINT信号,表示终止该进程;软件来源如kill -9 1111

  • Socket
    用于跨网络与不同主机上的进程进行通信,当然也可以完成同主机上的进程通信
    Socket的本质是一个编程接口(API)
    是应用层与TCP/IP协议的网络通信的抽象层。它对TCP/IP进行了封装,它把复杂的TCP/IP协议簇隐藏在Socket接口后面。对于用户来说,只需要进行一组简单的API就可以实现网络的连接

信号量的值 大于 0 表示有共享资源可供使用,这个时候为什么不需要唤醒进程?

所谓唤醒进程是从就绪队列(阻塞队列)中唤醒进程,而信号量的值大于 0 表示有共享资源可供使用,也就是说这个时候没有进程被阻塞在这个资源上,所以不需要唤醒,正常运行即可。

信号量的值 等于 0 的时候表示没有共享资源可供使用,为什么还要唤醒进程?

V 操作是先执行信号量值加 1 的,也就是说,把信号量的值加 1 后才变成了 0,在此之前,信号量的值是 -1,即有一个进程正在等待这个共享资源,我们需要唤醒它。

使用信号量和 PV 操作实现进程的同步也非常方便,三步走:

  • 定义一个同步信号量,并初始化为当前可用资源的数量
  • 在优先级较的操作的后面执行 V 操作,释放资源
  • 在优先级较的操作的前面执行 P 操作,申请占用资源

虚拟内存的布局

49aa9e63bf46602b175f49747ad27018.png

虚拟内存是如何实现的

虚拟内存技术的实现是建立在离散分配的内存管理的基础上的

目前最常用的三种实现虚拟内存技术的方法:

  • 请求分页存储管理

  • 请求分段存储管理

  • 请求段页式存储管理
    以上三种方式,无论哪种,都需要以下三个条件:

  • 一定容量的内存和外存
    程序执行时,只需要将程序的一部分装入内存,就可以运行了

  • 缺页中断
    若需要执行的程序未在内存中(即“缺页/段”),则处理器会通知操作系统将相应的页/段调入到内存中,与此同时也会将不常用的页/段调出到外外存中

  • 虚拟地址空间
    逻辑地址转化为物理地址

用什么命令查看内存使用情况

top
这个命令会动态显示系统中各个进程的资源占用状况,包括CPU使用率和内存使用情况。按Shift + M可以按照内存使用量进行排序。

ps aux
该命令可以显示所有进程的详细信息,包括进程ID(PID)、用户、CPU和内存使用百分比等

  • top
  • pidstat
  • ps
  • pmap

select poll epoll

IO多路复用是用一个进程来维护多个Socket
而select poll epoll就是内核提供给用户态的多路复用系统调用,进程可以通过一个系统调用函数从内核获取多个事件
这三者都是在获取事件时,先把所有连接(文件描述符fd)传给内核,再由内核返回产生了事件的连接,然后在用户态中再处理这些连接对应的请求即可

  • select
    将已连接的 Socket 都放到一个文件描述符集合,然后调用 select 函数将文件描述符集合拷贝到内核里,让内核来检查是否有网络事件产生。检查的方式就是暴力的遍历
    当经检查到有事件产生后,将次Socket标记为可读或者可写, 接着再把整个文件描述符集合拷贝回用户态里,然后用户态还需要再通过遍历的方法找到可读或可写的 Socket,然后再对其处理。
    所以,对于select这种方式,需要进行2次遍历文件描述符集合,一次在内核态、一次在用户态。还要发生2次拷贝文件描述符集合,先从用户空间传入内核空间中,再由内核修改后,再传出到用户空间中
    select 使用固定长度的 BitsMap,表示文件描述符集合,而且所支持的文件描述符的个数是有限制的,在 Linux 系统中,由内核中的 FD_SETSIZE 限制, 默认最大值为 1024,只能监听 0~1023 的文件描述符。

  • poll
    poll 不再用 BitsMap 来存储所关注的文件描述符,取而代之用动态数组,以链表形式来组织,突破了 select 的文件描述符个数限制,当然还会受到系统文件描述符限制。
    但是 poll 和 select 并没有太大的本质区别,都是使用「线性结构」存储进程关注的 Socket 集合,因此都需要遍历文件描述符集合来找到可读或可写的 Socket,时间复杂度为 O(n),而且也需要在用户态与内核态之间拷贝文件描述符集合,这种方式随着并发数上来,性能的损耗会呈指数级增长。

  • epoll

    • epoll 在内核里使用红黑树来跟踪进程所有待检测的文件描述字,红黑树的增删改一般时间复杂度是O(logn),由于有了红黑树来保存所有待检测的Socket,所有epoll机制不必要每次操作都传入整个Socket集合给内核,只需要传入一个待检测的Socket,减少了内核和用户空间大量数据拷贝和内存分配
    • epoll使用事件驱动的机制,其内核维护了一个链表来记录就绪事件,当某个Socket有事件发生时,通过回调函数,内核会将这个就绪事件加入到就绪时间列表当中,当用户调用epoll_wait函数时,只会返回有事件发生的文件描述符的个数,不需要轮询整个Socket集合,大大提高可检测效率
作者

Zhangmingyu

发布于

2024-04-16

更新于

2025-06-04

许可协议

You need to set install_url to use ShareThis. Please set it in _config.yml.
You forgot to set the business or currency_code for Paypal. Please set it in _config.yml.

评论

You forgot to set the shortname for Disqus. Please set it in _config.yml.