当前位置:   article > 正文

操作系统常见面试题_操作系统面试

操作系统面试

进程和线程

进程和线程的区别

  • 基本意义:进程:操作系统分配资源(内存空间等)的基本单位,系统中正在运行的一个应用程序,程序一旦运行就是进程。线程:是进程的一条执行路径,操作系统CPU独立调度执行的基本单位

  • 包含关系:线程是进程的一部分,一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。

  • 空间使用:每个进程有独立的代码区和内存空间;,系统不会为线程分配内存,不同线程共享所在进程的内存空间,不同线程共用堆,但是不同线程有各自的栈。

  • 开销:1. 创建和终止进程耗时,消耗内存资源更多,线程更少。2. 程序之间的上下文切换会有较大的开销时间和内存,同一类线程共享代码和数据空间,线程之间切换的开销小,因为不需要切换地址空间。

  • 相互影响:进程之间不会相互影响,而多线程之间影响较大。一个进程挂了对其他进程没影响,而一个线程挂了,则数据很可能出错,也很可能整个进程就挂了。进程之间相互独立,而不同线程之间更多是协作关系。

协程与线程的区别

包含: 一个线程可以多个协程,一个进程也可以单独拥有多个协程
资源:占用资源很少。比如CPU起一个线程就要占用1M,而协程起一个只需要几K。
调度策略:用户空间决定,调度策略由应用层代码定义。而线程调度策略只能操作系统来确定。

适合在并发量高IO高并发模型,较短的计算任务上。go自带协程。大多数时候都是要花大量等待时间的场景,也就是所谓的IO密集,协程极为适合这种场景

进程与线程的切换流程

进程切换分两步:
1、切换页表以使用新的地址空间,因为每个进程都有自己的虚拟地址空间,一旦去切换上下文,处理器中所有已经缓存的内存地址一瞬间都作废了。 进程都有自己的虚拟地址空间,把虚拟地址转换为物理地址需要查找页表,也就是每个进程拥有自己的页表。在页表中查找,有可能还有多级页表。进程一切换,页表就要跟着切换,因为要做虚拟地址到物理地址的映射。页表占的空间也不小,所以进程的切换就更加耗时。
2、切换内核栈和硬件上下文:保存进程指令和数据,在相关寄存器与堆栈

线程切换只需要第二步:
因为每个进程都有自己的虚拟地址空间,而线程是共享所在进程的虚拟地址空间的,因此同一个进程中的线程进行线程切换时不涉及虚拟地址空间的转换。只需要切换:栈、线程寄存器信息等不共享的线程私有的数据

什么是虚拟地址空间

对于每一个程序在执行时,此时会产生一个相应的进程,系统都会自动为其分配一个04G的虚拟地址空间,其中1G的内核空间用于:进程管理、内存管理、设备管理和虚拟文件系统等。下面详细介绍03G的用户空间。地址从0开始。程序认为是连续的,但实际上在物理内存上不一定连续。进程使用这块虚拟地址空间进行编程。
好处
1.方便用户使用,因为程序认为是连续的;
2.方便实现各个进程空间之间的隔离,互不干扰,因为每个进程都对应自己的虚拟地址空间;
3.实现虚拟存储,从逻辑上扩大了内存。

为什么虚拟地址空间切换后感觉程序变慢

  1. 进程都有自己的虚拟地址空间,把虚拟地址转换为物理地址需要查找页表,也就是每个进程拥有自己的页表。在页表中查找,有可能还有多级页表。进程一切换,页表就要跟着切换,因为要做虚拟地址到物理地址的映射。页表占的空间也不小,所以进程的切换就更加耗时。
  2. 页表查找是一个很慢的过程,所以通常使用Cache来缓存常用的地址映射,这样可以加速页表查找,这个Cache就是TLB(translation Lookaside Buffer,TLB本质上就是一个Cache,是用来加速页表查找的)。
  3. 当进程切换后页表也要进行切换,页表切换后TLB缓存就失效了,已经缓存的内存地址就作废了,Cache失效导致命中率降低,就要重新更新这些缓存,这个过程就比较耗时
  4. 并且没有了缓存页表,那么虚拟地址转换为物理地址,地址转换就会变慢,表现出来的就是程序运行会变慢,而线程切换则不会导致TLB失效,因为线程无需切换地址空间,因此我们通常说线程切换要比较进程切换块。

