《LKD3粗读笔记》(12)内存管理

news2024/11/30 12:48:07

1、页

  • 内核把物理页作为内存管理的基本单元
  • 内存管理单元(MMU)以为单位来管理系统中的页表
  • 从虚拟内存的角度看,就是最小单位
  • 体系结构不同,支持页的大小也不尽相同。大多数32位体系结构支持4KB的页,而64位体系结构一般会支持8KB的页。
  • 内核用struct page结构表示系统中的每个物理页,该结构位于<linux/mm.h>中。下面是简化后的结构体:
    struct page {
    	unsigned long			flags;//存放页的状态,每一位单独表示一种状态,至少可以同时表示出32种状态
    	atomic_t				_count ;//存放这一页的引用次数,当页空闲时,就可以在新的分配中使用它
    	atomic_t				_mapcount;
    	unsigned long			private;//作为私有数据使用
    	struct address_space	*mapping;//一个页可以由页缓存使用,这时,mapping域指向和这个页关联的address_sapce对象。
    	pgoff_t					index;
    	struct list_head		lru;
    	void					*virtual;//页的虚拟地址,通常情况下他就是页在虚拟内存种的地址。
    };
    
  • page结构物理页相关,而并非与虚拟页相关,内核仅仅用这个数据结构来描述当前时刻在相关的物理页中存放的东西,系统中的每个物理页都要分配一个这样的结构。

2、区

  • 由于硬件的限制,内核并不能对所有的页一视同仁,内核把页划分为不同的区。Linux使用了三种区:
    1. ZONE_DMA:这个区包含的页能用来执行DMA操作
    2. ZONE_NOEMAL:这个区包含的都是能正常映射的页
    3. ZONE_HIGHMEM:这个区包含高端内存,其中的页不能永久映射到内核地址空间。
  • 这些区定义位于linux/mmzone.h。区的实际使用和分布是与体系结构相关的。在x86上的区分布如下表:
    在这里插入图片描述
  • Linux通过区的划分形成不同的内存池,这样就可以根据用途分配,注意这样的划分是逻辑意义上的划分;
  • 某些特定的分配需要特定的区域,但某些一般用途的分配可以从两个不同的类型的任意一种分配区域(注意不能同时分配两种区的页,即存在区限界)。比如:一般用途的内存在可ZONE_NOEMAL不够用的情况下会访问ZONE_DMA
  • 每个区都用struct zone表示,定义在linux/mmzone.h
    struct zone {
    	unsigned long			watermark [NR_WMARK] ;
    	unsigned long			lowmem_reserve [MAX_NR__ZONES] ;
    	struct per_cpu_pageset	pageset [NR_CPUS] ;
    	spinlock_t				lock;
    	struct free_area		free_area [MAX_ORDER]
    	spinlock_t				lru_lock ;
    	struct zone_lru {
    		struct list_head list;
    		unsigned longnr_saved_scan;
    	} lru [NR_LRU_LISTS] ;
    	struct zone_reclaim_stat reclain_stat;
    	unsigned long			pages_scanned;
    	unsigned long			flags;
    	atomic_long_t			vmn_stat [NR_VM_ZONE_STAT_ITEMS];
    	int						prev_priority;
    	unsigned				int inactive_ratio;
    	wait_queue_head_t		*wait_table;
    	unsigned long			wait_table_hash_nr_entries;
    	unsigned long			wait__table_bits;
    	struct pglist_data		*zone_pgdat;
    	unsigned long			zone_start _pfn;
    	unsigned long			spanned pages;
    	unsigned long			present_pages;
    	const char				*name;
    };
    
  • 区的结构表示
    • lock:表示自旋锁,方式该结构被并发访问,但这个锁保护不了这个区的所有页,只单单保护这个结构
    • watermark:该数组有本区的最小值、最低和最高水位值,水位被内核设置每个区的内存消耗基准,随空间大小的改变而改变
    • name:表示该区的名字,分别为:DMANormalHighMem

