虚拟内存(深入理解计算机系统原书第3版9节)

news2024/9/22 3:49:37

深入理解计算机系统(原书第3版)读书笔记,其实就是嚼碎了原文然后把一部分挑了出来摘要,免得读着读着忘了

文章目录

  • 前言
  • 一、物理和虚拟寻址
  • 二、地址空间
  • 三、虚拟内存作为缓存的工具
    • 1、DRAM缓存的组织结构
    • 2、页表
    • 3、页命中
    • 4、缺页
    • 5、分配页面
    • 6、局部性
  • 四、虚拟内存作为内存管理的工具
  • 五、虚拟内存作为内存保护的工具
  • 六、地址翻译
    • 1、结合高速缓存和虚拟内存
    • 2、利用TLB加速地址翻译
    • 3、多级页表
    • 4、端到端的地址翻译
  • 七、 案例研究:Intel Core i7/Linux 内存系统
    • 1、Core i7 地址翻译
    • 2、Linux 虚拟内存系统
  • 八、内存映射
    • 1、再看共享对象
    • 2、再看fork函数
    • 3、再看execve函数
    • 4、使用mmap函数的用户级内存映射
  • 九、动态内存
  • 总结


前言

  • 虚拟内存是硬件异常、硬件地址翻译、主存、磁盘文件和内核软件的完美交互,它为每个进程提供了一个大的、一致的和私有的地址空间。
  • 它将主存看成是一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,并根据需要在磁盘和主存之间来回传送数据,通过这种方式,它高效地使用了主存。
  • 虚拟内存遍及计算机系统的所有层面,在硬件异常、汇编器、链接器、加载器、共享对象、文件和进程的设计中扮演着重要角色。理解虚拟内存将帮助你更好地理解系统通常是如何工作的。

这一章从两个角度来看虚拟内存。本章的前一部分描述虚拟内存是如何工作的后一部分描述的是应用程序如何使用和管理虚拟内存。好消息就是如果你掌握这些细节,你就能够手工模拟一个小系统的虚拟内存机制,而且虚拟内存的概念将永远不再神秘。第二部分是建立在这种理解之上的,向你展示了如何在程序中使用和管理虚拟内存。你将学会如何通过显式的内存映射和对象 malloc 程序这样的动态内存分配器的调用来管理虚拟内存。你还将了解到 C 程序中的大多数常见的与内存有关的错误,并学会如何避免它们的出现。(饼画的我很喜欢)


一、物理和虚拟寻址

计算机系统的主存被组织成一个由 M 个连续的字节大小的单元组成的数组。都有一个唯一的物理地址(Physical Address)。
在这里插入图片描述
将一个虚拟地址转换为物理地址的任务叫做地址翻译(address translation)。CPU 芯片上叫做内存管理单元(Memory Management Unit,MMU)的专用硬件,利用存放在主存中的查询表来动态翻译虚拟地址,该表的内容由操作系统管理。

二、地址空间

  • 地址空间(address space)是一个非负整数地址的有序集合。
  • 如果地址空间中的整数是连续的,那么我们说它是一个线性地址空间(linear address space)。
  • 一个地址空间的大小是由表示最大地址所需要的位数来描述的。例如,一个包含 N = 2 n N=2^n N=2n个地址的虚拟地址空间就叫做一个n位地址空间(二进制需要n个位数才能表示完)。现代系统通常支持32位或者64位虚拟地址空间。
  • 一个系统还有物理地址空间(physical address space),对应于系统中物理内存的M个字节(M不是求2的幂)。

地址空间清楚地区分了数据对象(字节)和它们的属性(地址)。 一旦认识到了这种区别,那么我们就可以将其推广,允许每个数据对象有多个独立的地址,其中每个地址都选自一个不同的地址空间。这就是虚拟内存的基本思想主存中的每字节都有一个选自虚拟地址空间的虚拟地址和一个选自物理地址空间的物理地址。(咋从一对多变成一对一了?因为数据对象包含很多个字节是吧)

三、虚拟内存作为缓存的工具

