【RTOS学习】信号量 | 互斥量 | 递归锁

🐱作者:一只大喵咪1201
🐱专栏:《RTOS学习》
🔥格言:你只管努力,剩下的交给时间!
图

信号量 | 互斥量 | 递归锁

  • 🍺信号量
    • 🥤原理
    • 🥤使用信号量的函数
    • 🥤基本使用
  • 🍺互斥量
    • 🥤原理
    • 🥤使用互斥量的函数
    • 🥤互斥量的基本使用
    • 🥤优先级反转
    • 🥤优先级继承
  • 🍺递归锁
    • 🥤大概原理
    • 🥤使用递归锁的函数
    • 🥤使用
  • 🍺总结

🍺信号量

信号量也是FreeRTOS实现同步与互斥的方式。

图
如上图便是信号量的模型,任务A和任务B是生产者任务,负责生产数据,任务C和任务D是消费者任务,负责消费数据。

生产者每生产一个数据,信号量就加1,当增加到用户设定的限定值时直接失败返回。

消费者每消费一个数据,信号量就减一,当信号量减到0的时候,消费者任务就处于阻塞状态,直到新数据到来才被唤醒。

  • 被唤醒时,谁的优先级高就唤醒谁,优先级相同就唤醒阻塞时间最长的任务。

从信号量这个名字来看:

  • 信号:起通知作用
  • 量:还可以用来表示资源的数量

没有限制时,它就是“计数型信号量”,当只有0、1两个取值时,它就是“二进制信号量”。

  • 二进制信号量和计数型信号量的唯一差别,就是计数值的最大值被限定为1。
  • 支持give给出资源,计数值加一,还支持take获得资源,计数值减一。
  • 信号量本身就是一个共享资源。

计数型信号量的典型场景是:

  • 计数:数据产生时"give"信号量,让计数值加1;处理数据时要先"take"信号量,就是获得信号量,让计数值减1。
  • 资源管理:要想访问资源需要先"take"信号量,让计数值减1;用完资源后"give"信号量,让计数值加1。
  • 信号量只表示数据的存在情况,相当于买票时票的余量,它并不管理数据本身。

🥤原理

图

如上图,在使用信号量的时候需要先调用xSemaphoreCreateCounting来创建信号量,可以看到,它底层调用的是xQueueCreateCountingSemaphore函数,从名字就可以看出其实是创建了一个队列。

在函数内部会调用xQueueGenericCreate创建一个队列,该队列的大小就是指定信号量的计数值。

  • 信号量的上限值就是队列的长度。

图
如上图,使用xSemaphoreGive增加信号量时,其本质就是向队列中写数据,每增加1就写一个数据。

使用xSemaphoreTake减少信号量时,其本质就是从队列中读取数据,每减1就读取一个数据。

由于信号量的底层其实是队列,所以它实现同步与互斥的原理是和队列是类似的。

信号量和队列的对比:

队列信号量
可以容纳多个数据,有两部分内存:队列结构体、存储数据的空间只有计数值,无法容纳其他数据
生产者:没有空间存入数据时可以阻塞生产者:不用阻塞,计数值达到最大时失败返回
消费者:没有数据时可以阻塞消费者:没有资源时可以阻塞
  • 除了使用内存上的区别外,最大的区别就是信号量的生产者任务一般不阻塞,失败了就直接返回。

🥤使用信号量的函数

使用信号量时,先创建、然后去添加资源、获得资源。使用句柄来表示一个信号量。

创建二进制信号量:

/* 动态创建 */
SemaphoreHandle_t xSemaphoreCreateBinary( void );/* 静态创建 */
SemaphoreHandle_t xSemaphoreCreateBinaryStatic(StaticSemaphore_t *pxSemaphoreBuffer );
  • 动态创建时不用传参,只需要接收返回的信号量句柄。
  • 静态创建时,需要用户指定存放信号量的内存空间pxSemaphoreBuffer

创建计数型信号量:

/* 动态创建 */
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount);/* 静态创建*/					
SemaphoreHandle_t xSemaphoreCreateCountingStatic( UBaseType_t uxMaxCount, UBaseType_t uxInitialCount, StaticSemaphore_t *pxSemaphoreBuffer );
  • uxMaxCount:最大计数值
  • uxInitialCount:计数初始值
  • pxSemaphoreBuffer:静态创建时需要指定存放信号量的内存。
  • 返回值:创建成功返回信号量句柄

