操作系统

IntYou (^_^)
  1. 进程和线程之间有什么区别?

    进程是运行中的程序,线程是进程的内部的一个执行序列
    进程是资源分配单元,线程是执行单元
    进程间切换代价大,线程间切换代价小
    进程拥有资源多,线程拥有资源少
    多个线程共享进程资源

    进程为啥切换代价比线程要大?

    物理地址就是真实的地址,这种寻址方式很容易破坏操作系统,而且使得操作系统中同时运行两个或以上的程序几乎是不可能的(第一个程序给物理内存地址赋值 10,第二个程序也同样给这个地址赋值为 100,那么第二个程序的赋值会覆盖掉第一个程序所赋的值,这会造成两个程序同时崩溃)。

    当然,也不是完全不可能,有一种方式可以实现比较粗糙的并发。就是说,我们将空闲的进程存储在磁盘上,这样当它们不运行时就不会占用内存,当进程需要运行的时候再从磁盘上转到内存上来,不过很显然这种方式比较浪费时间。

    于是,我们考虑,把所有进程对应的内存一直留在物理内存中,给每个进程分别划分各自的区域,这样,发生上下文切换的时候就切换到特定的区域
    那问题还是很明显的,就是仍然没法避免破坏操作系统,因为各个进程之间可以随意读取、写入内容。

    所以,我们需要一种机制对每个进程使用的地址进行保护,因此操作系统创造了一个新的内存模型,那就是虚拟地址空间, 就是说,每个进程都拥有一个自己的虚拟地址空间,并且独立于其他进程的地址空间,然后每个进程包含的栈、堆、代码段这些都会从这个地址空间中被分配一个地址,这个地址就被称为虚拟地址。底层指令写入的地址也是虚拟地址。

    有了虚拟地址空间后,CPU 就可以通过虚拟地址转换成物理地址这样一个过程,来间接访问物理内存了。

    地址转换需要两个东西,一个是 CPU 上的内存管理单元 MMU,另一个是内存中的页表,页表中存的虚拟地址到物理地址的映射。

    但是呢,每次访问内存,都需要进行虚拟地址到物理地址的转换,对吧,这样的话,页表就会被频繁地访问,而页表又是存在于内存中的。所以说,访问页表(内存)次数太多导致其成为了操作系统地一个性能瓶颈。

    于是,引入了转换检测缓冲区 TLB,也就是快表,其实就是一个缓存,把经常访问到的内存地址映射存在 TLB 中,因为 TLB 是在 CPU 的 MMU 中的嘛,所以访问起来非常快。

    然后,正是因为 TLB 这个东西,导致了进程切换比线程切换慢。

    由于进程切换会涉及到虚拟地址空间的切换,这就导致内存中的页表也需要进行切换,一个进程对应一个页表是不假,但是 CPU 中的 TLB 只有一个,页表切换后这个 TLB 就失效了。这样,TLB 在一段时间内肯定是无法被命中的,操作系统就必须去访问内存,那么虚拟地址转换为物理地址就会变慢,表现出来的就是程序运行会变慢。

    而线程切换呢,由于不涉及虚拟地址空间的切换,所以也就不存在这个问题了。

  2. 进程间有哪些通信方式?

    1. 管道:

      管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有关联的进程间使用,进程的关系通常是指父子进程关系。

    2. 消息队列通信:

      消息队列是由消息的链表存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。

    3. 信号量:

      信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

    4. 信号:

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

    5. 共享内存通信:

      共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个远程创建,但多个进程都可以访问。共享内存是最快的IPC方式,它是针对其他进程间简单通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。

    6. socket:

      套接字机制与其他机制不同的是,它可用于不同机器间的进程通信。

  3. 简述 socket 中 select 与 epoll 的使用场景以及区别?

    两个网络模型本质上都是IO多路复用的

    select 需要不断的去轮询数据,时间复杂度为O(n),性能比较低,并且它的文件描述符也是有限制的。像常见的服务器软件 apache 用的就是这种网络类型。

    而 epoll 则不一样,他不会去轮询,而是会注册一个监听事件,每当数据有变化的时候,就会通知,像 nginx 底层的网络模型就是用的 epoll ,这个也是为啥nginx 能处理高并发的主要原因。

    表面上看 epoll 的性能最好,但是在连接数少并且连接都十分活跃的情况下,select 和 poll 的性能可能比 epoll 好,毕竟 epoll 的通知机制需要很多函数回调。

    select 低效是因为每次它都需要轮询,但低效也是相对的,是情况而定,也可以通过良好的设计改善。

  4. epoll 中水平触发以及边缘触发有什么不同?

    • Level_triggered(水平触发):

      当被监控的文件描述符上有可读写事件发生时,epoll_wait() 会通知处理程序去读写,如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait() 时,它还会通知你在没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会一直通知你!!!如果系统中有大量的你不需要读写的就绪文件描述符,而他们每次都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率!!!

    • Edge_triggered(边缘触发):

      当被监控的文件描述符上有可读写事件发生时,epoll_wait() 会通知程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait() 时,它不会通知你,也就是它只会通知一次,知道该文件描述符上出现第二次可读写事件才会通知你!!!这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符!!!

  5. Linux 进程调度中有哪些常见算法以及策略?

    1. 先来先服务和短作业(进程)优先调度算法

    2. 高优先权优先调度算法

      • 非抢占式优先权算法

      • 抢占式优先权调度算法