虚拟内存被组织为一个由存放在磁盘上的M个连续的字节大小的单元组成的数组每字节都有一个唯一的虚拟地址,作为到数组的索引。磁盘上数组的内容被缓存在主存中。(应该说的是磁盘上这个虚拟内存的数组的内容被缓存在主存中)

和存储器层次结构中其他缓存一样,磁盘(较低层)上的数据被分割成块,这些块作为磁盘和主存(较高层)之间的传输单元。VM系统通过将虚拟内存分割为称为虚拟页(Virtual Page, VP)的大小固定的块来处理这个问题。每个虚拟页的大小为字节。类似地,物理内存被分割为物理页(Physical Page, PP)大小也为P字节(物理页也被称为页帧(page frame))。
在这里插入图片描述

1、DRAM缓存的组织结构

直写式(WT,Write Through)与回写式(WB,Write Back)指的是缓冲内存的工作方式。直写式缓存方式是当CPU要将数据写入内存除了更新缓冲内存上的数据外也将数据写在 DRAM 中以维持主存与缓冲内存的一致性。当要写入内存的数据一多速度自然就慢了下来。回写式的缓存方式是每当 CPU 要将数据写入内存时只会先更新缓冲内存上的数据随后再让缓冲内存在总线不塞车的时候才把数据写回DRAM所以速度自然快得多。(高速缓存将充当缓冲区)

2、页表

虚拟内存系统必须有某种方法来判定一个虚拟页是否缓存在DRAM中的某个地方。如果是,系统还必须确定这个虚拟页存放在哪个物理页中。如果不命中,系统必须判断这个虚拟页存放在磁盘的哪个位置,在物理内存中选择一个牺牲页,并将虚拟页从磁盘复制到DRAM中,替换这个牺牲页

由软硬件联合提供的,包括操作系统软件、MMU(内存管理单元)中的地址翻译硬件和一个存放在物理内存中叫做页表(page table)的数据结构,页表将虚拟页映射到物理页。每次地址翻译硬件将一个虚拟地址转换为物理地址时,都会读取页表。操作系统负责维护页表的内容,以及在磁盘与DRAM之间来回传送页。

页表就是一个页表条目(Page Table Entry, PTE)的数组。
在这里插入图片描述
图的解释:有效位表明了该虚拟页当前是否被缓存在DRAM中。如果设置了有效位,那么地址字段就表示DRAM中相应的物理页的起始置,这个物理页中缓存了该虚拟页。如果没有设置有效位,那么一个空地址表示这个虚拟页还未被分配。否则,这个地址就指向该虚拟页在磁盘上的起始位置。因为 DRAM 缓存是全相联的,所以任意物理页都可以包含任意虚拟页。

3、页命中

当CPU想要读包含在VP2中的虚拟内存的一个字时会发生什么?地址翻译硬件将虚拟地址作为一个索引来定位PTE2, 并从内存中读取它。因为设置了有效位,那么地址翻译硬件就知道VP2是缓存在内存中的了。所以它使用PTE中的物理内存地址(该地址指向 PP1中缓存页的起始位置), 构造出这个字的物理地址。
在这里插入图片描述

4、缺页

DRAM缓存不命中称为缺页(page fault)。
在这里插入图片描述

  1. CPU引用了VP3中的一个字,VP3并未缓存在DRAM中。地址翻译硬件从内存中读取PTE3, 从有效位推断出VP3未被缓存,并且触发一个缺页异常。
  2. 缺页异常调用内核中的缺页异常处理程序,该程序会选择一个牺牲页,在此例中就是存放在PP3中的VP4。如果VP4已经被修改了,那么内核就会将它复制回磁盘。无论哪种情况,内核都会修改VP4的页表条目,反映出VP4不再缓存在主存中这一事实。
  3. 内核从磁盘复制VP3到内存中的PP3, 更新PTE3,随后返回。
  4. 当异常处理程序返回时,它会重新启动导致缺页的指令,该指令会把导致缺页的虚拟地址重发送到地址翻译硬件。但是现在,VP3已经缓存在主存中了,那么页命中也能由地址翻译硬件正常处理了。图 9-7 展示了在缺页之后我们的示例页表的状态。
    在这里插入图片描述
    虚拟内存系统使用了和 SRAM 缓存不同的术语,即使它们的许多概念是相似的。在虚拟内存的习惯说法中,块被称为页。在磁盘和内存之间传送页的活动叫做交换(swapping)或者页面调度(paging)。页从磁盘换入(或者页面调入)DRAM和从DRAM 换出(或者页面调出)磁盘。一直等待,直到最后时刻,也就是当有不命中发生时,才换入页面的这种策略称为按需页面调度(demand paging)。也可以采用其他的方法,例如尝试着预测不命中,在页面实际被引用之前就换人页面。然而,所有现代系统都使用的是按需页面调度的方式

