这篇文章完成f2fs的segment管理结构f2fs_sm_info的创建和恢复。

build_segment_manager:首先分配容纳f2fs_sm_info的空间,然后用f2fs_super_block中的数据对f2fs_sm_info的一些关于segment数量的信息进行初始化。接着初始化其中的三个链表discard_list、wait_list、sit_entry_set。然后调用build_sit_info构建sit_info,主要是sit_info以及管理的结构的空间的分配。接着调用build_free_segmap构建free_segmap_info,这里主要完成空间的分配,并将所有的segment和section设置为脏,其修改过程在后面再完成。然后调用build_curseg来构建各种curseg_info,并完成curren segment的恢复。接着调用build_sit_entries来恢复所有的seg_entry和sec_entry,然后调用init_free_segmap来恢复free_segmap和free_secmap,接着调用build_dirty_segmap来构建dirty_seglist_info。最后更新sit_info中的min和max的mtime。

int build_segment_manager(struct f2fs_sb_info *sbi)
{struct f2fs_super_block *raw_super = F2FS_RAW_SUPER(sbi);struct f2fs_checkpoint *ckpt = F2FS_CKPT(sbi);struct f2fs_sm_info *sm_info;int err;sm_info = kzalloc(sizeof(struct f2fs_sm_info), GFP_KERNEL);if (!sm_info)return -ENOMEM;sbi->sm_info = sm_info;sm_info->seg0_blkaddr = le32_to_cpu(raw_super->segment0_blkaddr);sm_info->main_blkaddr = le32_to_cpu(raw_super->main_blkaddr);sm_info->segment_count = le32_to_cpu(raw_super->segment_count);sm_info->reserved_segments = le32_to_cpu(ckpt->rsvd_segment_count);sm_info->ovp_segments = le32_to_cpu(ckpt->overprov_segment_count);sm_info->main_segments = le32_to_cpu(raw_super->segment_count_main);sm_info->ssa_blkaddr = le32_to_cpu(raw_super->ssa_blkaddr);sm_info->rec_prefree_segments = sm_info->main_segments * DEF_RECLAIM_PREFREE_SEGMENTS / 100;if (sm_info->rec_prefree_segments > DEF_MAX_RECLAIM_PREFREE_SEGMENTS)sm_info->rec_prefree_segments = DEF_MAX_RECLAIM_PREFREE_SEGMENTS;if (!test_opt(sbi, LFS))sm_info->ipu_policy = 1 << F2FS_IPU_FSYNC;sm_info->min_ipu_util = DEF_MIN_IPU_UTIL;sm_info->min_fsync_blocks = DEF_MIN_FSYNC_BLOCKS;INIT_LIST_HEAD(&sm_info->discard_list);INIT_LIST_HEAD(&sm_info->wait_list);sm_info->nr_discards = 0;sm_info->max_discards = 0;sm_info->trim_sections = DEF_BATCHED_TRIM_SECTIONS;INIT_LIST_HEAD(&sm_info->sit_entry_set);if (test_opt(sbi, FLUSH_MERGE) && !f2fs_readonly(sbi->sb)) {err = create_flush_cmd_control(sbi);if (err)return err;}err = build_sit_info(sbi);if (err)return err;err = build_free_segmap(sbi);if (err)return err;err = build_curseg(sbi);if (err)return err;build_sit_entries(sbi);init_free_segmap(sbi);err = build_dirty_segmap(sbi);if (err)return err;init_min_max_mtime(sbi);return 0;
}

build_sit_info:主要完成f2fs_sm_info中的sit_info的空间的分配和其中的几个字段和位图的空间的分配,这些包括所有f2fs_sit_entry对应的内存结构seg_entry的空间、记录当前的sit位图的cur_valid_map和上次cp的sit位图的ckpt_valid_map、记录discard块的位图discard_map、临时位图tmp_map、所有section的相关信息的sec_entries。还有sit_info的一些字段的赋值。

static int build_sit_info(struct f2fs_sb_info *sbi)

{struct f2fs_super_block *raw_super = F2FS_RAW_SUPER(sbi);struct f2fs_checkpoint *ckpt = F2FS_CKPT(sbi);struct sit_info *sit_i;unsigned int sit_segs, start;char *src_bitmap, *dst_bitmap;unsigned int bitmap_size;sit_i = kzalloc(sizeof(struct sit_info), GFP_KERNEL);if (!sit_i)return -ENOMEM;SM_I(sbi)->sit_info = sit_i;sit_i->sentries = f2fs_kvzalloc(MAIN_SEGS(sbi) * sizeof(struct seg_entry), GFP_KERNEL);if (!sit_i->sentries)return -ENOMEM;bitmap_size = f2fs_bitmap_size(MAIN_SEGS(sbi));sit_i->dirty_sentries_bitmap = f2fs_kvzalloc(bitmap_size, GFP_KERNEL);if (!sit_i->dirty_sentries_bitmap)return -ENOMEM;for (start = 0; start < MAIN_SEGS(sbi); start++) {sit_i->sentries[start].cur_valid_map = kzalloc(SIT_VBLOCK_MAP_SIZE, GFP_KERNEL);sit_i->sentries[start].ckpt_valid_map = kzalloc(SIT_VBLOCK_MAP_SIZE, GFP_KERNEL);if (!sit_i->sentries[start].cur_valid_map || !sit_i->sentries[start].ckpt_valid_map)return -ENOMEM;if (f2fs_discard_en(sbi)) {sit_i->sentries[start].discard_map = kzalloc(SIT_VBLOCK_MAP_SIZE, GFP_KERNEL);if (!sit_i->sentries[start].discard_map)return -ENOMEM;}}sit_i->tmp_map = kzalloc(SIT_VBLOCK_MAP_SIZE, GFP_KERNEL);if (!sit_i->tmp_map)return -ENOMEM;if (sbi->segs_per_sec > 1) {sit_i->sec_entries = f2fs_kvzalloc(MAIN_SECS(sbi) * sizeof(struct sec_entry), GFP_KERNEL);if (!sit_i->sec_entries)return -ENOMEM;}sit_segs = le32_to_cpu(raw_super->segment_count_sit) >> 1;bitmap_size = __bitmap_size(sbi, SIT_BITMAP);src_bitmap = __bitmap_ptr(sbi, SIT_BITMAP);dst_bitmap = kmemdup(src_bitmap, bitmap_size, GFP_KERNEL);if (!dst_bitmap)return -ENOMEM;sit_i->s_ops = &default_salloc_ops;sit_i->sit_base_addr = le32_to_cpu(raw_super->sit_blkaddr);sit_i->sit_blocks = sit_segs << sbi->log_blocks_per_seg;sit_i->written_valid_blocks = le64_to_cpu(ckpt->valid_block_count);sit_i->sit_bitmap = dst_bitmap;sit_i->bitmap_size = bitmap_size;sit_i->dirty_sentries = 0;sit_i->sents_per_block = SIT_ENTRY_PER_BLOCK;sit_i->elapsed_time = le64_to_cpu(sbi->ckpt->elapsed_time);sit_i->mounted_time = CURRENT_TIME_SEC.tv_sec;mutex_init(&sit_i->sentry_lock);return 0;
}

build_free_segmap:首先分配一个free_segmap_info的空间,然后分配记录所有的segment的free_segmap位图,再分配记录所有的section的free_secmap的位图。接着将这两个位图初始化为全是1,表示全部都不是空闲的,然后初始化free_segmap_info的记录segment起始地segno的start_segno,将空闲的segment和section的个数赋值为0。

static int build_free_segmap(struct f2fs_sb_info *sbi)
{struct free_segmap_info *free_i;unsigned int bitmap_size, sec_bitmap_size;free_i = kzalloc(sizeof(struct free_segmap_info), GFP_KERNEL);if (!free_i)return -ENOMEM;SM_I(sbi)->free_info = free_i;bitmap_size = f2fs_bitmap_size(MAIN_SEGS(sbi));free_i->free_segmap = f2fs_kvmalloc(bitmap_size, GFP_KERNEL);if (!free_i->free_segmap)return -ENOMEM;sec_bitmap_size = f2fs_bitmap_size(MAIN_SECS(sbi));free_i->free_secmap = f2fs_kvmalloc(sec_bitmap_size, GFP_KERNEL);if (!free_i->free_secmap)return -ENOMEM;memset(free_i->free_segmap, 0xff, bitmap_size);memset(free_i->free_secmap, 0xff, sec_bitmap_size);free_i->start_segno = GET_SEGNO_FROM_SEG0(sbi, MAIN_BLKADDR(sbi));free_i->free_segments = 0;free_i->free_sections = 0;spin_lock_init(&free_i->segmap_lock);return 0;
}

build_curseg首先分配NR_CURSEG_TYPE个curseg_info的空间,然后对每个curseg_info进行空间的分配,首先是分配一个f2fs_summary,然后初始化管理journal的读写锁,接着分配f2fs_journal,然后将segno和next_blkoff分别初始化为NULL_SEGNO和0。然后调用函数restore_curseg_summaries对curseg_info进行恢复。

static int build_curseg(struct f2fs_sb_info *sbi)
{struct curseg_info *array;int i;array = kcalloc(NR_CURSEG_TYPE, sizeof(*array), GFP_KERNEL);if (!array)return -ENOMEM;SM_I(sbi)->curseg_array = array;for (i = 0; i < NR_CURSEG_TYPE; i++) {mutex_init(&array[i].curseg_mutex);array[i].sum_blk = kzalloc(PAGE_SIZE, GFP_KERNEL);if (!array[i].sum_blk)return -ENOMEM;init_rwsem(&array[i].journal_rwsem);array[i].journal = kzalloc(sizeof(struct f2fs_journal), GFP_KERNEL);if (!array[i].journal)return -ENOMEM;array[i].segno = NULL_SEGNO;array[i].next_blkoff = 0;}return restore_curseg_summaries(sbi);
}

restore_curseg_summaries:根据do_checkpoint时的curseg_info的两种写入方式,这里首先要判断那种方式进行恢复。首先检查是否设置了CP_COMPACT_SUM_FLAG,如果设置了那么就采用复杂的方式read_compacted对data的summaries进行读取恢复。然后检查之前是否将node的summaries也写入设备了,然后将node采用普通的方式read_normal_summaries对sumamies进行读取恢复。这里如果之前没有进行复杂的读取恢复,那么这里会将data和node一起以普通的方式read_normal_summaries读取恢复。

static int restore_curseg_summaries(struct f2fs_sb_info *sbi)
{int type = CURSEG_HOT_DATA;int err;if (is_set_ckpt_flags(sbi, CP_COMPACT_SUM_FLAG)) {int npages = npages_for_summary_flush(sbi, true);if (npages >= 2)ra_meta_pages(sbi, start_sum_block(sbi), npages, META_CP, true);if (read_compacted_summaries(sbi))return -EINVAL;type = CURSEG_HOT_NODE;}if (__exist_node_summaries(sbi))ra_meta_pages(sbi, sum_blk_addr(sbi, NR_CURSEG_TYPE, type),NR_CURSEG_TYPE - type, META_CP, true);for (; type <= CURSEG_COLD_NODE; type++) {err = read_normal_summaries(sbi, type);if (err)return err;}return 0;
}

build_sit_entries:首先将所有的f2fs_sit_entry读取出来恢复seg_entry。首先对f2fs_sit_block进行预读,然后遍历所有的segment,先获取当前segment的seg_entry,然后获取当前段的f2fs_sit_entry,接着调用check_block_count检查f2fs_sit_entry中的有效块数不能大于512,还有就是segno不能大于总的段数。然后调用seg_info_from_raw_sit将f2fs_sit_entry的信息同步到seg_entry中,接着更新seg_entry的discard_map,这个map跟seg_entry的cur_valid_map一致,其discard_blks跟seg_entry中的free的block的块数。另外由于sit的最新数据可能是放置在curseg_info的sit_journal中的,所以还需要读取这些f2fs_sit_entry来获取最新的f2fs_sit_entry,通过遍历其中的f2fs_journal数组,其恢复方式跟上面的是一致的。

static void build_sit_entries(struct f2fs_sb_info *sbi)
{struct sit_info *sit_i = SIT_I(sbi);struct curseg_info *curseg = CURSEG_I(sbi, CURSEG_COLD_DATA);struct f2fs_journal *journal = curseg->journal;struct seg_entry *se;struct f2fs_sit_entry sit;int sit_blk_cnt = SIT_BLK_CNT(sbi);unsigned int i, start, end;unsigned int readed, start_blk = 0;int nrpages = MAX_BIO_BLOCKS(sbi) * 8;do {readed = ra_meta_pages(sbi, start_blk, nrpages, META_SIT, true);start = start_blk * sit_i->sents_per_block;end = (start_blk + readed) * sit_i->sents_per_block;for (; start < end && start < MAIN_SEGS(sbi); start++) {struct f2fs_sit_block *sit_blk;struct page *page;se = &sit_i->sentries[start];page = get_current_sit_page(sbi, start);sit_blk = (struct f2fs_sit_block *)page_address(page);sit = sit_blk->entries[SIT_ENTRY_OFFSET(sit_i, start)];f2fs_put_page(page, 1);check_block_count(sbi, start, &sit);seg_info_from_raw_sit(se, &sit);if (f2fs_discard_en(sbi)) {memcpy(se->discard_map, se->cur_valid_map, SIT_VBLOCK_MAP_SIZE);sbi->discard_blks += sbi->blocks_per_seg - se->valid_blocks;}if (sbi->segs_per_sec > 1)get_sec_entry(sbi, start)->valid_blocks += se->valid_blocks;}start_blk += readed;} while (start_blk < sit_blk_cnt);down_read(&curseg->journal_rwsem);for (i = 0; i < sits_in_cursum(journal); i++) {unsigned int old_valid_blocks;start = le32_to_cpu(segno_in_journal(journal, i));se = &sit_i->sentries[start];sit = sit_in_journal(journal, i);old_valid_blocks = se->valid_blocks;check_block_count(sbi, start, &sit);seg_info_from_raw_sit(se, &sit);if (f2fs_discard_en(sbi)) {memcpy(se->discard_map, se->cur_valid_map, SIT_VBLOCK_MAP_SIZE);sbi->discard_blks += old_valid_blocks - se->valid_blocks;}if (sbi->segs_per_sec > 1)get_sec_entry(sbi, start)->valid_blocks += se->valid_blocks - old_valid_blocks;}up_read(&curseg->journal_rwsem);
}

init_free_segmap:对所有的main area的segment进行遍历,检查其seg_entry中的有效块数valid_blocks是否为零,如果满足,则调用__set_free将相应的segment设置为free,同时如果该段所在的section都没有有效块数的话,也将section设置为free。再对所有的curseg_info进行遍历,将所有的current segment对应的segno和躲在的section都从free的map中清除。

static void init_free_segmap(struct f2fs_sb_info *sbi)
{unsigned int start;int type;for (start = 0; start < MAIN_SEGS(sbi); start++) {struct seg_entry *sentry = get_seg_entry(sbi, start);if (!sentry->valid_blocks)__set_free(sbi, start);}for (type = CURSEG_HOT_DATA; type <= CURSEG_COLD_NODE; type++) {struct curseg_info *curseg_t = CURSEG_I(sbi, type);__set_test_and_inuse(sbi, curseg_t->segno);}
}

__set_free:将segno所对应的free_segmap_info中的管理segment的free_segmap的位消掉,表示这个segno是空闲的。同时对该segno所在的section在free_segmap_info中 的所有位进行检查,如果都是空闲的,那么就将free_segmap_info中的管理section的free_secmap的位消掉,表示这个section是空闲的。在这个过程中free_segmap_info中的相关的数量统计free_segments和free_sections也随之更新。

static inline void __set_free(struct f2fs_sb_info *sbi, unsigned int segno)
{struct free_segmap_info *free_i = FREE_I(sbi);unsigned int secno = segno / sbi->segs_per_sec;unsigned int start_segno = secno * sbi->segs_per_sec;unsigned int next;spin_lock(&free_i->segmap_lock);clear_bit(segno, free_i->free_segmap);free_i->free_segments++;next = find_next_bit(free_i->free_segmap, start_segno + sbi->segs_per_sec, start_segno);if (next >= start_segno + sbi->segs_per_sec) {clear_bit(secno, free_i->free_secmap);free_i->free_sections++;}spin_unlock(&free_i->segmap_lock);
}

__set_test_and_inuse:检查segno对应的free_segmap_info中的管理segment的free_segmap的位是否是空闲的,如果是,那就置位标志不再空闲,然后检查segno所在的section对应的free_segmap_info中的管理section的free_secmap的位是否是空闲的,如果是,那就置位标志不再空闲。在这个过程中free_segmap_info中的相关的数量统计free_segments和free_sections也随之更新。

static inline void __set_test_and_inuse(struct f2fs_sb_info *sbi, unsigned int segno)
{struct free_segmap_info *free_i = FREE_I(sbi);unsigned int secno = segno / sbi->segs_per_sec;spin_lock(&free_i->segmap_lock);if (!test_and_set_bit(segno, free_i->free_segmap)) {free_i->free_segments--;if (!test_and_set_bit(secno, free_i->free_secmap))free_i->free_sections--;}spin_unlock(&free_i->segmap_lock);
}

build_dirty_segno:首先分配dirty_seglist_info的空间,然后分配NR_DIRTY_TYPE个相关的管理不同类型的dirty的segment的位图。然后调用init_dirty_segmap利用free_segmap对dirty_segmap进行更新,最后调用init_victim_sectim分配dirty_seglist_info中的victim_secmap,初始化为全部都是零。

static int build_dirty_segmap(struct f2fs_sb_info *sbi)
{struct dirty_seglist_info *dirty_i;unsigned int bitmap_size, i;dirty_i = kzalloc(sizeof(struct dirty_seglist_info), GFP_KERNEL);if (!dirty_i)return -ENOMEM;SM_I(sbi)->dirty_info = dirty_i;mutex_init(&dirty_i->seglist_lock);bitmap_size = f2fs_bitmap_size(MAIN_SEGS(sbi));for (i = 0; i < NR_DIRTY_TYPE; i++) {dirty_i->dirty_segmap[i] = f2fs_kvzalloc(bitmap_size, GFP_KERNEL);if (!dirty_i->dirty_segmap[i])return -ENOMEM;}init_dirty_segmap(sbi);return init_victim_secmap(sbi);
}

init_dirty_segmap:首先在free_segmap_info的free_segmap中查找到不是空闲的segment,如果对应的有效块数是512或者0,那就不是dirty的,其他情况下调用函数__locate_dirty_segment将该segno在dirty_seglist_info的位图中置位。

static void init_dirty_segmap(struct f2fs_sb_info *sbi)
{struct dirty_seglist_info *dirty_i = DIRTY_I(sbi);struct free_segmap_info *free_i = FREE_I(sbi);unsigned int segno = 0, offset = 0;unsigned short valid_blocks;while (1) {segno = find_next_inuse(free_i, MAIN_SEGS(sbi), offset);if (segno >= MAIN_SEGS(sbi))break;offset = segno + 1;valid_blocks = get_valid_blocks(sbi, segno, 0);if (valid_blocks == sbi->blocks_per_seg || !valid_blocks)continue;if (valid_blocks > sbi->blocks_per_seg) {f2fs_bug_on(sbi, 1);continue;}mutex_lock(&dirty_i->seglist_lock);__locate_dirty_segment(sbi, segno, DIRTY);mutex_unlock(&dirty_i->seglist_lock);}
}

__locate_dirty_segment:首先检查segno是不是current segment,如果是就不进行操作了。然后在检查dirty_segmap [DIRTY]的中是否有置位,没有就置位并更新数量。然后在获取segno对应的seg_entry,获取其type,然后检查相应的dirty_segmap [type]中是否有置位,没有就置位并更新数量。

static void __locate_dirty_segment(struct f2fs_sb_info *sbi, unsigned int segno,enum dirty_type dirty_type)
{struct dirty_seglist_info *dirty_i = DIRTY_I(sbi);if (IS_CURSEG(sbi, segno))return;if (!test_and_set_bit(segno, dirty_i->dirty_segmap[dirty_type]))dirty_i->nr_dirty[dirty_type]++;if (dirty_type == DIRTY) {struct seg_entry *sentry = get_seg_entry(sbi, segno);enum dirty_type t = sentry->type;if (unlikely(t >= DIRTY)) {f2fs_bug_on(sbi, 1);return;}if (!test_and_set_bit(segno, dirty_i->dirty_segmap[t]))dirty_i->nr_dirty[t]++;}
}

init_victim_secmap:分配平衡gc时的victim_select的section的位图的空间。

static int init_victim_secmap(struct f2fs_sb_info *sbi)
{struct dirty_seglist_info *dirty_i = DIRTY_I(sbi);unsigned int bitmap_size = f2fs_bitmap_size(MAIN_SECS(sbi));dirty_i->victim_secmap = f2fs_kvzalloc(bitmap_size, GFP_KERNEL);if (!dirty_i->victim_secmap)return -ENOMEM;return 0;
}

f2fs系列文章fill_super(三)相关推荐

  1. 微信JS图片上传与下载功能--微信JS系列文章(三)

    概述 在前面的文章微信JS初始化-- 微信JS系列文章(一)中已经介绍了微信JS初始化的相关工作,接下来本文继续就微信JS的图片上传功能进行描述,供大家参考. 图片上传 $(function(){va ...

  2. html5 Game开发系列文章之 三 搭建基本游戏框架(代码封装)

    在之前的二节中,我做出一个基本的游戏精灵--一条红色的飞行的小飞龙,但是在进行下一步开发前,我觉得有必要对现有的代码进行封装!在这一节中,我将封装一些基本的方法,并演示如何在JS中实现继承! 首先了解 ...

  3. f2fs系列文章truncate

    这篇文章讲f2fs文件系统的截断,在调用这个函数之前会设置inode的i_size,这个函数完成在文件中i_size之后的数据的删除.其起始的函数是f2fs_truncate. f2fs_trunca ...

  4. 关于传统蒙古文系统系列文章(三)

    蒙古文读音输入方法 在链接中 请看链接 链接了 正式出版的<蒙文读音输入方法>的.BDF文件,作者:齐德华,<蒙文读音输入方法>是在电脑上完成编程实现的基础上,于1991年5月 ...

  5. H.264系列文章(三)——帧内预测

    H.264 White Paper学习笔记(二)帧内预测 为什么要有帧内预测?因为一般来说,对于一幅图像,相邻的两个像素的亮度和色度值之间经常是比较接近的,也就是颜色是逐渐变化的,不会一下子突变成完全 ...

  6. f2fs系列文章fsck(五)

    fsck_verify通过前面的检查结果来修正元数据. 首先是对nid的检查情况进行查看,f2fs_fsck中的nat_area_bitmap从开始的读取f2fs_nat_block中的所有的f2fs ...

  7. Docker+NETCore系列文章(三、Docker常用命令)

    文章目录 Docker常用命令 1.帮助命令 2.镜像命令 3.容器命令 3.1新建容器并启动 3.2查看容器列表 3.3退出容器 3.4删除容器 3.5启动和停止容器 4 其他常用命令 4.1后台启 ...

  8. Python字符串函数大全系列文章(三)

    1.join(iterable,/): 将一个迭代对象连接起来 >>> '-'.join(['hello','python','go']) 'hello-python-go' 2.l ...

  9. c语言 结构体_C语言 技能提升 系列文章 (三)结构体

    今天,来跟大家聊一聊C语言中的结构体. 在C语言的各种数据类型中,结构体最特别,因为它是可以被程序员定义的,它的特点是非常的灵活. 定义 struct defined_name{ type_name ...

最新文章

  1. Codeforces 769D k-Интересные пары чисел
  2. linux中人脸识别不了,虹软人脸识别在 linux中so文件加载不到的问题
  3. sdr 软件_【火腿专题】购买软件定义无线电(SDR)还是传统无线电台?追求欲望无止境...
  4. 【leetcode】41. First Missing Positive
  5. centos6 进入命令行_CentOS6.8设置开机直接进入命令行模式
  6. vue 中二维码的使用和工具比较
  7. 程序的图标无法改变_想体验程序猿日常工作的快乐吗?来玩国产烧脑益智游戏《异常》...
  8. 在WCF数据访问中使用缓存提高Winform字段中文显示速度
  9. 【笔记】VUE学习笔记
  10. Android 小知识:使用shell screencap / screenshot命令截屏
  11. mysql数据库压缩_Mysql压缩解决方案
  12. Kaggle数据集之电信客户流失数据分析(三)之决策树分类
  13. vs2017/2019无法登陆:我们无法刷新此账户的凭据,解决方法(亲测可用)
  14. EduCoder-程序设计技术R-循环结构程序设计2-(第1关:C循环-求平均成绩)(第2关:C循环-求各位数字)(第3关:C循环-求阶乘之和)(第4关:C循环-水仙花数)(第5关:C循环
  15. Android 外部存储App私有目录下照片和视频显示到相册
  16. JVM-Java虚拟机
  17. 3D MAX石墨工具学习技巧
  18. log4j2 自动删除过期日志文件配置及实现原理解析
  19. 论文阅读|EPSANet
  20. 读书笔记 1.数据包分析技术与网络基础 Wireshark数据包分析实战 第3版

热门文章

  1. 怎么样才叫软件团队开发
  2. Qt中使用qrc管理和使用资源文件(转)
  3. 测试显卡的软件叫游戏什么,显卡测试软件哪个好
  4. html5字体加重,javascript – Html5画布字体重量渲染比它应该重得多
  5. 说说看板在项目中的应用
  6. 策划经验分享——常用软件篇
  7. 本地电脑远程至工控现场一台普通PC上(没安装博途软件),实现读写与监控PLC程序
  8. FitNesse工具
  9. autoware的icp_maching解读
  10. java 中的class类_Java中Class类简介