内存映射mmap技术详解
一、mmap基础概念
mmap 即 memory map,也就是内存映射。mmap 是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用 read、write 等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。
如下图所示:
mmap的作用,在应用这一层,是让你把文件的某一段,当作内存一样来访问。将文件映射到物理内存,将进程虚拟空间映射到那块内存。这样,进程不仅能像访问内存一样读写文件,多个进程映射同一文件,还能保证虚拟空间映射到同一块物理内存,达到内存共享的作用。
mmap 具有如下的特点:
- mmap 向应用程序提供的内存访问接口是内存地址连续的,但是对应的磁盘文件的 block 可以不是地址连续的;
- mmap 提供的内存空间是虚拟空间(虚拟内存),而不是物理空间(物理内存),因此完全可以分配远远大于物理内存大小的虚拟空间(例如 16G 内存主机分配 1000G 的 mmap 内存空间);
- mmap 负责映射文件逻辑上一段连续的数据(物理上可以不连续存储)映射为连续内存,而这里的文件可以是磁盘文件、驱动假造出的文件(例如 DMA 技术)以及设备;
- mmap 由操作系统负责管理,对同一个文件地址的映射将被所有线程共享,操作系统确保线程安全以及线程可见性;
- mmap 的设计很有启发性。基于磁盘的读写单位是 block(一般大小为 4KB),而基于内存的读写单位是地址(虽然内存的管理与分配单位是 4KB)。
换言之,CPU 进行一次磁盘读写操作涉及的数据量至少是 4KB,但是进行一次内存操作涉及的数据量是基于地址的,也就是通常的 64bit(64 位操作系统)。mmap 下进程可以采用指针的方式进行读写操作,这是值得注意的。
二. 虚拟内存?虚拟空间?
其实是一个概念,前一篇对于这个词没有确切的定义,现在定义一下:
虚拟空间就是进程看到的所有地址组成的空间,虚拟空间是某个进程对分配给它的所有物理地址(已经分配的和将会分配的)的重新映射。
而虚拟内存,为啥叫虚拟内存,是因为它就不是真正的内存,是假的,因为它是由地址组成的空间,所以在这里,使用虚拟空间这个词更加确切和易懂。(不过虚拟内存这个词也不算错)
虚拟空间原理
物理内存
首先,物理地址实际上也不是连续的,通常是包含作为主存的DRAM和IO寄存器
以前的CPU(如X86)是为IO划分单独的地址空间,所以不能用直接访问内存的方式(如指针)IO,只能用专门的方法(in/read/out/write)诸如此类。现在的CPU利用PCI总线将IO寄存器映射到物理内存,所以出现了基于内存访问的IO。还有一点补充的,就如同进程空间有一块内核空间一样,物理内存也会有极小一部分是不能访问的,为内核所用。
三个总线
这里再补充下三个总线的知识,即:地址总线、数据总线、控制总线
- 地址总线,用来传输地址
- 数据总线,用来传输数据
- 控制总线,用来传输命令
比如CPU通过控制总线发送读取命令,同时用地址总线发送要读取的数据虚地址,经过MMU后到内存
内存通过数据总线将数据传输给CPU。虚拟地址的空间和指令集的地址长度有关,不一定和物理地址长度一致,比如现在的64位处理器,从VA角度看来,可以访问64位的地址,但地址总线长度只有48位,所以你可以访问一个位于2^52这个位置的地址。
虚拟内存地址转换(虚地址转实地址)
上面已经明确了虚拟内存是虚拟空间,即地址的集合这一概念。基于此,来说说原理。
如果还记得操作系统课程里面提到的虚地址,那么这个虚地址就是虚拟空间的地址了,虚地址通过转换得到实地址,转换方式课程内也讲得很清楚,虚地址头部包含了页号(段地址和段大小,看存储模式:页存储、段存储,段页式),剩下部分是偏移量,经过MMU转换成实地址。
存储方式
虚拟地址头部为页号通过查询页表得到物理页号,假设一页时1K,那么页号*偏移量就得到物理地址
虚拟地址头部为段号,段表中找到段基地址加上偏移量得到实地址
二、mmap原理
mmap函数创建一个新的vm_area_struct结构,并将其与文件/设备的物理地址相连。
vm_area_struct:
linux使用vm_area_struct来表示一个独立的虚拟内存区域,一个进程可以使用多个vm_area_struct来表示不用类型的虚拟内存区域(如堆,栈,代码段,MMAP区域等)。
vm_area_struct结构中包含了区域起始地址。同时也包含了一个vm_opt指针,其内部可引出所有针对这个区域可以使用的系统调用函数。从而,进程可以通过vm_area_struct获取操作这段内存区域所需的任何信息。
进程通过vma操作内存,而vma与文件/设备的物理地址相连,系统自动回写脏页面到对应的文件磁盘上(或写入到设备地址空间),实现内存映射文件。
内存映射文件的原理:
首先创建虚拟区间并完成地址映射,此时还没有将任何文件数据拷贝至主存。当进程发起读写操作时,会访问虚拟地址空间,通过查询页表,发现这段地址不在物理页上,因为只建立了地址映射,真正的数据还没有拷贝到内存,因此引发缺页异常。缺页异常经过一系列判断,确定无非法操作后,内核发起请求调页过程。
最终会调用nopage函数把所缺的页从文件在磁盘里的地址拷贝到物理内存。之后进程便可以对这片主存进行读写,如果写操作修改了内容,一定时间后系统会自动回写脏页面到对应的磁盘地址,完成了写入到文件的过程。另外,也可以调用msync()来强制同步,这样所写的内存就能立刻保存到文件中。
mmap内存映射的实现过程,总的来说可以分为三个阶段:
(一)进程启动映射过程,并在虚拟地址空间中为映射创建虚拟映射区域
- 进程在用户空间调用库函数mmap,原型:void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
- 在当前进程的虚拟地址空间中,寻找一段空闲的满足要求的连续的虚拟地址
- 为此虚拟区分配一个vm_area_struct结构,接着对这个结构的各个域进行了初始化
- 将新建的虚拟区结构(vm_area_struct)插入进程的虚拟地址区域链表或树中
(二)调用内核空间的系统调用函数mmap(不同于用户空间函数),实现文件物理地址和进程虚拟地址的一一映射关系
- 为映射分配了新的虚拟地址区域后,通过待映射的文件指针,在文件描述符表中找到对应的文件描述符,通过文件描述符,链接到内核“已打开文件集”中该文件的文件结构体(struct file),每个文件结构体维护着和这个已打开文件相关各项信息。
- 通过该文件的文件结构体,链接到file_operations模块,调用内核函数mmap,其原型为:int mmap(struct file *filp, struct vm_area_struct *vma),不同于用户空间库函数。
- 内核mmap函数通过虚拟文件系统inode模块定位到文件磁盘物理地址。
- 通过remap_pfn_range函数建立页表,即实现了文件地址和虚拟地址区域的映射关系。此时,这片虚拟地址并没有任何数据关联到主存中。
(三)进程发起对这片映射空间的访问,引发缺页异常,实现文件内容到物理内存(主存)的拷贝
注:前两个阶段仅在于创建虚拟区间并完成地址映射,但是并没有将任何文件数据的拷贝至主存。真正的文件读取是当进程发起读或写操作时。
- 进程的读或写操作访问虚拟地址空间这一段映射地址,通过查询页表,发现这一段地址并不在物理页面上。因为目前只建立了地址映射,真正的硬盘数据还没有拷贝到内存中,因此引发缺页异常。
- 缺页异常进行一系列判断,确定无非法操作后,内核发起请求调页过程。
- 调页过程先在交换缓存空间(swap cache)中寻找需要访问的内存页,如果没有则调用nopage函数把所缺的页从磁盘装入到主存中。
- 之后进程即可对这片主存进行读或者写的操作,如果写操作改变了其内容,一定时间后系统会自动回写脏页面到对应磁盘地址,也即完成了写入到文件的过程。
注:修改过的脏页面并不会立即更新回文件中,而是有一段时间的延迟,可以调用msync()来强制同步, 这样所写的内容就能立即保存到文件里了。
三、mmap 的 I/O 模型
mmap 也是一种零拷贝技术,其 I/O 模型如下图所示:
代码语言:javascript
复制
#include <sys/mman.h>
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset)
mmap 技术有如下特点:
- 利用 DMA 技术来取代 CPU 来在内存与其他组件之间的数据拷贝,例如从磁盘到内存,从内存到网卡;
- 用户空间的 mmap file 使用虚拟内存,实际上并不占据物理内存,只有在内核空间的 kernel buffer cache 才占据实际的物理内存;
- mmap() 函数需要配合 write() 系统调动进行配合操作,这与 sendfile() 函数有所不同,后者一次性代替了 read() 以及 write();因此 mmap 也至少需要 4 次上下文切换;
- mmap 仅仅能够避免内核空间到用户空间的全程 CPU 负责的数据拷贝,但是内核空间内部还是需要全程 CPU 负责的数据拷贝;
利用 mmap() 替换 read(),配合 write() 调用的整个流程如下:
- 用户进程调用 mmap(),从用户态陷入内核态,将内核缓冲区映射到用户缓存区;
- DMA 控制器将数据从硬盘拷贝到内核缓冲区(可见其使用了 Page Cache 机制);
- mmap() 返回,上下文从内核态切换回用户态;
- 用户进程调用 write(),尝试把文件数据写到内核里的套接字缓冲区,再次陷入内核态;
- CPU 将内核缓冲区中的数据拷贝到的套接字缓冲区;
- DMA 控制器将数据从套接字缓冲区拷贝到网卡完成数据传输;
- write() 返回,上下文从内核态切换回用户态。
通过mmap实现的零拷贝I/O进行了4次用户空间与内核空间的上下文切换,以及3次数据拷贝;其中3次数据拷贝中包括了2次DMA拷贝和1次CPU拷贝
四、mmap 的优势
1.简化用户进程编程
在用户空间看来,通过 mmap 机制以后,磁盘上的文件仿佛直接就在内存中,把访问磁盘文件简化为按地址访问内存。这样一来,应用程序自然不需要使用文件系统的 write(写入)、read(读取)、fsync(同步)等系统调用,因为现在只要面向内存的虚拟空间进行开发。
但是,这并不意味着我们不再需要进行这些系统调用,而是说这些系统调用由操作系统在 mmap 机制的内部封装好了。
(1)基于缺页异常的懒加载
出于节约物理内存以及 mmap 方法快速返回的目的,mmap 映射采用懒加载机制。具体来说,通过 mmap 申请 1000G 内存可能仅仅占用了 100MB 的虚拟内存空间,甚至没有分配实际的物理内存空间。当你访问相关内存地址时,才会进行真正的 write、read 等系统调用。CPU 会通过陷入缺页异常的方式来将磁盘上的数据加载到物理内存中,此时才会发生真正的物理内存分配。
(2)数据一致性由 OS 确保
当发生数据修改时,内存出现脏页,与磁盘文件出现不一致。mmap 机制下由操作系统自动完成内存数据落盘(脏页回刷),用户进程通常并不需要手动管理数据落盘。
读写效率提高:避免内核空间到用户空间的数据拷贝
简而言之,mmap 被认为快的原因是因为建立了页到用户进程的虚地址空间映射,以读取文件为例,避免了页从内核空间拷贝到用户空间。
3.避免只读操作时的 swap 操作
虚拟内存带来了种种好处,但是一个最大的问题在于所有进程的虚拟内存大小总和可能大于物理内存总大小,因此当操作系统物理内存不够用时,就会把一部分内存 swap 到磁盘上。
在 mmap 下,如果虚拟空间没有发生写操作,那么由于通过 mmap 操作得到的内存数据完全可以通过再次调用 mmap 操作映射文件得到。但是,通过其他方式分配的内存,在没有发生写操作的情况下,操作系统并不知道如何简单地从现有文件中(除非其重新执行一遍应用程序,但是代价很大)恢复内存数据,因此必须将内存 swap 到磁盘上。
4.节约内存
由于用户空间与内核空间实际上共用同一份数据,因此在大文件场景下在实际物理内存占用上有优势。
4. mmap 不是银弹
mmap 不是银弹,这意味着 mmap 也有其缺陷,在相关场景下的性能存在缺陷:
- 由于 MMAP 使用时必须实现指定好内存映射的大小,因此 mmap 并不适合变长文件;
- 如果更新文件的操作很多,mmap 避免两态拷贝的优势就被摊还,最终还是落在了大量的脏页回写及由此引发的随机 I/O 上,所以在随机写很多的情况下,mmap 方式在效率上不一定会比带缓冲区的一般写快;
- 读/写小文件(例如 16K 以下的文件),mmap 与通过 read 系统调用相比有着更高的开销与延迟;同时 mmap 的刷盘由系统全权控制,但是在小数据量的情况下由应用本身手动控制更好;
- mmap 受限于操作系统内存大小:例如在 32-bits 的操作系统上,虚拟内存总大小也就 2GB,但由于 mmap 必须要在内存中找到一块连续的地址块,此时你就无法对 4GB 大小的文件完全进行 mmap,在这种情况下你必须分多块分别进行 mmap,但是此时地址内存地址已经不再连续,使用 mmap 的意义大打折扣,而且引入了额外的复杂性;
5. mmap 的适用场景
mmap 的适用场景实际上非常受限,在如下场合下可以选择使用 mmap 机制:
- 多个线程以只读的方式同时访问一个文件,这是因为 mmap 机制下多线程共享了同一物理内存空间,因此节约了内存;
- mmap 非常适合用于进程间通信,这是因为对同一文件对应的 mmap 分配的物理内存天然多线程共享,并可以依赖于操作系统的同步原语;
- mmap 虽然比 sendfile 等机制多了一次 CPU 全程参与的内存拷贝,但是用户空间与内核空间并不需要数据拷贝,因此在正确使用情况下并不比 sendfile 效率差;
6.mmap使用细节
- 使用mmap需要注意的一个关键点是,mmap映射区域大小必须是物理页大小(page_size)的整倍数(32位系统中通常是4k字节)。原因是,内存的最小粒度是页,而进程虚拟地址空间和内存的映射也是以页为单位。为了匹配内存的操作,mmap从磁盘到虚拟地址空间的映射也必须是页。
- 内核可以跟踪被内存映射的底层对象(文件)的大小,进程可以合法的访问在当前文件大小以内又在内存映射区以内的那些字节。也就是说,如果文件的大小一直在扩张,只要在映射区域范围内的数据,进程都可以合法得到,这和映射建立时文件的大小无关。
- 映射建立之后,即使文件关闭,映射依然存在。因为映射的是磁盘的地址,不是文件本身,和文件句柄无关。同时可用于进程间通信的有效地址空间不完全受限于被映射文件的大小,因为是按页映射。
在上面的知识前提下,我们下面看看如果大小不是页的整倍数的具体情况:
情形一:一个文件的大小是5000字节,mmap函数从一个文件的起始位置开始,映射5000字节到虚拟内存中。
分析:因为单位物理页面的大小是4096字节,虽然被映射的文件只有5000字节,但是对应到进程虚拟地址区域的大小需要满足整页大小,因此mmap函数执行后,实际映射到虚拟内存区域8192个 字节,5000~8191的字节部分用零填充。映射后的对应关系如下图所示:
此时: (1)读/写前5000个字节(0~4999),会返回操作文件内容。 (2)读字节50008191时,结果全为0。写50008191时,进程不会报错,但是所写的内容不会写入原文件中 。 (3)读/写8192以外的磁盘部分,会返回一个SIGSECV错误。 情形二:一个文件的大小是5000字节,mmap函数从一个文件的起始位置开始,映射15000字节到虚拟内存中,即映射大小超过了原始文件的大小。
分析:由于文件的大小是5000字节,和情形一一样,其对应的两个物理页。那么这两个物理页都是合法可以读写的,只是超出5000的部分不会体现在原文件中。由于程序要求映射15000字节,而文件只占两个物理页,因此8192字节~15000字节都不能读写,操作时会返回异常。如下图所示:
此时: (1)进程可以正常读/写被映射的前5000字节(0~4999),写操作的改动会在一定时间后反映在原文件中。 (2)对于5000~8191字节,进程可以进行读写过程,不会报错。但是内容在写入前均为0,另外,写入后不会反映在文件中。 (3)对于8192~14999字节,进程不能对其进行读写,会报SIGBUS错误。 (4)对于15000以外的字节,进程不能对其读写,会引发SIGSEGV错误。 情形三:一个文件初始大小为0,使用mmap操作映射了10004K的大小,即1000个物理页大约4M字节空间,mmap返回指针ptr。
分析:如果在映射建立之初,就对文件进行读写操作,由于文件大小为0,并没有合法的物理页对应,如同情形二一样,会返回SIGBUS错误。但是如果,每次操作ptr读写前,先增加文件的大小,那么ptr在文件大小内部的操作就是合法的。例如,文件扩充4096字节,ptr就能操作ptr ~ [ (char)ptr + 4095]的空间。只要文件扩充的范围在1000个物理页(映射范围)内,ptr都可以对应操作相同的大小。这样,方便随时扩充文件空间,随时写入文件,不造成空间浪费。
五、mmap映射
在内存映射的过程中,并没有实际的数据拷贝,文件没有被载入内存,只是逻辑上被放入了内存,具体到代码,就是建立并初始化了相关的数据结构(struct address_space),这个过程有系统调用mmap()实现,所以建立内存映射的效率很高。 既然建立内存映射没有进行实际的数据拷贝,那么进程又怎么能最终直接通过内存操作访问到硬盘上的文件呢?那就要看内存映射之后的几个相关的过程了。 mmap()会返回一个指针ptr,它指向进程逻辑地址空间中的一个地址,这样以后,进程无需再调用read或write对文件进行读写,而只需要通过ptr就能够操作文件。但是ptr所指向的是一个逻辑地址,要操作其中的数据,必须通过MMU将逻辑地址转换成物理地址,这个过程与内存映射无关。 前面讲过,建立内存映射并没有实际拷贝数据,这时,MMU在地址映射表中是无法找到与ptr相对应的物理地址的,也就是MMU失败,将产生一个缺页中断,缺页中断的中断响应函数会在swap中寻找相对应的页面,如果找不到(也就是该文件从来没有被读入内存的情况),则会通过mmap()建立的映射关系,从硬盘上将文件读取到物理内存中,如图1中过程3所示。这个过程与内存映射无关。 如果在拷贝数据时,发现物理内存不够用,则会通过虚拟内存机制(swap)将暂时不用的物理页面交换到硬盘上,这个过程也与内存映射无关。 mmap内存映射的实现过程:
- 进程启动映射过程,并在虚拟地址空间中为映射创建虚拟映射区域
- 调用内核空间的系统调用函数mmap(不同于用户空间函数),实现文件物理地址和进程虚拟地址的一一映射关系
- 进程发起对这片映射空间的访问,引发缺页异常,实现文件内容到物理内存(主存)的拷贝
适合的场景
- 您有一个很大的文件,其内容您想要随机访问一个或多个时间
- 您有一个小文件,它的内容您想要立即读入内存并经常访问。这种技术最适合那些大小不超过几个虚拟内存页的文件。(页是地址空间的最小单位,虚拟页和物理页的大小是一样的,通常为4KB。)
- 您需要在内存中缓存文件的特定部分。文件映射消除了缓存数据的需要,这使得系统磁盘缓存中的其他数据空间更大 当随机访问一个非常大的文件时,通常最好只映射文件的一小部分。映射大文件的问题是文件会消耗活动内存。如果文件足够大,系统可能会被迫将其他部分的内存分页以加载文件。将多个文件映射到内存中会使这个问题更加复杂。
不适合的场景
- 您希望从开始到结束的顺序从头到尾读取一个文件
- 这个文件有几百兆字节或者更大。将大文件映射到内存中会快速地填充内存,并可能导致分页,这将抵消首先映射文件的好处。对于大型顺序读取操作,禁用磁盘缓存并将文件读入一个小内存缓冲区
- 该文件大于可用的连续虚拟内存地址空间。对于64位应用程序来说,这不是什么问题,但是对于32位应用程序来说,这是一个问题
- 该文件位于可移动驱动器上
- 该文件位于网络驱动器上
示例代码
代码语言:javascript
复制
//
// ViewController.m
// TestCode
//
// Created by zhangdasen on 2020/5/24.
// Copyright © 2020 zhangdasen. All rights reserved.
//#import "ViewController.h"
#import <sys/mman.h>
#import <sys/stat.h>
@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:@"test.data"];NSLog(@"path: %@", path);NSString *str = @"test str2";[str writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil];ProcessFile(path.UTF8String);NSString *result = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];NSLog(@"result:%@", result);
}int MapFile(const char * inPathName, void ** outDataPtr, size_t * outDataLength, size_t appendSize)
{int outError;int fileDescriptor;struct stat statInfo;// Return safe values on error.outError = 0;*outDataPtr = NULL;*outDataLength = 0;// Open the file.fileDescriptor = open( inPathName, O_RDWR, 0 );if( fileDescriptor < 0 ){outError = errno;}else{// We now know the file exists. Retrieve the file size.if( fstat( fileDescriptor, &statInfo ) != 0 ){outError = errno;}else{ftruncate(fileDescriptor, statInfo.st_size + appendSize);fsync(fileDescriptor);*outDataPtr = mmap(NULL,statInfo.st_size + appendSize,PROT_READ|PROT_WRITE,MAP_FILE|MAP_SHARED,fileDescriptor,0);if( *outDataPtr == MAP_FAILED ){outError = errno;}else{// On success, return the size of the mapped file.*outDataLength = statInfo.st_size;}}// Now close the file. The kernel doesn’t use our file descriptor.close( fileDescriptor );}return outError;
}
void ProcessFile(const char * inPathName)
{size_t dataLength;void * dataPtr;char *appendStr = " append_key2";int appendSize = (int)strlen(appendStr);if( MapFile(inPathName, &dataPtr, &dataLength, appendSize) == 0) {dataPtr = dataPtr + dataLength;memcpy(dataPtr, appendStr, appendSize);// Unmap filesmunmap(dataPtr, appendSize + dataLength);}
}@end
相关文章:

内存映射mmap技术详解
一、mmap基础概念 mmap 即 memory map,也就是内存映射。mmap 是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,…...
react 合成事件
React合成事件-CSDN博客 当然,很高兴为你解释React中的合成事件概念,非常适合React初学者理解。 想象一下,你正在组织一场派对,为了让派对顺利进行,你需要管理各种活动,比如游戏、音乐和食物分配。但是&a…...

springboot配置集成RedisTemplate和Redisson,使用分布式锁案例
文章要点 自定义配置属性类集成配置RedisTemplate集成配置分布式锁Redisson使用分布式锁简单实现超卖方案 1. 项目结构 2. 集成RedisTemplate和Redisson 添加依赖 依赖的版本与继承的spring-boot-starter-parent工程相对应,可写可不写 <!--spring data redis…...
随机数相关
产生随机数对象 固定写法: Random 随机数变量名 new Random();Random r new Random();生成随机数 int i r.Next(); //生成一个非负数的随机数 Console.WriteLine(i);i r.Next(100); // 生成一个 0~99的随机数 左边始终是0 左包含 右边是100 右不包含 Consol…...

EulerMaker Yocto Open Build Service
EulerMaker & Yocto & Open Build Service 1 介绍1.1 概述 2 工具2.1 Yocto 【嵌入式领域】介绍目标好处三大关键组件创建流程发行版本 2.2 Open Build Service 【OBS】【服务器领域】介绍应用 2.3 EulerMaker 【全场景】介绍特性需求背景(1)能支…...

SQL面试问题集
目录 Q.左连接和右连接的区别 Q.union 和 union all的区别 1、取结果的交集 2、获取结果后的操作 Q.熟悉开窗函数吗?讲一下row_number和dense_rank的区别。 Q.hive行转列怎么操作的 Q.要求手写的题主要考了聚合函数和窗口函数,row_number()&#…...

基于单片机的八路抢答器设计论文
绪 论1.1 课题研究的相关背景 抢答器是一种应用非常广泛的设备,在各种竞赛、抢答场合中,它能迅速、客观地分辨出最先获得发言权的选手。早期的抢答器只由几个三极管、可控硅、发光管等组成,能通过发光管的指示辩认出选手号码。现在大多数抢答器均使用单片机(如MCS-5…...
一个最简单基于spring的websocket服务端+客户端实现案例
1、服务端 代码分为两部分: 一个是服务器终端类:用java注解来监听连接ServerEndpoint、连接成功OnOpen、连接失败OnClose、收到消息等状态OnMessage import org.springframework.stereotype.Component;import javax.websocket.*; import javax.websoc…...
三.二、关于 Vue.js 中`transition`组件使用:页面切换动画和标签移动动画都是要用到的
一、引言 在 Vue.js 中,transition组件提供了一种简单而强大的方式来实现页面过渡效果。它可以让元素在状态改变时,如进入或离开视图时,以平滑的动画方式进行过渡。通过transition,我们可以为应用增添更加生动和吸引人的用户体验…...

指纹考勤系统
目录 1.课题研究目的和内容 1.1 课题研究目的 1.2 课题研究内容 2.系统总体方案设计及功能模块介绍 2.1总体方案设计 2.2 ATK-301模块介绍 2.3 TFTLCD显示功能模块介绍 2.4 蜂鸣器报警功能模块介绍 2.5 时钟模块介绍 3.系统硬件设计与实现 3.1 系统硬件电…...

怎么找抖音视频素材?下载抖音的素材视频网站分享给你
在这个视觉印象至关重要的时代,选用高质量的视频素材对于制作抖音视频来说是关键。如果你正在寻找适合的视频素材来丰富你的抖音创作,以下这份详细的视频素材网站指南将帮助你迈出第一步。 蛙学府网 蛙学府网提供了丰富多样的视频素材,包括动…...

【pytorch】大模型训练张量并行
Large Scale Transformer model training with Tensor Parallel (TP) 张量并行如何工作 原始 Tensor Parallel (TP) 模型并行技术于Megatron-LM论文中被提出,是一种用于培育大规模Transformer模型的高效模型并行技术。我们在本练习指南中介绍的序列并行 (SP) 实际…...
Flutter 中的 CupertinoSliverNavigationBar 小部件:全面指南
Flutter 中的 CupertinoSliverNavigationBar 小部件:全面指南 Flutter 是一个由 Google 开发的跨平台 UI 框架,它允许开发者使用 Dart 语言来构建高性能、美观的移动、Web 和桌面应用。在 Flutter 的丰富组件库中,CupertinoSliverNavigation…...
【数据库系统概论】程序题
“学生管理数据库”包含以下三个表,即学生表Student、课程表Course和选课表SC,结构如下: Student(Sno,Sname,Ssex,Sage,Sdept)Course (Cno,Cname&…...

群体优化算法---蝙蝠优化算法分类Iris数据集
介绍 蝙蝠算法(Bat Algorithm, BA)是一种基于蝙蝠回声定位行为的优化算法。要将蝙蝠算法应用于分类问题,可以通过将蝙蝠算法用于优化分类器的参数,图像分割等 本文示例 我们使用一个经典的分类数据集,如Iris数据集&…...

【C++】类和对象1.0
本鼠浅浅介绍一些C类和对象的知识,希望能得到读者老爷们的垂阅! 目录 1.面向过程和面向对象 2.类的引入 3.类的定义 4.类的访问限定符及封装 4.1.类的访问限定符 4.2.封装 5.C中struct和class的区别 6.类域 7.类的实例化 8.类对象模型 8.1.类…...

Linux下gcc编译32位程序报错
gcc使用-m32选项,编译32位程序时,报错:/usr/include/stdio.h:27:10: fatal error: bits/libc-header-start.h: No such file or directory gcc编译32位程序时,报错:/usr/include/stdio.h:27:10: fatal error: bits/li…...

godot.bk
1.搜索godot国内镜像,直接安装,mono是csharp版本 2.直接解压,50m,无需安装,直接运行 3.godot里分为场景,节点 主场景用control场景,下面挂textureact放背景图片,右键实例化子场景把…...

【C++修行之道】类和对象(三)拷贝构造函数
目录 一、 概念 二、特征 正确的拷贝构造函数写法: 拷贝函数的另一种写法 三、若未显式定义,编译器会生成默认的拷贝构造函数。 四、编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗? 深拷…...

校园外卖系统的技术架构与实现方案
随着校园生活的日益现代化,外卖需求在高校学生群体中迅速增长。为了满足这一需求,校园外卖系统应运而生。本文将详细探讨校园外卖系统的技术架构及其实现方案,帮助读者了解这一系统的核心技术与实现路径。 一、系统概述 校园外卖系统主要包…...

测试微信模版消息推送
进入“开发接口管理”--“公众平台测试账号”,无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息: 关注测试号:扫二维码关注测试号。 发送模版消息: import requests da…...

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录
ASP.NET Core 是一个跨平台的开源框架,用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录,以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...
【Linux】C语言执行shell指令
在C语言中执行Shell指令 在C语言中,有几种方法可以执行Shell指令: 1. 使用system()函数 这是最简单的方法,包含在stdlib.h头文件中: #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...
基于服务器使用 apt 安装、配置 Nginx
🧾 一、查看可安装的 Nginx 版本 首先,你可以运行以下命令查看可用版本: apt-cache madison nginx-core输出示例: nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...

最新SpringBoot+SpringCloud+Nacos微服务框架分享
文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的,根据Excel列的需求预估的工时直接打骨折,不要问我为什么,主要…...

DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI
前一阵子在百度 AI 开发者大会上,看到基于小智 AI DIY 玩具的演示,感觉有点意思,想着自己也来试试。 如果只是想烧录现成的固件,乐鑫官方除了提供了 Windows 版本的 Flash 下载工具 之外,还提供了基于网页版的 ESP LA…...
鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/
使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题:docker pull 失败 网络不同,需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包
文章目录 现象:mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时,可能是因为以下几个原因:1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...

九天毕昇深度学习平台 | 如何安装库?
pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子: 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...