lukaliou123 / lukaliou123.github.io

lukaliou123在2022年的面试用知识点总结
Other
5 stars 0 forks source link

操作系统篇 #13

Open lukaliou123 opened 2 years ago

lukaliou123 commented 2 years ago

1.什么是操作系统?

1.操作系统(Operating System,简称 OS)是管理计算机硬件与软件资源的程序,是计算机的基石。 2.操作系统本质上是一个运行在计算机上的软件程序 ,主要用于管理计算机硬件和软件资源。 举例:运行在你电脑上的所有应用程序都通过操作系统来调用系统内存以及磁盘等等硬件。 3.操作系统存在屏蔽了硬件层的复杂性。 操作系统就像是硬件使用的负责人,统筹着各种相关事项。 4.操作系统的内核(Kernel)是操作系统的核心部分,它负责系统的内存管理,硬件设备的管理,文件系统的管理以及应用程序的管理。 内核是连接应用程序和硬件的桥梁,决定着系统的性能和稳定性。

很多人容易把操作系统的内核(Kernel)和中央处理器(CPU,Central Processing Unit)弄混。可以简单从下面两点来区别: 1.操作系统的内核(Kernel)属于操作系统层面,而 CPU 属于硬件。 2.CPU 主要提供运算,处理各种指令的能力。内核(Kernel)主要负责系统管理比如内存管理,它屏蔽了对硬件的操作。

应用程序、内核、CPU 这三者的关系 image

2.操作系统主要有哪些功能?

资源管理的角度来看,操作系统有 6 大功能1.进程和线程的管理:进程的创建、撤销、阻塞、唤醒,进程间的通信等。 2.存储管理:内存的分配和管理、外存(磁盘等)的分配和管理等。 3.文件管理:文件的读、写、创建及删除等。 4.设备管理:完成设备(输入输出设备和外部存储设备等)的请求或释放,以及设备启动等功能。 5.网络管理:操作系统负责管理计算机网络的使用。网络是计算机系统中连接不同计算机的方式,操作系统需要管理计算机网络的配置、连接、通信和安全等,以提供高效可靠的网络服务。 6.安全管理:用户的身份认证、访问控制、文件加密等,以防止非法用户对系统资源的访问和操作。

3.内核态与用户态

3.1.什么是用户态和内核态?根据进程访问资源的特点,我们可以把进程在系统上的运行分为两个级别:

1.用户态(User Mode) : 用户态运行的进程可以直接读取用户程序的数据,拥有较低的权限。当应用程序需要执行某些需要特殊权限的操作,例如读写磁盘、网络通信等,就需要向操作系统发起系统调用请求,进入内核态。 2.内核态(Kernel Mode):内核态运行的进程几乎可以访问计算机的任何资源包括系统的内存空间、设备、驱动程序等,不受限制,拥有非常高的权限。当操作系统接收到进程的系统调用请求时,就会从用户态切换到内核态,执行相应的系统调用,并将结果返回给进程,最后再从内核态切换回用户态。 image 内核态相比用户态拥有更高的特权级别,因此能够执行更底层、更敏感的操作。不过,由于进入内核态需要付出较高的开销(需要进行一系列的上下文切换和权限检查),应该尽量减少进入内核态的次数,以提高系统的性能和稳定性。

3.2.为什么要有用户态和内核态?只有一个内核态不行么?

1.在 CPU 的所有指令中,有一些指令是比较危险的比如内存分配、设置时钟、IO 处理等,如果所有的程序都能使用这些指令的话,会对系统的正常运行造成灾难性地影响。因此,我们需要限制这些危险指令只能内核态运行。这些只能由操作系统内核态执行的指令也被叫做 特权指令

2.如果计算机系统中只有一个内核态,那么所有程序或进程都必须共享系统资源,例如内存、CPU、硬盘等,这将导致系统资源的竞争和冲突,从而影响系统性能和效率。并且,这样也会让系统的安全性降低,毕竟所有程序或进程都具有相同的特权级别和访问权限。因此,同时具有用户态和内核态主要是为了保证计算机系统的安全性、稳定性和性能。

3.3.用户态与内核态的切换

image

用户态切换到内核态的 3 种方式1.系统调用(Trap):用户态进程 主动 要求切换到内核态的一种方式,主要是为了使用内核态才能做的事情比如读取磁盘资源。系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现2.中断(Interrupt):当外围设备完成用户请求的操作后,会向 CPU 发出相应的中断信号,这时 CPU 会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。 3.异常(Exception):当 CPU 在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中执行异常处理程序,也就转到了内核态,比如缺页异常

在系统的处理上,中断和异常类似,都是通过中断向量表来找到相应的处理程序进行处理。区别在于,中断来自处理器外部,不是由任何一条专门的指令造成,而异常是执行当前指令的结果

4.系统调用

4.1.什么是系统调用?

我们运行的程序基本都是运行在用户态,如果我们调用操作系统提供的内核态级别的子功能咋办呢?那就需要系统调用了!也就是说在我们运行的用户程序中,凡是与系统态级别的资源有关的操作(如文件管理、进程控制、内存管理等),都必须通过系统调用方式向操作系统提出服务请求,并由操作系统代为完成。 image 这些系统调用按功能大致可分为如下几类1.设备管理:完成设备(输入输出设备和外部存储设备等)的请求或释放,以及设备启动等功能。 2.文件管理:完成文件的读、写、创建及删除等功能。 3.进程管理:进程的创建、撤销、阻塞、唤醒,进程间的通信等功能。 4.内存管理:完成内存的分配、回收以及获取作业占用内存区大小及地址等功能。

系统调用和普通库函数调用非常相似,只是系统调用由操作系统内核提供,运行于内核态,而普通的库函数调用由函数库或用户自己提供,运行于用户态。 总结:系统调用是应用程序与操作系统之间进行交互的一种方式,通过系统调用,应用程序可以访问操作系统底层资源例如文件、设备、网络等。

4.2.系统调用的过程了解吗?

系统调用的过程可以简单分为以下几个步骤: 1.用户态的程序发起系统调用,因为系统调用中涉及一些特权指令(只能由操作系统内核态执行的指令),用户态程序权限不足,因此会中断执行,也就是 Trap(Trap 是一种中断)。 2.发生中断后,当前 CPU 执行的程序会中断,跳转到中断处理程序。内核程序开始执行,也就是开始处理系统调用。 3.内核处理完成后,主动触发 Trap,这样会再次发生中断,切换回用户态工作。 image

局部性原理的28原则:

缓存的一个关键原则是局部性原理,也被称为2-8原则,这意味着大部分的操作(约80%)只会涉及到一小部分的资源(约20%)

补充:常见的4个寻址的方法

