f2fs文件系统 CP介绍

2023-11-02

前言

在前面的介绍中,f2fs保证一致性有两种方法:前滚恢复和后滚恢复。前滚恢复需要配合fsync流程使用,先不进行介绍。而后滚恢复就是本章节需要介绍的CheckPoint相关内容。所谓的文件系统一致性,可以简单类比交易操作,即交易双方中一方资金减少,另一方资金增加,任何时候(即使系统崩溃)都不应该出现一方资金减少,另一方资金没有增加,或者反过来,一方资金没有减少,另一方资金却增加了。这些都是违背了一致性。交易操作中常使用原子事务原理,只有最终提交交易完成操作,才算最终完成交易。如果没有提交交易完成操作,那么这笔交易可以被撤销。

CP介绍

CP是Checkpoint的缩写。上面将CP原理和交易流程做了类比,如果具体来说CP功能就是:将当前内存缓存的管理数据,写入到存储介质,一旦发生掉电或者宕机等事件,还可以恢复到上一次CP的状态。当然,还没有来得及做CP的修改,最终会丢失,所以掌握CP的时机显得尤为重要。因为CP可能需要做大量IO操作,非常耗时,但是不做CP的话,刚修改的数据可能会丢失,所以需要平衡好两者。

CP数据结构

盘上布局

struct f2fs_checkpoint {
    __le64 checkpoint_ver;      /* checkpoint block version number */
    __le64 user_block_count;    /* # of user blocks */
    __le64 valid_block_count;   /* # of valid blocks in main area */
    __le32 rsvd_segment_count;  /* # of reserved segments for gc */
    __le32 free_segment_count;  /* # of free segments in main area */
​
    /* information of current node segments */
    __le32 cur_node_segno[MAX_ACTIVE_NODE_LOGS];
    __le16 cur_node_blkoff[MAX_ACTIVE_NODE_LOGS];
    /* information of current data segments */
    __le32 cur_data_segno[MAX_ACTIVE_DATA_LOGS];
    __le16 cur_data_blkoff[MAX_ACTIVE_DATA_LOGS];
    __le32 ckpt_flags;      /* Flags : umount and journal_present */
    __le32 cp_pack_total_block_count;   /* total # of one cp pack */
    __le32 cp_pack_start_sum;   /* start block number of data summary */
    __le32 valid_node_count;    /* Total number of valid nodes */
    __le32 valid_inode_count;   /* Total number of valid inodes */
    __le32 next_free_nid;       /* Next free node number */
    __le32 checksum_offset;     /* checksum offset inside cp block */
    __le64 elapsed_time;        /* mounted time */
    /* allocation type of current segment */
    unsigned char alloc_type[MAX_ACTIVE_LOGS];
​
    /* SIT and NAT version bitmap */
    unsigned char sit_nat_version_bitmap[];
} __packed;

一个f2fs_checkpoint代表CP pack,其作用是记录本次CP的基本信息,包括版本号、sit和nat的bitmap使用情况、block的使用情况等等。

  • checkpoint_ver:本次CP的版本号。每次写CP时,都会将版本号递增,版本号越大,表示数据越新。在恢复时,优先选择版本号大的CP。
  • user_block_count:当前用户已经使用的block数量。
  • valid_block_count:Main Area区域已经使用的block数量。
  • rsvd_segment_count:为GC流程保留的segment数量。
  • free_segment_count:Main Area区域空闲的segment数量。
  • cur_node_segno:当前正在使用的node segment id号,对应curseg的数据。
  • cur_node_blkoff:当前正在使用node segment的下一个待使用的node id。
  • cur_data_segno:当前正在使用的data segment id号,对应curseg的数据。
  • cur_data_blkoff:当前正在使用data segment的下一个待使用的node id。
  • ckpt_flags:本次CP的标记,例如umount、resize、fsck等。
  • cp_pack_total_block_count:本CP pack所占用的block数,每次CP的长度不固定。
  • cp_pack_start_sum:本CP中,summary信息的开始位置。
  • valid_node_count:本次CP中,已经使用的node数量。
  • valid_inode_count:本次CP中,已经使用的inode数量。
  • next_free_nid:下一个待使用的free node,对应NAT的管理。
  • checksum_offset:本CP的checksum偏移位置。
  • elapsed_time:系统挂载的时间戳。
  • alloc_type:记录各个curseg对应的分配器类型,最多能记录16个。
  • sit_nat_version_bitmap:记录SIT和NAT的bitmap,用于判断本次使用的时主区还是备区。

