鸿蒙内核源码分析(消息封装篇) | 剖析LiteIpc(上)进程通讯内容

news2024/11/28 22:50:32

基本概念

LiteIPC是OpenHarmony LiteOS-A内核提供的一种新型IPC(Inter-Process Communication,即进程间通信)机制,为轻量级进程间通信组件,为面向服务的系统服务框架提供进程间通信能力,分为内核实现和用户态实现两部分,其中内核实现完成进程间消息收发、IPC内存管理、超时通知和死亡通知等功能;用户态提供序列化和反序列化能力,并完成IPC回调消息和死亡消息的分发。

我们主要讲解内核态实现部分,本想一篇说完,但发现它远比想象中的复杂和重要,所以分通讯内容和通讯机制上下两篇说。上篇可翻看 鸿蒙内核源码分析(消息封装篇) | 剖析LiteIpc(上)进程通讯内容 ,本篇为通讯机制,介绍liteipc在内核层的实现过程。

空间映射

映射 一词在系列篇中多次出现,虚拟地址的基础就是映射,共享内存的实现也要靠映射,LiteIPC通讯的底层实现也离不开映射,有意思的是将用户态的线性区和内核态的线性区进行了映射。也就是说当用户访问用户空间中的某个虚拟地址时,这个虚拟地址其实和内核空间某个地址都指向了同一个物理内存。可能有人会问这也行 ? 在了解完一下 物理地址,内核虚拟地址,用户虚拟地址之间的关系后就会明白这当然可以。

  • 虚拟地址包括内核空间地址和用户进程空间地址,它们的范围对外暴露,由内核开发人员划定,内核专门提供了判断函数。即看到一个虚拟地址可以知道是内核在用还是用户(应用)进程在用。
/// 虚拟地址是否在内核空间
  STATIC INLINE BOOL LOS_IsKernelAddress(VADDR_T vaddr)
  {
      return ((vaddr >= (VADDR_T)KERNEL_ASPACE_BASE) &&
              (vaddr <= ((VADDR_T)KERNEL_ASPACE_BASE + ((VADDR_T)KERNEL_ASPACE_SIZE - 1))));
  }
  /// 虚拟地址是否在用户空间
  STATIC INLINE BOOL LOS_IsUserAddress(VADDR_T vaddr)
  {
      return ((vaddr >= USER_ASPACE_BASE) &&
              (vaddr <= (USER_ASPACE_BASE + (USER_ASPACE_SIZE - 1))));
  }

物理地址是由物理内存提供,系统集成商根据实际的物理内存大小来划定物理地址范围。至于具体的某段物理内存是给内核空间使用还是给用户空间使用没有要求,所谓映射是指虚拟地址 <–> 物理地址的映射,是 N:1的关系,一个物理地址可以被多个虚拟地址映射,而一个虚拟地址只能映射到一个物理地址。

以上是LiteIPC的实现概念基础,明白后就不难理解结构体IpcPool存在的必要性了

/**
* @struct IpcPool | ipc池
* @brief  LiteIPC的核心思想就是在内核态为每个Service任务维护一个IPC消息队列,该消息队列通过LiteIPC设备文件向上层
* 用户态程序分别提供代表收取IPC消息的读操作和代表发送IPC消息的写操作。
*/
typedef struct {
    VOID   *uvaddr;	///< 用户态空间地址,由kvaddr映射而来的地址,这两个地址的关系一定要搞清楚,否则无法理解IPC的核心思想
    VOID   *kvaddr;	///< 内核态空间地址,IPC申请的是内核空间,但是会通过 DoIpcMmap 将这个地址映射到用户空间
    UINT32 poolSize; ///< ipc池大小
} IpcPool;

文件访问

LiteIPC的运行机制是首先将需要接收IPC消息的任务通过ServiceManager注册成为一个Service,然后通过ServiceManager为该Service任务配置访问权限,即指定哪些任务可以向该Service任务发送IPC消息。LiteIPC的核心思想就是在内核态为每个Service任务维护一个IPC消息队列,该消息队列通过LiteIPC设备文件向上层用户态程序分别提供代表收取IPC消息的读操作和代表发送IPC消息的写操作。

设备文件的接口层(VFS)实现为g_liteIpcFops,跟踪这几个函数就能够整明白整个实现LiteIPC过程

