MIT_6.828_lab2_exercise1_讲解

2023-11-10

这一部分任务就是完成5个函数

  • boot_alloc()
  • mem_init()
  • page_init()
  • page_alloc()
  • page_free()

做之前,要先分析一下内存分布和地址转换的内容。这些内容都是我做的时候边做边摸索的,遇到做不下去,就观察一下查一查;如果不明白下面这些的话,这几个函数是完不成的。如果想直接看答案,这部分可以跳过。

内存分布

下面是lab1里面就介绍了的内存分布。

+------------------+  <- 0xFFFFFFFF (4GB)
|      32-bit      |
|  memory mapped   |
|     devices      |
|                  |
/\/\/\/\/\/\/\/\/\/\

/\/\/\/\/\/\/\/\/\/\
|                  |
|      Unused      |
|                  |
+------------------+  <- depends on amount of RAM
|                  |
|                  |
| Extended Memory  |
|                  |
|                  |
+------------------+  <- 0x00100000 (1MB)
|     BIOS ROM     |
+------------------+  <- 0x000F0000 (960KB)
|  16-bit devices, |
|  expansion ROMs  |
+------------------+  <- 0x000C0000 (768KB)
|   VGA Display    |
+------------------+  <- 0x000A0000 (640KB)
|                  |
|    Low Memory    |
|                  |
+------------------+  <- 0x00000000

小于640KB的部分,即小于0x000A0000的部分,叫做low memory。

在0x000A0000~0x00100000之间的部分,是VGA显示设置字段、扩展ROM字段、BIOS ROM字段。

从0x00100000,即1MB部分开始向上,就是内核被加载到的物理地址,实验文档中是(where the boot loader loaded the kernel into physical memory)。虚拟地址对应于0xf0100000,映射到物理地址0x00100000。

页表结构

页表项采用线性表来存储。

具体地说,每一张页表都对应有一个结构体,记录当前页的使用情况,和下一张空闲页表结构体的地址(如果当前页已被使用,就设置为指向NULL)。同时,有一个指向第一张空闲页表的结构体的指针。通过这个指针,和结构体中指向下一个空闲页表的地址,我们就能得到一个包含所有空闲页表的单链表。

这个页表的结构体见下,在/home/yichuan/6.828/lab/inc/memlayout.h。

/*
 * Page descriptor structures, mapped at UPAGES.
 * Read/write to the kernel, read-only to user programs.
 *
 * Each struct PageInfo stores metadata for one physical page.
 * Is it NOT the physical page itself, but there is a one-to-one
 * correspondence between physical pages and struct PageInfo's.
 * You can map a struct PageInfo * to the corresponding physical address
 * with page2pa() in kern/pmap.h.
 */
struct PageInfo {
	// Next page on the free list.
	struct PageInfo *pp_link;

	// pp_ref is the count of pointers (usually in page table entries)
	// to this page, for pages allocated using page_alloc.
	// Pages allocated at boot time using pmap.c's
	// boot_alloc do not have valid reference count fields.

	uint16_t pp_ref;
};

指向空闲页表结构体的指针见下,在/home/yichuan/6.828/lab/kern/pmap.c。

static struct PageInfo *page_free_list;	// Free list of physical pages

pplink就是指向下一张空闲页表结构体的指针,ppref就是表示是否对应页已被使用。

有一个结构体数组用来保存所有的页表结构体,pages,后续page_init()函数中还要写这个数组。

之后问题就来了:要我写的话,数组结构体中我除了包含pplink和ppref之外,还要包含一个指向对应页的指针,来对结构体对应的页面进行操作。这个指针应该是必不可少的,但是这里显然没有。我一直往下做,做到了page_alloc()函数,发现了这个问题,发现如果搞不懂的话就做不下去了。

最后发现,没有这个对应页的指针,是因为使用其他方法同样可以得到映射关系。这个映射就是,直接通过page_free_list,减去pages(就是结构体数组的首地址),得到当前结构体在数组中的索引,将这个索引左移12位,得到一个页的起始地址(因为显然为212的倍数,而一页大小即212),这就是这个结构体映射到的页的物理地址。但是,由于这个内核程序默认按虚拟地址处理地址,所以还要将物理地址转换为虚拟地址。物理地址转换成虚拟地址很简单,直接加个0xf0000000就可以了。这就是由页表结构体转换得到对应物理页的过程。