1.立即寻址(Immediate Addressing):在这种寻址方式中,操作数就在指令本身中。也就是说,指令的操作数字段本身就是所需的数据。比如我们有一条指令是 ADD 5,那么,5就是一个立即数,我们要做的就是把5加到某个寄存器的值上。

2.直接寻址(Direct Addressing):在这种寻址方式中,指令中的操作数是要访问的内存单元的地址。比如,我们有一条指令是 LOAD 1000,那么,我们就要去地址为1000的内存单元中取数据。

3.间接寻址(Indirect Addressing):在这种寻址方式中,指令的操作数是指向内存地址的一个指针。首先我们需要访问这个指针指向的内存地址,然后才能得到我们需要的数据。比如,我们有一条指令是 LOAD INDIRECT 1000,我们首先要去地址为1000的内存单元中取得一个地址,然后再去这个地址中取得我们需要的数据。

4.索引寻址(Indexed Addressing):在这种寻址方式中,我们要访问的内存地址是由一个基址和一个索引(通常是存储在一个特定的寄存器中)相加得到的。比如,我们有一条指令是 LOAD 1000(X),那么,我们要访问的内存地址就是1000加上寄存器X中的值。

例子:

1691039403386 1691039417923 这个题目中给出的指令是 Load 300,那么根据不同的寻址模式,我们有:

1.立即寻址(Immediate):这种方式下,300 就是我们要的值,所以 Value loaded into AC 是 300。

2.直接寻址(Direct):这种方式下,300 是我们要访问的内存地址,从题目中我们可以知道,地址300的值是 400,所以 Value loaded into AC 是 400。

3.间接寻址(Indirect):这种方式下,300 是存有我们要访问的内存地址的地址,从题目中我们可以知道,地址300的值是 400,然后我们去访问地址400,得到值 900,所以 Value loaded into AC 是 900。

4.索引寻址(Indexed):这种方式下,300 是基址,我们需要到寄存器 R1 中取索引值,题目中告诉我们 R1 的值是 400,那么我们要访问的内存地址就是300+400=700,然后我们去访问地址700,得到值 300,所以 Value loaded into AC 是 300。

补充2:缓存映射方式

1.直接映射(Direct Mapped): 这是最简单的映射技术。在这种方法中,每个主存块只能映射到缓存的一个特定行。具体行是由主存地址的一部分决定的。这个方法的优点是查找速度快,但缺点是可能出现缓存冲突,即使缓存有空位

例子:假设有一个缓存有4行,一个主存有8块,如果我们只用直接映射的话,主存的块0、4就都会映射到缓存的行0,这就可能产生冲突。

2.全相联映射(Fully Associative): 在这种方法中,一个主存块可以映射到缓存的任何位置。当发生缓存失效时,需要检查整个缓存以确定主存块是否已在缓存中。这种方法可以避免直接映射中的冲突问题,但查找的复杂度和开销都较大

例子:假设有一个缓存有4行,如果我们采用全相联映射,那么主存的任何一块都可以放在缓存的任何一行,比如主存的块7可以放在缓存的行0。

3.集合相联映射(Set Associative): 这是前两种方法的一种折中。在这种方法中,缓存被分为几个集合,每个集合有多行。一个主存块可以映射到其对应集合的任意行中。这种方法允许我们以更低的查找复杂度实现相对于直接映射的更高的命中率。

例子:假设我们有一个有4行的缓存,我们把它分成2个集合,每个集合有2行。那么主存的块0可以放在集合0的任一行,主存的块2也可以放在集合0的任一行,以此类推。

需要使用缓存映射的场合(主要涉及到缓存,如果可以和局部性原理互相连接拓展)

1.加载和存储指令:当CPU执行加载和存储指令时,它需要从主存中读取数据或将数据写回主存。在这些操作中,如果需要的数据已经在缓存中,那么CPU可以直接从缓存中读取或写入数据,这比从主存中操作要快得多。但是,如果数据不在缓存中,那么就需要将数据从主存加载到缓存中,然后再进行操作。这就是所谓的缓存失效。

2.指令预取和数据预取:为了提高执行效率,处理器通常会预取将来可能需要的指令和数据,将它们加载到缓存中。这样当CPU真的需要这些指令和数据时,它们就已经在缓存中了。

3.上下文切换:当操作系统进行上下文切换,即切换运行的进程时,新的进程可能需要访问与旧进程不同的数据和指令。在这种情况下,就需要把新的数据和指令加载到缓存中。

4.缓存一致性:在多处理器或多核系统中,每个处理器或核心可能有自己的缓存。为了保证数据的一致性,当一个处理器改变了它的缓存中的数据时,需要把这个改变写回主存,同时其他处理器的缓存中对应的数据也需要更新。这就涉及到了缓存与主存之间的数据交换。 面试官提问的方式

1.直接提问:“你了解计算机系统中的缓存映射技术吗?能谈谈你对直接映射、全相联映射和集合相联映射的理解吗?”

2.问题解决:“假设我们有一个问题,当多个CPU核心并行处理任务时,缓存一致性问题会变得非常重要。你了解哪些方法可以帮助我们解决这个问题吗?”

3.深度讨论:“在设计和实现高性能计算系统时,选择合适的缓存策略是非常关键的。你能谈谈你对这个话题的看法吗?”

lukaliou123 commented 2 years ago

5.进程和线程

进程(Process) 是指计算机中正在运行的一个程序实例。举例:你打开的微信就是一个进程。 线程(Thread) 也被称为轻量级进程,更加轻量。多个线程可以在同一个进程中同时执行,并且共享进程的资源比如内存空间、文件句柄、网络连接等。举例:你打开的微信里就有一个线程专门用来拉取别人发你的最新的消息

6.进程和线程的区别是什么?

下图是 Java 内存区域,我们从 JVM 的角度来说一下线程和进程之间的关系吧! image 从上图可以看出:一个进程中可以有多个线程,多个线程共享进程的堆和方法区 (JDK1.8 之后的元空间)资源,但是每个线程有自己的程序计数器、虚拟机栈 和 本地方法栈

总结:线程是进程划分成的更小的运行单位,一个进程在其执行的过程中可以产生多个线程。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反。

7.有了进程为什么还需要线程?

1.进程切换是一个开销很大的操作,线程切换的成本较低。 2.线程更轻量,一个进程可以创建多个线程。 3.多个线程可以并发处理不同的任务,更有效地利用了多处理器和多核计算机。而进程只能在一个时间干一件事,如果在执行过程中遇到阻塞问题比如 IO 阻塞就会挂起直到结果返回。 4.同一进程内的线程共享内存和文件,因此它们之间相互通信无须调用内核

lukaliou123 commented 2 years ago

8.PCB 是什么?包含哪些信息?

