操作系统笔记、面试八股(三)—— 系统调用与内存管理

文章目录

  • 3. 系统调用
    • 3.1 用户态与内核态
    • 3.2 系统调用分类
    • 3.3 如何从用户态切换到内核态(系统调用举例)
  • 4. 内存管理
    • 4.1 内存管理是做什么的
      • 4.1.1 为什么需要虚拟地址空间
      • 4.1.2 使用虚拟地址访问内存有什么优势
    • 4.2 常见的内存管理机制
    • 4.3 分页管理
      • 4.3.1 基本概念
      • 4.3.2 地址转换(由逻辑地址访问内存的过程)
      • 4.3.3 快表
        • 4.3.3.1 为什么引入快表
        • 4.3.3.2 快表的访问流程
      • 4.3.4 多级页表
        • 4.3.4.1 为什么引入多级页表
    • 4.4 分段管理
      • 4.4.1 段表
      • 4.4.2 地址转换
      • 4.4.3 分页和分段的对比
    • 4.5 段页式管理
      • 4.5.1 逻辑地址结构
      • 4.5.2 段表与页表
      • 4.5.3 地址转换
    • 4.6 由逻辑地址访问内存的次数
    • 4.7 虚拟内存
      • 4.7.1 传统存储管理方式的特征及缺点
      • 4.7.2 局部性原理
      • 4.7.3 什么是虚拟内存
      • 4.7.4 虚拟内存的特征
      • 4.7.5 如何实现虚拟内存
    • 4.8 页面置换算法
      • 4.8.1 最佳置换算法(OPT)
      • 4.8.2 先进先出置换算法(FIFO)
      • 4.8.3 最近最久未使用置换算法(LRU)
      • 4.8.4 最少使用置换算法(LFU)
      • 4.8.5 时钟置换算法(CLOCK,NRU)
      • 4.8.6 改进型的时钟置换算法

3. 系统调用

3.1 用户态与内核态

进程在系统上的运行分为两个级别,用户态和内核态。

用户态的进程可以直接读取用户程序的数据,系统态的进程可以直接访问计算机的任何资源,不受限制。

运行的应用程序基本都是运行在用户态,如果调用操作系统提供的系统态的子功能,就需要系统调用。

3.2 系统调用分类

系统调用,按功能大致分为:

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

3.3 如何从用户态切换到内核态(系统调用举例)

  1. 用户态进程主动要求切换到内核态的系统调用
  2. 异常,比如缺页异常
  3. 外围设备的中断,比如硬盘读写操作完成。

4. 内存管理

4.1 内存管理是做什么的

  1. 负责内存空间的分配与回收(C++中的malloc函数申请内存,free函数释放内存)

  2. 从逻辑上对内存空间进行扩充(如4GB内存运行60GB大小的游戏)

  3. 负责进行地址转换,也就是把逻辑地址转换成相应的物理地址

  4. 内存保护。保证各进程在各自存储空间内运行,互不干扰。

4.1.1 为什么需要虚拟地址空间

如果没有虚拟地址空间,程序直接访问和操作的都是物理内存。这样会带来两个主要问题:

  1. 用户程序可以访问任意内存,寻址内存的每个字节,这样就很容易破坏操作系统,造成操作系统崩溃。
  2. 想要同时运行多个程序特别困难。举个例子:微信在运行的时候给内存地址 1xxx 赋值后,QQ 音乐也同样给内存地址 1xxx 赋值,那么 QQ 音乐对内存的赋值就会覆盖微信之前所赋的值,这就造成了微信这个程序就会崩溃。

4.1.2 使用虚拟地址访问内存有什么优势

  1. 程序可以使用一系列相邻的虚拟地址来访问物理内存中不相邻的大内存缓冲区。
  2. 程序可以使用一系列虚拟地址来访问大于可用物理内存的内存缓冲区。
  3. 不同进程使用的虚拟地址彼此隔离。一个进程中的代码无法更改正在由另一进程或操作系统使用的物理内存。

4.2 常见的内存管理机制

