当前位置: 首页 > news >正文

【学习日记】【FreeRTOS】空闲任务与阻塞延时

写在前面

本文是基于野火 RTOS 教程对空闲任务和阻塞延时的详解。

一、什么是任务中的阻塞延时

  • 说到阻塞延时,笔者的第一反应就是在单片机的 while 循环中,使用一个 for 循环不断递减一个大数,通过 CPU 不断执行一条指令的耗时进行延时。这种延时会占用 CPU 资源执行指令,在延时的时候 CPU 不能执行其他的指令。
  • 但是注意,我们现在是想在 RTOS 中的任务实现阻塞延时,RTOS 可以有多个任务,所有所谓任务中的阻塞延时虽然也是阻塞其后的代码运行,但是只阻塞了他所在的那个任务中阻塞延时函数后面的代码。
  • 也就是说,RTOS 中,任务中的阻塞延时就是先阻塞一下这个任务,然后把 CPU 使用权交给其他代码,虽然也是阻塞下文的代码执行,但是只阻塞这个任务的下文,CPU 在这个过程中可以执行其他任务中的指令,大大提高 CPU 利用率,和笔者印象中的阻塞延时并不一样。

二、空闲任务有什么用

  • 空闲任务的优先级是所有任务中优先级最低的,当其他任务都在阻塞延时中,CPU 就会切换到空闲任务运行。
  • 一般来说在空闲任务里面运行一些系统内存的清理工作,或者在空闲任务中让单片机休眠或者进入低功耗模式。

三、空闲任务的实现

  1. 定义空闲任务的任务栈
  2. 定义空闲任务的 TCB
  3. 空闲任务的创建

注意,空闲任务的任务栈和 TCB 变量我们都在 main.c 中声明为全局变量,但是同时,我们想在开启任务调度器的时候自动创建一个空闲任务,而 RTOS 的开发人员不用显式地去创建空闲任务,所以我们把空闲任务的创建集成在 void vTaskStartScheduler( void ) 这个函数中。这样,我们在启动调度器的同时就会自动创建一个空闲任务。代码如下:

void vTaskStartScheduler( void )
{
/*======================================创建空闲任务start==============================================*/     TCB_t *pxIdleTaskTCBBuffer = NULL;               /* 用于指向空闲任务控制块 */StackType_t *pxIdleTaskStackBuffer = NULL;       /* 用于空闲任务栈起始地址 */uint32_t ulIdleTaskStackSize;/* 获取空闲任务的内存:任务栈和任务TCB */vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &ulIdleTaskStackSize );    xIdleTaskHandle = xTaskCreateStatic( (TaskFunction_t)prvIdleTask,              /* 任务入口 */(char *)"IDLE",                           /* 任务名称,字符串形式 */(uint32_t)ulIdleTaskStackSize ,           /* 任务栈大小,单位为字 */(void *) NULL,                            /* 任务形参 */(StackType_t *)pxIdleTaskStackBuffer,     /* 任务栈起始地址 */(TCB_t *)pxIdleTaskTCBBuffer );           /* 任务控制块 *//* 将任务添加到就绪列表 */                                 vListInsertEnd( &( pxReadyTasksLists[0] ), &( ((TCB_t *)pxIdleTaskTCBBuffer)->xStateListItem ) );
/*======================================创建空闲任务end================================================*//* 手动指定第一个运行的任务 */pxCurrentTCB = &Task1TCB;/* 初始化系统时基计数器 */xTickCount = ( TickType_t ) 0U;/* 启动调度器 */if( xPortStartScheduler() != pdFALSE ){/* 调度器启动成功,则不会返回,即不会来到这里 */}
}

上面这段代码调用了 xTaskCreateStatic() 这个函数进行空闲任务的创建,但是这个函数需要传入空闲任务的任务栈和 TCB 变量,而我们把这些变量定义在了 main.c 中,所以需要使用 vApplicationGetIdleTaskMemory() 这个函数来使 vTaskStartScheduler() 函数中的任务指针等等变量指向定义在 main.c 中的任务栈和 TCB,然后再把这些任务指针等传入 xTaskCreateStatic() 中。vApplicationGetIdleTaskMemory() 的具体代码如下:

void vApplicationGetIdleTaskMemory( TCB_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize )
{*ppxIdleTaskTCBBuffer=&IdleTaskTCB;*ppxIdleTaskStackBuffer=IdleTaskStack; *pulIdleTaskStackSize=configMINIMAL_STACK_SIZE;
}