3、获得页

  • 分配2的order次方个页,返回第一页的页结构指针。
    struct page *alloc_pages(unsigned int gfp_mask, unsigned int order);
    
    使用下面的函数把给定的页转换为它的逻辑地址:
    void *page_address(struct page *page);
    
  • 分配2的order次方个页,直接返回指向其逻辑地址的指针(注意与前者之间的区别):
    unsigned long __get_free_pages(unsigned int gfp_make,unsigned int order);
    
  • 分配1页,返回页结构指针:
    struct page *alloc_page(gfp_t gfp_mask);
    
  • 分配1页,返回它的逻辑地址:
    unsigned long __get_free_page(gfp_t gfp_mask);
    
  • 获得填充为0的页:
    unsigned long get_zeroed_page(unsigned int gfp_mask)
    
    采取这种方式的理由很简单,避免将敏感信息传递给客户。
  • 释放页
    void __free_pages(struct page *page,unsigend int order);
    void free_pages(unsigned long addr,unsigend int order);
    void free_page(unsigned long addr);
    
    注意:释放页的时候需要谨慎,只能释放属于自己的页。传递错误的参数可能会导致系统崩溃。

4、kmalloc()

  • kmalloc()函数与用户空间的malloc()一族函数非常类似,只不过它多了一个flags参数。kmalloc函数是一个简单的接口,用它可以获得以字节为单位的一块内核内存。注意:第3节重点讲述的是以为单位的分配。
  • kmalloc()linux/slab.h中声明:
    void *kmalloc(size_t size, gfp_t flags);//size:字节数 flags:分配标志
    
    这个函数返回一个指向内存块的指针,其内存块至少要有size大小,所分配的内存区在物理上是连续的(第5节介绍的vmalloc就是非连续的),在出错时,它返回NULL
  1. gfp_mask标志
    不管在低级页分配函数中,还是在kmalloc()中,都用到了分配器标志。这些标志可分为三类:行为修饰符区修饰符类型。行为修饰符表示内核应当如何分配所需的内存,例如,中断处理程序就要求内核在分配内存的过程中不能睡眠。区修饰符表示从哪儿分配内存。类型标志组合了行为修饰符和区修饰符,将各种可能用到的组合归纳为不同类型。所有这些标志都是在linux/gfp.h中申明的。

    • 行为修饰符
      请添加图片描述
      请添加图片描述
    • 区修饰符

    请添加图片描述

    • 类型标志
      请添加图片描述每种类型标志后隐含的修饰符列表:
      请添加图片描述
    • 标志的使用情景
      请添加图片描述
  2. kfree()
    kfree释放由kmalloc分配出来的内存块。如果想要释放的内存不是由kmalloc()分配的,或者想要释放的内存早就被释放完了,调用这个函数会导致严重的后果。

5、vmalloc()

  • vmalloc()是用户空间分配函数的工作方式
  • vmalloc()kmalloc()很像,他们的区别是:
    • vmalloc()确保虚拟地址空间内连续,分配非连续的物理内存块
    • kmalloc()确保虚拟地址空间内连续,分配连续的物理内存块
  • 硬件设备根本不理解什么是虚拟地址。硬件设备需要得到连续的物理内存块,而软件就可以使用仅虚拟地址空间连续的内存块。
  • 出于性能考虑,很多内核代码都使用kmalloc()来获得内存,而非vmalloc()
  • vmalloc()函数通过分配非连续的物理内存块,再修正页表,把内存映射到逻辑地址空间的连续区域中
  • vmalloc()函数在linux/vmalloc.h中声明,在mm/vmalloc.c中定义。
    void *vmalloc(unsigned long size);
    
    该函数返回一个指针,指向逻辑上连续的一块内存区,其大小至少为size。在发生错误时,函数返回NULL。函数可能睡眠,因此,不能从中断上下文中进行调用,也不能从其他不允许阻塞的情况下使用。
  • 释放通过vmalloc()所获得的内存。
    void vfree(void *addr);
    
    这个函数会释放从addr开始的内存块,addr是由vmalloc分配的内存块地址。这个函数也可以睡眠,因此,不能从中断上下文中调用,它没有返回值。