这是页表项的结构,来自于/home/yichuan/6.828/lab/inc/mmu.h

// A linear address 'la' has a three-part structure as follows:
//
// +--------10------+-------10-------+---------12----------+
// | Page Directory |   Page Table   | Offset within Page  |
// |      Index     |      Index     |                     |
// +----------------+----------------+---------------------+
//  \--- PDX(la) --/ \--- PTX(la) --/ \---- PGOFF(la) ----/
//  \---------- PGNUM(la) ----------/
//
// The PDX, PTX, PGOFF, and PGNUM macros decompose linear addresses as shown.
// To construct a linear address la from PDX(la), PTX(la), and PGOFF(la),
// use PGADDR(PDX(la), PTX(la), PGOFF(la)).

这是一个二级页表,但也可以理解为,前20位为页号,后12位为页内偏移。

boot_alloc()

// This simple physical memory allocator is used only while JOS is setting
// up its virtual memory system.  page_alloc() is the real allocator.
//
// If n>0, allocates enough pages of contiguous physical memory to hold 'n'
// bytes.  Doesn't initialize the memory.  Returns a kernel virtual address.
//
// If n==0, returns the address of the next free page without allocating
// anything.
//
// If we're out of memory, boot_alloc should panic.
// This function may ONLY be used during initialization,
// before the page_free_list list has been set up.
static void *
boot_alloc(uint32_t n)
{
	static char *nextfree;	// virtual address of next byte of free memory
	char *result;

	// Initialize nextfree if this is the first time.
	// 'end' is a magic symbol automatically generated by the linker,
	// which points to the end of the kernel's bss segment:
	// the first virtual address that the linker did *not* assign
	// to any kernel code or global variables.
	if (!nextfree) {
		extern char end[];
		nextfree = ROUNDUP((char *) end, PGSIZE);
	}

	// Allocate a chunk large enough to hold 'n' bytes, then update
	// nextfree.  Make sure nextfree is kept aligned
	// to a multiple of PGSIZE.
	//
	// LAB 2: Your code here.

	if (n == 0)	return nextfree;
	else if (n > 0)	{
		result = nextfree;
		nextfree += ROUNDUP(n, PGSIZE);	//如果n大于零,就这么搞。注意这里res和nex的用法,nex始终指向被分配出来的空白空间的开头,而nex始终指向被分配空间的结尾,就象这里,可以对指针直接加整型数,来表示指针的移动和分配空间
		return result;
	}

	return NULL;
}

boot_alloc函数在提示上也说了,这是在未设置虚拟内存系统时使用的分配函数,在设置了虚拟内存系统后,page_init才是实际的分配函数。读一下英文提示,仿照一下他给出的nextfree为空时的代码,写起来不难。

但是要注意end[]。英文提示中说,“end是一个由链接器自动生成的魔数符号,指向kernel的bss段的结尾:第一个没有被链接器分配给任何内核代码和全局变量的地址”。

结合后面我们补充的代码,可以知道,boot_alloc分配保证了每次系统初始化时分配总是从end[]开始,所以后面我用来调试的代码分配内存时,也都是保证了都在0xf0000000之后,或者范围再小一点,都在end[]之后;写一个C程序测试malloc的分配,会发现每次分配的地址就不是都在0xf0000000之后,波动范围要宽得多,但是以xv6内核上面的结果推知,这个分配也应该是在我们测试的linux操作系统的end[]之后。

mem_init()

上面已经说了pages的含义了,按照英文提示,这里就是直接分配一个页表结构体数组,再对这个结构体数组使用memset初始化即可。

	//
	// Allocate an array of npages 'struct PageInfo's and store it in 'pages'.
	// The kernel uses this array to keep track of physical pages: for
	// each physical page, there is a corresponding struct PageInfo in this
	// array.  'npages' is the number of physical pages in memory.  Use memset
	// to initialize all fields of each struct PageInfo to 0.
	// Your code goes here:

	pages = (struct PageInfo*) boot_alloc(sizeof(struct PageInfo) * npages);
	memset(pages, 0, sizeof(struct PageInfo) * npages);

page_init()