PCB(Process Control Block) 即进程控制块,是操作系统中用来管理和跟踪进程的数据结构,每个进程都对应着一个独立的 PCB。你可以将 PCB 视为进程的大脑。 当操作系统创建一个新进程时,会为该进程分配一个唯一的进程 ID,并且为该进程创建一个对应的进程控制块。当进程执行时,PCB 中的信息会不断变化,操作系统会根据这些信息来管理和调度进程。 PCB 主要包含下面几部分的内容: 1.进程的描述信息,包括进程的名称、标识符等等; 2.进程的调度信息,包括进程阻塞原因、进程状态(就绪、运行、阻塞等)、进程优先级(标识进程的重要程度)等等; 3.进程对资源的需求情况,包括 CPU 时间、内存空间、I/O 设备等等。 4.进程打开的文件信息,包括文件描述符、文件类型、打开模式等等。 5.处理机的状态信息(由处理机的各种寄存器中的内容组成的),包括通用寄存器、指令计数器、程序状态字 PSW、用户栈指针。

9.进程有哪几种状态

1.创建状态(new):进程正在被创建,尚未到就绪状态。 2.就绪状态(ready): 进程已处于准备运行状态,即进程获得了除了处理器之外的一切所需资源,一旦得到处理器资源(处理器分配的时间片)即可运行 3.运行状态(running):进程正在处理器上运行(单核CPU下任意时刻只有一个进程处于运行状态) 4.阻塞状态(waiting):又称为等待状态,进程正在等待某一事件而暂停运行,如等待某资源为可用或等待IO操作完成。即使处理器空闲,该进程也不能运行 5.结束状态(terminated):进程正在从系统中消失。可能是进程正常结束或其他原因中断退出运行

image

lukaliou123 commented 2 years ago

10.进程间的通信方式

https://www.jianshu.com/p/c1015f5ffa74 1.管道/匿名管道(pipes):用于具有亲缘关系的父子进程间或兄弟进程之间的通信

2.有名管道(Names Pipes):匿名管道由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道。有名管道严格遵循先进先出。有名管道以磁盘文件的方式存在,可以实现本机任意两个进程通信。

3.信号(Signal): 信号是一种在Unix和类Unix操作系统中常见的通信方式。它们用于通知一个进程发生了某种情况。例如,Ctrl+C发送SIGINT信号,通知进程用户请求中断。

信号是软件层次上对中断机制的一种模拟,是一种异步通信方式,信号可以在用户空间进程和内核之间直接交互,内核可以利用信号来通知用户空间的进程发生了哪些系统事件,信号事件主要有两个来源:

硬件来源:用户按键输入Ctrl+C退出、硬件异常如无效的存储访问等。 软件终止:终止进程信号、其他进程调用kill函数、软件异常产生信号。

4.消息队列(Message Queuing):消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符表示。管道和消息队列的通信数据都是先进先出的原则。与管道(无名管道:只存在于内存中的文件;命名管道:存在于实际的磁盘介质或文件系统)不同的是消息队列放在内核(Kernel)中,只有内核重启(即操作系统重启),或者显示要删除一个消息队列时,该消息队列才会被真正的删除。纤细队列可以实现消息的随即查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取,比FIFO更有优势。消息队列克服了信号承载信息量少,管道只能承载无格式字节流,以及缓冲区大小受限的缺点 消息队列有解耦,异步,降峰**三大优点。

5.信号量(Semaphores):信号量是一种同步机制,可以帮助进程避免同时访问一个公共资源,防止产生冲突。当一个进程在使用共享资源时,它会增加信号量的计数,然后在结束时减少计数。如果计数为零,则其他进程必须等待直到资源变得可用。

6.共享内存(Shared memory):使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。可以说这是最有用的进程间通信方式。

7.套接字(Sockets):套接字是一种在网络环境中常见的通信方式,也可以用于同一台机器上的进程间通信。例如,一个web服务器和浏览器就通过套接字进行通信。

lukaliou123 commented 2 years ago

5.线程间的同步方式

1.互斥量(Mutex):采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问,比如Java的同步锁(synchronized关键词)和各种Lock都是这种机制 2.信号量(Semphares):信号量和互斥锁类似,但提供了更高级的同步功能。信号量可以控制同时访问某一资源的线程数,而不是只允许一个线程(如互斥锁)。当信号量计数大于0时,线程可以访问资源,并将信号量减一;当信号量为0时,线程将阻塞,直到其他线程释放资源并增加信号量。 3.事件(Event):Wait/Notify:事件是线程间的另一种通信机制。线程可以等待一个事件的发生,当事件发生时等待的线程将被唤醒。这种机制可以用来通知线程发生了某些特定的事情。

lukaliou123 commented 2 years ago

11.进程的调度算法

https://www.cnblogs.com/xiaolincoding/p/13631224.html 当 CPU 空闲时,操作系统就选择内存中的某个「就绪状态」的进程,并给其分配 CPU 为了确认首先执行哪个进程以及最后执行哪个进程以实现最大CPU利用率,计算机科学家已经定义了一些算法,它们是: 1.先到先服务(FCFS)调度算法:从就绪队列中选择一个最先进入该队列的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用CPU时再度调度 2.短作业优先(SJF)调度算法:从就绪队列中选出一个估计运行时间最短的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用CPU时再重新调度。 3.时间片轮转调度算法: 时间片轮转调度是一种最古老,最简单,最公平且使用最广的算法,又称RR(ROUND ROBIN)调度,每个进程被分配一个时间段(通常20-50ms 4.优先级调度:为每个流程分配优先级,首先执行具有最高优先级的进程,以此类推,具有相同优先级的进程以FCFS方法执行。可以根据内存要求,时间要求,或任何其他资源要求来确定优先级 5.多级反馈队列调度算法 Multilevel Feedback Queue Scheduling:前面介绍的进程调度的算法都有一定局限性。如短进程优先的调度算法,仅照顾了短进程而忽略了长进程。多级反馈队列调度算法既能使高优先级的作业得到响应,又能使短作业迅速完成。因此是目前被公认的一种较好的进程调度算法 image

image

1.设置了多个队列,赋予每个队列不同的优先级,每个队列优先级从高到低,同时优先级越高时间片越短; 2.新的进程会被放入到第一级队列的末尾,按先来先服务的原则排队等待被调度,如果在第一级队列规定的时间片没运行完成,则将其转入到第二级队列的末尾,以此类推,直至完成; 3.当较高优先级的队列为空才调度较低优先级的队列中的进程运行。如果进程运行时,有新进程进入较高优先级的队列,则停止当前运行的进程并将其移入到原队列末尾,接着让较高优先级的进程运行;

假设我们有三个队列,优先级依次降低。当一个进程刚开始运行时,我们将它放入第一个队列(优先级最高)。如果它在一个时间片内没有完成,那么我们将其移至第二个队列。如果在第二个队列的一个时间片内仍未完成,那么它会被移至第三个队列。而如果一个进程在任何队列的一个时间片内完成,那么它将在下一次调度时依然在这个队列运行。