6、slab层

  • 背景
    空闲链表在很多的地方都出现过,为了应对频繁的分配和回收,链表中存放预先设置好的数据结构,需要就抓取一个,不用分配内存,不需要就回收而不是释放;所以空闲链表相当于对象高速缓存——快速存储频繁使用的对象类型。
  • Linux中的空闲链表
    在内核中,空闲链表面临的挑战就是不能全局控制,当可用内存紧缺时,内核无法通知每个链表,让其收缩缓存的大小来补充一些内存出来,所以Linux内核提供slab层(slab分配器),扮演着通用数据结构缓存层的角色
  • slab需要满足的基本原则
    • 频繁分配和释放的数据结构应当缓存
    • 频繁分配和释放会造成内存碎片,空闲链表的缓存会连续存放
    • 回收的对象可以立即投入下一次分配,空闲链表提高性能
    • 分配器知道对象大小、页大小和缓存的大小可以做出更明确的选择
    • 如果部分缓存只属于某个单处理器则不需要加锁
    • 若分配器是基于非统一内存访问(NUMA:在NUMA下,处理器访问它自己的本地存储器的速度比非本地存储器(存储器的地方到另一个处理器之间共享的处理器或存储器)快一些),可以从相同的内存节点进行分配
    • 对存放在缓存中的对象进行标记,防止将多个对象映射到同一个缓存行中
  1. slab层的设计
    • 概述
      slab层把不同的对象划分为所谓高速缓存组,其中每个高速缓存组都存放不同类型的对象。每种对象类型对应一个高速缓存。例如,一个高速缓存用于存放进程描述符(task_struct结构的一个空闲链表),而另一个高速缓存存放索引节点对象(struct inode)。
    • slab
      每个缓存又被划分为多个slab,slab由一个或多个物理上连续的页组成(一般由一个页组成),每个slab都包含一些被缓存的数据结构等,每个slab处于三种状态之一:满、部分满或空,当内核分配时先从部分满开始寻找,如果没有则再去空的slab,如果没有创建空的则创建一个。下面是高速缓存、slab和对象之间的关系。
      在这里插入图片描述
    • slab的结构
      缓存由kmen_cache所表示,里面包含上面提到的三种链表:满、部分满和空,链表包含缓存中的所有slab
      struct slab {
      struct list_head list;		/*满、部分满或空链表*/
      unsigned long	co1ouroff ;	/*slab着色的偏移量*/
      void	s_mem;			/*在slab中的第一个对象*/
      unsigned int	inuse;	/*slab中已分配的对象数*/
      kmem_bufctl_t	free;	/*第一个空闲对象(如果有的话)*/
      } ;
      
    • 创建新的slab函数
      通过__get_free_pages()低级内核页分配器进行
      static inline void * kmem_getpages(struct kmem_cache *cachep,gfp_t flags){
      	void *addr;
      	flags |= cachep->gfpflags;//把高速缓存需要的缺省标志加到flags参数上
      	addr = (void*) _get_free_pages(flags,cachep->gfporder) ;//分配页的大小为2的幂次方
      	return addr;
      }
      
    • slab释放函数
      当内存变得紧缺时,系统试图释放出更多的内存以供使用,或者当高速缓存显示地被取消地时候,就会调用slab释放函数。调用方法:kmem_freepages()释放内存,最终实际上调用的是free_pages()
  2. slab分配器的接口
    • 这一节中讲的是高速缓存和对象 的 创建和销毁,也就是slab的接口。有关slab的操作在上一节有介绍到。
    • 高速缓存的创建
      kmem_cache_t *kmem_cache_create(const char *name, size_t size, size_t align, unsigned long flags,
      				       void (*ctor)(void *, kmem_cache_t *, unsigned long),
      				       void (*dtor)(void *, kmem_cache_t *, unsigned long));
      
      • 第一个参数是字符串,存放着高速缓存的名字。第二个参数是高速缓存中每个元素的大小,第三个参数就是高速缓存内第一个对象的偏移,这用来确保在页内进行特定的对齐,通常情况,0就可以满足要求,也就是标准对齐。flags是可选的设置项,用来控制高速缓存的行为。
      • 最后两个参数ctordtor分别是高速缓存的构造和析构函数。只有在新的页追加到高速缓存时,构造函数才被调用。只有从高速缓存中删去页时,析构函数才被调用。有一个析构函数就要有一个构造函数,实际上,Linux内核的高速缓存不使用析构函数或构造函数,可以将这两个参数都赋值为NULL
      • kmem_cache_create()在成功时会返回一个指向所创建高速缓存的指针,否则,返回NULL。这个函数不能在中断上下文中调用,因为它可能会睡眠。
    • 销毁一个高速缓存
      int kmem_cache_destroy(kmem_cache_t *cachep);
      
      同样,也不能从中断上下文中调用这个函数,因为它也可能会睡眠。调用该函数之前必须确保以下两个条件:
      • 高速缓存中的所有slab都必须为空
      • 在调用kmem_cache_destroy()期间,不能再访问这个高速缓存
    • 创建高速缓存之后,就可以通过下列函数从中获取对象:(注意slab和对象的关系)
      void *kmem_cache_alloc(kmem_cache_t *cachep,int flags);
      
      该函数从给定的高速缓存cachep中返回一个指向对象的指针。如果高速缓存的所有slab中都没有空闲的对象,那么slab层必须通过kmem_getpages()获取新的页,flags的值传递给__get_free_pages()
    • 最后释放一个对象,并把它返回给原先的slab,可以使用下面的函数:
      void kmem_cache_free(kmem_cache_t *cachep,void *objp);
      
      这样就能把高速缓存cachep中的对象objp标记为空闲了。

