MIT6.S081 - Lab10 mmap(文件内存映射)

本篇是 MIT6.S081 2020 操作系统课程 Lab10 的实验笔记,目标只有一个:实现文件映射到内存的功能,也就是 mmap

作为一名 Android 开发者,我可太熟悉 mmap 这个词儿了。Android 的 跨进程通信 Binder 驱动图形内存分配和管理腾讯 MMKV 键值对库 都离不开 mmap 技术的支持,所以,我对 什么是 mmap、它的使用场景和使用方式 以及 大概的实现原理 多多少少都了解一些。

  • Lab10 地址:https://pdos.csail.mit.edu/6.828/2020/labs/mmap.html
  • 我的实验记录:https://github.com/yibaoshan/xv6-labs-2020/tree/mmap

今天的实验要我们自己来实现 简化版的 mmap ,虽然只有 Linux mmap 的部分功能(仅支持把文件映射到内存),但足够让我们理解 mmap 的核心原理了。(能借着学习 xv6 的机会一窥 mmap 全貌,说实话我还是挺开心的

在开始实验之前,你需要:

  1. 观看 Lecture 17 课程录播视频:Virtual memory for applications(虚拟内存)
    • YouTube 原版:https://www.youtube.com/watch?v=YNQghIvk0jc
    • 哔哩哔哩中译版:https://www.bilibili.com/video/BV19k4y1C7kA?vd_source=6bce9c6d7d453b39efb8a96f5c8ebb7f&p=16
    • 中译文字版:https://mit-public-courses-cn-translatio.gitbook.io/mit6-s081/lec17-virtual-memory-for-applications-frans
  2. 阅读论文:Virtual memory Primitives for User Programs
    • mit 存档:https://pdos.csail.mit.edu/6.828/2020/readings/appel-li.pdf

聊聊 mmap

实验开始前,我们先来聊聊 什么是 mmap? 它的出现是为了解决什么问题?我们为什么需要它?

根据维基资料:https://en.wikipedia.org/wiki/Mmap,mmap 的原始设计来自 TOPS-20 操作系统,最早是为了解决 虚拟内存管理效率问题

  • 传统的文件 I/O 需要在 用户空间 和 内核空间 之间多次拷贝数据。
  • 大文件处理时,内存的开销比较大。

举个例子🌰

void file_io(const char* filename) {// 获取文件 fdint fd = open(filename, O_RDWR);if (fd < 0) return;// 分配位于用户空间的 buffer 缓冲区// ps:如果是大文件,为了提高效率,这里就需要申请比较大的缓冲区char* buffer = (char*)malloc(BUFFER_SIZE);// 从文件读数据到 buffer 缓冲区// 1. 内核先将数据从磁盘读入内核缓冲区// 2. 然后再将数据从内核缓冲区拷贝到用户空间缓冲区read(fd, buffer, BUFFER_SIZE);// 修改数据buffer[100] = 'A';// 把修改的内容写回文件// 1. 数据从用户空间缓冲区拷贝到内核缓冲区// 2. 然后再从内核缓冲区写入磁盘write(fd, buffer, BUFFER_SIZE);free(buffer);close(fd);
}

楼上是用 传统的文件 I/O 方式 读写文件代码:

  • 读取时,数据会先从 磁盘->内核缓冲区->用户缓冲区
  • 写回时,数据从 用户缓冲区->内核缓冲区->磁盘

如果遇到频繁读写的场景,每次读写都要在 内核空间 和 用户空间 之间拷贝数据,浪费 CPU 资源。

如果改为 mmap 实现:

void mmap_file_io(const char* filename) {int fd = open(filename, O_RDWR);if (fd < 0) return;// 将文件映射到进程的地址空间char* addr = mmap(NULL, BUFFER_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);// 直接修改内存,不需要额外的拷贝addr[100] = 'A';// 系统会自动将修改同步到文件munmap(addr, BUFFER_SIZE);close(fd);
}

我们只需要将 文件 映射到进程的 地址空间,读写内存 就可以 直接修改文件内容,不需要再进行 额外的 拷贝操作了,这将会大大减少 内存拷贝 的次数,提高系统的整体效率。

mmap (hard)

The mmap and munmap system calls allow UNIX programs to exert detailed control over their address spaces. They can be used to share memory among processes, to map files into process address spaces, and as part of user-level page fault schemes such as the garbage-collection algorithms discussed in lecture. In this lab you’ll add mmap and munmap to xv6, focusing on memory-mapped files.

前一小节通过小 demo 简单介绍 mmap 的使用,本章节我们将根据实验要求,实现 xv6 中的 mmap 功能。

增加系统调用

Start by adding _mmaptest to UPROGS, and mmap and munmap system calls, in order to get user/mmaptest.c to compile. For now, just return errors from mmap and munmap. We defined PROT_READ etc for you in kernel/fcntl.h. Run mmaptest, which will fail at the first mmap call.

为 xv6 实现 mmap 功能,实现思路参考(提示部分):https://pdos.csail.mit.edu/6.828/2020/labs/mmap.html

首先,我们按照实验提示,先为 xv6 工程添加 mmap()munmap() 两个系统调用

kernel/syscall.h
...
#define SYS_close  21
#define SYS_mmap   22
#define SYS_munmap 23kernel/syscall.c
...
extern uint64 sys_uptime(void);
extern uint64 sys_mmap(void);
extern uint64 sys_munmap(void);kernel/sysfile.c
...
uint64
sys_mmap(void) // 新增两个系统调用空函数,暂时不用实现
{return 0;
}uint64
sys_munmap(void)
{return 0;
}user/user.h
...
void *memcpy(void *, const void *, uint);
void *mmap(void *addr, int length, int prot, int flags, int fd, int offset);
int munmap(void *addr, int length);user/user.pl
...
entry("uptime");
entry("mmap");
entry("munmap");

mmap()munmap() 新增完成以后,运行 make qemu 可以正常编译,但是运行 mmaptest 测试程序会报错:

在这里插入图片描述

创建 VMA

Keep track of what mmap has mapped for each process. Define a structure corresponding to the VMA (virtual memory area) described in Lecture 15, recording the address, length, permissions, file, etc. for a virtual memory range created by mmap. Since the xv6 kernel doesn’t have a memory allocator in the kernel, it’s OK to declare a fixed-size array of VMAs and allocate from that array as needed. A size of 16 should be sufficient.

定义 Lec 16 提到的 VMA 结构体,用来记录 mmap 创建的虚拟内存区域,包括 地址、长度、权限、文件等,然后为进程创建长度 16 的 vma 数组

kernel/proc.h
...
enum procstate { UNUSED, SLEEPING, RUNNABLE, RUNNING, ZOMBIE };#define NVMA 16  // 每个进程的最大 VMA 数量struct vma {uint64 addr;       // 开始映射的地址(va)uint64 length;     // 映射的长度int prot;          // 保护标志 (PROT_READ, PROT_WRITE)int flags;         // MAP_SHARED 或 MAP_PRIVATEstruct file *file; // 映射的文件uint64 offset;     // 文件偏移量
};// Per-process state
struct proc {...struct vma vmas[NVMA];       // 进程的内存映射区域
};

