《操作系统导论》第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、开发板都处于同一个网段,跟着正点原子的视频试了双网…

新一代信息技术元年汇总

元年汇总: 大数据 1998 区块链 2008 云计算 2012 RPA 2018 移动通信技术(5G的元年) 2019

DW1000 定位技术解析

Qorvo 的 DW1000 是一款完全集成的单芯片超宽带 (UWB) 低功耗、低成本收发器 IC,符合 IEEE 802.15.4a 标准。它可用于 2 向测距或 TDoA 定位系统,以 10 厘米的精度定位资产。它还支持速率高达 6.8 Mbps 的数据传输。DW1000 由一个包含一个接收器137和一个…

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组件

单例设计模式(2)

单例设计模式(2) 单例模式存在的问题 单例对 OOP 特性的支持不友好 oop的特性:封装、继承、多态、抽象;以Id生成器代码为例,如果未来某一天,我们希望针对不同的业务采用不同的 ID 生成算法。比如&#x…

通过多选按钮选择需要修改什么字段

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、代码 前言 想要更新什么字段就将该字段更新,第一想到通过多选框控制,通过一系列的尝试,做了如下的布局和功能 直接上代…

[操作系统课设]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;从而导致关系型…

代码随想录刷题day39|不同路径不同路径II

文章目录 day39学习内容一、不同路径2.1、动态规划五部曲1.1.1、 确定dp数组&#xff08;dp table&#xff09;以及下标的含义1.1.2、确定递推公式1.1.3、 dp数组如何初始化1.1.4、确定遍历顺序1.1.5、计算并返回最终结果 1.2、代码 二、不同路径II2.1、动态规划五部曲2.1.1、 …

使用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;农村用电量呈现出逐年增长的趋势。同时&#…