四、任务中的阻塞延时怎么实现

具体想法如下:

  1. 为 TCB 添加记录延时时间的参数
  2. 在任务中调用阻塞延时函数时,会给 TCB 记录延时时间的参数进行赋值,然后调用任务切换函数
  3. 调用任务切换函数会产生 PendSV 中断,在 PendSV中断服务函数中会调用上下文切换函数 vTaskSwitchContext()
  4. 在上下文切换函数中,我们更新当前执行任务的指针。现在我们的思想是,如果当前任务是空闲任务,那么查看其他任务的延时是否结束,如果没有结束就继续执行空闲任务;如果当前执行的不是空闲任务,那么检查一下其他任务是否在延时中,如果不在延时中,就不忘初心进行任务切换,如果在延时中,就判断现在这个任务是否要延时,如果要延时就切换到空闲任务,否则就不进行任何切换。
  5. 上面检查任务是否在延时状态都是通过检查 TCB 的延时参数是否为 0 来实现的,我们使用 SysTick 中断来对 TCB 的延时参数进行定时修改
  6. 在每次 SysTick 中断触发时,我们更新一下系统时基计数器(以后有用),然后扫描一下就绪列表中所有 TCB 的延时参数,不为 0 就减 1,最后尝试任务切换

1. 为 TCB 添加记录延时时间的参数

typedef struct tskTaskControlBlock
{volatile StackType_t    *pxTopOfStack;    /* 栈顶 */ListItem_t			    xStateListItem;   /* 任务节点 */StackType_t             *pxStack;         /* 任务栈起始地址 *//* 任务名称,字符串形式 */char                    pcTaskName[ configMAX_TASK_NAME_LEN ];  TickType_t xTicksToDelay; /* 用于延时 */
} tskTCB;
typedef tskTCB TCB_t;

2. 阻塞延时函数 vTaskDelay()

给 TCB 记录延时时间的参数进行赋值,然后调用任务切换函数。

void vTaskDelay( const TickType_t xTicksToDelay )
{TCB_t *pxTCB = NULL;/* 获取当前任务的TCB */pxTCB = pxCurrentTCB;/* 设置延时时间 */pxTCB->xTicksToDelay = xTicksToDelay;/* 任务切换 */taskYIELD();
}

3. 上下文切换函数 vTaskSwitchContext()

  • 如果当前任务是空闲任务
    • 查看其他任务的延时是否结束
      • 没有结束 -> 继续执行空闲任务
      • 结束 -> 跳转到其他任务
  • 如果当前执行的不是空闲任务
    • 检查一下其他任务是否在延时中
      • 不在延时中 -> 进行任务切换
      • 在延时中 -> 判断现在这个任务是否要延时
        • 要延时就切换到空闲任务
        • 否则就不进行任何切换
void vTaskSwitchContext( void )
{/* 如果当前线程是空闲线程,那么就去尝试执行线程1或者线程2,看看他们的延时时间是否结束,如果线程的延时时间均没有到期,那就返回继续执行空闲线程 */if( pxCurrentTCB == &IdleTaskTCB ){if(Task1TCB.xTicksToDelay == 0){            pxCurrentTCB =&Task1TCB;}else if(Task2TCB.xTicksToDelay == 0){pxCurrentTCB =&Task2TCB;}else{return;		/* 线程延时均没有到期则返回,继续执行空闲线程 */} }else{/*如果当前线程是线程1或者线程2的话,检查下另外一个线程,如果另外的线程不在延时中,就切换到该线程否则,判断下当前线程是否应该进入延时状态,如果是的话,就切换到空闲线程。否则就不进行任何切换 */if(pxCurrentTCB == &Task1TCB){if(Task2TCB.xTicksToDelay == 0){pxCurrentTCB =&Task2TCB;}else if(pxCurrentTCB->xTicksToDelay != 0){pxCurrentTCB = &IdleTaskTCB;}else {return;		/* 返回,不进行切换,因为两个线程都处于延时中 */}}else if(pxCurrentTCB == &Task2TCB){if(Task1TCB.xTicksToDelay == 0){pxCurrentTCB =&Task1TCB;}else if(pxCurrentTCB->xTicksToDelay != 0){pxCurrentTCB = &IdleTaskTCB;}else {return;		/* 返回,不进行切换,因为两个线程都处于延时中 */}}}
}

