FREERTOS任务调度和切换

我们已经学会了 FreeRTOS 的任务创建和删除,挂起和恢复等基本操作,并且也学习了分析FreeRTOS 源码所必须掌握的知识:列表和列表项。但是任务究竟如何被创建、删除、挂起和恢复的?系统是怎么启动的等等这些我们还不了解,一个操作系统最核心的内容就是多任务管理,所以我们非常有必要去学习一下 FreeRTOS 的任务创建、删除、挂起、恢复和系统启动等,这样才能对 FreeRTOS 有一个更深入的了解。

本章和下一章要讲解的内容和 Cortex-M 处理器的内核架构联系非常紧密!阅读本章必须先对 Cortex-M 处理器的架构有一定的了解,在学习本章的时候一定要配合《权威指南》来学习,

推荐大家仔细阅读《权威指南》中的如下章节:

1、第 3 章 技术综述,通过阅读本章可以对 Cortex-M 处理器的架构有一个大体的了解。

2、第 4 章 架构,强烈建议仔细阅读本章内容,尤其是要理解其中讲解到的各个寄存器。

3、第 5 章 指令集,本章和下一章的内容会涉及到一些有关 ARM 的汇编指令,在阅读的

时遇到不懂的指令可以查阅《权威指南》的第 5 章中相关指令的讲解。

4、第 7 章 异常和中断,大概了解一下 。

5、第 8 章 深入了解异常处理,强烈建议仔细阅读!

6、第 10 OS 支持特性, 强烈建议仔细阅读!

《权威指南》中的其他章节大家依据个人爱好来阅读,由于《权威指南》讲解的内容非常的“底层”,所以看起来可能会感觉晦涩难懂,如果看不懂的话不要着急,看不懂的地方就跳过,先对 Cortex-M 的处理器有一个大概的了解就行了。

任务调度器开启

前面的所有例程中我们都是在 main()函数中先创建一个开始任务 start_task,后面紧接着调用函数 vTaskStartScheduler()。这个函数的功能就是开启任务调度器的,这个函数在文件tasks.c中有定义,具体可自行查阅。

内部实现流程大致如下:

(1)、创建空闲任务,如果使用静态内存的话使用函数 xTaskCreateStatic()来创建空闲任务,优先级为 tskIDLE_PRIORITY,宏 tskIDLE_PRIORITY 0,也就是说空闲任务的优先级为最低。

(2)、如果使用软件定时器的话还需要通过函数 xTimerCreateTimerTask()来创建定时器服务任务。定时器服务任务的具体创建过程是在函数 xTimerCreateTimerTask()中完成的,这个函数很简单,大家就自行查阅一下。

(3)、关闭中断,在 SVC 中断服务函数 vPortSVCHandler()中会打开中断。

(4)、变量 xSchedulerRunning 设置为 pdTRUE,表示调度器开始运行。

(5)、当宏 configGENERATE_RUN_TIME_STATS 1 的时候说明使能时间统计功能,此时需要用户实现宏 portCONFIGURE_TIMER_FOR_RUN_TIME_STATS,此宏用来配置一个定时器/计数器。

(6)、调用函数 xPortStartScheduler()来初始化跟调度器启动有关的硬件,比如滴答定时器、 FPU 单元和 PendSV 中断等等。

内核相关硬件初始化函数分析

关于上面的最后一点,内核相关硬件初始化函数 xPortStartScheduler()分析如下:

FreeRTOS 系统时钟是由滴答定时器来提供的,而且任务切换也会用到 PendSV 中断,这些硬件的初始化由函数 xPortStartScheduler()来完成,缩减后的函数代码如下:

(1)、设置 PendSV 的中断优先级,为最低优先级。

(2)、设置滴答定时器的中断优先级,为最低优先级。

(3)、调用函数 vPortSetupTimerInterrupt()来设置滴答定时器的定时周期,并且使能滴答定时

器的中断,函数比较简单,大家自行查阅分析。

(4)、初始化临界区嵌套计数器。

(5)、调用函数 prvStartFirstTask()开启第一个任务。

有一个问题,那就是,滴答定时器的定时周期以及中断开启不需要我们在移植初始化的时候配置吗?待解决。

启动第一个任务

经过上面的操作以后我们就可以启动第一个任务了,函数 prvStartFirstTask()用于启动第一个任务,这是一个汇编函数,函数源码如下:

(1)、将 0XE000ED08 保存在寄存器 R0 中。一般来说向量表应该是从起始地(0X00000000)开始存储的,不过,有些应用可能需要在运行时修改或重定义向量表,Cortex-M 处理器为此提供了一个叫做向量表重定位的特性。向量表重定位特性提供了一个名为向量表偏移寄存器(VTOR)的可编程寄存器。VTOR 寄存器的地址就是 0XE000ED08,通过这个寄存器可以重新定义向量表,比如在 STM32F103 ST 官方库中会通过函数 SystemInit()来设置VTOR 寄存器,代码如下:

SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; //VTOR=0x08000000+0X00

通过上面一行代码就将向量表开始地址重新定义到了 0X08000000,向量表的起始地址存储的就是 MSP 初始值。关于向量表和向量表重定位的详细内容请参阅《权威指南》的“第 7 章 异常和中断”的 7.5 小节

(2)、读取 R0 中存储的地址处的数据并将其保存在 R0 寄存器,也就是读取寄存器 VTOR 中的值,并将其保存在 R0 寄存器中。这一行代码执行完就以后 R0 的值应该为0X08000000

(3)、读取 R0 中存储的地址处的数据并将其保存在 R0 寄存器,也就是读取地址0X08000000处存储的数据,并将其保存在 R0 寄存器中。我们知道向量表的起始地址保存的就是主栈指针MSP 的初始值,这一行代码执行完以后寄存器 R0 就存储 MSP 的初始值。现在来看(1)(2)(3)这三步起始就是为了获取 MSP 的初始值而已!

(4)、复位 MSPR0 中保存了 MSP 的初始值,将其赋值给 MSP 就相当于复位 MSP

(5)(6)、使能中断,关于这两个指令的详细内容请参考《权威指南》的“第 4 章 架构”的第 4.2.3 小节

(7)(8)、数据同步和指令同步屏障,这两个指令的详细内容请参考《权威指南》的“第 5 章 指令集”的 5.6.13 小节

(9),调用 SVC 指令触发 SVC 中断,SVC 也叫做请求管理调用,SVC PendSV 异常对于OS 的设计来说非常重要。SVC 异常由 SVC 指令触发。关于 SVC 的详细内容请参考《权威指南》的“第 10 OS 支持特性”的 10.3 小节。在 FreeRTOS中仅仅使用 SVC 异常来启动第一个任务,后面的程序中就再也用不到 SVC 了。

SVC中断服务函数

在函数 prvStartFirstTask()中通过调用 SVC 指令触发了 SVC 中断,而第一个任务的启动就是在 SVC 中断服务函数中完成的,SVC 中断服务函数应该为 SVC_Handler(),但是FreeRTOSConfig.h 中通过#define 的方式重新定义为了 xPortPendSVHandler(),如下:

#define vPortSVCHandler SVC_Handler

函数 vPortSVCHandler()在文件 port.c 中定义,这个函数也是用汇编写的,函数源码如下:

详细过程参考视频,此处不赘述。

RTOS 系统的核心是任务管理,而任务管理的核心是任务切换,任务切换决定了任务的执行顺序,任务切换效率的高低也决定了一款系统的性能,尤其是对于实时操作系统。

任务切换场合

有两种场合会进行任务切换

● 可以执行一个系统调用

● 系统滴答定时器(SysTick)中断。

执行系统调用 

执行系统调用就是执行 FreeRTOS系统提供的相关API函数,比如任务切换函数 taskYIELD()FreeRTOS 有些 API 函数也会调用函数 taskYIELD(),这些 API 函数都会导致任务切换,这些 API 函数和任务切换函数 taskYIELD()都统称为系统调用。函数 taskYIELD()其实就是个宏,在文件 task.h中有如下定义:

#define taskYIELD()  portYIELD()

函数 portYIELD()也是个宏,在文件 portmacro.h 中有如下定义:

通过向中断控制和状态寄存器 ICSR bit28 写入 1 挂起 PendSV 来启动 PendSV 中断。 这样就可以在 PendSV 中断服务函数中进行任务切换了。

中断级的任务切换函数为 portYIELD_FROM_ISR(),定义如下:

可以看出 portYIELD_FROM_ISR()最终也是通过调用函数 portYIELD()来完成任务切换的。

系统滴答定时器(SysTick)中断

FreeRTOS 中滴答定时器(SysTick)中断服务函数中也会进行任务切换,滴答定时器中断服务函数如下:

在滴答定时器中断服务函数中调用了 FreeRTOS API 函数 xPortSysTickHandler(),此函数源码如下:

(1)、关闭中断