7、在栈上静态分配

  • 内核栈可以是1页,也可以是两页,这取决于编译时配置选项,栈大小因此在4KB-16KB的范围内。历史上,中断处理程序和被中断进程共享一个内核栈,当1页栈的选项被激活时,中断处理程序就获得了自己的栈。

8、高端内核的映射

  • 根据定义,在高端内存中的页不能永久映射到内核地址空间上。在x86体系结构上,高于896的所有物理内存的范围大都是高端内存,它并不会永久地或自动映射到内核地址空间
  • 永久映射
    • 要映射一个给定的page结构到内核地址空间,可以使用:
      void *kmap(struct page *page);
      
      这个函数在高端内存或低端内存都能用。如果page结构对应的是低端内存中的一页,函数只会单纯地返回该页的虚拟地址。如果页位于高端内存,则会建立一个永久映射,再返回地址。这个函数可以睡眠。因此kmap()只能用在进程上下文中。
    • 当不再需要高端内存时,应该解除映射,可以通过kunmap函数完成:
      void kunmap(struct page *page);
      
  • 临时映射
    当必须创建一个映射而当前上下文又不能睡眠时,内核提供了临时映射(也就是所谓的原子映射)。有一组保留的映射,它们可以存放新创建的临时映射,内核可以原子地把高端内存中的一个页映射到某个保留的映射中。因此,临时映射可以用在不能睡眠的地方,比如中断处理程序中,因为获取映射时绝不会阻塞
    • 可以通过下列函数创建一个临时映射:
      void *kmap_atomic(struct page *page,enum km_type type);
      
    • 可通过下列函数取消映射:
      void kunmap_atomic(void *kvaddr.enum km_type type);
      