进程间通信方式

  1. 管道:半双工,管道只允许单向通信,半双工,数据只能向一个方向流动,需要双方通信时,需要建立两个管道。一个进程写数据另一个进程读数据,并且按照先进先出的顺序。管道随进程,进程在管道在,进程消失管道对应的端口也关闭,两个进程都消失管道也消失。通常只在父子进程之间通信,速度慢。

  2. 消息队列:消息队列可以认为是一个消息链表,存储在内核中。 发送方把消息发送到消息队列,接收方从消息队列取消息。他与管道不同的是管道的队列存储在硬盘中访问速度慢,而消息队列的队列存放在内存中速度快。管道是数据流形式,而消息队列是数据块形式,类似与TCP和UDP。消息队列允许一个或多个进程向它写入与读取消息,所以需要唯一标识符来确定哪个。进程间通过消息队列通信,主要是:创建或打开消息队列,添加消息,读取消息和控制消息队列。容量受限。

  3. 共享内存:映射一段能被其他进程所访问的内存,这是最高效的进程间通信IPC方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据得更新。**但是这种方式需要依靠某种同步操作保证数据正确性,如锁和信号量等;**采用共享内存进行通信的一个主要好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝,对于像管道和消息队里等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次:一次从输入文件到共享内存区,另一次从共享内存到输出文件。和锁来配合使用。

  4. 信号量:信号量实质上就是一个标识可用资源数量的计数器,可以理解为一个简单的锁,不能传递复杂消息。一个共享资源中由一个信号量标识,当一个进程访问的时候,这个信号量就-1,变为0,这时其他进程就不能访问,当进程释放共享资源,信号量就+1,大于0,这时其他进程可以访问。他一般和共享内存配合使用实现数据的同步防止出现修改数据错误的问题。常作为一种锁机制

  5. 网络socket通信:以上都是单机不同进程通信,而socket指网络间不同计算机进程通信。任何进程间都能通讯,但速度慢;

线程(进程)间同步方式

线程间没有通信,线程间通信也就是同步
https://blog.csdn.net/Mr_Xuf/article/details/107110187
https://blog.csdn.net/qq_41248872/article/details/82991949
通常线程间同步方式有四种:互斥锁,事件(条件变量),信号量,读写锁。

  1. 互斥锁:互斥锁确保同一时间只能有一个线程访问共享资源。当锁被占用时试图对其加锁的线程都进入阻塞状态(释放CPU资源使其由运行状态进入等待状态)。

  2. 事件(条件变量): 事件机制,则允许一个线程在处理完一个任务后,主动唤醒另外其他线程执行任务。或者按照条件变量的说法。条件变量与互斥量一起使用时,允许线程等待特定的条件发生。即当某些条件不满足时,线程将自己加入队列,等待该条件。另外某个线程改变了条件的状态,就可以唤醒队列中等待的线程。比如生产者消费者中,要等到有货物,消费者线程才能消费条件变量是一个显示队列:即当某些条件不满足时,线程将自己加入队列,等待该条件。另外某个线程改变了条件的状态,就可以唤醒队列中等待的线程。所以他是一种线程间通信的方式。条件变量是用来等待线程而不是上锁的,条件变量通常和互斥锁一起使用。条件变量之所以要和互斥锁一起使用,条件变量可以通过允许线程阻塞和等待另一个线程发送信号来弥补互斥锁的不足,所以互斥锁和条件变量通常一起使用。
    如直接使用mutex,除了生产者、消费者之间要竞争互斥量以外,消费者之间也需要竞争互斥量,而条件变量指定了只有某些特定条件的生产者或者消费者线程来竞争。

  3. 信号量:可以作为互斥锁,也可以作为条件变量。他既可以当做锁,可以当做条件变量。可以使用信号量通知其他线程,sem_post让信号量的值+1,然后其他线程通过sem_wait等待某个该信号量值大于0就可以执行了,并且会将该信号量值减小

(1)同步信号量,值为资源可以使用的个数,信号量小于0,则线程进行等待,信号量大于0,表示可用资源个数。初始值0.,限制线程资源访问个数
(2)互斥信号量只有两个值0或1,0表示资源正在被占用,线程等待。1表示,资源没有被使用,线程可以进入。初始值为1
信号量和互斥锁的区别:
互斥锁用于一个进程的不同线程,而信号量既可以用于进程间又可以用于线程间。当信号量用于进程间同步时,要求信号量建立在共享内存区。
互斥锁必须是谁上锁就由谁来解锁,而信号量的wait和post操作不必由同一个线程执行。

  1. 读写锁:他是锁的一种,与互斥量类似,不过读写锁允许更高的并行性。读模式共享,写模式互斥。
    读写锁也叫做共享-独占锁,当读写锁以读模式锁住时,它是以共享模式锁住的;当他以写模式锁住时,它是以独占模式锁住的。
    读写锁可以由三种状态:读模式下加锁状态、写模式下加锁状态、不加锁状态。一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。
    读写锁非常适合于对数据结构读的次数远大于写的情况。

