鸿蒙原生开发——轻内核A核源码分析系列三 物理内存(2)

news2024/11/17 5:48:33

3.1.2.3 函数OsVmPhysLargeAlloc

当执行到这个函数时,说明空闲链表上的单个内存页节点的大小已经不能满足要求,超过了第9个链表上的内存页节点的大小了。⑴处计算需要申请的内存大小。⑵从最大的链表上进行遍历每一个内存页节点。⑶根据每个内存页的开始内存地址,计算需要的内存的结束地址,如果超过内存段的大小,则继续遍历下一个内存页节点。

⑷处此时paStart表示当前内存页的结束地址,接下来paStart >= paEnd表示当前内存页的大小满足申请的需求;paStart < seg->startpaStart >= (seg->start + seg->size)发生溢出错误,内存页结束地址不在内存段的地址范围内。⑸处表示当前内存页的下一个内存页结构体,如果该结构体不在空闲链表上,则break跳出循环。如果在空闲链表上,表示连续的空闲内存页会拼接起来,满足大内存申请的需要。⑹表示一个或者多个连续的内存页的大小满足申请需求。

STATIC LosVmPage *OsVmPhysLargeAlloc(struct VmPhysSeg *seg, size_t nPages)
{
    struct VmFreeList *list = NULL;
    LosVmPage *page = NULL;
    LosVmPage *tmp = NULL;
    PADDR_T paStart;
    PADDR_T paEnd;
⑴  size_t size = nPages << PAGE_SHIFT;

⑵  list = &seg->freeList[VM_LIST_ORDER_MAX - 1];
    LOS_DL_LIST_FOR_EACH_ENTRY(page, &list->node, LosVmPage, node) {
⑶      paStart = page->physAddr;
        paEnd = paStart + size;
        if (paEnd > (seg->start + seg->size)) {
            continue;
        }

        for (;;) {
⑷          paStart += PAGE_SIZE << (VM_LIST_ORDER_MAX - 1);
            if ((paStart >= paEnd) || (paStart < seg->start) ||
                (paStart >= (seg->start + seg->size))) {
                break;
            }
⑸          tmp = &seg->pageBase[(paStart - seg->start) >> PAGE_SHIFT];
            if (tmp->order != (VM_LIST_ORDER_MAX - 1)) {
                break;
            }
        }
⑹      if (paStart >= paEnd) {
            return page;
        }
    }

    return NULL;
}

3.1.2.4 函数OsVmPhysFreeListDelUnsafeOsVmPhysFreeListAddUnsafe

内部函数OsVmPhysFreeListDelUnsafe用于从空闲内存页节点链表上删除一个内存页节点,名称中有Unsafe字样,是因为函数体内并没有对链表操作加自旋锁,安全性由外部调用函数保证。⑴处进行校验,确保内存段和空闲链表索引符合要求。⑵处获取内存段和空闲链表,⑶处空闲链表上内存页节点数目减1,并把内存块从空闲链表上删除。⑷处设置内存页的order索引值为最大值来标记非空闲内存页。

STATIC VOID OsVmPhysFreeListDelUnsafe(LosVmPage *page)
{
    struct VmPhysSeg *seg = NULL;
    struct VmFreeList *list = NULL;

⑴  if ((page->segID >= VM_PHYS_SEG_MAX) || (page->order >= VM_LIST_ORDER_MAX)) {
        LOS_Panic("The page segment id(%u) or order(%u) is invalid\n", page->segID, page->order);
    }

⑵  seg = &g_vmPhysSeg[page->segID];
    list = &seg->freeList[page->order];
⑶  list->listCnt--;
    LOS_ListDelete(&page->node);
⑷  page->order = VM_LIST_ORDER_MAX;
}

和空闲链表上删除对应的函数是空闲链表上插入空闲内存页节点函数OsVmPhysFreeListAddUnsafe。⑴处更新内存页的要挂载的空闲链表的索引值,然后获取内存页所在的内存段seg,并获取索引值对应的空闲链表。执行⑵把空闲内存页节点插入到空闲链表并更新节点数目。