4. SysTick 中断对 TCB 的延时参数进行定时修改

/*
*************************************************************************
*                             SysTick中断服务函数
*************************************************************************
*/
void xPortSysTickHandler( void )
{/* 关中断 */vPortRaiseBASEPRI();/* 更新系统时基 */xTaskIncrementTick();/* 开中断 */vPortClearBASEPRIFromISR();
}

每次 SysTick 中断触发时,我们更新一下系统时基计数器(以后有用),然后扫描一下就绪列表中所有 TCB 的延时参数,不为 0 就减 1,最后尝试任务切换:

void xTaskIncrementTick( void )
{TCB_t *pxTCB = NULL;BaseType_t i = 0;/* 更新系统时基计数器xTickCount,xTickCount是一个在port.c中定义的全局变量 */const TickType_t xConstTickCount = xTickCount + 1;xTickCount = xConstTickCount;/* 扫描就绪列表中所有线程的xTicksToDelay,如果不为0,则减1 */for(i=0; i<configMAX_PRIORITIES; i++){pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &pxReadyTasksLists[i] ) );if(pxTCB->xTicksToDelay > 0){pxTCB->xTicksToDelay --;}}/* 任务切换 */portYIELD();
}

关于上面这段代码,有一段写得很奇怪:

    /* 更新系统时基计数器xTickCount,xTickCount是一个在port.c中定义的全局变量 */const TickType_t xConstTickCount = xTickCount + 1;xTickCount = xConstTickCount;

笔者刚开始看到的时候想问:直接递增xTickCount不行吗,为什么要写成
const TickType_t xConstTickCount = xTickCount + 1;
xTickCount = xConstTickCount;
这样不是画蛇添足吗?使代码更复杂。

其实不然,在任务调度器中,xTickCount 变量用于记录系统的时基计数器。它的目的是跟踪系统运行的时间,并且根据需要递增。

直接递增 xTickCount 可能会导致并发问题。在多线程或多任务的情况下,如果有多个任务同时尝试递增 xTickCount,并且中间存在竞争条件,可能会导致计数不准确或不一致。

为了避免这种并发问题,代码中将递增操作分解为两个步骤:

首先,通过 const TickType_t xConstTickCount = xTickCount + 1; 将 xTickCount 的值复制到一个中间变量 xConstTickCount 中,并递增这个中间变量。

然后,将中间变量 xConstTickCount 的值赋回给 xTickCount,完成递增操作。

这样做的好处是,无论何时进行递增操作,代码都使用了一个稳定的中间值 xConstTickCount 来执行计算和更新。这确保了计数器 xTickCount 在整个递增过程中保持一致,并且不会受到其他任务的干扰。这样可以避免并发问题,提高代码的可靠性和正确性。

5. 最后是 SysTick 的相关初始化代码

在调度器启动函数 xPortStartScheduler() 函数中调用 vPortSetupTimerInterrupt():

/*
*************************************************************************
*                              调度器启动函数
*************************************************************************
*/BaseType_t xPortStartScheduler( void )
{/*PendSV是一个用于低优先级任务切换的软件中断。通过触发PendSV中断,可以请求处理器在合适的时间切换到更高优先级的任务。PendSV中断具有最低的中断优先级,因此可以在其他中断处理完成后立即执行。*//* 配置PendSV 和 SysTick 的中断优先级为最低 */portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;//初始化SysTick中断vPortSetupTimerInterrupt();/* 启动第一个任务,不再返回 */prvStartFirstTask();/* 不应该运行到这里 */return 0;
}

初始化 SysTick 的函数 vPortSetupTimerInterrupt():

/*
*************************************************************************
*                             初始化SysTick
*************************************************************************
*/
void vPortSetupTimerInterrupt( void )
{/* 设置重装载寄存器的值 */portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;/* 设置系统定时器的时钟等于内核时钟使能SysTick 定时器中断使能SysTick 定时器 */portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT |portNVIC_SYSTICK_ENABLE_BIT ); 
}

这里解释一下重装载寄存器的值怎么设置。计时器实际上是一个计数器,当接收到设定数量的脉冲后进行一次中断,而这个设定的数量就是重装载寄存器的值。

我们把计时器接入到 CPU 晶振后,由于晶振每隔一段固定时间发出一个脉冲信号,此时计时器就将重装载寄存器的值减 1,当重装载寄存器的值减到 0 后,就触发一次中断,由此完成了对晶振的高频率信号的分频。