可以发现,对于短作业可能可以在第一级队列很快被处理完。对于长作业,如果在第一级队列处理不完,可以移入下次队列等待被执行,虽然等待的时间变长了,但是运行时间也会更长了,所以该算法很好的兼顾了长短作业,同时有较好的响应时间。

lukaliou123 commented 2 years ago

12.什么是僵尸进程和孤儿进程?

在 Unix/Linux 系统中,子进程通常是通过 fork()系统调用创建的,该调用会创建一个新的进程,该进程是原有进程的一个副本。子进程和父进程的运行是相互独立的,它们各自拥有自己的 PCB,即使父进程结束了,子进程仍然可以继续运行。

当一个进程调用 exit()系统调用结束自己的生命时,内核会释放该进程的所有资源,包括打开的文件、占用的内存等,但是该进程对应的 PCB 依然存在于系统中。这些信息只有在父进程调用 wait()或 waitpid()系统调用时才会被释放,以便让父进程得到子进程的状态信息。这样的设计可以让父进程在子进程结束时得到子进程的状态信息,并且可以防止出现“僵尸进程”(即子进程结束后 PCB 仍然存在但父进程无法得到状态信息的情况)。 1.僵尸进程子进程已经终止,但是其父进程仍在运行,且父进程没有调用 wait()或 waitpid()等系统调用来获取子进程的状态信息,释放子进程占用的资源,导致子进程的 PCB 依然存在于系统中,但无法被进一步使用。这种情况下,子进程被称为“僵尸进程”。避免僵尸进程的产生,父进程需要及时调用 wait()或 waitpid()系统调用来回收子进程。 2.孤儿进程:一个进程的父进程已经终止或者不存在,但是该进程仍在运行。这种情况下,该进程就是孤儿进程。孤儿进程通常是由于父进程意外终止或未及时调用 wait()或 waitpid()等系统调用来回收子进程导致的。为了避免孤儿进程占用系统资源,操作系统会将孤儿进程的父进程设置为 init 进程(进程号为 1),由 init 进程来回收孤儿进程的资源。

12.1如何查看是否有僵尸进程?

Linux 下可以使用 Top 命令查找,zombie 值表示僵尸进程的数量,为 0 则代表没有僵尸进程。

13.内存管理

13.1.内存管理主要做了什么? image 操作系统的内存管理非常重要,主要负责下面这些事情1.内存的分配与回收:对进程所需的内存进行分配和释放,malloc 函数:申请内存,free 函数:释放内存2.地址转换:将程序中的虚拟地址转换成内存中的物理地址3.内存扩充:当系统没有足够的内存时,利用虚拟内存技术或自动覆盖技术,从逻辑上扩充内存4.内存映射:将一个文件直接映射到进程的进程空间中,这样可以通过内存指针用读写内存的办法直接存取文件内容,速度更快。 5.内存优化:通过调整内存分配策略和回收算法来优化内存使用效率。 6.内存安全:保证进程之间使用内存互不干扰,避免一些恶意程序通过修改内存来破坏系统的安全性。

14.什么是内存碎片?

内存碎片是由内存的申请和释放产生的,通常分为下面两种: 1.内部内存碎片(Internal Memory Fragmentation,简称为内存碎片):已经分配给进程使用但未被使用的内存。导致内部内存碎片的主要原因是,当采用固定比例比如 2 的幂次方进行内存分配时,进程所分配的内存可能会比其实际所需要的大。举个例子,一个进程只需要 65 字节的内存,但为其分配了 128(2^7) 大小的内存,那 63 字节的内存就成为了内部内存碎片

2.外部内存碎片(External Memory Fragmentation,简称为外部碎片):由于未分配的连续内存区域太小,以至于不能满足任意进程所需要的内存分配请求,这些小片段且不连续的内存空间被称为外部碎片。也就是说,外部内存碎片指的是那些并未分配给进程但又不能使用的内存。我们后面介绍的分段机制就会导致外部内存碎片。

image 内存碎片会导致内存利用率下降,如何减少内存碎片是内存管理要非常重视的一件事情。

lukaliou123 commented 2 years ago

15.常见的内存管理方式有哪些?

内存管理方式可以简单分为下面两种: 1.连续内存管理:为一个用户程序分配一个连续的内存空间,内存利用率一般不高2.非连续内存管理:允许一个程序使用的内存分布在离散或者说不相邻的内存中,相对更加灵活一些。

1.连续内存管理(了解了解就行,过时的东西)

块式管理 是早期计算机操作系统的一种连续内存管理方式,存在严重的内存碎片问题。块式管理会将内存分为几个固定大小的块,每个块中只包含一个进程。如果程序运行需要内存的话,操作系统就分配给它一块,如果程序运行只需要很小的空间的话,分配的这块内存很大一部分几乎被浪费了。这些在每个块中未被利用的空间,我们称之为内部内存碎片。除了内部内存碎片之外,由于两个内存块之间可能还会有外部内存碎片,这些不连续的外部内存碎片由于太小了无法再进行分配。

在 Linux 系统中,连续内存管理采用了 伙伴系统(Buddy System)算法 来实现,这是一种经典的连续内存分配算法,可以有效解决外部内存碎片的问题。伙伴系统的主要思想是将内存按 2 的幂次划分(每一块内存大小都是 2 的幂次比如 2^6=64 KB),并将相邻的内存块组合成一对伙伴(注意:必须是相邻的才是伙伴)。

当进行内存分配时,伙伴系统会尝试找到大小最合适的内存块。如果找到的内存块过大,就将其一分为二,分成两个大小相等的伙伴块。如果还是大的话,就继续切分,直到到达合适的大小为止。

假设两块相邻的内存块都被释放,系统会将这两个内存块合并,进而形成一个更大的内存块,以便后续的内存分配。这样就可以减少内存碎片的问题,提高内存利用率。 image 伙伴系统(Buddy System)内存管理

虽然解决了外部内存碎片的问题,但伙伴系统仍然存在内存利用率不高的问题(内部内存碎片)。这主要是因为伙伴系统只能分配大小为 2^n 的内存块,因此当需要分配的内存大小不是 2^n 的整数倍时,会浪费一定的内存空间。举个例子:如果要分配 65 大小的内存快,依然需要分配 2^7=128 大小的内存块image 对于内部内存碎片的问题,Linux 采用 SLAB 进行解决。由于这部分内容不是本篇文章的重点,这里就不详细介绍了

2.非连续内存管理

非连续内存管理存在下面 3 种方式1.段式管理:以段(—段连续的物理内存)的形式管理/分配物理内存。应用程序的虚拟地址空间被分为大小不等的段,段是有实际意义的,每个段定义了一组逻辑信息,例如有主程序段 MAIN、子程序段 X、数据段 D 及栈段 S 等。 2.页式管理:把物理内存分为连续等长的物理页,应用程序的虚拟地址空间划也被分为连续等长的虚拟页,现代操作系统广泛使用的一种内存管理方式。 3.段页式管理机制:结合了段式管理和页式管理的一种内存管理机制,把物理内存先分成若干段,每个段又继续分成若干大小相等的页

lukaliou123 commented 2 years ago

16.虚拟内存

16.1.什么是虚拟内存?有什么用?

虚拟内存(Virtual Memory) 是计算机系统内存管理非常重要的一个技术,本质上来说它只是逻辑存在的,是一个假想出来的内存空间,主要作用是作为进程访问主存(物理内存)的桥梁并简化内存管理image 总结来说,虚拟内存主要提供了下面这些能力: 1.隔离进程:物理内存通过虚拟地址空间访问虚拟地址空间与进程一一对应。每个进程都认为自己拥有了整个物理内存,进程之间彼此隔离,一个进程中的代码无法更改正在由另一进程或操作系统使用的物理内存

2.提升物理内存利用率:有了虚拟地址空间后,操作系统只需要将进程当前正在使用的部分数据或指令加载入物理内存

3.简化内存管理:进程都有一个一致且私有的虚拟地址空间,程序员不用和真正的物理内存打交道,而是借助虚拟地址空间访问物理内存,从而简化了内存管理

4.多个进程共享物理内存:进程在运行过程中,会加载许多操作系统的动态库。这些库对于每个进程而言都是公用的,它们在内存中实际只会加载一份,这部分称为共享内存

5.提高内存使用安全性:控制进程对物理内存的访问,隔离不同进程的访问权限,提高系统的安全性。

6.提供更大的可使用内存空间:可以让程序拥有超过系统物理内存大小的可用内存空间。这是因为当物理内存不够用时,可以利用磁盘充当,将物理内存页(通常大小为 4 KB)保存到磁盘文件(会影响读写速度),数据或代码页会根据需要在物理内存与磁盘之间移动

16.2 细说虚拟内存和物理内存的转换?

每个程序有自己的地址空间,这个地址空间被划分为多个块,每一块称为一页。这些页被映射到物理内存,但并不是所有的页都必须在程序运行的时候都在内存中。当程序引用到一部分的地址空间时,系统将对应的页载入内存,当内存不够用时,系统会将一些页移出内存,存入磁盘上的交换区,这个过程称为换页。这样,就形成了有大量可用内存的错觉。

16.3 没有虚拟内存有什么问题?

如果没有虚拟内存的话,程序直接访问和操作的都是物理内存,看似少了一层中介,但多了很多问题。 具体有什么问题呢? 这里举几个例子说明(参考虚拟内存提供的能力回答这个问题): 1.用户程序可以访问任意物理内存,可能会不小心操作到系统运行必需的内存,进而造成操作系统崩溃,严重影响系统的安全。 2.同时运行多个程序容易崩溃。比如你想同时运行一个微信和一个 QQ 音乐,微信在运行的时候给内存地址 1xxx 赋值后,QQ 音乐也同样给内存地址 1xxx 赋值,那么 QQ 音乐对内存的赋值就会覆盖微信之前所赋的值,这就可能会造成微信这个程序会崩溃。 3.程序运行过程中使用的所有数据或指令都要载入物理内存,根据局部性原理,其中很大一部分可能都不会用到,白白占用了宝贵的物理内存资源

16.4.什么是虚拟地址和物理地址?

物理地址(Physical Address) 是真正的物理内存中地址,更具体点来说是内存地址寄存器中的地址。程序中访问的内存地址不是物理地址,而是 虚拟地址(Virtual Address) 。

也就是说,我们编程开发的时候实际就是在和虚拟地址打交道。比如在 C 语言中,指针里面存储的数值就可以理解成为内存里的一个地址,这个地址也就是我们说的虚拟地址

操作系统一般通过 CPU 芯片中的一个重要组件 MMU(Memory Management Unit,内存管理单元) 将虚拟地址转换为物理地址,这个过程被称为 地址翻译/地址转换(Address Translation)image 通过 MMU 将虚拟地址转换为物理地址后,再通过总线传到物理内存设备,进而完成相应的物理内存读写请求。

MMU 将虚拟地址翻译为物理地址的主要机制有两种: 分段机制分页机制

16.5.什么是虚拟地址空间和物理地址空间?

1.虚拟地址空间是虚拟地址的集合,是虚拟内存的范围。每一个进程都有一个一致且私有的虚拟地址空间。 2.物理地址空间是物理地址的集合,是物理内存的范围。

16.6.虚拟地址与物理内存地址是如何映射的?

MMU 将虚拟地址翻译为物理地址的主要机制有 3 种: 1.分段机制 2.分页机制 3.段页机制 其中,现代操作系统广泛采用分页机制,需要重点关注!

lukaliou123 commented 2 years ago

17.分段机制(了解,现在常用分页)

分段机制(Segmentation) 以段(—段 连续 的物理内存)的形式管理/分配物理内存。应用程序的虚拟地址空间被分为大小不等的段,段是有实际意义的,每个段定义了一组逻辑信息,例如有主程序段 MAIN、子程序段 X、数据段 D 及栈段 S 等。

段表有什么用?地址翻译过程是怎样的? 分段管理通过 段表(Segment Table) 映射虚拟地址和物理地址。分段机制下的虚拟地址由两部分组成:

段号:标识着该虚拟地址属于整个虚拟地址空间中的哪一个段。 段内偏移量:相对于该段起始地址的偏移量。

17.1.具体的地址翻译过程如下:

1.MMU 首先解析得到虚拟地址中的段号; 2.通过段号去该应用程序的段表中取出对应的段信息(找到对应的段表项); 3.从段信息中取出该段的起始地址(物理地址)加上虚拟地址中的段内偏移量得到最终的物理地址。 image 段表中还存有诸如段长(可用于检查虚拟地址是否超出合法范围)、段类型(该段的类型,例如代码段、数据段等)等信息。 通过段号一定要找到对应的段表项吗?得到最终的物理地址后对应的物理内存一定存在吗不一定。段表项可能并不存在: 1.段表项被删除:软件错误、软件恶意行为等情况可能会导致段表项被删除。 2.段表项还未创建:如果系统内存不足或者无法分配到连续的物理内存块就会导致段表项无法被创建

17.2.分段机制为什么会导致内存外部碎片?