实现 mmap

Implement mmap: find an unused region in the process’s address space in which to map the file, and add a VMA to the process’s table of mapped regions. The VMA should contain a pointer to a struct file for the file being mapped; mmap should increase the file’s reference count so that the structure doesn’t disappear when the file is closed (hint: see filedup). Run mmaptest: the first mmap should succeed, but the first access to the mmap-ed memory will cause a page fault and kill mmaptest.

按以下步骤实现 mmap() 函数:

  1. 在当前进程的 vmas 数组中,查找一个空闲未使用的 vma 对象。
  2. 把本次 mmap() 调用的参数,记录到刚刚申请的 vma 对象中。
  3. 调用 filedup() 为入参的 file 增加一次引用次数,防止其他地方调用 fileclose() 函数时,因为没人引用文件,导致该文件被关闭了。

注意,这一步不需要真的申请内存,和 懒加载 实验一样,申请内存延迟到在 缺页异常 里面处理

sys_mmap() 函数实现如下:

kernel/sysfile.cuint64
sys_mmap(void)
{// 调用 mmap 函数传进来的参数uint64 addr;int len, prot, flags, fd, offset;struct file* file;// 创建一个空的 vmastruct vma* vma = 0;// 获取 mmap 系统调用的入参,并检查是否合法if(argaddr(0, &addr)<0 || argint(1, &len)<0|| argint(2, &prot)<0 || argint(3, &flags)<0|| argfd(4, &fd, &file)<0 || argint(5, &offset)<0)return -1;// 一些参数合法性校验if(len <= 0)return -1;        if((prot & (PROT_READ|PROT_WRITE|PROT_EXEC)) == 0) // only PROT_READ, PROT_WRITE, PROT_EXECreturn -1;if((prot & PROT_WRITE) && !file->writable && flags==MAP_SHARED) // MAP_SHARED 时,文件必须可写return -1;if((prot & PROT_READ) && !file->readable) // 同理,MAP_PRIVATE 时,文件必须可读,否则返回错误return -1;struct proc* p = myproc();len = PGROUNDUP(len);// 如果进程的虚拟内存空间已经超出了最大值,返回错误if(p->sz+len > MAXVA)return -1;if(offset<0 || offset%PGSIZE)return -1;// 在当前进程 vma 数组里面,查找一个空闲的 vma 区域    for(int i=0; i<NVMA; i++) {if(p->vmas[i].addr)continue;vma = &p->vmas[i];break;}// vma 全部被占用,没有空闲,返回🔙if(!vma)return -1;if(addr == 0)vma->addr = p->sz; // 用户未指定地址,则使用进程当前大小作为起始地址elsevma->addr = addr; // 用户指定了地址// 一些赋值操作vma->length = len;vma->prot = prot;vma->flags = flags;vma->offset = offset;vma->file = file;p->sz += len;// 文件引用 +1filedup(file);return vma->addr;
}