9、CPU的接口

  • 为了方便创建和操作每个CPU数据,从而引进了新的操作接口,称作percpu。该接口使每个CPU数据的创建和操作得以简化。头文件linux/percpu.h声明了所有的接口操作例程,可以在文件mm/slab.casm/percput.h中找到它们的定义。
  • 编译时的每个CPU数据
    • 在编译时定义每个CPU变量很容易:
      DEFINE_PER_CPU(type,name);
      
    • 这个语句为系统中的每个处理器都创建一个类型为type,名字为name的变量实例。如果你需要在别处声明变量,以防编译时警告,那么下面的宏将是你的好帮手:
      DECLARE_PER_CPU(type,name);
      
    • 你可以用get_cpu_var()put_cpu_var()例程操作变量。调用get_cpu_var()返回当前处理器上的指定变量,同时它将禁止抢占,另一方面put_cpu_var()将相应地重新激活抢占(看到代码并没有进行加锁,这时因为操作的数据对于当前的处理器是唯一的,不存在并发问题,唯一会产生同步问题的因素是内核抢占):
      get_cpu_var(name)++; /*增加该处理器上的name变量的值*/
      put_cpu_var(name); /*完成;重新激活内核抢占*/
      
  • 运行时每个CPU数据
    • 内核实现每个CPU数据的动态分配方法类似于kmalloc()。这些方法原型在文件linux/percpu.h
      void alloc_percpu(type); /*一个宏*/
      void *__alloc_percpu(size_t size,size_t align);
      void free_percpu(const void *);
      
      alloc_percpu()给系统中的每个处理器分配一个指定类型对象的实例。它其实是宏__alloc_percpu()的一个封装,这个原始宏接受的参数有两个,一个是要分配的实际字节数,一个是分配时按多少字节对齐。而封装后的alloc_percpu()按照给定类型的自然边界对齐。调用free_percpu()将释放所有处理器上指定的每个CPU数据。
    • 无论是alloc_percpu()还是__alloc_percpu()都会返回一个指针,它用来间接引用动态创建的每个CPU数据,内核提供了两个宏来利用指针获取每个CPU数据:
      get_cpu_ptr(ptr);
      put_cpu_ptr(ptr);
      
      get_cpu_ptr()宏返回一个指向当前处理器数据的实例,它同时会禁止内核抢占,而在put_cpu_ptr()宏中重新激活内核抢占。

10、使用每个CPU数据的原因

  • 减少了数据锁定:因为按照每个处理器访问每个CPU数据的逻辑,你可以不在需要任何锁。
  • 使用每个CPU数据可以大大减少缓存失效。失效发生在处理器试图使它们的缓存保持同步时,如果一个处理器操作某个数据,而该数据又存放在其他处理器缓存中,那么存放该数据的那个处理器必须清理或刷新它自己的缓存。使用每个CPU数据将使缓存影响降至最低,因为理想情况下只会访问它自己的数据。percpu接口缓存对齐所有数据,以便确保在访问一个处理器的数据时,不会将另一个处理器的数据带入同一个缓存线上。

11、分配函数的选择

  • 如果你需要连续的物理页,就可以使用某个低级页分配器或kmalloc
  • 如果你想从高端内存进行分配,就可以alloc_pages()。该函数返回一个指向struct page结构的指针,而不是一个指向某个逻辑地址的指针。
  • 如果你不需要物理上的连续的页,而仅仅需要虚拟地址上连续的页,那么就使用vmalloc()
  • 如果你需要创建和销毁很多较大的数据结构,那么应该考虑建立slab高速缓存。

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

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

相关文章

排序算法之堆排序的实现

一、堆的相关概念 堆一般指的是二叉堆&#xff0c;顾名思义&#xff0c;二叉堆是完全二叉树或者近似完全二叉树 1. 堆的性质 ① 是一棵完全二叉树 ② 每个节点的值都大于或等于其子节点的值&#xff0c;为最大堆&#xff1b;反之为最小堆。 2. 堆的存储 一般用数组来表示堆&…

网站域名查询地址-域名所有者查询

