当前位置: 首页 > news >正文

6.S081的Lab学习——Lab5: xv6 lazy page allocation

文章目录

  • 前言
  • 一、Eliminate allocation from sbrk() (easy)
    • 解析:
  • 二、Lazy allocation (moderate)
    • 解析:
  • 三、Lazytests and Usertests (moderate)
    • 解析:
  • 总结


前言

一个本硕双非的小菜鸡,备战24年秋招。打算尝试6.S081,将它的Lab逐一实现,并记录期间心酸历程。
代码下载

官方网站:6.S081官方网站

安装方式:
通过 APT 安装 (Debian/Ubuntu)
确保你的 debian 版本运行的是 “bullseye” 或 “sid”(在 ubuntu 上,这可以通过运行 cat /etc/debian_version 来检查),然后运行:

sudo apt-get install git build-essential gdb-multiarch qemu-system-misc gcc-riscv64-linux-gnu binutils-riscv64-linux-gnu 

(“buster”上的 QEMU 版本太旧了,所以你必须单独获取。

qemu-system-misc 修复
此时此刻,似乎软件包 qemu-system-misc 收到了一个更新,该更新破坏了它与我们内核的兼容性。如果运行 make qemu 并且脚本在 qemu-system-riscv64 -machine virt -bios none -kernel/kernel -m 128M -smp 3 -nographic -drive file=fs.img,if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 之后出现挂起

则需要卸载该软件包并安装旧版本:

  $ sudo apt-get remove qemu-system-misc$ sudo apt-get install qemu-system-misc=1:4.2-3ubuntu6

在 Arch 上安装

sudo pacman -S riscv64-linux-gnu-binutils riscv64-linux-gnu-gcc riscv64-linux-gnu-gdb qemu-arch-extra

测试您的安装
若要测试安装,应能够检查以下内容:

$ riscv64-unknown-elf-gcc --version
riscv64-unknown-elf-gcc (GCC) 10.1.0
...$ qemu-system-riscv64 --version
QEMU emulator version 5.1.0

您还应该能够编译并运行 xv6: 要退出 qemu,请键入:Ctrl-a x。

# in the xv6 directory
$ make qemu
# ... lots of output ...
init: starting sh
$

一、Eliminate allocation from sbrk() (easy)

你的首项任务是删除sbrk(n)系统调用中的页面分配代码(位于sysproc.c中的函数sys_sbrk())。sbrk(n)系统调用将进程的内存大小增加n个字节,然后返回新分配区域的开始部分(即旧的大小)。新的sbrk(n)应该只将进程的大小(myproc()->sz)增加n,然后返回旧的大小。它不应该分配内存——因此您应该删除对growproc()的调用(但是您仍然需要增加进程的大小!)。

试着猜猜这个修改的结果是什么:将会破坏什么?

进行此修改,启动xv6,并在shell中键入echo hi。你应该看到这样的输出:

init: starting sh
$ echo hi
usertrap(): unexpected scause 0x000000000000000f pid=3sepc=0x0000000000001258 stval=0x0000000000004008
va=0x0000000000004000 pte=0x0000000000000000
panic: uvmunmap: not mapped

“usertrap(): …”这条消息来自trap.c中的用户陷阱处理程序;它捕获了一个不知道如何处理的异常。请确保您了解发生此页面错误的原因。“stval=0x0…04008”表示导致页面错误的虚拟地址是0x4008。

切换分支执行操作

git stash
git fetch
git checkout lazy
make clean

解析:

非常简单,删除sbrk(n)系统调用中的页面分配代码,并且新的sbrk(n)应该只将进程的大小(myproc()->sz)增加n,然后返回旧的大小。按照说得来就行。


uint64
sys_sbrk(void)
{int addr;int n;if(argint(0, &n) < 0)return -1;addr = myproc()->sz;/*if(growproc(n) < 0)return -1;*/myproc()->sz += n;return addr;
}

输出结果正常:
在这里插入图片描述
对照下面的图,显示是usertrap里面的缺页中断
在这里插入图片描述

二、Lazy allocation (moderate)

修改trap.c中的代码以响应来自用户空间的页面错误,方法是新分配一个物理页面并映射到发生错误的地址,然后返回到用户空间,让进程继续执行。您应该在生成“usertrap(): …”消息的printf调用之前添加代码。你可以修改任何其他xv6内核代码,以使echo hi正常工作。

提示:

  1. 你可以在usertrap()中查看r_scause()的返回值是否为13或15来判断该错误是否为页面错误
  2. stval寄存器中保存了造成页面错误的虚拟地址,你可以通过r_stval()读取
  3. 参考vm.c中的uvmalloc()中的代码,那是一个sbrk()通过growproc()调用的函数。你将需要对kalloc()和mappages()进行调用
  4. 使用PGROUNDDOWN(va)将出错的虚拟地址向下舍入到页面边界
  5. 当前uvmunmap()会导致系统panic崩溃;请修改程序保证正常运行
  6. 如果内核崩溃,请在kernel/kernel.asm中查看sepc
  7. 使用pgtbl lab的vmprint函数打印页表的内容
  8. 如果您看到错误“incomplete type proc”,请include“spinlock.h”然后是“proc.h”。

如果一切正常,你的lazy allocation应该使echo hi正常运行。您应该至少有一个页面错误(因为延迟分配),也许有两个。

解析:

首先按照提示,要修改usertrap()(kernel/trap.c)函数,查看r_scause()的返回值是否为13或15来判断该错误是否为页面错误。

根据提示,先查看uvmalloc()中的代码。

// Allocate PTEs and physical memory to grow process from oldsz to
// newsz, which need not be page aligned.  Returns new size or 0 on error.
uint64
uvmalloc(pagetable_t pagetable, uint64 oldsz, uint64 newsz)
{char *mem;uint64 a;if(newsz < oldsz)return oldsz;//它将oldsz向上舍入到最近的页面边界。这是因为内存通常按页面大小进行分配oldsz = PGROUNDUP(oldsz);for(a = oldsz; a < newsz; a += PGSIZE){//使用kalloc函数分配一个页面大小的物理内存,并返回其地址mem = kalloc();if(mem == 0){uvmdealloc(pagetable, a, oldsz);return 0;}memset(mem, 0, PGSIZE);//使用mappages函数将物理内存页面映射到虚拟地址空间中if(mappages(pagetable, a, PGSIZE, (uint64)mem, PTE_W|PTE_X|PTE_R|PTE_U) != 0){kfree(mem);uvmdealloc(pagetable, a, oldsz);return 0;}}return newsz;
}

先判断发生错误的虚拟地址(r_stval()读取)是否位于栈空间之上,进程大小(虚拟地址从0开始,进程大小表征了进程的最高虚拟地址)之下,然后分配物理内存并添加映射

......
else if (r_scause() == 13 || r_scause() == 15) {uint64 virtual_address = r_stval();char *mem;if(PGROUNDUP(p->trapframe->sp) > virtual_address && virtual_address >= p->sz)return;mem = kalloc();virtual_address = PGROUNDDOWN(virtual_address);if(mem == 0){return;}memset(mem, 0, PGSIZE);if(mappages(p->pagetable, virtual_address, PGSIZE, (uint64)mem, PTE_W|PTE_X|PTE_R|PTE_U) != 0) {kfree(mem);p->killed = 1;}} 
......

修改uvmunmap()(kernel/vm.c),之所以修改这部分代码是因为lazy allocation中首先并未实际分配内存,所以当解除映射关系的时候对于这部分内存要略过,而不是使系统崩溃,这部分在课程视频中已经解答。

......if((*pte & PTE_V) == 0)//panic("uvmunmap: not mapped");continue;
......

最后也是成功输出
在这里插入图片描述

三、Lazytests and Usertests (moderate)

我们为您提供了lazytests,这是一个xv6用户程序,它测试一些可能会给您的惰性内存分配器带来压力的特定情况。修改内核代码,使所有lazytests和usertests都通过。

  1. 处理sbrk()参数为负的情况。
  2. 如果某个进程在高于sbrk()分配的任何虚拟内存地址上出现页错误,则终止该进程。
  3. 在fork()中正确处理父到子内存拷贝。
  4. 处理这种情形:进程从sbrk()向系统调用(如read或write)传递有效地址,但尚未分配该地址的内存。
  5. 正确处理内存不足:如果在页面错误处理程序中执行kalloc()失败,则终止当前进程。
  6. 处理用户栈下面的无效页面上发生的错误。

如果内核通过lazytests和usertests,那么您的解决方案是可以接受的:

$ lazytests
lazytests starting
running test lazy alloc
test lazy alloc: OK
running test lazy unmap...
usertrap(): ...
test lazy unmap: OK
running test out of memory
usertrap(): ...
test out of memory: OK
ALL TESTS PASSED
$ usertests
...
ALL TESTS PASSED
$

解析:

处理sbrk()参数为负数的情况,参考之前sbrk()调用的growproc()程序,如果为负数,就调用uvmdealloc()函数,但需要限制缩减后的内存空间不能小于0

// Grow or shrink user memory by n bytes.
// Return 0 on success, -1 on failure.
int
growproc(int n)
{//定义了一个无符号整数 sz 来存储进程的当前大小uint sz;//并声明了一个指向 proc 结构体的指针 p用于获取当前进程的指针struct proc *p = myproc();//从 p 指向的 proc 结构体中获取当前进程的大小,并将其存储在 sz 中sz = p->sz;//如果 n 大于 0,则尝试增加内存。使用 uvmalloc 函数来增加从 sz 到 sz + n 的内存。如果 uvmalloc 返回 0,表示内存分配失败,函数返回 -1if(n > 0){if((sz = uvmalloc(p->pagetable, sz, sz + n)) == 0) {return -1;}} else if(n < 0){//如果 n 小于 0,则尝试减少内存。使用 uvmdealloc 函数来释放从 sz 到 sz + n 的内存。注意这里即使 uvmdealloc 可能失败(尽管函数签名没有显示返回值或错误处理),该函数也不会检查返回值或返回错误sz = uvmdealloc(p->pagetable, sz, sz + n);}p->sz = sz;return 0;
}

修改之后的sys_sbrk:

uint64
sys_sbrk(void)
{int addr;int n;if(argint(0, &n) < 0)return -1;addr = myproc()->sz;uint sz;struct proc *p = myproc();sz = p->sz;if(n > 0){p->sz += n;} else if(sz + (n > 0){sz = uvmdealloc(p->pagetable, sz, sz + n);p->sz = sz;} else {return -1;}return addr;
}

正确处理fork的内存拷贝:fork调用了uvmcopy进行内存拷贝,现在让其直接进入下一页,而不是报错,所以修改uvmcopy如下

    if((pte = walk(old, i, 0)) == 0)//panic("uvmcopy: pte should exist");continue;if((*pte & PTE_V) == 0)//panic("uvmcopy: page not present");continue;

还需要继续修改uvmunmap,同样的问题,同样的方式

    if((pte = walk(pagetable, a, 0)) == 0)//panic("uvmunmap: walk");continue;if((*pte & PTE_V) == 0)//panic("uvmunmap: not mapped");continue;

以上部分搞完之后我就去测试了,然后就g了。。。
看了大佬们的讲解,应该是write错了。
因为我们现在采用的是懒分配,写的时候由于此时传入的地址还未实际分配(因为还没映射,只分配了虚拟地址),就不能走到上文usertrap中判断scause是13或15后进行内存分配的代码。
解决办法我看到了两种:
一种是在walkaddr寻找物理地址时,如果发现虚拟地址没有映射,就给它实际分配一下。
另外一种是系统通过argaddr函数从寄存器中读取地址时添加物理内存分配的代码。
都可以解决,也没啥区别。

我采用在walkaddr函数中更改。代码总体跟之前的uvmalloc没差太多,毕竟是一个功能的。

uint64
sys_sigalarm(void) {struct proc *p;p = myproc();argint(0, &p->alarm_interval);argaddr(1, &p->handler);p->ticks_count = 0;return 0;
}uint64
sys_sigreturn(void) {return 0;
}

最后在trap.c中的usertrap()中处理.
增加用户报警函数的地址可能是0的判断。当进程的报警间隔期满时(也就是两次报警间的滴答计数达到了报警间隔),用户进程执行处理程序函数。

  // give up the CPU if this is a timer interrupt.if(which_dev == 2) {if (p->alarm_interval != 0) {p->ticks_count++;if (p->ticks_count == p->alarm_interval) {p->ticks_count = 0;p->trapframe->epc = p->handler;}}}yield();

没有什么问题
在这里插入图片描述

题目二中要解决的主要问题是寄存器保存恢复和防止重复执行的问题。
要在usertrap中再次保存用户寄存器,当handler调用sigreturn时将其恢复,并且要防止在handler执行过程中重复调用。
再在struct proc中新增两个字段

int is_alarming;                    // 是否正在执行告警处理函数
struct trapframe* alarm_trapframe;  // 告警陷阱帧

同时记得在allocproc中将它们初始化为0,并在freeproc中也设为0

  // 增加不是修改// 进程初始化,这里主要是防止申请不成功,那就学着已有的代码对进程进行销毁if((p->alarm_trapframe = (struct trapframe *)kalloc()) == 0){freeproc(p);release(&p->lock);return 0;}p->pid = allocpid();p->ticks_count = 0;p->alarm_interval = 0;p->handler = 0;p->is_alarming = 0;//增加不是修改if(p->alarm_trapframe)kfree((void*)p->alarm_trapframe);p->trapframe = 0;p->is_alarming = 0;

更改usertrap函数,保存进程陷阱帧p->trapframe到p->alarm_trapframe

  // give up the CPU if this is a timer interrupt.if(which_dev == 2) {if (p->alarm_interval != 0) {p->ticks_count++;if (p->ticks_count >= p->alarm_interval && p->is_alarming == 0) {memmove(p->alarm_trapframe, p->trapframe, sizeof(struct trapframe));p->is_alarming == 1;p->ticks_count = 0;p->trapframe->epc = (uint64)p->handler;}}}yield();usertrapret();
}

最后修改sys_sigreturn函数,恢复陷阱帧。

uint64
sys_sigreturn(void) {struct proc *p;p = myproc();memmove(p->trapframe, p->alarm_trapframe, sizeof(struct trapframe));p->is_alarming = 0;return 0;
}

在这里插入图片描述

总结

这个实验成功的带着我们实现了回溯,中断的处理过程并如何恢复初始状态。从系统调用中梳理中断的全流程。收获颇丰。

相关文章:

6.S081的Lab学习——Lab5: xv6 lazy page allocation

文章目录 前言一、Eliminate allocation from sbrk() (easy)解析&#xff1a; 二、Lazy allocation (moderate)解析&#xff1a; 三、Lazytests and Usertests (moderate)解析&#xff1a; 总结 前言 一个本硕双非的小菜鸡&#xff0c;备战24年秋招。打算尝试6.S081&#xff0…...

在WHM中如何调整max_post_size参数大小

今日我们在搭建新网站时需要调整一下PHP参数max_post_size 的大小&#xff0c;我们公司使用的Hostease的美国独立服务器产品默认5个IP地址&#xff0c;也购买了cPanel面板&#xff0c;因此联系Hostease的技术支持&#xff0c;寻求帮助了解到如何在WHM中调整PHP参数&#xff0c;…...

智能监控技术助力山林生态养鸡:打造智慧安全的养殖新模式

随着现代科技的不断发展&#xff0c;智能化、自动化的养殖方式逐渐受到广大养殖户的青睐。特别是在山林生态养鸡领域&#xff0c;智能化监控方案的引入不仅提高了养殖效率&#xff0c;更有助于保障鸡只的健康与安全。视频监控系统EasyCVR视频汇聚/安防监控视频管理平台在山林生…...

那些不起眼但很好玩的API合辑

那些不起眼但很好玩的API&#xff0c;为我们带来了许多出人意料的乐趣和惊喜。这些API可能看起来并不起眼&#xff0c;但它们却蕴含着无限的创意和趣味性。它们可以是一些小游戏API&#xff0c;让我们可以在闲暇时刻尽情娱乐&#xff1b;也可以是一些奇特的音乐API&#xff0c;…...

java —— 克隆对象、枚举

一、克隆对象 &#xff08;一&#xff09;在基本数据类型中&#xff0c;直接将对象 A 的值赋给对象 B&#xff0c;当更改对象 B 的时候&#xff0c;对象 A 的值保持不变。例如&#xff1a; public static void main(String[] args) {int a5;int ba; //将…...

STM32-GPIO八种输入输出模式

图片取自 江协科技 STM32入门教程-2023版 细致讲解 中文字幕 p5 【STM32入门教程-2023版 细致讲解 中文字幕】 https://www.bilibili.com/video/BV1th411z7sn/?p5&share_sourcecopy_web&vd_source327265f5c70f26411a53a9226af0b35c 目录 ​编辑 一.STM32的四种输…...

windows镜像虚拟机创建共享文件夹详细步骤 -- 和本地电脑传输文件

第一步&#xff1a;关闭客户机 第二步&#xff1a;右击“虚拟机名称”或菜单栏的“虚拟机”–>“设置” 网络适配器选择NAT或者其他的都可以 来到“选项”&#xff0c;启用共享文件夹&#xff0c;具体如下图&#xff1a;点击添加&#xff0c;添加主机文件夹。然后确定 第三步…...

通关!游戏设计之道Day18

过场动画&#xff0c;或者说根本没人看的东西 过场动画是一系列的动画或实时的动作序列&#xff0c;用来推进剧情制造大场面&#xff0c;烘托气氛&#xff0c;展示对话和角色成长&#xff0c;以及显现在某些情况下被玩家忽略的相关线索。 过场动画是一把双刃剑&#xff0c;一方…...

写Python时不用import,你会遭遇什么

from *** import *** 想必你已经再熟悉不过这样的python语法。 当你的 python 代码需要获取外部的一些功能&#xff08;一些已经造好的轮子&#xff09;&#xff0c;你就需要使用到 import 这个声明关键字。import可以协助导入其他 module 。&#xff08;类似 C 预约的 inclu…...

java网络:过滤器修改请求头

目录 一、gateway的全局过滤器 二、web的OncePerRequestFilter以及常见过滤器Filter 三、过滤器排序 一、gateway的全局过滤器 Component Slf4j public class GatewayAuthFilter implements GlobalFilter, Ordered {Overridepublic Mono<Void> filter(ServerWebExchan…...

yolov10 快速使用及训练

参考: https://docs.ultralytics.com/models/yolov10/ ultralytics其实大多数系列都能加载使用: 官方: https://github.com/THU-MIG/yolov10.git 代码参考: https://colab.research.google.com/github/roboflow-ai/notebooks/blob/main/notebooks/train-yolov10-object-…...

CSS变量 -- var() 使用教程

目录 1 CSS变量的基本使用1.1 变量定义1.1 变量使用1.3 全局/局部变量 2 CSS变量的参数3 CSS变量的拼接和计算3.1 拼接3.2 计算 4 JS 修改 CSS变量 CSS 自定义属性&#xff08;有时候也被称作CSS 变量或者级联变量&#xff09;&#xff0c;它的值可以在整个文档中重复使用。 复…...

python基础-数据结构-leetcode刷题必看-queue---队列-python的底层构建

文章目录 队列双端队列 deque底层存储deque接口1. __init__(self, iterable: Iterable[_T], maxlen: int | None None) -> None2. append(self, __x: _T) -> None3. appendleft(self, __x: _T) -> None4. copy(self) -> Self5. count(self, __x: _T) -> int6. …...

深入理解Spring Security:保护你的Web应用程序

深入理解Spring Security:保护你的Web应用程序 这听起来像是一部詹姆斯邦德电影,邦德试图进入坏家伙的藏身之处。坏家伙设置了一系列超级安全措施,有多层次的安全防御。邦德克服了其中一层,进入了隐藏处,但又遇到了下一个陷阱。他战胜了一个又一个陷阱,最终克服了所有障…...

【车载开发系列】Vector工具链的安装

【车载开发系列】Vector工具链的安装 【车载开发系列】Vector工具链的安装 【车载开发系列】Vector工具链的安装一. VectorDriver二. DaVinci_Developer三. DaVinci Configurator 一. VectorDriver Vector Driver Setup是Vector产品链中重要的驱动软件,所有的硬件设备进行连接…...

Windows系统部署YOLOv5 v6.1版本的训练与推理环境保姆级教程

文章目录 一 概述二 依赖环境(prerequisites)2.1 硬件环境2.2 软件环境 三 环境安装3.1 创建并激活虚拟环境3.2 安装Pytorch与torchvision3.3 校验Pytorch安装3.4 下载 YOLOv5 v6.1 源码3.5 安装 YOLOv5 依赖3.6 下载预训练模型3.7 安装其他依赖3.8 测试环境安装3.9 测试训练流…...

[RK3588-Android12] 关于EDP屏外设为Panel,不支持HPD的配置

问题描述 直接附上dts配置&#xff0c;也可自行查看先关文档RKDocs\common\display\Rockchip_RK3588_User_Guide_eDP_CN.pdf 解决方案&#xff1a; // EDP屏参数panel-edp {compatible "simple-panel";// 屏en脚 自行根据原理图配置enable-gpios <&gpioX R…...

142.栈和队列:用栈实现队列(力扣)

题目描述 代码解决 class MyQueue { public:stack<int> stIn; // 输入栈&#xff0c;用于push操作stack<int> stOut; // 输出栈&#xff0c;用于pop和peek操作MyQueue() {}void push(int x) {stIn.push(x); // 将元素压入输入栈}int pop() {// 如果输出栈为空&…...

乡村振兴的乡村产业创新发展:培育乡村新兴产业,打造乡村产业新名片,促进乡村经济多元化发展

目录 一、引言 二、乡村产业创新发展的必要性 &#xff08;一&#xff09;适应新时代发展要求 &#xff08;二&#xff09;满足消费升级需求 &#xff08;三&#xff09;促进农民增收致富 三、培育乡村新兴产业策略 &#xff08;一&#xff09;加强科技创新引领 &#…...

数据库|基于T-SQL创建数据库

哈喽&#xff0c;你好啊&#xff0c;我是雷工&#xff01; SQL Server用于操作数据库的编程语言为Transaction-SQL,简称T-SQL。 本节学习基于T-SQL创建数据库。以下为学习笔记。 01 打开新建查询 首先连接上数据库&#xff0c;点击【新建查询】打开新建查询窗口&#xff0c; …...

黑马Mybatis

Mybatis 表现层&#xff1a;页面展示 业务层&#xff1a;逻辑处理 持久层&#xff1a;持久数据化保存 在这里插入图片描述 Mybatis快速入门 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/6501c2109c4442118ceb6014725e48e4.png //logback.xml <?xml ver…...

阿里云ACP云计算备考笔记 (5)——弹性伸缩

目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...

Redis数据倾斜问题解决

Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中&#xff0c;部分节点存储的数据量或访问量远高于其他节点&#xff0c;导致这些节点负载过高&#xff0c;影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...

华硕a豆14 Air香氛版,美学与科技的馨香融合

在快节奏的现代生活中&#xff0c;我们渴望一个能激发创想、愉悦感官的工作与生活伙伴&#xff0c;它不仅是冰冷的科技工具&#xff0c;更能触动我们内心深处的细腻情感。正是在这样的期许下&#xff0c;华硕a豆14 Air香氛版翩然而至&#xff0c;它以一种前所未有的方式&#x…...

PAN/FPN

import torch import torch.nn as nn import torch.nn.functional as F import mathclass LowResQueryHighResKVAttention(nn.Module):"""方案 1: 低分辨率特征 (Query) 查询高分辨率特征 (Key, Value).输出分辨率与低分辨率输入相同。"""def __…...

现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?

现有的 Redis 分布式锁库&#xff08;如 Redisson&#xff09;相比于开发者自己基于 Redis 命令&#xff08;如 SETNX, EXPIRE, DEL&#xff09;手动实现分布式锁&#xff0c;提供了巨大的便利性和健壮性。主要体现在以下几个方面&#xff1a; 原子性保证 (Atomicity)&#xff…...

push [特殊字符] present

push &#x1f19a; present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中&#xff0c;push 和 present 是两种不同的视图控制器切换方式&#xff0c;它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...

Linux部署私有文件管理系统MinIO

最近需要用到一个文件管理服务&#xff0c;但是又不想花钱&#xff0c;所以就想着自己搭建一个&#xff0c;刚好我们用的一个开源框架已经集成了MinIO&#xff0c;所以就选了这个 我这边对文件服务性能要求不是太高&#xff0c;单机版就可以 安装非常简单&#xff0c;几个命令就…...

Vue 模板语句的数据来源

&#x1f9e9; Vue 模板语句的数据来源&#xff1a;全方位解析 Vue 模板&#xff08;<template> 部分&#xff09;中的表达式、指令绑定&#xff08;如 v-bind, v-on&#xff09;和插值&#xff08;{{ }}&#xff09;都在一个特定的作用域内求值。这个作用域由当前 组件…...

stm32wle5 lpuart DMA数据不接收

配置波特率9600时&#xff0c;需要使用外部低速晶振...