STATIC VOID OsVmPhysFreeListAddUnsafe(LosVmPage *page, UINT8 order)
{
    struct VmPhysSeg *seg = NULL;
    struct VmFreeList *list = NULL;

    if (page->segID >= VM_PHYS_SEG_MAX) {
        LOS_Panic("The page segment id(%d) is invalid\n", page->segID);
    }

⑴  page->order = order;
    seg = &g_vmPhysSeg[page->segID];

    list = &seg->freeList[order];
⑵   LOS_ListTailInsert(&list->node, &page->node);
    list->listCnt++;
}

3.1.2.5 函数OsVmPhysPagesSpiltUnsafe

函数OsVmPhysPagesSpiltUnsafe用于分割内存块,参数中oldOrder表示需要申请的内存页节点对应的链表索引,newOrder表示实际申请的内存页节点对应的链表索引。如果索引值相等,则不需要拆分,不会执行for循环块的代码。由于伙伴算法中的链表数组中元素的特点,即每个链表中的内存页节点的大小等于2的幂次方个内存页。在拆分时,依次从高索引newOrder往低索引oldOrder遍历,拆分一个内存页节点作为空闲内存页节点挂载到对应的空闲链表上。⑴处开始循环从高索引到低索引,索引值减1,然后执行⑵获取伙伴内存页节点,可以看出,申请的内存块大于需求时,会把后半部分的高地址部分放入空闲链表,保留前半部分的低地址部分。⑶处的断言确保伙伴内存页节点索引值是最大值,表示属于空闲内存页节点。⑷处调用函数把内存页节点放入空闲链表。

STATIC VOID OsVmPhysPagesSpiltUnsafe(LosVmPage *page, UINT8 oldOrder, UINT8 newOrder)
{
    UINT32 order;
    LosVmPage *buddyPage = NULL;

    for (order = newOrder; order > oldOrder;) {
⑴      order--;
⑵      buddyPage = &page[VM_ORDER_TO_PAGES(order)];
⑶      LOS_ASSERT(buddyPage->order == VM_LIST_ORDER_MAX);
⑷      OsVmPhysFreeListAddUnsafe(buddyPage, order);
    }
}

这里有必要放这一张图,直观演示一下。假如我们需要申请8个内存页大小的内存节点,但是只有freeList[7]链表上才有空闲节点。申请成功后,超过了应用需要的大小,需要进行拆分。把27个内存页分为2份大小为26个内存页的节点,第一份继续拆分,第二份挂载到freeList[6]链表上。然后把第一份26个内存页拆分为2个25个内存页节点,第一份继续拆分,第二份挂载到freeList[5]链表上。依次进行下去,最后拆分为2份2^3个内存页大小的内存页节点,第一份作为实际申请的内存页返回,第二份挂载到freeList[3]链表上。如下图红色部分所示。

另外,函数OsVmRecycleExtraPages会调用OsVmPhysPagesFreeContiguous来回收申请的多余的内存页,后文再分析。

3.2 释放物理内存页接口

3.2.1 释放物理内存页接口介绍

和申请物理内存页接口相对应着,释放物理内存页的接口有3个,分别用于满足不同的释放内存页需求。函数LOS_PhysPagesFreeContiguous的传入参数为要释放物理页对应的内核虚拟地址空间中的虚拟内存地址和内存页数目。⑴处调用函数OsVmVaddrToPage把虚拟内存地址转换为物理内存页结构体地址,然后⑵处把内存页的连续内存页数目设置为0。⑶处调用函数OsVmPhysPagesFreeContiguous()释放物理内存页。函数LOS_PhysPageFree用于释放一个物理内存页,传入参数为要释放的物理页对应的物理页结构体地址。⑷处对引用计数自减,当小于等于0,表示没有其他引用时才进一步执行释放操作。该函数同样会调用函数OsVmPhysPagesFreeContiguous()释放物理内存页。函数LOS_PhysPagesFree用于释放挂在双向链表上的多个物理内存页,返回值为实际释放的物理页数目。⑸处遍历内存页双向链表,从链表上移除要释放的内存页节点。⑹处代码和释放一个内存页的函数代码相同。⑺处计算遍历的内存页的数目,函数最后会返回该值。

