linux之进程地址空间

文章目录

  • 1.进程地址空间回顾
    • 1.1进程地址空间划分
    • 1.2验证进程地址空间划分
      • 1.简单划分
      • 2.完整划分
  • 2.初探进程地址空间
    • 2.1初看现象
    • 2.2Makefile的简便写法
  • 3.进程地址空间详解
    • 3.1地址空间是什么?
    • 3.2地址空间的设计/由来
    • 3.3空间区域划分
    • 3.4如何理解地址空间?
    • 3.5解释3.2的🐂🐎现象和fork()函数的返回值
    • 3.6linux命令行的指令
  • 4.可执行程序运行的底层
    • 4.1linux下查看反汇编
    • 4.2了解底层
  • 5.为什么要大费周折设计地址空间?
    • 5.1[地址空间+页表]对进程的非法访问进行有效拦截==>有效地保护了物理内存
    • 5.2将内存管理模块和进程管理模块解耦合 提升内存利用率
    • 5.35地址空间和页表实现了进程的独立性
  • 6.对挂起状态的理解
    • 6.1上篇博客的初识
    • 6.2通过程序运行理解挂起状态

在这里插入图片描述

1.进程地址空间回顾

1.1进程地址空间划分

在这里插入图片描述

1.2验证进程地址空间划分

1.简单划分

在这里插入图片描述在这里插入图片描述

  1. 一个C/C++程序 编译后形成的可执行程序 是一个二进制文件 在Linux下 ./test 是程序运行之后打印的 本质理解: 程序运行后执行的cout/printf 实际上是进程在输出数据
  2. 堆和栈相对而生

2.完整划分

对程序的理解

1. int a = 10;
把字面常量10放到局部变量a
2. 单纯的字面常量放在代码李可以编译通过如:
"hello linux";
100;
'a';

验证程序地址空间划分

在这里插入图片描述
在这里插入图片描述

在堆区申请了一块空间 1. 释放时为什么只用将空间首地址传给free() 2. 差值为什么多了10个?

堆区申请x个字节 实际上c标准库给当前程序申请的比x多 多出来的空间 用来存储此次申请的属性信息 称作"Cokkie" 饼干数据 用来记录 什么时间申请的 申请的空间多大 等 上图中堆区数据字节差值为20也验证了这句话

总结:

  1. 32位下,一个进程的地址空间,取值范围是0x0000 0000 ~ 0xFFFF FFFF
    [0,3GB]: 用户空间
    [3GB,4GB]:内核空间
  2. 上面的结论,默认只在linux有效 [在windows下会跑出不一样的结果 windows注重地址安全 增加了一些自己的设计

2.初探进程地址空间

2.1初看现象

来看一个比较牛马的场景

#include <stdio.h>
#include <unistd.h>

int g_val = 100;

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        int cnt = 0;
        //child
        while(1)
        {

            printf("I am child,  pid: %d, ppid: %d, g_val: %d, &g_val: %p\n",\
                    getpid(), getppid(), g_val, &g_val);
            sleep(1);
            cnt++;
            if(cnt == 5)
            {
                g_val = 200;
                printf("        child chage g_val 100 -> 200 success\n");
            }
        }
    }
    else 
    {
        //father
        while(1)
        {
            printf("I am father, pid: %d, ppid: %d, g_val: %d, &g_val: %p\n",\
                    getpid(), getppid(), g_val, &g_val);
            sleep(1);
        }
        
    }
}

同时访问同一个地址出现了不同的值[在3.5解释]

在这里插入图片描述

由此得出结论 这里的地址绝对不是物理内存的地址!那他是什么🐂🐎?

  1. 这个🐂🐎是是虚拟地址/线性地址
  2. 之前学到的编程语言中的"地址"概念不是物理地址而是虚拟地址
  3. OS不让用户直接访问物理地址 — 新手上路会犯错破坏内存 通过虚拟地址来保护

拓展知识

磁盘/网卡/显卡等外设也有寄存器 外设保存数据的寄存器可以称为端口/串口[硬件级别]
在这里插入图片描述

了解虚拟地址

在这里插入图片描述

逻辑地址

在这里插入图片描述

线性地址

在这里插入图片描述
在这里插入图片描述

2.2Makefile的简便写法