注意,重装载寄存器的值是从 0 开始减的,所以设置时要减 1。

可以看到,我们使用 configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL 进行设置,configSYSTICK_CLOCK_HZ 实际上就是 CPU 的晶振频率,而 configTICK_RATE_HZ 就是我们设置 SysTick 的中断频率。

其中的宏定义为:

#define configCPU_CLOCK_HZ			( ( unsigned long ) 25000000 )	
#define configTICK_RATE_HZ			( ( TickType_t ) 100 )/* SysTick 配置寄存器 */
#define portNVIC_SYSTICK_CTRL_REG			( * ( ( volatile uint32_t * ) 0xe000e010 ) )
#define portNVIC_SYSTICK_LOAD_REG			( * ( ( volatile uint32_t * ) 0xe000e014 ) )#ifndef configSYSTICK_CLOCK_HZ#define configSYSTICK_CLOCK_HZ configCPU_CLOCK_HZ/* 确保SysTick的时钟与内核时钟一致 */#define portNVIC_SYSTICK_CLK_BIT	( 1UL << 2UL )
#else#define portNVIC_SYSTICK_CLK_BIT	( 0 )
#endif#define portNVIC_SYSTICK_INT_BIT			( 1UL << 1UL )
#define portNVIC_SYSTICK_ENABLE_BIT			( 1UL << 0UL )

后记

如果您觉得本文写得不错,可以点个赞激励一下作者!
如果您发现本文的问题,欢迎在评论区或者私信共同探讨!
共勉!

相关文章:

【学习日记】【FreeRTOS】空闲任务与阻塞延时

写在前面 本文是基于野火 RTOS 教程对空闲任务和阻塞延时的详解。 一、什么是任务中的阻塞延时 说到阻塞延时&#xff0c;笔者的第一反应就是在单片机的 while 循环中&#xff0c;使用一个 for 循环不断递减一个大数&#xff0c;通过 CPU 不断执行一条指令的耗时进行延时。这…...

衣服材质等整理(时常更新)

参考文章&图片来源 https://zhuanlan.zhihu.com/p/390341736 00. 天然纤维 01. 化学纤维 02. 聚酯纤维&#xff08;即&#xff0c;涤纶&#xff09; 一种由有机二元酸和二元醇通过化学缩聚制成的合成纤维。具有出色的抗皱性和保形性&#xff0c;所制衣物在穿着过程中不容…...

电子商务环境下旅游价值链

迈克尔 ・ 波特(Michael E. Porter)在其《竞争优势》一书中提出了“价值链” 的概念&#xff0c;并认为一家企业最核心的竞争优势在于对价值链的设计。虽然迈克尔 ・ 波 特提出的价值链主要是针对企业内部的价值链&#xff0c;但他视价值链为一系列连续完成的 活动&#xff…...

spring源码分析bean的生命周期(下)

doGetBean()执行过程 createBean()执行过程 一、DependsOn注解 spring创建对象之前会判断类上是否加了DependsOn注解&#xff0c;加了会遍历然后会添加到一个map中&#xff0c;spring会先创建DependsOn注解指定的类 二、spring类加载器 在合并BeanDefinition&#xff0c;确定…...

完美解决Github提交PR后报错:File is not gofumpt-ed (gofumpt)

问题阐述 最近在Github上提交PR后&#xff0c;遇到了这么一个问题&#xff1a;golangci-lint运行失败&#xff0c;具体原因是File is not gofumpt-ed (gofumpt)。 名词解释 golangci-lint&#xff1a; golangci-lint 是Go语言社区中常用的代码质量检查工具&#xff0c;它可以…...

pytorch3d成功安装

一、pytorch3d是什么&#xff1f; PyTorch3D的目标是帮助加速深度学习和3D交叉点的研究。3D数据比2D图像更复杂&#xff0c;在从事Mesh R-CNN和C3DPO等项目时&#xff0c;我们遇到了一些挑战&#xff0c;包括3D数据表示、批处理和速度。我们开发了许多有用的算子和抽象&#xf…...

【vue3】同个页面引入多个图表组件实现自适应的方法

首先说明&#xff0c;此方案仅针对vue3项目在同一个页面引入了多个图表组件&#xff0c;因为我发现不能框架不同的引入&#xff0c;resize的写法还不同 window.addEventListener("resize", function() {...// 在此处重新调用即可 }以下是具体写法&#xff1a; 循环…...

