02 FreeRTOS 任务

news2024/9/19 10:11:15

1、创建任务函数

1.1 动态内存的使用

        在之前我们如果要创建一个与学生有关的任务,我们会定义:

//打印50个学生的信息
char name[50][100];
int age[50];
int sex[50];	//1表示男,0表示女
int score[50];

         如果之后要对其进行修改会非常麻烦,因此我们要引入面对对象的编程思想,这个对象就是student,那么我们就定义一个相关的结构体来表示一个学生:

struct Student{
	char name[100];
	int age;
	int sex;
	int score;
    struct Student *next;    //这里定义一个指针,可以使用链表把学生管理起来
};

        通过这种编程思想,我们在FressRTOS中对任务也要构造出一个结构体,之前我们是通过xTaskCreate函数动态创建了任务,xTaskCreate(Task1Function, "Task1", 100, NULL, 1, &xHandleTask1),同时在创建时指定了栈的大小为100*4个字节,这个TaskHandle_t * const则是指向了typedef struct tskTaskControlBlock * TaskHandle_t,也就是TCB_t *这个结构体,所以说我们创建任务时所返回的handle,就只是这个TCB_t 的指针,另外取了一个名字。

        在xTaskCreate中有一个TCB_t *结构体,同时可以看出这里用了malloc从动态内存,也就是从堆里面来做分配

1.2 静态创建任务

        使用xTaskCreateStatic函数静态创建任务,要事先分配好TCB结构体,栈。

        如果要使用这个函数,还要在FreeRTOSconfig.h中定义"configSUPPORT_STATIC_ALLOCATION""为1,并且实现vApplicationGetldleTaskMemory函数。

//关键代码
void Task1Function( void * param)
{
	while(1){
		printf("1");
	}
}

void Task2Function( void * param)
{
	while(1){
		printf("2");
	}
}

void Task3Function( void * param)
{
	while(1){
		printf("3");
	}
}


/*-----------------------------------------------------------*/
StackType_t xTask3Stack[100];

StaticTask_t xTask3TCB;

StackType_t xIdleTaskStack[100];	//定义空闲任务栈
StaticTask_t xIdleTask3TCB;			//定义空闲任务TCB

//申请获得空闲任务内存
//要提供空闲任务的TCBBuffer,空闲任务的栈Buffer,空闲任务的栈大小
void vApplicationGetIdleTaskMemory(StaticTask_t ** ppxIdleTaskTCBBuffer, 
								StackType_t ** ppxIdleTaskStackBuffer,
								uint32_t * pulIdleTaskStackSize)
{
	*ppxIdleTaskTCBBuffer = &xIdleTask3TCB;
	*ppxIdleTaskStackBuffer = xIdleTaskStack;
	*pulIdleTaskStackSize = 100;
}

//main.c
xTaskCreate(Task1Function, "Task1", 100, NULL, 1, &xHandleTask1);    //动态创建
xTaskCreate(Task2Function, "Task2", 100, NULL, 1, NULL);    //动态创建
xTaskCreateStatic(Task3Function, "Task3", 100, NULL, 1, xTask3Stack, &xTask3TCB);    //静态创建

1.3 进一步实验

1.3.1 优先级实验

        在FreeRTOS中,优先级的取值范围是:0~(configMAX_PRIORITIES – 1),数值越大优先级越高。

        在之前的代码中添加修改:

static int task1flagrun = 0;
static int task2flagrun = 0;
static int task3flagrun = 0;


void Task1Function( void * param)
{
	while(1){
		task1flagrun = 1; 
		task2flagrun = 0;
		task3flagrun = 0;
		printf("1");
	}
}

void Task2Function( void * param)
{
	while(1){
		task1flagrun = 0; 
		task2flagrun = 1;
		task3flagrun = 0;
		printf("2");
	}
}

void Task3Function( void * param)
{
	while(1){
		task1flagrun = 0; 
		task2flagrun = 0;
		task3flagrun = 1;
		printf("3");
	}
}

        然后再main函数中打断点,打开调试,运行到断点,然后将task1flagrun、task2flagrun、task3flagrun这三个变量添加到逻辑分析仪中去运行一段时间,我们会发现,同优先级的3个任务,不会同时进行。

        如果我们将Task1Function的优先级设置为2,其它仍为1,运行时候我们就会发现这时候,只打印1,没有执行另外两个任务。由此可以看出,对于FreeRTOS来说,在默认的调度下面,高优先级的任务先执行,如果高优先级的任务没有主动放弃运行,其它低优先级的任务无法执行。

