嵌入式实时操作系统的设计与开发(二)

news2024/11/15 12:02:05

加载应用程序与创建程序运行环境

将应用程序从Flash加载到RAM的实现代码是一定在启动代码中的。
计算机系统的运行其实是CPU到相应的内存地址去取回指令,然后译码并执行指令,再依次从下一个地址取指、执行,而程序就是指令与数据的集合。
程序的运行就是CPU从程序中取出指令、执行指令,当需要时,再从程序中取得需要的数据。
对于没有任何操作系统或裸板上的应用程序,它的执行会设计许多步骤。
需要了解,在给定的CPU体系下,裸板应用程序的镜像文件的组成方式,这里镜像文件就是可执行文件。
由ADS编译链接器生成的ARM镜像文件,当都由一个或多个域组成,域有两种:加载域与运行域。

  1. 加载域:程序被加载到内存的地方。
  2. 运行域:程序在内存中具体运行时所占有的地方。

一个域由一个或多个输出段组成,而一个输出段由一个或多个输入段组成。
输入段的属性有三种:RO(只读)、RW(可读写)、ZI(可读写但是未被初始化且需初始化为0)。

  1. RO代表的是一个源文件中指令代码与常量,这些在程序中不能改变。
  2. RW代表一个源文件中已经被初始化的变量(全局变量),这些变量可以修改和读取,并且已经被初始化为一个确定的值。
  3. ZI代表未被初始化且初始化为0的变量(ZI段在镜像文件中并不占空间)。

输出段由一个和多个属性相同的输入段组成。
一个简单裸板ARM镜像文件在外部存储器中的大致结构为:
在这里插入图片描述
在该镜像文件结构中,ZI段没有占应有的数据空间,只有一些必要的信息。RW输出段紧跟在RO输出段之后,加载域的RO起始位置与运行域时的起始位置相同。
但在ARM镜像文件执行时,RW段可以不与RO段连续,ZI段与RW段连续。这样设计的原因是让应用程序能充分使用系统有限的内存。
进行完应用程序运行域的生成后,程序就可以开始运行了。
参数的设置是将应用程序不同属性输出段放在RAM的相应位置,这样镜像不必要连续存放,能更充分利用有限的存储资源。

跳转到主程序

跳转到主程序可通过一条bl指令完成。

bl my_MainLoop;

内核基础

RTOS:RTOS可以简单认为是功能强大的主控程序,它嵌入在目标机代码中,系统复位后首先执行;在硬件基础上为应用软件建立一个功能强大的运行环境,用户的应用程序都运行于RTOS之上。
RTOS内含一个实时内核,将CPU、定时器、中断、I/O等资源集中管理起来,为用户提供一套标准API;并可根据各个任务的优先级合理安排任务在CPU上执行。

编写内核

aCoral线程

aCoral调度的基本单位是线程,aCoral的一个线程也可称为一个任务。
真正的RTOS,基本上没有做到进程,只是停留在多线程,因为多进程要解决很多问题,且需要硬件支持,这样就使得系统复杂了,可能影响系统实时性。
线程和进程的区别:
线程之间是共享地址的,也就是说当前线程的地址对于其它线程的地址是可见的,如果修改了地址的内容,其它线程是可以知道,并且能访问的。

int i = 1;
test(){
	sleep(10s);
	printf("%d",i);
}
int main(){
	create_task(test,....);
	i++;
}

如果create_task对应的是创建线程的接口,则test输出2,如果是创建进程的接口,则test输出1。
如果是多进程,main函数所在进程和test所在进程是不能相互访问彼此之间的变量的。

  1. 地址保护。每个进程都有自己的地址空间,如果当前进程跨界访问了其它进程的区域,则会出错,就访问不了这个地址,这种地址保护需要硬件有存储保护单元MPU(Memory Protection Unit)的支持。
  2. 虚拟地址。各个进程仅管访问同一地址,但是由于虚拟地址机制,它们对应的物理地址是不一样的,所以读取的值就会不一样。虚拟地址需要硬件有内存管理单元MMU(Memory Management Unit)的支持。

所以进程之间相互独立、隔离,一个进程的崩溃或错误操作不会影响其它进程。但是无法直接访问全局变量,因为全局变量都变成了进程范围内的全局变量。

所以RTOS很少支持多进程,一是RTOS从单片机发展来的,硬件不支持;二是进程间通信、互斥的开销太大,导致系统复杂,对注重实时性的应用来说,代价太大。

描述线程