VOID LOS_PhysPagesFreeContiguous(VOID *ptr, size_t nPages)
{
    UINT32 intSave;
    struct VmPhysSeg *seg = NULL;
    LosVmPage *page = NULL;

    if (ptr == NULL) {
        return;
    }

⑴   page = OsVmVaddrToPage(ptr);
    if (page == NULL) {
        VM_ERR("vm page of ptr(%#x) is null", ptr);
        return;
    }
⑵  page->nPages = 0;

    seg = &g_vmPhysSeg[page->segID];
    LOS_SpinLockSave(&seg->freeListLock, &intSave);

⑶   OsVmPhysPagesFreeContiguous(page, nPages);

    LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
}

......

VOID LOS_PhysPageFree(LosVmPage *page)
{
    UINT32 intSave;
    struct VmPhysSeg *seg = NULL;

    if (page == NULL) {
        return;
    }

⑷  if (LOS_AtomicDecRet(&page->refCounts) <= 0) {
        seg = &g_vmPhysSeg[page->segID];
        LOS_SpinLockSave(&seg->freeListLock, &intSave);

        OsVmPhysPagesFreeContiguous(page, ONE_PAGE);
        LOS_AtomicSet(&page->refCounts, 0);

        LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
    }
}
······
size_t LOS_PhysPagesFree(LOS_DL_LIST *list)
{
    UINT32 intSave;
    LosVmPage *page = NULL;
    LosVmPage *nPage = NULL;
    LosVmPhysSeg *seg = NULL;
    size_t count = 0;

    if (list == NULL) {
        return 0;
    }

    LOS_DL_LIST_FOR_EACH_ENTRY_SAFE(page, nPage, list, LosVmPage, node) {
⑸      LOS_ListDelete(&page->node);
⑹      if (LOS_AtomicDecRet(&page->refCounts) <= 0) {
            seg = &g_vmPhysSeg[page->segID];
            LOS_SpinLockSave(&seg->freeListLock, &intSave);
            OsVmPhysPagesFreeContiguous(page, ONE_PAGE);
            LOS_AtomicSet(&page->refCounts, 0);
            LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
        }
⑺      count++;
    }

    return count;
}

3.2.2 释放物理内存页内部接口实现

3.2.2.1 函数OsVmVaddrToPage

函数OsVmVaddrToPage把虚拟内存地址转换为物理页结构体地址。⑴处调用函数LOS_PaddrQuery()把虚拟地址转为物理地址,该函数在虚实映射部分会详细讲述。⑵处遍历物理内存段,如果物理内存地址处于物理内存段的地址范围,则可以返回该物理地址对应的物理页结构体地址。

LosVmPage *OsVmVaddrToPage(VOID *ptr)
{
    struct VmPhysSeg *seg = NULL;
⑴  PADDR_T pa = LOS_PaddrQuery(ptr);
    UINT32 segID;

    for (segID = 0; segID < g_vmPhysSegNum; segID++) {
        seg = &g_vmPhysSeg[segID];
⑵      if ((pa >= seg->start) && (pa < (seg->start + seg->size))) {
            return seg->pageBase + ((pa - seg->start) >> PAGE_SHIFT);
        }
    }

    return NULL;
}
3.2.2.2 函数OsVmPhysPagesFreeContiguous

函数OsVmPhysPagesFreeContiguous()用于释放指定数量的连续物理内存页。⑴处根据物理内存页获取对应的物理内存地址。⑵处根据物理内存地址获取空闲内存页链表数组索引数值。⑶处获取索引值对应的链表上的内存页节点的内存页数目。⑷处如果要释放的内存页数nPages小于当前链表上的内存页节点的数目,则跳出循环执行⑹处代码,去释放到小索引的双向链表上。⑸处调用函数OsVmPhysPagesFree()释放指定链表上的内存页,然后更新内存页数量和内存页结构体地址。

⑹处根据内存页数量计算对应的链表索引,根据索引值计算链表上内存页节点的大小。⑺处调用函数OsVmPhysPagesFree()释放指定链表上的内存页,然后更新内存页数量和内存页结构体地址。