实现 munmap

Implement munmap: find the VMA for the address range and unmap the specified pages (hint: use uvmunmap). If munmap removes all pages of a previous mmap, it should decrement the reference count of the corresponding struct file. If an unmapped page has been modified and the file is mapped MAP_SHARED, write the page back to the file. Look at filewrite for inspiration.

按下面的步骤实现 munmap() 函数:

  1. 在当前进程中,查找要解除映射的 vma 对象。
  2. 如果找到了,调用 uvmunmap() 函数,解除映射。
  3. 调用 fileclose() 函数,释放文件对象。
  4. 如果是 共享映射(MAP_SHARED)且 页面被修改过,要把内容写回文件(参考 filewrite() 的实现)。
uint64
sys_munmap(void)
{uint64 addr;int len;struct vma* vma = 0;struct proc* p = myproc();if(argaddr(0, &addr)<0 || argint(1, &len)<0)return -1;// 参数检查if(len <= 0 || addr + len > p->sz)return -1;addr = PGROUNDDOWN(addr);len = PGROUNDUP(len);// 查找 addr 对应的 VMAfor(int i=0; i<NVMA; i++) {if(p->vmas[i].addr && addr>=p->vmas[i].addr&& addr+len<=p->vmas[i].addr+p->vmas[i].length) {vma = &p->vmas[i];break;}}// addr 不合法,返回if(!vma || addr != vma->addr)return -1;// 如果是共享映射,需要在接触映射的时候写回文件!if(vma->flags & MAP_SHARED)filewrite(vma->file, addr, len);// 解除页表映射uvmunmap(p->pagetable, addr, len/PGSIZE, 1);// 更新 VMA 信息if(len == vma->length) {// 完全解除映射,则释放 VMAfileclose(vma->file);memset(vma, 0, sizeof(*vma));} else {// 部分解除映射,更新地址和长度vma->addr += len;vma->length -= len;}// 解除映射的是进程地址空间的末尾,调整当前进程大小if(addr + len == p->sz)p->sz -= len;return 0;
}