分段机制容易出现外部内存碎片,即在段与段之间留下碎片空间(不足以映射给虚拟地址空间中的段)。从而造成物理内存资源利用率的降低。 举个例子:假设可用物理内存为 5G 的系统使用分段机制分配内存。现在有 4 个进程,每个进程的内存占用情况如下: 进程 1:0~1G(第 1 段) 进程 2:1~3G(第 2 段) 进程 3:3~4.5G(第 3 段) 进程 4:4.5~5G(第 4 段) 此时,我们关闭了进程 1 和进程 4,则第 1 段和第 4 段的内存会被释放,空闲物理内存还有 1.5G。由于这 1.5G 物理内存并不是连续的,导致没办法将空闲的物理内存分配给一个需要 1.5G 物理内存的进程image

lukaliou123 commented 2 years ago

18.分页机制

分页机制(Paging) 把主存(物理内存)分为连续等长的物理页,应用程序的虚拟地址空间划也被分为连续等长的虚拟页。现代操作系统广泛采用分页机制。 注意:这里的页是连续等长的,不同于分段机制下不同长度的段。

在分页机制下,应用程序虚拟地址空间中的任意虚拟页可以被映射到物理内存中的任意物理页上,因此可以实现物理内存资源的离散分配。分页机制按照固定页大小分配物理内存,使得物理内存资源易于管理,可有效避免分段机制中外部内存碎片的问题

18.1.页表有什么用?地址翻译过程是怎样的?

分页管理通过 页表(Page Table) 映射虚拟地址和物理地址。我这里画了一张基于单级页表进行地址翻译的示意图。 image

在分页机制下,每个应用程序都会有一个对应的页表。

分页机制下的虚拟地址由两部分组成:

1.页号:通过虚拟页号可以从页表中取出对应的物理页号; 2.页内偏移量物理页起始地址+页内偏移量=物理内存地址 image 页表中还存有诸如访问标志(标识该页面有没有被访问过)、脏数据标识位等信息。 通过虚拟页号一定要找到对应的物理页号吗?找到了物理页号得到最终的物理地址后对应的物理页一定存在吗?

不一定!可能会存在 页缺失 。也就是说,物理内存中没有对应的物理页或者物理内存中有对应的物理页但虚拟页还未和物理页建立映射(对应的页表项不存在)。关于页缺失的内容,后面会详细介绍到。

单级页表有什么问题?为什么需要多级页表?

以 32 位的环境为例,虚拟地址空间范围共有 2^32(4G)。假设 一个页的大小是 2^12(4KB),那页表项共有 4G / 4K = 2^20 个。每个页表项为一个地址,占用 4 字节,2^20 2^2/10241024= 4MB

也就是说一个程序啥都不干,页表大小就得占用 4M。系统运行的应用程序多起来的话,页表的开销还是非常大的。而且,绝大部分应用程序可能只能用到页表中的几项,其他的白白浪费了。

为了解决这个问题,操作系统引入了 多级页表 ,多级页表对应多个页表,每个页表也前一个页表相关联。32 位系统一般为二级页表,64 位系统一般为四级页表

这里以二级页表为例进行介绍:二级列表分为一级页表和二级页表。一级页表共有 1024 个页表项,一级页表又关联二级页表,二级页表同样共有 1024 个页表项。二级页表中的一级页表项是一对多的关系,二级页表按需加载(只会用到很少一部分二级页表),进而节省空间占用。 假设只需要 2 个二级页表,那两级页表的内存占用情况为: 4KB(一级页表占用) + 4KB * 2(二级页表占用) = 12 KB。 image 多级页表属于时间换空间的典型场景,利用增加页表查询的次数减少页表占用的空间。

例子: 假设内存有32位,12位存页,20位存指针(页表项),就会有2^20页表项,多级页表用10位存页表项,另外10位存指向页表项的指针页目录项,这样两指针就是2^10+2^10 = 2^11数量,省了很多空间

18.2.TLB 有什么用?使用 TLB 之后的地址翻译流程是怎样的?

为了提高虚拟地址到物理地址的转换速度,操作系统在 页表方案 基础之上引入了 转址旁路缓存(Translation Lookaside Buffer,TLB,也被称为快表)image

在主流的 AArch64 和 x86-64 体系结构下,TLB 属于 (Memory Management Unit,内存管理单元) 内部的单元,本质上就是一块高速缓存(Cache),缓存了虚拟页号到物理页号的映射关系,你可以将其简单看作是存储着键(虚拟页号)值(物理页号)对的哈希表。 使用 TLB 之后的地址翻译流程是这样的: 1.用虚拟地址中的虚拟页号作为 key 去 TLB 中查询; 2.如果能查到对应的物理页的话,就不用再查询页表了,这种情况称为 TLB 命中(TLB hit)。 3.如果不能查到对应的物理页的话,还是需要去查询主存中的页表,同时将页表中的该映射表项添加到 TLB 中,这种情况称为 TLB 未命中(TLB miss)。 4.当 TLB 填满后,又要登记新页时,就按照一定的淘汰策略淘汰掉快表中的一个页image 由于页表也在主存中,因此在没有 TLB 之前,每次读写内存数据时 CPU 要访问两次主存。有了 TLB 之后,对于存在于 TLB 中的页表数据只需要访问一次主存即可。(页表存在CPU内部) TLB 的设计思想非常简单,但命中率往往非常高,效果很好。这就是因为被频繁访问的页就是其中的很小一部分

看完了之后你会发现快表和我们平时经常在开发系统中使用的缓存(比如 Redis)很像,的确是这样的,操作系统中的很多思想、很多经典的算法,你都可以在我们日常开发使用的各种工具或者框架中找到它们的影子。

lukaliou123 commented 1 year ago

18.3.换页机制有什么用?

换页机制的思想是当物理内存不够用的时候,操作系统选择将一些物理页的内容放到磁盘上去,等要用到的时候再将它们读取到物理内存中。也就是说,换页机制利用磁盘这种较低廉的存储设备扩展的物理内存。 这也就解释了一个日常使用电脑常见的问题:为什么操作系统中所有进程运行所需的物理内存即使比真实的物理内存要大一些,这些进程也是可以正常运行的,只是运行速度会变慢。 这同样是一种时间换空间的策略,你用 CPU 的计算时间,页的调入调出花费的时间,换来了一个虚拟的更大的物理内存空间来支持程序的运行

18.4.什么是页缺失?

根据维基百科: 页缺失(Page Fault,又名硬错误、硬中断、分页错误、寻页缺失、缺页中断、页故障等)指的是当软件试图访问已映射在虚拟地址空间中,但是目前并未被加载在物理内存中的一个分页时,由 MMU 所发出的中断。 常见的页缺失有下面这两种: 1.硬性页缺失(Hard Page Fault)物理内存中没有对应的物理页。于是,Page Fault Handler 会指示 CPU 从已经打开的磁盘文件中读取相应的内容到物理内存,而后交由 MMU 建立相应的虚拟页和物理页的映射关系。 2.软性页缺失(Soft Page Fault):物理内存中有对应的物理页,但虚拟页还未和物理页建立映射。于是,Page Fault Handler 会指示 MMU 建立相应的虚拟页和物理页的映射关系。 发生上面这两种缺页错误的时候,应用程序访问的是有效的物理内存,只是出现了物理页缺失或者虚拟页和物理页的映射关系未建立的问题。如果应用程序访问的是无效的物理内存的话,还会出现 无效缺页错误(Invalid Page Fault)