VOID OsVmPhysPagesFreeContiguous(LosVmPage *page, size_t nPages)
{
    paddr_t pa;
    UINT32 order;
    size_t n;

    while (TRUE) {
⑴      pa = VM_PAGE_TO_PHYS(page);
⑵      order = VM_PHYS_TO_ORDER(pa);
⑶      n = VM_ORDER_TO_PAGES(order);
⑷      if (n > nPages) {
            break;
        }
⑸      OsVmPhysPagesFree(page, order);
        nPages -= n;
        page += n;
    }

    while (nPages > 0) {
⑹      order = LOS_HighBitGet(nPages);
        n = VM_ORDER_TO_PAGES(order);
⑺      OsVmPhysPagesFree(page, order);
        nPages -= n;
        page += n;
    }
}
3.2.2.3 函数OsVmPhysPagesFree

函数OsVmPhysPagesFree()释放内存页到对应的空闲内存页链表。内存页块释放时,会在当前链表找地址连续的伙伴内存页块进行合并,然后去上一级链表上继续查找是否存在连续的伙伴内存页块。⑴做传入参数校验。⑵处需要至少是倒数第二个链表,这样内存页节点可以和大索引链表上的节点合并。⑶处获取内存页对应的物理内存地址,然后后面会开始do-while循环,查找是否存在连续的内存页节点。⑷处的VM_ORDER_TO_PHYS(order)计算出链表索引值对应的伙伴位图,然后进行异或运算计算出伙伴内存页的物理内存地址。⑸处物理地址转换为内存页结构体,进一步判断:如果内存页不存在或者不在空闲链表上,则跳出循环while循环。否则如果伙伴内存节点存在,则执行⑹把伙伴页从链表上移除,然后索引值加1。⑺处链表索引加1,然后进行逻辑与计算得到物理内存地址。此时物理内存地址,和合并的两块内存页块地址连续。该内存地址在高一级的空闲链表上不一定存在,存在则继续合并,不存在则退出循环。当索引order为8,要插入到最后一个链表上时,或者没有再找到可以合并的节点时,则直接执行⑻插入内存页节点到空闲链表上。

VOID OsVmPhysPagesFree(LosVmPage *page, UINT8 order)
{
    paddr_t pa;
    LosVmPage *buddyPage = NULL;

⑴  if ((page == NULL) || (order >= VM_LIST_ORDER_MAX)) {
        return;
    }

⑵  if (order < VM_LIST_ORDER_MAX - 1) {
⑶        pa = VM_PAGE_TO_PHYS(page);        
        do {
⑷          pa ^= VM_ORDER_TO_PHYS(order);
⑸          buddyPage = OsVmPhysToPage(pa, page->segID);
            if ((buddyPage == NULL) || (buddyPage->order != order)) {
                break;
            }
⑹          OsVmPhysFreeListDel(buddyPage);
            order++;
⑺          pa &= ~(VM_ORDER_TO_PHYS(order) - 1);
            page = OsVmPhysToPage(pa, page->segID);
        } while (order < VM_LIST_ORDER_MAX - 1);
    }

⑻  OsVmPhysFreeListAdd(page, order);
}

3.3 查询物理页地址接口

3.3.1 函数LOS_VmPageGet()

函数LOS_VmPageGet用于根据物理内存地址参数计算对应的物理内存页结构体地址。⑴处遍历物理内存段,调用函数OsVmPhysToPage根据物理内存地址和内存段编号计算物理内存页结构体,该函数后文再分析。⑵处如果获取的物理内存页结构体不为空,则跳出循环,返回物理内存页结构体指针。

LosVmPage *LOS_VmPageGet(PADDR_T paddr)
{
    INT32 segID;
    LosVmPage *page = NULL;

    for (segID = 0; segID < g_vmPhysSegNum; segID++) {
⑴      page = OsVmPhysToPage(paddr, segID);
⑵      if (page != NULL) {
            break;
        }
    }

    return page;
}

继续看下函数OsVmPhysToPage的代码。⑴处如果参数传入的物理内存地址不在指定的物理内存段的地址范围之内则返回NULL。⑵处计算物理内存地址相对内存段开始地址的偏移值。⑶处根据偏移值计算出偏移的内存页的数目,然后返回物理内存地址对应的物理页结构体的地址。

