关于操作系统:三天吃透操作系统面试八股文

38次阅读

共计 6971 个字符,预计需要花费 18 分钟才能阅读完成。

本文曾经收录到 Github 仓库,该仓库蕴含 计算机根底、Java 根底、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享 等外围知识点,欢送 star~

Github 地址:https://github.com/Tyson0314/Java-learning


操作系统的四个个性?

并发:同一段时间内多个程序执行(与并行辨别,并行指的是同一时刻有多个事件,多处理器零碎能够使程序并行执行)

共享:零碎中的资源能够被内存中多个并发执行的进线程独特应用

虚构:通过分时复用(如分时系统)以及空分复用(如虚拟内存)技术把一个物理实体虚构为多个

异步:零碎过程用一种走走停停的形式执行,(并不是一下子走完),过程什么时候以怎么的速度向前推动是不可预知的

过程线程

过程是指一个内存中运行的应用程序,每个过程都有本人独立的一块内存空间。

线程是比过程更小的执行单位,它是在一个过程中独立的控制流,一个过程能够启动多个线程,每条线程并行执行不同的工作。

过程和线程的区别如下

  • 调度:过程是资源管理的根本单位,线程是程序执行的根本单位。
  • 切换:线程上下文切换比过程上下文切换要快得多。
  • 领有资源:过程是领有资源的一个独立单位,线程不领有系统资源,然而能够拜访隶属于过程的资源。
  • 零碎开销:创立或撤销过程时,零碎都要为之调配或回收系统资源,如内存空间,I/ O 设施等,OS 所付出的开销显著大于在创立或撤销线程时的开销,过程切换的开销也远大于线程切换的开销。

并发和并行

并发就是在一段时间内,多个工作都会被解决;但在某一时刻,只有一个工作在执行。单核处理器能够做到并发。比方有两个过程 ABA运行一个工夫片之后,切换到 BB 运行一个工夫片之后又切换到A。因为切换速度足够快,所以宏观上体现为在一段时间内能同时运行多个程序。最全面的 Java 面试网站

并行就是在同一时刻,有多个工作在执行。这个须要多核处理器能力实现,在宏观上就能同时执行多条指令,不同的程序被放到不同的处理器上运行,这个是物理上的多个过程同时进行。

多线程相较单线程的益处

1、并发晋升程序执行效率

2、晋升 CPU 利用率,访存的时候能够切换线程来执行

3、更快的响应速度,能够有专门的线程来监听用户申请和专门的线程来解决申请。比方监听线程和工作线程是两个线程,这样监听就负责监听,工作的就负责工作,监听到用户申请马上把申请转到工作线程去解决,监听线程持续监听

什么是协程?

协程是一种用户态的轻量级线程。

协程不是由操作系统内核治理,而是齐全由用户程序所管制,这样带来的益处就是性能失去了很大的晋升,不会像线程切换那样耗费资源。

协程能够了解为能够暂停执行的函数。它领有本人的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保留到其余中央,在切回来的时候,复原先前保留的寄存器上下文和栈,间接操作栈则根本没有内核切换的开销,能够不加锁的拜访全局变量,所以上下文的切换十分快。

线程和协程有什么区别呢?

1、线程是抢占式,而协程是非抢占式的,所以须要用户本人开释使用权来切换到其余协程,因而同一时间其实只有一个协程领有运行权,相当于单线程的能力。
2、线程是协程的资源。协程通过 能够关联任意线程或线程池的执行器(Interceptor)来间接应用线程的资源的。

过程通信

过程间通信形式有以下几种:

1、管道通信

匿名管道(pipe):管道是一种半双工的通信形式,数据只能单向流动,而且只能在具备亲缘关系的过程间应用。过程的亲缘关系通常是指父子过程关系。
有名管道是半双工的通信形式,数据只能单向流动。

2、音讯队列

3、共享内存。共享内存是最快的 IPC 形式,它是针对其余过程间通信形式运行效率低而专门设计的。它往往与其余通信机制,如信号量,配合应用,来实现过程间的同步和通信。

4、信号量。信号量是一个计数器,能够用来管制多个过程对共享资源的拜访。它常作为一种锁机制,避免某过程正在访问共享资源时,其余过程也拜访该资源。因而,次要作为过程间以及同一过程内不同线程之间的同步伎俩。

什么是死锁?