看英文说明,由于内存各个部分有的已被使用,有的未被使用,所以分配时要注意区间,英文提示中已经给出了分配区间。

  • 物理页0设为已使用
  • base memory的剩余部分,即[PGSIZE, npages_basemem * PGSIZE)设为未使用
  • 后面紧接着的[IOPHYSMEM, EXTPHYSMEM)绝对不能被分配
  • 扩展内存[EXTPHYSMEM, …)有的未使用,有的已使用,要决定如何分配

实践中发现,不能分配的部分不用处理,只要标记可分配部分的标记位为pp_ref=0即可。如何设置为可分配的代码原始程序中已经提供,照着写就行了。

npages_basemem = basemem / (PGSIZE / 1024); //表示basemem中有多少个页,basemem指的是最低的640K内存

但是问题在于,EXTPHYSMEM之后的起始代码如何设置。其实使用bootalloc分配时,就可以确定ext部分从哪里开始可以分配内存,之后使用PGNUM

#define PGNUM(la) (((uintptr_t) (la)) >> PTXSHIFT) //由地址得到页号

而PADDR宏定义见下,在/home/yichuan/6.828/lab/kern/pmap.h

#define PADDR(kva) _paddr(__FILE__, __LINE__, kva)

static inline physaddr_t
_paddr(const char *file, int line, void *kva)	//检测虚拟内存是否越界,未越界就将虚拟地址转换成物理地址
{
	if ((uint32_t)kva < KERNBASE)
		_panic(file, line, "PADDR called with invalid kva %08lx", kva);
	return (physaddr_t)kva - KERNBASE;
}

则ext段如何处理就有答案了:先使用bootalloc分配内存,返回一个指向虚拟地址的指针,再使用PADDR得到分配位置的物理地址(xv6的地址转换比较简单,虚拟地址 - 内核加载到的虚拟地址基址,就是物理地址),最后使用PGNUM将物理地址重新转换为页号(右移12位即可),即得到在对应页结构体在页表结构体数组中的索引,可以用这个索引来进行初始化。

void
page_init(void)
{
	// The example code here marks all physical pages as free.
	// However this is not truly the case.  What memory is free?
	//  1) Mark physical page 0 as in use.
	//     This way we preserve the real-mode IDT and BIOS structures
	//     in case we ever need them.  (Currently we don't, but...)
	//  2) The rest of base memory, [PGSIZE, npages_basemem * PGSIZE)
	//     is free.
	//  3) Then comes the IO hole [IOPHYSMEM, EXTPHYSMEM), which must
	//     never be allocated.
	//  4) Then extended memory [EXTPHYSMEM, ...).
	//     Some of it is in use, some is free. Where is the kernel
	//     in physical memory?  Which pages are already in use for
	//     page tables and other data structures?
	//
	// Change the code to reflect this.
	// NB: DO NOT actually touch the physical memory corresponding to
	// free pages!

	//看上面的说明能看个差不多,关键是EXTPHYSMEM之后的内存空间用什么来确定。

	size_t i;

	for (i = 1; i< npages_basemem; i++)	{
		pages[i].pp_ref = 0;
		pages[i].pp_link = page_free_list;
		page_free_list = &pages[i];
	}

	//这两个宏在头文件里有,阅读源码时就需要理解,所以直接拿来用。

	for (i = PGNUM(PADDR(boot_alloc(0))); i < npages; i++) {
		pages[i].pp_ref = 0;
		pages[i].pp_link = page_free_list;
		page_free_list = &pages[i];
	}
}

page_alloc()

struct PageInfo *
page_alloc(int alloc_flags)
{
	// Fill this function in
	if (page_free_list == NULL)	return NULL;
	struct PageInfo* res = page_free_list;
	//cprintf("\n\npage_free_list: %x\n", page_free_list);
	page_free_list = page_free_list->pp_link;
	//cprintf("res: %x\n", res);
	//cprintf("pp-pages: %x\n",page2pa_test(res));
	//cprintf("page2pa(pp): %x\n",page2pa(res));
	res->pp_link = NULL;
	if (alloc_flags & ALLOC_ZERO)
		memset(page2kva(res), '\0', PGSIZE);
	//cprintf("res after transmit: %x\n\n\n", res);
	return res;
}

我试过,res->pp_ref不能赋值为1,直接不用管就行了,赋值为0也可以;不能memset不执行就else将res = NULL,这样也过不了测试。

为了弄清楚页表结构,我用了上述cprintf测试语句进行测试。同时,在/home/yichuan/6.828/lab/kern/pmap.h中加入函数page2pa_test(),见下。

static inline physaddr_t
page2pa_test(struct PageInfo *pp)
{
	cprintf("pp: %x\n",pp);
	cprintf("pages: %x\n",pages);
	cprintf("sizeof( PageInfo): %x\n",sizeof(struct PageInfo));
	return pp - pages;
}

测试后发现,一个页表结构体占8字节,即指针减法res - pages = 地址之差 * 8,

下面是page2kva函数

static inline void*
page2kva(struct PageInfo *pp)
{
	return KADDR(page2pa(pp));
}

将物理地址转换成内核虚拟地址,KADDR宏见下。

#define KADDR(pa) _kaddr(__FILE__, __LINE__, pa)

static inline void*
_kaddr(const char *file, int line, physaddr_t pa)	//检测物理内存内存是否越界,未越界就将物理地址转换成虚拟地址
{
	if (PGNUM(pa) >= npages)
		_panic(file, line, "KADDR called with invalid pa %08lx", pa);
	return (void *)(pa + KERNBASE); 	//KERNBASE = 0xF0000000
}

page2pa(pp)定义如下,可以看到,就是上文页表结构部分所说,结构体索引左移12位,即得页面实际存储位置的物理地址,这就是页结构体与页面本身的映射方式。

static inline physaddr_t
page2pa(struct PageInfo *pp)
{
	return (pp - pages) << PGSHIFT;
}

那么全流程就清楚了:使用pagefreelist得到一个空闲页表项赋给res,之后pagefreelist更新;使用page2kva得到页表结构体对应页面的内核虚拟地址,memset赋值,返回res即可。

page_free()

看好英语提示就解决了:将一个页返回freelist。由于上文已经说过,空闲页按单链表方式存储,所以这样写。

//
// Return a page to the free list.
// (This function should only be called when pp->pp_ref reaches 0.)
//
void
page_free(struct PageInfo *pp)
{
	// Fill this function in
	// Hint: You may want to panic if pp->pp_ref is nonzero or
	// pp->pp_link is not NULL.
	if (pp->pp_ref || pp->pp_link)	panic("Page isn't empty!\n");
	pp->pp_link = page_free_list;
	page_free_list = pp;
}

测试结果

image-20210723105545115

附录

下面是来自于/home/yichuan/6.828/lab/inc/memlayout.h文件中的虚拟内存分布图。

/*
 * Virtual memory map:                                Permissions
 *                                                    kernel/user
 *
 *    4 Gig -------->  +------------------------------+
 *                     |                              | RW/--
 *                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *                     :              .               :
 *                     :              .               :
 *                     :              .               :
 *                     |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| RW/--
 *                     |                              | RW/--
 *                     |   Remapped Physical Memory   | RW/--
 *                     |                              | RW/--
 *    KERNBASE, ---->  +------------------------------+ 0xf0000000      --+
 *    KSTACKTOP        |     CPU0's Kernel Stack      | RW/--  KSTKSIZE   |
 *                     | - - - - - - - - - - - - - - -|                   |
 *                     |      Invalid Memory (*)      | --/--  KSTKGAP    |
 *                     +------------------------------+                   |
 *                     |     CPU1's Kernel Stack      | RW/--  KSTKSIZE   |
 *                     | - - - - - - - - - - - - - - -|                 PTSIZE
 *                     |      Invalid Memory (*)      | --/--  KSTKGAP    |
 *                     +------------------------------+                   |
 *                     :              .               :                   |
 *                     :              .               :                   |
 *    MMIOLIM ------>  +------------------------------+ 0xefc00000      --+
 *                     |       Memory-mapped I/O      | RW/--  PTSIZE
 * ULIM, MMIOBASE -->  +------------------------------+ 0xef800000
 *                     |  Cur. Page Table (User R-)   | R-/R-  PTSIZE
 *    UVPT      ---->  +------------------------------+ 0xef400000
 *                     |          RO PAGES            | R-/R-  PTSIZE
 *    UPAGES    ---->  +------------------------------+ 0xef000000
 *                     |           RO ENVS            | R-/R-  PTSIZE
 * UTOP,UENVS ------>  +------------------------------+ 0xeec00000
 * UXSTACKTOP -/       |     User Exception Stack     | RW/RW  PGSIZE
 *                     +------------------------------+ 0xeebff000
 *                     |       Empty Memory (*)       | --/--  PGSIZE
 *    USTACKTOP  --->  +------------------------------+ 0xeebfe000
 *                     |      Normal User Stack       | RW/RW  PGSIZE
 *                     +------------------------------+ 0xeebfd000
 *                     |                              |
 *                     |                              |
 *                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *                     .                              .
 *                     .                              .
 *                     .                              .
 *                     |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|
 *                     |     Program Data & Heap      |
 *    UTEXT -------->  +------------------------------+ 0x00800000
 *    PFTEMP ------->  |       Empty Memory (*)       |        PTSIZE
 *                     |                              |
 *    UTEMP -------->  +------------------------------+ 0x00400000      --+
 *                     |       Empty Memory (*)       |                   |
 *                     | - - - - - - - - - - - - - - -|                   |
 *                     |  User STAB Data (optional)   |                 PTSIZE
 *    USTABDATA ---->  +------------------------------+ 0x00200000        |
 *                     |       Empty Memory (*)       |                   |
 *    0 ------------>  +------------------------------+                 --+
 *
 * (*) Note: The kernel ensures that "Invalid Memory" is *never* mapped.
 *     "Empty Memory" is normally unmapped, but user programs may map pages
 *     there if desired.  JOS user programs map pages temporarily at UTEMP.
 */
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