5、分配页面

分配一个新的虚拟内存页时对我们示例页表的影响,例如,调用 malloc 的结果。在这个示例中,VP5的分配过程是在磁盘上创建空间,并更新PTE5使它指向磁盘上这个新创建的页面。(page table 是在DRAM里的,这个很有意思,MMU有一个TLB,MMU在CPU里)
在这里插入图片描述

6、局部性

程序将趋向于在一个较小的活动页面(active page)集合上工作,这个集合叫做工作集(working set)或者常驻集合(resident set)。只要我们的程序有好的时间局部性,虚拟内存系统就能工作得相当好。但是,当然不是所有的程序都能展现良好的时间局部性。如果工作集的大小超出了物理内存的大小,那么程序将产生一种不幸的状态,叫做抖动(thrashing), 这时页面将不断地换进换出。虽然虚拟内存通常是有效的,但是如果一个程序性能慢得像爬一样,那么聪明的程序员会考虑是不是发生了抖动。

四、虚拟内存作为内存管理的工具

到目前为止,我们都假设有一个单独的页表,将一个虚拟地址空间映射到物理地址空间。实际上,操作系统为每个进程提供了一个独立的页表,因而也就是一个独立的虚拟地址空间。注意,多个虚拟页面可以映射到同一个共享物理页面上
在这里插入图片描述

  • 简化链接
  • 简化加载
  • 简化共享
  • 简化内存分配:当一个运行在用户进程中的程序要求额外的堆空间时(如调用 malloc 的结果), 操作系统分配一个适当数字(例如k个)连续的虚拟内存页面,并且将它们映射到物理内存中任意位置的k个物理页面。由于页表工作的方式,操作系统没有必要分配k个连续的物理内存页面。页面可以随机地分散在物理内存中。

五、虚拟内存作为内存保护的工具

在这里插入图片描述
在这个示例中,每个PTE中已经添加了三个许可位。SUP 位表示进程是否必须运行在内核(超级用户)模式下才能访问该页。运行在内核模式中的进程可以访问任何页面,但是运行在用户模式中的进程只允许访问那些SUP为0的页面。READ位和WRITE位控制对页面的读和写访问。例如,如果进程i运行在用户模式下,那么它有读VP0和读写VP1的权限。然而,不允许它访问 VP2。如果一条指令违反了这些许可条件,那么 CPU 就触发一个一般保护故障,将控制传递给一个内核中的异常处理程序。Linux shell —般将这种异常报告为“段错误(segmentation fault)” 。

六、地址翻译

了解硬件在支持虚拟内存中的角色,省略了大量的细节,尤其是和时序相关的细节。图 9-11 概括了我们在这节里将要使用的所有符号。
在这里插入图片描述
图9-12展示了MMU如何利用页表来实现虚拟地址空间到物理地址空间映射。CPU中的一个控制寄存器,页表基址寄存器(Page Table Base Register, PTBR) 指向当前页表。n位的虚拟地址包含两个部分:一个p位的虚拟页面偏移(Virtual Page Offset, VPO)和一个(n-p)位的虚拟页号(Virtual Page Number, VPN)。MMU利用VPN来选择适当的PTE。例如,VPN0选择PTE0,VPN1选择PTE1,以此类推。将页表条目中物理页号(Physical Page Number, PPN)和虚拟地址中的VPO串联起来,就得到相应的物理地址。 注意,因为物理和虚拟页面都是P字节的,所以物理页面偏移(Physical Page Offset, PPO)和VPO是相同的
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

(第7步其实就是a)的一次循环了)。

1、结合高速缓存和虚拟内存