域名查询注册信息查询 147SEO域名查询是一款全能的域名查询注册信息查询软件。它不仅提供了单个域名的实时查询功能&#xff0c;还支持批量域名查询功能&#xff0c;可以快速查询多个域名的注册和到期信息。以下是147SEO域名查询的主要特点&#xff1a; 批量域名查询&#xff…

基于【EasyDL】【图像分类】实现农作物病害识别小程序

内容、数据集来源:基于飞桨的农作物病害智能识别系统 - 飞桨AI Studio 项目背景 联合国粮食及农业组织的一份报告表明&#xff0c;每年农业生产的自然损失中有三分之一以上是由农业病虫害造成的&#xff0c;使这些成为当前影响农业生产和农业生产的最重要因素。需要考虑的农业…

浅谈Vue响应式

什么是响应式 不管是自己面试还是八股文告诉你的是&#xff0c;响应式描述的是视图与数据变化之间的一种关系。但这不够准确。 要要了解什么是响应式&#xff0c;我们必须了解不管是React,还是Vue其实本质就是一个函数。 那么我们可以形象的说&#xff1a;响应式描述的就是函数…

编译C++ makefile ZXing-cpp生成条型码 二维码及识别 再试验证成功vs2022

下载ZXing-cpp源码及vs2022 cmake --help看看支持的vs 为什么没有win32呢 进到目录&#xff0c;//新建编译目录//生成vs工程 mkdir build & cd build cmake .. 中间提示opencv和qt等缺东西不要紧&#xff0c;我只用到生成条型码 release也正常。生成成功。 默认是MD 先…

记一次 .NET 某医院门诊软件 卡死分析

一&#xff1a;背景 1. 讲故事 前几天有位朋友找到我&#xff0c;说他们的软件在客户那边卡死了&#xff0c;让我帮忙看下是怎么回事&#xff1f;我就让朋友在程序卡死的时候通过 任务管理器 抓一个 dump 下来&#xff0c;虽然默认抓的是 wow64 &#xff0c;不过用 soswow64.…

FreeRTOS学习笔记(四)——应用开发(二)

文章目录 0x01 互斥量互斥量的优先级继承机制互斥量应用场景互斥量运作机制互斥量控制块互斥量接口函数xSemaphoreCreateMutex()xQueueCreateMutex()prvInitialiseMutex&#xff08;&#xff09;xSemaphoreCreateRecursiveMutex()vSemaphoreDelete()xSemaphoreTake()xQueueSema…

通过nginx配置 将vue项目运行到阿里云linux服务器上

先将 我们的vue项目打包起来 打包好之后 我们在项目根目录下 找到 dist 下的 index.html 保证这个文件要能正常运行 然后 我们将这个dist文件夹 压缩一下 然后 回到项目终端 执行 scp -r ./dist.zip 用户名(如果之前没设置过就是 root)服务器公网地址:/root然后 他会要求我…

多语言网站的外包开发流程

随着互联网/移动互联网的全球普及&#xff0c;越来越多的企业希望将产品卖向全球&#xff0c;这就首先需要有一个多语言的网站来宣传公司的产品&#xff0c;那设计和开发这样的网站需要注意什么呢&#xff0c;今天和大家分享这方面的知识。北京木奇移动技术有限公司&#xff0c…

C-函数栈帧