内存管理机制分为连续管理方式(指为一个用户程序分配一个连续的内存空间)和非连续管理方式(指允许一个程序使用的内存有着不相邻的分布)

  • 连续分配管理方式

    1. 分块管理

      把内存分为大小相等且固定的几个块,每个进程占用其中的一个。如果进程很小的话,会浪费大量的空间。块式管理已经被淘汰。

  • 非连续分配管理方式

    1. 分页管理

      把内存分为若干个很小的页面,相比分块,划分的力度更大。分页管理提高了内存利用率,减少内存碎片。

      分页管理通过页表对应逻辑地址和物理地址。

      分页管理虽然提高了内存利用率,但是“页”无实际意义。

    2. 分段管理

      把内存分为几个大小不固定的、有意义的段,每个段定义了一组逻辑信息。例如,主程序段Main、子程序段X、数据段D、栈段S等。

      分段管理通过段表对应逻辑地址和物理地址。

    3. 段页式管理

      段页式管理结合了分段管理和分页管理的优点。把主存先分成若干段,每个段又分成若干页。段与段之间、段的内部(因为是页)都是离散的。

4.3 分页管理

4.3.1 基本概念

  1. 页框:将内存空间分为一个个大小相等的分区(比如每个分区4KB),每个分区就是一个“页框”,或者称为“页帧”、“内存块”、“物理块”

  2. 页框号:每个页框有一个编号,称为“页框号”,或者“页帧号”、“内存块号”、“物理块号”。页框号从0开始

  3. 页/页面:

    将用户进程的地址空间也分为与页框大小相等的一个个区域,称为“页”或“页面”。每个页面也有一个编号,即“页号”,页号也是从0开始。

    一个进程的最后一个页可能并没有页框那么大(这样最后一个页放在页框中会有内部碎片)。因此页框不能太大,避免产生过大的内部碎片。

操作系统以页框为单位为各个进程分配内存空间。进程的每个页面分别放入一个页框中。也就是说,进程的页面与内存的页框有一一对应的关系。

各个页面不必连续存放,也不必按照顺序,可以放到不相邻的页框中。

  1. 分页管理的虚拟地址分为两部分,一部分是存储页面号,一部分是存储偏移量

  • 如果有K位表示“页内偏移量”,则说明该系统中一个页面的大小是2^K个内存单元。
  • 如果有M位表示“页号”,则说明该系统中,一个进程最多允许有2^M个页面。
  1. 页表

分页管理中,每个页表项由“页号”和“内存块号”组成。

4.3.2 地址转换(由逻辑地址访问内存的过程)

内存管理单元(MMU)管理着逻辑地址和物理内存的转换,其中页表(Page Table)存储着页(程序地址空间)与页框(物理内存)的映射表。

下图页表存放着16个页,这16个页要用4个比特位来进行索引定位。

例如,对于虚拟地址(0010 0000 0000 0100),前4位是存储页面号(0010)2,后12位(0000 0000 0100)是存储偏移量。

根据页面号读取页表中对应表项(2)的内容(1101)。页表项内容的前3位表示内存块号,最后一位表示是否存在于内存中(0表示不存在,1表示存在)。

拼接页表项内容的前三位(内存块号,110)和虚拟地址中的后12位(存储偏移量,0000 0000 0100),得到物理地址(110 0000 0000 0100)。

根据得到的物理地址访问内存空间。

4.3.3 快表

4.3.3.1 为什么引入快表

快表的引入,是为了加快逻辑地址(虚拟地址)到物理地址转换的速度。

在引入快表之前,由逻辑地址访问内存的过程如下:

  1. 根据逻辑地址的高位拿到页号
  2. 根据页号访问内存中的页表,根据页表的映射拿到实际的内存块号(一次访问)
  3. 把内存块号和逻辑地址的低位拼接,得到物理地址
  4. 访问物理地址对应的内存(二次访问)

这个过程是需要两次直接访问内存的,所以为了加快这个访问速度,引入了快表。

快表可以认为是一个高速缓冲存储器(Cache),内容是页表的一部分或者全部内容,和页表的功能是一样的,只不过比在内存中的页表访问速度要快很多。

由于采用页表做地址转换,读写内存数据时 CPU 要访问两次主存。有了快表,有时只要访问一次高速缓冲存储器,一次主存,这样可加速查找并提高指令执行速度。

