第4章 陷入与系统调用
有三种类型的事件会让CPU停止正常执行的指令,强制切换到指定的代码,处理这些事件。一种是系统调用,当用户程序执行ecall指令来让内核为它做一些事。另一种是异常:一个指令(用户或内核)做了一些非法的事,比方说除以0或者使用无效的虚拟地址。第三种是设备中断,当设备发信号说它需要关注时,比方说当磁盘硬件完成了读或写的请求。
本书使用trap作为这些情况的通用术语。通常,不管trap发生时在执行什么代码,后面都需要恢复,这些代码不需要知道发生了什么不同的事。就是说,我们通常需要trap是透明的:这对中断来说尤其重要,被中断掉的代码不希望中断发生。通常的执行顺序是,trap强制将控制转移到内核;内核保存寄存器和其他的状态,以便之后执行能恢复;内核执行合适的处理代码(例如系统调用实现或设备驱动);内核重载保存的状态,从trap中返回;最初的代码从它离开的位置恢复。
xv6内核处理所有的trap。这对系统调用来说自然而然。对中断也有意义,因为隔离要求用户进程不直接使用设备,而且内核拥有设备处理所需要的状态。对异常同样有意义,因为xv6对所有来自用户空间的异常都是同样的回应:干掉产生异常的程序。
xv6的trap处理分四步进行:1. RISC-V CPU采取的硬件动作,2. 一个汇编写的向量表通向内核的C代码,3. C语言trap handler决定怎么处理这个trap,4. 系统调用或设备驱动服务例行程序。虽然三种trap类型的共同点说明内核能够通过一条代码路径处理所有的trap,事实证明,对三种不同的实例分别使用不同的汇编向量表更方便:用户空间trap,内核空间trap,计时器中断。
distinct
4.1 RISC-V 陷入机制
每个RISC-V CPU都有一组控制寄存器,内核通过写入这些寄存器告诉CPU怎样处理trap,内核也可以读取这些寄存器来知道trap的发生。RISC-V文件详述了整个过程。riscv.h(kernel/riscv.h:1)包含xv6使用的定义。下面是大部分主要寄存器的说明:
- stvec:内核将trap handler的地址写在这;RISC-V跳到这里来处理一个trap。
- sepc:当trap发生时,RISC-V将程序计数器保存在这(因为pc会被stvec覆盖掉)。sret(从trap返回)指令将sepc拷贝到pc。内核可以通过写入sepc来控制sret返回到哪里。
- scause:RISC-V将引发trap的原因编号放在这。
- sscratch:内核把一个值放在这,它将在trap handler最开始派上用场。
- sstatus:sstatus中的SIE位控制了设配中断是否被允许。如果内核清空了SIE,RISC-V将推迟设备中断,直到内核设置SIE位。SPP位表示trap是来自用户模式还是管理者模式,也控制了sret返回到什么模式。
上面的寄存器与管理者模式下处理的trap有关,它们不能在用户模式下被读写。在机器模式下也有一组等价的寄存器,xv6只在特殊的定时器中断下使用它们。
多核芯片上的每个CPU都有它自己的一组寄存器,在任意给定的时间可能不止一个CPU在处理一个trap。
当需要强制陷入时,RISC-V硬件对所有trap类型(除了定时器中断)执行下列步骤:
- 如果trap是设备中断,且sstatus中的SIE位被清除,则不再向下执行
- 通过清除SIE禁止中断
- 将pc拷贝到sepc中
- 在sstatus中的SPP位保存当前模式(用户或管理者)
- 设置scause来反映trap的触发原因
- 将模式设置为管理者
- 将stvec拷贝到pc
- 在新的pc处开始执行
请注意,CPU不会切换到内核页表,不会切换到内核栈,也不会保存除pc外的任何寄存器。内核软件必须完成这些工作。CPU在trap过程中只做最少工作的一个原因,是为软件提供灵活性;比方说,有些操作系统不在某些情况下不需要页表切换,这能够提高性能。
你可能会好奇,是否还能进一步简化CPU硬件的trap处理过程。比方说,假设CPU不切换程序计数器。然后,trap可能切换到管理者模式,却仍然运行用户指令。这些用户指令将破坏用户、内核之间的隔离,举例来说,通过修改satp寄存器来指向一个允许访问所有物理内存的页表。因此,CPU切换到内核指定的指令地址即stvec非常重要。
comes in handy defer
4.2 来自用户空间的trap
trap可能发生在用户空间,当用户程序发起系统调用(ecall指令),或做了非法的事情,或者设备中断。来自用户空间的trap的高层路径是:uservec(kernel/trampoline.S:16),然后usertrap(kernel/trap.c:37);返回时,usertrapret(kernel/trap.c:90),然后userret(kernel/trampoline.S:16)。
来自用户代码的的traps比来自内核的更复杂,因为satp指向用户页表,而不映射内核,而且栈指针可能包含一个无效甚至恶意的值。
因为RISC-V硬件在trap中不切换页表,用户页表必须包含一个指向uservec的映射,uservec是stvec指向的trap向量表的所有指令。uservec切换satp使其指向内核页表;为了在切换之后继续执行指令,uservec在内核页表与用户页表中必须位于同一个地址。
xv6使用一个包含uservec的跳板页来满足这些限制条件。xv6在内核页表中与每个用户页表中都将跳板页应设在一个相同的虚拟地址上。这个虚拟地址就是TRAMPOLINE(正如我们在图2.3与图3.3中看到的)。跳板内容在trampoline.S中设置,而且,(当执行用户代码时)stvec被设置为uservec(kernel/trampoline.S:16)。
当uservec开始执行时,所有32个寄存器中包含的都是被打断的代码所拥有的值。但是uservec需要修改一些寄存器来设置satp,并生成存放寄存器的地址。RISC-V以sscratch寄存器的形式提供了帮助。uservec中最开始的指令csrrw交换了a0与sscratch寄存器的内容。现在用户代码中的a0得到了保存,uservec也有了一个寄存器a0可以使用,而且a0包含了内核之前放在sscratch寄存器中的值。
uservec接下来的任务是保存用户寄存器。在进入用户空间之前,内核设置sscratch,指向一个每个进程的trapframe,这个帧有空间保存所有的用户寄存器(kernel/proc.h:44)。因为satp仍然指向用户页表,uservec需要这个trapframe在用户页表中可以映射。当创建每个进程时,xv6为这个进程的trapframe分配一个页,然后把它跟用户虚拟地址TRAPFRAME建立映射关系,这个虚拟地址紧邻TRAMPOLINE,在其下面。进程的p->trapframe也指向trapframe,它指向trapframe的物理地址,这样内核就能通过内核页表使用trapframe。
因此,在交换a0和sscratch后,a0保存了一个指向当前进程trapframe的指针。uservec现在将所有的用户寄存器保存在那里,包括从sscratch中读到的用户的a0。
trapframe包含了指向当前进程内核栈的指针,当前CPU的hartid,usertrap的地址,以及内核页表的地址。uservec检索这些信息,将satp切换到内核页表,然后调用usertrap。
usertrap的工作是确定trap的触发原因,执行它,然后返回(kernel/trap.c:37)。正如上面所提到的,它首先修改stvec使得trap将被kernelvec处理。它再次保存sepc(被保存永和程序计数器),因为在usertrap中可能有进程切换,这会导致sepc被覆盖。如果trap是一个系统调用,syscall处理它;如果是用户中断,devintr;如果是异常,内核直接杀掉出错的进程。系统调用路线会在保存的用户pc上加4,因为RISC-V在系统调用时,将程序指针留在了ecall指令处。在离开时,usertrap会检查是否进程已经被杀掉,或者是否应该CPU让权(如果trap是计时器中断)。
返回用户空间的第一步是调用usertrapret(kernel/trap.c:90)。这个函数设置好RISC-V的控制寄存器,准备好迎接下一个来自用户空间的trap。这包括修改stvec让它指向uservec,准备uservec依赖的trapframe域,以及设置sepc为之前保存的用户程序计数器。最后,usertrapret调用用户或内核页表跳板页上的userret;这么说的原因是userret中的汇编代码会切换页表。
usertrapret对userret的调用在a0中传递了指向进程用户页表的指针,在a1中传递了指向TRAPFRAME的指针(kernel/trampoline.S:88)。userret将satp切换到进程的用户页表。回忆一下,用户页表除了trampoline页和TRAPFRAME外,没有映射内核的任何东西。再一次,trampoline页在用户页表和内核页表被映射到同一虚拟地址的事实,使得uservec能够在修改satp后继续执行。userret将trapframe所保存的用户a0复制到sscratch来为后面与TAPFRAME的交换做准备。从此开始,userret能使用的数据只有寄存器内容和trapframe中的内容。下一个userret从trapframe中恢复保存的用户寄存器,最后交换一次a0和sscratch来恢复用户a0,为下一个trap保存TRAPFRAME,然后调用sret返回用户空间。
retrieve
4.3 代码:使用系统调用
第二章截止于initcode.S调用exec系统调用(user/initcode.S:11)。我们来看一下,用户的调用是怎样调到内核中exec系统调用的实现的。
用户代码将exec的参数放在a0和a1中,将系统调用号放在a7中。系统调用号与systemcalls数组中的项匹配,systemcalls是一个函数指针表(kernel/syscall.c:108)。ecall指令陷入内核,然后执行uservec,usertrap,然后是syscall,正如我们上面所看到的。
syscall(kernel/syscall.c:133)从trapframe所保存的a7中找到系统调用号,用它索引对应的系统调用。对第一个系统调用来说,a7包含了SYS_exec(kernel/syscall.h:8),转到对系统调用实现函数sys_exec的调用。
当系统调用实现函数返回时,syscall将它的返回值记录在p->trapframe->a0中。这将使得最初用户空间调用的exec()函数返回这个值,因为RISC-V中C语言函数调用的惯例是将返回值放在a0寄存器中。系统调用返回值的惯例是用负值表示错误,0或正数表示成功。如果系统调用号是无效的,syscall打印错误并返回-1。
convention
4.4 代码:系统调用参数
内核中的系统调用实现需要找到用户代码传来的参数。因为用户代码调用的是系统调用包装程序,参数最初位于RISC-V C调用通常放置参数的地方:寄存器中。内核trap代码将用户寄存器保存到当前进程的trapframe中,这样内核代码就能找到它们。函数argint,argaddr和argfd从trapframe中找回第n个系统调用参数,作为整数,指针或是文件描述符。它们都调用argraw来找回保存的对应用户寄存器(kernel/syscall.c:35)。
有些系统调用传递指针作为参数,然后内核必须使用这些指针读写用户内存。举例来说,exec系统调用将一个指向用户空间字符串参数的指针数组传给内核。这些指针带来了两个挑战。首先,用户程序可能有bug或是恶意的,可能传给内核无效的指针,也可能欺骗内核,使用这个指针访问内核内存而不是用户内存。第二,xv6内核页表映射与用户页表不同,所以内核不能使用普通指令来从用户提供的地址加载或存储内容。
内核实现了能够安全地从/向用户提供的地址拷贝数据的函数。fetchstr是一个例子(kernel/syscall.c:25)。exec之类的文件系统调用使用fetchstr来从用户空间取回字符串类型的文件名参数。fetchstr调用copyinstr来完成困难的工作。
copyinstr(kernel/vm.c:406)从虚拟地址srcva向dst拷贝最多max字节的数据,srcva是来自用户页表的虚拟地址。它使用walkaddr(会调用walk)在软件中查找页表,确定srcva对应了物理地址pa0。因为内核将所有物理RAM地址映射在相同的内核虚拟地址上,copyinstr能够直接从pa0往dst拷贝字符串数据。walkaddr(kernel/vm.c:95)检查用户提供的虚拟地址,保证它是这个进程的用户地址空间的一部分,从而避免程序欺骗内核读取别处的内存。copyout与之相似,从内核向用户提供的地址拷贝数据。
implementation retrieve pose
4.5 来自内核空间的trap
根据trap发生时执行的是用户还是内核代码,xv6注册CPU trap寄存器的过程有一定的不同。当内核运行在CPU上时,内核将stvec指向kernelvec的汇编代码(kernel/kernel.S:10)。因为xv6已经在内核中,kernelvec能够使用已经指向内核页表的satp,也能使用已经指向有效内核栈的栈指针。kernelvec保存了所有的寄存器,以便被打断的代码最后能够不受干扰地恢复执行。
kernelvec将寄存器保存在被打断的内核线程的栈上,这是有效的因为寄存器的值属于那个线程。这一点特别重要,因为trap造成线程的切换 - 这时trap实际上将在新线程的栈上返回,让被打断进程中的寄存器安全地留在它的线程上。
kernel在保存好寄存器后,跳转到kerneltrap(kernel/trap.c:134)。kerneltrap用于处理两种类型的trap:设备中断和异常。它调用devintr(kernel/trap.c:177)来检查并处理前者。如果trap不是设备中断,它一定是异常,在xv6内核中出现异常时通常是一个严重错误,内核会调用panic函数并停止执行。
如果kerneltrap是因为计时器中断被调用,而且一个进程的内核线程正在执行(不是调度线程),kerneltrap会调用yield,把运行机会让给其他线程。到某些时候,那些线程将会让步,让我们的线程和它的kerneltrap再次恢复运行。第7章解释了yield函数中发生了什么。
当kerneltrap的工作完成后,它需要返回被trap打断的代码。因为yield函数可能会扰乱保存的sepc和sstatus中保存的之前模式,kerneltrap在刚开始时就保存了它们。它现在恢复这些控制寄存器,然后返回到kernelvec(kernel/kernelvec.S:48)。kernelvec将保存的寄存器从线程栈弹出,然后执行sret。sret会将sepc拷贝到pc,恢复被打断的内核代码。
有一个问题值得认真思考,如果因为定时器中断,kerneltrap调用了yield,trap将如何返回?
当CPU从用户空间进入到内核,xv6把CPU的stvec设置为kernelvec;你可以在usertrap(kernel/trap.c:29)中看到这个过程。在这之前有一个时间窗口,内核仍在执行,但stvec还是uservec,这时,禁止设备中断至关紧要。幸运的是RISC-V开始处理trap时都会禁掉中断,xv6会在stvec设置完成之后才开启它们。
eventually thinking through
4.6 页错误异常
xv6对异常的响应非常无聊:如果用户空间发生了异常,内核会干掉出错的进程。如果内核发生了异常,内核会panic。真实地操作系统会有更多有趣的响应方式。
举个例子,很多内核都使用页错误来实现写时拷贝(copy-on-write,COW)fork。为了解释写时拷贝fork,我们来考虑下第3章中所讲的xv6的fork。fork调用uvmcopy(kernel/vm.c:309)为子进程分配物理内存,然后即将父进程的内存拷贝过去,使子进程与父进程拥有相同的内存内容。如果子进程能与父进程共享物理内存,这个过程将更高效。然而,直接这样实现是不行的,因为当它们向共享的栈或者堆写入时,双方的执行都会被打乱。
父子进程之间可以使用写时拷贝fork共享物理内存,这是有页错误驱动的。当CPU无法将虚拟内存翻译成物理内存时,会产生一个页错误异常。RISC-V有三种类型的页错误:加载页错误(当load指令不能翻译虚拟地址),存储页错误(当store指令不能翻译虚拟地址),指令页错误(当指令的地址无法翻译时)。scause寄存器中的值表示页错误的类型,stval寄存器中包含了无法翻译的地址。
COW fork的基本计划是,父子进程最初共享所有的物理页表,但将它们映射为只读。因此,当子进程或父进程执行store指令时,RISC-V CPU将产生一个页错误异常。在响应这个异常时,内核将包含错误的地址拷贝了一份。它将一份可读可写的拷贝映射到子进程地址空间,另一份可读可写拷贝映射给福进程地址空间。更新完页表后,内核在引起错误的指令处恢复出错的进程。因为内核已经将对应的PTE更新为可写,出错的指令现在就不会报错了。
COW计划在fork中运行良好,因为通常子进程在fork之后会马上调用exec,将它的地址空间替换为新的地址空间。在这种一般情况下,子进程只需经历几个页错误,内核就能避免完整的拷贝。此外,COW是透明的:应用程序不需要改任何东西就能受益。
页表和页错误的结合为一大批有趣的除COW fork以外的用法打开了大门。另一个广泛应用的特性叫做懒分配,包含两部分。首先,当应用调用sbrk,内核增加了地址空间,但将页表中新加的地址标记为不可用。第二,在新加地址的一个页错误中,内核才分配物理内存,然后将它映射到页表中。因为应用程序经常申请比实际需求更多的内存,懒分配非常有效:内核只在应用真的用到内存时才给它分配。像COW fork一样,内核可以实现这项特性,而对应用完全透明。
但另一项利用页错误的广泛使用的特性是从硬盘分页。如果应用需要比可用物理RAM更多的内存,内核能够驱逐一些页:把它们写到硬盘之类的存储设备上,并将他们的PTE标记为不可用。如果应用读或写一个被驱逐的页,CPU将产生页错误。内核能够检查出错的地址。如果地址属于硬盘上的一个页,内核会分配一个物理内存页,从硬盘把页读到内存中,将PTE更新为可用,并指向那块内存,然后恢复应用。为了给那个页腾出空间,内核可能必须驱逐另一个页。这个特性不需要应用做改动,在应用有引用位置时工作良好(即,它们在运行时只使用他们内存的一部分)。
其他结合页表和页错误异常的特性还包括自动扩展栈和内存映射文件。
evict inspect
4.7 真实世界
如果内核内存被映射到每个进程的用户页表中(用合适的权限标记),特殊的跳板页的就不需要了。从用户空间陷入到内核时的页表切换也不再需要。这反过来使得内核中的系统调用实现能够使用当前进程映射的用户空间内存,从而使内核代码能够直接解引用用户指针。很多操作系统都使用这种思想来提高效率。xv6没有采用这种思想,从而减少了随意使用用户指针带来的安全性bug出现的可能,同时也减少了保证用户和内核虚拟地址不重叠带来的复杂性。
inadvertent
相关文章:
第4章 陷入与系统调用
有三种类型的事件会让CPU停止正常执行的指令,强制切换到指定的代码,处理这些事件。一种是系统调用,当用户程序执行ecall指令来让内核为它做一些事。另一种是异常:一个指令(用户或内核)做了一些非法的事&…...
项目基于oshi库快速搭建一个cpu监控面板
后端: <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.github.oshi</groupId><artifactId>oshi-…...
力扣C语言刷题记录 (二)移除元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。 假设 nums 中不等于 val 的元素数量为 k,要通过此题,您需要执行以下操作: 更改…...
多模态大语言模型的对比
简介 文章主要对比了包括 VideoLLaMA 2 、CogVLM2-video 、MiniCPM-V等模型 目前主流的多模态视觉问答大模型,大部分采用视觉编码器、大语言模型、图像到文本特征的投影模块 目录 简介1. VideoLLaMA 21.1 网络结构1.2 STC connector具体的架构 2. MiniCPM-V 2.62.…...
关于最近od机考中--树
题目 树按照层级遍历获取非叶子结点,然后将非叶子结点以后序遍历打印。 eg 图解 只需要将1,3,4,2进行后序遍历打印:既左-右-中方式打印 最后结果如:2,3,4,1。 思路&a…...
基数排序(代码+注释)
#include <stdio.h> #include <stdlib.h>// 获取数组中的最大值 int GetMax(int* a, int n) {int max a[0];for (int i 1; i < n; i) {if (a[i] > max) {max a[i];}}return max; }// 对数组按照某个位数进行计数排序 void CountingSortForRadix(int* a, i…...
NLP 相关知识的构成
自然语言处理(Natural Language Processing, NLP) 自然语言处理相关知识 自然语言处理(Natural Language Processing, NLP)什么是自然语言处理?自然语言处理的构成1. 基本术语1.1 分词(Segmentation&#x…...
算力100问☞第20问:GPU算力的影响因素有哪些?
影响因素1:核心数量 GPU中的计算核心数量是决定其算力的关键因素之一。更多的计算核心意味着可以同时处理更多的数据和任务,从而提高整体的计算效率。例如,GPU里面的计算核心就好像是工厂里的工人。工人数量越多,同时干活儿的也就…...
C语言柔性数组
在C语言中,结构体定义数组指定长度0,sizeof时候不计入占用,实际分配时候占用为准! 也许你从来没有听说过柔性数组的概念,但其确实存在。C99规定:结构中的最后一个元素允许是未知大小的数组,这就…...
TensorFlow与PyTorch对比:哪个更适合初学者?
小贴士: 不必着急,慢慢消化每一部分。有问题就查阅示例,实践是最好的老师!别忘了,选择合适的框架才是王道!💻🔍 本文全面对比了 TensorFlow 与 PyTorch 两大深度学习框架,详细分析了它们的特点、优势及适用场景,帮助初学者做出框架选择。内容包含框架背景、代码示例…...
Windows 11 如何配置node.js
一,官网下载 官网首页 下载最新LTS版本,比较稳定,如果想探索更新的版本去探索新的nodejs功能。 1. 下载完成后,双击运行程序,点击next 2. 勾选接受协议,点击next 3. 选择自己的安装路径(默认是…...
std::reverse_iterator
std::reverse_iterator 是 C 标准库中的一个迭代器适配器(iterator adapter),它允许你以反向顺序遍历容器或序列。这个适配器通过封装一个基础迭代器(通常是正向迭代器)并提供反向的递增()和递减…...
On-Chip-Network之router微架构的物理实现
Low-Power Microarchitecture 自20世纪90年代以来,功耗一直是嵌入式芯片和高性能芯片面临的一个挑战。自2000年代中期以来,它已经成为大多数设计的主要约束。多核解决了功耗问题,由此产生的communication substrate,namely the on…...
学习threejs,使用canvas更新纹理
👨⚕️ 主页: gis分享者 👨⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨⚕️ 收录于专栏:threejs gis工程师 文章目录 一、🍀前言1.1 ☘️Texture 贴图 二、…...
CSS 选择器的优先级
一、基本概念 CSS 选择器的优先级决定了在样式冲突时,哪个样式规则将被应用到 HTML 元素上。通过理解 CSS 选择器的优先级,可以更好地控制网页元素的样式,避免样式冲突。 二、优先级计算规则 1. 内联样式 内联样式具有最高的优先级。 &l…...
如何将python项目导出为docker镜像
如何将python项目导出为docker镜像 前提条件步骤 1: 创建并准备 Python 项目步骤 2: 创建 `setup.py`步骤 3: 打包项目步骤 4: 创建 Dockerfile步骤 5: 构建 Docker 镜像步骤 6: 运行 Docker 容器步骤 7: 保存修改并继续开发总结要将修改后的Python代码导出为 .tar.gz 格式,并…...
微信 创建小程序码-有数量限制
获取小程序码:小程序码为圆图,有数量限制。 目录 文档 接口地址 功能描述 注意事项 请求参数 对接 获取小程序码 调用获取 小程序码示例 总结 文档 接口地址 https://api.weixin.qq.com/wxa/getwxacode?access_tokenaccess_token 功能描述 …...
桶排序(代码+注释)
#include <stdio.h> #include <stdlib.h>// 定义桶的结构 typedef struct Bucket {int* data; // 动态数组int count; // 当前存储的元素个数int capacity; // 桶的容量 } Bucket;// 初始化桶 void InitBucket(Bucket* bucket) {bucket->capacity 10; // 初…...
Python从入门到入狱
Python是从入门到入狱?这个充满调侃意味的说法在程序员圈子里流传甚广。表面看,它似乎是在嘲笑这门语言从简单易学到深陷麻烦的巨大反差,实际上却隐藏着很多值得深思的问题。要解读这个话题,得从Python的特点、使用场景以及潜在风…...
图像模糊处理
图像模糊处理 C语言实现C实现Java实现Python实现 💐The Begin💐点点关注,收藏不迷路💐 给定n行m列的图像各像素点的灰度值,要求用如下方法对其进行模糊化处理: 1 四周最外侧的像素点灰度值不变;…...
全面UI组件库Telerik 2024 Q4全新发布——官方宣布支持.NET 9
Telerik DevCraft包含一个完整的产品栈来构建您下一个Web、移动和桌面应用程序。它使用HTML和每个.NET平台的UI库,加快开发速度。Telerik DevCraft提供最完整的工具箱,用于构建现代和面向未来的业务应用程序,目前提供UI for ASP.NET MVC、Ken…...
请求响应:常见参数接收及封装(Json参数及路径参数)
Json参数 Json格式的数据具有轻量级、易于阅读和编写、易于解析等诸多优点。在前后端交互时,大部分情况下请求体中的数据会以JSON格式进行传递。前端的请求在请求体中携带了Json格式数据,后端程序需要对其进行解析并封装使用,而接收Json参数…...
Doge东哥wordpress主题
Doge东哥wordpress主题是一款专为中小型企业设计的WordPress外贸网站模板,它以其现代、专业且用户友好的界面,为企业提供了一个展示产品和服务的理想平台。以下是对该模板的详细描述: 首页设计概览 首页的设计简洁而不失大气,顶…...
深度学习常用指标
1. 混淆矩阵(误差矩阵) 2. 准确率(overall accuracy) 代表了所有预测正确的样本占所有预测样本总数的比例 这里分类正确代表了正样本被正确分类为正样本,负样本被正确分类为负样本 3. 平均精度(average…...
AMR移动机器人赋能制造业仓储自动化升级
在当今制造业的激烈竞争中,智能化、数字化已成为企业转型升级的关键路径。一家制造业巨头,凭借其庞大的生产体系和多个仓库资源,正以前所未有的决心和行动力,在制造业智能化浪潮中勇立潮头,开启了降本增效的新篇章。这…...
显卡(Graphics Processing Unit,GPU)比特币挖矿
1. 比特币挖矿基本原理 比特币挖矿是通过参与比特币网络的共识机制——工作量证明(Proof of Work, PoW) 来完成的。具体来说,矿工通过不断尝试不同的哈希值,以解决一个难度逐渐增大的数学问题,从而验证交易并获得比特…...
11.7【miniob】【debug】
这里的vector是实际值,而relation是指针,所以要解引用,*$1,并在最后调用其析构函数 emplace_back 和 push_back 都是用于在容器(如 std::vector)的末尾添加元素的方法,但它们的工作方式有所不同…...
力扣第89题 格雷编码
题目描述 格雷编码序列是一个二进制数字序列,其中的每两个相邻的数字只有一个二进制位不同。给定一个整数 n,表示格雷编码的位数,要求返回 n 位的格雷编码序列。 示例 1 输入: n 2输出: [0, 1, 3, 2]解释&#x…...
ros sensor_msgs::Imu详细介绍 Eigen::Vector3d 详细介绍
1.ros sensor_msgs::Imu详细介绍 sensor_msgs::Imu 是 ROS(Robot Operating System)中用于表示惯性测量单元(IMU)数据的消息类型。IMU 是一种传感器,通常用于测量物体的线性加速度、角速度和方向信息。以下是 sensor_…...
【ArcGIS微课1000例】0133:二维建筑物依据高度生成三维模型
拓展阅读:【ArcGIS Pro微课1000例】0032:创建具有指定高程Z值的矢量数据 文章目录 一、二维面要素拉伸实现三维显示二、依据高度实现要素转3D一、二维面要素拉伸实现三维显示 打开ArcScene软件,加载实验配套数据0133.rar中的建筑物.shp数据,如下图: 数据属性表中的Z为建筑…...
虚拟内存的意义
1.什么是虚拟内存 虚拟内存的基本原理是将物理内存与磁盘空间组合使用,将正在执行的程序的部分数据和代码加载到物理内存中,而不是全部加载。当程序需要访问未加载到内存的部分时,操作系统会将相关数据从磁盘中加载到内存中 2.为了解决什么…...
h5 sqlite 操作封装
参考文档 错误码 // 数据库名称 const namesjk "sl" // 存储路径 const path _doc/${name}.db/** 基本操作* 查询数据库连接状态 isOpenDatabase * 无参数* 返回 true false* * * 关闭数据库 closeDatabase* 无参数* Promise 成功/失败* * * …...
Git 详解
Git 详解 Git 是一个分布式版本控制系统,用于高效地管理项目代码的版本历史。它是目前最流行的版本控制工具之一,广泛应用于软件开发领域。Git 的分布式架构允许开发者在本地进行代码的版本管理,并与远程仓库同步,实现团队协作。…...
Redis设计与实现第17章 -- 集群 总结3(ASK错误、复制与故障转移、消息)
17.5 ASK错误 在进行重新分片期间,源节点向目标节点迁移一个槽的过程中,可能会出现这样一种情况:属于被迁移槽的一部分键值对保存在源节点里面,而另一部分键值对则保存在目标节点里面。当客户端向源节点发送一个与数据库键有关的…...
支持向量机(SVM)的解析与应用:从封闭解到时代演变 (中英双语)
中文版 支持向量机(SVM)的解析与应用:从封闭解到时代演变 什么是支持向量机(SVM)? 支持向量机(Support Vector Machine, SVM)是一种经典的监督学习算法,用于解决分类和…...
Linux 密码学的基本知识与应用技术
一、基本知识 (一)加密算法 • 对称加密算法 • 原理:对称加密使用相同的密钥进行加密和解密。例如,在Linux中常用的AES(高级加密标准)算法,发送方和接收方都需要持有相同的密钥。假设要加密…...
【力扣】2094.找出3为偶数
思路 方法一:使用Set集合 1.首先是三层for循环,遍历,并且遇到不满足的情况,便跳过,继续计算。不如前导为0,以及遍历同一个数组下标的情况 2.使用Set集合来确保答案是唯一的,使用桶来标记也是可以的 3.但是…...
【信息系统项目管理师】【综合知识】【备考知识点】第十四章 项目沟通管理
【移动端浏览】☞【信息系统项目管理师】第十四章 项目沟通管理 第十四章 项目沟通管理 (项目沟通管理)定义 项目沟通管理是确保及时、正确地产生、收集、分发、存储和最终处理项目信息所需的过程。 (项目沟通管理)组成部分 (…...
CTFshow黑盒测试刷题
web380 先扫目录 打开 报错了 先用伪协议去查看源码 之前扫到有flag.php 访问一下 就得到flag了 web381 查看一下源码 点击第三个css 藏在目录里面 web382 跟上题一样 不过访问这个页面是一个登录框 试一下弱口令 最后是admin admin888 就进去了 web383 进入这个后台 …...
抖音矩阵系统快速部署指南/抖音矩阵系统源码分发,短视频矩阵账号管理系统开发部署—
抖音矩阵系统的源码分发与短视频账号管理平台的开发部署,要求通过对接官方API来实现功能的拓展。当前开发的账号矩阵管理系统专注于提供一键式管理多个账户的能力,支持定时发布内容、自动化关键词生成以实现搜索引擎优化(SEO)和霸…...
windows文件下换行, linux上不换行 解决CR换行符替换为LF notepad++
html文件是用回车换行的,在windows电脑上,显示正常。 文件上传到linux服务器后,文件不换行了。只有一行。而且相关js插件也没法正常运行。 用notepad查看,显示尾部换行符,是CR,这就是原因。CR是不被识别的。…...
服务器数据恢复—硬盘掉线导致热备盘同步失败的RAID5阵列数据恢复案例
服务器存储数据恢复环境: 华为S5300存储中有12块FC硬盘,其中11块硬盘作为数据盘组建了一组RAID5阵列,剩下的1块硬盘作为热备盘使用。基于RAID的LUN分配给linux操作系统使用,存放的数据主要是Oracle数据库。 服务器存储故障&#…...
活着就好20411205
5号亲爱的朋友们,大家早上好!🌞 今天是5号,星期四,2024年12月的第五天,同时也是第49周的第四天,农历甲辰[龙]年十一月初一日。在这晨曦初露的美好时刻,愿第一缕柔和的阳光悄悄探进你…...
JDK8 下载与安装
下载安装包 官网下载 官网 找到适合的版本: 网盘下载 网盘链接 提取码: 6666 下载得到的安装包: 安装步骤 双击安装包开始安装. 安装路径不要有中文或者特殊符号如空格等. 更改安装路径: 跳出一个页面, 安装公共 JRE: 安装完成: 安装目录: 安装的公共 JRE: JDK 里面的 JR…...
基于MATLAB的信号处理工具:信号分析器
信号(或时间序列)是与特定时间相关的一系列数字或测量值,不同的行业和学科将这一与时间相关的数字序列称为信号或时间序列。生物医学或电气工程师会将其称为信号,而统计学家或金融定量分析师会使用时间序列这一术语。例如…...
Docker Compose 和 Kubernetes 之间的区别?
一、简介🎀 1.1 Docker Compose Docker Compose 是 Docker 官方的开源项目,负责实现对 Docker 容器集群的快速编排,可以管理多个 Docker 容器组成一个应用。你只需定义一个 YAML 格式的配置文件 docker-compose.yml ,即可创建并…...
uniapp远程摄像头流界面上显示
用到的插件:dplayer、hls dplayer官网:dplayer 远程摄像头视频流格式:m3u8 可以用来测试的视频流(有的用不了,多试几个,找可以用的):m3u8测试视频 安装hls,任选其一 npm…...
写译 Essay | Translation
单词 参考上篇 总结写译热点单词 | 50篇文章整理 | 手敲自用-CSDN博客 文化类词汇: 包括传统节日及相关活动,如春节(Spring Festival)、中秋节(Mid-Autumn Festival)等。 涵盖中国特色艺术和工艺品,如京剧(Peking opera)、中国画(traditi…...
知乎大数据开发面试题及参考答案
Java 两个线程之间是怎么通信的,属于哪种机制? 在 Java 中,线程间通信主要有以下几种方式: 共享变量:线程可以通过访问共享变量来进行通信。例如,一个线程修改一个共享的成员变量,另一个线程读取这个变量的值。但是这种方式需要注意线程安全问题。如果多个线程同时访问和…...
C# 绘制GDI红绿灯控件
C# 绘制GDI红绿灯控件 using System; using System.Windows.Forms; using System.Drawing;public class TrafficLightControl : Control {protected override void OnPaint(PaintEventArgs e){base.OnPaint(e);Graphics g e.Graphics;g.SmoothingMode System.Drawing.Drawin…...