操作系统八股文

1.操作系统概述

1.1 系统调用、用户态和核心态

根据进程访问资源的特点,我们可以把进程在系统上的运行分为两个级别:

  1. 用户态(user mode) : 用户态运行的进程可以直接读取用户程序的数据。
  2. 系统态(kernel mode):可以简单的理解系统态运行的进程或程序几乎可以访问计算机的任何资源,不受限制。

在我们运行的用户程序中,凡是与系统态级别的资源有关的操作(如文件管理、进程控制、内存管理等),都必须通过系统调用方式向操作系统提出服务请求,并由操作系统代为完成。
这些系统调用按功能大致可分为如下几类:

  • 设备管理。完成设备的请求或释放,以及设备启动等功能。
  • 文件管理。完成文件的读、写、创建及删除等功能。
  • 进程控制。完成进程的创建、撤销、阻塞及唤醒等功能。
  • 进程通信。完成进程之间的消息传递或信号传递等功能。
  • 内存管理。完成内存的分配、回收以及获取作业占用内存区大小及地址等功能。

1.3 那么如何从用户态切换到内核态呢? (仅需知道是这哪三种就可以了,其实一般只会问到举几个系统调用)

系统调用
这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作,比如fork()实际上就是执行了一个创建新进程的系统调用。而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现,例如Linux的int 80h中断。
异常
当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。
外围设备的中断
当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。

2.进程管理

2.1 线程、进程、协程的区别

线程是进程划分成的更小的运行单位,一个进程在其执行的过程中可以产生多个线程。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反。
协程是一种用户态的轻量级线程,协程的调度完全由用户控制。一个线程可以拥有多个协程,协程不是被操作系统内核所管理,而完全是由程序所控制。与其让操作系统调度,不如我自己来,这就是协程
总结:协程最主要的作用是在单线程的条件下实现并发的效果,但实际上还是串行的(像yield一样)一个线程可以拥有多个协程,协程不是被操作系统内核所管理,而完全是由程序所控制。

2.2 PCB是什么?(我字节同一个岗的连着两面被问到PCB)

为了使参与并发执行的每个程序,包含数据都能独立地运行,在操作系统中必须为之配置一个专门的数据结构,称为进程控制块(PCB,Process Control Block)。进程与PCB是一一对应的,用户进程不能修改。

2.3 进程和线程创建和撤销的过程中发生了什么事情? (问的不多,我被问到过一次)

进程的创建

  1. 分配空间:操作系统为进程分配一块内存空间,用于存储代码、数据和执行状态。
  2. 加载程序:将进程的程序代码和数据从磁盘加载到内存中,使得进程可以开始执行。
  3. 初始化进程控制块(PCB):操作系统为进程创建一个进程控制块(PCB),用于记录进程的各种状态和信息,例如进程ID、进程优先级、进程状态等等。
  4. 设置进程优先级:操作系统根据进程的类型和要求设置进程的优先级,以便决定进程在系统中的执行顺序。
  5. 将进程添加到就绪队列:操作系统将进程添加到就绪队列中,以便在系统调度器的调度下,进程可以被分配CPU资源开始执行。

进程的撤销

  1. 终止进程:当进程完成执行,或者由于某些原因需要被强制终止时,操作系统将终止进程的执行。
  2. 释放资源:操作系统释放进程所占用的资源,包括内存、打开的文件、网络连接等等。
  3. 更新进程状态:操作系统更新进程的状态,例如将进程的状态从“运行中”变为“已终止”。
  4. 回收进程控制块:操作系统回收进程控制块(PCB),释放进程占用的内存。

线程的创建

  1. 初始化线程控制块(TCB):操作系统为线程创建一个线程控制块(TCB),用于记录线程的状态和信息,例如线程ID、线程优先级、线程状态等等。
  2. 分配栈空间:每个线程都需要一个独立的栈空间,用于存储线程执行的上下文信息。
  3. 设置线程优先级:操作系统根据线程的类型和要求设置线程的优先级,以便决定线程在系统中的执行顺序。
  4. 将线程添加到就绪队列:操作系统将线程添加到就绪队列中,以便在系统调度器的调度下,线程可以被分配CPU资源开始执行。