4.3.3.2 快表的访问流程

被访问的内存块很可能在短时间内再次被访问,可能程序在一段时间内会多次访问同一个页表项。

所以在每次访问页表项时,先在快表里查询是否有该页表项,如果没有再去页表中查询,并把查询的页表项存入快表。

如果快表满了,就根据一些策略把其中的一些页表项淘汰掉,再把新查询的页表加进去。

总结:使用快表之后的地址转换流程如下:

  1. 根据虚拟地址中的页号查快表;
  2. 如果该页在快表中,直接从快表中读取相应的物理地址;
  3. 如果该页不在快表中,就访问内存中的页表,再从页表中得到物理地址,同时将页表中的该映射表项添加到快表中;
  4. 当快表填满后,又要登记新页时,就按照一定的淘汰策略淘汰掉快表中的一个页。

4.3.4 多级页表

4.3.4.1 为什么引入多级页表

多级页表主要是为了避免把全部页表一直放在内存中占用过多空间的问题,特别是那些根本就不需要的页表就不需要保留在内存中。属于时间换空间。

单级页表存在的问题:

  1. 页表必须连续存放,因此当页表很大时,需要占用很多个连续的页框。——多级页表解决
  2. 整个单级页表都会常驻内存,这是没有必要的,因为进程在一段时间内可能只要访问某几个特定的页面。——虚拟存储技术解决

两级页表,将全部的页表项分组,使得每组的页表项刚好能够装入一个内存块,让各组页表项(二级页表)可以离散地分配在各个内存块中,同时为了记录二级页表的顺序,通过一个“页目录表”来映射二级页表的页号和二级页表存放的内存块号。

两级页表的引入,使得虚拟地址的结构也要发生相应的变化:一级页号+二级页号+页内偏移量

两级页表的地址转换:

注意,采用多级页表机制时,各级页表的大小不能超过一个页面。

4.4 分段管理

4.4.1 段表

4.4.2 地址转换

段长大小是不固定的,但是段表项长度是固定的,不要混淆。

4.4.3 分页和分段的对比

  1. 单位
    • 页是信息的物理单位
    • 段是信息的逻辑单位
  2. 目的
    • 分页的目的是为了实现离散匹配,提高内存利用率。
    • 分段的目的是更好地满足用户的需求。
  3. 层面
    • 分页仅仅是系统管理的需要,完全是系统行为,对用户是不可见的。
    • 一个段通常包含着一组属于一个逻辑模块的信息,分段对用户是可见的,用户编程时需要显式地给出段名。
  4. 大小
    • 页的大小是固定的,且由系统决定。页表由页号(可隐含)和页框号组成
    • 段的长度不固定,由用户编写的程序大小决定。段表由段号(可隐含)、段长、基址组成。
  5. 地址转换过程
    • 分段管理相比于分页管理,需要进行两次越界检查:
      • 段号与段表寄存器中的段表长度比较,检查是否越界
      • 根据段表中记录的段长,检查段内地址是否越界
  6. 地址空间
    • 分页的用户进程地址空间是一维的,程序员只需要给出一个记忆符指出页号即可表示一个地址。
    • 分段的用户进程地址空间是二维的,程序员在标识一个地址时,既要给出段名,也要给出段内地址。

  1. 分段比分页更容易实现信息的共享和保护(不可更改的代码可以共享)
  2. 内存碎片
    • 分页管理内存空间利用率高,不会产生外部碎片,只会有少量的页内碎片
    • 分段管理会产生外部碎片。如果采用“紧凑”的方式来解决外部碎片,会付出较大的时间代价。

4.5 段页式管理

4.5.1 逻辑地址结构

4.5.2 段表与页表

段页式管理中,每个段对应一个段表项,每个段表项由段号、页表长度、页表存放块号(页表起始地址)组成。每个段表项长度相等,段号是隐含的。

分段管理中,每个段表项由段号、段长、基址组成

段页式管理中以及分页管理中,每个页面对应一个页表项,每个页表项由页号、页面存放的内存块号组成。

Note:段页式管理中的段表项结构与分段管理中的段表项结构不同,但段页式管理中的页表项结构与分页管理中的页表项结构相同。