18.5.常见的页面置换算法有哪些?

当发生硬性页缺失时,如果物理内存中没有空闲的物理页面可用的话。操作系统就必须将物理内存中的一个物理页淘汰出去,这样就可以腾出空间来加载新的页面了。 用来选择淘汰哪一个物理页的规则叫做 页面置换算法 ,我们可以把页面置换算法看成是淘汰物物理页的规则

页缺失太频繁的发生会非常影响性能,一个好的页面置换算法应该是可以减少页缺失出现的次数

常见的页面置换算法有下面这 5 种(其他还有很多页面置换算法都是基于这些算法改进得来的):

image 1.最佳页面置换算法(OPT,Optimal):优先选择淘汰的页面是以后永不使用的,或者是在最长时间内不再被访问的页面,这样可以保证获得最低的缺页率。但由于人们目前无法预知进程在内存下的若干页面中哪个是未来最长时间内不再被访问的,因而该算法无法实现,只是理论最优的页面置换算法,可以作为衡量其他置换算法优劣的标准。

2.先进先出页面置换算法(FIFO,First In First Out) : 最简单的一种页面置换算法,总是淘汰最先进入内存的页面,即选择在内存中驻留时间最久的页面进行淘汰。该算法易于实现和理解,一般只需要通过一个 FIFO 队列即可需求。不过,它的性能并不是很好。

3.最近最久未使用页面置换算法(LRU ,Least Recently Used):LRU 算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 T,当须淘汰一个页面时,选择现有页面中其 T 值最大的,即最近最久未使用的页面予以淘汰。LRU 算法是根据各页之前的访问情况来实现,因此是易于实现的。OPT 算法是根据各页未来的访问情况来实现,因此是不可实现的。

4.最少使用页面置换算法(LFU,Least Frequently Used) : 和 LRU 算法比较像,不过该置换算法选择的是之前一段时间内使用最少的页面作为淘汰页。

5.时钟页面置换算法(Clock):可以认为是一种最近未使用算法,即逐出的页面都是最近没有使用的那个

18.6.FIFO 页面置换算法性能为何不好?

主要原因主要有二: 1.经常访问或者需要长期存在的页面会被频繁调入调出:较早调入的页往往是经常被访问或者需要长期存在的页,这些页会被反复调入和调出。

2.存在 Belady 现象(贝拉迪异常):被置换的页面并不是进程不会访问的,有时就会出现分配的页面数增多但缺页率反而提高的异常现象。出现该异常的原因是因为 FIFO 算法只考虑了页面进入内存的顺序,而没有考虑页面访问的频率和紧迫性

18.7.哪一种页面置换算法实际用的比较多?

LRU 算法是实际使用中应用的比较多,也被认为是最接近 OPT 的页面置换算法。 不过,需要注意的是,实际应用中这些算法会被做一些改进,就比如 InnoDB Buffer Pool( InnoDB 缓冲池,MySQL 数据库中用于管理缓存页面的机制)就改进了传统的 LRU 算法,使用了一种称为"Adaptive LRU"的算法(同时结合了 LRU 和 LFU 算法的思想)。

19.分页机制和分段机制有哪些共同点和区别?

共同点: 1.都是非连续内存管理的方式。 2.都采用了地址映射的方法,将虚拟地址映射到物理地址,以实现对内存的管理和保护。

区别: 1.分页机制以页面为单位进行内存管理,而分段机制以段为单位进行内存管理。页的大小是固定的,由操作系统决定,通常为 2 的幂次方。而段的大小不固定,取决于我们当前运行的程序

2.页是物理单位,即操作系统将物理内存划分成固定大小的页面,每个页面的大小通常是 2 的幂次方,例如 4KB、8KB 等等。而段则是逻辑单位,是为了满足程序对内存空间的逻辑需求而设计的,通常根据程序中数据和代码的逻辑结构来划分

3.分段机制容易出现外部内存碎片,即在段与段之间留下碎片空间(不足以映射给虚拟地址空间中的段)。分页机制解决了外部内存碎片的问题,但仍然可能会出现内部内存碎片

4.分页机制采用了页表来完成虚拟地址到物理地址的映射,页表通过一级页表和二级页表来实现多级映射;而分段机制则采用了段表来完成虚拟地址到物理地址的映射,每个段表项中记录了该段的起始地址和长度信息。

5.分页机制对程序没有任何要求,程序只需要按照虚拟地址进行访问即可;而分段机制需要程序员将程序分为多个段,并且显式地使用段寄存器来访问不同的段。

20.段页机制

结合了段式管理和页式管理的一种内存管理机制,把物理内存先分成若干段,每个段又继续分成若干大小相等的页

在段页式机制下,地址翻译的过程分为两个步骤:

1.段式地址映射。 2.页式地址映射。

段页式看起来很美好,为什么没成为主流?因为太复杂了。。

21.局部性原理

要想更好地理解虚拟内存技术,必须要知道计算机中著名的 局部性原理(Locality Principle)。另外,局部性原理既适用于程序结构,也适用于数据结构,是非常重要的一个概念。 局部性原理是指在程序执行过程中,数据和指令的访问存在一定的空间和时间上的局部性特点。其中,时间局部性是指一个数据项或指令在一段时间内被反复使用的特点,空间局部性是指一个数据项或指令在一段时间内与其相邻的数据项或指令被反复使用的特点。 在分页机制中,页表的作用是将虚拟地址转换为物理地址,从而完成内存访问。在这个过程中,局部性原理的作用体现在两个方面: 1.时间局部性:由于程序中存在一定的循环或者重复操作,因此会反复访问同一个页或一些特定的页,这就体现了时间局部性的特点。为了利用时间局部性,分页机制中通常采用缓存机制来提高页面的命中率,即将最近访问过的一些页放入缓存中,如果下一次访问的页已经在缓存中,就不需要再次访问内存,而是直接从缓存中读取。

2.空间局部性:由于程序中数据和指令的访问通常是具有一定的空间连续性的,因此当访问某个页时,往往会顺带访问其相邻的一些页。为了利用空间局部性,分页机制中通常采用预取技术来预先将相邻的一些页读入内存缓存中,以便在未来访问时能够直接使用,从而提高访问速度。

总之,局部性原理是计算机体系结构设计的重要原则之一,也是许多优化算法的基础。在分页机制中,利用时间局部性和空间局部性,采用缓存和预取技术,可以提高页面的命中率,从而提高内存访问效率 缓存的一个关键原则是局部性原理,也被称为2-8原则,这意味着大部分的操作(约80%)只会涉及到一小部分的资源(约20%)