#define LITEIPC_DRIVER "/dev/lite_ipc"	///< 虚拟设备,文件访问读取
STATIC const struct file_operations_vfs g_liteIpcFops = {
    .open = LiteIpcOpen,   /* open | 创建Ipc内存池*/
    .close = LiteIpcClose,  /* close */
    .ioctl = LiteIpcIoctl,  /* ioctl | 包含读写操作 */
    .mmap = LiteIpcMmap,   /* mmap | 实现线性区映射*/
};

解读

  • 进来先获取当前进程OsCurrProcessGet(),即为每个进程创建唯一的IPC消息控制体,ProcIpcInfo在进程控制块中,负责管理IPC消息

  • 初始化消息内存池,此处只申请结构体本身占用内存,真正的内存池在LiteIpcMmap中完成

LiteIpcMmap | 映射

///将参数线性区设为IPC专用区
LITE_OS_SEC_TEXT STATIC int LiteIpcMmap(struct file *filep, LosVmMapRegion *region)
{
    int ret = 0;
    LosVmMapRegion *regionTemp = NULL;
    LosProcessCB *pcb = OsCurrProcessGet();
    ProcIpcInfo *ipcInfo = pcb->ipcInfo;
	//被映射的线性区不能在常量和私有数据区
    if ((ipcInfo == NULL) || (region == NULL) || (region->range.size > LITE_IPC_POOL_MAX_SIZE) ||
        (!LOS_IsRegionPermUserReadOnly(region)) || (!LOS_IsRegionFlagPrivateOnly(region))) {
        ret = -EINVAL;
        goto ERROR_REGION_OUT;
    }
    if (IsPoolMapped(ipcInfo)) {//已经用户空间和内核空间之间存在映射关系了
        return -EEXIST;
    }
    if (ipcInfo->pool.uvaddr != NULL) {//ipc池已在进程空间有地址
        regionTemp = LOS_RegionFind(pcb->vmSpace, (VADDR_T)(UINTPTR)ipcInfo->pool.uvaddr);//在指定进程空间中找到所在线性区
        if (regionTemp != NULL) {
            (VOID)LOS_RegionFree(pcb->vmSpace, regionTemp);//先释放线性区
        }
		// 建议加上 ipcInfo->pool.uvaddr = NULL; 同下
    }
    ipcInfo->pool.uvaddr = (VOID *)(UINTPTR)region->range.base;//将指定的线性区和ipc池虚拟地址绑定
    if (ipcInfo->pool.kvaddr != NULL) {//如果存在内核空间地址
        LOS_VFree(ipcInfo->pool.kvaddr);//因为要重新映射,所以必须先释放掉物理内存
        ipcInfo->pool.kvaddr = NULL; //从效果上看, 这句话可以不加,但加上看着更舒服,    uvaddr 和 kvaddr 一对新人迎接美好未来
    }
    /* use vmalloc to alloc phy mem */
    ipcInfo->pool.kvaddr = LOS_VMalloc(region->range.size);//从内核动态空间中申请线性区,分配同等量的物理内存,做好 内核 <-->物理内存的映射
    if (ipcInfo->pool.kvaddr == NULL) {//申请物理内存失败, 肯定是玩不下去了.
        ret = -ENOMEM; //返回没有内存了
        goto ERROR_REGION_OUT;
    }
    /* do mmap */
    ret = DoIpcMmap(pcb, region);//对uvaddr和kvaddr做好映射关系,如此用户态下通过操作uvaddr达到操作kvaddr的目的
    if (ret) {
        goto ERROR_MAP_OUT;
    }
    /* ipc pool init */
    if (LOS_MemInit(ipcInfo->pool.kvaddr, region->range.size) != LOS_OK) {//初始化ipc池
        ret = -EINVAL;
        goto ERROR_MAP_OUT;
    }
    ipcInfo->pool.poolSize = region->range.size;//ipc池大小为线性区大小
    return 0;
ERROR_MAP_OUT:
    LOS_VFree(ipcInfo->pool.kvaddr);
ERROR_REGION_OUT:
    if (ipcInfo != NULL) {
        ipcInfo->pool.uvaddr = NULL;
        ipcInfo->pool.kvaddr = NULL;
    }
    return ret;
}

解读

  • 这个函数一定要看明白,重要部分已加上注释,主要干了两件事。
  • 通过LOS_VMalloc向内核堆空间申请了一段物理内存,参数是线性区的大小,并做好了映射,因是内核堆空间,所以分配的虚拟地址就是个内核地址,并将这个地址赋给了pool.kvaddr