死锁是指两个或两个以上的线程在执行过程中,因抢夺资源而造成的一种相互期待的景象。若无外力作用,它们都将无奈推动上来。

如下图所示,线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方持有的资源,所以这两个线程就会相互期待而进入死锁状态。

上面通过例子阐明线程死锁,代码来自并发编程之美。

public class DeadLockDemo {private static Object resource1 = new Object();// 资源 1
    private static Object resource2 = new Object();// 资源 2

    public static void main(String[] args) {new Thread(() -> {synchronized (resource1) {System.out.println(Thread.currentThread() + "get resource1");
                try {Thread.sleep(1000);
                } catch (InterruptedException e) {e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting get resource2");
                synchronized (resource2) {System.out.println(Thread.currentThread() + "get resource2");
                }
            }
        }, "线程 1").start();

        new Thread(() -> {synchronized (resource2) {System.out.println(Thread.currentThread() + "get resource2");
                try {Thread.sleep(1000);
                } catch (InterruptedException e) {e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting get resource1");
                synchronized (resource1) {System.out.println(Thread.currentThread() + "get resource1");
                }
            }
        }, "线程 2").start();}
}

代码输入如下:

Thread[线程 1,5,main]get resource1
Thread[线程 2,5,main]get resource2
Thread[线程 1,5,main]waiting get resource2
Thread[线程 2,5,main]waiting get resource1

线程 A 通过 synchronized (resource1) 取得 resource1 的监视器锁,而后通过 Thread.sleep(1000)。让线程 A 休眠 1s 为的是让线程 B 失去执行而后获取到 resource2 的监视器锁。线程 A 和线程 B 休眠完结了都开始希图申请获取对方的资源,而后这两个线程就会陷入相互期待的状态,这也就产生了死锁。

死锁怎么产生?怎么防止?

死锁产生的四个必要条件

  • 互斥:一个资源每次只能被一个过程应用
  • 申请与放弃:一个过程因申请资源而阻塞时,不开释取得的资源
  • 不剥夺:过程已取得的资源,在未应用之前,不能强行剥夺
  • 循环期待:过程之间循环期待着资源

防止死锁的办法

  • 互斥条件不能毁坏,因为加锁就是为了保障互斥
  • 一次性申请所有的资源,防止线程占有资源而且在期待其余资源
  • 占有局部资源的线程进一步申请其余资源时,如果申请不到,被动开释它占有的资源
  • 按序申请资源

过程调度策略有哪几种?

  • 先来先服务 :非抢占式的调度算法,依照申请的程序进行调度。有利于长作业,但不利于短作业,因为短作业必须始终期待后面的长作业执行结束能力执行,而长作业又须要执行很长时间,造成了短作业等待时间过长。另外,对I/O 密集型过程也不利,因为这种过程每次进行 I/O 操作之后又得从新排队。
  • 短作业优先:非抢占式的调度算法,按预计运行工夫最短的程序进行调度。长作业有可能会饿死,处于始终期待短作业执行结束的状态。因为如果始终有短作业到来,那么长作业永远得不到调度。
  • 最短剩余时间优先:最短作业优先的抢占式版本,按残余运行工夫的程序进行调度。当一个新的作业达到时,其整个运行工夫与以后过程的剩余时间作比拟。如果新的过程须要的工夫更少,则挂起以后过程,运行新的过程。否则新的过程期待。
  • 工夫片轮转:将所有就绪过程按 FCFS 的准则排成一个队列,每次调度时,把 CPU 工夫调配给队首过程,该过程能够执行一个工夫片。当工夫片用完时,由计时器收回时钟中断,调度程序便进行该过程的执行,并将它送往就绪队列的开端,同时持续把 CPU 工夫调配给队首的过程。

    工夫片轮转算法的效率和工夫片的大小有很大关系:因为过程切换都要保留过程的信息并且载入新过程的信息,如果工夫片太小,会导致过程切换得太频繁,在过程切换上就会花过多工夫。而如果工夫片过长,那么实时性就不能失去保障。

  • 优先级调度:为每个过程调配一个优先级,按优先级进行调度。为了避免低优先级的过程永远等不到调度,能够随着工夫的推移减少期待过程的优先级。

过程有哪些状态?

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

  • 运行状态就是过程正在 CPU 上运行。在单处理机环境下,每一时刻最多只有一个过程处于运行状态。
  • 就绪状态就是说过程已处于筹备运行的状态,即过程取得了除 CPU 之外的所有所需资源,一旦失去 CPU 即可运行。
  • 阻塞状态就是过程正在期待某一事件而暂停运行,比方期待某资源为可用或期待 I/O 实现。即便 CPU 闲暇,该过程也不能运行。

运行态→阻塞态 :往往是因为期待外设,期待主存等资源分配或期待人工干预而引起的。
阻塞态→就绪态 :则是期待的条件已满足,只需调配到处理器后就能运行。
运行态→就绪态 :不是因为本身起因,而是由外界起因使运行状态的过程让出处理器,这时候就变成就绪态。例如工夫片用完,或有更高优先级的过程来抢占处理器等。
就绪态→运行态:零碎按某种策略选中就绪队列中的一个过程占用处理器,此时就变成了运行态。

操作系统里的内存碎片怎么了解?

内存碎片通常分为外部碎片和内部碎片:

  1. 外部碎片是因为采纳固定大小的内存分区,当一个过程不能齐全应用分给它的固定内存区域时就会产生外部碎片。通常外部碎片难以完全避免
  2. 内部碎片是因为某些未调配的间断内存区域太小,以至于不能满足任意过程的内存调配申请,从而不能被过程利用的内存区域。

有什么解决办法

当初广泛采取的内存调配形式是段页式内存调配。将内存分为不同的段,再将每一段分成固定大小的页。通过页表机制,使段内的页能够不用间断处于同一内存区域。

虚拟内存

虚拟存储器就是具备申请调入性能,能从逻辑上对内存容量加以裁减的一种存储器零碎,虚拟内存有屡次性,对换性和虚拟性三个特色,它能够将程序分屡次调入内存,使得在较小的用户空间能够执行较大的用户程序,所以同时包容更多的过程并发执行,从而进步零碎的吞吐量。产生缺页时能够调入一个段也能够调入一个页,取决于内存的存储管理形式。虚拟性示意虚拟内存和物理内存的映射。

Linux 下,过程不能间接读写内存物理地址,只能拜访【虚拟内存地址】。操作系统会把虚拟内存地址 –> 物理地址。

虚拟内存解决无限的内存空间加载较大应用程序的问题,依据须要在内存和磁盘之间来回传送数据。

通过段页表的模式,虚拟内存中取一段间断的内存空间映射到主内存中,主内存空间的程序段能够不间断。

什么是分页?

把内存空间划分为 大小相等且固定的块 ,作为主存的根本单位。因为程序数据存储在不同的页面中,而页面又离散的散布在内存中, 因而须要一个页表来记录映射关系,以实现从页号到物理块号的映射。

拜访分页零碎中内存数据须要 两次的内存拜访 (一次是从内存中拜访页表,从中找到指定的物理块号,加上页内偏移失去理论物理地址;第二次就是依据第一次失去的物理地址拜访内存取出数据)。

什么是分段?

分页是为了进步内存利用率,而分段是为了满足程序员在编写代码的时候的一些逻辑需要(比方数据共享,数据保护,动静链接等)。

分段内存治理当中,地址是二维的,一维是段号,二维是段内地址;其中每个段的长度是不一样的,而且每个段外部都是从 0 开始编址的。因为分段治理中,每个段外部是间断内存调配,然而段和段之间是离散调配的,因而也存在一个逻辑地址到物理地址的映射关系,相应的就是段表机制。

分页和分段有什区别?

  • 分页对程序员是通明的,然而分段须要程序员显式划分每个段。
  • 分页的地址空间是一维地址空间,分段是二维的。
  • 页的大小不可变,段的大小能够动静扭转。
  • 分页次要用于实现虚拟内存,从而取得更大的地址空间;分段次要是为了使程序和数据能够被划分为逻辑上独立的地址空间并且有助于共享和爱护。

页面置换算法

为什么要页面置换:

因为应用程序是分屡次装入内存的,所以运行到肯定的工夫,肯定会产生缺页。地址映射的过程中,如果页面中发现要拜访的页面不在内存中,会产生缺页中断。此时操作系统必须在内存里抉择一个页面把他移出内存,为行将调入的页面让出空间。抉择淘汰哪一页的规定就是页面置换算法

几种页面置换算法:

最佳置换算法(现实):将以后页面中在将来最长工夫内不会被拜访的页置换进来

先进先出:淘汰最早调入的页面

最近最久未应用 LRU:每个页面有一个 t 来记录上次页面被拜访直到现在,每次置换时置换 t 值最大的页面(用寄存器或栈实现)

时钟算法 clock(也被称为最近未应用算法 NRU):页面设置拜访为,将页面链接为一个环形列表,每个页有一个拜访位 0 /1, 1 示意又一次获救的机会,下次循环指针指向它时能够罢黜此次置换,然而会把拜访地位为 0,代表他下次如果碰到循环指针就该被置换了。页面被拜访的时候拜访位设为 1。页面置换的时候,如果以后指针的拜访位为 0,置换,否则将这个值置为 0,循环直到遇到拜访位为 0 的页面。

改进型 Clock 算法:在 clock 算法的根底上增加一个批改位,优先替换拜访位和批改位都是 0 的页面,其次替换拜访位为 0 批改位为 1 的页面。

起码应用算法 LFU:设置寄存器记录页面被拜访次数,每次置换以后拜访次数起码的。

用户态和内核态

内核态:cpu 能够拜访内存的所有数据,包含外围设备,例如硬盘,网卡,cpu 也能够将本人从一个程序切换到另一个程序。

用户态:只能受限的拜访内存,且不容许拜访外围设备,占用 cpu 的能力被剥夺,cpu 资源能够被其余程序获取。

最大的区别就是权限不同,在运行在用户态下的程序不能间接拜访操作系统内核数据结构和程序。

为什么要有这两种状态?

内核速度快然而资源无限,能管制的过程数不多,所以须要速度慢一些的用户态帮助,然而为了防止用户态被歹意利用,所以限度了用户态程序的权限。

须要限度不同的程序之间的拜访能力,避免他们获取别的程序的内存数据,或者获取外围设备的数据,并发送到网络,CPU 划分出 两个权限等级 — 用户态和内核态。

什么时候转换

1、零碎调用

用户过程被动发动的。用户态过程通过零碎调用申请应用操作系统提供的服务程序实现工作,比方 fork()就是执行一个创立新过程的零碎调用

用户程序应用零碎调用,零碎调用会转换为内核态并调用操作系统

2、产生异样

会从以后运行过程切换到解决次此异样的内核相干程序中

3、外围设备的中断:

所有程序都运行在用户态,但在从硬盘读取数据、或从键盘输入时,这些事件只有操作系统能做,程序须要向操作系统申请以程序的名义来执行这些操作。这个时候用户态程序切换到内核态。

什么是缓冲区溢出?有什么危害?

缓冲区溢出是指当计算机向缓冲区填充数据时超出了缓冲区自身的容量,溢出的数据笼罩在非法数据上。

危害有以下两点:

  • 程序解体,导致回绝额服务
  • 跳转并且执行一段恶意代码

造成缓冲区溢出的次要起因是程序中没有仔细检查用户输出。

IO 多路复用

IO 多路复用是指内核一旦发现过程指定的一个或者多个 IO 条件筹备读取,它就告诉该过程。IO 多路复用实用如下场合

  • 当客户解决多个形容字时(个别是交互式输出和网络套接口),必须应用 I / O 复用。
  • 当一个客户同时解决多个套接口时,而这种状况是可能的,但很少呈现。
  • 如果一个 TCP 服务器既要解决监听套接口,又要解决已连贯套接口,个别也要用到 I / O 复用。
  • 如果一个服务器即要解决 TCP,又要解决 UDP,个别要应用 I / O 复用。
  • 如果一个服务器要解决多个服务或多个协定,个别要应用 I / O 复用。
  • 与多过程和多线程技术相比,I/ O 多路复用技术的最大劣势是零碎开销小,零碎不用创立过程 / 线程,也不用保护这些过程 / 线程,从而大大减小了零碎的开销。

最初给大家分享一个 Github 仓库,下面有大彬整顿的 300 多本经典的计算机书籍 PDF,包含 C 语言、C++、Java、Python、前端、数据库、操作系统、计算机网络、数据结构和算法、机器学习、编程人生 等,能够 star 一下,下次找书间接在下面搜寻,仓库继续更新中~

Github 地址:https://github.com/Tyson0314/java-books

正文完
 0