线程的分类

https://www.cnblogs.com/Mered1th/p/10745137.html
从线程的运行空间来说,分为用户级线程和内核级线程

  • 用户态线程:用户自己创建的,比如java创建线程机制,系统对此线程不可见,用户创建线程非常简单。应用进程利用线程库提供创建、同步、调度和管理线程的函数来控制用户线程。不需要用户态/核心态切换,速度快,操作系统内核不知道多线程的存在

  • 内核态线程:由操作系统内核创建和撤销。内核维护进程及线程的上下文信息以及线程切换。

当我们创建完用户态线程的时候,操作系统会将用户态线程映射为内核态线程然后调度运行。映射方法为,1对1,多对1,多对多。一般操作系统都是使用多对多模型,也就是说比如我们创建7个用户态线程,而操作系统会创建4个内核线程对应七个线程,然后4个内核态线程分别运行在CPU四个核中。

协程(纤程)

https://zhuanlan.zhihu.com/p/169426477
定义:协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。他是用户空间线程,他对于内核来说是不可见的。
一个线程可以拥有多个协程。

轻量级:比如JVM中一个线程是重量级的,他的一个线程对应的是系统级别的一个线程。比如我开一个系统开个上千个线程基本就卡的不行了,所有的时间都用在了线程切换上。而协程他是轻量级的也就是他是在软件中切换,他不需要和内核打交道。他不像线程需要CPU去切换。所以协程切换起来速度也很快,JVM自己管理自己切换。

线程和协程的区别
  1. 包含: 一个线程可以多个协程,一个进程也可以单独拥有多个协程
  2. 资源:占用资源很少。比如CPU起一个线程就要占用1M,而协程起一个只需要几K。
  3. 调度策略:可以被调度,调度策略由应用层代码定义,而线程调度策略只能操作系统来确定
  • 场景:适合在并发量高,较短的计算任务上。go自带协程。大多数时候都是要花大量等待时间的场景,也就是所谓的IO密集,协程极为适合这种场景。
    线程在多核的环境下是能做到正意义上的并行执行的,注意,是并行,不是并发,而协程是为并发而生的。

进程(线程)调度策略

  1. FIFO先进先出:非抢占式
  2. 最短任务优先:非抢占式,有个队列,从队列中选取时间最短的先执行,但是还是会等到前面的任务执行完
  3. 最短完成时间优先:抢占式最短任务优先,每个新任务进入系统就会评估谁剩余执行时间最少就先做谁。
  4. 轮转:每个任务执行一段时间。轮转时间要考虑上下文切换成本设计轮转时间。时间公平,时间片太小,导致进程切换太频繁,进程切换时间过长,那么实时性就不能得到保证。
  5. 比例份额调度:每个进程按照时间比例,来决定执行时间长短。
  6. 多级反馈队列:
    1. 将任务放在不同队列中,每个队列有优先级,比如三个优先级队列,高中低。先执行高优先级队列的进程。
    2. 对于高优先级队列中的多个进程,我们使用时间片轮转调度。
    3. 新工作进入系统放到最高优先级
    4. 工作用完一个时间片,降低优先级。

进程(线程)有哪些状态

进程一共有5种状态,分别是创建、就绪、运行(执行)、终止、阻塞。

内存管理

什么是虚拟内存

虚拟内存就是计算机虚拟出一块给应用程序或者用户使用的内存,让物理内存扩充成更大的逻辑内存,从而让程序获得更多的可用内存

  1. 它使得应用程序认为它拥有连续可用的内存,即一个连续完整的地址空间。而实际上,它通常是被分隔成多个物理内存碎片。
  2. 操作系统只将一个程序使用的内存放到真实物理内存中,而还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。这样可以运行更多的程序。虚拟内存使用部分加载的技术,让一个进程或者资源的某些页面加载进内存,从而能够加载更多的进程,甚至能加载比内存大的进程,这样看起来好像内存变大了,这部分内存其实包含了磁盘或者硬盘,并且就叫做虚拟内存。

什么是交换空间

当内存资源不足时,Linux把某些页的内容转移至硬盘上的一块空间上,以释放内存空间。硬盘上的那块空间叫做交换空间(swap space),而这一过程被称为交换(swapping)。物理内存和交换空间的总容量就是虚拟内存的可用容量。

用途:
物理内存不足时一些不常用的页可以被交换出去,腾给系统。让系统可以运行更多的程序。
比如:程序启动时很多内存页被用来初始化,之后便不再需要,可以交换出去。