一文了解汽车芯片的分类及用途介绍

汽车芯片按其功能可分为控制类&#xff08;MCU和AI芯片&#xff09;、功率类、传感器和其他&#xff08;如存储器&#xff09;四种类型。市场基本被国际巨头所垄断。人们常说的汽车芯片是指汽车里的计算芯片&#xff0c;按集成规模可分为MCU芯片和AI芯片&#xff08;SoC芯片&am…...

Linux0.11内核源码解析-truncate.c

truncate文件只要实现释放指定i节点在设备上占用的所有逻辑块&#xff0c;包括直接块、一次间接块、二次间接块。从而将文件节点对应的文件长度截为0&#xff0c;并释放占用的设备空间。 索引节点的逻辑块连接方式 释放一次间接块 static void free_ind(int dev,int block) {…...

LED驱动型IC芯片的原理介绍

一、LED驱动器是什么 LED驱动器&#xff08;LED Driver&#xff09;&#xff0c;是指驱动LED发光或LED模块组件正常工作的电源调整电子器件。由于LED PN结的导通特性决定&#xff0c;它能适应的电源电压和电流变动范围十分狭窄&#xff0c;稍许偏离就可能无法点亮LED或者发光效…...

VLAN实验

实验题目如下&#xff1a; 实验拓扑如下&#xff1a; 实验要求如下&#xff1a; 【1】PC1/3的接口均为access模式&#xff0c;且属于van2&#xff0c;在同一网段 【2】PC2/4/5/6的IP地址在同一网段&#xff0c;与PC1/3不在同一网段 【3】PC2可以访问4/5/6&#xff0c;PC4不能…...

Qt应用开发(基础篇)——高级纯文本窗口 QPlainTextEdit

一、前言 QPlainTextEdit类继承于QAbstractScrollArea&#xff0c;QAbstractScrollArea继承于QFrame&#xff0c;是Qt用来显示和编辑纯文本的窗口。 滚屏区域基类https://blog.csdn.net/u014491932/article/details/132245486?spm1001.2014.3001.5501框架类QFramehttps://blo…...

三维可视化平台有哪些?Sovit3D可视化平台怎么样?

随着社会经济的发展和数字技术的进步&#xff0c;互联网行业发展迅速。为了适应新时代社会发展的需要&#xff0c;大数据在这个社会经济发展过程中随着技术的进步而显得尤为重要。同时&#xff0c;大数据技术的快速发展进程也推动了可视化技术的飞速发展&#xff0c;国内外各类…...

Xxl-job安装部署以及SpringBoot集成Xxl-job使用

1、安装Xxl-job&#xff1a; 可以使用docker拉取镜像部署和源码编译两种方式&#xff0c;这里选择源码编译安装。 代码拉取地址&#xff1a; https://github.com/xuxueli/xxl-job/tree/2.1.2 官方开发文档&#xff1a; https://www.xuxueli.com/xxl-job/#%E3%80%8A%E5%88%…...

【【超声波避障小车代码】】

超声波避障小车代码 #include <reg51.h> //通用51头文件 #include <intrins.h> //使用了_nop()_函数#define uchar unsigned char //用 uchar 表示 unsigned char 类型 #define uint unsigned int //用 uint 表示 unsigned int 类型sbit EN…...

TDI(Time Delay Integration)

TDI&#xff08;Time Delay Integration&#xff09;是一种特殊的图像采集技术&#xff0c;常用于线阵CCD&#xff08;Charge-Coupled Device&#xff09;相机。TDI技术可以在保持高分辨率的同时增强图像的信噪比&#xff08;Signal-to-Noise Ratio, SNR&#xff09;&#xff0…...

RHCE——一、安装部署及例行性工作

RHCE 一、网络服务1、准备工作2、RHEL9操作系统的安装部署3、配置并优化RHEL9操作系统4、网络配置5、修改网络连接 二、例行性工作1、单一执行的例行性工作2、循环执行的例行性工作 三、书写定时任务的注意事项四、系统级别的计划任务五、实验1、实验一&#xff1a;编写脚本tes…...

服务器数据库中了360后缀勒索病毒怎么办?360后缀勒索病毒的加密形式