Makefile格式: target : prerequisties 目标文件: 先决条件

hello:hello.c hello.c1 hello.c2
	gcc -o $@ $^

$@: 依赖方法对应的依赖关系中的目标文件即hello
$^: 所有的依赖文件 即hello.c hello.c1 hello.c2这一堆文件
$< 第一个依赖文件 即hello.c
$? 比目标还要新的依赖文件列表

3.进程地址空间详解

3.1地址空间是什么?

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
富翁有10亿 底下有三个私生子 三个私生子互不知道对方的存在 富翁对他们三人分别承诺 他死后10亿就是他的 三人分别相信了 在富翁还存活时 三人找他要钱用 富翁也会给 但是如果要得太多 比如一次要了一亿 富翁就不给了 因为没有正当用途等原因

富翁 – OS 私生子 – 进程 老爹画的饼 – 地址空间 要注意的是:

内核中的地址空间 当未来和某一进程联系起来时 它实际上也是一种数据结构 因为他要对进程进行描述组织 即 富翁/OS 要对他画的饼/地址空间 进行组织 否则饼/进程太多了可能会露馅

3.2地址空间的设计/由来

我们首先要了解 计算机早期的设计是直接访问物理内存的 后来才引入了 线性地址/虚拟地址 显而易见引入虚拟地址是为了让计算机更好的工作 无论是安全问题或者是效率问题 看下面这种情况 就可以了解直接访问物理内存是极其危险的!
在这里插入图片描述

  1. 假设进程1中有一个野指针int* p = 乱码 进程1要对指针p进行访问/修改/删除操作 而这个指针恰好指向了进程2/3所在的内存 那么此时就芭比Q了[内存本身可以随时被读写]
  2. 进程2是一个合法的正在运行的程序 一个hacker自己写了一个程序 在物理内存上运行 若此时进程2执行了让用户输入密码的操作 那么进程1可以通过在进程2中的相同地址处设一个指针接收 此时 hacker就获得了密码 这不彻底完蛋
  3. 进程1/2/3并不是一个个紧挨着的 此时就会有内存碎片问题 假设现在进程4来了 但是没有足够的地方 而实际上所有的碎片加起来是有地方的 那么OS就不得不把进程1/2/3再一个个挨着放 — 效率低下

上述这些问题的原因: 直接访问的是物理内存 对应的就是物理地址 现代计算机怎么设计的呢?
在这里插入图片描述
在这里插入图片描述

  1. 通过某种映射机制不直接访问物理内存
  2. 当虚拟地址是一个非法地址 禁止映射去访问物理内存

3.3空间区域划分

在这里插入图片描述

源码

在这里插入图片描述

3.4如何理解地址空间?

地址空间是一种内核数据结构 它里面要有各个区域的划分
在这里插入图片描述

  1. 每一个进程的页表映射的是物理内存的不同区域 这样保证了进程之间不相互干扰 保证进程之间的独立性
  2. 每一个进程都有自己的地址空间和页表

3.5解释3.2的🐂🐎现象和fork()函数的返回值

在这里插入图片描述

在这里插入图片描述

  1. 子进程的PCB大部分和父进程相同 少部分自己修改独有 val在父子进程的虚拟地址相同 映射机制相同
  2. 父子进程中的变量的虚拟地址相同 如果不发生拷贝 子进程的变量和父进程的变量在物理内存中是同一块空间
  3. 如果发生拷贝 此时在内存中当场为val开辟一块内存 用来存子进程新的值 这个过程叫写时拷贝 写时拷贝的优势在于 如果不发生拷贝 既符合虚拟地址的设计又不浪费空间
  4. 这就是为什么相同的地址(虚拟地址)有不同的值(不同的物理地址对应的不同值)
  5. 地址相同: 打印的地址是虚拟地址 值不同: 映射机制被改 子进程的值指向了自己的变量空间

解释之前讲的fork()函数一个返回值同时保存两个不同的值的问题

在这里插入图片描述

pid_t fork()
{
   //创建子进程

   return id;
}
pid_t Id = fork();
  1. return id;之前 子进程已经被创建出来 父子进程分别return 自己代码的id值
  2. return id; 在fork()函数即将返回 执行return语句时 对Id值进行修改/写入 ===> 发生写时拷贝
  3. 父子进程在物理内存中有各自的属于自己的变量空间 在用户层用同一个变量/虚拟地址来标识