(2)、通过向中断控制和状态寄存器 ICSR bit28 写入 1 挂起 PendSV 来启动 PendSV 中断。这样就可以在 PendSV 中断服务函数中进行任务切换了。

(3)、打开中断。

PendSV异常

PendSV(可挂起的系统调用)异常对 OS 操作非常重要,其优先级可以通过编程设置。可以通过将中断控制和状态寄存器 ICSR bit28,也就是 PendSV 的挂起位置 1 来触发PendSV 中断。与 SVC 异常不同,它是不精确的,因此它的挂起状态可在更高优先级异常处理内设置,且会在高优先级处理完成后执行。利用该特性,若将 PendSV 设置为最低的异常优先级,可以让 PendSV 异常处理在所有其他中断处理完成后执行,这对于上下文切换非常有用,也是各种 OS 设计中的关键。

中断的优先级永远高于任务的优先级,用中断最低优先级的中断来切换任务,既不会影响高优先级的中断的执行,又能实现任务的切换。

在具有嵌入式 OS 的典型系统中,处理时间被划分为了多个时间片。若系统中只有两个任 务,这两个任务会交替执行,如下图所示:

OS 中,任务调度器决定是否应该执行上下文切换,如上图中任务切换都是由 SysTick中断执行,每次它都会决定切换到一个不同的任务中。

若中断请求(IRQ)SysTick 异常前产生,则 SysTick 异常可能会抢占 IRQ 的处理,在这种情况下,OS 不应该执行上下文切换,否则中断请求 IRQ 处理就会被延迟,而且在真实系统中延迟时间还往往不可预知——任何有一丁点实时要求的系统都决不能容忍这种事。对于 Cortex-M3 Cortex-M4 处理器,当存在活跃的异常服务时,设计默认不允许返回到线程模式,若存在活跃中断服务,且 OS 试图返回到线程模式,则将触发用法 fault,如下图 所示。

在一些 OS 设计中,要解决这个问题,可以在运行中断服务时不执行上下文切换,此时可以检查栈帧中的压栈 xPSR NVIC 中的中断活跃状态寄存器。不过,系统的性能可能会受到影响,特别时当中断源在 SysTick 中断前后持续产生请求时,这样上下文切换可能就没有执行的机会了。

为了解决这个问题,PendSV 异常将上下文切换请求延迟到所有其他 IRQ 处理都已经完成后,此时需要将 PendSV 设置为最低优先级。若 OS 需要执行上下文切换,他会设置PendSV 的挂起状态,并在 PendSV 异常内执行上下文切换。如下图所示:

上图中事件的流水账记录如下:

(1) 任务 A 呼叫 SVC 来请求任务切换(例如,等待某些工作完成)

(2) OS 接收到请求,做好上下文切换的准备,并且 pend 一个 PendSV 异常。

(3) CPU 退出 SVC 后,它立即进入 PendSV,从而执行上下文切换。

(4) PendSV 执行完毕后,将返回到任务 B,同时进入线程模式。

(5) 发生了一个中断,并且中断服务程序开始执行。

(6) ISR 执行过程中,发生 SysTick 异常,并且抢占了该 ISR

(7) OS 执行必要的操作,然后 pend PendSV 异常以作好上下文切换的准备。

(8) SysTick 退出后,回到先前被抢占的 ISR 中, ISR 继续执行

(9) ISR 执行完毕并退出后, PendSV 服务例程开始执行,并且在里面执行上下文切换。

(10) PendSV 执行完毕后,回到任务 A,同时系统再次进入线程模式。

讲解 PendSV 异常的原因就是让大家知道,FreeRTOS 系统的任务切换最终都是在 PendSV 中断服务函数中完成的,UCOS 也是在 PendSV 中断中完成任务切换的。

PendSV中断服务函数

前面说了 FreeRTOS 任务切换的具体过程是在 PendSV 中断服务函数中完成的,接着我们就来学习PendSV 的中断服务函数,看看任务切换过程究竟是怎么进行的。PendSV 中断服务函数本应该为 PendSV_Handler(),但是 FreeRTOS 使用#define 重定义了,如下:

#define xPortPendSVHandler PendSV_Handler

该函数源码如下:

(1)、读取进程栈指针,保存在寄存器 R0 里面。

(2)(3),获取当前任务的任务控制块,并将任务控制块的地址保存在寄存器 R2 里面。

(4)、保存 r4~r11 R14 这几个寄存器的值。