MIT_6.828_lab2_exercise1_讲解 的相关文章

  • 20分钟,使用Amazon SageMaker快速搭建属于自己的AIGC应用

    真火 作为最近一段时间人工智能领域内的顶流之一 AIGC AI Generated Content 早已火爆出圈 频登各大互联网平台热搜 cite 微软亚洲研究院官方微博 这段时间以来 基于深度学习的内容生成在图像 视频 语音 音乐 文本等
  • 融合网络位置服务器,4G/5G定位的关键技术及异构融合一体化定位系统的网络架构...

    摘要 如何提高室内定位精度并实现室内外定位的连续无缝覆盖是定位领域急需解决的问题 针对该问题 首先介绍了4G定位的标准与技术 并分析了其特点与不足 然后 分析了面向5G的超密集组网下的定位技术 AOA TDOA定位技术以及上下行定位技术 最
  • 【python教程】之try--except异常捕获

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 文章目录 异常简介 捕获异常 try except except捕获异常类型 捕获多个异常的方式 不明确错误类型的情况 try except else try except
  • 上线代码质量审查解决方案

    前言 使用自动化工具和人工审查并行方式检查程序源代码是否存在安全隐患 或者有编码不规范的地方 利用自动化代码审计工具 如CheckStyle FindBugs PMD JTest 进行代码审计 以及每周定期组织对最近开发的业务进行代码评审
  • java javaw 命令区别_java 和javaw 的区别

    javaw exe用法和java exe 相同 javaw的程序不在java console 上面显示任何东西 如果在开发程序 就用java 这样可以看到错误提示 如果是运行完成了的程序 就用javaw 可以提高一点速度 两个应用程序都能运
  • I2C总线的通讯距离

    关于I2C总线 I2C总线不适合远距离通信 尤其是使用线缆与接口器件进行通信时一般不选择 I2C 使用I2C总线都是在用一个开发板上或者距离比较近 如果是远距离通信 比如说超过2米 这个时候对硬件就有特殊的要求了 通信线缆小于0 2米 一般
  • STM32学习之以太网介绍

    以太网简介 以太网 Ethernet 是互联网技术的一种 由于它是在组网技术中占的比例最高 很多人直接把以太网理解为互联网 以太网是指遵守IEEE802 3标准组成的局域网 由IEEE 802 3标准规定的主要是位于参考模型的物理层 PHY
  • uboot命令使用学习(4)

    学习目标 uboot命令使用学习 4 EMMC和SD卡操作命令 学习内容 学习使用了正点原子的I MX6ULL教程及开发平台 1 mmc info命令 2 mmc rescan命令 3 mmc list命令 4 mmc dev命令 5 mm
  • 手撕RPC第一天

    前言 众所周知 RPC 远程服务调用 成为现代架构中不可或缺的一部分 那么 熟悉RPC的原理就显得相当重要了 在这个前提下 我开始了学习RPC之路 常见的rpc框架有轻量级的thrift 国内开源的使用众多的dubbo go实现的gRPC
  • linux cannot connect to wifi

    If you cannot connect to wifi while previously you can You may switch to a different kernel if you have an alternative o
  • Android xml属性大全

    第一类 属性值为true或false android layout centerHrizontal 水平居中 android layout centerVertical 垂直居中 android layout centerInparent
  • php:一次完整的HTTP请求过程笔记

    HTTP 事务执行过程 1 客户端 浏览器 做出请求操作 输入网址 点击链接 提交表单 2 客户端检测缓存 1 有缓存且较新 客户端直接读取本地缓存进行资源展示 2 有缓存但是不新 准备http请求包 发送至服务端进行缓存校验 3 客户端对
  • vue3 使用element plus 打包时 报错

    vue3 vite ts elementPlus中运行正常打包出错 能正常运行 但是打包出错 解决打包时出现导入element plus相关的爆红 导致无法打包的问题 如若出现类似于 Module element plus has no e
  • jenkins+ant&findbugs&fireline

    火线在jenkins上的安装 http magic 360 cn zh user html Jenkins jenkins插件离线下载的好地方 http updates jenkins ci org download plugins 在je
  • C语言运算符——自增与自减

    自增与自减 一个整数类型的变量自身加 1 可以这样写 a a 1 或者a 1 不过 C语言还支持另外一种更加简洁的写法 就是 a 或者 a 这种写法叫做自加或自增 意思很明确 就是每次自身加 1 相应的 也有a 和 a 它们叫做自减 表示自
  • 功能测试数据测试之数据测试关注点

    数据类型 常见数据类型 整型 浮点型 字符型 布尔型等 可用等价类方法对输入数据类型设计测试用例 数据长度 数据长度可能是固定长度或者是在某个范围内的长度 可用等价类和边界值方法对数据长度设计测试用例 数据一致性 组织数据测试该交易 在交易
  • Fatal Python error: init_sys_streams: can‘t initialize sys standard streamsPython runtime state: 问题

    哈喽 大家好 我是 奇点 江湖人称 singularity 刚工作几年 想和大家一同进步 一位上进心十足的 Java ToB端大厂领域博主 喜欢java和python 平时比较懒 能用程序解决的坚决不手动解决 如果有对 java 感兴趣的
  • canvas绘制火柴人

  • 每日面试题day02

    1 int 和 Integer 有什么区别 int 是基本数据类型 Integer 是其包装类 注意是一个类 在 java 中包装类 用途比较多的是用在于各种数据类型的转化中 2 重载和重写的区别 overload 重载 参数类型 个数 顺