随着信息技术的发展&#xff0c;企业的计算机服务器数据库变得越来越重要。然而&#xff0c;在数字时代&#xff0c;网络上的威胁也日益增多。近期&#xff0c;我们收到很多企业的求助&#xff0c;企业的计算机服务器遭到了360后缀勒索病毒的攻击&#xff0c;导致服务器内的所有…...

期权就是股指期货吗,哪个好做一点?

近年来&#xff0c;场内ETF期权产品不断扩大&#xff0c;越来越多的投资者有投资期权的想法。当我们看到期权时&#xff0c;我们会不知不觉地想到期货&#xff0c;虽然期货与期权只有一个字的区别&#xff0c;但实际上有很大的不同&#xff0c;那么期权就是股指期货吗&#xff…...

week32

本周目标&#xff1a; Belady现象的解释 操作系统 计组IO/MM chapter 刷力扣 ubuntu磁盘/网络/命令行进阶*1 tarball之类的使用 Question 大数据系统实验要学吗&#xff1f; 据说课讲得不好这是一门类似数据库的课程——大数据之hadoop / hive / hbase 的区别是什么&a…...

【数据库】P1 数据库基本常识

数据库基本常识 数据库 ≠ 数据库管理系统表&#xff08;Table&#xff09;SQL是什么 数据库 ≠ 数据库管理系统 数据库是保存有组织的数据的容器&#xff0c;数据库称为 DB&#xff08;DataBase&#xff09;&#xff1b;数据库管理系统是创建和操纵数据库的软件&#xff0c;数…...

c语言——计算两个数的乘积

//计算两个数的乘积 #include<stdio.h> #include<stdlib.h> int main() {double firstNumber,secondNumber,product;printf("两个浮点数&#xff1a;");scanf("%lf,%lf",&firstNumber,&secondNumber);productfirstNumber*secondNumber…...

单机模型并行最佳实践

单机模型并行最佳实践 模型并行在分布式训练技术中被广泛使用。 先前的帖子已经解释了如何使用 DataParallel 在多个 GPU 上训练神经网络&#xff1b; 此功能将相同的模型复制到所有 GPU&#xff0c;其中每个 GPU 消耗输入数据的不同分区。 尽管它可以极大地加快训练过程&…...

编程练习(3)

一.选择题 第一题&#xff1a; 函数传参的两个变量都是传的地址&#xff0c;而数组名c本身就是地址&#xff0c;int型变量b需要使用&符号&#xff0c;因此答案为A 第二题&#xff1a; 本题考察const修饰指针变量&#xff0c;答案为A,B,C,D 第三题&#xff1a; 注意int 型变…...

PyTorch学习笔记(十三)——现有网络模型的使用及修改

以分类模型的VGG为例 vgg16_false torchvision.models.vgg16(weightsFalse) vgg16_true torchvision.models.vgg16(weightsTrue) 设置为 False 的情况&#xff0c;相当于网络模型中的参数都是初始化的、默认的设置为 True 时&#xff0c;网络模型中的参数在数据集上是训练好…...

Python爬虫的scrapy的学习(学习于b站尚硅谷)

目录 一、scrapy  1. scrapy的安装  &#xff08;1&#xff09;什么是scrapy  &#xff08;2&#xff09;scrapy的安装 2. scrapy的基本使用  &#xff08;1&#xff09;scrap的使用步骤  &#xff08;2&#xff09;代码的演示 3. scrapy之58同城项目结构和基本方法&…...

“深入解析JVM:揭秘Java虚拟机的工作原理“

标题&#xff1a;深入解析JVM&#xff1a;揭秘Java虚拟机的工作原理 摘要&#xff1a;本文将深入解析Java虚拟机&#xff08;JVM&#xff09;的工作原理&#xff0c;探讨其内部结构和运行机制。我们将介绍JVM的组成部分、类加载过程、内存管理、垃圾回收、即时编译等关键概念&…...

【数据结构与算法】十大经典排序算法-归并排序

&#x1f31f;个人博客&#xff1a;www.hellocode.top &#x1f3f0;Java知识导航&#xff1a;Java-Navigate &#x1f525;CSDN&#xff1a;HelloCode. &#x1f31e;知乎&#xff1a;HelloCode &#x1f334;掘金&#xff1a;HelloCode ⚡如有问题&#xff0c;欢迎指正&#…...

基于深度学习创建-表情符号--附源码