既使用虚拟内存又使用SRAM髙速缓存的系统中,都有应该使用虚拟地址还是使用物理地址来访问SRAM高速缓存的问题。大多数系统是选择物理寻址的
在这里插入图片描述

2、利用TLB加速地址翻译

每次CPU产生一个虚拟地址,MMU就必须查阅一个PTE, 以便将虚拟地址翻译为物理地址。在MMU中包括了一个关于PTE的小的缓存,称为翻译后备缓冲器(Translation Lookaside Buffer, TLB)。TLB 是一个小的、虚拟寻址的缓存,其中每一行都保存着一个由单个 PTE 组成的块。TLB 通常有高度的相联度。当 TLB 命中时(通常情况)所有的地址翻译步骤都是在芯片上的 MMU 中执行的,因此非常快。
在这里插入图片描述

3、多级页表

这种方法从两个方面减少了内存要求。第一,如果一级页表中的一个 PTE 是空的,那么相应的二级页表就根本不会存在。这代表着一种巨大的潜在节约,因为对于一个典型的程序,4GB 的虚拟地址空间的大部分都会是未分配的。第二,只有一级页表才需要总是在主存中;虚拟内存系统可以在需要时创建、页面调入或调出二级页表,这就减少了主存的压力;只有最经常使用的二级页表才需要缓存在主存中。
在这里插入图片描述

4、端到端的地址翻译

一个具体的端到端的地址翻译示例。书P609/775

七、 案例研究:Intel Core i7/Linux 内存系统

书P612/775。

1、Core i7 地址翻译

当MMU翻译每一个虚拟地址时,它还会更新另外两个内核缺页处理程序会用到的位。每次访问一个页时,MMU都会设置A位,称为引用位(reference bit)。内核可以用这个引用位来实现它的页替换算法。每次对一个页进行了写之后,MMU都会设置D位,又称修改位或脏位(dirty bit)。修改位告诉内核在复制替换页之前是否必须写回牺牲页。内核可以通过调用一条特殊的内核模式指令来清除引用位或修改位。

2、Linux 虚拟内存系统

在这里插入图片描述
在这里插入图片描述
任务结构中的一个条目指向 mm_struct,它描述了虚拟内存的当前状态。我们感兴趣的两个字段是 pgd 和 mmap。其中pgd指向第一级页表(页全局目录)的基址,而 mmap 指向一个vm_area_structs(区域结构)的链表,其中每个vm_area_structs都描述了当前虚拟地址空间的一个区域。当内核运行这个进程时,就将pgd存放在CR3控制寄存器中。
为了我们的目的,一个具体区域的区域结构包含下面的字段:

  • vm_start: 指向这个区域的起始处。
  • vm_end: 指向这个区域的结束处。
  • vm_port:描述这个区域内包含的所有页的读写许可权限。
  • vm_flags:描述这个区域内的页面是与其他进程共享的,还是这个进程私有的(还描述了其他一些信息)。
  • vm_next: 指向链表中下一个区域结构。

八、内存映射

将一组连续的虚拟页映射到任意一个文件中的任意位置的表示法称作内存映射(memory mapping)Linux 提供一个称为 mmap的系统调用,允许应用程序自己做内存映射。

Linux通过将一个虚拟内存区域与一个磁盘上的对象(object)关联起来,以初始化这个虚拟内存区域的内容,这个过程称为内存映射(memory mapping)虚拟内存区域可以映射到两种类型的对象中的一种:
1) Linux文件系统中的普通文件:一个区域可以映射到一个普通磁盘文件的连续部分,例如一个可执行目标文件。文件区(section)被分成页大小的片,每一片包含一个虚拟页面的初始内容。因为按需进行页面调度,所以这些虚拟页面没有实际交换进入物理内存,直到 CPU 第一次引用到页面(即发射一个虚拟地址,落在地址空间这个页面的范围之内)。 如果区域比文件区要大,那么就用零来填充这个区域的余下部分(?)。
2) 匿名文件:一个区域也可以映射到一个匿名文件,匿名文件是由内核创建的,包含的全是二进制零。CPU第一次引用这样一个区域内的虚拟页面时,内核就在物理内存中找到一个合适的牺牲页面,如果该页面被修改过,就将这个页面换出来,用二进制零覆盖牺牲页面并更新页表,将这个页面标记为是驻留在内存中的。注意在磁盘和内存之间并没有实际的数据传送。因为这个原因,映射到匿名文件的区域中的页面有时也叫做请求二进制零的页(demand-zero page)