1.3.2 删除任务

        我们在创建任务的时候传入了一个handle,以后想要引用这个任务就必须要通过这个handle执行。如果

void Task2Function( void * param)
{
	int i = 0;
	while(1){
		task1flagrun = 0; 
		task2flagrun = 1;
		task3flagrun = 0;
		printf("2");
		
		//删除任务一
		if(i++ == 100){
			vTaskDelete(xHandleTask1);
		}
		
		//自杀
		if(i == 200){
			vTaskDelete(NULL);
		}
	}
}

        通过观察实验结果可以发现,经过修改的程序,在运行之初会打印1、2、3,运行一会儿后只打印2、3,再之后就只打印3了。

        使用vTaskDelete既可以删除动态创建的任务,也可以删掉静态创建的任务,但是要想删除任务,必须要记录创建任务时的返回值handle。

        FreeRTOS在xTaskCreate中分配TCB和栈,但是并不是在vTaskDelete中释放TCB和栈,而是在空闲任务中进行这些清理工作,如果连续不断的调用xTaskCreate和vTaskDelete,最终会导致内存耗光。 

1.3.3 使用同一个任务函数创建多个任务

void TaskGenericFunction( void * param)
{
	int val = (int)param;
	while(1){		
		printf("%d", val);
	}
}


xTaskCreate(TaskGenericFunction, "Task4", 100, (void *)4, 1, NULL);
xTaskCreate(TaskGenericFunction, "Task5", 100, (void *)5, 1, NULL);

        通过结果发现4和5都成功打印出来了,证明可以在同一个任务函数中创建多个任务。

        使用同一个任务会产生不同的效果,是因为它们的栈是不一样的,传入的参数都保存在不同的栈里面,运行的时候互不影响。

1.3.4 栈大小实验

void Task1Function( void * param)
{
	//在创建任务时,申请了100*4的空间,这里故意定义500,耗尽栈的空间,从高地址向下增长,破坏下面的空间
	//这样程序运行的时候是完全不可控的,程序会崩溃
	volatile char buf[500];	//使用关键字,不允许没有使用的空间被优化掉
	int i;
	
	while(1){
		task1flagrun = 1; 
		task2flagrun = 0;
		task3flagrun = 0;
		printf("1");
		
		for(i = 0; i < 500; i++){
			buf[i] = 0;
		}
	}
}

        程序按上面修改之后运行,直接崩溃了,在申请的栈空间以及TCB空间的前面会有一个头来存储有关的信息,便于程序返回等操作,一旦栈的空间使用不当,冲破了空间限制,把前面头部的信息更改了,程序就会发生不可控的问题。

2、任务状态

2.1 任务切换的基础:tick中断

        在FreeRTOS系统中有个定时器,这个定时器每隔一段时间会产生一个中断,这个间隔就称为tick,发生中断时,它会将发生中断的次数记录下来,初始值为0,每发生一次中断就累加1,这个中断值就成为tick count,这将是RTOS的时钟基准。

        在发生中断时,tick中断处理函数被调用,在这个函数中,它会判断是否要切换任务,如果要切换任务就会去切换任务。而运行任务的基准时间(周期),可以在源码的FreeRTOSConfig.h中去配置,我们也可以指定每个任务每次执行几个tick:

#define configTICK_RATE_HZ        ((TickType_t)1000)

        在使用keil模拟器中的逻辑分析仪时,如果时间不准确,要确认代码中设置时钟时用的频率和Options中Xtal的频率相同。

        在main函数中创建多个任务时,为什么后面创建的任务反而先运行?因为后面的任务插入链表时,pxCurrentTCB先执行它。创建任务时代码如下,后创建的最高优先级任务先执行:

2.2 有哪些任务状态?状态切换图

        正在运行的任务:Running状态

        可以随时运行但是现在还没轮到:Ready状态

        阻塞,等待某些事情发生才能继续执行:Blocked状态

        暂停,主动/被动休息:Suspended状态

        状态转换图:

2.3 怎么管理不同状态的任务:放在不同的链表里

        将不同状态的任务放在不同的链表里,当有需要执行时,就从对应链表中挑出一个来执行。

