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

鸿蒙内核源码分析(互斥锁篇) | 互斥锁比自旋锁丰满多了

内核中哪些地方会用到互斥锁?看图:

图中是内核有关模块对互斥锁初始化,有文件,有内存,用消息队列等等,使用面非常的广.其实在给内核源码加注的过程中,会看到大量的自旋锁和互斥锁,它们的存在有序的保证了内核和应用程序的正常运行.是非常基础和重要的功能.

概述

自旋锁互斥锁 虽都是锁,但解决的问题不同, 自旋锁解决用于CPU核间共享内存的竞争,而互斥锁解决线程(任务)间共享内存的竞争.

自旋锁的特点是死守共享资源,拿不到锁,CPU选择睡眠,等待其他CPU释放资源.所以共享代码段不能太复杂,否则容易死锁,休克.

互斥锁的特点是拿不到锁往往原任务阻塞,切换到新任务运行.CPU是会一直跑的.这样很容易会想到几个问题:

第一:会出现很多任务在等同一把锁的情况出现,因为切换新任务也可能因要同一把锁而被阻塞,CPU又被调去跑新新任务了.这样就会出现一个等锁的链表.

第二:持有锁的一方再申请同一把锁时还能成功吗? 答案是可以的,这种锁叫递归锁,是鸿蒙内核默认方式.

第三:当优先级很高的A任务要锁失败,主动让出CPU进入睡眠,而如果持有锁的B任务优先级很低, 迟迟等不到调度不到B任务运行,无法释放锁怎么办?
答案是会临时调整B任务的优先级,调到A一样高,这样B能很快的被调度到,等B释放锁后其优先级又会被还原.所以一个任务的优先级会看情况时高时低.

第四:B任务释放锁之后要主动唤醒等锁的任务链表,使他们能加入就绪队列,等待被调度.调度算法是一视同仁的,它只看优先级.

带着这些问题,进入鸿蒙内核互斥锁的实现代码,本篇代码量较大, 每行代码都一一注解说明.

互斥锁长什么样?

enum {LOS_MUX_PRIO_NONE = 0,  //线程的优先级和调度不会受到互斥锁影响,先来后到,普通排队.LOS_MUX_PRIO_INHERIT = 1, //当高优先级的等待低优先级的线程释放锁时,低优先级的线程以高优先级线程的优先级运行。//当线程解锁互斥量时,线程的优先级自动被将到它原来的优先级LOS_MUX_PRIO_PROTECT = 2 //详见:OsMuxPendOp中的注解,详细说明了LOS_MUX_PRIO_PROTECT的含义
};
enum {LOS_MUX_NORMAL = 0,  //非递归锁 只有[0.1]两个状态,不做任何特殊的错误检,不进行deadlock detection(死锁检测)LOS_MUX_RECURSIVE = 1, //递归锁 允许同一线程在互斥量解锁前对该互斥量进行多次加锁。递归互斥量维护锁的计数,在解锁次数和加锁次数不相同的情况下,不会释放锁,别的线程就无法加锁此互斥量。LOS_MUX_ERRORCHECK = 2, //进行错误检查,如果一个线程企图对一个已经锁住的mutex进行relock或对未加锁的unlock,将返回一个错误。LOS_MUX_DEFAULT = LOS_MUX_RECURSIVE //鸿蒙系统默认使用递归锁
};
typedef struct { //互斥锁的属性UINT8 protocol;  //协议UINT8 prioceiling; //优先级上限UINT8 type;   //类型属性UINT8 reserved;  //保留字段
} LosMuxAttr;typedef struct OsMux { //互斥锁结构体UINT32 magic;        /**< magic number */  //魔法数字LosMuxAttr attr;     /**< Mutex attribute */ //互斥锁属性LOS_DL_LIST holdList; /**< The task holding the lock change */ //当有任务拿到本锁时,通过holdList节点把锁挂到该任务的锁链表上LOS_DL_LIST muxList; /**< Mutex linked list */ //等这个锁的任务链表,上面挂的都是任务,注意和holdList的区别.VOID *owner;         /**< The current thread that is locking a mutex */ //当前拥有这把锁的任务UINT16 muxCount;     /**< Times of locking a mutex */ //锁定互斥体的次数,递归锁允许多次
} LosMux;

初始化

LITE_OS_SEC_TEXT UINT32 LOS_MuxInit(LosMux *mutex, const LosMuxAttr *attr)
{   //...SCHEDULER_LOCK(intSave);        //拿到调度自旋锁mutex->muxCount = 0;            //锁定互斥量的次数mutex->owner = NULL;            //持有该锁的任务LOS_ListInit(&mutex->muxList);  //初始化等待该锁的任务链表mutex->magic = OS_MUX_MAGIC;    //固定标识,互斥锁的魔法数字SCHEDULER_UNLOCK(intSave);      //释放调度自旋锁return LOS_OK;
}

留意mutex->muxList,这又是一个双向链表, 双向链表是内核最重要的结构体,不仅仅是鸿蒙内核,在linux内核中(list_head)又何尝不是,牢牢的寄生在宿主结构体上.muxList上挂的是未来所有等待这把锁的任务.

三种申请模式

申请互斥锁有三种模式:无阻塞模式、永久阻塞模式、定时阻塞模式。

无阻塞模式:即任务申请互斥锁时,入参timeout等于0。若当前没有任务持有该互斥锁,或者持有该互斥锁的任务和申请该互斥锁的任务为同一个任务,则申请成功,否则立即返回申请失败。

永久阻塞模式:即任务申请互斥锁时,入参timeout等于0xFFFFFFFF。若当前没有任务持有该互斥锁,则申请成功。否则,任务进入阻塞态,系统切换到就绪任务中优先级最高者继续执行。任务进入阻塞态后,直到有其他任务释放该互斥锁,阻塞任务才会重新得以执行。

定时阻塞模式:即任务申请互斥锁时,0<timeout<0xFFFFFFFF。若当前没有任务持有该互斥锁,则申请成功。否则该任务进入阻塞态,系统切换到就绪任务中优先级最高者继续执行。任务进入阻塞态后,超时前如果有其他任务释放该互斥锁,则该任务可成功获取互斥锁继续执行,若超时前未获取到该互斥锁,接口将返回超时错误码。

如果有任务阻塞于该互斥锁,则唤醒被阻塞任务中优先级最高的,该任务进入就绪态,并进行任务调度。
如果没有任务阻塞于该互斥锁,则互斥锁释放成功。

申请互斥锁主函数 OsMuxPendOp

//互斥锁的主体函数,由OsMuxlockUnsafe调用,互斥锁模块最重要的几个函数之一
//最坏情况就是拿锁失败,让出CPU,变成阻塞任务,等别的任务释放锁后排到自己了接着执行. 
STATIC UINT32 OsMuxPendOp(LosTaskCB *runTask, LosMux *mutex, UINT32 timeout)
{UINT32 ret;LOS_DL_LIST *node = NULL;LosTaskCB *owner = NULL;if ((mutex->muxList.pstPrev == NULL) || (mutex->muxList.pstNext == NULL)) {//列表为空时的处理/* This is for mutex macro initialization. */mutex->muxCount = 0;//锁计数器清0mutex->owner = NULL;//锁没有归属任务LOS_ListInit(&mutex->muxList);//初始化锁的任务链表,后续申请这把锁任务都会挂上去}if (mutex->muxCount == 0) {//无task用锁时,肯定能拿到锁了.在里面返回mutex->muxCount++;              //互斥锁计数器加1mutex->owner = (VOID *)runTask; //当前任务拿到锁LOS_ListTailInsert(&runTask->lockList, &mutex->holdList);//持有锁的任务改变了,节点挂到当前task的锁链表if ((runTask->priority > mutex->attr.prioceiling) && (mutex->attr.protocol == LOS_MUX_PRIO_PROTECT)) {//看保护协议的做法是怎样的?LOS_BitmapSet(&runTask->priBitMap, runTask->priority);//1.priBitMap是记录任务优先级变化的位图,这里把任务当前的优先级记录在priBitMapOsTaskPriModify(runTask, mutex->attr.prioceiling);//2.把高优先级的mutex->attr.prioceiling设为当前任务的优先级.}//注意任务优先级有32个, 是0最高,31最低!!!这里等于提高了任务的优先级,目的是让其在下次调度中继续提高被选中的概率,从而快速的释放锁.return LOS_OK;}//递归锁muxCount>0 如果是递归锁就要处理两种情况 1.runtask持有锁 2.锁被别的任务拿走了if (((LosTaskCB *)mutex->owner == runTask) && (mutex->attr.type == LOS_MUX_RECURSIVE)) {//第一种情况 runtask是锁持有方mutex->muxCount++;  //递归锁计数器加1,递归锁的目的是防止死锁,鸿蒙默认用的就是递归锁(LOS_MUX_DEFAULT = LOS_MUX_RECURSIVE)return LOS_OK;      //成功退出}//到了这里说明锁在别的任务那里,当前任务只能被阻塞了.if (!timeout) {//参数timeout表示等待多久再来拿锁return LOS_EINVAL;//timeout = 0表示不等了,没拿到锁就返回不纠结,返回错误.见于LOS_MuxTrylock }//自己要被阻塞,只能申请调度,让出CPU core 让别的任务上if (!OsPreemptableInSched()) {//不能申请调度 (不能调度的原因是因为没有持有调度任务自旋锁)return LOS_EDEADLK;//返回错误,自旋锁被别的CPU core 持有}OsMuxBitmapSet(mutex, runTask, (LosTaskCB *)mutex->owner);//设置锁位图,尽可能的提高锁持有任务的优先级owner = (LosTaskCB *)mutex->owner;  //记录持有锁的任务runTask->taskMux = (VOID *)mutex;   //记下当前任务在等待这把锁node = OsMuxPendFindPos(runTask, mutex);//在等锁链表中找到一个优先级比当前任务更低的任务ret = OsTaskWait(node, timeout, TRUE);//task陷入等待状态 TRUE代表需要调度if (ret == LOS_ERRNO_TSK_TIMEOUT) {//这行代码虽和OsTaskWait挨在一起,但要过很久才会执行到,因为在OsTaskWait中CPU切换了任务上下文runTask->taskMux = NULL;// 所以重新回到这里时可能已经超时了ret = LOS_ETIMEDOUT;//返回超时}if (timeout != LOS_WAIT_FOREVER) {//不是永远等待的情况OsMuxBitmapRestore(mutex, runTask, owner);//恢复锁的位图}return ret;
}

释放锁的主体函数 OsMuxPostOp

//是否有其他任务持有互斥锁而处于阻塞状,如果是就要唤醒它,注意唤醒一个任务的操作是由别的任务完成的
//OsMuxPostOp只由OsMuxUnlockUnsafe,参数任务归还锁了,自然就会遇到锁要给谁用的问题, 因为很多任务在申请锁,由OsMuxPostOp来回答这个问题
STATIC UINT32 OsMuxPostOp(LosTaskCB *taskCB, LosMux *mutex, BOOL *needSched)
{LosTaskCB *resumedTask = NULL;if (LOS_ListEmpty(&mutex->muxList)) {//如果互斥锁列表为空LOS_ListDelete(&mutex->holdList);//把持有互斥锁的节点摘掉mutex->owner = NULL;return LOS_OK;}resumedTask = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&(mutex->muxList)));//拿到等待互斥锁链表的第一个任务实体,接下来要唤醒任务if (mutex->attr.protocol == LOS_MUX_PRIO_INHERIT) {//互斥锁属性协议是继承会怎么操作?if (resumedTask->priority > taskCB->priority) {//拿到锁的任务优先级低于参数任务优先级if (LOS_HighBitGet(taskCB->priBitMap) != resumedTask->priority) {//参数任务bitmap中最低的优先级不等于等待锁的任务优先级LOS_BitmapClr(&taskCB->priBitMap, resumedTask->priority);//把等待任务锁的任务的优先级记录在参数任务的bitmap中}} else if (taskCB->priBitMap != 0) {//如果bitmap不等于0说明参数任务至少有任务调度的优先级OsMuxPostOpSub(taskCB, mutex);//}}mutex->muxCount = 1;//互斥锁数量为1mutex->owner = (VOID *)resumedTask;//互斥锁的持有人换了resumedTask->taskMux = NULL;//resumedTask不再等锁了LOS_ListDelete(&mutex->holdList);//自然要从等锁链表中把自己摘出去LOS_ListTailInsert(&resumedTask->lockList, &mutex->holdList);//把锁挂到恢复任务的锁链表上,lockList是任务持有的所有锁记录OsTaskWake(resumedTask);//resumedTask有了锁就唤醒它,因为当初在没有拿到锁时处于了pend状态if (needSched != NULL) {//如果不为空*needSched = TRUE;//就走起再次调度流程}return LOS_OK;
}

编程实例

本实例实现如下流程。

  • 任务Example_TaskEntry创建一个互斥锁,锁任务调度,创建两个任务Example_MutexTask1、Example_MutexTask2。Example_MutexTask2优先级高于Example_MutexTask1,解锁任务调度,然后Example_TaskEntry任务休眠300Tick。

  • Example_MutexTask2被调度,以永久阻塞模式申请互斥锁,并成功获取到该互斥锁,然后任务休眠100Tick,Example_MutexTask2挂起,Example_MutexTask1被唤醒。

  • Example_MutexTask1以定时阻塞模式申请互斥锁,等待时间为10Tick,因互斥锁仍被Example_MutexTask2持有,Example_MutexTask1挂起。10Tick超时时间到达后,Example_MutexTask1被唤醒,以永久阻塞模式申请互斥锁,因互斥锁仍被Example_MutexTask2持有,Example_MutexTask1挂起。

  • 100Tick休眠时间到达后,Example_MutexTask2被唤醒, 释放互斥锁,唤醒Example_MutexTask1。Example_MutexTask1成功获取到互斥锁后,释放锁。

  • 300Tick休眠时间到达后,任务Example_TaskEntry被调度运行,删除互斥锁,删除两个任务。

/* 互斥锁句柄id */
UINT32 g_testMux;
/* 任务ID */
UINT32 g_testTaskId01;
UINT32 g_testTaskId02;VOID Example_MutexTask1(VOID)
{UINT32 ret;printf("task1 try to get  mutex, wait 10 ticks.\n");/* 申请互斥锁 */ret = LOS_MuxPend(g_testMux, 10);if (ret == LOS_OK) {printf("task1 get mutex g_testMux.\n");/* 释放互斥锁 */LOS_MuxPost(g_testMux);return;} else if (ret == LOS_ERRNO_MUX_TIMEOUT ) {printf("task1 timeout and try to get mutex, wait forever.\n");/* 申请互斥锁 */ret = LOS_MuxPend(g_testMux, LOS_WAIT_FOREVER);if (ret == LOS_OK) {printf("task1 wait forever, get mutex g_testMux.\n");/* 释放互斥锁 */LOS_MuxPost(g_testMux);return;}}return;
}VOID Example_MutexTask2(VOID)
{printf("task2 try to get  mutex, wait forever.\n");/* 申请互斥锁 */(VOID)LOS_MuxPend(g_testMux, LOS_WAIT_FOREVER);printf("task2 get mutex g_testMux and suspend 100 ticks.\n");/* 任务休眠100Ticks */LOS_TaskDelay(100);printf("task2 resumed and post the g_testMux\n");/* 释放互斥锁 */LOS_MuxPost(g_testMux);return;
}UINT32 Example_TaskEntry(VOID)
{UINT32 ret;TSK_INIT_PARAM_S task1;TSK_INIT_PARAM_S task2;/* 创建互斥锁 */LOS_MuxCreate(&g_testMux);/* 锁任务调度 */LOS_TaskLock();/* 创建任务1 */memset(&task1, 0, sizeof(TSK_INIT_PARAM_S));task1.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_MutexTask1;task1.pcName       = "MutexTsk1";task1.uwStackSize  = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;task1.usTaskPrio   = 5;ret = LOS_TaskCreate(&g_testTaskId01, &task1);if (ret != LOS_OK) {printf("task1 create failed.\n");return LOS_NOK;}/* 创建任务2 */memset(&task2, 0, sizeof(TSK_INIT_PARAM_S));task2.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_MutexTask2;task2.pcName       = "MutexTsk2";task2.uwStackSize  = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;task2.usTaskPrio   = 4;ret = LOS_TaskCreate(&g_testTaskId02, &task2);if (ret != LOS_OK) {printf("task2 create failed.\n");return LOS_NOK;}/* 解锁任务调度 */LOS_TaskUnlock();/* 休眠300Ticks */LOS_TaskDelay(300);/* 删除互斥锁 */LOS_MuxDelete(g_testMux);/* 删除任务1 */ret = LOS_TaskDelete(g_testTaskId01);if (ret != LOS_OK) {printf("task1 delete failed .\n");return LOS_NOK;}/* 删除任务2 */ret = LOS_TaskDelete(g_testTaskId02);if (ret != LOS_OK) {printf("task2 delete failed .\n");return LOS_NOK;}return LOS_OK;
}

结果验证

task2  to get  mutex wait forever
task2 get mutex g_testMux  suspend  ticks
task1  to get  mutex wait  ticks
task1 timeout   to get mutex wait forever
task2 resumed  post the g_testMux
task1 wait foreverget mutex g_testMux

总结

1.互斥锁解决的是任务间竞争共享内存的问题.

2.申请锁失败的任务会进入睡眠OsTaskWait,内核会比较持有锁的任务和申请锁任务的优先级,把持有锁的任务优先级调到尽可能的高,以便更快的被调度执行,早日释放锁.

3.释放锁的任务会在等锁链表中找一个高优先级任务,通过OsTaskWake唤醒它,并向调度算法申请调度.但要注意,调度算法只是按优先级来调度,并不保证调度后的任务一定是要唤醒的任务.

4.互斥锁篇关键是看懂 OsMuxPendOp 和 OsMuxPostOp 两个函数.

鸿蒙全栈开发全新学习指南

也为了积极培养鸿蒙生态人才,让大家都能学习到鸿蒙开发最新的技术,针对一些在职人员、0基础小白、应届生/计算机专业、鸿蒙爱好者等人群,整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线【包含了大APP实战项目开发】

本路线共分为四个阶段:

第一阶段:鸿蒙初中级开发必备技能

第二阶段:鸿蒙南北双向高工技能基础:gitee.com/MNxiaona/733GH

第三阶段:应用开发中高级就业技术

第四阶段:全网首发-工业级南向设备开发就业技术:https://gitee.com/MNxiaona/733GH

《鸿蒙 (Harmony OS)开发学习手册》(共计892页)

如何快速入门?

1.基本概念
2.构建第一个ArkTS应用
3.……

开发基础知识:gitee.com/MNxiaona/733GH

1.应用基础知识
2.配置文件
3.应用数据管理
4.应用安全管理
5.应用隐私保护
6.三方应用调用管控机制
7.资源分类与访问
8.学习ArkTS语言
9.……

基于ArkTS 开发

1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……

鸿蒙开发面试真题(含参考答案):gitee.com/MNxiaona/733GH

鸿蒙入门教学视频:

美团APP实战开发教学:gitee.com/MNxiaona/733GH

写在最后

  • 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
  • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
  • 想要获取更多完整鸿蒙最新学习资源,请移步前往小编:gitee.com/MNxiaona/733GH

相关文章:

鸿蒙内核源码分析(互斥锁篇) | 互斥锁比自旋锁丰满多了

内核中哪些地方会用到互斥锁?看图: 图中是内核有关模块对互斥锁初始化,有文件,有内存,用消息队列等等,使用面非常的广.其实在给内核源码加注的过程中,会看到大量的自旋锁和互斥锁,它们的存在有序的保证了内核和应用程序的正常运行.是非常基础和重要的功能. 概述 自旋锁 和…...

MySQL之查询 拿下 * 。*

DQL数据查询语言 对上述的的查询操作进行代码演示&#xff08;续上一篇学生表代码进行处理&#xff09; 下面是上一篇的代码分享 下面进行简单的查询操作 字符串如果强行进行算数运算默认只为0 查询时常用的单行函数列举 未完待续...

目标检测(二阶段)领域,常见词汇

1、Backbone&#xff08;主干网络&#xff09; 定义: Backbone是目标检测模型的基础部分&#xff0c;通常是一个预训练的卷积神经网络&#xff08;如ResNet、VGG、MobileNet等&#xff09;&#xff0c;负责从输入图像中提取多层特征图。这些特征图包含了不同尺度和抽象级别的信…...

区块链与人工智能哪个更有前景?

一、引言 随着科技的飞速发展&#xff0c;区块链技术和人工智能&#xff08;AI&#xff09;无疑是两大热门领域&#xff0c;各自以其独特的魅力吸引着全球的关注。两者虽源自不同的技术基础&#xff0c;却都预示着未来技术发展的无限可能。本文旨在探讨区块链与人工智能各自的前…...

计算机网络【应用层】邮件和DNS

文章目录 电子邮件DNSDNS提供的服务&#xff1a;域名分级域名解析流程DNS资源记录DNS服务器类型 电子邮件 使用SMTP协议发送邮件之前&#xff0c;需要将二进制多媒体数据编码为ASCII码SMTP一般不使用中间邮件服务器发送邮件&#xff0c;如果收件服务器没开机&#xff0c;那么会…...

js遍历数组将数组中属性名相同的属性值组成新的数组再转化成字符串并换行(js换行和html换行不同)

{label: 告警结果,display:true, html:true,formatter:(row)>{let list ""if(row.funRes&&row.funRes.length){let propName value; list row.funRes.map(obj > {return <span style"vertical-align: text-top;padding-right: 2px;">…...

Ai绘画|如何安装使用秋叶comfyui整合包,手把手详细教程

B 站的秋叶大佬在 1 月份就已经发布了 comfy ui 的整合包。用户将压缩包下载后&#xff0c;能够一键启动 comfy ui。其便利性与之前的 webui 整合包如出一辙。然而在整合包下载完成后&#xff0c;新手或许会遭遇插件以及模型缺失的情况&#xff0c;同时也不清楚该如何运行工作流…...

【React】React-redux多组件间的状态传递

效果&#xff08;部分完整代码在最底部&#xff09;&#xff1a; 编写 Person 组件 上面的 Count 组件&#xff0c;已经在前面几篇写过了&#xff0c;也可以直接翻到最底部看 首先我们需要在 containers 文件夹下编写 Person 组件的容器组件 首先我们需要编写 index.jsx 文件…...

XSS-Labs 靶场通过解析(上)

前言 XSS-Labs靶场是一个专门用于学习和练习跨站脚本攻击&#xff08;XSS&#xff09;技术的在线平台。它提供了一系列的实验场景和演示&#xff0c;帮助安全研究人员、开发人员和安全爱好者深入了解XSS攻击的原理和防御方法。 XSS-Labs靶场的主要特点和功能包括&#xff1a;…...

开源版本管理系统的搭建一:SVN服务端安装

作者&#xff1a;私语茶馆 1.Windows搭建SVN版本管理系统 点评&#xff1a;SVN本身非常简洁易用&#xff0c;VisualSVN文档支撑非常好&#xff0c;客户端TortoiseSVN非常专业。5星好评。 1.1.SVN概要和组成 背景介绍 Svn是一个开源版本管理系统&#xff0c;由CollabNet公司…...

Fastfetch一个类似neofetch的系统信息工具软件

1. 使用默认配置运行&#xff1a;fastfetch 2. 使用所有支持的模块运行&#xff0c;并找到您感兴趣的内容&#xff1a;fastfetch -c all.jsonc 3. 查找 fastfetch 检测到的所有数据&#xff1a;fastfetch -s <模块> --format json 4. 显示帮助信息&#xff1a;fastfetch …...

DV试验和PV试验介绍

1 基本介绍 DV试验 DV试验&#xff0c;全称Design Verification Test&#xff0c;又称设计验证试验&#xff0c;是指在产品设计阶段&#xff0c;对产品的设计进行验证的一种试验方法。DV试验的主要目的是为了验证产品的设计是否满足功能和性能要求&#xff0c;并找出设计中的…...

RTT PIN设备学习

获取GPIO编号 GET_PIN(port, pin)#define LED_BLUE_PIN GET_PIN(A, 0)设置引脚模式 void rt_pin_mode(rt_base_t pin, rt_base_t mode);设置引脚电平 void rt_pin_write(rt_base_t pin, rt_base_t value);rt_base_t pin 同上&#xff0c; 为引脚编号&#xff0c;尽量通过宏定…...

Spring Boot面试知识点总结(经典15问)

Spring Boot面试知识点总结&#xff08;问答合集&#xff09; 文章目录 Spring Boot面试知识点总结&#xff08;问答合集&#xff09;一、Spring Boot简介二、核心特性三、面试问题及答案问题1&#xff1a;Spring Boot的核心配置文件是什么&#xff1f;问题2&#xff1a;Spring…...

安卓手机原生运行 ARM Ubuntu 24.04 桌面版(一)

本篇文章&#xff0c;聊一聊尝试让安卓手机原生运行 Ubuntu&#xff0c;尤其是运行官方未发布过的 ARM 架构的 Ubuntu 24.04 桌面版本。 写在前面 最近的几篇文章&#xff0c;都包含了比较多的实操内容、需要反复的复现验证&#xff0c;以及大量的调试过程&#xff0c;为了不…...

AHB---数据总线

1. 数据总线 为了实现AHB系统&#xff0c;需要独立的读写数据总线。虽然推荐的最小数据总线宽度被指定为32位&#xff0c;但这可以根据数据总线宽度进行更改。 数据总线包含以下部分&#xff1a; HWDATAHRDATAEndianness&#xff08;字节序&#xff09; 1.1 HWDATA 在写传输…...

「51媒体」企业单位新闻稿件考核,怎么发布

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 电力税务企事业单位部门等单位提供了新闻稿件&#xff0c;如何在一些重点媒体进行宣发呢&#xff1a; 精准锁定发布媒体 了解考核要求&#xff1a;仔细阅读宣传任务名单&#xff0c;了解…...

「 网络安全常用术语解读 」SBOM主流格式CycloneDX详解

CycloneDX是软件供应链的现代标准。CycloneDX物料清单&#xff08;BOM&#xff09;可以表示软件、硬件、服务和其他类型资产的全栈库存。该规范由OWASP基金会发起并领导&#xff0c;由Ecma International标准化&#xff0c;并得到全球信息安全界的支持&#xff0c;如今CycloneD…...

React 之 内置标签<Fragment> (<>...</>) (十一)

通常使用 <>…</> 代替&#xff0c;它们都允许你在不添加额外节点的情况下将子元素组合。相当于vue的内置标签<template/> 1. 返回多个元素 <><OneChild /><AnotherChild /> </>2. 分配多个元素给一个变量 和其他元素一样&#xf…...

Mac M1 解决安装grpcio不可用

问题描述&#xff1a; 使用 pip 已经更新 grpcio 至最新版&#xff0c;调用时还是报错 如下图&#xff1a; Traceback (most recent call last):File "/Users/yu/anaconda3/envs/dify2/lib/python3.10/site-packages/flask/cli.py", line 245, in locate_app__imp…...

Linux第三节--常见的指令介绍集合(持续更新中)

点赞关注不迷路&#xff01;&#xff0c;本节涉及初识Linux第三节&#xff0c;主要为常见的几条指令介绍。 如果文章对你有帮助的话 欢迎 评论&#x1f4ac; 点赞&#x1f44d;&#x1f3fb; 收藏 ✨ 加关注&#x1f440; 期待与你共同进步! Linux下基本指令 1. man指令 Linu…...

SpringMVC简介和体验

一、SpringMVC简介和体验 1.1 介绍 Spring Web MVC :: Spring Framework Spring Web MVC是基于Servlet API构建的原始Web框架&#xff0c;从一开始就包含在Spring Framework中。正式名称“Spring Web MVC”来自其源模块的名称&#xff08; spring-webmvc &#xff09;&#…...

Android单行字符串末尾省略号加icon,图标可点击

如图 设置仅显示单行字符串&#xff0c;末尾用省略号&#xff0c;加跟一个icon&#xff0c;icon可点击 tvName.text "test"val drawable ResourcesCompat.getDrawable(resources, R.mipmap.icon_edit, null)tvName.setCompoundDrawablesWithIntrinsicBounds(null,…...

山东省文史书画研究会成立20周年系列活动徽标征集胜选名单公布

2024年5月1日&#xff0c;山东省文史书画研究会成立20周年系列活动徽标征集落下帷幕。征稿启事下发后&#xff0c;得到社会各界人士的广泛关注与参与&#xff0c;共收到设计方案608件。经过初评&#xff0c;选出5幅作品进入复评&#xff0c;并经过网络投票和专家投票相结合的方…...

相机2:曝光三要素之ISO(感光度)

曝光是相机的感光元件与光线接触成像的过程&#xff0c;而曝光三要素分别指的是光圈大小&#xff0c;快门速度和感光度。这三个因素都可以控制曝光量&#xff0c;同时也分别有自己的特点。 什么是感光度&#xff1f; ISO又叫感光度&#xff0c;指的是相机感光元件&#xff08;…...

已解决java.util.IllegalFormatConversionException异常的正确解决方法,亲测有效!!!

已解决java.util.IllegalFormatConversionException异常的正确解决方法&#xff0c;亲测有效&#xff01;&#xff01;&#xff01; 目录 问题分析 报错原因 示例报错代码&#xff1a; 解决思路 解决方法 检查和更正格式说明符 示例修正代码&#xff1a; 调整参数类型…...

OpenCV 库来捕获和处理视频输入和相似度测量(73)

返回:OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇:OpenCV的周期性噪声去除滤波器(70) 下一篇 :使用 OpenCV 创建视频(74) ​ 目标 如今&#xff0c;拥有数字视频录制系统供您使用是很常见的。因此&#xff0c;您最终会遇到不再处理一批图像&#xf…...

了解TMS运输管理系统,实现物流高效运转

TMS运输管理系统&#xff08;Transportation Management System&#xff09;是一种集成物流和信息技术的解决方案&#xff0c;通过优化运输流程、实时跟踪货物信息和自动化管理操作&#xff0c;提高物流效率&#xff0c;降低运营成本&#xff0c;实现高效运输。 TMS运输管理系…...

数据库原理与应用实验三 嵌套查询

实验目的和要求 加深和掌握对嵌套查询的理解和应用 实验环境 Windows10 SQLServer 实验内容与过程 图书&#xff08;书号&#xff0c;书名&#xff0c;价格&#xff0c;出版社&#xff09; 读者&#xff08;卡号&#xff0c;姓名&#xff0c;年龄&#xff0c;所属单位&a…...

LeetCode 110.平衡二叉树(Java/C/Python3/Go实现含注释说明,Easy)

标签 树深度优先搜索递归 题目描述 给定一个二叉树&#xff0c;判断它是否是高度平衡的二叉树。 本题中&#xff0c;一棵高度平衡的二叉树定义为&#xff1a; 一个二叉树每个节点的左右两个子树的高度差的绝对值不超过1。 原题&#xff1a;LeetCode 110.平衡二叉树 思路及…...

如何做印刷报价网站/推广网站哪个好

转载&#xff1a;http://www.360doc.com/content/14/0915/22/6624919_409758903.shtml 在自动换行设置的时候&#xff0c;要在EDIT控件的属性中选中&#xff02;multiline”的属性和Auto_HScroll、Vertical scroll。 经过多次测试&#xff0c;总结出VC编辑框&#xff08;EDIT&a…...

wordpress真伪查询主题/百度seo关键词排名查询工具

Outlook Express收件箱容量超过2G时的处理办法问题描述&#xff1a;如果您的计算机是Windows系统&#xff0c;而且您所使用的文件格式为FAT32&#xff0c;那么在OutlookExpress中&#xff0c;如果“收件箱”的容量超过了2G&#xff0c;那么当使用Outlook Express接收新邮件时&a…...

淮北做网站/seo常用工具有哪些

http://blog.sina.com.cn/s/blog_4ddef8f80100iwwv.html 两个quad空格a \qquad b两个m的宽度quad空格a \quad b一个m的宽度大空格a\ b1/3m宽度中等空格a\;b2/7m宽度小空格a\,b1/6m宽度没有空格ab 紧贴a\!b缩进1/6m宽度\quad、1em、em、m代表当前字体下接近字符‘M’的宽度&…...

云建站app/网页设计个人主页

一、引言 本文只是一个引子&#xff0c;来讲述树这种数据结构的一些入门点 对于刚接触树这种结构的小伙伴&#xff0c;我们需要思考几个问题&#xff08;我当年也有这样的疑问&#xff09;&#xff1f; 1、这种数据结构长什么样&#xff1f; 2、有哪些基础概念是需要了解的…...

景观平台设计/seo按照搜索引擎的

编译器实现之旅——第一章 编译器概观 编译器,近在咫尺却又远在天边。当我们写下任何非机器语言代码后,我们都需要借助编译器将这些代码变为通过计算机可运行的状态。但是,就是这样一个使用率极高的程序,我们对其却知之甚少。什么是编译器?编译器对我们的代码做了什么?又…...

遵义做网站的网络公司/海外推广专员

> 的作用&#xff1a; 1&#xff1a;将this与当前词法作用域绑定&#xff1b; 2&#xff1a;简化代码&#xff1b; 本来需要 self this&#xff1b;现在只需要()>{…} 就可以了&#xff1b; **p > p.name的含义**&#xff1a;引用codewars的用户 AcesOfGlory的解释…...