ipcInfo->pool.kvaddr = LOS_VMalloc(region->range.size);//从内核动态空间中申请线性区,分配同等量的物理内存,做好 内核 <-->物理内存的映射
  • 通过DoIpcMmap将参数pcb(用户进程)的IPC消息池的pool.uvaddr也映射到LOS_VMalloc分配的物理内存上,
  ret = DoIpcMmap(pcb, region);//对uvaddr和kvaddr做好映射关系,如此用户态下通过操作uvaddr达到操作

详看DoIpcMmap的实现,因为太重要此处代码不做删改。

LITE_OS_SEC_TEXT STATIC INT32 DoIpcMmap(LosProcessCB *pcb, LosVmMapRegion *region)
  {
      UINT32 i;
      INT32 ret = 0;
      PADDR_T pa;
      UINT32 uflags = VM_MAP_REGION_FLAG_PERM_READ | VM_MAP_REGION_FLAG_PERM_USER;
      LosVmPage *vmPage = NULL;
      VADDR_T uva = (VADDR_T)(UINTPTR)pcb->ipcInfo->pool.uvaddr;//用户空间地址
      VADDR_T kva = (VADDR_T)(UINTPTR)pcb->ipcInfo->pool.kvaddr;//内核空间地址
      (VOID)LOS_MuxAcquire(&pcb->vmSpace->regionMux);
      for (i = 0; i < (region->range.size >> PAGE_SHIFT); i++) {//获取线性区页数,一页一页映射
          pa = LOS_PaddrQuery((VOID *)(UINTPTR)(kva + (i << PAGE_SHIFT)));//通过内核空间查找物理地址
          if (pa == 0) {
              PRINT_ERR("%s, %d\n", __FUNCTION__, __LINE__);
              ret = -EINVAL;
              break;
          }
          vmPage = LOS_VmPageGet(pa);//获取物理页框
          if (vmPage == NULL) {//目的是检查物理页是否存在
              PRINT_ERR("%s, %d\n", __FUNCTION__, __LINE__);
              ret = -EINVAL;
              break;
          }
          STATUS_T err = LOS_ArchMmuMap(&pcb->vmSpace->archMmu, uva + (i << PAGE_SHIFT), pa, 1, uflags);//将物理页映射到用户空间
          if (err < 0) {
              ret = err;
              PRINT_ERR("%s, %d\n", __FUNCTION__, __LINE__);
              break;
          }
      }
      /* if any failure happened, rollback | 如果中间发生映射失败,则回滚*/
      if (i != (region->range.size >> PAGE_SHIFT)) {
          while (i--) {
              pa = LOS_PaddrQuery((VOID *)(UINTPTR)(kva + (i << PAGE_SHIFT)));//查询物理地址
              vmPage = LOS_VmPageGet(pa);//获取物理页框
              (VOID)LOS_ArchMmuUnmap(&pcb->vmSpace->archMmu, uva + (i << PAGE_SHIFT), 1);//取消与用户空间的映射
              LOS_PhysPageFree(vmPage);//释放物理页
          }
      }
      (VOID)LOS_MuxRelease(&pcb->vmSpace->regionMux);
      return ret;
  }

  • 至次LiteIPC的准备工作已经完成,接下来就是操作/控制阶段了

LiteIpcIoctl | 控制

LITE_OS_SEC_TEXT int LiteIpcIoctl(struct file *filep, int cmd, unsigned long arg)
{
    UINT32 ret = LOS_OK;
    LosProcessCB *pcb = OsCurrProcessGet();
    ProcIpcInfo *ipcInfo = pcb->ipcInfo;
	// 整个系统只能有一个ServiceManager,而Service可以有多个。ServiceManager有两个主要功能:一是负责Service的注册和注销,
	// 二是负责管理Service的访问权限(只有有权限的任务(Task)可以向对应的Service发送IPC消息)。
    switch (cmd) {
        case IPC_SET_CMS:
            return SetCms(arg); //设置ServiceManager , 整个系统只能有一个ServiceManager
        case IPC_CMS_CMD: // 控制命令,创建/删除/添加权限 
            return HandleCmsCmd((CmsCmdContent *)(UINTPTR)arg);
        case IPC_SET_IPC_THREAD:
            return SetIpcTask();//将当前任务设置成当前进程的IPC任务ID
        case IPC_SEND_RECV_MSG://发送和接受消息,代表消息内容
            ret = LiteIpcMsgHandle((IpcContent *)(UINTPTR)arg);//处理IPC消息
            break;
    }
    return ret;
}