lukaliou123 commented 1 year ago

22.文件系统

22.1.文件系统主要做了什么?

文件系统主要负责管理和组织计算机存储设备上的文件和目录,其功能包括以下几个方面: 1.存储管理:将文件数据存储到物理存储介质中,并且管理空间分配,以确保每个文件都有足够的空间存储,并避免文件之间发生冲突。 3.文件管理:文件的创建、删除、移动、重命名、压缩、加密、共享等等。 4.目录管理:目录的创建、删除、移动、重命名等等。 5.文件访问控制:管理不同用户或进程对文件的访问权限,以确保用户只能访问其被授权访问的文件,以保证文件的安全性和保密性。

22.2.硬链接和软链接有什么区别?

在 Linux/类 Unix 系统上,文件链接(File Link)是一种特殊的文件类型,可以在文件系统中指向另一个文件。常见的文件链接类型有两种: 1、硬链接(Hard Link) 1.在 Linux/类 Unix 文件系统中,每个文件和目录都有一个唯一的索引节点(inode)号,用来标识该文件或目录。硬链接通过 inode 节点号建立连接,硬链接和源文件的 inode 节点号相同,两者对文件系统来说是完全平等的(可以看作是互为硬链接,源头是同一份文件),删除其中任何一个对另外一个没有影响,可以通过给文件设置硬链接文件来防止重要文件被误删。 2.只有删除了源文件和所有对应的硬链接文件,该文件才会被真正删除。 3.硬链接具有一些限制,不能对目录以及不存在的文件创建硬链接,并且,硬链接也不能跨越文件系统。 4.ln 命令用于创建硬链接

2.软链接(Symbolic Link 或 Symlink) 1.软链接和源文件的 inode 节点号不同,而是指向一个文件路径。 2.源文件删除后,软链接依然存在,但是指向的是一个无效的文件路径。 3.软连接类似于 Windows 系统中的快捷方式。 4.不同于硬链接,可以对目录或者不存在的文件创建软链接,并且,软链接可以跨越文件系统。 5.ln -s 命令用于创建软链接

以此类比,你可以将硬链接想象成一本书的不同副本,无论你更改哪一本,所有的副本都会发生改变。而软链接则像是一本书的书签,书签告诉你书的位置,但如果书被移走了,书签就失去了作用。

实际使用的例子:

1690719881088 1690719900039 1690719933790

22.3.硬链接为什么不能跨文件系统?

我们之前提到过,硬链接是通过 inode 节点号建立连接的,而硬链接和源文件共享相同的 inode 节点号。然而,每个文件系统都有自己的独立 inode 表,且每个 inode 表只维护该文件系统内的 inode。如果在不同的文件系统之间创建硬链接,可能会导致 inode 节点号冲突的问题,即目标文件的 inode 节点号已经在该文件系统中被使用

lukaliou123 commented 1 year ago

22.4.提高文件系统性能的方式有哪些?

1.优化硬件:使用高速硬件设备(如 SSD、NVMe)替代传统的机械硬盘,使用 RAID(Redundant Array of Inexpensive Disks)等技术提高磁盘性能。 2.选择合适的文件系统选型:不同的文件系统具有不同的特性,对于不同的应用场景选择合适的文件系统可以提高系统性能。 3.运用缓存:访问磁盘的效率比较低,可以运用缓存来减少磁盘的访问次数。不过,需要注意缓存命中率,缓存命中率过低的话,效果太差。 4.避免磁盘过度使用:注意磁盘的使用率,避免将磁盘用满,尽量留一些剩余空间,以免对文件系统的性能产生负面影响。 5.对磁盘进行合理的分区:合理的磁盘分区方案,能够使文件系统在不同的区域存储文件,从而减少文件碎片,提高文件读写性能。

22.5.常见的磁盘调度算法有哪些?

磁盘调度算法是操作系统中对磁盘访问请求进行排序和调度的算法,其目的是提高磁盘的访问效率。 一次磁盘读写操作的时间由磁盘寻道/寻找时间、延迟时间和传输时间决定。磁盘调度算法可以通过改变到达磁盘请求的处理顺序,减少磁盘寻道时间和延迟时间。 常见的磁盘调度算法有下面这 6 种(其他还有很多磁盘调度算法都是基于这些算法改进得来的) image

1.先来先服务算法(First-Come First-Served,FCFS):按照请求到达磁盘调度器的顺序进行处理,先到达的请求的先被服务。FCFS 算法实现起来比较简单,不存在算法开销。不过,由于没有考虑磁头移动的路径和方向,平均寻道时间较长。同时,该算法容易出现饥饿问题,即一些后到的磁盘请求可能需要等待很长时间才能得到服务。

2.最短寻道时间优先算法(Shortest Seek Time First,SSTF):也被称为### 最佳服务优先(Shortest Service Time First,SSTF)算法,优先选择距离当前磁头位置最近的请求进行服务。SSTF 算法能够最小化磁头的寻道时间,但容易出现饥饿问题,即磁头附近的请求不断被服务,远离磁头的请求长时间得不到响应。实际应用中,需要优化一下该算法的实现,避免出现饥饿问题。

3.扫描算法(SCAN):也被称为电梯(Elevator)算法,基本思想和电梯非常类似。磁头沿着一个方向扫描磁盘,如果经过的磁道有请求就处理,直到到达磁盘的边界,然后改变移动方向,依此往复。SCAN 算法能够保证所有的请求得到服务,解决了饥饿问题。但是,如果磁头从一个方向刚扫描完,请求才到的话。这个请求就需要等到磁头从相反方向过来之后才能得到处理。

4.循环扫描算法(Circular Scan,C-SCAN):SCAN 算法的变体,只在磁盘的一侧进行扫描,并且只按照一个方向扫描,直到到达磁盘边界,然后回到磁盘起点,重新开始循环。

5.边扫描边观察算法(LOOK)SCAN 算法中磁头到了磁盘的边界才改变移动方向,这样可能会做很多无用功,因为磁头移动方向上可能已经没有请求需要处理了。LOOK 算法对 SCAN 算法进行了改进,如果磁头移动方向上已经没有别的请求,就可以立即改变磁头移动方向,依此往复。也就是边扫描边观察指定方向上还有无请求,因此叫 LOOK。

6.均衡循环扫描算法(C-LOOK):C-SCAN 只有到达磁盘边界时才能改变磁头移动方向,并且磁头返回时也需要返回到磁盘起点,这样可能会做很多无用功。C-LOOK 算法对 C-SCAN 算法进行了改进,如果磁头移动的方向上已经没有磁道访问请求了,就可以立即让磁头返回,并且磁头只需要返回到有磁道访问请求的位置即可