一个进程对应一个段表,但是一个进程有可能对应多个页表。

4.5.3 地址转换

4.6 由逻辑地址访问内存的次数

  1. 分页

    1. 单级页表

      • 第一次访问内存——查内存中的页表
      • 第二次访问内存——访问目标内存单元
    2. 多级页表(n级)

      • 逐级访问页表(n次)
      • 第n+1次,访问
  2. 分段

    • 第一次访问内存——查内存中的段表
    • 第二次访问内存——访问目标内存单元
  3. 段页式

    • 第一次访问内存——查内存中的段表
    • 第二次访问内存——查页表
    • 第三次访问内存——访问目标内存单元
  4. 快表

    • 分页管理、分段管理中,都可以引入快表,将近期访问过的页表项/段表项放到快表中。若快表命中,则仅需一次访问内存。
    • 段页式管理中,也可以引入快表,将近期访问过的段号和页号作为查询快表的关键字。若快表命中,则仅需一次访问内存。

4.7 虚拟内存

4.7.1 传统存储管理方式的特征及缺点

4.7.2 局部性原理

4.7.3 什么是虚拟内存

4.7.4 虚拟内存的特征

4.7.5 如何实现虚拟内存

实现虚拟内存,操作系统需要在传统的非连续分配存储管理方式的基础上,再增加两个主要功能:

  • 请求调页/请求调段——解决访问的信息不在内存时的问题
  • 页面置换/分段置换——解决内存空间不够的问题

实现虚拟内存的三种主要方式为:

  1. 请求分页存储管理

    • 请求分页存储管理建立在传统分页管理之上,为了支持虚拟内存而增加了请求调页功能和页面置换功能。
    • 请求分页是目前最常用的一种实现虚拟内存的方法。
    • 在作业开始运行时,仅装入当前要执行的部分页面即可运行;
    • 假如在作业运行的过程中发现要访问的页面不在内存,则由处理器通知操作系统将相应的页面调入到内存。
    • 如果发现在把页面调入内存时,内存已满,操作系统也可以将暂时不用的页面置换到外存中。
  2. 请求分段存储管理

    原理和请求分页存储管理相似,不过是把页面换成段

  3. 请求段页式存储管理

不管是上面哪种实现方式,一般都需要:

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

4.8 页面置换算法

请求分页存储管理中的页面置换算法。

页面的换入、换出需要磁盘IO,会有较大的开销,因此好的页面置换算法应该追求更少的缺页率

4.8.1 最佳置换算法(OPT)

最佳置换算法,每次选择淘汰的页面将是以后永不使用或者在最长时间内不再被访问的页面,这样可以保证最低的缺页率。

实际上,只有在进程执行的过程中才能知道接下来会访问到的是哪个页面,无法提前预判页面访问序列。因此,最佳页面置换算法是无法实现的。

4.8.2 先进先出置换算法(FIFO)

先进先出置换算法,每次选择淘汰的页面是最早进入内存的页面。

实现算法:把调入的页面根据调入的先后顺序排成一个队列,需要换出页面时选择队头页面换出即可。

队列的最大长度限制取决于系统为进程分配了多少个内存块。

4.8.3 最近最久未使用置换算法(LRU)

最近最久未使用置换算法,每次淘汰的页面是最近最久未使用的页面。

实现方法:LRU赋予每个页面一个访问字段,用访问字段记录该页面自上次被访问以来所经历的时间T。当需要淘汰一个页面时,选择现有页面中T值最大的,即最近最久未使用的页面。

4.8.4 最少使用置换算法(LFU)

最少使用页面置换算法(LFU,Least Frequently Used),选择在之前时期使用最少的页面作为淘汰页。

4.8.5 时钟置换算法(CLOCK,NRU)

时钟置换算法,又称最近未用算法(NRU,Not Recently Used),是一种性能和开销较均衡的算法。