线程的撤销

  1. 中止线程执行:操作系统通过向线程发送一个中止信号来中止线程的执行,线程在接收到中止信号后会停止执行。
  2. 释放资源:线程所占用的资源包括栈空间、寄存器内容等等,需要被释放,以便其他线程可以使用。
  3. 更新线程状态:操作系统会将线程的状态从“运行中”或“就绪”状态修改为“终止”状态。
  4. 回收线程控制块:操作系统会回收线程控制块(TCB),释放线程占用的内存空间。
  5. 处理线程的退出代码:线程在退出时可以返回一个退出代码,操作系统会将这个退出代码传递给父线程或主线程,以便进行进一步的处理。

2.4 进程的五种状态

进程全部可分为五种状态分别是:创建状态、就绪状态、运行状态、阻塞状态、终止状态。 在运行期间主要是三种状态:就绪、运行、阻塞状态。

2.5 进程调度算法(高频考点)

  • 先到先服务(FCFS)调度算法 : 从就绪队列中选择一个最先进入该队列的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。
  • 短作业优先(SJF)的调度算法 : 从就绪队列中选出一个估计运行时间最短的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。
  • 时间片轮转调度算法:时间片轮转调度是一种最古老,最简单,最公平且使用最广的算法,又称 RR(Round robin)调度。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。
  • 多级反馈队列调度算法:前面介绍的几种进程调度的算法都有一定的局限性。如短进程优先的调度算法,仅照顾了短进程而忽略了长进程 。多级反馈队列调度算法既能使高优先级的作业得到响应又能使短作业(进程)迅速完成。,因而它是目前被公认的一种较好的进程调度算法,UNIX 操作系统采取的便是这种调度算法。
  • 优先级调度 :为每个流程分配优先级,首先执行具有最高优先级的进程,依此类推。具有相同优先级的进程以 FCFS 方式执行。可以根据内存要求,时间要求或任何其他资源要求来确定优先级。

https://www.cnblogs.com/xiaolincoding/p/13631224.html

2.6 进程同步的方式

  • 互斥量(Mutex):采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问。比如 Java 中的 synchronized 关键词和各种 Lock 都是这种机制。
  • 信号量(Semaphore) :它允许同一时刻多个线程访问同一资源,但是需要控制同一时刻访问此资源的最大线程数量。
  • 事件(Event) :Wait/Notify:通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作。

2.7 进程间通信的方式(重要!)

  • 管道/匿名管道(Pipes) :用于具有亲缘关系的父子进程间或者兄弟进程之间的通信。
  • 有名管道(Named Pipes) : 匿名管道由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道。有名管道严格遵循先进先出(first in first out)。有名管道以磁盘文件的方式存在,可以实现本机任意两个进程通信。
  • 信号(Signal) :信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生;
  • 消息队列(Message Queuing) :消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识。管道和消息队列的通信数据都是先进先出的原则。与管道(无名管道:只存在于内存中的文件;命名管道:存在于实际的磁盘介质或者文件系统)不同的是消息队列存放在内核中,只有在内核重启(即,操作系统重启)或者显式地删除一个消息队列时,该消息队列才会被真正的删除。消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取.比 FIFO 更有优势。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。
  • 信号量(Semaphores) :信号量是一个计数器,用于多进程对共享数据的访问,信号量的意图在于进程间同步。这种通信方式主要用于解决与同步相关的问题并避免竞争条件。
  • 共享内存(Shared memory) :使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。可以说这是最有用的进程间通信方式。
  • 套接字(Sockets) : 此方法主要用于在客户端和服务器之间通过网络进行通信。套接字是支持 TCP/IP 的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。

2.8 生产者消费者模型(要会写伪代码)