3.6linux命令行的指令

readelf的用法

readelf是一个Linux下的命令行工具,用于查看ELF格式的目标文件或可执行文件的信息。ELF(Executable and Linkable Format)是一种常见的二进制文件格式,用于在Linux系统中表示可执行文件、共享库、目标文件等。使用readelf命令可以查看这些文件的头部、节区、符号表、重定位表等信息。以下是readelf命令的一些常用选项和用法:

  1. 查看目标文件的头部信息:
readelf -h <file>
  1. 查看目标文件的节区信息:
readelf -S <file>
  1. 查看目标文件的符号表信息:
readelf -s <file>
  1. 查看目标文件的重定位表信息:
readelf -r <file>
  1. 查看目标文件的动态符号表信息:
readelf -d <file>
  1. 查看目标文件的字符串表信息:
readelf -p <section_name> <file>

例如,要查看可执行文件ls的头部信息,可以使用以下命令:

readelf -h /bin/ls

objdump的用法

objdump是一个二进制文件反汇编工具,可以用于查看二进制文件的汇编代码、符号表、重定位表等信息。在Linux下,可以使用objdump命令来进行反汇编操作。以下是一些常用的objdump命令:

  1. 查看二进制文件的汇编代码
objdump -d <binary_file>

其中,-d表示反汇编操作,<binary_file>表示要反汇编的二进制文件。

  1. 查看二进制文件的符号表
objdump -t <binary_file>

其中,-t表示查看符号表。

  1. 查看二进制文件的重定位表
objdump -r <binary_file>

其中,-r表示查看重定位表。

  1. 查看二进制文件的头部信息
objdump -x <binary_file>

其中,-x表示查看头部信息。

在这里插入图片描述
在这里插入图片描述
objdump是一个二进制文件分析工具,可以用来查看二进制文件的汇编代码、符号表、重定位表等信息。-a选项表示显示所有信息,-f选项表示显示文件头信息,-h选项表示显示节头信息。

在Linux中,可以使用以下命令来查看二进制文件的所有信息:

objdump -afh <filename>

其中,<filename>是要查看的二进制文件的文件名。执行该命令后,会输出该二进制文件的所有信息,包括文件头信息、节头信息、符号表、重定位表等。

举个例子,如果要查看可执行文件/bin/ls的所有信息,可以执行以下命令:

objdump -afh /bin/ls

4.可执行程序运行的底层

4.1linux下查看反汇编

程序编译形成可执行程序 没有加载到内存时 在程序内部实际上已经有地址 – 可执行程序编译时内部已经有地址

在这里插入图片描述
在这里插入图片描述

4.2了解底层

  1. 地址空间不仅OS内部遵守,编译器也要遵守!
  2. 编译器编译代码的时候,已经形成了各个区域: 代码区,数据区 堆区 栈区…
  3. 采用和Linux内核中一样的编址方式,给每一个变量,每一行代码都进行了编址
  4. 程序在编译的时候,每一个字段(所有的代码和数据)早已经具有了一个虚拟地址
  5. 当可执行程序加载到内存时 每行代码/变量/函数便具有了一个外部物理地址
  6. CPU读取每一条执行时 指令内部也有地址 这个地址是虚拟地址
  7. 每一个变量/函数 都有一个编译器给出的虚拟地址 虚拟地址连同代码加载到了内存中