(5)、将寄存器 R0 的值写入到寄存器 R2 所保存的地址中去,也就是将新的栈顶保存在任务控制块的第一个字段中。此时的寄存器 R0 保存着最新的堆栈栈顶指针值,所以要将这个最新的栈顶指针写入到当前任务的任务控制块第一个字段,而经过(2)(3)已经获取到了任务控制块,并将任务控制块的首地址写如到了寄存器 R2 中。

(6)、将寄存器 R3 R14 的值临时压栈,寄存器 R3 中保存了当前任务的任务控制块,而接下来要调用函数 vTaskSwitchContext(),为了防止 R3 R14 的值被改写,所以这里临时将 R3R14 的值先压栈。

(7)(8)、关闭中断,进入临界区

(9)、调用函数 vTaskSwitchContext(),此函数用来获取下一个要运行的任务,并将 pxCurrentTCB 更新为这个要运行的任务。

(10)(11)、打开中断,退出临界区。

(12)、刚刚保存的寄存器 R3 R14 的值出栈,恢复寄存器 R3 R14 的值。注意,经过(12)步,此时 pxCurrentTCB 的值已经改变了,所以读取 R3 所保存的地址处的数据就会发现其值改变了,成为了下一个要运行的任务的任务控制块。

(13)(14)、获取新的要运行的任务的任务堆栈栈顶,并将栈顶保存在寄存器 R0 中。

(15)R4~R11,R14 出栈,也就是即将运行的任务的现场。

(16)、更新进程栈指针 PSP 的值。

(17)、执行此行代码以后硬件自动恢复寄存器 R0~R3R12LRPC xPSR 的值,确定

异常返回以后应该进入处理器模式还是进程模式,使用主栈指针(MSP)还是进程栈指针(PSP)

很明显这里会进入进程模式,并且使用进程栈指针(PSP),寄存器 PC 值会被恢复为即将运行的任务的任务函数,新的任务开始运行!至此,任务切换成功。

总的来说,其实就是保存之前的任务现场,然后恢复下一个任务的现场。

查找下一个要运行的任务

PendSV 中断服务程序中有调用函数 vTaskSwitchContext()来获取下一个要运行的任务, 也就是查找已经就绪了的优先级最高的任务。

该函数内部实现过程如下:

(1)、如果调度器挂起那就不能进行任务切换。

(2)、调用函数 taskSELECT_HIGHEST_PRIORITY_TASK()获取下一个要运行的任务。

taskSELECT_HIGHEST_PRIORITY_TASK()本质上是一个宏,在 tasks.c 中有定义。

FreeRTOS 中查找下一个要运行的任务有两种方法:一个是通用的方法,另外一个就是使用硬件的方法,这个在我们讲解 FreeRTOSCofnig.h 文件的时候就提到过了,至于选择哪种方法通过宏configUSE_PORT_OPTIMISED_TASK_SELECTION 来决定的。当这个宏为 1 的时候就使用硬件的方法,否则的话就是使用通用的方法,我们来看一下这两个方法的区别。

通用方法

顾名思义,就是所有的处理器都可以用的方法,通用方法是完全通过 C 语言来实现的,肯定适用于不同的芯片和平台,而且对于任务数量没有限制,但是效率肯定相对于使用硬件方法的要低很多。

硬件方法

硬件方法就是使用处理器自带的硬件指令来实现的,比如 Cortex-M 处理器就带有的计算前 导 0 个数指令:CLZ

如果使用硬件方法的话最多只能有 32 个优先级。

可以看出硬件方法借助一个指令就可以快速的获取处于就绪态的最高优先级,但是会限制任务的优先级数,比如 STM32 只能有 32 个优先级,不过 32 个优先级已经完全够用了。要知道FreeRTOS 是支持时间片的,每个优先级可以支持无限多个任务。

FreeRTOS 时间片调度

前面多次提到 FreeRTOS 支持多个任务同时拥有一个优先级,这些任务的调度是一个值得考虑的问题,不过这不是我们要考虑的。在 FreeRTOS 中允许一个任务运行一个时间片(一个时钟节拍的长度)后让出 CPU 的使用权,让拥有同优先级的下一个任务运行,至于下一个要运行哪个任务?在上小节里面已经分析过了,FreeRTOS 中的这种调度方法就是时间片调度。下图展示了运行在同一优先级下的执行时间图,在优先级 N 下有 3 个就绪的任务。

(1)任务3正在运行。

(2)这时一个时钟节拍中断(滴答定时器中断)发生,任务3的时间片用完,但是任务3