2.4 阻塞状态(Blocked)举例:vTaskDelay函数

        使用vTaskDelay时,如果想要延时若干毫秒,那么可以自己把毫秒换算成Tick数,或者使用宏把毫秒换算成Tick数。有一个宏:pdMS_TO_TICKS(ms),可以把毫秒转换成Tick数。

void Task2Function( void * param)
{
	int i = 0;
	while(1){
		task1flagrun = 0; 
		task2flagrun = 1;
		task3flagrun = 0;
		printf("2");
		
		//让任务2进入阻塞状态
		vTaskDelay(10);
	}
}

2.5 暂停状态(Suspended)举例:vTaskSuspend/vTaskResume

void Task1Function( void * param)
{
	//获得TickCount的值
	TickType_t tStart = xTaskGetTickCount();
	TickType_t t;
	int flag = 0;
	
	while(1){
		t = xTaskGetTickCount();	//在运行的时候获取当前时间
		
		task1flagrun = 1; 
		task2flagrun = 0;
		task3flagrun = 0;
		printf("1");
		
		//命令任务3进入暂停状态,如果想让自己主动休息,可以传入NULL
		//对于进入暂停状态的任务必须由别人来唤醒
		if(!flag && t > tStart + 10){
			vTaskSuspend(xHandleTask3);
			flag = 1;	//设置一个标志位,不要让重复让任务3进入暂停状态
		}
		
		if(t > tStart + 30){
			//命令任务3恢复运行状态
			vTaskResume(xHandleTask3);
		}
		
	}
}

3、实现周期性的任务

3.1 vTaskDelay

        至少等待指定个数的Tick Interrupt才能变成就绪状态。

static int rands[] = {2, 18, 64, 121, 9};

void Task1Function( void * param)
{
	//获得TickCount的值
	TickType_t tStart = xTaskGetTickCount();
	int i =0;
	int j =0;
	
	while(1){

		
		task1flagrun = 1; 
		task2flagrun = 0;
		task3flagrun = 0;
		
		for(i = 0; i < rands[j]; i++){
			printf("1");		
		}
		j++;
		if(j == 5){
			j = 0;
		}
		
		vTaskDelay(20);	//延时20个tick
	
	}
}

        通过结果图可以看出,延迟的时间的一致的。

3.2 vTaskDelayUntil

        等待到指定的绝对时刻,才能变为就绪状态。

        vTaskDelayUntil是老版本,它没有返回值,在新版本中可以用xTaskDelayUntil函数,二者传入的参数是一样的,只是新的有返回值。

static int rands[] = {2, 18, 64, 121, 9};


void Task1Function( void * param)
{
	//获得TickCount的值
	TickType_t tStart = xTaskGetTickCount();
	int i =0;
	int j =0;
	
	while(1){

		
		task1flagrun = 1; 
		task2flagrun = 0;
		task3flagrun = 0;
		
		for(i = 0; i < rands[j]; i++){
			printf("1");		
		}
		j++;
		if(j == 5){
			j = 0;
		}
	

//设置个开关
#if 0
		vTaskDelay(20);	//延时20个tick
#else
		vTaskDelayUntil(&tStart, 20);	//延时到(tStart+20tick)时刻,同时更新tStart=tStart+20tick
#endif
		
	}
}

        通过结果图可以看出,每两次开始执行之间的时间是一致的。

4、空闲任务及其钩子函数

4.1 空闲任务        

        在xTaskCreate中分配TCB和栈,但是并不一定是在vTaskDelete中释放TCB和栈,对于自杀的任务,由空闲任务来清理内存,对于他杀的任务,由凶手来清理。

        我们在任务1中创建任务2,并且将任务一的优先级设置为1,任务二的优先级设置为2,在任务二中打印语句之后自杀。这样我们在程序中会有3个任务,任务一、任务二和空闲任务,空闲任务的优先级最低,为0。这样执行之后,程序很快就崩溃了,因为可分配的内存不够了,堆不够了。

void Task2Function( void * param);

/*-----------------------------------------------------------*/

void Task1Function( void * param)
{
	TaskHandle_t xHandleTask2;
	BaseType_t xReturn;
	while(1){
		printf("1");
		xReturn = xTaskCreate(Task2Function, "Task2", 1024, NULL, 2, &xHandleTask2);
		if(xReturn != pdPASS){
			printf("xTaskCreate err\r\n");
		}
		
	}
}

void Task2Function( void * param)
{
	while(1){
		printf("2");
		//vTaskDelay(2);
		vTaskDelete(NULL);
	}
}

        如果是在任务一中杀死任务二,那么程序现象会一直执行。