以上就是一个CP pack的全部内容,这只是记录了内存中的一些基础信息,curseg中的内容则附加在CP pack后。

CP的管理

写CP全流程

如下是CP的全流程图,下面将对照着代码逐一展开。

f2fs写CP全流程

f2fs_write_checkpoint

int f2fs_write_checkpoint(struct f2fs_sb_info *sbi, struct cp_control *cpc)
{
    ...
​
    // 阻塞文件系统写操作
    err = block_operations(sbi);
​
    // 提交sbi中缓存的bio(可能经过合并了的)
    f2fs_flush_merged_writes(sbi);
​
    // 获取当前的CP版本号
    ckpt_ver = cur_cp_version(ckpt);
    // CP版本号递增
    ckpt->checkpoint_ver = cpu_to_le64(++ckpt_ver);
​
    /* write cached NAT/SIT entries to NAT/SIT area */
    // 下刷journal中的nat entry cache
    err = f2fs_flush_nat_entries(sbi, cpc);
​
    // 下刷journal中的sit entry cache
    f2fs_flush_sit_entries(sbi, cpc);
​
    /* save inmem log status */
    // 将pinned文件sum信息下刷
    f2fs_save_inmem_curseg(sbi);
​
    // 写CP
    err = do_checkpoint(sbi, cpc);
​
    // TODO:恢复pinned文件的sum信息?
    f2fs_restore_inmem_curseg(sbi);
}

这是写CP全流程的操作,对照流程图可以看出有如下步骤:

  1. 阻塞系统写操作,将脏数据落盘,包括脏inline数据、脏dentry、脏node数据等等,这些数据会影响系统的一致性。
  2. 将sbi中缓存的bio下发。
  3. 将当前CP版本号递增,作为本次CP的版本号。版本号可以检验本次CP是否可用,以及哪一份数据是最新的。
  4. 将对NAT和SIT的修改下刷。
  5. 完成前面的脏数据处理后,开始真正写CP区域,也就是说提交一次“交易”完成的记录。

block_operations

/*
 * Freeze all the FS-operations for checkpoint.
 */
static int block_operations(struct f2fs_sb_info *sbi)
{
    /*
     * Let's flush inline_data in dirty node pages.
     */
    // 将inline data回写到inode page中
    f2fs_flush_inline_data(sbi);
​
    /* write all the dirty dentry pages */
    // 下刷所有脏dentry页,fsync并没有处理dir的data数据,只下刷了文件的data
    // 数据,在此处将dir的data数据下刷
    if (get_pages(sbi, F2FS_DIRTY_DENTS)) {
        f2fs_unlock_all(sbi);
        err = f2fs_sync_dirty_inodes(sbi, DIR_INODE, true);
    }
​
    /*
     * POR: we should ensure that there are no dirty node pages
     * until finishing nat/sit flush. inode->i_blocks can be updated.
     */
    f2fs_down_write(&sbi->node_change);
​
    // 将管理元数据下刷(不属于文件、目录、链接的inode,应该就是元数据inode,例如:
    // acl、attr等等)
    if (get_pages(sbi, F2FS_DIRTY_IMETA)) {
        f2fs_up_write(&sbi->node_change);
        f2fs_unlock_all(sbi);
        err = f2fs_sync_inode_meta(sbi);
    }
​
    f2fs_down_write(&sbi->node_write);
​
    // fsync流程只下刷了普通文件的node page,配合前滚恢复可以保证一致性;
    // 在CP流程中,将其他类型的noage page下刷,配合后滚恢复保证一致性
    if (get_pages(sbi, F2FS_DIRTY_NODES)) {
        f2fs_up_write(&sbi->node_write);
        atomic_inc(&sbi->wb_sync_req[NODE]);
        err = f2fs_sync_node_pages(sbi, &wbc, false, FS_CP_NODE_IO);
        atomic_dec(&sbi->wb_sync_req[NODE]);
    }
    ...
}

在写CP的时候,会冻结所有文件系统操作,这也是为啥CP流程开销很大的原因,需要尽可能减少CP次数。在这个流程有如下步骤:

  1. 遍历所有脏direct node(主要是为了找到inode page),如果该node存在inline data,则将page cache(已经被修改的inline data)数据回写到inode page,等待后续inode page落盘。
    注:f2fs使用inline data来优化空间,一个inode page占4KB大小,但是元数据只占用一小部分空间。如果data数据小于一定范围,则存放在inline区域,省去分配一个data block的开销。同时由于inline区域的存在,所以在写数据之前,需要将inline data转为0号page cache,等待需要回写时,在将其写入inode page中,如果超过inline data范围,则不再回写。
  2. 通过遍历inode_list链表,将脏dirty dentry页下刷。
  3. 将脏元数据页下刷。
  4. 将脏node页下刷。

以上流程是将内存中缓存的脏页下刷,这是在写CP区域之前需要完成的动作。

f2fs_flush_nat_entries

int f2fs_flush_nat_entries(struct f2fs_sb_info *sbi, struct cp_control *cpc)
{
    ...
​
    /*
     * if there are no enough space in journal to store dirty nat
     * entries, remove all entries from journal and merge them
     * into nat entry set.
     */
    // 如果是umount流程,就将journal下刷(但前面已经下刷过了?)
    // 如果当前journal没有空间,也会释放journal的空间。
    if (cpc->reason & CP_UMOUNT ||
        !__has_cursum_space(journal,
            nm_i->nat_cnt[DIRTY_NAT], NAT_JOURNAL))
        remove_nats_in_journal(sbi);
​
    // 遍历nat_set_root,将set中所有nat_entry下刷,这里需要考虑net_entry的合并
    while ((found = __gang_lookup_nat_set(nm_i,
                    set_idx, SETVEC_SIZE, setvec))) {
        unsigned idx;
​
        set_idx = setvec[found - 1]->set + 1;
        for (idx = 0; idx < found; idx++)
            __adjust_nat_entry_set(setvec[idx], &sets,
                        MAX_NAT_JENTRIES(journal));
    }
​
    /* flush dirty nats in nat entry set */
    // 将nat_entry_set放到journal或者下刷到存储介质。
    list_for_each_entry_safe(set, tmp, &sets, set_list) {
        err = __flush_nat_entry_set(sbi, set, cpc);
        if (err)
            break;
    }
    ...
}

将内存中缓存的NAT表项修改下刷,而该信息是缓存在CURSEG_HOT_DATA类型的journal中。首先判断journal剩余空间是否满足缓存脏NAT表项,如果不足,则将当前journal中的表项放入到nat_entry_set中,等待下刷。如果本次是umount或者journal空间不足,则直接下刷到NAT区域中,否则存放在journal中即可。

f2fs_flush_sit_entries

void f2fs_flush_sit_entries(struct f2fs_sb_info *sbi, struct cp_control *cpc)
{
    ...
    // 如果没有dirty sentry entry,则直接跳过,不处理
    if (!sit_i->dirty_sentries)
        goto out;
​
    /*
     * add and account sit entries of dirty bitmap in sit entry
     * set temporarily
     */
    // 将dirty sentry entry放入到set中
    // 修改过的sit entry都放入到dirty_sentries_bitmap中
    add_sits_in_set(sbi);
​
    /*
     * if there are no enough space in journal to store dirty sit
     * entries, remove all entries from journal and add and account
     * them in sit entry set.
     */
    // 如果journal没有足够的空间,则将journal放到set中,并清空journal的内容
    if (!__has_cursum_space(journal, sit_i->dirty_sentries, SIT_JOURNAL) ||
                                !to_journal)
        remove_sits_in_journal(sbi);
​
    /*
     * there are two steps to flush sit entries:
     * #1, flush sit entries to journal in current cold data summary block.
     * #2, flush sit entries to sit page.
     */
    list_for_each_entry_safe(ses, tmp, head, set_list) {
        ...
        // 如果本次要写journal,但是空间不足,则直接落盘
        if (to_journal &&
            !__has_cursum_space(journal, ses->entry_cnt, SIT_JOURNAL))
            to_journal = false;
​
        /* flush dirty sit entries in region of current sit set */
        // 遍历bitmap,获取脏sit entry,并写入journal或者存储介质
        for_each_set_bit_from(segno, bitmap, end) {
            int offset, sit_offset;
​
            se = get_seg_entry(sbi, segno);
​
            /* add discard candidates */
            if (!(cpc->reason & CP_DISCARD)) {
                cpc->trim_start = segno;
                add_discard_addrs(sbi, cpc, false);
            }
​
            if (to_journal) {
                // 找到可以写本次journal的偏移,如果journal中有相应的记录,
                // 修改对应的位置
                offset = f2fs_lookup_journal_in_cursum(journal,
                            SIT_JOURNAL, segno, 1);
                segno_in_journal(journal, offset) =
                            cpu_to_le32(segno);
                seg_info_to_raw_sit(se,
                    &sit_in_journal(journal, offset));
            } else {
                // 直接写到sit表中(前面获取sit页的时候已经写过了???)
                sit_offset = SIT_ENTRY_OFFSET(sit_i, segno);
                seg_info_to_raw_sit(se,
                        &raw_sit->entries[sit_offset]);
            }
        }
​
    }
    ...
}
​

SIT缓存的下刷与NAT类似,将脏的SIT表项放入到sit_entry_set集合中,遍历该集合,如果journal中有空间,则存放在journal中,否则直接下刷到SIT区域。不同的是,SIT对应的是CURSEG_COLD_DATA类型的journal。

do_checkpoint

static int do_checkpoint(struct f2fs_sb_info *sbi, struct cp_control *cpc)
{
    ...
    /* Flush all the NAT/SIT pages */
    f2fs_sync_meta_pages(sbi, META, LONG_MAX, FS_CP_META_IO);
    ...
    start_blk = __start_cp_next_addr(sbi);
​
    /* write nat bits */
    if ((cpc->reason & CP_UMOUNT) &&
            is_set_ckpt_flags(sbi, CP_NAT_BITS_FLAG)) {
        __u64 cp_ver = cur_cp_version(ckpt);
        block_t blk;
​
        cp_ver |= ((__u64)crc32 << 32);
        *(__le64 *)nm_i->nat_bits = cpu_to_le64(cp_ver);
​
        blk = start_blk + sbi->blocks_per_seg - nm_i->nat_bits_blocks;
        for (i = 0; i < nm_i->nat_bits_blocks; i++)
            f2fs_update_meta_page(sbi, nm_i->nat_bits +
                    (i << F2FS_BLKSIZE_BITS), blk + i);
    }
​
    /* write out checkpoint buffer at block 0 */
    f2fs_update_meta_page(sbi, ckpt, start_blk++);
​
    for (i = 1; i < 1 + cp_payload_blks; i++)
        f2fs_update_meta_page(sbi, (char *)ckpt + i * F2FS_BLKSIZE,
                            start_blk++);
​
    if (orphan_num) {
        write_orphan_inodes(sbi, start_blk);
        start_blk += orphan_blocks;
    }
​
    f2fs_write_data_summaries(sbi, start_blk);
    start_blk += data_sum_blocks;
​
    ...
​
    /* flush all device cache */
    err = f2fs_flush_device_cache(sbi);
    if (err)
        return err;
​
    /* barrier and flush checkpoint cp pack 2 page if it can */
    commit_checkpoint(sbi, ckpt, start_blk);
    ...
}

该接口是才是最终写CP区域的,其可以总结为如下步骤:

  1. 等待NAT/SIT的修改落盘。
  2. 将文件系统状态信息填入CP pack #1中,包括各个curseg的segno号、next_blkoff、分配策略、SIT/NAT bitmap等等,最终计算CP pack #1的checksum。
  3. 计算CP的起始地址,由于CP区域存在主备区,采用“乒乓”策略写入。
  4. 如果支持NAT_BITS,将nat_bits写入CP区域的结尾处。
  5. 在CP区域开头处写入CP pack #1的block,然后写入填充内容(cp_payload)。
  6. 如果存在“孤儿”节点,需要在其后写入孤儿节点的内容。
  7. 写入data的summary,如果允许压缩(只写有实际内容的SSA表项),还需要将压缩后的数据写入。
  8. 写入node的summary,不支持压缩。
  9. 等待上述的数据真正落盘后,才最终写入CP pack #2。(注:如果不等待,可能出现CP pack #1和#2都已经落盘,但其他元数据还没有落盘,这是后发生宕机,那么其他数据是受损的。在写入CP pack #2前等待,可以保证所有元数据已经落盘,即使宕机,由于CP pack #2还没写入,最终该CP会被丢弃。)、

以上是一次写CP的完整流程,CP区域的具体内容参考如下:

f2fs的CP布局

如上就是一个CP的具体布局,分为主备两个segment。每次轮流使用主备区域,确保总有一个区域的内容是可用的。

总结

本章节介绍了f2fs一致性中的后滚恢复部分,虽然只是介绍了写CP的流程,但是读操作只是写操作的反向流程,就不过多赘述。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

f2fs文件系统 CP介绍 的相关文章

  • 无法禁用 Firestore 中的离线数据

    从我的数据中删除数据后Firestore Database 这需要我的Android app一段时间后才意识到数据已被删除 我认为这是由于自动数据缓存而发生的 我的应用程序与离线使用无关 我想禁用此功能 我已将其添加到我的自定义中Appli
  • Javadoc 链接到其他类中的方法

    目前我正在使用以下 Javadoc 语法引用其他类中的方法 see link com my package Class method 据我从文档中了解到 这是执行此操作的正确方法 但现在到了有趣的部分 或者说令人沮丧的部分 当我生成这个 j
  • SharePoint 2010 Web 服务上的 Java JBoss 401 错误

    我的代码在 Eclipse IDE 中测试时运行成功 我正在使用生成的 Copy wsdl 通过 Web 服务连接到 MS SharePoint 2010 当我在 JBoss 服务器上部署代码 运行 Adob e LifeCycle 时 我
  • android新手需要了解“?android:attr/actionBarSize”

    我正在经历拉尔斯 沃格尔的教程 http www vogella com articles AndroidFragments article html在使用 Fragments 时 我遇到了以下代码 android layout margi
  • Android 服务 START_STICKY START_NOT_STICKY

    我需要让我的服务始终在后台运行 并使用 startService 函数启动我的服务 无论应用程序的状态如何 我都不想重新启动服务 这是我的观察 START STICKY gt 如果应用程序启动 则服务正在重新启动 当应用程序关闭时 服务也会
  • rmi类找不到异常

    我使用 java rmi 编写了一个简单的项目并导出到可执行 jar 文件 当我尝试运行它时 有时会出现异常 有时会起作用 当我指定 Djava rmi server codebase file serverClasses 时 它似乎没有正
  • 当Java中set已经是原子的时候,为什么我们还需要compareAndSet呢?

    因为原子意味着线程安全 当 set 本身在java中是原子和线程安全的时候 我们什么时候使用compareAndSet 举例来说 我想以原子方式设置一个变量 以便每个其他线程都可以看到它 但我希望以线程安全的方式设置该变量 我可以简单地将其
  • Java TCP Echo 服务器 - 广播

    我有一个简单的回显服务器 我希望当连接的用户向服务器键入任何内容时 所有其他客户端和该客户端都会收到消息 MOD 它现在不会发送给所有客户端 但它应该发送 而且我只是不知道我的代码出了什么问题 所以现在它只会将消息 MOD 发送给发送消息的
  • 控制启动时的竞争条件

    我有一些代码想要执行一些一次性初始化 但这段代码没有明确的生命周期 因此在初始化完成之前 我的逻辑可能会被多个线程调用 所以 我想基本上确保我的逻辑代码 等待 直到初始化完成 这是我的第一次剪辑 public class MyClass p
  • 无法从 PDFA1-a 格式文档中提取图像

    我正在使用以下代码从 PDFA1 a 格式的 pdf 中提取图像 但我无法获取图像 List
  • 如何修复运行 Android 模拟器时出现 GPU Driver Issue 错误

    我的 Android 模拟器几周前运行良好 但现在出现错误 当我运行代码时 GPU 驱动程序问题错误对话框与模拟器一起弹出 当我单击 确定 时 Android 模拟器不会按预期运行应用程序 错误如下 Your GPU driver info
  • xclock 工作,X11 DISPLAY 设置但仍然 java.awt.HeadlessException:

    获取 java awt HeadlessException 似乎是一个非常常见的问题 并且 中已经讨论过 以下问题 没有 X11 DISPLAY 变量 这是什么意思 https stackoverflow com questions 662
  • CompletableFuture SupplyAsync

    我刚刚开始探索 Java 8 的一些并发特性 让我有点困惑的一件事是这两个静态方法 CompletableFuture
  • 错误:类 kotlin.reflect.jvm.internal.FunctionCaller$FieldSetter

    我已尝试一切方法来消除此错误 但它不断出现 Class kotlin reflect jvm internal FunctionCaller FieldSetter can not access a member of class com
  • 为什么我在 Java 中收到有关“可能丢失精度”的警告?

    class Test public static void main String args short s 2 s1 200 s2 s2 s s1 error possible loss of precision System out p
  • 何时对字符串文字使用 intern()

    我看到很多这样的遗留代码 class A public static final String CONSTANT value intern 我看不出使用 intern 的任何原因 因为在 Javadoc 中可以读到 所有文字字符串和字符串值
  • 不支持使用 JDK 版本“11.0.1”进行构建。请安装 JDK 版本 `1.8.0`

    我已经下载了 Visual Studio for Mac 并尝试开始学习 Xamarin iOS 和 Android 问题是当我尝试运行 android 项目时出现以下错误 Building with JDK Version 11 0 1
  • Java:易失性足以使类线程安全?

    我有一个关于 Java 中 volatile 语句的问题 请看这个构造的例子 class Master Foo is a class with thread safe methods public volatile Foo foo clas
  • 项目级别的@PowerMockIgnore

    在 Maven 中运行时 我的 powermock 测试用例出现以下错误 java lang LinkageError loader constraint violation loader instance of org powermock
  • 如何读取FTL文件中的JSONArray?

    我在我的 Java 文件中硬编码了以下 JSON 对象 JSONObject notificationInfoJson new JSONObject notificationInfoJson put title Payment Receiv

随机推荐

  • 下一步可以学下ue4 shader

    上周六 Osg直播间 恒歌提到了他们的做法 osgearth ue4 shader 这是非常好的思路 因为ue4缺点是底层资料少 优点是渲染效果好 这算是扬长避短的做法了 我正好会ue4 也会shader 正好也在学osgearth 虽然都
  • 微博数据爬虫——获取特定ID的粉丝和关注(二)

    注意 近期发现使用requests库访问微博数据出现ssl error错误 而使用urllib库访问则不会出现错误 功能 给定特定微博用户ID 获取微博用户的粉丝和关注 1 通过o id获取p id 用户主页结构如下所示 通过使用正则匹配即
  • Java远程调试

    1 把导出的jar包放到服务器上 执行的时候增加执行参数 jdk1 7版本之前的命令 java agentlib jdwp transport dt socket address 8000 server y suspend y jar xx
  • linux中病毒排查步骤,linux系统下病毒排除思路

    1 top查看是否有特别吃cpu和内存的进程 病毒进程kill是杀不死的 因为ps命令被修改 2 ls la proc 病毒进程pid pwd为病毒进程程序目录 一般在 usr bin下 3 bin ps bin netsta程序都是1 2
  • unity3d Object.Destroy 销毁

    static function Destroy obj Object t float 0 0F void Description描述 删除一个游戏物体 组件或者资源 物体obj现在被销毁或在指定了t时间过后销毁 如果obj是组件 它将从Ga
  • 深圳白领集体居家办公 远程办公或成企业新选择

    近日 深圳地区疫情爆发 多个办公大楼被划为封控区 众多白领临时接到居家办公的通知 一批又一批的员工从CBD大楼走出来 大量白领带上电脑显示屏与主机 被网友戏称 跑毒 带主机回家办公 成为突发疫情下众多人的无奈选择 远程办公成常态化 远程产品
  • STM32HAL库-移植mbedtls开源库示例(二)

    概述 本篇文章介绍如何使用STM32HAL库 这篇文章只要是讲如何使用mbedtls开源库 实现 1 base64编码 2 AES加解密示例 怎么样移植mbedtls开源库 请阅读我写的一篇文章 STM32HAL库 移植mbedtls开源库
  • ucharts饼状图文字过长超出屏幕不显示问题

    项目场景 在项目中通过饼状图对获取到的数据进行一个显示 问题描述 通过ucharts中的饼状图来进行数据的显示 通过labelText属性自定义饼状图标签文字 但发现有些数据太长 超出屏幕无法显示 如图 解决方案 1 可以进行饼状图半径的缩
  • Scanvenger游戏制作笔记(九)Unity3D创建声音

    Scanvenger游戏制作笔记 九 Unity3D创建声音 前言 一 在GameManager 上创建audio source 播放背景音乐 二 创建其他声音 三 将audioSource拖入efx source中 四 停止背景音乐 系列
  • springboot中Instant时间传参及序列化

    在部分场景中 后台的时间属性用的不是Date或Long 而是Instant Java8引入的一个精度极高的时间类型 可以精确到纳秒 但实际使用的时候不需要这么高的精确度 通常到毫秒就可以了 而在前后端传参的时候需要对Instant类型进行序
  • Vue教程(二):数据代理和事件处理

    1 数据代理 数据代理 通过obj2操作obj1的对象
  • 写给萌新的mmdet

    写给萌新的mmdet 本文简称mmdetection为mmdet 安装是劝退新入坑的小伙伴的大老虎 一般按照官方readme安装一遍 然后发现运行官方demo报错 网上搜了半天 试了一些奇怪的命令发现没用 只能删除环境 重新再装一遍 然后报
  • QT笔记:QT 窗口关闭 程序进程不退出

    最近在做一个工具用于控制mcu 这其中肯定就需要open端口 所以不能同时打开一个设备 所以在发布程序后 打开设备后关闭QT的UI界面 再次打开程序进行连接设备的时候出现open设备失败的问题 第一次打开设备 关闭应用再次连接设备 后面查看
  • SpringBoot对接小程序微信支付

    目录 前言 一 准备工作 2 1 企业微信小程序开通 2 1 1 获取开发者ID 2 1 2 开通支付功能 2 1 3 关联商户号 2 2 企业商户号的开通 2 2 1 获取商户号mch id 2 2 2 获取商户API密钥mch key
  • I帧、P帧和B帧的特点

    I帧 帧内编码帧 I帧特点 1 它是一个全帧压缩编码帧 它将全帧图像信息进行JPEG压缩编码及传输 2 解码时仅用I帧的数据就可重构完整图像 3 I帧描述了图像背景和运动主体的详情 4 I帧不需要参考其他画面而生成 5 I帧是P帧和B帧的参
  • 李沐论文精读系列三:MoCo、对比学习综述(MoCov1/v2/v3、SimCLR v1/v2、DINO等)

    文章目录 一 MoCo 1 1 导言 1 1 1 前言 1 1 2 摘要 1 1 3 导言 1 2 相关工作 1 2 1 SimCLR 端到端的学习方式 Inva Spread也是 1 2 2 memory bank InstDisc模型
  • MySql下最好用的数据库管理工具是哪个?

    维基上有个很全的列表 https en wikipedia org wiki Comparison of database tools 1 phpmyadmin 用过3 9版 UTF 8 中文不显示 2 HeidiSQL 7 0 不错 以前
  • 关于CoInitialize()

    在msdn中对于CoInitialize的解释如下 Initializes the COM library on the current apartment and identifies the concurrency model as s
  • ADC基本原理

    ADC基本原理 1 ADC即为典型的模拟数字转换器将模拟信号转换为表示一定比例电压值的数字信号 2 对于32 1 ADC转换时间 最大转换速率 1us 最大转换速度为1MHz 在ADCCLK 14M 采样周期为1 5个ADC时钟下得到 2
  • f2fs文件系统 CP介绍

    前言 在前面的介绍中 f2fs保证一致性有两种方法 前滚恢复和后滚恢复 前滚恢复需要配合fsync流程使用 先不进行介绍 而后滚恢复就是本章节需要介绍的CheckPoint相关内容 所谓的文件系统一致性 可以简单类比交易操作 即交易双方中一