解读

  • LiteIPC中有两个主要概念,一个是ServiceManager,另一个是Service。整个系统只能有一个ServiceManager,而Service可以有多个。ServiceManager有两个主要功能:一是负责Service的注册和注销,二是负责管理Service的访问权限(只有有权限的任务(Task)可以向对应的Service发送IPC消息)。IPC_SET_CMS为设置ServiceManager命令,IPC_CMS_CMD为对Service的管理命令。

  • IPC_SEND_RECV_MSG为消息的处理过程,消息的封装结合上篇理解,接收和发送消息对应的是LiteIpcRead和LiteIpcWrite两个函数。

  • 本处只说明LiteIpcWrite 写消息指的是从用户空间向内核空间写数据,在消息内容体中已经指明这个消息时写给哪个任务的,如此达到了进程间(其实也是任务间)通讯的目的。

  /// 写IPC消息队列,从用户空间到内核空间
  LITE_OS_SEC_TEXT STATIC UINT32 LiteIpcWrite(IpcContent *content)
  {
      UINT32 ret, intSave;
      UINT32 dstTid;
      IpcMsg *msg = content->outMsg;
      LosTaskCB *tcb = OS_TCB_FROM_TID(dstTid);//目标任务实体
      LosProcessCB *pcb = OS_PCB_FROM_PID(tcb->processID);//目标进程实体
      if (pcb->ipcInfo == NULL) {
          PRINT_ERR("pid %u Liteipc not create\n", tcb->processID);
          return -EINVAL;
      }
      //这里为什么要申请msg->dataSz,因为IpcMsg中的真正数据体 data是个指针,它的大小是dataSz . 同时申请存储偏移量空间
      UINT32 bufSz = sizeof(IpcListNode) + msg->dataSz + msg->spObjNum * sizeof(UINT32);//这句话是理解上层消息在内核空间数据存放的关键!!! @note_good
      IpcListNode *buf = (IpcListNode *)LiteIpcNodeAlloc(tcb->processID, bufSz);//向内核空间申请bufSz大小内存
      if (buf == NULL) {
          PRINT_ERR("%s, %d\n", __FUNCTION__, __LINE__);
          return -ENOMEM;
      }//IpcListNode的第一个成员变量就是IpcMsg
      ret = CopyDataFromUser(buf, bufSz, (const IpcMsg *)msg);//将消息内容拷贝到内核空间,包括消息控制体+内容体+偏移量
      if (ret != LOS_OK) {
          PRINT_ERR("%s, %d\n", __FUNCTION__, __LINE__);
          goto ERROR_COPY;
      }
      if (tcb->ipcTaskInfo == NULL) {//如果任务还没有IPC信息
          tcb->ipcTaskInfo = LiteIpcTaskInit();//初始化这个任务的IPC信息模块,因为消息来了要处理了
      }
      ret = HandleSpecialObjects(dstTid, buf, FALSE);//处理消息
      if (ret != LOS_OK) {
          PRINT_ERR("%s, %d\n", __FUNCTION__, __LINE__);
          goto ERROR_COPY;
      }
      /* add data to list and wake up dest task *///向列表添加数据并唤醒目标任务
      SCHEDULER_LOCK(intSave);
      LOS_ListTailInsert(&(tcb->ipcTaskInfo->msgListHead), &(buf->listNode));//把消息控制体挂到目标任务的IPC链表头上
      OsHookCall(LOS_HOOK_TYPE_IPC_WRITE, &buf->msg, dstTid, tcb->processID, tcb->waitFlag);
      if (tcb->waitFlag == OS_TASK_WAIT_LITEIPC) {//如果这个任务在等这个消息,注意这个tcb可不是当前任务
          OsTaskWakeClearPendMask(tcb);//撕掉对应标签
          OsSchedTaskWake(tcb);//唤醒任务执行,因为任务在等待读取 IPC消息
          SCHEDULER_UNLOCK(intSave);
          LOS_MpSchedule(OS_MP_CPU_ALL);//设置调度方式,所有CPU核发生一次调度,这里非要所有CPU都调度吗? 
          //可不可以查询下该任务挂在哪个CPU上,只调度对应CPU呢?   注者在此抛出思考 @note_thinking
          LOS_Schedule();//发起调度
      } else {
          SCHEDULER_UNLOCK(intSave);
      }
      return LOS_OK;
  ERROR_COPY:
      LiteIpcNodeFree(OS_TCB_FROM_TID(dstTid)->processID, buf);//拷贝发生错误就要释放内核堆内存,那可是好大一块堆内存啊
      return ret;
  }  
  • 大概流程就是从LiteIpc内存池中分配内核空间装用户空间的数据,注意一定要从LiteIpcNodeAlloc分配,原因代码中也已注明。
  • 有数据了就将数据挂到目标任务的IPC双向链表上,如果任务在等待读取消息(OS_TASK_WAIT_LITEIPC)则唤醒目标任务执行,并发起调度LOS_Schedule。
  • 调度到目标任务后,继续执行LiteIpcRead,此时读函数正在经历一个死循环等待将消息读到用户空间。

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

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

