《操作系统导论》第15章读书笔记:机制:地址转换(address translation)

《操作系统导论》第15章读书笔记:机制:地址转换(address translation)

效果图

效果图

—— 杭州 2024-03-30 夜

文章目录

  • 《操作系统导论》第15章读书笔记:机制:地址转换(address translation)
    • 1.前言
    • 2.一个例子
    • 3.动态(基于硬件)重定位(dynamic relocation)
      • 转换示例
    • 4.硬件支持:总结
    • 5.操作系统的问题
    • 6.小结

1.前言

  • 受限直接访问(Limited Direct Execution,LDE)。LDE背后的想法很简单:让程序运行的大部分指令直接访问硬件,只在一些关键点(如进程发起系统调用或发生时钟中断)由操作系统介入来确保“在正确时间,正确的地点,做正确的事”。
  • 为了实现高效的虚拟化,操作系统应该尽量让程序自己运行,同时通过在关键点的及时介入(interposing),来保持对硬件的控制。高效和控制是现代操作系统的两个主要目标。
  • 基于硬件的地址转换(hardware-based address translation),简称为地址转换(address translation)。它可以看成是受限直接执行这种一般方法的补充。利用地址转换,硬件对每次内存访问进行处理(即指令获取、数据读取或写入),将指令中的虚拟(virtual)地址转换为数据实际存储的物理(physical)地址。因此,在每次内存引用时,硬件都会进行地址转换,将应用程序的内存引用重定位到内存中实际的位置。
  • 当然,仅仅依靠硬件不足以实现虚拟内存,因为它只是提供了底层机制来提高效率。操作系统必须在关键的位置介入,设置好硬件,以便完成正确的地址转换。因此它必须管理内存(manage memory),记录被占用和空闲的内存位置,并明智而谨慎地介入,保持对内存使用的控制。
  • 同样,所有这些工作都是为了创造一种美丽的假象:每个程序都拥有私有的内存,那
    里存放着它自己的代码和数据。虚拟现实的背后是丑陋的物理事实:许多程序其实是在同一时间共享着内存,就像CPU(或多个CPU)在不同的程序间切换运行。通过虚拟化,
    操作系统(在硬件的帮助下)将丑陋的机器现实转化成一种有用的、强大的、易于使用的抽象。

在这里插入图片描述

2.一个例子

  • 设想一个进程的地址空间如图 15.1 所示。这里我们要检查一小段代码,它从内存中加载一个值,对它加 3,然后将它存回内存。你可以设想,这段代码的 C 语言形式可能像这样:
void func() {int x;x = x + 3; // this is the line of code we are interested in
  • 编译器将这行代码转化为汇编语句,可能像下面这样(x86 汇编)。我们可以用 Linux的 objdump 或者 Mac 的 otool 将它反汇编:
    128: movl 0x0(%ebx), %eax   ;load 0+ebx into eax132: addl $0x03, %eax       ;add 3 to eax register135: movl %eax, 0x0(%ebx)   ;store eax back to mem
  • 这段代码相对简单,它假定 x 的地址已经存入寄存器 ebx,之后通过 movl指令将这个地址的值加载到通用寄存器eax(长字移动)。下一条指令对 eax 的内容加 3。最后一条指令将 eax中的值写回到内存的同一位置。

  • 介入(Interposition):介入是一种很常见又很有用的技术,计算机系统中使用介入常常能带来很好的效果。在虚拟内存中,硬件可以介入到每次内存访问中,将进程提供的虚拟地址转换为数据实际存储的物理地址。

  • 在图 15.1 中,可以看到代码和数据都位于进程的地址空间,3 条指令序列位于地址 128(靠近头部的代码段),变量 x 的值位于地址 15KB(在靠近底部的栈中)。如图 15.1 所示,x的初始值是 3000。

  • 如果这 3 条指令执行,从进程的角度来看,发生了以下几次内存访问:
    从地址 128 获取指令;
    执行指令(从地址 15KB 加载数据);
    从地址 132 获取命令;
    执行命令(没有内存访问);
    从地址 135 获取指令;
    执行指令(新值存入地址 15KB)。

  • 从程序的角度来看,它的地址空间(address space)从 0 开始到 16KB结束。它包含的所有内存引用都应该在这个范围内。然而,对虚拟内存来说,操作系统希望将这个进程地址空间放在物理内存的其他位置,并不一定从地址 0 开始。因此我们遇到了如下问题:怎样在内存中重定位这个进程,同时对该进程透明(transparent)?怎么样提供一种虚拟地址空间从 0 开始的假象,而实际上地址空间位于另外某个物理地址?

在这里插入图片描述

3.动态(基于硬件)重定位(dynamic relocation)

为了更好地理解基于硬件的地址转换,我们先来讨论它的第一次应用。在 20世纪50年代后期,它在首次出现的时分机器中引入,那时只是一个简单的思想,称为基址加界限机制(base and bound),有时又称为动态重定位(dynamic relocation),我们将互换使用这两个术语。

具体来说,每个 CPU 需要两个硬件寄存器:基址(base)寄存器和界限(bound)寄存器,有时称为限制(limit)寄存器。这组基址和界限寄存器,让我们能够将地址空间放在物理内存的任何位置,同时又能确保进程只能访问自己的地址空间。

采用这种方式,在编写和编译程序时假设地址空间从零开始。但是,当程序真正执行时,操作系统会决定其在物理内存中的实际加载地址,并将起始地址记录在基址寄存器中。在上面的例子中,操作系统决定加载在物理地址 32KB的进程,因此将基址寄存器设置为这个值。

当进程运行时,有趣的事情发生了。现在,该进程产生的所有内存引用,都会被处理器通过以下方式转换为物理地址:

physical address = virtual address + base

进程中使用的内存引用都是虚拟地址(virtual address),硬件接下来将虚拟地址加上基址寄存器中的内容,得到物理地址(physical address),再发给内存系统。

为了更好地理解,让我们追踪一条指令执行的情况。具体来看前面序列中的一条指令:

128: movl 0x0(%ebx), %eax

程序计数器(PC)首先被设置为 128。当硬件需要获取这条指令时,它先将这个值加上基址寄存器中的32KB(32768),得到实际的物理地址 32896,然后硬件从这个物理地址获取指令。

接下来,处理器开始执行该指令。这时,进程发起从虚拟地址 15KB的加载,处理器同样将虚拟地址加上基址寄存器内容(32KB),得到最终的物理地址 47KB,从而获得需要的数据。

将虚拟地址转换为物理地址,这正是所谓的地址转换(address translation)技术。也就是说,硬件取得进程认为它要访问的地址,将它转换成数据实际位于的物理地址。由于这种重定位是在运行时发生的,而且我们甚至可以在进程开始运行后改变其地址空间,这种技术一般被称为动态重定位(dynamic relocation)。

提示:基于硬件的动态重定位
在动态重定位的过程中,只有很少的硬件参与,但获得了很好的效果。一个基址寄存器将虚拟地址转换为物理地址,一个界限寄存器确保这个地址在进程地址空间的范围内。它们一起提供了既简单又高效的虚拟内存机制。

现在你可能会问,界限(限制)寄存器去哪了?不是基址加界限机制吗?正如你猜测的那样,界限寄存器提供了访问保护。在上面的例子中,界限寄存器被置为 16KB。如果进程需要访问超过这个界限或者为负数的虚拟地址,CPU将触发异常,进程最终可能被终止。界限寄存器的用处在于,它确保了进程产生的所有地址都在进程的地址"界限"中。

这种基址寄存器配合界限寄存器的硬件结构是芯片中的(每个 CPU 一对)。有时我们将CPU的这个负责地址转换的部分统称为内存管理单元(Memory Management Unit,MMU)。随着我们开发更复杂的内存管理技术,MMU 也将有更复杂的电路和功能。

关于界限寄存器再补充一点,它通常有两种使用方式。在一种方式中(像上面那样),它记录地址空间的大小,硬件在将虚拟地址与基址寄存器内容求和前,就检查这个界限。另一种方式是界限寄存器中记录地址空间结束的物理地址,硬件在转化虚拟地址到物理地址之后才去检查这个界限。这两种方式在逻辑上是等价的。简单起见,我们这里假设采用第一种方式。

转换示例

为了更好地理解基址加界限的地址转换的详细过程,我们来看一个例子。设想一个进程拥有 4KB 大小地址空间(是的,小得不切实际),它被加载到从 16KB 开始的物理内存中。一些地址转换结果见表 15.1。

表 15.1 地址转换结果

虚拟地址物理地址
016KB
1KB17KB
300019384
4400错误(越界)

从例子中可以看到,通过基址加虚拟地址(可以看作是地址空间的偏移量)的方式,很容易得到物理地址。虚拟地址"过大"或者为负数时,会导致异常。

补充:数据结构——空闲列表

操作系统必须记录哪些空闲内存没有使用,以便能够为进程分配内存。很多不同的数据结构可以用于这项任务,其中最简单的(也是我们假定在这里采用的)是空闲列表(free list)。它就是一个列表,记录当前没有使用的物理内存的范围。

在这里插入图片描述

4.硬件支持:总结

  • 两种 CPU 模式。操作系统在特权模式(privileged mode,或内核模式,kernelmode),可以访问整个机器资源。
  • 应用程序在用户模式(user mode)运行,只能做有限的操作。只要一个位,也许保存在处理器状态字(processor status word)中,就能说明当前的CPU 运行模式。在一些特殊的时刻(如系统调用、异常或中断),CPU 会切换状态。
硬件要求解释
特权模式需要,以防用户模式的进程执行特权操作
基址/界限寄存器每个 CPU 需要一对寄存器来支持地址转换和界限检查
能够转换虚拟地址并检查它是否越界电路来完成转换和检查界限,在这种情况下,非常简单
修改基址/界限寄存器的特权指令在让用户程序运行之前,操作系统必须能够设置这些值
注册异常处理程序的特权指令操作系统必须能告诉硬件,如果异常发生,那么执行哪些代码
能够触发异常如果进程试图使用特权指令或越界的内存
  • 硬件还必须提供基址和界限寄存器(base and bounds register),因此每个 CPU 的内存管理单元(Memory Management Unit,MMU)都需要这两个额外的寄存器。用户程序运行时,硬件会转换每个地址,即将用户程序产生的虚拟地址加上基址寄存器的内容。硬件也必须能检查地址是否有用,通过界限寄存器和 CPU 内的一些电路来实现。

  • 硬件应该提供一些特殊的指令,用于修改基址寄存器和界限寄存器,允许操作系统在切换进程时改变它们。这些指令是特权(privileged)指令,只有在内核模式下,才能修改这些寄存器。想象一下,如果用户进程在运行时可以随意更改基址寄存器,那么用户进程可能会造成严重破坏。想象一下吧!然后迅速将这些阴暗的想法从你的头脑中赶走,因为它们很可怕,会导致噩梦。

  • 最后,在用户程序尝试非法访问内存(越界访问)时,CPU必须能够产生异常(exception)。在这种情况下,CPU应该阻止用户程序的执行,并安排操作系统的"越界"异常处理程序(exception handler)去处理。操作系统的处理程序会做出正确的响应,比如在这种情况下终止进程。

  • 类似地,如果用户程序尝试修改基址或者界限寄存器时,CPU也应该产生异常,并调用"用户模式尝试执行特权指令"的异常处理程序。xCPU 还必须提供一种方法,来通知它这些处理程序的位置,因此又需要另一些特权指令。

在这里插入图片描述

5.操作系统的问题

为了支持动态重定位,硬件添加了新的功能,使得操作系统有了一些必须处理的新问题。硬件支持和操作系统管理结合在一起,实现了一个简单的虚拟内存。具体来说,在一些关键的时刻操作系统需要介入,以实现基址和界限方式的虚拟内存,见表 15.3。

  • 第一,在进程创建时,操作系统必须采取行动,为进程的地址空间找到内存空间。由于我们假设每个进程的地址空间小于物理内存的大小,并且大小相同,这对操作系统来说很容易。它可以把整个物理内存看作一组槽块,标记了空闲已用。当新进程创建时,操作系统检索这个数据结构(常被称为空闲列表,free list),为新地址空间找到位置,并将其标记为已用。如果地址空间可变,那么生活就会更复杂,我们将在后续章节中讨论。

我们来看一个例子。在图 15.2 中,操作系统将物理内存的第一个槽块分配给自己,然后将例子中的进程重定位到物理内存地址 32KB。另两个槽块(16~32KB,48~64KB)空闲,因此空闲列表(free list)就包含这两个槽块

  • 第二,在进程终止时(正常退出,或因行为不端被强制终止),操作系统也必须做一些工作,回收它的所有内存,给其他进程或者操作系统使用。在进程终止时,操作系统会将这些内存放回到空闲列表,并根据需要清除相关的数据结构。

  • 第三,在上下文切换时,操作系统也必须执行一些额外的操作。每个 CPU 毕竟只有一个基址寄存器和一个界限寄存器,但对于每个运行的程序,它们的值都不同,因为每个程序被加载到内存中不同的物理地址。因此,在切换进程时,操作系统必须保存和恢复基础和界限寄存器。具体来说,当操作系统决定中止当前的运行进程时,它必须将当前基址和界限寄存器中的内容保存在内存中,放在某种每个进程都有的结构中,如进程结构(process structure)或进程控制块(Process Control Block,PCB)中。类似地,当操作系统恢复执行某个进程时(或第一次执行),也必须给基址和界限寄存器设置正确的值。

表 15.3 动态重定位:操作系统的职责

操作系统的要求解释
内存管理需要为新进程分配内存
从终止的进程回收内存
一般通过空闲列表(free list)来管理内存
基址/界限管理必须在上下文切换时正确设置基址/界限寄存器
异常处理当异常发生时执行的代码,可能的动作是终止犯错的进程

需要注意,当进程停止时(即没有运行),操作系统可以改变其地址空间的物理位置,这很容易。要移动进程的地址空间,操作系统首先让进程停止运行,然后将地址空间拷贝到新位置,最后更新保存的基址寄存器(在进程结构中),指向新位置。当该进程恢复执行时,它的(新)基址寄存器会被恢复,它再次开始运行,显然它的指令和数据都在新的内存位置了。

  • 第四,操作系统必须提供异常处理程序(exception handler),或要一些调用的函数,像上面提到的那样。操作系统在启动时加载这些处理程序(通过特权命令)。例如,当一个进程试图越界访问内存时,CPU 会触发异常。在这种异常产生时,操作系统必须准备采取行动。通常操作系统会做出充满敌意的反应:终止错误进程。操作系统应该尽力保护它运行的机器,因此它不会对那些企图访问非法地址或执行非法指令的进程客气。再见了,行为不端的进程,很高兴认识你。

表15.4 为按时间线展示了大多数硬件与操作系统的交互。可以看出,操作系统在启动时做了什么,为我们准备好机器,然后在进程(进程A)开始运行时发生了什么。请注意,地址转换过程完全由硬件处理,没有操作系统的介入。在这个时候,发生时钟中断,操作系统切换到进程B 运行,它执行了“错误的加载”(对一个非法内存地址),这时操作系统必须介入,终止该进程,清理并释放进程B 占用的内存,将它从进程表中移除。从表中可以看出,我们仍然遵循受限直接访问(limited direct execution)的基本方法,大多数情况下,操作系统正确设置硬件后,就任凭进程直接运行在CPU上,只有进程行为不端时才介入
表 15.4 受限直接执行协议(动态重定位)

时间操作系统@启动(内核模式)硬件
T0初始化陷阱表
开始时钟,在 x ms 后中断
初始化进程表
初始化空闲列表
记住以下地址:
系统调用处理程序
时钟处理程序
非法内存处理程序
非常指令处理程序
开始中断时钟
T1为了启动进程 A:
在进程表中分配条目
为进程分配内存
设置基址/界限寄存器
从陷阱返回(进入 A)
恢复 A 的寄存器
转向用户模式
跳到 A(最初)的程序计数器
T2进程 A 运行
获取指令
执行指令
……
转换虚拟地址并执行获取
如果显式加载/保存
确保地址不越界
转换虚拟地址并执行
加载/保存
T3时钟中断
转向内核模式
跳到中断处理程序
T4处理陷阱
调用 switch()例程
将寄存器(A)保存到进程结构(A)
(包括基址/界限)
从进程结构(B)恢复寄存器(B)
(包括基址/界限)
从陷阱返回(进入 B)
T5处理本期报告
决定终止进程 B
回收 B 的内存
移除 B 在进程表中的条目
恢复 B 的寄存器
转向用户模式
跳到 B 的程序计数器
T6进程 B 运行
执行错误的加载
加载越界
转向内核模式
跳到陷阱处理程序

在这里插入图片描述

在这里插入图片描述

6.小结

本章通过虚拟内存使用的一种特殊机制,即地址转换(address translation)扩展了受限直接访问的概念。利用地址转换,操作系统可以控制进程的所有内存访问,确保访问在地址空间的界限内。这个技术高效的关键是硬件支持,硬件快速地将所有内存访问操作中的虚拟地址(进程自己看到的内存位置)转换为物理地址(实际位置)。所有的这一切对进程来说都是透明的,进程并不知道自己使用的内存引用已经被重定位,制造了美妙的假象

我们还看到了一种特殊的虚拟化方式,称为基址加界限的动态重定位。基址加界限的虚拟化方式非常高效,因为只需要很少的硬件逻辑,就可以将虚拟地址和基址寄存器加起来,并检查进程产生的地址没有越界。基址加界限也提供了保护,操作系统和硬件的协作,确保没有进程能够访问其地址空间之外的内容。保护肯定是操作系统最重要的目标之一。没有保护,操作系统不可能控制机器(如果进程可以随意修改内存,它们就可以轻松地做出可怕的事情,比如重写陷阱表并完全接管系统)。

遗憾的是,这个简单的动态重定位技术有效率低下的问题。例如,从图 15.2 中可以看到,重定位的进程使用了从 32KB 到 48KB 的物理内存,但由于该进程的栈区和堆区并不很大,导致这块内存区域中大量的空间被浪费。这种浪费通常称为内部碎片(internal fragmentation)指的是已经分配的内存单元内部有未使用的空间(即碎片),造成了浪费。在我们当前的方式中,即使有足够的物理内存容纳更多进程,但我们目前要求将地址空间放在固定大小的槽块中,因此会出现内部碎片①。所以,我们需要更复杂的机制,以便更好地利用物理内存,避免内部碎片。第一次尝试是将基址加界限的概念稍稍泛化,得到分段(segmentation)的概念,我们接下来将讨论。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/781950.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

正点原子imx6ull-mini不使用网络更新内核系统

参考视频:【【正点原子】Linux网络环境搭建篇】 参考文档:从正点原子官方下载 这几天在学imx6ull写网络驱动检测出网卡,但是一直ping不通ubuntu,电脑还有ubuntu、开发板都处于同一个网段,跟着正点原子的视频试了双网…

flutter Got socket error trying to find package nested at

flutter Got socket error trying to find package nested at xxx 报错信息:“Got socket error trying to find package nested at” 通常出现在Flutter尝试从pub.dev获取依赖包时,由于网络问题导致无法连接到pub.dev或者无法正确解析包的路径。 例如&…

2_1.Linux中的网络配置

#1.什么是IP ADDRESS# internet protocol ADDRESS ##网络进程地址 ipv4 internet protocol version 4 ip是由32个01组成 11111110.11111110.11111110.11111110 254.254.254.254 #2.子网掩码# 用来划分网络区域 子网掩码非0的位对应的ip上的数字表示这个ip的网络位 子网掩码0位…

S7-1500PLC与ABB机器人RobotStudio调试演示

(1)建立空工作站 (2)选择机器人、导入吸盘、托盘、传送带 (3) 将导入的吸盘变为工具 (4)创建机器人系统 布局如下 (5)创建物体 (6)设置物体本地原点 (7)创建传送带Smart组件

[操作系统课设]GeeKOS操作系统的研究与实现

一.GeekOS操作系统概论 1.1教学操作系统 (1)针对RISC结构MIPS处理器 操作系统:Nachos、OS/161 (2)针对CISC结构Intel IA-32 (or x86)通用处理 操作系统:MINIX、GeekOS 我们用到的是:GeekOS 1&…

二分(二段性)

本文用于记录个人算法竞赛学习,仅供参考 一.二分算法 二分算法一般用于具有二段性的问题,数据不一定具有单调性,所以单调可二分,可二分不一定就要单调。 二.整数二分 1. 模板一:将区间[l, r]划分为[l, mid] 和 [mid…

字符串(KMP)

P3375 【模板】KMP - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) #include<iostream> #include<algorithm> #include<cstdio> #include<cstring> using namespace std; #define ll long long const int N1e6100; int n0,m; char s1[N]; char s2[N];…

36.HarmonyOS鸿蒙系统 App(ArkUI) 创建第一个应用程序hello world

36.HarmonyOS App(ArkUI) 创建第一个应用程序helloworld 线性布局 1.鸿蒙应用程序开发app_hap开发环境搭建 3.DevEco Studio安装鸿蒙手机app本地模拟器 打开DevEco Studio,点击文件-》新建 双击打开index.ets 复制如下代码&#xff1a; import FaultLogger from ohos.fau…

通俗易懂Redis缓存穿透,缓存击穿,缓存雪崩

1.1 缓存穿透 原因&#xff1a;当我们查询一个数据的时候&#xff0c;缓存中没有&#xff0c;就会去查询我们的关系型数据库&#xff0c;而且查询不到的数据是不会放到我们的缓存中&#xff0c;就会导致我们每次的请求都会来到我们的关系型数据库中&#xff0c;从而导致关系型…

使用Flink实现Kafka到MySQL的数据流转换:一个基于Flink的实践指南

使用Flink实现Kafka到MySQL的数据流转换 在现代数据处理架构中&#xff0c;Kafka和MySQL是两种非常流行的技术。Kafka作为一个高吞吐量的分布式消息系统&#xff0c;常用于构建实时数据流管道。而MySQL则是广泛使用的关系型数据库&#xff0c;适用于存储和查询数据。在某些场景…

小米SU7 我劝你再等等

文 | AUTO芯球 作者 | 李逵 我必须承认我一时没忍住 犯错了 我不会被我老婆打吧 感觉有点慌呀 这不前两天 我刚提了台问界M9嘛 但是昨晚看小米汽车发布会 是真的被雷总感染到了 真的没忍住 我又冲了台小米SU7 Pro版 本来我是准备抢创始版的 结果1秒钟时间 点进去就…

yolov5 v7.0打包exe文件,使用C++调用

cd到yolo5文件夹下 pyinstaller -p 当前路径 -i logo图标 detect.py问题汇总 运行detect.exe找不到default.yaml 这个是yolov8里的文件 1 复制权重文件到exe所在目录。 2 根据报错提示的配置文件路径&#xff0c;把default.yaml复制放到相应的路径下。&#xff08;缺少相应…

杨辉三角形(c++实现)

题目 下面的图形是著名的杨辉三角形&#xff1a; 如果我们按从上到下、从左到右的顺序把所有数排成一列&#xff0c;可以得到如下数列&#xff1a; 1, 1, 1, 1, 2, 1, 1, 3, 3, 1, 1, 4, 6, 4, 1, … 给定一个正整数 N&#xff0c;请你输出数列中第一次出现 N 是在第几个数&a…

实现 Element UI el-table 树形数据的懒加载

当面对大量数据时&#xff0c;一次性加载所有数据可能会导致性能问题。为了解决这一问题&#xff0c;我们可以实现树形数据的懒加载。本文将介绍如何在使用 Element UI 的 Vue 应用中为 el-table 组件的树形数据添加懒加载功能。 懒加载的基本概念 懒加载是一种优化网页或应用…

中国31个省农村用电量(2000-2022年)

数据介绍&#xff1a; 农村用电量是一个动态变化的数据&#xff0c;受到多种因素的影响&#xff0c;包括农村经济发展、人口增长、农业生产活动增加以及电力设备的升级改造等。随着农村经济的发展和农民生活水平的提高&#xff0c;农村用电量呈现出逐年增长的趋势。同时&#…

消息中间件区别

ActiveMQ 我们先看ActiveMQ。其实一般早些的项目需要引入消息中间件&#xff0c;都是使用的这个MQ&#xff0c;但是现在用的确实不多了&#xff0c;说白了就是有些过时了。我们去它的官网看一看&#xff0c;你会发现官网已经不活跃了&#xff0c;好久才会更新一次。 它的单机吞…

查找某数据在单链表中出现的次数

#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> typedef int ElemType; typedef struct LinkNode {ElemType data;LinkNode* next; }LinkNode, * LinkList; //尾插法建立单链表 void creatLinkList(LinkList& L) {L (LinkNode*)mallo…

设计模式之解释器模式的魅力:让代码读懂你的语言

目录 一、什么是解释器模式 二、解释器模式的应用场景 三、解释器模式的优缺点 3.1. 优点 3.2. 缺点 四、解释器模式示例 4.1. 问题描述 4.2. 问题分析 4.3. 代码实现 4.4. 优化方向 五、总结 一、什么是解释器模式 解释器模式&#xff08;Interpreter pattern&…

kubernetes(K8S)学习(七):K8S之系统核心组件

K8S之系统核心组件 K8s系统核心组件1.1 Master和Node1.2 kubeadm1.3 先把核心组件总体过一遍1.4 Kubernetes源码查看方式1.5 kubectl1.6 API Server1.7 集群安全机制之API Server1.8 Scheduler1.9 kubelet1.10 kube-proxy K8s系统核心组件 1.1 Master和Node 官网 &#xff1a;…

360奇酷刷机 360刷机助手 QGDP360手机QGDP刷机

360奇酷刷机 360刷机助手 QGDP破解版360手机QGDP刷机 360手机刷机资源下载链接&#xff1a;360rom.github.io 参考&#xff1a;360手机-360刷机360刷机包twrp、root 360奇酷刷机&#xff1a;360高通驱动安装 360手机刷机驱动&#xff1b;手机内置&#xff0c;可通过USB文件传输…