随机推荐

  • vue3如何进行数据监听watch/watchEffect

    我们都知道监听器的作用是在每次响应式状态发生变化时触发 在组合式 API 中 我们可以使用 watch 函数和watchEffect 函数 当你更改了响应式状态 它可能会同时触发 Vue 组件更新和侦听器回调 默认情况下 用户创建的侦听器回
  • IntelliJ IDEA Plugins加载太慢_IntelliJ IDEA Plugins搜不出来【已解决】

    问题 IntelliJ IDEA Plugins加载太慢或者IntelliJ IDEA Plugins搜不出来 解决方案
  • html form 表单

    定义 form 表单在网页中主要负责数据采集功能 属于一个容器标记 表单组成 一个表单由 form元素 表单控件 和 表单按钮 组成 1 form元素 form元素用来创建表单 语法格式如下
  • SQL中根据经纬度计算两点之间的直线距离

    最近接到一个需求获取当前用户的经纬度 然后计算与目标地的的距离 我自己也是看别人的博客学习 自己也做个记录吧 直接放出计算的公式 不想浪费时间的 直接看公式套进去就成 依次是纬度 纬度 经度 round 6378 138 2 ASIN SQ
  • vue显示PDF文件

    小编最近接手的项目中 有个需求 前端显示后端返回的PDF格式的文件 经过小编两天的调研和试验 终于找到了一个比较好的插件方法 直接贴代码 1 安装 npm i vue pdf signature save dev 2 pdfShow vue
  • 一个测试的成长历程【功能测试篇】——web测试的总结

  • js自写发布订阅模块

    实现效果如下图所示 代码如下
  • 【论文阅读】文献阅读笔记-泊松重建

    先了解泊松分布 就二项分布而言 泊松分布可以是二项分布的推广 样本数趋向于无穷大 而事件发生的概率趋近于0时 此时期望满足np Lamda 常数 且此时事件发生的概率满足泊松分布 且概率的计算只与Lamda有关 但泊松方程和泊松分布没啥关系
  • 微信小程序实现扫二维码时仿微信扫码音效

    需求分析 使用wx scanCode时无交互感 对用户来说没有反馈 故增加扫码成功时震动及播放微信扫码音效 index html
  • 一些关于c语言if语句的练习

    练习题1 在终端输入一个整数 用来表示学生的成绩 输出学生成绩对应的等级 90 100 A 80 90 B 70 80 C 60 70 D 0 60 不及格 练习题2 在终端输入一个整数 用来表示年份 输出这一年是平年还是闰年 闰年 能被4
  • 联盟链FISCO BCOS网络端口讲解

    FISCO BCOS是完全开源的联盟区块链底层技术平台 由金融区块链合作联盟 深圳 简称金链盟 成立开源工作组通力打造 开源工作组成员包括博彦科技 华为 深证通 神州数码 四方精创 腾讯 微众银行 亦笔科技和越秀金科等金链盟成员机构 代码仓
  • IDEA 查看源码快捷键

    一 快捷键 快捷键 功能 Ctrl Shift i 出现类似于预览的小窗口 Ctrl Enter 接上步 完全打开源码 Ctrl 鼠标左键 一步到位打开源码 Ctrl Shift i gt Ctrl Enter IDEA 2018版 实用快
  • 使用Canal实现mysql binlog增量订阅数据

    前言 是由公司业务改造搜索功能 使用ES搜索引擎中间件 那么我们需要将mysql中的数据同步至ES服务中 最总选择使用alibaba的canal增量订阅和解析工具 简单原理 canal模拟mysql slave的交互协议 伪装自己为mysq
  • 使用Java操作excel的几种方法

    在平时的业务系统开发中 少不了需要用到导出 导入excel功能 今天我们就一起来总结一下 下面给大家介绍一下几种常用方法 apache poi easypoi easyexcel 文章目录 一 Apache poi 1 1 首先添加依赖 1
  • 动态IP代理是什么?一文看懂动态代理IP

    一 什么是动态IP代理 动态IP代理是一种代理服务 而动态IP是由ISP动态分配给用户的IP地址 这些IP地址会周期性地更改 每次链接互联网时 用户会被分配一个新的IP地址 因而也称为 轮换IP IP地址轮换是一个过程 您的IP 地址 网络
  • 【深入浅出深度学习】1、深度学习的发展

    人工智能 机器学习 深度学习的关系 1 人工智能 机器推理 利用计算机构建具有人类智力特征的复杂机器 即为通用人工智能或强人工智能 即让机器拥有人类的所有感觉 所有理智 像人类一样思考 要实现真正意义上的人工智能可能还有很长的路 但是在一些
  • Linux 进程异常退出 如何查看日志

    当进程异常退出时 可以通过以下步骤来查看日志 找到进程的 PID 进程 ID 可以通过 ps 命令来查看 ps aux grep lt 进程名称 gt 找到进程的日志文件 一般情况下 进程的日志文件都会被记录在 var log 目录下 可以
  • 将Echart的canvas动画导出为.gif进行下载,有Demo

    JS前端下载导出Echart的动画为 gif图 实现方式为 html2canvas js gif js gif worker js 下载html2canvas js引入项目中 官网 html2canvas js 官网 2 下载gif js引
  • C++重载前置和后置++运算符

    重载前置和后置 运算符 大家在学习运算符号的时候应该是注意到了 的两种用法 一种是前置的 一种是后置的 二者的主要的区别就是一个是先增加后取值 一个是先取值后自加 下面通过一个案例来解释一下重载 运算符号 来源 清华c C l o c k
  • MIT_6.828_lab2_exercise1_讲解

    这一部分任务就是完成5个函数 boot alloc mem init page init page alloc page free 做之前 要先分析一下内存分布和地址转换的内容 这些内容都是我做的时候边做边摸索的 遇到做不下去 就观察一下查