本路线共分为四个阶段:

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

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

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

第四阶段:全网首发-工业级南向设备开发就业技术: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

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

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

相关文章

电商数据接口|如何获取电商数据?

随着互联网的发展&#xff0c;电商的运营方式也逐渐数据化&#xff0c;在大数据的影响下&#xff0c;电商领域很大程度上改变了传统的运营模式。很多商家如今都非常重视数据&#xff0c;并将数据贯穿于整个店铺的运营之中。 那么&#xff0c;具体来说电商大数据有哪些妙用呢&a…

C++ 对象型参数和返回值

对象型参数和返回值 1.对象型类型作为函数的参数2.对象型参数作为函数的返回值 1.对象型类型作为函数的参数 使用对象类型作为函数的参数或者返回值&#xff0c;可能会产生一些不必要的中间对象 例子&#xff1a; // 使用对象类型作为函数的参数 void test1(Car car) {}完整代…

空号检测-号码批量检测API接口-关机停机风险号检测

手机空号检测分为普通空号检测和实时检测两种类型&#xff1a; 普通空号检测返回结果&#xff1a;实号、风险号、空号、沉默号 。 1.普通版的检测不会实时更新数据&#xff0c;因此其数据库中的信息可能不是最新的。 2.覆盖基础运营商的数据库&#xff0c;检测范围相对有限&…

MSMQ消息队列

MQ是一种企业服务的消息中间节技术&#xff0c;这种技术常常伴随着企业服务总线相互使用&#xff0c;构成了企业分布式开发的一部分&#xff0c;如果考虑到消息的发送和传送之间是可以相互不联系的并且需要分布式架构&#xff0c;则可以考虑使用MQ做消息的中间价技术&#xff0…

【云原生】kubernetes核心组件

引言&#xff1a; Kubernetes 是为运行分布式集群而建立的&#xff0c;分布式系统的本质使得网络成为 Kubernetes 的核心和必要组成部分&#xff0c;了解 Kubernetes 网络模型可以使你能够正确运行、监控和排查应用程序故障。 一、Kubernetes的核心组件 1.1、Master组件 1.1.…

C# WinForm —— 18 NumericUpDown 介绍

1. 简介 数字显示框&#xff0c;通过向上、向下按钮来 增加/减小 显示的数值 2. 常用属性 属性解释(Name)控件ID&#xff0c;在代码里引用的时候会用到,一般以 numUD 开头Hexadecimal数值 up-down 控件的值是否应以十六进制显示Increment每单击一下按钮&#xff0c;增加或减…

0508GoodsContent的Maven项目

0508GoodsContent的Maven项目包-CSDN博客 数据库字段 页面需求

立聪堂助听器29周年暨第九届助听使者活动圆满落幕

5月10日对于立聪堂来说是个特别的日子&#xff0c;这家专注于听力健康领域的公司迎来了29周年。同时&#xff0c;立聪堂第九届助听使者代表及其家人也受邀参观立聪堂南京总部&#xff0c;共庆29周年。 易被忽视的老人听力健康 大数据显示&#xff0c;我国65岁以上老人&#x…

HubSpot海外获客系统的客户生命周期管理

潜在客户阶段&#xff1a;精准识别与吸引 在潜在客户阶段&#xff0c;HubSpot的强大数据分析能力使得企业能够精准地识别出目标市场的潜在客户。通过HubSpot的跟踪和分析工具&#xff0c;企业可以深入了解潜在客户的来源、浏览行为、兴趣偏好等关键信息&#xff0c;进而定制出…

MATLAB | 最新版MATLAB绘图速查表来啦!!