增加信号量:

BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
  • xSemaphore :要增加的信号量句柄。
  • 返回值:成功返回pdTRUE,失败返回pdFALSE

获取信号量:

BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore,TickType_t xTicksToWait);
  • xSemaphore:要减少的信号量句柄
  • xTicksToWait:无法获得信号量时的阻塞时间,0:不阻塞,马上返回,portMAX_DELAY一直阻塞直到被唤醒。
  • 返回值:成功返回pdTRUE,失败返回pdFALSE

删除信号量:

对于动态创建的信号量,不再需要它们时,可以删除它们以回收内存。

void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );
  • xSemaphore :要删除的信号量句柄
  • 无返回值,必然会成功。

🥤基本使用

二进制信号量:

通过二进制信号量来解决两个任务同时使用一个串口的时的缺陷:

图
如上图,创建一个二进制信号量,此时信号量的初始值是0,所以需要先使用xSemaphoreGive给它增加1。

当两个任务在使用串口时,先Take信号量,如果Take成功才能使用串口,否则就阻塞等待,使用完毕后需要Give信号量,以便另一个任务使用串口。

图
如上图,两个任务在交替使用串口。

这个过程中,两个任务只能有一个任务使用串口,有任务在使用串口,这个二进制信号量就是0,另一个任务就无法Take,只能阻塞,就实现了互斥的目的。

  • 使用信号量不存在Take到一半时被切换走,因为获取过程中会关闭所有中断,不会调度其他任务。
  • 这种要么不做,要做就做完的性质称为原子性

信号量的TakeGive操作是具有原子性的。

计数型信号量:

tu
如上图,在使用计数型信号量之前,必须先定义宏开关configUSE_COUNTING_SEMAPHORES

图
如上图代码,先创建计数型信号量,计数上限是3,初始值是0,然后创建两个任务,发送任务vSender用来增加信号量,优先级是2,接收任务vReceiver用来减少信号量,优先级是1。

图
如上图代码所示,任务vSender的优先级高,所以先执行,它连续四次增加信号量,增加成功打印成功对应的信息,成功计数值加一,失败的话打印失败信息,失败计数值加一,然后进入延时阻塞状态。

任务vReceiver的此时才有机会运行,它不断减少信号量,减少成功就打印成功信息,成功计数值加一,失败的话就打印失败信息,失败计数值加一,等vSender延时结束后抢占CPU,继续增加信号量,如此反复。

图
如上图所示运行结果,vSender任务连续四次Giver信号量中,只有前三次是成功的,因为信号量的上限是3。

vReceiverTake信号量时,也值能成功三次,因为信号量的计数值只到3,由于它是阻塞式Take,所以没有信号量后就处于阻塞状态了,故而没有答应错误信息。

🍺互斥量

拿最开始(上篇文章)中上厕所的例子来说,怎么独享厕所?你当然可以进去后,让别人帮你把门:但是,命运就掌握在别人手上了。

使用队列、信号量,都可以实现互斥访问,以信号量为例:

  • 信号量初始值为1
  • 任务A想上厕所,"take"信号量成功,它进入厕所
  • 任务B也想上厕所,"take"信号量不成功,等待
  • 任务A用完厕所,"give"信号量;轮到任务B使用

这需要两个前提才能保证安全:

  • 任务B很老实,不撬门(不"give"信号量)。
  • 没有坏人:别的任务不会"give"信号量。

前面信号量的介绍中也可以看到,负责givetake信号量的任务并不是同一个。

所以说,使用信号量确实可以实现互斥访问,但是并不完美,最完美的方式是:自己开门上锁,完事了自己开锁。

使用互斥量就可以解决这个问题:
t图
如上图,互斥量的值只能为1或者0,它也经常被称为,也就是只有两个状态,上锁和解锁。

任务A上锁后,互斥量就变为0,此时其他任何一个任务都无法再申请到这个锁,只有任务A解锁,互斥量变为1后,其他任务才能申请到锁进行上锁。

  • 它的核心在于:谁上锁,就只能由谁开锁。

很奇怪的是,FreeRTOS的互斥锁,并没有在代码上实现这点,甚至是Linux也没有实现:即使任务A获得了互斥锁,任务B竟然也可以释放互斥锁,后面在使用本喵会用代码演示。

  • 谁上锁,谁就解锁,这只是一个约定,需要程序员自己去维护。

在多任务系统中,任务A正在使用某个资源,还没用完的情况下任务B也来使用的话,就可能导致问题。比如对于串口,任务A正使用它来打印,在打印过程中任务B也来打印,客户看到的结果就是A、B的信息混杂在一起。

再比如对于同一个变量,比如 int a ,如果有两个任务同时写它就有可能导致问题:

图

对于变量a的修改,C代码只有一句a = a + 8,但是实际上它分3步实现:读出原数值,修改数值,写回到内存。

我们想让任务A、B都执行add_a函数,函数执行两次,最终的结果是1 + 8 + 8 = 17

现在假设任务A运行完代码①,在执行代码②之前被任务B抢占了:现在任务A的R0等于1。任务B执行完add_a函数,a等于9。

任务A继续运行,在代码②处R0仍然是被抢占前的数值1,执行完②③的代码,a等于9,这跟预期的17不符合。

  • 修改变量、设置结构体、在16位的机器上写32位的变量,这些操作都是非原子的。也就是它们的操作过程都可能被打断,如果被打断的过程有其他任务来操作这些变量,就可能导致冲突。

上述问题的解决方法是:任务A访问全局变量、函数代码时,独占它,就是上个锁。这些全局变量、函数代码必须被独占地使用,它们被称为临界资源

🥤原理

图
如上图,使用互斥量之前需要先调用xSemaphoreCreateMutex创建互斥量,其实是在调用xQueueCreateMutex,它的底层会创建一个队列,长度为1。

从创建互斥量函数的名字中也可以看出,其实就是一个类似二进制型的信号量,底层同样也是用的队列。

所以当锁被任务A申请走以后,任务B就会被阻塞,同样是放在队列的接收阻塞链表中,当任务A归还锁后,任务B才会被唤醒去申请锁。

  • 为了独立理解互斥量,就认为它是一把锁,只有上锁和开锁两种状态,申请锁后就上锁了,归还后就开锁了。

既然是互斥量的本质就是一个二进制型的信号量,那么申请锁和归还锁用的也是信号量的TakeGive方式。


互斥量也被称为互斥锁,使用过程如下:

  • 互斥量初始值为1
  • 任务A想访问临界资源,先获得并占有互斥量,然后开始访问
  • 任务B也想访问临界资源,也要先获得互斥量:被别人占有了,于是阻塞
  • 任务A使用完毕,释放互斥量;任务B被唤醒、得到并占有互斥量,然后开始访问临界资源
  • 任务B使用完毕,释放互斥量

正常来说:在任务A占有互斥量的过程中,任务B、任务C等等,都无法释放互斥量。但是FreeRTOS未实现这点:任务A占有互斥量的情况下,任务B也可释放互斥量,后面本喵会演示。

🥤使用互斥量的函数

创建互斥量:

/* 动态创建 */
SemaphoreHandle_t xSemaphoreCreateMutex( void );
/* 静态创建 */
SemaphoreHandle_t xSemaphoreCreateMutexStatic( StaticSemaphore_t *pxMutexBuffer );
  • pxMutexBuffer :静态创建时需要用户指定存放互斥量的内存空间。
  • 返回值:用于控制互斥量的句柄。

申请锁/释放锁:

/* 申请锁 */
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore,TickType_t xTicksToWait );
/*释放锁*/
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
  • xSemaphore:互斥量的句柄。
  • xTicksToWait :等待时间
  • 返回值:成功返回pdTRUE,失败返回pdFALSE
  • 互斥量本质上就是信号量,所以互斥量的句柄类型也是SemaphoreHandle_t

删除互斥量:

void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );

🥤互斥量的基本使用

tu
如上图,使用互斥锁之前,需要先定义宏configUSE_MUTEXS

一个任务上锁,另一个任务解锁:

图
如上图,创建一个互斥量,再创建两个任务,申请锁任务优先级是2,释放锁任务优先级是1,优先级为2的任务先执行。

图
如上图,Take任务先执行,先申请互斥锁,成功则打印成功信息后阻塞,失败则直接阻塞,让GiveAndTake任务有机会执行。

GiveAndTake任务执行时,先尝试申请一下互斥锁,成功则打印成功信息,不成功则打印失败信息,并且将互斥锁私自释放,释放成功则打印相关信息,然后再申请互斥锁,成功则打印成功信息后阻塞,失败后直接阻塞。

  • GiveAndTake尝试申请互斥锁,如果不成功则监守自盗。

图
如上图,Take申请互斥锁成功后,GiveAndTake任务是无法再次申请到这个互斥锁的,进而私自释放互斥锁,然后再申请互斥锁,从而监守自盗成功。

  • 切记切记,我们写代码时不要这么做。

两个任务使用同一个串口:

仍然使用两个任务使用串口的例子,虽然用二进制信号量解决了,但是最恰当的还是信号量:
图
如上图,创建互斥量后,不用像二进制信号量那样要Give一下进行初始化,互斥量创建函数中内部已经进行了初始化。

图
如上图,在使用串口前需要先上锁,使用完毕后再解锁,当任务1上锁了以后,任务2是无法使用串口的。

  • 上锁和解锁之间的代码就是临界区,上锁能够保证临界区的安全,让所有任务串行执行临界区代码。

图
如上图,此时任务1和任务2在交替使用串口,比如不会出现打印信息的混乱。

🥤优先级反转

互斥锁虽然没有实现谁上锁谁解锁,但是它实现了优先级继承,用来解决优先级反转的问题。

图
如上图,此时有三个任务,任务A的优先级是1,任务B的优先级是2,任务C的优先级是3,它们共同使用一把互斥锁。

任务B和任务C先阻塞一会,让任务A先运行,任务A在运行的过程中申请了互斥锁,当任务B和任务C延时结束以后,最高优先级的任务C开始运行。

任务C在运行的过程中也要申请这把锁,但是锁已经被任务A申请走了,任务A此时已经被切换走了进入阻塞状态,它抱着锁走了。所以任务C无法申请到锁也进入阻塞状态。

此时能运行的就只有任务B了,因为任务B的优先级高于任务A,所以任务A始终得不到运行,也就始终无法释放锁。

  • 这就导致,优先级最高的任务C因为无法申请到锁而阻塞无法运行,优先级发生了反转。

图

如上图代码,创建三个任务,分别执行低优先级任务vLPTask,中优先级任务vMPTask,高优先级任务vHPTask,也就是任务A,任务B和任务C。

  • 这里必须创建二进制信号量,不能创建互斥锁,因为二进制信号量没有解决优先级反转的功能,才能看到优先级反转的实验现象。

图
如上图,低优先级任务中,将对应的标志位置一,然后申请互斥锁,之后进行长时间的运算,运算完毕后归还锁。

图
二上图,中优先级任务先延时,防止抢占低优先级任务,让低优先级任务先运行。

图
如上图代码,高优先级任务一开始也进入延时,防止抢占低优先级任务,延时结束后申请互斥锁,然后再释放互斥锁。

图
如上图所示运行结果,低优先级任务先执行,成功申请互斥锁,但是还没有来得及归还,高优先级任务抢占执行,低优先级任务抱着锁被切走了。

高优先级任务运行后也申请锁,但是申请失败,所以阻塞了,此时中优先级任务得到了机会始终运行。

  • 这种现象就是优先级反转。

🥤优先级继承

而互斥锁的成功解决了优先级反转这一问题,它是通过优先级继承解决的。

图
如上图,前面部分和优先级反转一样,任务C因为无法申请到锁而进入阻塞状态。

但是使用互斥锁时,短暂地将任务C的高优先级3赋给任务A,此时任务A的优先级就比任务B的优先级高,所以此时执行的是任务A而不是B。

直到任务A将锁归还以后,立刻将任务A的高优先级回收,任务A的优先级又变成了1,此时任务C的优先级最高,所以任务C执行。

由于任务A已经将锁归还了,所以任务C可以顺利的申请到锁而继续执行下去,此时就符合最高优先及的任务抢占执行的特性了。

  • 优先级继承就是,短暂提高低优先级任务到高优先级,让它有机会执行,归还互斥锁,然后再恢复到低优先级。

图
如上图代码,只需要将原本创建的二进制型信号量变成创建互斥量,就可以解决优先级反转的问题,其他代码都不用作任何改变。

图
如上图运行结果,和我们分析的一致,当高优先级任务因为低优先级任务抱着锁被切走而无法申请锁时,短暂的赋予低优先级任务高优先级的权利。

原本的低优先级任务得以执行,释放互斥锁后恢复到了原本的低优先级,此时高优先级任务抢占执行,并且申请锁成功,不再阻塞,从而始终执行。

在优先级继承这个过程中,中优先级任务就没有机会被执行,此时就完美解决了优先级反转的问题。

🍺递归锁

假设这样的场景: 任务 A 获得了互斥锁 M,它又调用了一个函数,这个函数函数也要去获取同一个互斥锁 M,于是它阻塞,此时任务 A 休眠,等待任务 A来释放互斥锁!

  • 任务A自己阻塞不动了,还在等着自己释放互斥锁来救自己。
  • 此时就产生了死锁

就像我们找工作的时候,公司只招有工作经验的人,但是我们没有工作经验,又只能去找工作。

图
如上图代码所示,创建一个互斥锁,再创建一个任务,在该任务中申请锁,申请成功后打印一句话表示自己在运行,然后调用另一个函数,在这个函数中也申请这个锁,申请成功也打印一句话,表示该函数在执行,没有申请成功就阻塞。

图
如上图,只有新创建的任务在申请锁成功够打印了一句话,它调用的另一个函数并没有打印,说明这个函数申请锁失败了。

但是程序并没有运行下去,此时整个任务处于阻塞状态,因为在两处申请了同一把锁,而且第一次申请完后还没有来得及释放,这就是发生了死锁

  • 普通的互斥锁,在FreeRTOS中,发生死锁后,可以由其他任务来释放锁,但是不建议这么做,因为它设计的初衷就是谁申请,谁释放。

这样来说,上面这种情况就不能有吗?如果就是需要这种场景呢?此时就可以使用递归锁(Recursive Mutexes)

  • 任务A获得递归锁M后,还可以多次去获得这个锁。
  • Take了N次,要GiveN次,这锁才会释放。
  • 递归锁实现了由谁上锁就必须由谁解锁,其他人不能解锁。

🥤大概原理

图
如上图,递归锁的结构大概如上图所示,它虽然也是一个互斥量,但是和互斥量不同的是,它还有一个专门用来记录任务TCB节点的成员,以及一个记录申请锁次数的计数值。

Task1申请到递归锁以后,TCB*成员中存放的就是Task1,此时Task2再来申请这个递归锁时,由于和记录的TCB节点不符,就会拒绝Task2申请递归锁。

对于Task1,它可以多次申请递归锁,每Take申请一次,递归锁内部的计数值就会加一,每Give释放一次,该计数值就会减一。

所以Task1申请了多少次就必须释放多少次,否则这个锁就一直属于Task1,其他任务无法申请。

🥤使用递归锁的函数

递归锁的函数和普通互斥锁的函数名不一样,但是参数类型一样,所以本喵就不介绍它的参数类型了:

功能递归锁普通互斥锁
创建xSemaphoreCreateRecursiveMutexxSemaphoreCreateMutex
申请锁xSemaphoreTakeRecursivexSemaphoreTake
释放锁xSemaphoreGiveRecursivexSemaphoreGive

可以看到,递归锁的常用操作也是只有这三个,在函数名上比普通互斥锁多了Recursive表示这是递归锁的函数。

🥤使用

图
如上图,在使用递归锁之前,先要定义互斥锁的宏开关configUSE_RECURSIVE_MUTEXES

一个任务申请递归锁另一个释放:

图
如上图,创建两个任务,任务1的优先级是2,任务2的优先级1,开始调度后让任务1先执行。

图
如上图,任务1先开始运行,连续多次申请递归锁,每申请成功一次就打印一次成功信息,失败则打印失败信息,当多次申请完毕后,让自己进入阻塞状态,让出CPU让任务2执行。

任务2开始执行后,先释放任务1申请的递归锁,如果释放成功则打印成功信息,失败则打印失败信息,之后再申请任务1的递归锁,如果申请成功则打印成功信息,失败打印失败信息。

图
如上图,任务1五次申请递归锁都成功,此时意味着递归锁中的计数值就成了5。任务2 释放 任务1申请的递归锁失败了,之后 申请 任务1的递归锁也失败了。

  • 谁申请的递归锁,必须由谁释放。
  • 递归锁只能由一个任务申请。

解决死锁问题:

图
如上图,仍然是前面产生死锁的例子,只是这里将普通互斥锁换成了递归锁,将申请锁和释放锁的方式换成对应递归锁的方式,其他没有变。

图
如上图,此时就不再有死锁的情况了,两个函数都可以执行,OtherFunctionTaskFunction申请递归锁还没有释放的情况下又再次申请成功。

🍺总结

信号量是基于队列构建的,只负责管理数据的余量,不管理数据本身,互斥量是基于二进制型信号量构建的,它的计数值只有0和1,并且增加了一个优先级继承功能来解决优先级反转的问题。又衍生出了递归锁解决了死锁问题和监守自盗的问题。

这三种结构,最底层还是队列,所以它们实现同步与互斥的原理和队列是相同的。

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

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

相关文章

基于springboot实现java学习平台项目【项目源码+论文说明】

基于springboot实现java学习平台演示 摘要 在Internet高速发展的今天,我们生活的各个领域都涉及到计算机的应用,其中包括学习平台的网络应用,在外国学习平台已经是很普遍的方式,不过国内的管理平台可能还处于起步阶段。学习平台具…

使用 Typhoeus 和 Ruby 编写的爬虫程序

以下是一个使用 Typhoeus 和 Ruby 编写的爬虫程序,用于爬取 ,同时使用了 jshk.com.cn/get_proxy 这段代码获取代理: #!/usr/bin/env rubyrequire typhoeus require jsondef get_proxyurl "https://www.duoip.cn/get_proxy"respon…

正则表达式[总结]

文章目录 1. 为什么要学习正则表达式2. 再提出几个问题?3. 解决之道-正则表达式4. 正则表达式基本介绍5. 正则表达式底层实现(重要)6. 正则表达式语法6.1 基本介绍6.2 元字符(Metacharacter)-转义号 \\\6.3 元字符-字符匹配符6.4 元字符-选择匹配符6.5 元字符-限定符…

vscode中4个json的区别和联系

在vscode中快捷键ctrlshiftp,然后输入setting,会出现下图几个选项 当不同设置之间出现冲突时,听谁的: Open Workspace Settings(JSON) > Open Settings(JSON) Open User Settings > Open Default Settings(JSON) Open Wo…

WPF实现签名拍照功能

✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…

如何理解OSI七层模型?

一、是什么 OSI (Open System Interconnect)模型全称为开放式通信系统互连参考模型,是国际标准化组织 ( ISO ) 提出的一个试图使各种计算机在世界范围内互连为网络的标准框架 OSI 将计算机网络体系结构划分为七层,每一层实现各自…

element-ui中表格树类型数据的显示

项目场景&#xff1a; 1&#xff1a;非懒加载的情况 1&#xff1a;效果展示 2&#xff1a;问题描述以及解决 1&#xff1a;图片展示 2&#xff1a;html <-- default-expand-all 代表默认展开 如果不展开删除就行 --> <el-tableref"refsTable"v-loadin…

Linux_Shell运行原理(命令行解释器)

一般我们叫Linux操作系统&#xff0c;狭义上就是指Linux内核&#xff08;kernel&#xff09;&#xff0c;广义上就是Linux内核Linux外壳程序对应的配套程序&#xff0c;这里我们来详细介绍一下这个“外壳程序”。 在我们使用指令时&#xff0c;这个外壳程序会将我们的解释指令并…

【Arduino TFT】基于 ESP32S3 S7789 240x240 TFT实现的龙猫太空人天气时钟

忘记过去&#xff0c;超越自己 ❤️ 博客主页 单片机菜鸟哥&#xff0c;一个野生非专业硬件IOT爱好者 ❤️❤️ 本篇创建记录 2023-10-21 ❤️❤️ 本篇更新记录 2023-10-21 ❤️&#x1f389; 欢迎关注 &#x1f50e;点赞 &#x1f44d;收藏 ⭐️留言&#x1f4dd;&#x1f64…

【趣味随笔】农业机器人的种类与发展前景

&#x1f4e2;&#xff1a;如果你也对机器人、人工智能感兴趣&#xff0c;看来我们志同道合✨ &#x1f4e2;&#xff1a;不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 &#x1f4e2;&#xff1a;文章若有幸对你有帮助&#xff0c;可点赞 &#x1f44d;…

关于Mysql中的索引与事务

索引 定义 索引&#xff1a;为了提高查找效率而使用的一种数据结构把数据组织起来&#xff0c;可以把索引理解在书的目录或字典的检索表&#xff08;拼音检索&#xff09; 索引是一种特殊的文件&#xff0c;可以包含着对数据表里的所有记录的引用指针&#xff0c;对表中的一…

重磅发布!RflySim Cloud 智能算法云仿真平台亮相,助力大规模集群算法高效训练

RflySim Cloud智能算法云仿真平台&#xff08;以下简称RflySim Cloud平台&#xff09;是由卓翼智能及飞思实验室为无人平台集群算法验证、大规模博弈对抗仿真、人工智能模型训练等前沿研究领域研发的平台。主要由环境仿真模块、物理效应计算模块、多智能体仿真模块、分布式网络…

代码随想录Day24 LeetCode T491 递增子序列 LeetCode T46 全排列 LrrtCode T47 全排列II

LeetCode T491 递增子序列 题目链接:491. 递增子序列 - 力扣&#xff08;LeetCode&#xff09; 题目思路: 首先这里的测试用例很容易误导我们,这道题不能使用上次子集的思路对数组先排序,使用一个used数组来解决问题. 我们用[4,7,6,7]举例这道题的递增序列不存在[4,6,7,7]这个…

合同管理系统

合同管理系统 功能介绍&#xff1a; 功能特性&#xff1a; 根据对合同管理系统系统分析合同管理由以下模块组成&#xff0c;相对方管理、合同文本管理、合同审批管理、合同履行审批、风险事件管理、合同查询、合同统计、系统提醒、系统管理。 1、相对方管理 1.有“相对方…

SpringBoot环境搭建与初创程序

一&#xff1a;IDEA环境准备 IDEA社区版版本: 2021.1-2022.1.4 IDEA专业版版本: 无要求 &#x1f31f;如果个人电脑安装的IEDA不在这个范围&#xff0c;需要卸载重新安装&#xff1b;且⼀定要删除注册表 参考文章➜IDEA卸载和删除注册表 二&#xff1a; Maven (1)Maven的概念…

第六届“中国法研杯”司法人工智能挑战赛进行中!

第六届“中国法研杯”司法人工智能挑战赛 赛题上新&#xff01; 第六届“中国法研杯”司法人工智能挑战赛&#xff08;LAIC2023&#xff09;目前已发布司法大模型数据和服务集成调度 、证据推理、司法大数据征文比赛、案件要素识别四大任务。本届大赛中&#xff0c;“案件要素…

克隆的虚拟机,查不到IP号

文章目录 问题解决描述解决步骤重新生成MAC地址修改一修改二 相关操作查看当前所有网卡修改网络配置文件文件内容修改修改文件名 问题解决 描述 使用克隆的虚拟机&#xff0c;网卡和原虚拟机的相同&#xff0c;会导致克隆虚拟机的网卡不可用&#xff0c;从而使用ip addr查看不…

上新啦!请查收云原生虚拟数仓 PieCloudDB 十月动态

PieCloudDB Database 最新动态 PieCloudDB 压缩效率得到提升 为了节省存储空间&#xff0c;降低用户存储费用&#xff0c;PieCloudDB 在压缩率上不断优化&#xff0c;包括&#xff1a; 对 HLL&#xff08;HyperLogLog&#xff09;支持游程编码&#xff08;Run Length Encodi…

Visual Studio2019 与 MySQL连接 版本关系

Refer: VS 连接MySQL | mysql-for-visualstudio 的安装-CSDN博客 【精选】用VS2019&#xff08;C#&#xff09;连接MYSQL(从0入门&#xff0c;手把手教学&#xff09;_mysql-for-visualstudio-1.2.9.msi_Flying___rabbit的博客-CSDN博客 一、工具&#xff1a;VS2019需要连接M…

Qt 官方文档及阅读方法

文章目录 选择 All Qt Modules 查找模块选择 C Classes 查看该模块的所有的类当前类说明文档 QT 官方文档参考&#xff1a;https://doc.qt.io/qt-5 选择 All Qt Modules 查找模块 选择 C Classes 查看该模块的所有的类 当前类说明文档 包括 属性公共函数重新实现的公共功能公…