(虚拟内存)内存管理的作用
  1. 方便用户使用,每个程序从0开始编址
  2. .内存之间程序互相打扰访问。不小心访问写数据到别人的内存空间造成错误。
  3. 扩大内存:多个进程装入内存内存不够用。
实现方式

虚拟内存中,允许将一个作业分多次调入内存。釆用连续分配方式时,会使相当一部分内存空间都处于暂时或永久的空闲状态,造成内存资源的严重浪费,而且也无法从逻辑上扩大内存容量。以下三种方式:

分页存储管理。
分段存储管理。
段页式存储管理。

什么是分页

物理内存空间划分为大小相等且固定的块,作为基本单位。然后呢用户程序也划分为相同大小的块作为逻辑空间。
比如物理内存中分页4k,然后把程序页内存大小也分为4K。将物理内存的4K是不连续,可以前面一个后面一个,然后通过映射映射到逻辑内存,这个逻辑内存是连续的给用户程序使用。
Linux大多数情况下是分页存储管理

因为程序数据存储在不同的页面中,而页面又离散(不连续)的分布在物理内存中,因此需要一个页表来记录逻辑内存页与实际物理内存页的映射关系,比如下图页号1对应块号3。

访问分页系统中内存数据需要两次的内存访问 。因为页表存储在一快区域中。
一次是从内存中访问页表,从中找到指定的物理块号,加上页内偏移得到实际物理地址;
第二次就是根据物理地址访问实际内存取出数据。
在这里插入图片描述
缺点:1. 两次访问,则大大降低执行效率. 2. 容易产生页内碎片,有的页比较大,程序内存不满一个页,那么这个页内就会空出很多空间。

快表

快表TLB:提高访问速度
本身页表也比较大放在内存中。加一层高速缓存存放常用的页表。
为了减少两次访问内存导致的效率影响,分页管理中引入了快表机制,相当于再加一层(高速缓存),是一种访问速度比内存快很多的高速缓存,用来存放最近访问的页表项的副本,可以加速地址变换的速度(基本地址变换机构要访存2次)
步骤:
根据虚拟地址中的页号查快表;
如果该页在快表中,直接从快表中读取相应的物理地址;
如果该页不在快表中,就访问内存中的页表,再从页表中得到物理地址,同时将页表中的该映射表项添加到快表中;
当快表填满后,又要登记新页时,就按照一定的淘汰策略淘汰掉快表中的一个页。

什么是分段

分页是为了提高内存利用率,而分段是为了满足程序员在编写代码的时候的一些逻辑需求,比如我要使用一整段连续物理内存,而不是几个页拼成的内存。

首先作业空间,看到的段都是从0开始编址。但是其中每个段的长度可以是不一样的。

因此也存在一个逻辑地址到物理地址的映射关系,相应的就是段表机制。

段表存储段号,和段的长度,和基址(也就是实际地址)
在物理内存中,存放的是基址开始加段长的这样的内存空间。

在这里插入图片描述
缺点:产生页外碎片。分段会将物理内存空间分成不同长度的段,空间本身会碎片化。不同的段和段之间内存中的空隙比较小的话,就不能插入一个段,这样会有很多的段之前都产生碎片。

分页和分段有什么区别

  • 目的不同:分页是由于系统管理的需要而不是用户的需要,它是信息的物理单位;分段的目的是为了能更好地满足用户的需要,它是信息的逻辑单位,它含有一组其意义相对完整的信息;

  • 大小不同:页的大小固定且由系统决定,而段的长度却不固定,由其所完成的功能决定;

  • 内存碎片:页式存储管理的优点是没有外碎片(因为页的大小固定),但会产生内碎片(一个页可能填充不满);而段式管理的优点是没有内碎片(因为段大小可变,改变段大小来消除内碎片)。但段换入换出时,会产生外碎片(比如4k的段换5k的段,会产生1k的外碎片)。

段页式存储管理:

https://www.cnblogs.com/wkfvawl/p/11733057.html

先把程序的内存按段分成若干段,满足程序需求,因为程序按段来划分执行效率更高。
再把段按照页式管理分成若干页,并将页存放到物理内存中。提高计算机内存效率。
段页式管理结合了段式管理和页式管理的优点。

这样既满足了用户需求,又满足计算机需求,减少内存碎片。
页是物理单位,从物理角度
段是逻辑单位,主要时满足用户需求,段的划分是不固定的。
缺点:实现复杂,效率更低,维护成本更高。

逻辑地址和物理转换