进程的状态有哪几种?

  1. 运行态:

    该状态表明进程在实际占用CPU。

  2. 就绪态:

    该状态下进程可以运行,但因为其他进程正在运行而暂时停止。

  3. 阻塞态:

    该状态下进程不能运行,除非某种外部事件的发送。

运行态 –> 等待态

往往是由于等待外设,等待主存等资源分配或等待人工干预而引起的

等待态 –> 就绪态

则是等待的条件已满足,只需分配到处理器后就能运行

运行态 –> 就绪态

不是由于自身原因,而是由外界原因使运行状态的进程让出处理器,这时候就变成就绪态。例如时间片用完,或有更高优先级的进程来抢占处理器等

就绪态 –> 运行态

系统按某种策略选中就绪队列中的一个进程占用处理器,此时就变成了运行态

  1. 操作系统如何申请以及管理内存的?

    虚拟内存是现代操作系统普遍使用的一项技术,很多情况下,现有内存无法满足仅仅一个大进程的内存要求(比如很多游戏都是10G+的级别)。在早期的操作系统曾使用覆盖(overlays)来解决这个问题,将一个程序分为多个块,基本思想是先将块0加入内存,块0执行完成后,将块1加入内存。以此往复,这个解决方案最大的问题是需要程序员去程序进行分块,这是一个费时费力的过程。后来这个解决方案的修正版就是虚拟内存。

    虚拟内存的基本思想是,每个进程有独立的逻辑地址空间,内存被分为大小相等的多个快,称为页。每个页都是一段连续的地址。对于进程来看,逻辑上貌似有很多内存空间,其中一部分对应物理内存的一块(称为页框,通常页和页框大小相等),还有一些没加载在内存中的对应在硬盘上。

  2. 简述 Linux 系统态与用户态,什么时候会进入系统态?

    1. 系统调用

    2. 发生异常,当 cpu 正在执行运行在用户的程序的时候,突然发生某些预先不可知的异常事件,这个时候就会触发从当前用户态执行的进程转向内核执行相关的异常事件,典型的如缺页异常。

    3. 外围设备的中断,当外围设备完成用户的请求操作后,会像CPU发出中断信号,此时,CPU 就会暂停执行下一条即将要执行的指令,转而去执行中断信号对应的处理程序,如果先前执行的指令是在用户态下,则自然就发生从用户态到内核态的转换。

  3. 简述 LRU 算法及其实现方式

    Least Recently Used,最近最久未使用法,它是按照一个非常著名的计算机操作系统基础理论得来的:最近使用的页面数据会在未来一段时期内仍然被使用,已经很久没有使用的页面很有可能在未来较长的一段时间内仍然不会被使用。基于这个思想,会存在一种缓存淘汰机制,每次从内存中找到最久未使用的数据然后置换出来,从而存入新的数据!它的主要衡量指标是使用的时间,附加指标是使用的次数。在计算机中大量使用了这个机制,它的合理性在于优先筛选热点数据,所谓热点数据,就是最近最多使用的数据!因为,利用 LRU 我们可以解决很多实际开发中的问题,并且很符合业务。利用双向链表实现

  4. 线程间有哪些通信方式

    共享变量,共享内存,共享数据库,消息队列

  5. 简述同步与异步的区别,阻塞与非阻塞的区别

    1. 同步和异步

      同步和异步通常用来描述消息传递或通信的方式。

      在同步通信中,发送方会等待接收方的响应,然后才继续发送下一条消息。相反,在异步通信中,发送方不会等待接收方的响应,而是继续发送下一条消息。

    2. 阻塞与非阻塞

      阻塞与非阻塞通常用来描述系统调用的行为。系统调用是应用程序请求操作系统内核提供服务的一种机制,例如读写文件,创建进程等。

      当一个应用程序发起一个阻塞的系统调用时,他会一直等待,直到该调用完成。相反,当一个应用程序发起一个非阻塞的系统调用时,如果该调用不能立即完成,他会立即返回。

    总结

    同步:

    执行一个操作之后,等待结果,然后才继续执行后续的操作

    异步:

    执行一个操作之后,可以去执行其他的操作,然后等待通知再回来执行完的操作

    阻塞:

    进程给CPU传达一个任务之后,一直等待CPU处理完成,然后才执行后面的操作

    非阻塞:

    进程给CPU传达任务后,继续处理后续的操作,隔段时间再来询问之前的操作是否完成,这样的过程也叫轮询

  6. 简述操作系统中的缺页中断

    虚拟内存实际上可以比物理内存大,当访问虚拟内存时,会访问MMU(内存管理单元)去匹配对应的物理地址,而如果虚拟内存的页并不存在于物理内存中,会产生缺页中断,从磁盘中取得缺的页放入内存中,如果内存已满,还会根据页的置换算法将磁盘中的页换出。

  7. 简述操作系统中 malloc 的实现原理

    malloc 基本的实现原理就是维护一个内存空闲链表,当申请内存空间时,搜索内存空闲链表,找到适配的空闲内存空间,然后将空间分割成两个内存块,一个变成分配块,一个变成新的空闲块。如果没有搜索到,那么就会用sbrk()推进brk指针来申请内存空间。

    搜索空闲块最常见的算法有:首次适配,下一次适配,最佳适配。

    首次适配:

    第一次找到足够大的内存块就分配,这种方法会产生很多内存碎片

    下一次适配:

    等第二次找到足够大的内存块就分配,这样会产生比较少的内存碎片

    最佳适配:

    对堆进行彻底的搜索,重投开始,遍历所有块,使用数据区大小大于size且差值最小的块作为此次分配的块

  8. 简述 Linux 的 IO模型

    在linux操作系统中,共有五种IO模型

    • 阻塞IO

      应用程序在进行系统调用时会被阻塞,直到数据准备好并复制到用户空间后才返回

    • 非阻塞IO

      应用程序在进行系统调用时不会被阻塞,而是立即返回一个状态值。应用程序需要不断轮询内核以检查数据是否准备好

    • 多路复用IO

      应用程序可以同时监视多个文件描述符,当某个文件描述符就绪时,对其进行处理

    • 信号驱动IO

      应用程序向内核注册一个信号处理函数,当数据报准备好时,内核会向应用程序发送一个信号,应用程序对信号进行捕捉,并调用信号处理函数来获取数据报

    • 异步IO

      应用程序发起系统调用后立即返回,当内核将数据准备好并复制完成后会给应用程序发送通知

    这些模型区别在于应用程序和内核之间的交互方式以及数据准备和拷贝的过程

  • 标题: 操作系统
  • 作者: IntYou
  • 创建于: 2023-04-08 11:07:24
  • 更新于: 2023-04-10 18:14:51
  • 链接: https://intyou.netlify.app/2023/04/08/操作系统/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
此页目录
操作系统