image.png
该问题需要注意的几点:

  • 在缓冲区为空时,消费者不能再进行消费
  • 在缓冲区为满时,生产者不能再进行生产
  • 在一个线程进行生产或消费时,其余线程不能再进行生产或消费等操作,即保持线程间的同步
  • 注意条件变量与互斥锁的顺序

由于前两点原因,因此需要保持线程间的同步,即一个线程消费(或生产)完,其他线程才能进行竞争CPU,获得消费(或生产)的机会。对于这一点,可以使用条件变量进行线程间的同步:生产者线程在product之前,需要wait直至获取自己所需的信号量之后,才会进行product的操作;同样,对于消费者线程,在consume之前需要wait直到没有线程在访问共享区(缓冲区),再进行consume的操作,之后再解锁并唤醒其他可用阻塞线程。
伪代码实现
假设缓冲区大小为10,生产者、消费者线程若干。生产者和消费者相互等效,只要缓冲池未满,生产者便可将消息送入缓冲池;只要缓冲池未空,消费者便可从缓冲池中取走一个消息。

items代表缓冲区已经使用的资源数,spaces代表缓冲区可用资源数
mutex代表互斥锁
buf[10] 代表缓冲区,其内容类型为item
in、out代表第一个资源和最后一个资源

var items = 0, space = 10, mutex = 1;
var in = 0, out = 0;
item buf[10] = { NULL };

producer {
    while( true ) {
        wait( space );  // 等待缓冲区有空闲位置, 在使用PV操作时,条件变量需要在互斥锁之前
        wait( mutex );  // 保证在product时不会有其他线程访问缓冲区

        // product
        buf.push( item, in );  // 将新资源放到buf[in]位置 
        in = ( in + 1 ) % 10;
        
        signal( mutex );  // 唤醒的顺序可以不同
        signal( items );  // 通知consumer缓冲区有资源可以取走
    }
}

consumer {
    while( true ) {
        wait( items );  // 等待缓冲区有资源可以使用
        wait( mutex );  // 保证在consume时不会有其他线程访问缓冲区

        // consume
        buf.pop( out );  // 将buf[out]位置的的资源取走
        out = ( out + 1 ) % 10;

        signal( mutex );  // 唤醒的顺序可以不同
        signal( space );  // 通知缓冲区有空闲位置
    }
}

不能将线程里两个wait的顺序调换否则会出现死锁。例如(调换后),将consumer的两个wait调换,在producer发出signal信号后,如果producer线程此时再次获得运行机会,执行完了wait(space),此时,另一个consumer线程获得运行机会,执行了 wait(mutex) ,如果此时缓冲区为空,那么consumer将会阻塞在wait(items),而producer也会因为无法获得锁的所有权所以阻塞在wait(mutex),这样两个线程都在阻塞,也就造成了死锁。

2.9 死锁

2.9.1 产生死锁的四个必要条件是什么?

  • 互斥:资源必须处于非共享模式,即一次只有一个进程可以使用。如果另一进程申请该资源,那么必须等待直到该资源被释放为止。
  • 占有并等待:一个进程至少应该占有一个资源,并等待另一资源,而该资源被其他进程所占有。
  • 非抢占:资源不能被抢占。只能在持有资源的进程完成任务后,该资源才会被释放。
  • 循环等待:有一组等待进程 {P0, P1,…, Pn}, P0 等待的资源被 P1 占有,P1 等待的资源被 P2 占有,…,Pn-1 等待的资源被 Pn 占有,Pn 等待的资源被 P0 占有。

这四个条件是产生死锁的 必要条件 ,也就是说只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。

2.9.2 如何避免死锁的发生

通过算法,其中最具有代表性的 避免死锁算法 就是 Dijkstra 的银行家算法,银行家算法用一句话表达就是:当一个进程申请使用资源的时候,银行家算法 通过先 试探 分配给该进程资源,然后通过 安全性算法 判断分配后系统是否处于安全状态,若不安全则试探分配作废,让该进程继续等待,若能够进入到安全的状态,则就真的分配资源给该进程

