鸿蒙内核源码分析(VFS篇) | 文件系统和谐共处的基础

基本概念 | 官方定义

VFS(Virtual File System)是文件系统的虚拟层,它不是一个实际的文件系统,而是一个异构文件系统之上的软件粘合层,为用户提供统一的类Unix文件操作接口。由于不同类型的文件系统接口不统一,若系统中有多个文件系统类型,访问不同的文件系统就需要使用不同的非标准接口。而通过在系统中添加VFS层,提供统一的抽象接口,屏蔽了底层异构类型的文件系统的差异,使得访问文件系统的系统调用不用关心底层的存储介质和文件系统类型,提高开发效率。

OpenHarmony内核中,VFS框架是通过在内存中的树结构来实现的,树的每个结点都是一个Vnode结构体,父子结点的关系以PathCache结构体保存。VFS最主要的两个功能是:

  • 查找节点。
  • 统一调用(标准)。

VFS层具体实现包括四个方面:

  • 通过三大函数指针操作接口,实现对不同文件系统类型调用不同接口实现标准接口功能;
  • 通过VnodePathCache机制,提升路径搜索以及文件访问的性能;
  • 通过挂载点管理进行分区管理;
  • 通过FD管理进行进程间FD隔离等。

三大操作接口

VFS层通过函数指针的形式,将统一调用按照不同的文件系统类型,分发到不同文件系统中进行底层操作。各文件系统的各自实现一套Vnode操作(VnodeOps)、挂载点操作(MountOps)以及文件操作接口(file_operations_vfs),并以函数指针结构体的形式存储于对应Vnode、挂载点、File结构体中,实现VFS层对下访问。这三个接口分别为:

VnodeOps | 操作 Vnode 节点
struct VnodeOps {
    int (*Create)(struct Vnode *parent, const char *name, int mode, struct Vnode **vnode);//创建节点
    int (*Lookup)(struct Vnode *parent, const char *name, int len, struct Vnode **vnode);//查询节点
    //Lookup向底层文件系统查找获取inode信息
    int (*Open)(struct Vnode *vnode, int fd, int mode, int flags);//打开节点
    int (*Close)(struct Vnode *vnode);//关闭节点
    int (*Reclaim)(struct Vnode *vnode);//回收节点
    int (*Unlink)(struct Vnode *parent, struct Vnode *vnode, const char *fileName);//取消硬链接
    int (*Rmdir)(struct Vnode *parent, struct Vnode *vnode, const char *dirName);//删除目录节点
    int (*Mkdir)(struct Vnode *parent, const char *dirName, mode_t mode, struct Vnode **vnode);//创建目录节点
    /*
    创建一个目录时,实际做了3件事:在其“父目录文件”中增加一个条目;分配一个inode;再分配一个存储块,
    用来保存当前被创建目录包含的文件与子目录。被创建的“目录文件”中自动生成两个子目录的条目,名称分别是:“.”和“..”。
    前者与该目录具有相同的inode号码,因此是该目录的一个“硬链接”。后者的inode号码就是该目录的父目录的inode号码。
    所以,任何一个目录的"硬链接"总数,总是等于它的子目录总数(含隐藏目录)加2。即每个“子目录文件”中的“..”条目,
    加上它自身的“目录文件”中的“.”条目,再加上“父目录文件”中的对应该目录的条目。
    */
    int (*Readdir)(struct Vnode *vnode, struct fs_dirent_s *dir);//读目录节点
    int (*Opendir)(struct Vnode *vnode, struct fs_dirent_s *dir);//打开目录节点
    int (*Rewinddir)(struct Vnode *vnode, struct fs_dirent_s *dir);//定位目录节点
    int (*Closedir)(struct Vnode *vnode, struct fs_dirent_s *dir);//关闭目录节点
    int (*Getattr)(struct Vnode *vnode, struct stat *st);//获取节点属性
    int (*Setattr)(struct Vnode *vnode, struct stat *st);//设置节点属性
    int (*Chattr)(struct Vnode *vnode, struct IATTR *attr);//改变节点属性(change attr)
    int (*Rename)(struct Vnode *src, struct Vnode *dstParent, const char *srcName, const char *dstName);//重命名
    int (*Truncate)(struct Vnode *vnode, off_t len);//缩减或扩展大小
    int (*Truncate64)(struct Vnode *vnode, off64_t len);//缩减或扩展大小
    int (*Fscheck)(struct Vnode *vnode, struct fs_dirent_s *dir);//检查功能
    int (*Link)(struct Vnode *src, struct Vnode *dstParent, struct Vnode **dst, const char *dstName);
    int (*Symlink)(struct Vnode *parentVnode, struct Vnode **newVnode, const char *path, const char *target);
    ssize_t (*Readlink)(struct Vnode *vnode, char *buffer, size_t bufLen);
};