文章目录 函数栈帧栈帧创建栈帧销毁根据栈帧关系更改值拓展 可变参数列表基本原理整形提升 命令行参数打印环境变量 函数栈帧 int MyAdd(int a, int b) {int c 0;c a b;return c; } int main() {int x 0xA;int y 0xB;int z MyAdd(x,y);system("pause");return …

怎么隐藏回收站?3个方法轻松隐藏回收站!

案例&#xff1a;怎么隐藏回收站 【我不太想把回收站放到桌面上&#xff0c;想把它隐藏了&#xff0c;请问大家有什么好的方法可以隐藏回收站吗&#xff1f;】 回收站是一个非常常见的功能&#xff0c;允许用户恢复已删除的文件。然而&#xff0c;有些人可能不希望回收站一直…

SpringMVC高手进阶

&#x1f648;作者简介&#xff1a;练习时长两年半的Java up主 &#x1f649;个人主页&#xff1a;程序员老茶 &#x1f64a; ps:点赞&#x1f44d;是免费的&#xff0c;却可以让写博客的作者开兴好久好久&#x1f60e; &#x1f4da;系列专栏&#xff1a;Java全栈&#xff0c;…

【MySQL】-- 数据库基础

目录 MySQL概述 MySQL初期概念 小结 主流数据库 连接服务器 服务器&#xff0c;数据库&#xff0c;表关系 数据逻辑存储 MySQL架构 SQL分类 存储引擎 存储引擎 查看存储引擎 MySQL概述 #问&#xff1a;什么是数据库&#xff1f; MySQL初期概念 这个所谓的mysql严格…

从零开始Vue3+Element Plus后台管理系统(七)——手写一个简单的多页签组件

以前都是用别人现成的多页签组件&#xff0c;自己也想尝试下做个Vue3的版本&#xff0c;目前还只有基本功能&#xff0c;慢慢完善。 主要思路 使用 Pinia 记录页签数据、处理操作初始状态没有页签数据&#xff0c;使用默认路由数据填充右击页签&#xff0c;显示更多关闭操作…

移动云与启明星辰联合发布移动云|星辰安全品牌

数字中国时代&#xff0c;企业数字化转型不断深化&#xff0c;云安全市场发展持续高增&#xff0c;其安全更需自主可控、全程可信。基于此&#xff0c;移动云和启明星辰共同打造移动云|星辰安全品牌&#xff0c;聚力协行共筑安全云的压舱石&#xff0c;携手共塑中国云安全产业发…

原神服务器服务端多人联机教程

原神服务器服务端多人联机教程 大家好&#xff0c;我是艾西在上一篇文章中我们说了win系统服务器怎么搭建原神服务端&#xff0c;在最后结尾时有带一嘴怎么改为多人联机但不是很详细。哪么这篇文章艾西会给小伙伴们说清楚原神服务端怎么改为多人联机&#xff0c;毕竟玩游戏肯定…

MySQL高级语句(一)

一、SQL高级语句 1、 SELECT 显示表格中一个或数个栏位的所有资料 语法&#xff1a;SELECT "字段" FROM "表名"; select * from test1; select name from test1; select name,sex from test1;2、DISTINCT 不显示重复的内容 语法&#xff1a;SELECT D…

pdf怎么转换成ppt?4种方法1分钟处理

​ pdf怎么转换成ppt&#xff1f;在日常的办公中&#xff0c;经常需要进行PDF文件格式的转换。例如&#xff0c;我们从互联网上下载的许多资料都是以PDF格式保存的。此外&#xff0c;在使用Microsoft Office时&#xff0c;有些用户需要将Word文档转换为PDF格式&#xff0…

MySQL的概念、编译安装,以及自动补全

一.数据库的基本概念 1、数据&#xff08;Data&#xff09; • 描述事物的符号记录 • 包括数字&#xff0c;文字&#xff0c;图形&#xff0c;图像&#xff0c;声音&#xff0c;档案记录等 • 以“记录”形式按统一的格式进行存储 2、表 • 将不同的记录组织在一起 • …

JavaWeb08(MVC应用02[家居商城]连接数据库)

目录 一.绑定分类 1.1 效果预览 1.2 代码实现 ①底层代码 ②前端代码 二.绑定所有商品 2.1 效果预览 2.2.代码实现 ①底层代码 ②前端代码 三.分类查询 3.1效果预览 3.2代码实现 ①底层代码 ②前端代码 四.模糊查询 4.1 效果预览 4.2代码实现 ①底层代码 ②前…