2.9.3 解决死锁的方法

解决死锁的方法可以从多个角度去分析,一般的情况下,有预防,避免,检测和解除四种

  • 预防 是采用某种策略,限制并发进程对资源的请求,从而使得死锁的必要条件在系统执行的任何时间上都不满足。
  • 避免则是系统在分配资源时,根据资源的使用情况提前做出预测,从而避免死锁的发生
  • 检测是指系统设有专门的机构,当死锁发生时,该机构能够检测死锁的发生,并精确地确定与死锁有关的进程和资源。
  • 解除 是与检测相配套的一种措施,用于将进程从死锁状态下解脱出来

2.9.4 死锁的例子

就是线程1持有对象1的锁,线程2持有对象2的锁,然后两者都等待对方释放其持有对象的锁,然后一直等,等到死亡。

public class DieLock {
 
    public static Object t1 = new Object();
    public static Object t2 = new Object();
 
    public static void main(String[] args){
        new Thread(){
            @Override
            public void run(){
                synchronized (t1){
                    System.out.println("Thread1 get t1");
 
                    try {
                        Thread.sleep(100);
                    }catch (Exception e){
 
                    }
 
                    synchronized (t2){
                        System.out.println("Thread2 get t2");
                    }
                }
            }
        }.start();
 
        new Thread(){
            @Override
            public void run(){
                synchronized (t2){
                    System.out.println("Thread2 get t2");
 
                    try {
                        Thread.sleep(100);
                    }catch (Exception e){
 
                    }
 
                    synchronized (t1){
                        System.out.println("Thread2 get t1");
                    }
                }
            }
        }.start();
    }
}

3.内存管理(重中之重)

3.1内存管理到底是什么的?

操作系统的内存管理主要负责内存的分配与回收(malloc 函数:申请内存,free 函数:释放内存),另外地址转换也就是将逻辑地址转换成相应的物理地址等功能也是操作系统内存管理做的事情。

3.2 内存管理分页的基础知识(有很多概念非常容易混淆,所以统一放在这了,面试并不会问)

3.3 讲讲内存管理的几种机制

简单分为连续分配管理方式非连续分配管理方式这两种。连续分配管理方式是指为一个用户程序分配一个连续的内存空间,常见的如 块式管理 。同样地,非连续分配管理方式允许一个程序使用的内存分布在离散或者说不相邻的内存中,常见的如页式管理段式管理

  1. 块式管理 :远古时代的计算机操作系统的内存管理方式。将内存分为几个固定大小的块,每个块中只包含一个进程。如果程序运行需要内存的话,操作系统就分配给它一块,如果程序运行只需要很小的空间的话,分配的这块内存很大一部分几乎被浪费了。这些在每个块中未被利用的空间,我们称之为碎片。
  2. 页式管理 :把主存分为大小相等且固定的一页一页的形式,页较小,相比于块式管理的划分粒度更小,提高了内存利用率,减少了碎片。页式管理通过页表对应逻辑地址和物理地址。
  3. 段式管理 :页式管理虽然提高了内存利用率,但是页式管理其中的页并无任何实际意义。 段式管理把主存分为一段段的,段是有实际意义的,每个段定义了一组逻辑信息,例如,有主程序段 MAIN、子程序段 X、数据段 D 及栈段 S 等。 段式管理通过段表对应逻辑地址和物理地址。

4.段页式管理机制 :段页式管理机制结合了段式管理和页式管理的优点。简单来说段页式管理机制就是把主存先分成若干段,每个段又分成若干页,也就是说 段页式管理机制 中段与段之间以及段的内部的都是离散的。
简单来说:页是物理单位,段是逻辑单位。分页可以有效提高内存利用率,分段可以更好满足用户需求。

3.4 分页和分段有什么区别呢?