MountOps | 挂载点操作

//挂载操作
struct MountOps {
    int (*Mount)(struct Mount *mount, struct Vnode *vnode, const void *data);//挂载
    int (*Unmount)(struct Mount *mount, struct Vnode **blkdriver);//卸载
    int (*Statfs)(struct Mount *mount, struct statfs *sbp);//统计文件系统的信息,如该文件系统类型、总大小、可用大小等信息
};

file_operations_vfs | 文件操作接口
struct file_operations_vfs 
{
  int     (*open)(struct file *filep);	//打开文件
  int     (*close)(struct file *filep);	//关闭文件
  ssize_t (*read)(struct file *filep, char *buffer, size_t buflen);	//读文件
  ssize_t (*write)(struct file *filep, const char *buffer, size_t buflen);//写文件
  off_t   (*seek)(struct file *filep, off_t offset, int whence);//寻找,检索 文件
  int     (*ioctl)(struct file *filep, int cmd, unsigned long arg);//对文件的控制命令
  int     (*mmap)(struct file* filep, struct VmMapRegion *region);//内存映射实现<文件/设备 - 线性区的映射>
  /* The two structures need not be common after this point */

#ifndef CONFIG_DISABLE_POLL
  int     (*poll)(struct file *filep, poll_table *fds);	//轮询接口
#endif
  int     (*stat)(struct file *filep, struct stat* st);	//统计接口
  int     (*fallocate)(struct file* filep, int mode, off_t offset, off_t len);
  int     (*fallocate64)(struct file *filep, int mode, off64_t offset, off64_t len);
  int     (*fsync)(struct file *filep);
  ssize_t (*readpage)(struct file *filep, char *buffer, size_t buflen);
  int     (*unlink)(struct Vnode *vnode);
};

PathCache | 路径缓存

PathCache是路径缓存,它通过哈希表存储,利用父节点Vnode的地址和子节点的文件名,可以从PathCache中快速查找到子节点对应的Vnode。当前PageCache仅支持缓存二进制文件,在初次访问文件时通过mmap映射到内存中,下次再访问时,直接从PageCache中读取,可以提升对同一个文件的读写速度。另外基于PageCache可实现以文件为基底的进程间通信。下图展示了文件/目录的查找流程。

LIST_HEAD g_pathCacheHashEntrys[LOSCFG_MAX_PATH_CACHE_SIZE];	//路径缓存哈希表项
struct PathCache {//路径缓存
    struct Vnode *parentVnode;    /* vnode points to the cache */	
    struct Vnode *childVnode;     /* vnode the cache points to */
    LIST_ENTRY parentEntry;       /* list entry for cache list in the parent vnode */
    LIST_ENTRY childEntry;        /* list entry for cache list in the child vnode */
    LIST_ENTRY hashEntry;         /* list entry for buckets in the hash table */
    uint8_t nameLen;              /* length of path component */
#ifdef LOSCFG_DEBUG_VERSION
    int hit;                      /* cache hit count*/
#endif
    char name[0];                 /* path component name */
};
//路径缓存初始化
int PathCacheInit(void)
{
    for (int i = 0; i < LOSCFG_MAX_PATH_CACHE_SIZE; i++) {
        LOS_ListInit(&g_pathCacheHashEntrys[i]);
    }
    return LOS_OK;
}

挂载点管理