CLOCK算法的实现方法:为每个页面设置一个访问位,再将内存中的页面都通过链接指针链接成一个循环队列。当某页被访问时,其访问位 置1。当需要淘汰一个页面时,只需检查页的访问位。如果是0,则选择该页换出;如果是1,则将其置为0,暂不换出,继续检查下一个页面。如果第一轮扫描中所有页面都是1,则将这些页面的访问位依次置0后,再进行第二轮扫描(第二轮扫描中一定会有访问位为0的页面),因此简单的CLOCK算法选择淘汰一个页面最多会经过两轮扫描

4.8.6 改进型的时钟置换算法

简单的时钟置换算法,仅考虑到一个页面最近是否被访问过。事实上,如果被淘汰的页面没有被修改过,就不需要执行I/O操作写回外存

只有被淘汰的页面被修改过时,才需要写回外存。

因此,改进型的时钟置换算法,除了考虑一个页面最近是否被访问过之外,还应考虑该页面是否被修改过。在其他条件都相同时,应优先淘汰没有被修改过的页面,避免I/O操作。

  • 访问位:访问位=0表示最近未被访问过,访问位=1表示最近被访问过。
  • 修改位:修改位=0表示页面没有被修改过,修改位=1表示页面被修改过。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

OpenCV4图像处理-图像交互式分割-GrabCut

本文将实现一个与人(鼠标)交互从而分割背景的程序。 GrabCut 1.理论介绍2. 鼠标交互3. GrabCut 1.理论介绍 用户指定前景的大体区域,剩下为背景区域,还可以明确指出某些地方为前景或者背景,GrabCut算法采用分段迭代的…

2.多线程-初阶(中)

文章目录 4. 多线程带来的的风险-线程安全 (重点)4.1 观察线程不安全4.2 线程安全的概念4.3 线程不安全的原因4.3.1原子性4.3.2可见性4.3.3代码顺序性 4.4 解决之前的线程不安全问题 5. synchronized[ˈsɪŋkrənaɪzd] 关键字-监视器锁monitor lock5.1 synchronized 的特性5.…

数据分享|R语言逻辑回归、Naive Bayes贝叶斯、决策树、随机森林算法预测心脏病...

全文链接:http://tecdat.cn/?p23061 这个数据集(查看文末了解数据免费获取方式)可以追溯到1988年,由四个数据库组成。克利夫兰、匈牙利、瑞士和长滩。"目标 "字段是指病人是否有心脏病。它的数值为整数,0无…

ElasticSearch学习--数据聚合

介绍 数据聚合可以帮助我们对海量的数据进行统计分析,如果结合kibana,我们还能形成可视化的图形报表。自动补全可以根据用户输入的部分关键字去自动补全和提示。数据同步可以帮助我们解决es和mysql的数据一致性问题。集群可以帮助我们了解结构和不同节点…

Flask 文件上传,删除上传的文件

目录结构 app.py from flask import Flask, request, render_template, redirect, url_for import osapp Flask(__name__) BASE_DIR os.getcwd() UPLOAD_FOLDER os.path.join(BASE_DIR, testfile)app.route(/) def home():files os.listdir(UPLOAD_FOLDER)return render_t…

回归预测 | MATLAB实现TCN-BiGRU时间卷积双向门控循环单元多输入单输出回归预测