LosVmPage *OsVmPhysToPage(paddr_t pa, UINT8 segID)
{
    struct VmPhysSeg *seg = NULL;
    paddr_t offset;

    if (segID >= VM_PHYS_SEG_MAX) {
        LOS_Panic("The page segment id(%d) is invalid\n", segID);
    }
    seg = &g_vmPhysSeg[segID];
⑴  if ((pa < seg->start) || (pa >= (seg->start + seg->size))) {
        return NULL;
    }

⑵  offset = pa - seg->start;
⑶  return (seg->pageBase + (offset >> PAGE_SHIFT));
}

3.3.2 函数LOS_PaddrToKVaddr

函数LOS_PaddrToKVaddr根据物理地址获取其对应的内核虚拟地址。⑴处遍历物理内存段数组,然后在⑵处判断如果物理地址处于遍历到的物理内存段的地址范围内,则执行⑶,传入的物理内存地址相对物理内存开始地址的偏移加上内核态虚拟地址空间的开始地址就是物理地址对应的内核虚拟地址。

VADDR_T *LOS_PaddrToKVaddr(PADDR_T paddr)
{
    struct VmPhysSeg *seg = NULL;
    UINT32 segID;

    for (segID = 0; segID < g_vmPhysSegNum; segID++) {
 ⑴     seg = &g_vmPhysSeg[segID];
 ⑵     if ((paddr >= seg->start) && (paddr < (seg->start + seg->size))) {
 ⑶          return (VADDR_T *)(UINTPTR)(paddr - SYS_MEM_BASE + KERNEL_ASPACE_BASE);
        }
    }

    return (VADDR_T *)(UINTPTR)(paddr - SYS_MEM_BASE + KERNEL_ASPACE_BASE);
}

3.4 其他函数

3.4.1 函数OsPhysSharePageCopy

函数OsPhysSharePageCopy用于复制共享内存页。 ⑴处进行参数校验, ⑵处获取老内存页, ⑶处获取内存段。⑷处如果老内存页引用计数为1,则把老物理内存地址直接赋值给新物理内存地址。⑸处如果内存页有多个引用,则先转化为虚拟内存地址,然后执行⑹进行内存页的内容复制。⑺刷新新老内存页的引用计数。

VOID OsPhysSharePageCopy(PADDR_T oldPaddr, PADDR_T *newPaddr, LosVmPage *newPage)
{
    UINT32 intSave;
    LosVmPage *oldPage = NULL;
    VOID *newMem = NULL;
    VOID *oldMem = NULL;
    LosVmPhysSeg *seg = NULL;

 ⑴  if ((newPage == NULL) || (newPaddr == NULL)) {
        VM_ERR("new Page invalid");
        return;
    }

 ⑵  oldPage = LOS_VmPageGet(oldPaddr);
    if (oldPage == NULL) {
        VM_ERR("invalid oldPaddr %p", oldPaddr);
        return;
    }

 ⑶  seg = &g_vmPhysSeg[oldPage->segID];
    LOS_SpinLockSave(&seg->freeListLock, &intSave);
⑷  if (LOS_AtomicRead(&oldPage->refCounts) == 1) {
        *newPaddr = oldPaddr;
    } else {
⑸      newMem = LOS_PaddrToKVaddr(*newPaddr);
        oldMem = LOS_PaddrToKVaddr(oldPaddr);
        if ((newMem == NULL) || (oldMem == NULL)) {
            LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
            return;
        }
⑹      if (memcpy_s(newMem, PAGE_SIZE, oldMem, PAGE_SIZE) != EOK) {
            VM_ERR("memcpy_s failed");
        }

⑺      LOS_AtomicInc(&newPage->refCounts);
        LOS_AtomicDec(&oldPage->refCounts);
    }
    LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
    return;
}

总结

本文首先了解了物理内存管理的结构体,接着阅读了物理内存如何初始化,然后分析了物理内存的申请、释放和查询等操作接口的源代码。

如果大家想更加深入的学习 OpenHarmony 开发的内容,不妨可以参考以下相关学习文档进行学习,助你快速提升自己:

OpenHarmony 开发环境搭建:https://qr18.cn/CgxrRy