当前OpenHarmony内核中,对系统中所有挂载点通过链表进行统一管理。挂载点结构体中,记录了该挂载分区内的所有Vnode。当分区卸载时,会释放分区内的所有Vnode。

static LIST_HEAD *g_mountList = NULL;//挂载链表,上面挂的是系统所有挂载点
struct Mount {
    LIST_ENTRY mountList;              /* mount list */			 //通过本节点将Mount挂到全局Mount链表上
    const struct MountOps *ops;        /* operations of mount */ //挂载操作函数	
    struct Vnode *vnodeBeCovered;      /* vnode we mounted on */ //要被挂载的节点 即 /bin1/vs/sd 对应的 vnode节点
    struct Vnode *vnodeCovered;        /* syncer vnode */		 //要挂载的节点	即/dev/mmcblk0p0 对应的 vnode节点
    struct Vnode *vnodeDev;            /* dev vnode */
    LIST_HEAD vnodeList;               /* list of vnodes */		//链表表头
    int vnodeSize;                     /* size of vnode list */	//节点数量
    LIST_HEAD activeVnodeList;         /* list of active vnodes */	//激活的节点链表
    int activeVnodeSize;               /* szie of active vnodes list *///激活的节点数量
    void *data;                        /* private data */	//私有数据,可使用这个成员作为一个指向它们自己内部数据的指针
    uint32_t hashseed;                 /* Random seed for vfs hash */ //vfs 哈希随机种子
    unsigned long mountFlags;          /* Flags for mount */	//挂载标签
    char pathName[PATH_MAX];           /* path name of mount point */	//挂载点路径名称  /bin1/vs/sd
    char devName[PATH_MAX];            /* path name of dev point */		//设备名称 /dev/mmcblk0p0
};
//分配一个挂载点
struct Mount* MountAlloc(struct Vnode* vnodeBeCovered, struct MountOps* fsop)
{
    struct Mount* mnt = (struct Mount*)zalloc(sizeof(struct Mount));//申请一个mount结构体内存,小内存分配用 zalloc
    if (mnt == NULL) {
        PRINT_ERR("MountAlloc failed no memory!\n");
        return NULL;
    }

    LOS_ListInit(&mnt->activeVnodeList);//初始化激活索引节点链表
    LOS_ListInit(&mnt->vnodeList);//初始化索引节点链表

    mnt->vnodeBeCovered = vnodeBeCovered;//设备将装载到vnodeBeCovered节点上
    vnodeBeCovered->newMount = mnt;//该节点不再是虚拟节点,而作为 设备结点
#ifdef LOSCFG_DRIVERS_RANDOM	//随机值	驱动模块
    HiRandomHwInit();//随机值初始化
    (VOID)HiRandomHwGetInteger(&mnt->hashseed);//用于生成哈希种子
    HiRandomHwDeinit();//随机值反初始化
#else
    mnt->hashseed = (uint32_t)random(); //随机生成哈子种子
#endif
    return mnt;
}

fd管理 | 两种描述符/句柄的关系

Fd(File Descriptor)是描述一个打开的文件/目录的描述符。当前OpenHarmony内核中,fd总规格为896,分为三种类型:

  • 普通文件描述符,系统总数量为512。
    #define CONFIG_NFILE_DESCRIPTORS    512	// 系统文件描述符数量
  • Socket描述符,系统总规格为128。
    #define LWIP_CONFIG_NUM_SOCKETS         128	//socket链接数量
    #define CONFIG_NSOCKET_DESCRIPTORS  LWIP_CONFIG_NUM_SOCKETS 
  • 消息队列描述符,系统总规格为256。
    #define CONFIG_NQUEUE_DESCRIPTORS    256