操作系统完成:
动态重定位
每个 CPU 需要两个硬件寄存器:基址(base)寄存器和界限(bound)寄存 器,让我们能够将地址空间放在物 理内存的任何位置,同时又能确保进程只能访问自己的地址空间。进程中使用的内存引用都是虚拟地址(virtual address),硬件接下来将虚拟地址加上基 址寄存器中的内容,得到物理地址(physical address),再发给内存系统。

通过基址加虚拟地址,也就是逻辑地址加地址空间偏移量的方式,使用映射到物理地址。
通过界限寄存器就防止越界访问,如果用户程序越界访问,则CPU产生异常,操作系统就会去处理。这样就起到防止进程访问到其他地址空间访问到其他进程。

页面替换算法有哪些?

产生场景:程序只会把要使用的页放在物理内存中,而有时程序要使用的页不存在于物理内存中,会产生缺页中断,从硬盘中取得缺的页放入内存,如果这时物理内存已满,还会根据某种算将磁盘中页进行交换。

  1. FIFO:先进先出,谁先进来谁就先出去。但是可能剔除重要的页。
  2. LRU:最近使用原则:每个最近被访问的都会被提到队头,然后删除最久未访问的。1)新数据插入到链表头部;2)每当缓存命中(即缓存数据被访问),则将数据移到链表头部;3)当链表满的时候,将链表尾部的数据丢弃。
  3. LFU:最不常用算法:每个访问页计数,置换访问次数最少的页面,将计数最小的替换。
  4. 时钟置换算法(Clock):是FIFO和LRU的结合。环形链表,不再排序而是使用2个标记,0和1,第一个次加载到该页的时候,页标记被设置为0,内存中再次访问该页则标记该页面为1。当需要替换时,指针从当前位置循环查找环形链表(类似于FIFO),如果遇到标记为1的,标记为0,不替换如果遇到标记为0的,替换它。更平均公平一些。

硬链接和软链接

你可以将链接简单地理解为 Windows 中常见的快捷方式,Linux 中常用它来解决一些库版本的问题,通常也会将一些目录层次较深的文件链接到一个更易访问的目录中。在这些用途上,我们通常会使用到软链接(也称符号链接)。

在 ls 结果的最左边一列,是文件的 inode 值,你可以简单把它想成 C 语言中的指针。它指向了物理硬盘的一个区块,事实上文件系统会维护一个引用计数,只要有文件指向这个区块,它就不会从硬盘上消失。
硬链接: 与普通文件没什么不同,inode 都指向同一个文件在硬盘中的区块,类似于浅拷贝,知识拷贝指针。
软链接: 保存了其代表的文件的绝对路径,是另外一种文件,在硬盘上有独立的区块,访问时替换自身路径。类似于深拷贝,将源文件进行拷贝

用户态(模式)与内核态(模式)

用户态和系统态是操作系统的两种运行状态:

内核态:内核态运行的程序可以访问计算机的任何数据和资源,不受限制,包括外围设备,比如网卡、硬盘等。处于内核态的 CPU 可以从一个程序切换到另外一个程序,并且占用 CPU 不会发生抢占情况。切换进程,拥有最高权限。
用户态:用户态运行的程序只能受限地访问内存,只能直接读取用户程序的数据,并且不允许访问外围设备,用户态下的 CPU 不允许独占,也就是说 CPU 能够被其他程序获取。大部分用户直接面对的程序都是运行在用户态

将操作系统的运行状态分为用户态和内核态,主要是为了对访问能力进行限制,防止随意进行一些比较危险的操作导致系统的崩溃,比如设置时钟、内存清理,这些都需要在内核态下完成 。

用户模式和内核模式最根本区别就是是否拥有对硬件的控制权。

如果用户模式想操作硬件,这时操作系统可以暴露一些借口给我们,比如创建销毁进程,让用户分配更多内存等操作。等几百个API供用户模式使用。

用户态切换到内核态的3种方式

  1. 系统调用
    这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使 用操作系统提供的服务程序完成工作,比如前例中fork()实际上就是执行了一个创建新进程的系统调用。而系统调用的机制其核心还是使用了操作系统为用户 特别开放的一个中断来实现,例如Linux的int 80h中断。

  2. 外围设备的中断
    硬件中断,进程执行过程中,好比说用户点击了什么按钮,触发了按键中断,要赶紧去处理这个中断啊,保存进程上下文,切换到中断处理流程,处理完了,恢复进程上下文,返回用户态(返回之前可能会进行进程调度,选择一个更值得运行的进程投入运行态),进程继续执行

  3. 异常
    当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。

参考资料

https://github.com/cosen1024/Java-Interview/blob/main/操作系统/操作系统.md

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/430986
推荐阅读
相关标签
  

闽ICP备14008679号