表情符号深度学习概述 如今,我们使用多种表情符号或头像来表达我们的心情或感受。它们充当人类的非语言线索。它们成为情感识别、在线聊天、品牌情感、产品评论等的关键部分。针对表情符号驱动的故事讲述的数据科学研究不断增加。 从图像中检测人类情绪非常流行,这可能是由…...

.netcore grpc的proto文件字段详解

一、.proto文件字段概述 grpc的接口传输参数都是根据.proto文件约定的字段格式进行传输的grpc提供了多种类型字段&#xff1b;主要包括标量值类型&#xff08;基础类型&#xff09;、日期时间、可为null类型、字节、列表、字典、Any类型&#xff08;任意类型&#xff09;、One…...

带你了解建堆的时间复杂度

目录 用向上调整建堆的时间复杂度 1.向上调整建堆的时间复杂度O(N*logN) 2.数学论证 3.相关代码 用向下调整建堆的时间复杂度 1.建堆的时间复杂度为O(N) 2.数学论证 3.相关代码 完结撒花✿✿ヽ(▽)ノ✿✿ 博主建议:面试的时候可能会被面试官问到建堆时间复杂度的证明过…...

人工智能原理(6)

目录 一、机器学习概述 1、学习和机器学习 2、学习系统 3、机器学习发展简史 4、机器学习分类 二、归纳学习 1、归纳学习的基本概念 2、变型空间学习 3、归纳偏置 三、决策树 1、决策树组成 2、决策树的构造算法CLS 3、ID3 4、决策树的偏置 四、基于实例的学习…...

单片机模块化编程文件创建流程

一、在工程文件夹下创建一个新的文件夹&#xff0c;命名为“ModulesCodesFiles”&#xff0c;译为“模块化代码文件”&#xff0c;用于存放所有模块化代码文件。 二、在“ModulesCodesFiles”文件夹下为每个模块创建一个新的文件夹&#xff0c;命名为模块的名称&#xff0c;例…...

docker image

docker image 1. 由来 docker image是Docker容器管理工具中的一个命令&#xff0c;用于管理和操作Docker镜像。 2. 常见五种示例命令和说明 以下是docker image的常见示例命令及其说明&#xff1a; 示例一&#xff1a;列出所有镜像 docker image ls描述&#xff1a;使用d…...

力扣75——单调栈

总结leetcode75中的单调栈算法题解题思路。 上一篇&#xff1a;力扣75——区间集合 力扣75——单调栈 1 每日温度2 股票价格跨度1 - 2 解题总结 1 每日温度 题目&#xff1a; 给定一个整数数组 temperatures &#xff0c;表示每天的温度&#xff0c;返回一个数组 answer &…...

Webpack和Parcel详解

构建工具和打包器是在开发过程中帮助组织、优化和打包项目的工具。它们可以处理依赖管理、资源优化、代码转换等任务&#xff0c;从而使开发流程更高效。以下是关于构建工具和打包器的一些指导&#xff1a; **Webpack&#xff1a;** Webpack 是一个功能强大的模块打包器&#…...

linux系统服务学习(六)FTP服务学习

文章目录 FTP、NFS、SAMBA系统服务一、FTP服务概述1、FTP服务介绍2、FTP服务的客户端工具3、FTP的两种运行模式&#xff08;了解&#xff09;☆ 主动模式☆ 被动模式 4、搭建FTP服务&#xff08;重要&#xff09;5、FTP的配置文件详解&#xff08;重要&#xff09; 二、FTP任务…...

7.原 型

7.1原型 【例如】 另外- this指向&#xff1a; 构造函数和原型对象中的this都指向实例化的对象 7.2 constructor属性 每个原型对象里面都有个constructor属性( constructor构造函数) 作用&#xff1a;该属性指向该原型对象的构造函数 使用场景: 如果有多个对象的方法&#…...

【图像分类】理论篇(2)经典卷积神经网络 Lenet~Resenet

目录 1、卷积运算 2、经典卷积神经网络 2.1 Lenet 网络构架 代码实现 2.2 Alexnet 网络构架 代码实现 2.3 VGG VGG16网络构架 代码实现 2.4 ResNet ResNet50网络构架 代码实现 1、卷积运算 在二维卷积运算中&#xff0c;卷积窗口从输入张量的左上角开始&#xff…...

C++系列-内存模型