void Task2Function( void * param);

/*-----------------------------------------------------------*/

void Task1Function( void * param)
{
	TaskHandle_t xHandleTask2;
	BaseType_t xReturn;
	while(1){
		printf("1");
		xReturn = xTaskCreate(Task2Function, "Task2", 1024, NULL, 2, &xHandleTask2);
		if(xReturn != pdPASS){
			printf("xTaskCreate err\r\n");
		}
		vTaskDelete(xHandleTask2);
		
	}
}

void Task2Function( void * param)
{
	while(1){
		printf("2");
		vTaskDelay(2);
		
	}
}

4.2 钩子函数         

        使用空闲任务不仅可以帮我们清理自杀的任务,还可以执行一些低优先级、后台的、需要连续执行的函数、测量系统的空闲时间、让系统进入省电模式等。如果要做到这些,我们可以通过修改空闲任务的函数来实现,但是如果直接修改会破坏FreeRTOS的核心文件,因此提供了一个钩子函数,可以先配置规定的宏,来告诉程序你要使用这个钩子函数。

        对钩子函数,空闲任务对其也会有一些限制:不能导致空闲任务进入阻塞状态、暂停状态;如果你会使用vTaskDelete()来删除任务,那么钩子函数要非常高效地执行。如果空闲任务一直卡在钩子函数里的话,它就无法释放内存。

        使用钩子函数的前提:

                在FreeRTOSConfig.h中把这个宏定义为1:configUSE_IDLE_HOOK

                实现vApplicationIdleHook函数

void Task2Function( void * param);

/*-----------------------------------------------------------*/
static int task1flagrun = 0;
static int task2flagrun = 0;
static int taskidleflagrun = 0;

void Task1Function( void * param)
{
	TaskHandle_t xHandleTask2;
	BaseType_t xReturn;
	while(1){
		task1flagrun = 1; 
		task2flagrun = 0;
		taskidleflagrun = 0;
		printf("1");
		xReturn = xTaskCreate(Task2Function, "Task2", 1024, NULL, 2, &xHandleTask2);
		if(xReturn != pdPASS){
			printf("xTaskCreate err\r\n");
		}
		vTaskDelete(xHandleTask2);
		
	}
}

void Task2Function( void * param)
{
	while(1){
		task1flagrun = 0; 
		task2flagrun = 1;
		taskidleflagrun = 0;
		printf("2");
		vTaskDelay(2);
		
	}
}

void vApplicationIdleHook(void)
{
	task1flagrun = 0; 
	task2flagrun = 0;
	taskidleflagrun = 1;
	printf("0");
}

//main函数里
xTaskCreate(Task1Function, "Task1", 100, NULL, 0, &xHandleTask1);

        在这个程序中,任务一先执行,在任务一中创建任务二,此时任务二的优先级最高,因此任务二先执行,之后杀掉任务二,任务一和空闲任务的优先级相同,二者交替执行,在执行空闲任务时会用到钩子函数。

5、任务调度算法

5.1 状态与事件

        正在运行的任务,被称为"正在使用处理器",它处于运行状态。在单处理器系统中,任何时间里只能有一个任务处于运行状态。

        非运行状态的任务,它处于这3种状态之一:

                阻塞(Blocked)

                暂停(Suspended)

                就绪(Ready)

        就绪态的任务,可以被调度器挑选出来切换为运行状态,调度器永远都是挑选最高优先级的就绪态任务并让它进入运行状态。

        阻塞状态的任务,它在等待"事件",当事件发生时任务就会进入就绪状态。

        事件分为两类:

                时间相关的事件:就是设置超时时间,在指定时间内阻塞,时间到了就进入就绪状态。使用时间相关的事件,可以实现周期性的功能、可以实现超时功能。

                同步事件:同步事件就是某个任务在等待某些信息,别的任务或者中断服务程序会给它发送信息。怎么"发送信息"的方法有很多,比如任务通知(task notification)、队列(queue)、事件组(event group)、信号量(semaphoe)、互斥量(mutex)等。这些方法用来发送同步信息,比如表示某个外设得到了数据。

5.2 调度策略

5.2.1 可否抢占?

        高优先级的任务能否优先执行(配置项: configUSE_PREEMPTION)

        如果可以:被称作"可抢占调度"(Pre-emptive),高优先级的就绪任务马上执行,下面再细化。

        如果不可以:不能抢就只能协商了,被称作"合作调度模式"(Co-operative Scheduling)。当前任务执行时,更高优先级的任务就绪了也不能马上运行,只能等待当前任务主动让出CPU资源。其他同优先级的任务也只能等待:更高优先级的任务都不能抢占,平级的更应该老实点

2.2.2 可抢占的前提下,同优先级的任务是否轮流执行(配置项: configUSE_TIME_SLICING)?

        轮流执行:被称为"时间片轮转"(Time Slicing),同优先级的任务轮流执行,你执行一个时间片、我再执行一个时间片

        不轮流执行:英文为"without Time Slicing",当前任务会一直执行,直到主动放弃、或者被高优先级任务抢占。

5.2.3 允许抢占、允许时间片轮转时,空闲任务是否让步?

        在"可抢占"+"时间片轮转"的前提下,进一步细化:空闲任务是否让步于用户任务(配置项:configlDLE_SHOULD_YIELD)。如果让步,则空闲任务低人一等,每执行一次循环,就看看是否主动让位给用户任务。如果不让步,空闲任务跟用户任务一样,大家轮流执行,没有谁更特殊。

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

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

相关文章

java面对对象编程-多态

介绍 方法的多态 多态是在继承&#xff0c;重载&#xff0c;重写的基础上实现的 我们可以看看这个代码 package b;public class main_ {public static void main(String[] args) { // graduate granew graduate(); // gra.cry();//这个时候&#xff0c;子类的cry方法就重写…

[Java EE] 网络编程与通信原理(三):网络编程Socket套接字(TCP协议)

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏:&#x1f355; Collection与数据结构 (92平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm1001.2014.3001.5482 &#x1f9c0;Java …

第36届世界超级小姐大赛安徽赛区正式启动

5月26日,在现场几百位出席仪式的社会名流、时尚界大咖、文化旅游机构负责人和前沿品牌代表以及现场嘉宾的共同见证下&#xff0c;第36届世界超级小姐大赛安徽赛区活动的神秘面纱终于在安徽黄山悠悠湖文化中心正式揭开。伴随着高亢激情的现代音乐&#xff0c;长发飘逸、身形灵动…

“AURORA-M:首个遵循人类审查安全指令微调的开源多语言模型

在人工智能领域&#xff0c;多模态学习是一个日益增长的研究领域&#xff0c;它涉及将来自不同源&#xff08;如图像、文本、音频等&#xff09;的信息结合起来。但高昂的训练计算成本限制了模型的普及性&#xff0c;使得小型机构和个人难以负担。而且现有模型在多语言能力上受…

结算协同,打通企业上下游业、财、票、资

市场变革&#xff0c;转型当先 随着电子发票的普及与数字化浪潮的涌动&#xff0c;企业正面临着前所未有的转型挑战。如何在快速变化的市场中站稳脚跟&#xff0c;提升竞争力&#xff1f;答案在于数字化转型&#xff0c;特别是供应链结算流程的革新。 无纸化、自动化&#xff0…

数字孪生为什么这么火?水务离不开它的原因又是什么?

数字孪生利用可视化技术结合视频融合、BIM、5G、物联网、云计算和大数据等先进技术&#xff0c;围绕实现水质达标、安全生产、高效节能等生产、运营和管理目标。它构建了一个三维场景&#xff0c;涵盖自来水厂区的建筑、生产设备、管线等设施&#xff0c;以实现对水厂实时运行信…

分享目前堪称最好用的车机桌面app,支持画中画模式!

好用的车机桌面了&#xff0c;支持悬浮画中画&#xff01; 如果你想改变单调的车机桌面&#xff0c;那么这篇文章你不要错过了&#xff01;支持地图悬浮、画中画的车机桌面&#xff0c;不要错过&#xff01; 今天阿星给大家分享3款好用、好看的大屏桌面软件&#xff0c;重要的…

Python数据分析常用函数

Python基础 数字处理函数 Python提供了用于数字处理的内置函数和内置模块(math)&#xff0c;使用内置模块&#xff0c;需要先导入 import math。 内置函数math模块abs(-5)返回绝对值math.ceil(2.3)返回不小于x的最小整数divmod(9,4)返回商和余数math.floor(2.3)返回不大于x的…

Unity OutLine 模型外描边效果

效果展示&#xff1a; 下载链接

十四天学会Vue——Vue核心(理论+实战)中篇(第二天)

声明&#xff1a;是接着上篇讲的哦&#xff0c;感兴趣可以去看一看~ 这里一些代码就不写了&#xff0c;为了缩减代码量&#xff0c;大家知道就可以了&#xff1a; Vue.config.productionTip false //阻止 vue 在启动时生成生产提示。热身小tips&#xff0c;可以安装这个插件&…

记一次重定向问题(浏览器安全)解决

近期做单点登陆功能&#xff0c;本身应该是一个很简单的功能&#xff0c;却发生了意向不到的问题…让我们看下&#xff1a; 首先第三方给出的地址需要通过JWT框架获取token拼接后跳转&#xff0c;我这边为了方便首选肯定是考虑用response.sendRedirect(url)&#xff0c;但是做好…

源代码防泄漏方案需要具备哪些因素?

首选选择加密软件对公司来说是一项关键决策&#xff0c;需要细致考虑多个因素。 选择合适的加密软件&#xff1a;关键因素与推荐方案 一、稳定性&#xff1a;加密软件的核心 稳定性是评估加密软件的首要因素。一个不稳定的加密软件可能导致数据损坏或系统冲突&#xff0c;影响…

【机器学习】机器学习在信息安全领域中的典型应用

&#x1f680;&#x1f680;&#x1f680;传送门 &#x1f512;机器学习在信息安全领域中的典型应用&#x1f4d5;利用机器学习检测恶意行为并阻断攻击&#x1f308;使用机器学习分析移动终端安全状况⭐借助机器学习提高信息安全分析水平&#x1f3ac;依靠机器学习自动完成重复…

“按摩”科技?

都说A股股民是特别善于学习的&#xff0c;这不市场又现新概念——“按摩科技”&#xff0c;成立仅6年&#xff0c;把上门按摩干到35亿营收也是没谁了&#xff0c;现在号称有1000万用户&#xff0c;3万家入驻商户数的按摩平台&#xff0c;难道就凭借2.5万名女技师&#xff0c;活…

【YOLO 系列】基于YOLO V8的学生上课行为检测系统【python源码+Pyqt5界面+数据集+训练代码】

前言 在现代教育环境中&#xff0c;学生上课行为的监测对于提升教学质量和学生学习效率具有重要意义。然而&#xff0c;传统的人工观察方法不仅效率低下&#xff0c;而且难以保证客观性和准确性。为了解决这一问题&#xff0c;我们启动了这个项目&#xff0c;目的是利用YOLOV8…

【LeetCode算法】第83题:删除排序链表中的重复元素

目录 一、题目描述 二、初次解答 三、官方解法 四、总结 一、题目描述 二、初次解答 1. 思路&#xff1a;双指针法&#xff0c;只需遍历一遍。使用low指向前面的元素&#xff0c;high用于查找low后面与low不同内容的节点。将具有不同内容的节点链接在low后面&#xff0c;实…

make disclean V=1 分析

文章目录 make distclean步骤1&#xff1a;2090-2114行&#xff0c;执行依赖 clean步骤2&#xff1a;2120-2124行&#xff0c;执行依赖 $(mrproper-dirs)步骤3&#xff1a;2118-2129行&#xff0c;执行依赖 mrproper步骤4&#xff1a;2135-2142行&#xff0c;实现 distclean 编…

恶意退市潮?

一张A4纸&#xff0c;炸出一池鱼。史上&#xff08;最&#xff09;严新规&#xff0c;这一拳打到了&#xff08;违规减持&#xff09;上。 新规算是对新国九条的补充&#xff0c;更是给大股东们上紧箍咒。那市场买账吗&#xff1f;昨晚爆出19家董监高亲属&#xff08;违规&…

post请求

文章目录 一、get请求和post请求区别二、get请求和post请求的用法对比1.get请求2.post请求 三、如何知道是get请求还是post请求 一、get请求和post请求区别 二者区别就是一句话&#xff1a;post请求更安全 二、get请求和post请求的用法对比 1.get请求 get请求: 请求参数&am…

RK3568笔记二十七:LPRNet车牌识别

若该文为原创文章&#xff0c;转载请注明原文出处。 记录自训练并在RK3568上部署。 一、介绍 LPRNet的Pytorch实现&#xff0c;一种高性能和轻量级的车牌识别框架。完全适用于中国车牌识别&#xff08;Chinese License Plate Recognition&#xff09;及国外车牌识别&#xf…