回归预测 | MATLAB实现TCN-BiGRU时间卷积双向门控循环单元多输入单输出回归预测 目录 回归预测 | MATLAB实现TCN-BiGRU时间卷积双向门控循环单元多输入单输出回归预测预测效果基本介绍模型描述程序设计参考资料 预测效果 ![6 基本介绍 1.MATLAB实现TCN-BiGRU时间卷积双向门控循…

解决JMeter+Grafana+influxdb 配置出现transaction无数据情形

问题描述 JMeterGrafanainfluxdb 配置时,Darren洋发现jmeter中明明已经配置好了事务条件以及接口实例信息,但就是在grafana的头部导航栏中的transaction按钮下来没有相应事务数据信息,经过相关资料查询,Darren洋发现执行以下两个步…

前端,测试,后端,该如何选择?

前端开发,测试,后端,该如何选择?说实话,只要对互联网行业有了解的,都会推荐你学测试。 首先必须声明,能在前端开发、测试、后端(主要是Java)这三个岗位中进行选择&#…

yum镜像源更新很慢,不管是阿里源还是清华源

今天想要再Centos7上安装docker测试,但是发现不管是阿里源还是清华源 yum makecache都更新的特别慢。有大佬知道啥原因不? 坐标成都,联通宽带300M

zabbix安装Grafana

一、web访问 https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-4.6.1-1.x86_64.rpm [rootserver ~] yum localinstall -y grafana-4.6.1-1.x86_64.rpm //yum方式安装本地rpm并自动解决依赖关系 [rootserver ~] grafana-cli plugins install alexanderzob…

分布式 RPC 框架HSF

分布式 RPC 框架HSF 概述HSF架构调用方式优势应用场景 概述 HSF (High-speed Service Framework),高速服务框架,是在阿里巴巴内部广泛使用的分布式 RPC 服务框架。HSF 作为阿里巴巴的基础中间件,联通不同的业务系统,解耦系统间的…

Network Dissection 论文阅读笔记

Network Dissection 论文阅读笔记 1. 简介2. 网络刨析2.1 深度视觉表征的可解释性的测量步骤2.2 数据集2.3 可解释神经元评分 3. 实验3.1 对解释的人类评价3.2 Measurement of Axis-Aligned Interpretability3.3 理解层概念3.4 网络架构和监督3.5 训练条件 vs 可解释性3.6 网络…

Windows上配置Python环境变量

Python配置环境变量 🍁博主简介 🏅云计算领域优质创作者   🏅华为云开发者社区专家博主   🏅阿里云开发者社区专家博主 💊交流社区:运维交流社区 欢迎大家的加入! Python下载官网&#xff1…

新星计划打卡学习:VUE3组合式API

目录 1、vue3组件页面的构成 2、setup选项 3、reactive 4、ref 最后 1、vue3组件页面的构成 从上到下依次是 逻辑、结构、样式 2、setup选项 经过语法糖的封装更简单的使用组合式api <script setup> // 经过语法糖的封装更简单的使用组合式api const message t…

kubesphere安装中间件

kubesphere安装mysql 创建configMap [client] default-character-setutf8mb4[mysql] default-character-setutf8mb4[mysqld] init_connectSET collation_connection utf8mb4_unicode_ci init_connectSET NAMES utf8mb4 character-set-serverutf8mb4 collation-serverutf8mb4_…

Django笔记之in查询及date日期相关过滤操作

这一篇介绍关于范围&#xff0c;日期的筛选 inrangedateyearweekweekdayquarterhour 1、in in 对应于 MySQL 中的 in 操作&#xff0c;可以接受数组、元组等类型数据作为参数&#xff1a; Blog.objects.filter(id__in[1,2,3])对应的 SQL 是&#xff1a; select * from blo…

字符函数和字符串函数下篇(详解)

❤️ 作者简介 &#xff1a;RO-BERRY 致力于C、C、数据结构、TCP/IP、数据库等等一系列知识&#xff0c;对纯音乐有独特的喜爱 &#x1f4d7; 日后方向 : 偏向于CPP开发以及大数据方向&#xff0c;如果你也感兴趣的话欢迎关注博主&#xff0c;期待更新 字符函数和字符串函数2 1…

el-popover在原生table中,弹出多个以及内部取消按钮无效问题

问题&#xff1a;当el-popover和原生table同时使用的时候会失效&#xff08;不是el-table) <el-popover placement"bottom" width"500" trigger"click" :key"popover-${item.id}"></el-popover> 解决&#xff1a; :key…

C程序环境及预处理

​​​​​文章目录 一、程序的翻译环境和执行环境 1.程序编译过程 2.编译内部原理 3.执行环境 二、程序运行前的预处理 1.预定义符号归纳 2.define定义标识符 3.define定义宏 4.define替换规则 5.宏和函数的对比 三、头文件被包含的方式 四、练习&#xff1a;写一…

【python工具】html中表格转化为excel

背景 大家在实际的工作中可能会遇到这样的场景,查看某个统计的页面数据,其中一些数据是表格形式展示的,比如这是国家统计局关于人口统计的数据: 你想将表格内容下载下来根据自己的需要进行二次加工,但是页面没有提供下载功能或者需要你登陆才能下载。那么重点来了~~ 操…