请记住,在OpenHarmony内核中,在不同的层面会有两种文件句柄::

  • 系统文件描述符(sysfd),由内核统一管理,和进程描述符形成映射关系,一个sysfd可以被多个profd映射,也就是说打开一个文件只会占用一个sysfd,但可以占用多个profd,即一个文件被多个进程打开.

  • 进程文件描述符(profd),由进程管理的叫进程文件描述符,内核对不同进程中的fd进行隔离,即进程只能访问本进程的fd.举例说明之间的关系:

    文件            sysfd     profd
    吃个桃桃.mp4        10    13(A进程)
    吃个桃桃.mp4        10    3(B进程)
    容嬷嬷被冤枉.txt    12    3(A进程)
    容嬷嬷被冤枉.txt    12    3(C进程)

  • 不同进程的相同fd往往指向不同的文件,但有三个fd例外

    • STDIN_FILENO(fd = 0) 标准输入 接收键盘的输入
    • STDOUT_FILENO(fd = 1) 标准输出 向屏幕输出
    • STDERR_FILENO(fd = 2) 标准错误 向屏幕输出
      sysfd和所有的profd的(0,1,2)号都是它们.熟知的 printf 就是向 STDOUT_FILENO中写入数据.
  • 具体涉及结构体

    struct file_table_s {//进程fd <--> 系统FD绑定
        intptr_t sysFd; /* system fd associate with the tg_filelist index */
    };//sysFd的默认值是-1
    struct fd_table_s {//进程fd表结构体
        unsigned int max_fds;//进程的文件描述符最多有256个
        struct file_table_s *ft_fds; /* process fd array associate with system fd *///系统分配给进程的FD数组 ,fd 默认是 -1
        fd_set *proc_fds;	//进程fd管理位,用bitmap管理FD使用情况,默认打开了 0,1,2	       (stdin,stdout,stderr)
        fd_set *cloexec_fds;
        sem_t ft_sem; /* manage access to the file table */ //管理对文件表的访问的信号量
    };
    struct files_struct {//进程文件表结构体
        int count;				      //持有的文件数量
        struct fd_table_s *fdt; //持有的文件表
        unsigned int file_lock;	//文件互斥锁
        unsigned int next_fd;	  //下一个fd
    #ifdef VFS_USING_WORKDIR
        spinlock_t workdir_lock;	//工作区目录自旋锁
        char workdir[PATH_MAX];		//工作区路径,最大 256个字符
    #endif
    };
    typedef struct ProcessCB {
    #ifdef LOSCFG_FS_VFS
        struct files_struct *files;        /**< Files held by the process */ //进程所持有的所有文件,注者称之为进程的文件管理器
    #endif	//每个进程都有属于自己的文件管理器,记录对文件的操作. 注意:一个文件可以被多个进程操作
    }

解读

  • 鸿蒙的每个进程ProcessCB都有属于自己的进程的文件描述符files_struct,该进程和文件系统有关的信息都由它表达.
  • 搞清楚 files_struct,fd_table_s,file_table_s三个结构体的关系就明白了进度描述符和系统描述符的关系.
  • fd_table_s是由alloc_fd_table分配的一个结构体数组,用于存放进程的文件描述符
        //分配进程文件表,初始化 fd_table_s 结构体中每个数据,包括系统FD(0,1,2)的绑定
        static struct fd_table_s * alloc_fd_table(unsigned int numbers)
        {
          struct fd_table_s *fdt;
          void *data;
          fdt = LOS_MemAlloc(m_aucSysMem0, sizeof(struct fd_table_s));//申请内存
          if (!fdt)
            {
              goto out;
            }
          fdt->max_fds = numbers;//最大数量
          if (!numbers)
            {
              fdt->ft_fds = NULL;
              fdt->proc_fds = NULL;
              return fdt;
            }
          data = LOS_MemAlloc(m_aucSysMem0, numbers * sizeof(struct file_table_s));//这是和系统描述符的绑定
          if (!data)
            {
              goto out_fdt;
            }
          fdt->ft_fds = data;//这其实是个 int[] 数组,
          for (int i = STDERR_FILENO + 1; i < numbers; i++)
            {
                fdt->ft_fds[i].sysFd = -1;//默认的系统描述符都为-1,即还没有和任何系统文件描述符绑定
            }
          data = LOS_MemAlloc(m_aucSysMem0, sizeof(fd_set));//管理FD的 bitmap 
          if (!data)
            {
              goto out_arr;
            }
          (VOID)memset_s(data, sizeof(fd_set), 0, sizeof(fd_set));
          fdt->proc_fds = data;
          alloc_std_fd(fdt);//分配标准的0,1,2系统文件描述符,这样做的结果是任务进程都可以写系统文件(0,1,2)
          (void)sem_init(&fdt->ft_sem, 0, 1);//互斥量初始化
          return fdt;
        out_arr:
          (VOID)LOS_MemFree(m_aucSysMem0, fdt->ft_fds);
        out_fdt:
          (VOID)LOS_MemFree(m_aucSysMem0, fdt);
        out:
          return NULL;
        }
  • file_table_s记录 sysfdprofd的绑定关系.fdt->ft_fds[i].sysFd中的i就是profd

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