没有执行完。

(3)FreeRTOS 将任务切换到任务1,任务1是优先级 N 下的下一个就绪任务。

(4)任务1连续运行至时间片用完。

(5)任务3再次获取到 CPU 使用权,接着运行。

(6)任务3运行完成,调用任务切换函数 portYIELD()强行进行任务切换放弃剩余的时间片, 从而使优先级N下的下一个就绪的任务运行。

(7)FreeRTOS 切换到任务1

(8)任务1执行完其时间片。

要使用时间片调度的话宏 configUSE_PREEMPTION 和宏 configUSE_TIME_SLICING 必须为 1。时间片的长度由宏 configTICK_RATE_HZ 来确定,一个时间片的长度就是滴答定时器的中断周期,比如本教程中 configTICK_RATE_HZ 1000,那么一个时间片的长度就是 1ms。时间片调度发生在滴答定时器的中断服务函数中,前面讲解滴答定时器中断服务函数的时候说了在中断服务函数 SysTick_Handler()中会调用 FreeRTOS API 函数xPortSysTickHandler(),而函数 xPortSysTickHandler() 会 引 发 任 务 调 度 , 但 是 这个 任 务 调 度 是 有 条 件 的 ,函 数xPortSysTickHandler()如下:

上述代码中红色部分表明只有函数 xTaskIncrementTick()的返回值不为pdFALSE的时候就会进行任务调度!查看函数 xTaskIncrementTick()会发现有如下条件编译语句:

(1)、当宏 configUSE_PREEMPTION 和宏 configUSE_PREEMPTION 都为 1 的时候下面的代码才会编译。所以要想使用时间片调度的话这这两个宏都必须为 1,缺一不可!

(2)、判断当前任务所对应的优先级下是否还有其他的任务。

(3)、如果当前任务所对应的任务优先级下还有其他的任务那么就返回 pdTRUE

从上面的代码可以看出,如果当前任务所对应的优先级下有其他的任务存在,那么函数xTaskIncrementTick() 就会返回pdTURE ,由于函数返回值为 pdTURE因此函数xPortSysTickHandler()就会进行一次任务切换。

也就是说,时间片调度只有在同一优先级下还有其他任务时才会进行任务切换。

遗留问题:抢占式调度是怎么实现的?

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

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

相关文章

windows使用docker运行TP6使用swoole内置http服务

1,下载docker-Windows客户端 下载地址:https://www.docker.com/products/docker-desktop docker --version #查看docker版本 docker-compose --version #查看docker-compose版本 2,安装环境 使用一键安装包:https://gitee.com/yes…

汇总全网免费API,持续更新(新闻api、每日一言api、音乐。。。)

Public&FreeAPI 网址:apis.whyta.cn (推荐) UomgAPI 网址:https://api.uomg.com 教书先生 网址:https://api.oioweb.cn/ 山海API https://api.shserve.cn/ 云析API铺 https://api.a20safe.com/ 韩小韩…

深度学习pytorch——基本数据类型创建Tensor(持续更新)

声明:本深度学习笔记基于课时18 索引与切片-1_哔哩哔哩_bilibili学习而来 All is about Tensor 定义:Tensors are simply mathematical objects that can be used to describe physical properties, just like scalars and vectors. In fact tensors a…

day6 3/18