《OpenHarmony源码解析》:https://qr18.cn/CgxrRy

  • 搭建开发环境
  • Windows 开发环境的搭建
  • Ubuntu 开发环境搭建
  • Linux 与 Windows 之间的文件共享
  • ……

系统架构分析:https://qr18.cn/CgxrRy

  • 构建子系统
  • 启动流程
  • 子系统
  • 分布式任务调度子系统
  • 分布式通信子系统
  • 驱动子系统
  • ……

OpenHarmony 设备开发学习手册:https://qr18.cn/CgxrRy

在这里插入图片描述

OpenHarmony面试题(内含参考答案):https://qr18.cn/CgxrRy

写在最后

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

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

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

相关文章

10秒变鬼短视频:四川鑫悦里文化传媒有限公司

10秒变鬼短视频&#xff1a;创意与惊悚的完美融合 在短视频的世界里&#xff0c;创新与独特性 节奏、巧妙的剪辑和惊悚的氛围&#xff0c;成为了许多观众喜爱的对象。四川鑫悦里文化传媒有限公司将探讨“10秒变鬼”短视频的创作技巧、受众心理以及其对短视频行业的启示。 一…

人脸识别系统---多人脸提取

一、多人脸提取 1.导包、设置窗口、选择文件夹里图片的函数和单人脸提取一致&#xff0c;此处省略&#xff08;详情看‘单人脸提取’&#xff09; 2.对图像进行处理&#xff0c;在窗口画布上显示出来 2.1定义名为tq的函数&#xff0c;在函数内部声明了三个全局变量file_path…

[Spring Boot实战] 如何快速地创建spring boot项目

文章目录 一、背景说明二、环境说明三、创建步骤3.1 选择项目JDK版本和Initializer服务地址3.2 设置项目的元数据3.3 引入依赖3.4 选择项目路径 四、额外的工作五、问题5.1 为什么使用Lombok&#xff1f; 一、背景说明 使用spring boot创建JAVA项目&#xff0c;是目前比较主流…

数据结构与算法笔记:基础篇 - 红黑树(下):掌握这些技巧,你也可以实现一个红黑树

概述 红黑树是一个让人又爱又恨的数据结构&#xff0c;“爱” 是因为它稳定、高性能&#xff0c;“恨” 是因为实现起来实在太难了。本章讲红黑树的实现&#xff0c;对于基础不太好的同学&#xff0c;理解起来可能会有些困难。但是&#xff0c;我觉得没有必要去死磕它。 为什…

一个公用的数据状态修改组件

灵感来自于一项重复的工作&#xff0c;下图中&#xff0c;这类禁用启用、审核通过不通过、设计成是什么状态否什么状态的场景很多。每一个都需要单独提供接口。重复工作还蛮大的。于是&#xff0c;基于该组件类捕获组件跳转写了这款通用接口。省时省力。 代码如下&#xff1a;…

专业学习|博弈论-课程沿革

学习来源&#xff1a;北京大学刘霖《博弈论》MOOC公开课 备注&#xff1a;仅做学习分享&#xff0c;请勿转载&#xff0c;转载必究&#xff01; &#xff08;一&#xff09;博弈论的预备知识 基本的微积分的知识和概率论的知识。简单的说会求导数&#xff0c;会求简单的积分&am…

文案策划日常大公开:掌握这些技巧,让你工作效率翻倍

本周的工作和生活安排得满满当当&#xff0c;但这些看似繁重的任务&#xff0c;其实都是我利用碎片化时间高效完成的。 这里和大家分享一下&#xff0c;我是如何在这忙碌的一周中&#xff0c;既完成本职工作又享受生活的。 本周工作日常汇总&#xff1a; 1. 品牌小红薯笔记&…

Java高阶数据结构-----并查集(详解)

目录 &#x1f9d0;一.并查集的基本概念&实例&#xff1a; &#x1f92a;二.并查集代码&#xff1a; &#x1f602;三&#xff1a;并查集的一些习题&#xff1a; A.省份数量 B.等式方程的可满足性 &#x1f9d0;一.并查集的基本概念&实例&#xff1a; 并查集概念&…

5.3 数据模型设计总结