也为了积极培养鸿蒙生态人才,让大家都能学习到鸿蒙开发最新的技术,针对一些在职人员、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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

nginx配置文件和配置命令详解案例

一.nginx.conf配置结构 1.1配置结构图 1.2 nginx中配置nginx.conf配置内容 #user nobody; user root; # 表示worker进程是有root用户去执行的 worker_processes 2; events {# 默认使用epolluse epoll;# 每个worker链接最大数据worker_connections 1024; } http {include …

PCIE协议-2-事务层规范-MEM/IO/CFG request rules

2.2.7 内存、I/O和配置请求规则 以下规则适用于所有内存、I/O和配置请求。每种类型的请求还有特定的额外规则。 所有内存、I/O和配置请求除了常见的头标字段外&#xff0c;还包括以下字段&#xff1a;requester ID[15:0]和Tag[9:0]&#xff0c;形成事务ID。Last DW BE[3:0] a…

uniapp百度地图聚合

// loadBMap.js ak 百度key export default function loadBMap(ak) {return new Promise((resolve, reject) > {//聚合API依赖基础库,因此先加载基础库再加载聚合APIasyncLoadBaiduJs(ak).then(() > {// 调用加载第三方组件js公共方法加载其他资源库// 加载聚合API// Ma…

定时任务执行 报错command not found 解决方案

目录 写在前面所需知识 问题复现解决方式方法1. 使用绝对路径的命令&#xff1a;方法2. 重新加载环境变量&#xff1a;成功解决截图 原理 写在前面 定时任务脚本出现command not found报错&#xff0c;解决方案。 所需知识 定时任务shell脚本环境变量 问题复现 编写了一个…

HaDoop Hive

目录 1.VMware 的配置 2.JDK的部署 3.防火墙&#xff0c;SElinux&#xff0c;时间同步设置 4.云平台 5.阿里云 6.UCloud 7.Hadoop理论 7.1 Hadoop理论 7.2 VMware Hadoop实践 7.3集群部署常见问题解决 7.4 云服务器上 Hadoop实践 7.5 HDFS 的 shell 7.6…

android进阶-Binder

参考&#xff1a;Android——Binder机制-CSDN博客 机制&#xff1a;Binder是一种进程间通信的机制 驱动&#xff1a;Binder是一个虚拟物理设备驱动 应用层&#xff1a;Binder是一个能发起进程间通信的JAVA类 Binder相对于传统的Socket方式&#xff0c;更加高效Binder数据拷贝…

​​​【收录 Hello 算法】5.1 栈

目录 5.1 栈 5.1.1 栈的常用操作 5.1.2 栈的实现 1. 基于链表的实现 2. 基于数组的实现 5.1.3 两种实现对比 5.1.4 栈的典型应用 5.1 栈 栈&#xff08;stack&#xff09;是一种遵循先入后出逻辑的线性数据结构。 我们可以将栈类比为桌面上的一摞盘子…

融资融券概念和操纵流程,案例解析

融资融券是一种金融工具&#xff0c;它允许投资者在证券市场上进行杠杆交易。简单来说&#xff0c;融资就是借钱买股票&#xff0c;融券就是借股票卖出。这种交易方式可以帮助投资者在短期内获得更高的收益&#xff0c;但同时也伴随着较高的风险。 案例背景&#xff1a; 假设…

JavaWeb--13Mybatis(2)