无论在哪种情况中,一个虚拟页面被初始化了,它就在一个由内核维护的专门的交换文件(swap file)之间换来换去。交换文件也叫做交换空间(swap space)或者交换区域(swap area)需要意识到的很重要的一点是,在任何时刻,交换空间都限制着当前运行着的进程能够分配的虚拟页面的总数

1、再看共享对象

一个对象可以被映射到虚拟内存的一个区域,要么作为共享对象,要么作为私有对象。

2、再看fork函数

3、再看execve函数

4、使用mmap函数的用户级内存映射

mmap函数要求内核创建一个新的虚拟内存区域,最好是从地址start开始的一个区域,并将文件描述符fd指定的对象的一个连续的片(chunk)映射到这个新的区域。连续的对象片大小为length字节,从距文件开始处偏移量为offset字节的地方开始。start地址仅仅是一个暗示,通常被定义为 NULL。为了我们的目的,我们总是假设起始地址为NULL。
在这里插入图片描述

九、动态内存


总结

提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

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

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

相关文章

[附源码]Python计算机毕业设计SSM基于的小区物业管理系统(程序+LW)

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: SSM mybatis Maven Vue 等等组成,B/S模式 M…

微服务框架 SpringCloud微服务架构 微服务保护 33 授权规则 33.1 授权规则

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式,系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 微服务保护 文章目录微服务框架微服务保护33 授权规则33.1 授权规则33.1.1 授权规则33 授权规则 33.1 授权规则 33.1.1 授权规则 看看sen…

康鹏科技将于12月16日上会:曾在纽交所上市,由杨建华家族控股

近日,上海康鹏科技股份有限公司(下称“康鹏科技”)在上海证券交易所递交招股书(上会稿),准备在科创板上市。据贝多财经了解,康鹏科技将于2022年12月16日接受科创板上市委的现场审议。 根据公开信…

基于C++实现(MFC)职工工作量统计系统【100010022】

【职工工作量统计系统设计】 1、问题描述 职工包括姓名、职工号、性别、年龄、所在部门、联系方式等信息。 工作量包括职工号、完成的产品数量等信息。 该设计系统能够对职工的工作量进行统计,并排出名次。注意,一个职工的工作量是可以多次输入的。 2…

Alibaba官方上线,Java并发编程全彩图册(终极版)GitHub已置顶

都说程序员工资高、待遇好, 2022 金九银十到了,你的小目标是 30K、40K,还是 16薪的 20K?作为一名 Java 开发工程师,当能力可以满足公司业务需求时,拿到超预期的 Offer 并不算难。然而,提升 Java…

Python图像识别实战(一):实现按数量随机抽取图像复制到另一文件夹(附源码和实现效果)

前面我介绍了可视化的一些方法以及机器学习在预测方面的应用,分为分类问题(预测值是离散型)和回归问题(预测值是连续型)(具体见之前的文章)。 从本期开始,我将做一个关于图像识别的…

Qt实现全局鼠标事件监听器-Linux

Qt实现全局鼠标事件监听器-Linux版🦑 var code “bc8d4eb4-a9df-48e9-8028-bbe1ae7fbd05” 文章目录Qt实现全局鼠标事件监听器-Linux版🦑1、概述🦞2、实现效果🍰3、实现方式🦀4、关键代码🍦5、源代码&…

SpringMVC基础篇:第一个MVC程序和细节分析

文章整理自孙哥说SpringMVC,相关课程联系孙哥学习谢谢。第一章:编码开发一:思路分析二:SpringMVC程序编码三:控制器提供多个服务方法四:注意事项第二章:细节分析一:控制器创建次数二…

C++ 【set、map模拟实现】