之前看大佬Pjer做的MATLAB速查表 http://home.ustc.edu.cn/~pjer1316/matlabplot/ 感觉非常的实用&#xff0c;最近几次MATLAB更新围绕画图方面也有很多新东西&#xff0c;于是就有了自己做一张最新版的速查表的想法&#xff0c;这张表长这样&#xff1a; 这张表的配色基本上…

为什么跨境电商大佬都在自养号测评?看完你就懂了!

在跨境电商的激烈竞争中&#xff0c;各大平台如亚马逊、拼多多Temu、shopee、Lazada、wish、速卖通、煤炉、敦煌、独立站、雅虎、eBay、TikTok、Newegg、Allegro、乐天、美客多、阿里国际、沃尔玛、Nike、OZON、Target以及Joom等&#xff0c;纷纷成为商家们竞相角逐市场份额的焦…

Adaptive leakthrough ANC自适应通透ANC调试快速上手3

1.在MDE中通过命令控制,让耳机进入到ANC tuning状态,在ancdesigner工具中连接成功; 2.前面Mision mode的方法中得到5各录音文件,生产相应的missin model; Adaptive leakthrough ANC自适应通透ANC调试快速上手 3.在file->configration中设置FB为Filter_PEQ手动调试,可以…

C++入门——引用(2)

前言 上一节我们开始学习了C&#xff0c;并且对C有了初步的了解&#xff0c;这一节我们继续学习C的基础&#xff0c;那么废话不多说&#xff0c;我们正式进入今天的学习 C中的引用 1.1引用的概念 引用不是新定义一个变量&#xff0c;而是给已存在变量取了一个别名&#xff0…

限流算法(令牌桶漏桶计数器)

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;Spring⛺️稳中求进&#xff0c;晒太阳 业务重的三种情况&#xff1a;突发流量、恶意流量、业务本身需要 限流: 是为了保护自身系统和下游系统不被高并发流量冲垮&#xff0c;导致系统雪崩…

jQuery-2.鼠标焦点事件、节点操作、遍历元素、效果

鼠标事件 鼠标事件是当用户在文档上移动或单击鼠标时而产生的事件&#xff0c;常用的鼠标事件&#xff1a; 方法 描述 执行时机 click() 触发或将函数绑定到指定元素的click事件 单击鼠标时 mouseover() 触发或将函数绑定到指定元素的mouse over事件 鼠标移过时 mous…

体重秤蓝牙语音芯片方案-WT2605蓝牙音频ic在电子秤上的应用

在快节奏的现代生活中&#xff0c;健康成为了每个人关注的焦点。而体重作为健康指标之一&#xff0c;更是备受关注。如今&#xff0c;一款全新的智能体重秤蓝牙语音芯片方案正悄然改变着我们的健康管理方式&#xff0c;让健康触手可及。 性能&#xff1a; 1&#xff1a;蓝牙语…

.Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 发布到 Win7+

.Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 实测可以完整运行在 win7sp1/win10/win11. 如果用其他工具打包,还可以运行在mac/linux下, 传送门BlazorHybrid 发布为无依赖包方式 安装 WebView2Runtime 1.57 MB或136 MB 测试DEMO 发布为依赖包方式 安装 WebView2Runtime 1.…

渗透测试工具及插件第二期

一、OWASP Penetration Testing Kit 这个工具他集成了中间件&#xff0c;等版本信息&#xff0c;漏洞信息&#xff0c;url&#xff0c;标识头等信息&#xff0c;WAF/CDN识别&#xff0c;密匙等信息&#xff0c;多种信息的功能上集合的插件。 说明书&#xff1a;https://micros…

pwn(一)前置技能

以下是pwn中的题目&#xff08;漏洞&#xff09;类型&#xff1a; 关于pwn的学习&#xff1a; 一.什么是pwn&#xff1f;&#xff08;二进制的漏洞&#xff09; "Pwn"是一个俚语&#xff0c;起源于电子游戏社区&#xff0c;经常在英语中用作网络或电子游戏文化中的…

qt 5.15.x 安装android过程记录

1.经过好几天的qt for android 安装&#xff0c;发现存在很多坑 参考其他文章可以编译出APK文件。但是我发现(我的机器上)无法调试apk程序&#xff0c;不能调试那怎么行呢&#xff0c;看了很多文章都是运行出结果了就结束了。没有展示怎么调试程序。 很多文章都是建议安装JDK8…