aCoral是多线程嵌入式实时操作系统,线程就是一段代码的执行体。

ACORAL_COMM_THREAD test3(acoral_u32 timer){
	while(1){
		acoral_delay_self(timer);
	}
}
void test_delay_init(){
	acoral_print("%d\n",i);
	id = acoral_create_thread(test3,256,500+i*18,"delay",i+1,-l);
}

test_delay_init()创建了34个线程,这些线程都执行相同的代码,即test3。
相同的执行代码为什么是不同的线程呢?因为它们有不同的执行环境,所以线程保护了执行代码+执行环境。
执行环境就是“堆栈+寄存器”。

在aCoral中,线程控制块TCB(Task Control Block)是acoral_thread_t。

typedef struct{
	acoral_res_t res;
#ifdef CFG_CMP
	acoral_spinlock_t move_lock;
#endif
	acoral_u8 state;
	acoral_u8 prio;
	acoral_8 CPU;
	acoral_u32 CPU_mask;
	acoral_u8 policy;
	acoral_list_t ready;
	acoral_list_t timeout;
	acoral_list_t waiting;
	acoral_list_t global_list;
	acoral_evt_t *evt;
	acoral_u32 *stack;
	acoral_u32 *stack_buttom;
	acoral_u32 stack_size;
	acoral_u32 delay;
	acoral_u32 slice;
	acoral_char *name;
	acoral_id console_id;
	void* pricate_data;
	void* data;
}acoral_thread_t;
  • res:线程控制块是一种资源,因此拥有一个称为res的结构体成员。
  • move_lock:用以支持自旋锁,使aCoral支持多核CMP(Chip Multi-Processors)。自旋锁是专为防止多核/处理器并发而引入的一种锁机制。
  • state:线程状态,有五种线程状态ACORAL_THREAD_STATE_READY、ACORAL_THREAD_STATE_SUSPEND、ACORAL_THREAD_STATE_EXIT、ACORAL_THREAD_STATE_RELEASE、ACORAL_THREAD_STATE_RUNNING。
    ACORAL_THREAD_STATE_EXIT意味着某个线程退出了,不会再参与调度,但此时该线程的资源,如线程控制块TCB、堆栈等资源还未释放,而ACORAL_THREAD_STATE_RELEASE状态意味着可以释放这些资源。
  • prio:优先级。
  • CPU:aCoral是一款支持多核的RTOS,这个指示线程在哪个CPU上执行。当前aCoral尚不支持线程迁移:线程创建时在哪个CPU,以后的整个执行过程也都是在该CPU上。
  • CPU_mask:指示线程可以在哪些CPU上执行,如0x1表示线程只可以在CPU0上执行,0x3表示线程可在CPU0、CPU1上执行。
  • policy:线程调度策略,如时间片轮转、先来先服务、周期性调度策略,一种策略对应一类线程。
  • ready、waiting、timeout、global_list:这4个acoral_list_t成员主要是用来将线程结构挂载到相应链表队列上。
    (1)ready:当用户调用了acoral_rdy_thread或acoral_resume_thread接口时,就会将线程挂到就绪队列acoral_ready_queue上。
    (2)waiting:当用户调用了acoral_udrdy_thread或acoral_delay_self接口时,就会将线程挂到延时队列timer_delay_queue上。
    (3)timeout:当线程因为申请某种资源而被阻塞,且超过了预先设置的时间时,则会将线程挂到超时链表队列上。
    (4)global_list:用来将线程挂到全局线程链表。
  • evt:指向线程占用的事件(信号量、互斥量、邮箱等),当线程退出时,必须释放该事件。
  • stack:指示线程的堆栈。在当前线程被其它线程抢占,并切换到其它线程时,当前线程的stack会赋值为CPU堆栈寄存器sp的值。每个线程都有自己的堆栈,用以存放自己的运行环境,当任务切换时,存放被切换进程的运行环境,恢复新线程的运行环境。
  • stack_buttom:这是栈底。一个线程的堆栈是有大小的,当堆栈指针超过了栈底,这时sp指向的内存地址已经不是本线程的内存空间,可能会破坏其它线程的数据结构,严重时会导致系统崩溃。
  • stack_size:堆栈大小。
  • delay:当用户需要延迟某个线程的执行时,用它来指定延迟的时间,单位是Ticks。当用户调用acoral_delay_self时传入的时间参数转化为Ticks再赋给delay。
  • slice:线程执行的时间片,用于同优先级且支持时间片轮转策略的线程调度,内核将根据各个线程的slice来调度线程。
  • name:线程名字。
  • console_id:线程控制台ID号。
  • private_data:长久备用数据指针,目前用于线程策略私有数据指针。
  • data:临时备用数据指针。

TCB里的stack成员隐含了该线程的执行代码信息,因为当任务切换时,stack将保存被切换线程的PC指针,PC指向线程的当前执行代码。

res结构体的定义

typedef union{
	acoral_id id;
	acoral_u16 next_id;
}acoral_res_t;

线程控制块是一种资源,id表示线程的资源ID,当某个资源空闲时,id的高16位表示该资源在资源池的编号,分配后表示该资源的ID。
next_id表示下一资源的ID,它是个空闲链表指针,指向下一个空闲的资源的编号,属于资源ID的一部分。
res代表了资源的ID,资源ID由资源类型Type和空闲内存池ID两部分组成。
aCoral定义了6种资源类型:线程型、事件型、时钟型、驱动型、GUI型、用户使用型。

#define ACORAL_RES_THREAD 1
#define ACORAL_RES_EVENT 2
#define ACORAL_RES_TIMER 3
#define ACORAL_RES_DRIVER 4
#define ACORAL_RES_GUI 5
#define ACORAL_RES_USER 6

如果资源为线程,则其类型Type为1。
aCoral采用了资源池的内存管理方式,而资源池由结构acoral_pool_t定义。
空闲内存池ID由aCoral内存管理模块在初始化分配内存时,根据当前内存块数确定。
aCoral启动完成后,若用户要创建某一新线程,将调用函数acoral_get_free_pool(),从空闲内存资源池中获取一空闲内存,并获取其ID号,将申请的内存空间供该线程使用。

typedef struct{
	void *base_adr;//在空闲时指向下一个pool,否则为管理的资源的基地址。
	void *res_free;//指向下一空闲资源
	...
}acoral_pool_t;
资源池初始化
void acoral_pools_cinit(void);
创建某一资源池
acoral_err acoral_create_pool(acoral_pool_ctrl_t *pool_strl);

aCoral的优先级与数字大小成反比,即:数字越大,优先级越低。

#ifdef CFG_THRD_POSIX
	#define ACORAL_MAX_PRIO_NUM ((CFG_MAX_THREAD+CFG_POSIX_START_NUM+1) & 0xff)
#else
	#define ACORAL_MAX_PRIO_NUM ((CFG_MAX_THREAD+1) & 0xff)
#define ACORAL_INIT_PRIO 0 #aCoral的初始优先级为0
#define ACORAL_MAX_PRIO 1 #aCoral的最高优先级为1
#define ACORAL_MIN_PRIO ACORAL_MAX_PRIO_NUM-1 #最小优先级是总的优先级数减一

一般情况下ACORAL_MAX_PRIO_NUM-1=100,如果为了支持POSIX线程标准((CFG_MAX_THREAD+CFG_POSIX_START_NUM+1) & 0xff) =130

acoral_list_t是一个双向链表,如果将aCoral配置为支持CMP,acoral_list_t还定义了自旋锁acoral_spinlock_t。

struct acoral_list{
	struct acoral_list *prev,*next;
#ifdef CFG_CMP
	acoral_spinlock_t lock;
#endif
};

这种通过TCB成员定义的结构(acoral_list_t)来挂到相应链表队列上的方式的优点是:可以用相同数据处理方式来描述所有双向链表,不用再单独为各个链表编写各种函数。

线程优先级

aCoral的就绪队列采用的是优先级链表,每个优先级是一个链表,相同优先级的线程都挂在此链表上。
对于RTOS,几乎都是采用了基于优先级的抢占调度策略。
为了支持基于优先级的抢占调度,aCoral的优先级通过acoral_prio_array来定义。

struct acoral_prio_array{
	acoral_u32 num; //就绪任务总数
	acoral_u32 bitmap[PRIO_BITMAP_SIZE];//标识某一优先级是否有就绪队列,这样才能确保以O(1)复杂度找出最高优先级的线程。
	//PRIO_BITMAP_SIZE由系统配置的优先级总数确定
	acoral_queue_t queue[ACORAL_MAX_PRIO_NUM];//优先级链表数组,数组成员是一个链表队列acoral_queue_t,挂在该优先级的就绪队列。
}

优先级位图数组bitmap[PRIO_BITMAP_SIZE]每个变量都是32位。
每个变量从右到左的每一位依次代表一个优先级(bitmap的每个变量共代表了32个优先级),而每一位由“0”,“1”两个可能值。

  • “0”:该位对应的优先级没有任务就绪。
  • “1”:该位对应的优先级有任务就绪。

调度策略

aCoral把线程相关的操作统称为线程调度。
把调度分为两层,上层策略,下层机制,采用策略与机制分离的原则,可以灵活方便地扩展调度策略,而不改变底层的调度机制。

  • 调度策略就是如何确定线程的CPU、优先级prio等参数,线程是按照什么策略来调度。一种策略对应一种线程。
  • 调度机制根据调度策略来安排任务的具体执行,如何创建线程?…

线程调度分层结构

调度策略本质就是调度算法,即确定任务执行顺序的规则。
调度策略目前包括通用策略、分时策略、周期策略和RM策略,用户还可以自行扩展新的调度策略。
当用户创建线程时,需要指定某种调度策略,并找到对应的策略控制块,再为TCB成员赋值。
在这里插入图片描述
线程创建的最后一步就是将其挂到就绪队列上,之后由调度机制来负责具体任务调度。

调度策略分类

一种调度策略对应一种线程。

  1. 普通线程(通用策略创建的线程)。这种线程,需要人为指定CPU、优先级信息,通过acoral_thread_create创建。
  2. 分时线程(分时策略创建的线程)。aCoral支持相同优先级的线程,对于相同优先级的线程,默认采用FIFO方式调度。当用户需要线程以分时的方式和其它线程共享CPU时,可以将线程设置为分时线程。需要注意(1)只存在相同优先级的分时策略,不同优先级线程之间不存在所谓的分时策略,而是按优先级抢占策略来调度的。(2)必须是两厢情愿的,当a分时后,它执行指定时间片后会将CPU移交给b。
  3. 周期线程(周期策略创建的线程)。这种线程每隔一个固定时间就要执行一次。这种需求在嵌入式实时系统较常见,如信号采集系统有一个采样周期,每隔一段时间要采集一路信号。
  4. RM线程(RM策略创建的线程)。RM是一种可以满足任务截止时间的强实时调度算法,这种策略需要周期性线程策略的支持。
  5. POSIX线程(POSIX策略创建的线程)。POSIX线程属于非实时线程,这类线程的主要特点是越公平越好,这种线程的调度算法是电梯调度算法。
typedef struct{
	acoral_list_t list; //策略链表结点,用于将策略挂到策略链表上去。
	acoral_u8 type; //策略类型ACORAL_SCHED_POLICY_COMM、ACORAL_SCHED_POLICY_SLICE、ACORAL_SCHED_POLICY_PERIOD、ACORAL_SCHED_POLICY_RM、ACORAL_SCHED_POLICY_POSIX
	acoral_id (*policy_thread_init)(acoral_thread_t*,void (*route)(void *args),void *,void *); //策略初始化函数,用于确定线程的CPU、优先级prio等
	void (*delay_deal)(); //与延时相关的处理函数,period、slice等策略都要用到类似的延时机制。
	acoral_char *name; //用于传递某种调度策略所需要的参数,每种策略对应一种数据结构,用来保存线程的参数,不同策略需要的参数不同,用户创建线程时传递的数据结构也不一样,比如普通策略的参数只有CPU、prio
}acoral_sched_policy_t;

查找调度策略

当用户想根据某种调度策略创建线程时,须根据TCB的policy成员值从策略控制块链表中查找到相应的结点,将信息取出赋值给相应的TCB成员。
具体查找过程:

acoral_sched_policy_t *acoral_get_policy_ctrl(acoral_u8 type){
	acoral_list_t *tmp,*head;
	acoral_sched_policy_t *policy_ctrl;
	head = &policy_list.head;
	tmp = head;
	for(tmp=head->next;tmp!=head;tmp=tmp->next){
		policy_ctrl = list_entry(tmp,acoral_sched_policy_t,list);
		if(policy_ctrl->type == type){
			return policy_ctrl;
		}
	}
	return NULL;
}

注册调度策略

若要在aCoral扩展新的调度策略并生效,须进行注册,注册后,用户才能通过此策略创建特定类型的线程。
注册就是将用户自己定义的调度策略挂载到策略控制块上,放在队列尾(将策略挂到策略链表上)。

void acoral_register_sched_policy(acoral_sched_policy_t *policy){
	acoral_list_add2_tial(&policy->list,&policy_list.head);
}

通用调度策略在进行通用调度策略初始化com_policy_init()时注册

void comm_policy_init(){
	comm_policy.type = ACORAL_SCHED_POLICY_COMM;
	comm_policy.policy_thread_init = comm_policy_thread_init;//绑定策略初始化函数
	...
	acoral_register_sched_policy(&comm_policy);//绑定完后,对策略进行注册,挂载到策略控制块链表的尾部。
}

通用调度策略初始化(comm_policy_init())是在aCoral调度策略初始化(acoral_sched_policy_init())时被调用的。

void acoral_sched_policy_init(){
	acoral_list_init(&policy_list.head);
	comm_policy_init();
#ifdef CFG_THRD_SLICE
	slice_policy_init();
#endif
#ifdef CFG_THRD_PERIOD
	period_policy_init();
#endif
#ifdef RM_THRD_SLICE
	rm_policy_init();
#endif
#ifdef CFG_THRD_POSIX
	posix_policy_init();
#endif
}

aCoral调度策略初始化(acoral_sched_policy_init())在aCoral系统初始化时启用。

// 内核各模块初始化
void acoral_module_init(){
	/*中断系统初始化*/
	acoral_intr_sys_init();
	/*内存管理系统初始化*/
	acoral_mem_sys_init();
	/*资源管理系统初始化*/
	acoral_res_sys_init();
	/*线程管理系统初始化*/
	acoral_thread_sys_init();
	/*时钟管理系统初始化*/
	acoral_time_sys_init();
	/*事件系统初始化*/
	acoral_evt_sys_init();
#ifdef CFG_DRIVER
	acoral_drv_sys_init();
#endif
}

在这里插入图片描述
acoral_start()是内核各模块初始化的入口,也是aCoral系统初始化的入口,当CPU启动完成后,就会通过

ldr		pc,=acoral_start

进入acoral_start(),开始aCoral的启动工作。至此,对CPU等硬件资源的管理由裸板程序时代进入操作系统时代。

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

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

相关文章

JAVAWeb开发(基于分片的网络状态查询方法、装置及存储介质)

本文基于分片的网络状态查询方法、装置及存储介质已是申请的专利。本人为第一发明人,这里给出来是提供一种写专利的范本,仅供参考。专利申请号:CN202110346967.5正文部分:技术领域本申请涉及但不限于计算机网络数据传输一致性领域…

opencv--颜色物体追踪 图片的形态学处理函数

目录 一、主要函数介绍 1. cv2.erode() 2. cv2.dilate() 3. cv2.findContours() 4. cv2.circle() 5. cv2.line() 二、代码 这里首先确定是否安装imutils库,这个库能让调整大小或者翻转屏幕等基本任务更加容易实现。这一次主要应用的是对于图片的形态学处理函…

【Android春招】Android基础day1

一、填空题 1.Android是基于__ 的移动端开源操作系统。 Linux 2.Android系统是由__公司推出的。 谷歌 3.Android 11对应的API编号是__。 30 4.App除了在手机上运行,还能在电脑的__上运行。 模拟器(AVD&…

测试之概念篇【需求、测试用例、Bug描述、产品的生命周期、开发模型、测试模型】

文章目录1. 什么是需求2. 测试用例是什么3. Bug 是描述4. 产品的生命周期5. 软件测试贯穿于软件的整个生命,如何贯穿?6. 开发模型(瀑布模型、螺旋模型、增量模型和迭代模型、敏捷模型)7. 测试模型(V模型、W模型&#x…

【Java寒假打卡】Java基础-BigDecimal

【Java寒假打卡】Java基础-BigDecimal构造方法四则运算BigDecimal的特殊方法基本数据类型包装类自动装箱与自动拆箱Integer的类型转换将数字字符串进行拆分成整数数组构造方法 package com.hfut.edu.test1;import java.math.BigDecimal;public class test3 {public static void…

Crontab命令详解

crontab命令是Unix和Linux用于设置周期性被执行的指令,是互联网很常用的技术,很多任务都会设置在crontab循环执行。crontab命令可以精确到分(精确到秒的一般写脚本),相当于闹钟。 如果不使用crontab,那么任…

GD32F103-TIMER模块

定时器是一个功能强大的外设。 一般功能: 定时中断,计时器,给定一个时间,到达时间后产生一个中断定时器输出比较的功能,用于PWM波形的产生,驱动电机定时器输入捕获,测频率 核心关键参数&#…

基于Java+SpringBoot+vue+element实现新冠疫情物资管理系统详细设计

基于JavaSpringBootvueelement实现新冠疫情物资管理系统详细设计 博主介绍:5年java开发经验,专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 欢迎点赞 收藏 ⭐留言 文末获取源码联系方式 文章目录基于…

文件IO----(open、close、read、write、lseek)

1.文件IO 介绍:(系统IO、系统调用) POSIX(可移植操作系统接口)定义的一组函数,不提供缓冲机制,每次读写操作都引起系统调用,核心概念是文件描述符,访问各种文件类型,Lin…

Keychron 键盘指南

文章目录QQ1Q2Q3Q4Q5KK1K2k3 && k3proK3 VS K7k6 && K6prok7K8k10K12K14CVV1V2V3V4V5V6SQ Q1 Q1 是一款革命性的全金属键盘,每个开关、键帽、稳定器、旋钮甚至面板都具有可定制的功能。它专为个性化体验和卓越的打字舒适度而设计。 双垫片设计…

Cadence PCB仿真使用Allegro PCB SI配置仿真库的方法图文教程

⏪《上一篇》   🏡《总目录》   ⏩《下一篇》 目录 1,概述2,配置方法3,总结1,概述 本文简单介绍使用Allegro PCB SI软件选择需要仿真的网络的方法。 2,配置方法 第1步:打开待仿真的PCB文件,并确认软件为Allegro PCB SI 如果,打开软件不是Allegro PCB SI则可这样…

模型微调,低预算,高期望!

作为迁移学习中的常用技术,Fine-tuning(微调)已经成为了深度学习革命的重要部分。微调不需要针对新任务从头开始学习,只需要加载预训练模型的参数,然后利用新任务的数据进行一步训练模型即可。也可以说微调是对开放域任…

一文简单了解并部署Zookeeper集群

GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源。GreatSQL是MySQL的国产分支版本,使用上与MySQL一致。作者:蟹黄瓜子文章来源:GreatSQL社区投稿 1.Zookeeper概述 Zookeeper对于很多人开始可能都有所耳闻&am…

基于Java+SpringBoot+vue+element实现爱心捐赠平台系统

基于JavaSpringBootvueelement实现爱心捐赠平台系统 博主介绍:5年java开发经验,专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java毕设项目精品实战案例《500套》 欢迎点赞 收藏 ⭐留言 文末获取源码…

排他思想的运用

思路简述 需求:桌子上有一排灯,点哪个亮哪个,但是要求每次只能点亮一盏。如果不使用排他思想,操作过程如下: 第一次打开一盏灯,记为 A,记录下来。第二次打开灯之前,先去找记录&…

[cpp进阶]C++智能指针

文章目录为什么需要智能指针?智能指针的原理及使用智能指针的原理智能指针的使用C中的智能指针C智能指针的发展历程std::auto_ptrstd::auto_ptr的使用std::auto_ptr的模拟实现std::unique_ptrstd::unique_ptr的使用std::unique_ptr的模拟实现std::shared_ptrstd::shared_ptr的…

Springboot @InitBinder处理from-data表单传参,指定参数默认新增前缀

前言 有兄弟突然找到我,江湖救急,我以为是啥问题呢? 一看这位小兄弟也是半路出家, 没有对springboot的常用注解有过研究。 不过没大碍,还是那句话, 学习的事情,只有先知和后知 现在你看完这篇…

纳米软件分享:光伏逆变器ATE测试系统,逆变器测试解决方案

光伏并网逆变器(以下简称“逆变器”)是光伏发电系统的核心部件之一,其主要功能是将光伏阵列的直流逆变为符合电网接入要求的交流电并入电网。并网逆变器ATE测试平台,主要是模拟光伏阵列特性输入的直流电源、模拟电网电源、系统控制…

Java垃圾分类查询管理系统源码+数据库,基于SpringBoot+mybatis-plus,垃圾分类查询及预约上门回收

垃圾分类查询管理系统 完整代码下载地址:Java垃圾分类查询管理系统源码数据库 1.介绍 垃圾分类查询管理系统,对不懂的垃圾进行查询进行分类并可以预约上门回收垃圾。 让用户自己分类垃圾, 按国家标准自己分类, 然后在网上提交订…

java学习之main方法

目录 一、main方法的注意事项 二、在IDEA中传入参数 一、main方法的注意事项 形式:public static void main(String[] args){},main方法是一个静态方法,访问修饰符是:public,形参是String数组 args 注意事项&#xf…