共同点

  • 分页机制和分段机制都是为了提高内存利用率,减少内存碎片。
  • 页和段都是离散存储的,所以两者都是离散分配内存的方式。但是,每个页和段中的内存是连续的。

区别

  • 页的大小是固定的,由操作系统决定;而段的大小不固定,取决于我们当前运行的程序。
  • 分页仅仅是为了满足操作系统内存管理的需求,而段是逻辑信息的单位,在程序中可以体现为代码段,数据段,能够更好满足用户的需要。

3.5 讲讲分页管理的快表和多级页表(按照why how的方式来回答,即为什么出现快表,是如何解决痛点

原理必看
分页是一种操作系统中的内存管理技术,将物理内存划分为固定大小的页框,同时将逻辑地址空间分割成相同大小的页。分页管理的核心是通过页表来映射逻辑地址和物理地址。
在分页管理中,由于页表需要频繁地进行访问,因此使用快表可以显著提高分页效率。快表是页表的一种缓存,保存了最近使用的一些页表项,这些页表项可以直接用于地址转换,从而避免了访问主页表的开销。
多级页表是一种页表的组织方式,将一张大的页表分成多个小的页表,每个小的页表称为页目录,其中每个页目录项指向一个页表,每个页表中的页表项则指向实际的物理页框。这样做可以有效地减小页表的大小,从而节约内存空间。此外,多级页表还可以支持虚拟地址空间的非连续分配,允许虚拟地址空间被划分为多个区域,每个区域使用不同的页表。
多级页表和快表的组合是现代操作系统中常用的内存管理技术,可以显著提高分页效率和节约内存空间。在具体实现中,不同的操作系统会有不同的设计方案和参数设置,例如页表的大小、页目录的数量等等

3.5.1 快表(是一个高速缓冲寄存器)

具体原理
操作系统的快表(Translation Lookaside Buffer, TLB)是一种缓存机制,用于加速虚拟内存到物理内存地址的转换过程。
当CPU访问内存时,它会先尝试从TLB中查找相应的内存地址是否已经被缓存。如果TLB中存在该地址的映射,CPU就可以直接使用这个映射而不必访问内存管理单元(MMU)进行地址转换。这种方式比每次都访问MMU进行地址转换要快得多,因为TLB通常被设计成位于CPU内部或非常接近CPU,可以直接从快速缓存中访问。
如果CPU在TLB中找不到所需的内存地址映射,则需要向MMU发出请求,获取该地址的物理内存地址,然后把该映射添加到TLB中。由于TLB的大小有限,当TLB中的所有项都被使用并且没有可用项时,CPU需要把一些现有的TLB项淘汰以腾出空间。
快表通常被用于支持虚拟内存系统,在这种系统中,每个进程都认为它有自己的一块连续内存地址空间,但实际上它们共享物理内存。TLB的作用是把虚拟地址转换为物理地址,从而支持多个进程同时使用物理内存。

3.5.2 多级页表

多级页表是一种用于管理虚拟内存的数据结构,它把虚拟地址空间划分成多个层级的页表。每个页表包含一组页表项,每个页表项记录了虚拟地址与物理地址之间的映射关系。通过多级页表,操作系统可以支持更大的虚拟地址空间,并且可以更高效地管理和访问页表。
多级页表通常由一个或多个页目录表和一个页表组成。页目录表包含一组页目录项,每个页目录项指向一个页表。页表包含一组页表项,每个页表项记录了一个虚拟页的映射信息。当一个进程访问虚拟地址时,操作系统会先根据虚拟地址的高位找到对应的页目录项,然后根据页目录项中的指针找到对应的页表,最后根据虚拟地址的低位在页表中查找对应的页表项,以获取物理地址。
多级页表的主要优点是它可以有效地降低页表的空间需求。由于虚拟地址空间非常大,单个页表的大小可能会非常大,需要大量的页表项。使用多级页表,操作系统可以把虚拟地址空间划分成多个部分,每个部分由一个小的页表来管理,这样可以大大降低单个页表的大小。同时,多级页表还可以提高访问页表的效率,因为只有在必要时才需要访问每个级别的页表。

3.6 讲讲虚拟地址和物理地址? 为什么要有虚拟地址空间?

虚拟地址和物理地址都是计算机系统中用于访问内存的地址。
物理地址是指计算机内存中的实际物理位置,它是唯一的,直接指向存储器中的物理位置。
虚拟地址是指由CPU生成的地址,它不直接指向实际物理位置,而是由操作系统和硬件协作完成地址映射,将虚拟地址映射到物理地址。虚拟地址空间是操作系统给每个进程分配的独立地址空间,每个进程都可以认为自己独占整个内存空间,而不必担心与其他进程的内存冲突。操作系统通过虚拟地址来管理进程的内存访问,实现内存保护和内存共享等功能。
为什么要有虚拟地址空间呢?主要有以下几个原因:

  1. 提高内存利用率:由于虚拟地址是由操作系统进行映射的,不同的进程可以共享物理内存,从而提高内存利用率。
  2. 提高安全性:操作系统可以通过虚拟地址来实现进程间的内存隔离和保护,从而防止恶意程序对其他程序的内存进行破坏。
  3. 简化程序设计:虚拟地址使得程序员可以使用连续的地址空间来访问内存,而不必担心实际的物理地址是如何分配的。
  4. 方便内存扩展:操作系统可以在物理内存不足时,将一部分虚拟地址空间映射到硬盘上的交换文件中,从而扩展可用内存。

总之,虚拟地址空间的引入可以使得操作系统更好地管理内存,提高内存利用率和安全性,简化程序设计,同时也方便了内存扩展。

3.7 讲讲虚拟内存? (程序的局部性原理)

这个在我们平时使用电脑特别是 Windows 系统的时候太常见了。很多时候我们使用了很多占内存的软件,这些软件占用的内存可能已经远远超出了我们电脑本身具有的物理内存。为什么可以这样呢? 正是因为 虚拟内存 的存在,通过 虚拟内存 可以让程序拥有超过系统物理内存大小的可用内存空间。另外,虚拟内存为每个进程提供了一个一致的、私有的地址空间,它让每个进程产生了一种自己在独享主存的错觉(每个进程拥有一片连续完整的内存空间)。这样会更加有效地管理内存并减少出错。
虚拟内存是计算机系统内存管理的一种技术,我们可以手动设置自己电脑的虚拟内存。不要单纯认为虚拟内存只是“使用硬盘空间来扩展内存“的技术。虚拟内存的重要意义是它定义了一个连续的虚拟地址空间,并且把内存扩展到硬盘空间

3.8 虚拟内存的实现

虚拟内存的实现需要建立在离散分配的内存管理方式的基础上。 虚拟内存的实现有以下三种方式:

  1. 请求分页存储管理 :建立在分页管理之上,为了支持虚拟存储器功能而增加了请求调页功能和页面置换功能。请求分页是目前最常用的一种实现虚拟存储器的方法。请求分页存储管理系统中,在作业开始运行之前,仅装入当前要执行的部分段即可运行。假如在作业运行的过程中发现要访问的页面不在内存,则由处理器通知操作系统按照对应的页面置换算法将相应的页面调入到主存,同时操作系统也可以将暂时不用的页面置换到外存中。
  2. 请求分段存储管理 :建立在分段存储管理之上,增加了请求调段功能、分段置换功能。请求分段储存管理方式就如同请求分页储存管理方式一样,在作业开始运行之前,仅装入当前要执行的部分段即可运行;在执行过程中,可使用请求调入中断动态装入要访问但又不在内存的程序段;当内存空间已满,而又需要装入新的段时,根据置换功能适当调出某个段,以便腾出空间而装入新的段。
  3. 请求段页式存储管理

这里多说一下?很多人容易搞混请求分页与分页存储管理,两者有何不同呢?
请求分页存储管理建立在分页管理之上。他们的根本区别是是否将程序所需的全部地址空间都装入主存,这也是请求分页存储管理可以提供虚拟内存的原因,我们在上面已经分析过了。
它们之间的根本区别在于是否将一作业的全部地址空间同时装入主存。请求分页存储管理不要求将作业全部地址空间同时装入主存。基于这一点,请求分页存储管理可以提供虚存,而分页存储管理却不能提供虚存。
不管是上面那种实现方式,我们一般都需要:

  1. 一定容量的内存和外存:在载入程序的时候,只需要将程序的一部分装入内存,而将其他部分留在外存,然后程序就可以执行了;
  2. 缺页中断:如果需执行的指令或访问的数据尚未在内存(称为缺页或缺段),则由处理器通知操作系统将相应的页面或段调入到内存,然后继续执行程序;
  3. 虚拟地址空间 :逻辑地址到物理地址的变换。

3.9讲讲页面置换算法

地址映射过程中,若在页面中发现所要访问的页面不在内存中,则发生缺页中断 。
缺页中断 就是要访问的不在主存,需要操作系统将其调入主存后再进行访问。 在这个时候,被内存映射的文件实际上成了一个分页交换文件。
当发生缺页中断时,如果当前内存中并没有空闲的页面,操作系统就必须在内存选择一个页面将其移出内存,以便为即将调入的页面让出空间。用来选择淘汰哪一页的规则叫做页面置换算法,我们可以把页面置换算法看成是淘汰页面的规则。

  • OPT 页面置换算法(最佳页面置换算法) :最佳(Optimal, OPT)置换算法所选择的被淘汰页面将是以后永不使用的,或者是在最长时间内不再被访问的页面,这样可以保证获得最低的缺页率。但由于人们目前无法预知进程在内存下的若千页面中哪个是未来最长时间内不再被访问的,因而该算法无法实现。一般作为衡量其他置换算法的方法。
  • FIFO(First In First Out) 页面置换算法(先进先出页面置换算法) : 总是淘汰最先进入内存的页面,即选择在内存中驻留时间最久的页面进行淘汰。
  • LRU (Least Recently Used)页面置换算法(最近最久未使用页面置换算法) :LRU 算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 T,当须淘汰一个页面时,选择现有页面中其 T 值最大的,即最近最久未使用的页面予以淘汰。
  • LFU (Least Frequently Used)页面置换算法(最少使用页面置换算法) : 该置换算法选择在之前时期使用最少的页面作为淘汰页。

4.0 Socket

socket的TCP三次连接

结合三次握手连接的 TCP socket

image.png
客户端使用 connect() API 会触发三次握手动作

结合四次次挥手的 TCP socket

补充问题

(1)为什么创建或撤销进程开销比线程要大

  1. 资源分配:每个进程都有自己的地址空间、文件描述符、进程标识符等资源,这些资源需要分配和初始化,而线程共享进程的地址空间和大部分资源,所以创建进程时需要比创建线程更多的资源分配。
  2. 上下文切换:当进程切换时,需要保存和恢复进程的所有资源状态,包括程序计数器、寄存器、内存、I/O状态等。而线程只需要保存和恢复线程私有的寄存器和堆栈指针等少量资源状态,因此进程上下文切换的开销比线程更大。
  3. 进程间通信:进程间通信需要通过操作系统提供的进程间通信机制进行,比如管道、消息队列、共享内存等,而这些机制会增加进程的创建和撤销的开销,同时也会增加系统的开销。
  4. 安全性:由于进程拥有自己的地址空间和资源,因此它们之间的隔离性更强,能够更好地保护系统的安全性。然而,这种安全性也增加了创建和撤销进程的开销。

总的来说,由于进程需要分配更多的资源、进行更多的上下文切换、使用更多的进程间通信机制和提供更高的安全性,因此创建或撤销进程的开销比线程要大。

(2)为什么进程切换开销大于线程

进程切换比线程切换开销大是因为进程切换时要切页表,而且往往伴随着页调度,因为进程的数据段代码段要换出去,以便把将要执行的进程的内容换进来。本来进程的内容就是线程的超集。而且线程只需要保存线程的上下文(相关寄存器状态和栈的信息)就好了,动作很小。

(3) BufferCache和PageCache

BufferCache和PageCache都是计算机操作系统中用于缓存数据的机制,但是它们针对的数据类型和缓存的位置略有不同。
BufferCache主要是用于缓存文件系统中的数据块,是文件系统缓存的一种实现。当应用程序需要访问文件系统中的某个数据块时,BufferCache会先查看缓存中是否已经有该数据块的副本,如果有,就直接返回该数据块;如果没有,则会从磁盘上读取该数据块,并将其缓存到BufferCache中,以便以后更快地访问。BufferCache是位于文件系统内核中的缓存区域,可以减少磁盘I/O的次数,提高文件系统性能。
PageCache则是用于缓存内存中的页面数据,是虚拟内存系统的一种实现。当进程需要访问某个页面时,PageCache会先查看缓存中是否已经有该页面的副本,如果有,就直接返回该页面;如果没有,则会从磁盘上读取该页面,并将其缓存到PageCache中,以便以后更快地访问。PageCache是位于内存中的缓存区域,可以减少磁盘I/O的次数,提高系统的性能。
因此,BufferCache和PageCache都是用于缓存数据的机制,但是它们的缓存对象和缓存位置略有不同,BufferCache用于缓存文件系统中的数据块,而PageCache用于缓存内存中的页面数据。

(4)孤儿进程和僵尸进程

孤儿进程是指父进程先于子进程结束,导致子进程成为没有父进程的进程。这种情况通常是由于父进程异常终止或者父进程没有正确等待子进程的结束导致的。孤儿进程会被init进程(PID为1的进程)接管,成为init进程的子进程,从而不会影响系统的正常运行。孤儿进程在被接管前可能会一直占用系统资源,因此及时清理孤儿进程对系统的稳定性和安全性很重要。
僵尸进程是指一个已经完成执行的进程,但其父进程还没有对其进行善后处理,即没有使用wait()或waitpid()等函数来回收子进程的进程描述符和其他资源,导致子进程的进程描述符没有被释放,成为僵尸进程。僵尸进程不占用系统资源,但是会占用进程ID,过多的僵尸进程会耗尽系统的进程ID资源,从而导致系统无法创建新的进程。为避免产生过多的僵尸进程,父进程应该及时回收子进程的资源。
总的来说,孤儿进程和僵尸进程都是进程状态的一种,但是它们的产生原因和表现形式略有不同。孤儿进程是由于父进程先于子进程结束导致的,会被init进程接管;而僵尸进程是由于父进程没有及时回收子进程的资源导致的,会占用进程ID。

(5) CPU load

CPU负载(CPU load)是指正在运行的进程或任务占用CPU资源的程度,通常使用平均负载(load average)来表示。平均负载是指在特定时间段内,CPU正在运行的进程或任务的平均数量,即可运行进程的平均长度。
在Unix/Linux系统中,平均负载是通过对系统的运行状态进行采样并计算得出的。一般情况下,平均负载的值是一个实数,表示正在运行的进程和等待CPU资源的进程数之和。例如,一个平均负载为1的系统表示正在运行的进程数等于CPU核心数,而一个平均负载为2的系统表示有两倍于CPU核心数的进程在等待CPU资源。
CPU负载是衡量系统负载的一个重要指标,高负载表示系统运行的任务较多或者CPU资源不足,可能会导致系统的响应变慢或者出现卡顿现象。因此,管理员可以根据CPU负载情况进行资源调度或者优化系统配置,以保证系统的稳定性和高效性。

猜你喜欢

转载自blog.csdn.net/weixin_56640241/article/details/129867283
今日推荐