在这里插入图片描述

  • 可执行程序运行 进入main函数 在虚拟地址(mm_struct)将0x0010到0x0019作为栈的start和end 变量a的虚拟地址为0x0010 其他亦然 将虚拟地址放在页表左侧 根据映射机制 映射一个物理地址作为变量a在物理内存的位置 物理地址放在页表右侧
  • 假设访问完函数A后访问函数B 根据函数A的虚拟地址访问物理内存上的函数A 获取函数B的虚拟地址 按照虚拟地址查找页表 获取函数B的物理地址 (页表底层和哈希表相似
  • 程序在编译的链接阶段链接动态库实际上是在代码中拷贝了库函数/调用接口的地址 依据地址去访问
  • 这样CPU每次拿到的都是虚拟地址
  • 地址空间: OS为进程设计的一种看待内存/外设的一种方案

5.为什么要大费周折设计地址空间?

5.1[地址空间+页表]对进程的非法访问进行有效拦截==>有效地保护了物理内存

  1. 对于非法的访问或映射 OS会识别并终止此进程 [代码运行后成为进程 由于代码写的不对 进程崩溃 即进程退出 实际上是OS杀死了这个不正确/不合法的进程]
  2. 地址空间和页表是OS创建并维护的 想使用地址空间和页表进行映射 要在OS的监管之下来进行访问
  3. 保护了物理内存中的所有的合法数据(各个进程,内核相关有效数据)

什么叫非法的访问/映射?

int main()
{
	char* str = "hello linux!\n";
	*str = 'H';

明显上述代码会报错 str存在于栈上 字符串存在只读常量区 不可修改 页表不仅会把虚拟地址映射为物理地址 还会有权限的检查 如果不具有写的权限 就终止 内存可以随时任意读写 地址空间和页表的存在使得它不在可以那么随意了!

5.2将内存管理模块和进程管理模块解耦合 提升内存利用率

在这里插入图片描述

地址空间 + 页表的映射 使得在物理内存中可以对未来加入内存的数据进行任意位置的加载(前提是有空间) 使得物理内存分配就和进程管理的工作分离 即内存管理模块和进程管理模块完成了解耦合

C/C++语言中父进程malloc/new空间时,本质是在虛拟地址空间申请的 优势:

  1. 代码写完形成可执行程序 这个程序可能不是马上运行 如果在写代码或者形成可执行程序时就为其申请了空间 那么程序不运行它不用这个空间 别的程序也没法用 这是一种极大的浪费 且 会造成效率大大降低

  2. 有地址空间的存在,上层申请空间是在地址空间上申请的,物理内存可以/甚至一个字节都不给(此时的申请的空间其实压根就不是空间只不过是编译器按照进程地址空间划分为每一句代码都生成了虚拟地址申请的空间也为他们生成了虚拟地址 当这个程序运行成为进程时通过映射才会真正的去物理内存申请空间)

  3. 当进行对物理地址空间访问的时候,才执行内存的相关管理算法缺页中断==>[操作系统自动完成用户和进程,完全0感知]然后在进行内存的访问
    在这里插入图片描述
    在这里插入图片描述

  4. 申请了物理空间,不立马使用是空间的浪费 通过延迟分配的策略来提高整机的效率 使得内存的有效使用几乎100%

5.35地址空间和页表实现了进程的独立性

  1. 理论上 物理内存可以对未来加入内存的数据进行任意位置的加载 那么实际上物理内存中的几乎所有数据和代码在内存中是乱序的
  2. 页表将地址空间上的虚拟地址和物理地址进行映射,在进程的视角下 内存分布是有序的 即地址空间+页表将内存的分布有序化
  3. 进程要访问的物理内存中的数据和代码,可能目前并没有在物理内存中,页表可以让不同的进程映射到不同的物理内存,即实现了进程独立性(不干扰其他进程+不知道有其他进程的存在)
  4. 地址空间的存在使得每一个进程都认为自己拥有 各个区域是有序的4GB空间(32位) ,不同的进程通过页表映射到不同的区域,实现了进程的独立性 每一个进程不知道也不需要知道其他进程的存在

6.对挂起状态的理解

6.1上篇博客的初识

在这里插入图片描述

6.2通过程序运行理解挂起状态

  1. 前面我们讲到 进程 = 进程内核数据结构(PCB) + 进程对应的磁盘上的可执行程序(代码+数据) 现在我们了解到进程内核数据结构不仅仅有task_struct还有task_struct内的mm_struct* mm指针指向的mm_struct 现在我们对进程的认识是进程 = 进程内核数据结构task_struct/mm_struct/页表 + 进程对应的磁盘上的可执行程序(代码+数据)
  2. 程序是存放在磁盘上的,创建一个进程不是一开始就把所有的数据全部加载到内存里的,如果写了一百万行代码,程序大小4个G,结果运行的代码就几十行,把整个程序加载到内存中就是浪费内存
  3. 把代码和数据加载到内存本质就是创建进程 但是创建进程不是立马就把程序的所有代码和数据都加载到了内存中也不是立马创建内核数据结果建立映射关系 极端情况下 只有内核数据结构task_struct/mm_struct被创建出来了 页表映射关系/代码和数据加载到内存 工作都没有完成 这个只创建了task_struct/mm_struct的状态叫新建状态当真正运行这个程序时 代码和数据才被加载到内存
  4. 理论上 可以实现对程序的分批加载 既然可以分批加载/换入(将磁盘上的代码和数据换入到内存) 那么也就可以分批换出 当这个进程短时间不会被执行如阻塞状态(需要等待某种资源: 网络 磁盘…) 这个进程的代码和数据 就可以被换出以节省空间让急于执行的/准备好的进程的代码和数据换入 被换出代码和数据的进程的状态叫挂起状态

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

在中国企业出海的大浪潮下,亚马逊云科技提供遍及全球的基础设施和技术支持

中国技术出海是中国企业更高层次更高质量的全球化。在人类文明发展史上&#xff0c;凝聚中国古人智慧结晶的造纸术、印刷术、火药、指南针等&#xff0c;曾为中国技术出海写下过浓墨重彩的一笔。在今天&#xff0c;如金山办公、店匠科技、ADVANCE.AI等公司又以技术立业&#xf…

最受欢迎的猫罐头有那些?精选的5款热门猫罐头推荐!

新手养猫很容易陷入疯狂购买的模式&#xff0c;但有些品牌真的不能乱买&#xff01;现在的大环境不太好&#xff0c;我们需要学会控制自己的消费欲望&#xff0c;把钱花在刀刃上&#xff01;现在宠物市场真的很内卷&#xff0c;很多品牌都在比拼产品的数据和营养成分。很多铲屎…

如何查找批量企业的联系方式?

​我们都知道&#xff0c;企业的联系方式在企业的年报中就能找到&#xff0c;但是年报上的电话真的是你要找的吗&#xff1f; 很多企业年报上留的是第三方代记账公司&#xff0c;或者是其他没用的号码&#xff0c;这对于做B端业务的企业来说是不够精准的。 市面上有很多做企业…

redis运维(十六) 有序集合

一 有序集合 把握一点&#xff1a; 各种redis 命令都提供各种语言对应的API 接口,后续API是关键 ① 概念 1、sorted set --> 有序集合2、redis有序集合也是集合类型的一部分&#xff0c;所以它保留了集合中元素不能重复的特性3、但是不同的是,有序集合给每个元素多设置…

[Docker]九.Docker compose讲解

docker-compose 是 docker 官方的一个开源项目&#xff0c;可以实现对 docker 容器集群的快速编排, docker-compose 通过一个 配置文件 来管理多个 Docker 容器,在配置文件中&#xff0c;所有的容器通过 services 来定义&#xff0c;然后使用 docker-compose脚本 来 启动&am…

10、信息打点——APP小程序篇抓包封包XP框架反编译资产提取

APP信息搜集思路 外在——抓包封包——资产安全测试 抓包&#xff08;Fiddle&茶杯&burp&#xff09;封包&#xff08;封包监听工具&#xff09;&#xff0c;提取资源信息 资产收集——资源提取——ICO、MAD、hash——FOFA等网络测绘进行资产搜集 外在——功能逻辑 内在…

pikachu靶场PHP反序列化漏洞

pikachu靶场PHP反序列化漏洞 源码分析 查看源代码 class S{var $test "pikachu";function __construct(){echo $this->test;} }// O:1:"S":1:{s:4:"test";s:29:"<script>alert(xss)</script>";} $html; if(isset($_PO…

pikachu_php反序列化

pikachu_php反序列化 源代码 class S{var $test "pikachu";function __construct(){echo $this->test;} }//O:1:"S":1:{s:4:"test";s:29:"<script>alert(xss)</script>";} $html; if(isset($_POST[o])){$s $_POST[…

优先级队列(priority_queue)

文章目录 优先级队列的定义定义&#xff1a;接口头文件优先队列和堆的关系使用&#xff1a;排序的规则容器 仿函数应用 队列存指针问题&#xff1a; 优先级队列的定义 定义&#xff1a; 黄色部分是仿函数 接口 头文件 这里不需要包含其他的头文件只需要使用队列的头文件就可以…

SpringBoot监听器解析

监听器模式介绍 监听器模式的要素 事件监听器广播器触发机制 SpringBoot监听器实现 系统事件 事件发送顺序 监听器注册 监听器注册和初始化器注册流程类似 监听器触发机制 获取监听器列表核心流程: 通用触发条件: 自定义监听器实现 实现方式1 实现监听器接口: Order(1) …

SPSS系统聚类

前言&#xff1a; 本专栏参考教材为《SPSS22.0从入门到精通》&#xff0c;由于软件版本原因&#xff0c;部分内容有所改变&#xff0c;为适应软件版本的变化&#xff0c;特此创作此专栏便于大家学习。本专栏使用软件为&#xff1a;SPSS25.0 本专栏所有的数据文件请点击此链接下…

基于python人脸性别年龄检测系统-深度学习项目

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介简介技术组成1. OpenCV2. Dlib3. TensorFlow 和 Keras 功能流程 二、功能三、系统四. 总结 一项目简介 # Python 人脸性别年龄检测系统介绍 简介 该系统基…

VirtualBox配置共享文件夹,如果你一直安装增强功能失败,又没有尝试过改内核版本。。。

1 背景 想设置电脑本地和virtualbox虚拟机之间的共享文件夹&#xff0c;这样在电脑本地对共享文件的修改&#xff0c;就可以在虚拟机中被感知。 如果想配置共享文件夹&#xff0c;前提是必须安装virtualbox的增强功能。 我的虚拟机是7.0.10版本 安装的centOS8.5 可以看我之前的…

一篇文章搞懂WPF动画的使用技巧

WPF 动画系统提供了丰富的功能&#xff0c;用于为 UI 元素创建流畅的动态效果。动画可以应用于任何可用于渲染的属性&#xff0c;比如位置、颜色、大小等。在 WPF 中&#xff0c;动画是通过更改随时间变化的属性来实现的。 WPF动画基本用法 例如实现如下的动画效果&#xff1…

Kubernetes+Gitlab+Jenkins+ArgoCD多集群部署

KubernetesGitlabJenkinsArgoCD多集群部署 文章目录 KubernetesGitlabJenkinsArgoCD多集群部署1. KubernetesGitlabJenkinsArgoCD多集群部署2. 添加WebHooks自动触发3. Jenkins-构建-执行Shell4. 制作镜像及修改Yaml文件4.1 Dockerfile4.2 Build-Shell 5.自动部署Demo测试5.1 推…

基于跳蛛算法优化概率神经网络PNN的分类预测 - 附代码

基于跳蛛算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于跳蛛算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于跳蛛优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神经网络的光滑…

三、防火墙-源NAT

学习防火墙之前&#xff0c;对路由交换应要有一定的认识 源NAT基本原理1.1.NAT No-PAT1.2.NAPT1.3.出接口地址方式&#xff08;Easy IP&#xff09;1.4.Smart NAT1.5.三元组 NAT1.6.多出口场景下的源NAT 总结延伸 ——————————————————————————————…

基于单片机直流电机调速(proteus仿真+源程序)

一、系统方案 1、本设计采用这51单片机作为主控器。 2、转速值送到液晶1602显示。 3、按键设加减速&#xff0c;开始暂停、正反转。 二、硬件设计 原理图如下&#xff1a; 三、单片机软件设计 1、首先是系统初始化 en0; rw0; write_com(0x01); //lcd初始化 write_com(0x38)…

蓝桥杯算法双周赛心得——迷宫逃脱(记忆化搜索)

大家好&#xff0c;我是晴天学长&#xff0c;非常经典实用的记忆化搜索题&#xff0c;当然也可以用dp做&#xff0c;我也会发dp的题解&#xff0c;需要的小伙伴可以关注支持一下哦&#xff01;后续会继续更新的。&#x1f4aa;&#x1f4aa;&#x1f4aa; 1) .迷宫逃脱 迷官逃脱…

FPGA----ZCU106使用petalinux 2019.1(全网最详)

一、环境安装 1、软硬件需求&#xff1a;Vivado 2019.1、ZCU106、Ubuntu 18.04.1、petalinux 2019.1 本文基于2019.1版本的UG1144文档构建https://docs.xilinx.com/api/khub/documents/HXzkPWw1pfgmyp8i8JKniQ/content?Ft-Calling-Appft%2Fturnkey-portal&Ft-Calling-Ap…