概述&#xff1a; 数据模型设计是指根据需求和目标设计出合适的数据模型的过程。数据模型是对现实世界中数据的抽象和表示&#xff0c;它定义了数据的结构、关系和约束。数据模型设计的目标是保证数据的一致性、完整性和可用性&#xff0c;同时满足系统性能和可扩展性的要求。…

Python基础教程(十二):模块

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; &#x1f49d;&#x1f49…

【Java】解决Java报错:IOException during File Operations

文章目录 引言一、IOException的定义与概述1. 什么是IOException&#xff1f;2. IOException的常见触发场景3. 示例代码 二、解决方案1. 检查文件是否存在2. 使用try-with-resources语句3. 捕获和处理IOException4. 使用NIO进行文件操作 三、最佳实践1. 检查文件状态2. 使用try…

为什么你应该本地化你的软件

本地化您的软件是一项战略举措&#xff0c;可以显著提高其成功率和影响力。以下是您应该投资于软件本地化的几个令人信服的原因&#xff1a; 扩大您的市场覆盖范围 通过本地化您的软件&#xff0c;您可以开拓新市场并接触到更广泛的受众。许多用户更喜欢甚至需要他们母语的软…

(十二)人工智能应用--深度学习原理与实战--模型编译及训练参数的选择

神经网络模型的编译实际上是为网络指定几个非常重要的运行参数,包括优化器、损失函数(误差函数】和评价指标,这三者也代表着神经网络的核心运行机制----通过损失函数来计算网络误差、通过优化器来调整网络参数以降低误差、通过评价指标来衡量网络的性能。神经网络训练时除了…

【漏洞复现】Rejetto HTTP文件服务器 未授权RCE漏洞(CVE-2024-23692)

0x01 产品简介 Rejetto HTTP File Server(HFS)是一个基于HTTP协议的文件服务器软件&#xff0c;旨在为用户提供简单、轻量级且易于使用的文件共享解决方案。功能强大、易于使用的文件服务器软件&#xff0c;无论是个人使用还是团队协作&#xff0c;HFS都能满足用户的需求&…

平价蓝牙耳机推荐有哪些?四款平价顶尖机型盘点

对于预算有限但又追求高品质音效的朋友们&#xff0c;平价蓝牙耳机成为了一个非常实用的选择&#xff0c;在市面上琳琅满目的蓝牙耳机中&#xff0c;挑选出性价比极高且性能出众的款式并不容易&#xff0c;作为一个多年的蓝牙耳机发烧友&#xff0c;接下来我就将为大家盘点四款…

《精通ChatGPT:从入门到大师的Prompt指南》第7章:创意写作

第7章&#xff1a;创意写作 7.1 角色设定 角色设定是创意写作中最关键的环节之一。成功的角色设定能够让读者对故事产生共鸣&#xff0c;使故事更加生动有趣。角色不仅仅是情节发展的载体&#xff0c;更是读者情感的投射对象。因此&#xff0c;深入了解如何设定一个生动而有深…

如何使用免费的 Instant Data Scraper快速抓取网页数据

Instant Data Scraper 是一款非常简单易用的网页数据爬虫工具&#xff0c;你不需要任何代码知识&#xff0c;只需要点几下鼠标&#xff0c;就可以把你想要的数据下载到表格里面。以下是详细的使用步骤&#xff1a; 第一步&#xff1a;安装 Instant Data Scraper 打开谷歌浏览…

java:一个springfox swagger2的简单例子

# 示例程序 【pom.xml】 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.3.12.RELEASE</version> </dependency> <dependency><groupId>…

【每日刷题】Day61

【每日刷题】Day61 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f34d; &#x1f33c;文章目录&#x1f33c; 1. 671. 二叉树中第二小的节点 - 力扣&#xff08;LeetCode&#xff09; 2. 2331. 计算布尔二叉树的值…

02-DHCP原理与配置

1、DHCP的工作原理 当局域网中有大量的主机时&#xff0c;如果逐个为每一台主机手动设置IP地址、默认网关、DNS服务器地址等网络参数&#xff0c;这显然是一个费力也未必讨好的办法。 而DHCP服务器的应用&#xff0c;正好可以解决这一问题。 1.1 DHCP是什么 DHCP——动态主机…