目录 set概念 set基本使用 map概念 map的使用 map统计次数 operator[] operator[]底层如何实现? set和map迭代器封装 红黑树迭代器基本结构 operator operator-- operator[] 源代码链接 map、set底层都使用平衡搜索树(即红黑树),容器中的元素…

HanLP 基于朴素贝叶斯 训练 文本分类

一、HanLP 朴素贝叶斯分类器 HanLP 针对文本分类算法已经帮我们实现 朴素贝叶斯法 ,用户可以无需关心内部细节,HanLP 也提供了相关自定义训练接口,前提需要将数据集根据分类放到不同的目录中,例如: 官方给出了相关性能…

HanLP 基于SVM支持向量机 训练 文本分类

一、HanLP 基于SVM支持向量机分类器 上篇文章通过朴素贝叶斯文本分类器,训练测试了 搜狗文本分类语料库迷你版 ,本篇继续测试SVM支持向量机分类器。 由于HanLP 官方给出的 SVM 分类器依赖了第三方库,没有集成在主项目中,需要拉取…

问题解决(1)——VS中scanf报错怎么解决

目录 方法一: 方法二: 方法三: 各位好,博主新建了个公众号《自学编程村》,拉到底部即可看到,有情趣可以关注看看哈哈,关注后还可以加博主wx呦~~~(公众号拉到底部就能看到呦~~&am…

Redis【13】-修改数据库后,如何保证Redis与数据库的数据一致性

一、需求起因 在高并发的业务场景下,数据库大多数情况都是用户并发访问最薄弱的环节。所以,就需要使用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问MySQL等数据库。 这个业务场景,主要是解决读数据从Redi…

ARM 代码重定位实战

前言 任务 在 SRAM 中将代码从 0xd0020010 重定位到 0xd0024000。任务解释:本来代码是运行在0xd0020010的,但是因为一些原因我们又希望代码实际是在0xd0024000位置运行 的。这时候就需要重定位了。注解:本练习对代码本身运行无实际意义&…

你都工作两年半了,还不会RabbitMQ?

What is rabbitMQ ? RabbitMQ 是一个由 Erlang 语言开发的 AMQP(高级消息队列协议) 的开源实现。 RabbitMQ 是轻量级且易于部署的,能支持多种消息协议。 RabbitMQ 可以部署在分布式和联合配置中,以满足高规模、高可用性的需求。 具体特点包括…

ADI Blackfin DSP处理器-BF533的开发详解29:TOUCH_LINE(屏幕画线)(含源代码)

硬件准备 ADSP-EDU-BF533:BF533开发板 AD-HP530ICE:ADI DSP仿真器 软件准备 Visual DSP软件 硬件链接 硬件设计原理图 功能介绍 代码实现了读取触摸屏坐标,并将触摸屏坐标换算为液晶屏的显示坐标,将像素点显示到触摸坐标的位…

学习Python中turtle模块的基本用法(4:绘制科赫曲线和谢尔宾斯基三角形)

科赫曲线和谢尔宾斯基三角形是常见的分形图形(详细介绍见参考文献1),本文使用turtle库绘制这两类图形。 科赫曲线 科赫曲线的详细介绍见参考文献2,其中的绘图思路是“画正三角形,并把每一边三等分,取三等分后的一边中…

【LeetCode】Day194-超级丑数

题目 313. 超级丑数【中等】 题解 之前做过丑数,规定丑数是质因数只包含2,3,5的正整数,而这道题丑数升级为超级丑数,规定为包含的质因数是在primes数组中的正整数 丑数的题解用动态规划,那么超级丑数也可以利用相同的方法解答…

CSS -- CSS元素显示模式总结(块元素,行内元素,行内块元素)

文章目录CSS 的元素显示模式1 什么是元素显示模式2 块元素3 行内元素4 行内块元素5 元素的显示模式总结CSS 的元素显示模式 1 什么是元素显示模式 作用:网页的标签非常多,在不同地方会用到不同类型的标签,了解他们的特点可以更好的布局我们…

[附源码]JAVA毕业设计-学生宿舍故障报修管理信息系统-(系统+LW)

[附源码]JAVA毕业设计-学生宿舍故障报修管理信息系统-(系统LW) 项目运行 环境项配置: Jdk1.8 Tomcat8.5 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff…