Lab2实验报告

Execrise 1

static void *boot_alloc(uint32_t n)
{static char *nextfree;char *result;if (!nextfree) {extern char end[];nextfree = ROUNDUP((char *) end, PGSIZE);}result = nextfree;nextfree = nextfree + ROUNDUP(n, PGSIZE);return result;
}

nextfree指向下一个空闲字节,而起始时,这个空闲字节位于内核程序的.bss段后面
这里分配内存的方法就是简单地移动并返回指针

void mem_init(void)
{// 缺少的部分pages = (struct PageInfo *) boot_alloc(npages * sizeof(struct PageInfo));memset(pages, 0, npages * sizeof(struct PageInfo));
}

分配npages * sizeof(struct PageInfo)个字节用于存放物理页面信息

void page_init(void)
{size_t left_i = PGNUM(IOPHYSMEM);size_t right_i = PGNUM(PADDR(pages + npages));for (size_t i = 1; i < npages; i++) {if (left_i > i || i > right_i) {pages[i].pp_link = page_free_list;page_free_list = &pages[i];}}
}

在初始化物理页面信息时,注意如下已被占有的物理页面不能加入空闲链表

  • IDT存放的位置,即第0个页面
  • IO映射内存,即0xA00000x100000
  • 用于存放物理页面信息的内存,即pagespages+npages
struct PageInfo *page_alloc(int alloc_flags)
{if (page_free_list == NULL) {return NULL;}struct PageInfo *pp = page_free_list;page_free_list = page_free_list -> pp_link;pp -> pp_link = NULL;if (alloc_flags & ALLOC_ZERO) {memset(page2kva(pp), 0, PGSIZE);}return pp;
}

这里注意不要增加引用计数,并需要判断没用空闲界面的情况

void page_free(struct PageInfo *pp)
{if (pp -> pp_ref || pp -> pp_link) {panic("free error!");}pp -> pp_link = page_free_list;page_free_list = pp;
}

这里注意判断存在引用或本来就是空闲界面时,不释放页面

Execrise 4

pte_t *pgdir_walk(pde_t *pgdir, const void *va, int create)
{pde_t pde = pgdir[PDX(va)];if (!(pde & PTE_P)) {if (!create) {return NULL;        }struct PageInfo *pp = page_alloc(true);if (!pp) {return NULL;}(pp -> pp_ref)++;pgdir[PDX(va)] = page2pa(pp) | PTE_U | PTE_P | PTE_W;return (pte_t *) page2kva(pp) + PTX(va);}return (pte_t *) KADDR(PTE_ADDR(pde)) + PTX(va);
}

当一级页表内不存在va对应的表项时,需要根据create判断是否要为其分配一个物理页面用作二级页表
这里需要设置权限,由于一级页表和二级页表都有权限控制,所以一般的做法是,放宽一级页表的权限,主要由二级页表来控制权限
还要注意,一级页表中存放的地址是物理地址,而返回的必须是虚拟地址且必须去掉权限位

static void boot_map_region(pde_t *pgdir, uintptr_t va, size_t size, physaddr_t pa, int perm)
{for (uintptr_t end = va + size; va != end; pa += PGSIZE, va+= PGSIZE) {pte_t *pte = pgdir_walk(pgdir, (void *) va, true);*pte = pa | perm | PTE_P;}
}

这个函数用于之后将一块虚拟地址映射到物理地址

struct PageInfo *page_lookup(pde_t *pgdir, void *va, pte_t **pte_store)
{pte_t *pte = pgdir_walk(pgdir, va, false);if (!pte || !(*pte & PTE_P)) {return NULL;}if (pte_store != NULL) {*pte_store = pte;}   return pa2page(PTE_ADDR(*pte));
}

注意判断不存在表项,或表项内容不可用时,pte_store用于存放指向表项的指针

void page_remove(pde_t *pgdir, void *va)
{pte_t *pte;struct PageInfo *pp = page_lookup(pgdir, va, &pte);if (pp) {page_decref(pp);*pte = 0;tlb_invalidate(pgdir, va);  }
}

这里需要将表项内容清零,并且清除TLB中对应的内容

int page_insert(pde_t *pgdir, struct PageInfo *pp, void *va, int perm)
{pte_t *pte = pgdir_walk(pgdir, va, true);if (!pte) {return -E_NO_MEM;}if (*pte & PTE_P) {if (PTE_ADDR(*pte) == page2pa(pp)) {*pte = page2pa(pp) | perm | PTE_P;return 0;}page_remove(pgdir, va);}++(pp -> pp_ref);*pte = page2pa(pp) | perm | PTE_P;return 0;
}

分配页面失败时返回-E_NO_MEM,插入时分三种情况

  • 插入位置不存在页面,直接插入即可
  • 插入位置存在不相同的页面,先移除该页面再插入
  • 插入位置存在相同页面,设置权限即可

Execrise 5

void mem_init(void)
{// 缺少的部分boot_map_region(kern_pgdir, UPAGES, ROUNDUP(npages * sizeof(struct PageInfo), PGSIZE), PTSIZE, PTE_U | PTE_P);boot_map_region(kern_pgdir, KSTACKTOP-KSTKSIZE, KSTKSIZE, PADDR(bootstack), PTE_W | PTE_P); boot_map_region(kern_pgdir, KERNBASE, 1 << 28, 0, PTE_W | PTE_P);
}

这里需要设置PTE_W否则之后会出错

Question

  • Assuming that the following JOS kernel code is correct, what type should variable x have, uintptr_t or physaddr_t?

这里显然是虚拟地址

  • What entries (rows) in the page directory have been filled in at this point? What addresses do they map and where do they point? In other words, fill out this table as much as possible:
Entry Base Virtual Address Points to (logically)
1023 0x003be000 Page table for top 4MB of phys memory
Page table for top 4MB of phys memory
961 0x003fc000 Page table for top 4MB of phys memory
960 0x003ff000 Page table for top 4MB of phys memory
959 0x003fe000 Kernel Stack & Invalid Memory
958 NULL NULL
957 0x00118000 Page Table
956 0x003fd000 Read-Only PAGES
955 NULL NULL
NULL
0 NULL NULL
  • We have placed the kernel and user environment in the same address space. Why will user programs not be able to read or write the kernel’s memory? What specific mechanisms protect the kernel memory?

通过权限控制,设置U/S = 0可阻止用户程序访问内核内存

  • What is the maximum amount of physical memory that this operating system can support? Why?

由于PAGES的大小为4MB而一个PageInfo的大小为8Byte,所以最多有524288个页面,即最大内存为524288 * 4KB = 2GB

  • How much space overhead is there for managing memory, if we actually had the maximum amount of physical memory? How is this overhead broken down?

无法拥有最大内存,管理内存的开销为1个一级页表与1024个二级页表,再加上4MBPAGES,即 1025 * 4KB + 4MB = 6100KB

  • Revisit the page table setup in kern/entry.S and kern/entrypgdir.c. Immediately after we turn on paging, EIP is still a low number (a little over 1MB). At what point do we transition to running at an EIP above KERNBASE? What makes it possible for us to continue executing at a low EIP between when we enable paging and when we begin running at an EIP above KERNBASE? Why is this transition necessary?

entry.S中,指令

jmp *%eax

执行之后eip位于KERNBASE之上,在设置页表后还有一小段指令是运行在低地址的,可以运行的原因是页表中同时也把虚拟地址[0, 4MB)映射到了物理地址[0, 4MB),这是必须的,不然会找不到地址

Challenge

  • We consumed many physical pages to hold the page tables for the KERNBASE mapping. Do a more space-efficient job using the PTE_PS (“Page Size”) bit in the page directory entries.

我的实现代码如下

void mem_init(void)
{
//  boot_map_region(kern_pgdir, KERNBASE, 1 << 28, 0, PTE_W | PTE_P);// 开启 cr4 PSE 位uint32_t cr4;cr4 = rcr4();cr4 |= CR4_PSE;lcr4(cr4);// 设置 PDEuintptr_t va = KERNBASE;physaddr_t pa = 0;for (size_t i = 0; i != 64; ++i) {kern_pgdir[PDX(va)] = pa | PTE_W | PTE_P | PTE_PS;va += PTSIZE;pa += PTSIZE;}
}

首先注释掉先前对于KERNBASE以上的boot_map_region(),由于要使用PDEPS位,所以需要先开启CR4PSE位,然后便是每个PED表项对应4MB的内存,所以无需再分配二级页表
原本4KB的页面大小,需要64PDE表项和64 * 1024PTE表项,也就是大约256KB
现在4MB的页面大小,需要64PDE表项,也就是256B
注意,需要注释掉check_kern_pgdir(),因为他会检查PTE的相关信息,然而这部分没有PTE
qemu虚拟机中,输入指令info pg,下面为截取片段

[f0000-f43ff]  PDE[3c0-3d0] --SDA---WP 00000-043ff
[f4400-fffff]  PDE[3d1-3ff] --S-----WP 04400-0ffff

可以看到,映射成功了,其中S就是PS

  • Extend the JOS kernel monitor with commands

首先在command数组中,加入showmappings,结果如下

static struct Command commands[] = {{ "help", "Display this list of commands", mon_help },{ "kerninfo", "Display information about the kernel", mon_kerninfo },{ "showmappings", "Display information about physical page mappings", mon_showmappings }
};

接下来便是实现showmappings函数

int
mon_showmappings(int args, char **argv, struct Trapframe *tf)
{char flag[1 << 8] = {[0] = '-',[PTE_W] = 'W',[PTE_U] = 'U',[PTE_A] = 'A',[PTE_D] = 'D',[PTE_PS] = 'S'};char *arg1 = argv[1];char *arg2 = argv[2];char *arg3 = argv[3];char *endptr;if (arg1 == NULL || arg2 == NULL || arg3) {cprintf("we need exactly two arguments!\n");return 0;}uintptr_t va_l = strtol(arg1, &endptr, 16);if (*endptr) {cprintf("argument's format error!\n");return 0;}uintptr_t va_r = strtol(arg2, &endptr, 16);if (*endptr) {cprintf("argument's format error!\n");return 0;}if (va_l > va_r) {cprintf("the first argument should not larger than the second argument!\n");return 0;}pde_t *pgdir = (pde_t *) PGADDR(PDX(UVPT), PDX(UVPT), 0);   // 这里直接用 kern_pgdir 也可以cprintf("      va range         entry      flag           pa range      \n");cprintf("---------------------------------------------------------------\n");while (va_l <= va_r) {pde_t pde = pgdir[PDX(va_l)];if (pde & PTE_P) {char bit_w = flag[pde & PTE_W];char bit_u = flag[pde & PTE_U];char bit_a = flag[pde & PTE_A];char bit_d = flag[pde & PTE_D];char bit_s = flag[pde & PTE_PS];pde = PTE_ADDR(pde);if (va_l < KERNBASE) {cprintf("[%08x - %08x]", va_l, va_l + PTSIZE - 1);cprintf(" PDE[%03x] --%c%c%c--%c%cP\n", PDX(va_l), bit_s, bit_d, bit_a, bit_u, bit_w);pte_t *pte = (pte_t *) (pde + KERNBASE);for (size_t i = 0; i != 1024 && va_l <= va_r; va_l += PGSIZE, ++i) {if (pte[i] & PTE_P) {bit_w = flag[pte[i] & PTE_W];bit_u = flag[pte[i] & PTE_U];bit_a = flag[pte[i] & PTE_A];bit_d = flag[pte[i] & PTE_D];bit_s = flag[pte[i] & PTE_PS];cprintf(" |-[%08x - %08x]", va_l, va_l + PGSIZE - 1);   cprintf(" PTE[%03x] --%c%c%c--%c%cP", i, bit_s, bit_d, bit_a, bit_u, bit_w);cprintf(" [%08x - %08x]\n", PTE_ADDR(pte[i]), PTE_ADDR(pte[i]) + PGSIZE - 1);           }}continue;}cprintf("[%08x - %08x]", va_l, va_l + PTSIZE - 1, PDX(va_l));cprintf(" PDE[%03x] --%c%c%c--%c%cP", PDX(va_l), bit_s, bit_d, bit_a, bit_u, bit_w);cprintf(" [%08x - %08x]\n", pde, pde + PTSIZE - 1);if (va_l == 0xffc00000) {break;}}va_l += PTSIZE;}return 0;
}

刚开始简单的判断参数正确性
之后便是简单地从页表中读取相关信息并打印到屏幕上,需要注意4KB4MB的情况有所不同,因为4MB页面不存在PTE
运行结果如下

K> showmappings ef400000 f0000000va range         entry      flag           pa range
---------------------------------------------------------------
[ef400000 - ef7fffff] PDE[3bd] ----A--U-P|-[ef7bc000 - ef7bcfff] PTE[3bc] -------UWP [003fd000 - 003fdfff]|-[ef7bd000 - ef7bdfff] PTE[3bd] ----A--U-P [00118000 - 00118fff]|-[ef7bf000 - ef7bffff] PTE[3bf] -------UWP [003fe000 - 003fefff]|-[ef7c0000 - ef7c0fff] PTE[3c0] --SDA---WP [00000000 - 00000fff]|-[ef7c1000 - ef7c1fff] PTE[3c1] --SDA---WP [00400000 - 00400fff]|-[ef7c2000 - ef7c2fff] PTE[3c2] --SDA---WP [00800000 - 00800fff]|-[ef7c3000 - ef7c3fff] PTE[3c3] --SDA---WP [00c00000 - 00c00fff]|-[ef7c4000 - ef7c4fff] PTE[3c4] --SDA---WP [01000000 - 01000fff]|-[ef7c5000 - ef7c5fff] PTE[3c5] --SDA---WP [01400000 - 01400fff]|-[ef7c6000 - ef7c6fff] PTE[3c6] --SDA---WP [01800000 - 01800fff]|-[ef7c7000 - ef7c7fff] PTE[3c7] --SDA---WP [01c00000 - 01c00fff]|-[ef7c8000 - ef7c8fff] PTE[3c8] --SDA---WP [02000000 - 02000fff]|-[ef7c9000 - ef7c9fff] PTE[3c9] --SDA---WP [02400000 - 02400fff]|-[ef7ca000 - ef7cafff] PTE[3ca] --SDA---WP [02800000 - 02800fff]|-[ef7cb000 - ef7cbfff] PTE[3cb] --SDA---WP [02c00000 - 02c00fff]|-[ef7cc000 - ef7ccfff] PTE[3cc] --SDA---WP [03000000 - 03000fff]|-[ef7cd000 - ef7cdfff] PTE[3cd] --SDA---WP [03400000 - 03400fff]|-[ef7ce000 - ef7cefff] PTE[3ce] --SDA---WP [03800000 - 03800fff]|-[ef7cf000 - ef7cffff] PTE[3cf] --SDA---WP [03c00000 - 03c00fff]|-[ef7d0000 - ef7d0fff] PTE[3d0] --SDA---WP [04000000 - 04000fff]|-[ef7d1000 - ef7d1fff] PTE[3d1] --S-----WP [04400000 - 04400fff]|-[ef7d2000 - ef7d2fff] PTE[3d2] --S-----WP [04800000 - 04800fff]|-[ef7d3000 - ef7d3fff] PTE[3d3] --S-----WP [04c00000 - 04c00fff]|-[ef7d4000 - ef7d4fff] PTE[3d4] --S-----WP [05000000 - 05000fff]|-[ef7d5000 - ef7d5fff] PTE[3d5] --S-----WP [05400000 - 05400fff]|-[ef7d6000 - ef7d6fff] PTE[3d6] --S-----WP [05800000 - 05800fff]|-[ef7d7000 - ef7d7fff] PTE[3d7] --S-----WP [05c00000 - 05c00fff]|-[ef7d8000 - ef7d8fff] PTE[3d8] --S-----WP [06000000 - 06000fff]|-[ef7d9000 - ef7d9fff] PTE[3d9] --S-----WP [06400000 - 06400fff]|-[ef7da000 - ef7dafff] PTE[3da] --S-----WP [06800000 - 06800fff]|-[ef7db000 - ef7dbfff] PTE[3db] --S-----WP [06c00000 - 06c00fff]|-[ef7dc000 - ef7dcfff] PTE[3dc] --S-----WP [07000000 - 07000fff]|-[ef7dd000 - ef7ddfff] PTE[3dd] --S-----WP [07400000 - 07400fff]|-[ef7de000 - ef7defff] PTE[3de] --S-----WP [07800000 - 07800fff]|-[ef7df000 - ef7dffff] PTE[3df] --S-----WP [07c00000 - 07c00fff]|-[ef7e0000 - ef7e0fff] PTE[3e0] --S-----WP [08000000 - 08000fff]|-[ef7e1000 - ef7e1fff] PTE[3e1] --S-----WP [08400000 - 08400fff]|-[ef7e2000 - ef7e2fff] PTE[3e2] --S-----WP [08800000 - 08800fff]|-[ef7e3000 - ef7e3fff] PTE[3e3] --S-----WP [08c00000 - 08c00fff]|-[ef7e4000 - ef7e4fff] PTE[3e4] --S-----WP [09000000 - 09000fff]|-[ef7e5000 - ef7e5fff] PTE[3e5] --S-----WP [09400000 - 09400fff]|-[ef7e6000 - ef7e6fff] PTE[3e6] --S-----WP [09800000 - 09800fff]|-[ef7e7000 - ef7e7fff] PTE[3e7] --S-----WP [09c00000 - 09c00fff]|-[ef7e8000 - ef7e8fff] PTE[3e8] --S-----WP [0a000000 - 0a000fff]|-[ef7e9000 - ef7e9fff] PTE[3e9] --S-----WP [0a400000 - 0a400fff]|-[ef7ea000 - ef7eafff] PTE[3ea] --S-----WP [0a800000 - 0a800fff]|-[ef7eb000 - ef7ebfff] PTE[3eb] --S-----WP [0ac00000 - 0ac00fff]|-[ef7ec000 - ef7ecfff] PTE[3ec] --S-----WP [0b000000 - 0b000fff]|-[ef7ed000 - ef7edfff] PTE[3ed] --S-----WP [0b400000 - 0b400fff]|-[ef7ee000 - ef7eefff] PTE[3ee] --S-----WP [0b800000 - 0b800fff]|-[ef7ef000 - ef7effff] PTE[3ef] --S-----WP [0bc00000 - 0bc00fff]|-[ef7f0000 - ef7f0fff] PTE[3f0] --S-----WP [0c000000 - 0c000fff]|-[ef7f1000 - ef7f1fff] PTE[3f1] --S-----WP [0c400000 - 0c400fff]|-[ef7f2000 - ef7f2fff] PTE[3f2] --S-----WP [0c800000 - 0c800fff]|-[ef7f3000 - ef7f3fff] PTE[3f3] --S-----WP [0cc00000 - 0cc00fff]|-[ef7f4000 - ef7f4fff] PTE[3f4] --S-----WP [0d000000 - 0d000fff]|-[ef7f5000 - ef7f5fff] PTE[3f5] --S-----WP [0d400000 - 0d400fff]|-[ef7f6000 - ef7f6fff] PTE[3f6] --S-----WP [0d800000 - 0d800fff]|-[ef7f7000 - ef7f7fff] PTE[3f7] --S-----WP [0dc00000 - 0dc00fff]|-[ef7f8000 - ef7f8fff] PTE[3f8] --S-----WP [0e000000 - 0e000fff]|-[ef7f9000 - ef7f9fff] PTE[3f9] --S-----WP [0e400000 - 0e400fff]|-[ef7fa000 - ef7fafff] PTE[3fa] --S-----WP [0e800000 - 0e800fff]|-[ef7fb000 - ef7fbfff] PTE[3fb] --S-----WP [0ec00000 - 0ec00fff]|-[ef7fc000 - ef7fcfff] PTE[3fc] --S-----WP [0f000000 - 0f000fff]|-[ef7fd000 - ef7fdfff] PTE[3fd] --S-----WP [0f400000 - 0f400fff]|-[ef7fe000 - ef7fefff] PTE[3fe] --S-----WP [0f800000 - 0f800fff]|-[ef7ff000 - ef7fffff] PTE[3ff] --S-----WP [0fc00000 - 0fc00fff]
[efc00000 - efffffff] PDE[3bf] -------UWP|-[efff8000 - efff8fff] PTE[3f8] --------WP [0010d000 - 0010dfff]|-[efff9000 - efff9fff] PTE[3f9] --------WP [0010e000 - 0010efff]|-[efffa000 - efffafff] PTE[3fa] --------WP [0010f000 - 0010ffff]|-[efffb000 - efffbfff] PTE[3fb] --------WP [00110000 - 00110fff]|-[efffc000 - efffcfff] PTE[3fc] --------WP [00111000 - 00111fff]|-[efffd000 - efffdfff] PTE[3fd] --------WP [00112000 - 00112fff]|-[efffe000 - efffefff] PTE[3fe] --------WP [00113000 - 00113fff]|-[effff000 - efffffff] PTE[3ff] --------WP [00114000 - 00114fff]
[f0000000 - f03fffff] PDE[3c0] --SDA---WP [00000000 - 003fffff]

MIT 6.828 学习笔记4 Lab2实验报告相关推荐

  1. MIT 6.828 学习笔记2 阅读main.c

    #include <inc/x86.h> #include <inc/elf.h>/********************************************** ...

  2. gram矩阵的性质_第十七课:正交矩阵和GramSchmidt正交化——MIT线性代数课程学习笔记...

    公众号关注  "DL_NLP" 设为 "星标",重磅干货,第一时间送达! ◎ 原创 | 深度学习算法与自然语言处理 ◎ 作者 | 丁坤博 一. 知识概要 这一节 ...

  3. MIT 6.s081学习笔记

    MIT 6.s081学习笔记 introduction 计算机组织结构: 最底部是一些硬件资源,包括了CPU,内存,磁盘,网卡 最上层会运行各种应用程序,比如vim,shell等,这些就是正在运行的所 ...

  4. STM32学习笔记:按键实验

    STM32学习笔记:按键实验 一.所使用的函数 1.时钟使能函数 RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState New ...

  5. 软件构造lab2 - 实验报告

    软件构造lab2 - 实验报告 1.实验目标概述 2.环境配置 3.实验过程 3.1Poetic Walks 3.1.1Get the code and prepare Git repository ...

  6. 哈工大2020软件构造Lab2实验报告

    本项目于3.17日实验课验收,请放心参考 参考时文中有给出一些建议,请查看 基本更新完成 2020春计算机学院<软件构造>课程Lab2实验报告 Software Construction ...

  7. MIT 6.824 学习笔记(一)--- RPC 详解

    从本文开始,将记录作者学习 MIT 6.824 分布式系统的学习笔记,如果有志同道合者,欢迎一起交流. RPC 的定义和结构 RPC 全称为 Remote Procedure Call,他表示一种远程 ...

  8. [MIT]微积分重点学习笔记 目录

    先介绍下自己的情况,大学的时候学习不认真,很多概念都忘记了,工作中有时要用到微积分,碰到不会的在网上查询,感觉这样学习的比较零散,也不能建立系统的认识.多次想要从头看一遍同济版<高等数学> ...

  9. HIT 软件构造 lab2实验报告

    2020年春季学期 计算机学院<软件构造>课程 Lab 2实验报告 学号 1180300223 班号 1803002 目录 1 实验目标概述 1 2 实验环境配置 1 3 实验过程 1 3 ...

最新文章

  1. 使用solr的DIHandler 构建mysql大表全量索引,内存溢出问题的解决方法
  2. 【Git/Github】第一次提交和再次添加文件
  3. spring el 表达式的上下文关联到 ApplicationContext
  4. case when条件表达式
  5. 直通BAT必考题系列:JVM的4种垃圾回收算法、垃圾回收机制与总结
  6. SQLServer 事物与索引
  7. Java虚拟机(七)——本地方法接口与本地方法栈
  8. WebAPI性能监控-MiniProfiler与Swagger集成
  9. matlab数字图像处理大作业_线上教学优秀案例(16) | 数字图像处理基于蓝墨云+企业微信的线上教学经验分享...
  10. 对话周鸿祎:从程序员创业谈起
  11. 基于RV1126平台imx291分析 --- media部件连接 三
  12. 计算机软件 如何评正高职称,正高职称评审条件
  13. 用参数方程绘制椭球体
  14. HDU-4567-思维-Brilliant Programmers Show -13长沙邀请赛
  15. Thinkpad T420,430等电脑使用微信进行语音视频时麦克杂音
  16. 计算KL距离的几个例子
  17. oracle改字体大小_集成开发环境PL/SQL Developer教程:设置行号和修改字体大小
  18. 计算机软件工程专业心得,非计算机专业学软件工程的一点心得体会
  19. Android 如何获取唯一性ID实践
  20. android 应用未验证,解决微信分享显示“未验证应用”问题。

热门文章

  1. python物业管理系统_jsp物业管理系统,源码下载
  2. Android百度地图显示附近的位置
  3. 软件开发到底是在做什么?
  4. 电脑开关坏了,用Reset键代替开关机键盘
  5. Easy3D 孔洞识别
  6. VUE搭建后台管理界面
  7. 深度学习和计算机视觉相关总结
  8. 设置Xshell最大显示行数
  9. nodemcu刷鸿蒙系统,ESP01S刷入NodeMCU固件
  10. Centos8安装显卡驱动以及Cuda