2.试编程: 封装一个动物的基类,类中有私有成员:姓名,颜色,指针成员年纪 再封装一个狗这样类,共有继承于动物类,自己拓展的私有成员有:指针成员:腿的个数(整…

JAVA实战开源项目:天然气工程业务管理系统(Vue+SpringBoot)

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、使用角色3.1 施工人员3.2 管理员 四、数据库设计4.1 用户表4.2 分公司表4.3 角色表4.4 数据字典表4.5 工程项目表4.6 使用材料表4.7 使用材料领用表4.8 整体E-R图 五、系统展示六、核心代码6.1 查询工程项目6.2 工程物资…

HackTheBox WifineticTwo

靶机信息系统LinuxIP/难度Medium状态Active/Reason 4地址https://app.hackthebox.com/machines/WifineticTwo 端口扫描 ┌──(st4rry🚀Kali)-[/mnt/e/htb/WifineticTwo] └─\ ✨ nmap -p22,8080 -sC -sV 10.129.41.69 -oN cv Starting Nmap 7.94 ( https://nma…

【Django开发】0到1美多商城项目md教程第2篇:展示用户注册页面,1. 创建用户模块子应用【附代码文档】

美多商城完整教程(附代码资料)主要内容讲述:欢迎来到美多商城!,项目准备。展示用户注册页面,创建用户模块子应用。用户注册业务实现,用户注册前端逻辑。图形验证码,图形验证码接口设…

【C语言进阶篇】C语言内存函数

目录 1.memcpy函数及其模拟实现 1.1 memcpy函数的使用 1.2 memcpy函数的模拟实现 2.memmove函数及其模拟实现 2.1 memmove函数的使用 2.2 memmove函数的模拟实现 3.memset函数 4.memcmp函数 1.memcpy函数及其模拟实现 1.1 memcpy函数的使用 memcpy函数是用来拷贝内存的函数&…

mysql 索引(为什么选择B+ Tree?)

索引实现原理 索引:排好序的数据结构 优点:降低I/O成本,CPU的资源消耗(数据持久化在磁盘中,每次查询都得与磁盘交互) 缺点:更新表效率变慢,(更新表数据,还要…

数据的响应式:实现动态数据驱动的技巧

🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 🍚 蓝桥云课签约作者、上架课程《Vue.js 和 E…

洛谷P1100 高低位交换

#先看题目 题目描述 给出一个小于 的非负整数。这个数可以用一个 32 位的二进制数表示(不足 32 位用 0 补足)。我们称这个二进制数的前 16 位为“高位”,后 16 位为“低位”。将它的高低位交换,我们可以得到一个新的数。试问这…

​关于robotframework,app,appium的xpath定位问题及常用方法​

关于类似的帖子好像很多,但是没有找到具体能帮我解决问题的办法。还是自己深究了好久才基本知道app上面的xpath定位和web上的不同点: 先放一个图: A,先说说不用xpath的场景,一般是用于存在id或者name。可能没有看到na…

UI 学习 三 可访问性 UX

设计、交流和实现不同领域内容的易访问性决策,涉及到一系列考虑因素,以达到更容易访问的产品体验。 Material使用的框架借鉴了WCAG标准和行业最佳实践,以帮助任何人预测、计划、记录和实现可访问体验。 下面描述的三个阶段有助于将可视化UI…

Verilog——信号类型

Verilog HDL 的信号类型有很多种,主要包括两种数据类型:线网类型 (net type) 和寄存器类型 ( reg type )。在进行工程设计的过程中也只会使用到这两个类型的信号。 4.1 信号位宽 定义信号类型的同时,必须定义好信号…

使用决策树模型绘制混淆矩阵、ROC曲线、特征变量重要性排序图

大家好,我是带我去滑雪! 决策树模型可以处理各种类型的特征(连续型、离散型、类别型等),不需要对特征进行过多的预处理工作,因此非常适合初步探索数据。通过绘制混淆矩阵、ROC曲线和特征变量重要性排序图&a…

基于java+springboot+vue实现的高校自习室预约系统(文末源码+Lw+ppt)23-428

摘 要 高校自习室预约系统采用B/S架构,数据库是MySQL。网站的搭建与开发采用了先进的java进行编写,使用了springboot框架。该系统从两个对象:由管理员和学生来对系统进行设计构建。主要功能包括:个人信息修改,对用户…

DNA序列修正——HashMap应用

题目链接:1.DNA序列修正 - 蓝桥云课 (lanqiao.cn) 利用HashMap的特性,将字母匹配转换成数字匹配 A T 0 1 C G 1 2 3 另外,Java中没有交换(swap)函数,需要自己进行编写。 程序代码: pac…

【电路笔记】-MOSFET作为开关

MOSFET 作为开关 文章目录 MOSFET 作为开关1、概述2、MOSFET特性曲线2.1 截住区域2.2 饱和区域3、MOSFET作为开关的示例4、功率MOSFET电机控制5、P沟道MOSFET作为开关6、互补MOSFET作为开关电机控制器当 MOSFET 在截止区和饱和区之间工作时,MOSFET 是非常好的电子开关,用于控…

mybatis项目中配置sql提示

2023版的idea好像内置了这个功能。 第一步: 第二步:第一步完成后user会爆红,这时我们需要连接数据库。

TypeScript中的 K、T 、V

文章目录 前言泛型类型链接关系K、T、V 含义自动类型推断泛型的应用场景容器类和数据结构函数和方法接口和类类型约束和扩展常用的工具类型 前言 在 TypeScript 的泛型里经常会碰到一些字母,比如 K、T、V,是不是觉得很奇怪? 泛型类型 图中的…