trap handler 懒分配

Add code to cause a page-fault in a mmap-ed region to allocate a page of physical memory, read 4096 bytes of the relevant file into that page, and map it into the user address space. Read the file with readi, which takes an offset argument at which to read in the file (but you will have to lock/unlock the inode passed to readi). Don’t forget to set the permissions correctly on the page. Run mmaptest; it should get to the first munmap.

处理 缺页异常,当程序首次访问 映射的区域 时,会触发 缺页异常:

  1. 这时候才会真正的为 va 分配内存 mem
  2. 把要映射的文件内容按 offset 读出一页,写到刚申请的内存中。
  3. memva 创建映射关系。
kernel/trap.c
...
#include "spinlock.h"
#include "sleeplock.h"
#include "fs.h"
#include "file.h"
#include "proc.h"
#include "fcntl.h"
#include "defs.h"void
usertrap(void)
{...if(r_scause() == 8){...} else if((which_dev = devintr()) != 0){// ok} else if(r_scause()==13 || r_scause()==15) { // 处理缺页异常,这里是程序首次访问映射的区域出错了,需要为该 va 分配物理内存// 读取需要处理错误的 va,参考页表实验uint64 va = r_stval();                    struct vma* vma = 0;                      char* mem = 0;                           int success = 0;                       // 对齐 vava = PGROUNDDOWN(va);// 验证地址合法性(必须在用户栈顶和进程大小之间)if(va < p->sz && va > p->trapframe->sp) {// 尝试查找当前 va 在是哪个 vma 中for(int i=0; i<NVMA; i++) {if(va>=p->vmas[i].addr && va<p->vmas[i].addr+p->vmas[i].length) {vma = &p->vmas[i];break;}}// 找到 va 对应的 vmaif(vma) {// 尝试为 va 分配物理内存mem = kalloc();if(mem) { // 分配成功memset(mem, 0, PGSIZE);  // 清零新分配的内存// 把要映射的文件的数据读出来,然后写入刚申请的内存中 memuint offset = va - vma->addr + vma->offset;  // 计算文件偏移量,确定要读取的位置ilock(vma->file->ip);int bytes_read = readi(vma->file->ip, 0, (uint64)mem, offset, PGSIZE);iunlock(vma->file->ip);if(bytes_read >= 0) {// 更新页表项 pte 的权限int flags = PTE_U;  // 基本权限,用户可访问// 根据 prot 字段设置 pte 的权限if(vma->prot & PROT_READ)flags |= PTE_R;  // 可读if(vma->prot & PROT_WRITE)flags |= PTE_W;  // 可写if(vma->prot & PROT_EXEC)flags |= PTE_X;  // 可执行// 如果是共享映射,确保内存的修改可以被写回到文件中if(vma->flags & MAP_SHARED)flags |= PTE_R;// 为 va 建立页表映射if(mappages(p->pagetable, va, PGSIZE, (uint64)mem, flags) == 0) {success = 1;  // 查找 vma、分配内存、建立页表映射这几步全部都成功了}}}}}// 没成功的话,如果已经分配过内存,释放掉if(!success) {if(mem)kfree(mem);  // 释放已分配的内存p->killed = 1;   // 终止进程}} else {...}
...
}

修改 exit

Modify exit to unmap the process’s mapped regions as if munmap had been called. Run mmaptest; mmap_test should pass, but probably not fork_test.

修改 exit() 函数,在进程退出时,要像调用 munmap() 一样清理掉进程的所有映射区域

kernel/proc.c
void
exit(int status)
{...// Close all open files.for(int fd = 0; fd < NOFILE; fd++){if(p->ofile[fd])...}// 清理所有的 VMAfor(int i = 0; i < 16; i++) {if(p->vmas[i].length > 0) { // 同样只处理被使用的 vma// 解除映射uvmunmap(p->pagetable, p->vmas[i].addr, p->vmas[i].length/PGSIZE, 1);if(p->vmas[i].file) // 如果有映射的文件,需要关闭fileclose(p->vmas[i].file);p->vmas[i].length = 0; // 清空 length,表示未使用}}begin_op();...
}

如果进程退出,遍历 vmas 数组,释放已经使用的 vma,并调用 fileclose() 关闭映射的文件。

修改 fork

Modify fork to ensure that the child has the same mapped regions as the parent. Don’t forget to increment the reference count for a VMA’s struct file. In the page fault handler of the child, it is OK to allocate a new physical page instead of sharing a page with the parent. The latter would be cooler, but it would require more implementation work. Run mmaptest; it should pass both mmap_test and fork_test.

fork() 也支持内存映射功能:

  1. 修改 fork,把 爸爸进程 的映射区域 同样复制给 儿子进程
  2. 复制 vma 时,记得调用 filedup() 增加文件的 引用计数
int
fork(void)
{...np->state = RUNNABLE;// 复制父进程的VMAfor(int i = 0; i < 16; i++) { // length>0 表示该 VMA 正在使用if(p->vmas[i].length > 0) {// 复制爸爸的 vmamemmove(&np->vmas[i], &p->vmas[i], sizeof(struct vma));if(p->vmas[i].file) // 调用 mmap 传进来的 fd ,要映射的文件filedup(p->vmas[i].file); // 文件引用次数加一,因为现在子进程也在使用这个文件}}release(&np->lock);return pid;
}

异常处理 uvmunmap: not mapped

mmap 的核心代码已经写完了,但是,当我们执行 mmaptest 时,会遇到 “panic: uvmunmap: not mapped” 错误。

这个错误是 解除内存映射 uvmunmap() 函数抛出来的,因为 mmap 使用了 **懒加载(lazy allocation)**策略:

  1. sys_mmap() 函数中,vma 只记录了入参,没有实际分配物理内存。
  2. usertrap() 中,首次访问这些页面时,才通过 缺页中断 分配物理内存。

所以在 uvmunmap() 时,可能存在一些页面从未被访问过,那它们的 页表项 就是无效的(PTE_V = 0),在本实验里面这种情况是正常的,不应该触发 panic,注释掉抛错代码即可。

void
uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free)
{...for(a = va; a < va + npages*PGSIZE; a += PGSIZE){if((pte = walk(pagetable, a, 0)) == 0)panic("uvmunmap: walk");if((*pte & PTE_V) == 0)continue; // 和懒加载实验一样,可能存在申请了内存但没使用,页表项可能是无效的// panic("uvmunmap: not mapped");...}
}

异常处理: page not present

继续执行 mmaptest 测试程序,发现虽然 mmap_test 测试用例可以正常通过,但 fork_test 会出现 “panic: uvmcopy: page not present” 错误。

在这里插入图片描述

这是因为在 fork() 过程中, uvmcopy() 函数尝试 复制 爸爸进程 的所有页面到 儿子进程 时,因为 mmap() 的 懒加载 机制,有些 已映射的页面 可能还没有 实际分配物理内存。

解决方案是,注释掉 uvmcopy() 函数抛出的 panic 错误即可

int
uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
{...for(i = 0; i < sz; i += PGSIZE){if((pte = walk(old, i, 0)) == 0)panic("uvmcopy: pte should exist");if((*pte & PTE_V) == 0)continue; // 复制的时候也可能存在申请了内存但没使用的情况,忽略异常
//       panic("uvmcopy: page not present");pa = PTE2PA(*pte);...}}return 0;err:uvmunmap(new, 0, i / PGSIZE, 1);return -1;
}

最后一次运行 mmaptest 测试程序

在这里插入图片描述

mmap_test 和 fork_test 都可以正常通过。

再执行 usertests

在这里插入图片描述

测试通过,完整代码在:https://github.com/yibaoshan/xv6-labs-2020/commits/mmap

参考资料

  • CS自学指南:https://csdiy.wiki/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/MIT6.S081/
  • 「实验记录」MIT 6.S081 Lab10 mmap:https://zhuanlan.zhihu.com/p/610226018

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于BenchmarkSQL的OceanBase数据库tpcc性能测试

基于BenchmarkSQL的OceanBase数据库tpcc性能测试 安装BenchmarkSQL及其依赖安装软件依赖编译BenchmarkSQLBenchmarkSQL props文件配置数据库和测试表配置BenchmarkSQL压测装载测试数据TPC-C压测(固定事务数量)TPC-C压测(固定时长)生成测试报告重复测试流程梳理安装Benchmar…

WinForm真入门(17)——NumericUpDown控件详解

一、基本概念‌ NumericUpDown 是 Windows 窗体中用于数值输入的控件&#xff0c;由文本框和上下调节按钮组成。用户可通过以下方式调整数值&#xff1a; 点击调节按钮增减数值键盘直接输入使用方向键调整 适用于需要限制数值范围或精确控制的场景&#xff08;如年龄、参数配…

汽车自动驾驶介绍

0 Preface/Foreword 1 介绍 1.1 FSD FSD: Full Self-Driving&#xff0c;完全自动驾驶 &#xff08;Tesla&#xff09; 1.2 自动驾驶级别 L0 - L2&#xff1a;辅助驾驶L3&#xff1a;有条件自动驾驶L4/5 &#xff1a;高度/完全自动驾驶

AiCube 试用 - ADC 水位监测系统

AiCube 试用 - ADC 水位监测系统 水位检测在水资源管理、城市防洪、农业灌溉、家用电器和工业生产等多领域发挥积极建设作用。利用水位传感器&#xff0c;可以实现水资源的智能管理&#xff0c;提高生产效率。 本文介绍了擎天柱开发板利用 AiCube 工具快速创建 I/O 电压读取&…

秒杀压测计划 + Kafka 分区设计参考

文章目录 前言&#x1f680; 秒杀压测计划&#xff08;TPS预估 测试流程&#xff09;1. 目标设定2. 压测工具推荐3. 压测命令示例&#xff08;ab版&#xff09;4. 测试关注指标 &#x1f4e6; Kafka Topic 分区设计参考表1. 单 Topic 设计2. 分区路由规则设计&#xff08;Part…

memcpy 使用指南 (C语言)

memcpy 是 C 语言标准库中的一个重要函数&#xff0c;用于在内存区域之间复制数据。它是 <string.h> 头文件中定义的高效内存操作函数之一。 函数原型 void *memcpy(void *dest, const void *src, size_t n); 参数说明 dest: 目标内存地址&#xff0c;数据将被复制到这…

跨境电商货物体积与泡重计算器:高效便捷的物流计算工具

跨境电商货物体积与泡重计算器&#xff1a;高效便捷的物流计算工具 工具简介 货物体积与泡重计算器是一款免费的在线工具&#xff0c;专门为物流从业者、跨境电商卖家和需要计算货物运输体积重量的用户设计。这款工具可以帮助您快速计算货物的体积和对应的空运、快递泡重&…

如何避免爬虫因Cookie过期导致登录失效

1. Cookie的作用及其过期机制 1.1 什么是Cookie&#xff1f; Cookie是服务器发送到用户浏览器并保存在本地的一小段数据&#xff0c;用于维持用户会话状态。爬虫在模拟登录后&#xff0c;通常需要携带Cookie访问后续页面。 1.2 Cookie为什么会过期&#xff1f; 会话Cookie&…

matlab simulink中理想变压激磁电流容易有直流偏置的原因分析。

simulink把线性变压器模块拉出来&#xff0c;设置没有绕线电阻的变压器&#xff0c;激磁电感和Rm都有&#xff0c;然后给一个50%占空比的方波&#xff0c;幅值正负10V&#xff0c;线路中设置一个电阻&#xff0c;模拟导线阻抗。通过示波器观察激磁电流&#xff0c;发现电阻越小…

电力系统失步解列与振荡解析

一、基本概念解析 1. 失步&#xff08;Out-of-Step&#xff09; 在电力系统中&#xff0c;失步是指并列运行的同步发电机因功率失衡导致转子间相对角度超过稳定极限&#xff0c;无法维持同步运行的状态。具体表现为&#xff1a; 当系统发生短路、负荷突变或故障切除等扰动时&…

ctfhub-RCE

关于管道操作符 windows&#xff1a; 1. “|”&#xff1a;直接执行后面的语句。 2. “||”&#xff1a;如果前面的语句执行失败&#xff0c;则执行后面的语句&#xff0c;前面的语句只能为假才行。 3. “&”&#xff1a;两条命令都执行&#xff0c;如果前面的语句为假则直…

Missashe考研日记-day28

Missashe考研日记-day28 1 专业课408 学习时间&#xff1a;2h学习内容&#xff1a; 今天先是预习了OS关于虚拟内存管理的内容&#xff0c;然后听了一部分视频课&#xff0c;明天接着学。知识点回顾&#xff1a; 1.传统存储管理方式特征&#xff1a;一次性、驻留性。2.局部性原…

01 appium环境搭建

环境搭建 Java JDKNode.jsAndroidStudio(提供sdk)appiumappium Inspector 相关安装包下载 链接&#xff1a;https://pan.xunlei.com/s/VOOf3sCttAdHvlMkc7QygsoJA1# 提取码&#xff1a;x4s5 AndroidStudio下载安装sdk AndroidStudio下载 安装运行&#xff0c;配置代理及测…

指针(4)

1.回调函数 回调函数就是通过函数指针调用的函数。 将函数的指针&#xff08;地址&#xff09;作为一个参数传递给另一个函数&#xff0c;当这个指针被调用其所指向的函数时&#xff0c;被调用的函数就是回调函数。回调函数不是由该函数的实现方直接调用&#xff0c;而是在特…

Raptor码的解码成功率matlab实现

下面是使用matlab实现关于Raptor 码解码成功率的仿真代码&#xff0c;并绘制成功率随编码符号数量变化的图形示例。代码中包含了 Raptor 码的预编码&#xff08;使用稀疏矩阵乘法模拟&#xff09;、LT 编码、解码过程&#xff0c;以及解码成功率的计算和绘图。 具体代码如下&am…

域名系统DNS

DNS介绍 DNS是一个域名系统&#xff0c;在互联网环境中为域名和IP地址相互映射的一个分布式数据库 &#xff0c; 能够使用户更方便的访问互联网&#xff0c;而不用去记住能够被机器直接读取的IP数串。类似于生活中的114服务&#xff0c;可以通过人名找到电话号码&#xff0c;也…

Spark Streaming核心编程总结(四)

一、有状态转化操作&#xff1a;UpdateStateByKey 概念与作用 UpdateStateByKey 用于在流式计算中跨批次维护状态&#xff08;如累加统计词频&#xff09;。它允许基于键值对形式的DStream&#xff0c;通过自定义状态更新函数&#xff0c;将历史状态与新数据结合&#xff0c;生…

Dijkstra 算法代码步骤[leetcode.743网络延迟时间]

有 n 个网络节点&#xff0c;标记为 1 到 n。 给你一个列表 times&#xff0c;表示信号经过 有向 边的传递时间。 times[i] (ui, vi, wi)&#xff0c;其中 ui 是源节点&#xff0c;vi 是目标节点&#xff0c; wi 是一个信号从源节点传递到目标节点的时间。 现在&#xff0c;…

【java】lambda表达式总结

目录 一、面向对象的处理方法 二、函数式编程的处理方法 先使用匿名内部类&#xff1a; lambda改造&#xff1a; lambda改造规则 示例&#xff1a; 三、补充&#xff1a;函数式接口 大家好&#xff0c;我是jstart千语。今天总结一下lambda表达式。lambda表达式在后面的s…

AtCoder Beginner Contest 242 G - Range Pairing Query (莫队)

每周五篇博客&#xff1a;&#xff08;5/5&#xff09; 我做到了&#xff01; https://atcoder.jp/contests/abc242/tasks/abc242_g 这题主要是想给大家提供一份莫队的板子&#xff0c;很多莫队题基本上填空就差不多了&#xff08; 板子 void solve() {int n;std::cin >…