Mybatis&#xff08;2&#xff09; 1 Mybatis基础操作1.1 需求和准备工作1.2 删除员工日志输入参数占位符 1.3 新增员工1.4 修改员工信息1.5 查询员工1.5.1 根据ID查询数据封装 1.5.3 条件查询 2 XML配置文件规范3 MyBatis动态SQL3.1 什么是动态SQL3.2 动态SQL-if更新员工 3.3 …

C语言 | Leetcode C语言题解之第82题删除排序链表中的重复元素II

题目&#xff1a; 题解&#xff1a; struct ListNode* deleteDuplicates(struct ListNode* head) {if (!head) {return head;}struct ListNode* dummy malloc(sizeof(struct ListNode));dummy->next head;struct ListNode* cur dummy;while (cur->next && cu…

【回溯 状态压缩 深度优先】37. 解数独

本文涉及知识点 回溯 状态压缩 深度优先 LeetCode37. 解数独 编写一个程序&#xff0c;通过填充空格来解决数独问题。 数独的解法需 遵循如下规则&#xff1a; 数字 1-9 在每一行只能出现一次。 数字 1-9 在每一列只能出现一次。 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只…

3d如何同时贴两个图在模型上?---模大狮模型网

在3D设计中&#xff0c;为模型贴上纹理或图案是常见的操作&#xff0c;可以使模型更加逼真和生动。然而&#xff0c;有时候我们需要在同一个模型上同时贴上两个不同的图案&#xff0c;这可能会对初学者构成一定的挑战。在本文中&#xff0c;我们将分享一些简单而有效的方法&…

基于SSM框架多人命题系统

采用技术 基于SSM框架多人命题系统的设计与实现~ 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringMVCMyBatis 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 页面展示效果 学生端 登录 个人中心 公告信息 试题信息 管理员 登录 个人信息…

阳光厨房/明厨亮灶解决方案

现状分析 随着社会和科技的进步&#xff0c;日益增多的食品安全问题&#xff0c;国家四部委市场监督管理总局、教育部、公安部、国家卫生健康委联合印发《校园食品安全守护行动方案&#xff08;2020年—2022年&#xff09;》“互联网明厨亮灶”工程号召&#xff0c;对食品、餐饮…

机器学习-L1正则/L2正则

机器学习-L1正则/L2正则 目录 1.L1正则 2.L2正则 3.结合 1.L1正则 L1正则是一种用来约束模型参数的技术&#xff0c;常用于机器学习和统计建模中&#xff0c;特别是在处理特征选择问题时非常有用。 想象一下&#xff0c;你在装备行囊准备去旅行&#xff0c;但你的行囊有一…

详解Python测试框架Pytest的参数化

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 上篇博文介绍过&#xff0c;Pytest是目前比较成熟功能齐全的测试框架&#xff0c;使用率肯定也不…

【深度学习】Diffusion扩散模型原理解析2

由于篇幅受限&#xff0c;CSDN不能发布超过一定次数的文章&#xff0c;故在此给出上一篇链接&#xff1a;【深度学习】diffusion原理解析 3.2、目标函数求解 里面的最后一项&#xff0c; q ( x T ∣ x 0 ) q(x_T|x_0) q(xT​∣x0​)我们前面提到过&#xff0c;其近似服从标准…

flowable多对并发网关跳转的分析

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 http://218.75.87.38:9666/ 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码&#xff1a; h…

C++:虚函数表Hook

Hook 在计算机编程中&#xff0c;"Hook"&#xff08;钩子&#xff09;是一种技术&#xff0c;用于拦截并修改特定事件或函数的执行流程。它允许程序员在特定的代码点插入自定义的代码&#xff0c;以实现对程序行为的修改、监视或增强。 虚函数表Hook 虚函数表&#…

控制台打印空数组展开有数据

控制台打印空数组展开有数据 控制台显示&#xff1a; 代码如下&#xff1a; export const getDict1 (dictCode) > {let list []queryDict({ dictCode }).then(({data}) > {list.push( ...data.map(item > ({ label: item.itemText, value: item.itemValue })))})c…