内存模型 内存模型四个区代码区全局区栈区堆区内存开辟和释放在堆区开辟数组 内存模型四个区 不同区域存放的数据生命周期是不同的&#xff0c;更为灵活。 代码区&#xff1a;存放函数体的二进制代码&#xff0c;操作系统管理。全局区&#xff1a;存放全局变量&#xff0c;常…...

[管理与领导-30]:IT基层管理者 - 人的管理 - 向上管理,管理好你的上司,职业发展事半功倍。什么样的上司不值得跟随?

目录 前言&#xff1a; 一、什么是向上管理 二、为什么要向上管理 三、如何进行向上管理 四、向上管理的注意事项 五、向上管理的忌讳 六、向上管理常犯的错 七、如何帮助上司解决他关心的问题 7.1 如何帮助上司解决他关心的问题 7.2 如何帮助上司降低压力 八、什么…...

Java进阶篇--迭代器模式

目录 同步迭代器&#xff08;Synchronous Iterator&#xff09;&#xff1a; Iterator 接口 常用方法&#xff1a; 注意&#xff1a; 扩展小知识: 异步迭代器&#xff08;Asynchronous Iterator&#xff09;&#xff1a; 常用的方法 注意&#xff1a; 总结&#xff1a…...

【CAM】CAM(Class Activation Mapping)——可视化CNN的特征定位

文章目录 一、CAM(Class Activation Mapping)二、CAM技术实现2.1 网络修改2.2 微调2.2 特征提取 三、总结Reference 完整代码见Github &#xff1a;https://github.com/capsule2077/CAM-Visualization &#xff0c;如果有用可以点个Star&#xff0c;谢谢&#xff01; 一、CAM(C…...

Maven教程_编程入门自学教程_菜鸟教程-免费教程分享

教程简介 Maven 是一款基于 Java 平台的项目管理和整合工具&#xff0c;它将项目的开发和管理过程抽象成一个项目对象模型&#xff08;POM&#xff09;。开发人员只需要做一些简单的配置&#xff0c;Maven 就可以自动完成项目的编译、测试、打包、发布以及部署等工作。Maven 是…...

Gof23设计模式之模板方法模式

1.定义 定义一个操作中的算法骨架&#xff0c;而将算法的一些步骤延迟到子类中&#xff0c;使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。 2.结构 模板方法&#xff08;Template Method&#xff09;模式包含以下主要角色&#xff1a; 抽象类&#xff0…...

springBoot 配置文件 spring.resources.add-mappings 参数的作用

在Spring Boot应用中&#xff0c;spring.resources.add-mappings参数用于控制是否将特定路径的资源文件添加到URL路径映射中。 默认情况下&#xff0c;该参数的值为true&#xff0c;即会自动将静态资源&#xff08;例如CSS、JavaScript、图片等&#xff09;的URL路径添加到Spr…...

《Java极简设计模式》第03章:工厂方法模式(FactoryMethod)

作者&#xff1a;冰河 星球&#xff1a;http://m6z.cn/6aeFbs 博客&#xff1a;https://binghe.gitcode.host 文章汇总&#xff1a;https://binghe.gitcode.host/md/all/all.html 源码地址&#xff1a;https://github.com/binghe001/java-simple-design-patterns/tree/master/j…...

C++11并发与多线程笔记(11) std::atomic续谈、std::async深入谈

C11并发与多线程笔记&#xff08;11&#xff09; std::atomic续谈、std::async深入谈 1、std::atomic续谈2、std::async深入理解2.1 std::async参数详述2.2 std::async和std::thread()区别&#xff1a;2.3 async不确定性问题的解决 1、std::atomic续谈 #include <iostream&…...

React快速入门

最近需要学到react&#xff0c;这里进行一个快速的入门&#xff0c;参考react官网 1.创建和嵌套组件 react的组件封装是个思想&#xff0c;我这里快速演示代码&#xff0c;自己本身也不太熟悉。 代码的路径是src底下的App.js function MyButton() {return (<button>I…...

windows权限维持—SSPHOOKDSRMSIDhistorySkeletonKey

windows权限维持—SSP&HOOK&DSRM&SIDhistory&SkeletonKey 1. 权限维持介绍1.1. 其他 2. 基于验证DLL加载—SPP2.1. 操作演示—临时生效2.1.1. 执行命令2.1.2. 切换用户 2.2. 操作演示—永久生效2.2.1. 上传文件2.2.2. 执行命令2.2.3. 重启生效 2.3. 总结 3. 基…...