【Linux】初始进程地址空间

最近,我发现了一个超级强大的人工智能学习网站。它以通俗易懂的方式呈现复杂的概念,而且内容风趣幽默。我觉得它对大家可能会有所帮助,所以我在此分享。点击这里跳转到网站。

目录

  • 一、再谈fork
  • 二、程序地址空间
    • 2.1代码验证
  • 三、虚拟地址&&线性地址
    • 3.1初步理解这种现象--引入地址空间概念
    • 3.2再次理解这种现象
  • 四、地址空间究竟是什么?
    • 4.1什么叫做地址空间?
    • 4.2为什么要有进程地址空间?
  • 五、页表
    • 5.1 CR3寄存器
    • 5.2 理解代码段和字符常量段是只读的
    • 5.3 缺页中断
  • 🍀小结🍀

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

🎉博客主页:小智_x0___0x_

🎉欢迎关注:👍点赞🙌收藏✍️留言

🎉系列专栏:Linux入门到精通

🎉代码仓库:小智的代码仓库


一、再谈fork

之前在【Linux】探索进程的父与子中提到了fork之后对父子进程返回不同的id值,给父进程返回子进程的pid,给子进程返回0,所以对于一个id如何存储两个值的说法,在我们之前已经提到过了一个概念叫做写时拷贝,就是在子进程要想修改父进程的id变量是,操作系统就会给子进程重新划分一块空间,将父进程的空间中的内容拷贝给子进程,再让子进程去修改id值,今天我们就从内存角度深度理解这个过程!

二、程序地址空间

在我们之前学习C语言的时候大家应该都见过这张地址表。
在这里插入图片描述
在我们32位机器下,这张地址表一共有4GB的大小,因为我们的CPU有32根总线的话,它的寻址范围就是 2 32 2^{32} 232,所以能接受内存的最大大小就是4GB。

2.1代码验证

#include <iostream>
#include <cstdio>using namespace std;
int g_val_1;
int g_val_2 = 100;int main(int argc,char *argv[],char *env[])
{printf("正文代码区:%p\n",main);const char *str = "hello world";printf("字符常量区:%p\n",str);printf("已初始化数据区:%p\n",&g_val_2);printf("未初始化数据区:%p\n",&g_val_1);char *mem = (char*)malloc(10);printf("堆区:%p\n",mem);printf("栈区:%p\n",&str);static int a = 0;printf("静态局部变量地址:%p\n",&a);printf("命令行参数argv[0]:%p\n",argv[0]);printf("环境变量env[0]:%p\n",env[0]);return 0;
}

在这里插入图片描述
从上面打印信息可以看到我们内存中各个区域的分配确实是按照那张程序地址空间表进行分配的,而且我们在函数中定义的静态局部变量其实是属于全局数据区的

我们再来单独打印一些栈区地址:

int main()
{int a = 0;int b = 0;int c = 0;int d = 0;printf("栈区:%p\n",&a);printf("栈区:%p\n",&b);printf("栈区:%p\n",&c);printf("栈区:%p\n",&d);return 0;
}

在这里插入图片描述

我们可以看到栈区的地址是由高向低增长的,这说明栈区是先使用高地址在使用低地址。这正是因为我们栈这个数据结构是需要压栈的,所以先进去的往往是在高地址处。

我们再来打印一波堆区地址:

int main()
{char *mem = (char*)malloc(10);char *mem1 = (char*)malloc(10);char *mem2 = (char*)malloc(10);char *mem3 = (char*)malloc(10);printf("堆区:%p\n",mem);printf("堆区:%p\n",mem1);printf("堆区:%p\n",mem2);printf("堆区:%p\n",mem3);return 0;
}

在这里插入图片描述
可以看到堆区的地址是由低向高增长,此时我们可以得到一个结论就是:堆栈之间是相对增长的。
在这里插入图片描述
对于中间这块共享区,我们在以后讲解动静态库的时候再来聊。

三、虚拟地址&&线性地址

下面我们再来写一段代码:

#include <iostream>
#include <cstdio>
#include <unistd.h>
using namespace std;
int g_val = 100;
int main()
{pid_t id = fork();if (id == 0) // 子进程{int cnt = 3;while (true){printf("i am child, pid:%d,ppid:%d,g_val:%d,&g_val:%p\n", getpid(), getppid(), g_val, &g_val);sleep(1);if (cnt--);else g_val = 200;}}else{while (true){printf("i am parent, pid:%d,ppid:%d,g_val:%d,&g_val:%p\n", getpid(), getppid(), g_val, &g_val);sleep(1);}}return 0;}

上面代码是使用fork()函数创建一个子进程。子进程和父进程在不同的内存空间运行,但是初始时内存内容是相同的。代码中,子进程和父进程都有一个无限循环,每隔一秒打印出自己的进程ID(pid),父进程ID(ppid),全局变量g_val的值和地址(&g_val)。子进程中,有一个计数器cnt,初始值为3,每次循环减1,直到变为0,然后将g_val的值改为200。因为子进程在修改父进程的数据时候会发生写时拷贝所以子进程改变g_val的值不会影响父进程的g_val的值。
在这里插入图片描述
但是我们通过观察g_val的地址在两个进程中是相同的,可是明明两个值都不一样为什么他们的地址却是一样的呢?通过我们打印的这个结果来看,如果我们变量的地址是物理地址的话,那绝对不可能会出现这种情况,所以我们又得出一个结论,上面打印出来的地址绝对不是物理地址,物理地址绝对不会出现一个地址两个值。

那么上面打印出来的地址不是物理地址,我们一般称它为线性地址(虚拟地址)。由此我们可以知道我们平时写的C/C++代码中使用的指针,指针里面的地址都不是物理地址。
【注意】:以上代码都是在Linux下的结果,Windows下的vs中可能会有所不同。

3.1初步理解这种现象–引入地址空间概念

在我们创建进程时,一旦进程的PCB(进程控制块)被创建出来,操作系统会为该进程创建一个叫做地址空间的东西,以使该进程能够更好地运行。除了内核创建PCB这样的内核结构之外,地址空间是操作系统为每个进程提供的独立空间,用于存储进程的代码、数据和各种系统资源。
在这里插入图片描述
线性或虚拟地址,这是我们在编程中经常使用的东西。为了使这些地址能被正确地使用,系统需要为每个进程构建一个名为页表的结构。页表的作用是将虚拟地址映射到物理地址,这是一种键值对(kv)的映射关系。左侧是虚拟地址,右侧是物理地址。

假设我们定义了一个全局变量,并给它分配了一个地址,比如0x40405c。这个地址其实是我们上层代码使用的虚拟地址。当这个虚拟地址被填入页表的左侧时,系统就会为这个变量在内存中分配一段空间。这段空间在物理内存中也有自己的物理地址,比如0x11223344。

当我们尝试访问这个虚拟地址时,操作系统会自动根据页表将虚拟地址转换为物理地址。这样,我们就可以直接访问物理地址中的内容了。简而言之,页表的作用就是将我们使用的虚拟地址转换成真正的物理地址,使我们能够正确地访问内存中的内容。

3.2再次理解这种现象

在父进程创建子进程时,操作系统会为新创建的子进程创建一个内核的PCB结构。子进程会以父进程为模板初始化其内部的结构体对象。每个进程都需要一个地址空间,所以子进程也会复制父进程的地址空间。为了保持独立性,子进程具有独立的PCB和地址空间,虽然其中一些字段是从父进程继承下来的。

子进程在初始化数据区也有一个虚拟地址,比如0x40405c。子进程同样需要一个独立的页表结构。开始时,父子进程的大部分资源共享,它们的页表指向相同的物理地址。但当子进程尝试修改某个地址时,比如将g_val从100改为200,操作系统会识别到这个修改操作。

操作系统会在内存中为父子进程共享的数据重新划分空间,在物理内存中为这个变量开辟新的空间。这个新空间的物理地址会发生变化,比如变成0x00112233。然后,操作系统会将页表中的旧物理地址(比如0x11223344)改为新地址(比如0x00112233)。

这样,父子进程在物理内存上的值就被分开了。通过这种方式,子进程能够独立地运行,而不会受到父进程的影响。
在这里插入图片描述

所以子进程在打印时,显示的地址与父进程相同,这是因为子进程继承了父进程的地址空间,包括这个地址。但在实际访问时,读取到的内容是不同的。这是因为父进程通过页表找到的是物理内存中的某个地址,值为100,而子进程通过自己的页表找到的是物理内存中的另一个地址,值为200。因此,虽然应用层面上看到的虚拟地址相同,但实际的值却是不一样的。

【注意】

  • 写时拷贝 --是由操作系统自己完成的。
  • 重新开辟空间这个过程中,页表左侧的虚拟地址是0感知的,不关心,不会影响到它

四、地址空间究竟是什么?

4.1什么叫做地址空间?

地址空间本质上是进程对内存进行宏观划分的一个视角。当进程需要读取代码或数据时,它必须正在运行,因为只有当前正在运行的进程才会有访问内存的需求。如果一个进程没有被调度或者被挂起阻塞,它就不会有访问内存的需求。

在32位计算机中,我们有32位的地址总线,这些地址总线可以被用来在内存中选取特定的位置。由于每一根地址总线上只有0和1两种状态,也就是强弱电平的概念,所以从CPU到内存的01组合一共有 2 32 2^{32} 232 种。因为内存选址的最基本单位是字节,所以我们的内存大小一定是 2 32 2^{32} 232字节,即4GB。这就是一个32位计算机最多能够装载的内存空间。

那么,什么是地址空间呢?地址空间就是由这些地址的排列组合形成的范围。在32位计算机中,地址空间就是从0到 2 32 − 1 2^{32}-1 2321的范围。每个进程都有自己的地址空间,它是进程对内存进行管理和访问的基础。

总的来说,所谓的进程地址空间,本质是一个描述进程可视范围的大小。地址空间内一定要存在各种区域划分,对线性地址进行start,和end即可。

地址空间本质是内核的一个数据结构对象,类似PCB一样,地址空间也是要被操作系统管理的:先描述,在组织

struct mm_struct //默认划分的区域是4GB.
{/* ... 其他字段 ... */unsigned long start_code, end_code; /* 代码段的开始和结束地址 */unsigned long start_data, end_data; /* 数据段的开始和结束地址 */unsigned long start_brk, brk;       /* 堆的当前和最大结束地址 */unsigned long start_stack;/* 栈的开始地址*/unsigned long arg_start, arg_end;   /* 命令行参数的开始和结束地址 */  unsigned long env_start, env_end;   /* 环境变量的开始和结束地址 */ /* ... 其他字段 ... */
};

在可视范围内,连续的空间中,每一个最小单位都可以有地址,这个地址可以被进程直接使用!!!

所谓的地址空间其实就是操作系统给进程画的一个大饼,虽然说给你们每个进程都分配了4GB大小,但是,每个进程都不知道其他进程的存在,都以为会自己独享4GB,实际上操作系统并不会直接把4GB直接给进程,而是进程需要多少空间的时候向操作系统申请多少空间。

4.2为什么要有进程地址空间?

所谓的进程=内核数据结构(task_struct && mm_struct &&页表)+程序的代码和数据。

  • 原因一:让进程以统一的视角看待内存。
  • 原因二:增加进程虚拟地址空间可以让我们访问内存的时候,增加一个转换的过程,在这个转化的过程中,可以对我们的寻址请求进行审查,所以一旦异常访问,直接拦截,该请求不会到达物理内存,起到保护物理内存的作用
  • 原因三:因为有地址空间和页表的存在,将进程管理模块,和内存管理模块进行解耦合!

五、页表

基本概念:
页表是一种特殊的数据结构,被放在系统空间的页表区,用于存放逻辑页与物理页帧的对应关系。每一个进程都拥有一个自己的页表,进程控制块(PCB)中有指向页表的指针。页表的作用是记录虚拟内存地址到物理内存地址的映射关系,以实现内存管理。当程序访问一个虚拟内存地址时,操作系统会根据页表将该地址映射到相应的物理内存地址上。
在采用页式内存管理的系统中,虚拟内存空间和物理内存空间都被划分成相同大小的页(或称为页面)和页框。页表记录了每个虚拟页对应的物理页框的地址。此外,页表的每一个表项通常还包含一些标志位,用于描述该页的访问权限、是否在内存中、是否被修改过等信息。
当进程访问某个虚拟地址时,如果对应的页不在物理内存中(即发生缺页中断),则会引发一个缺页异常,此时操作系统会根据页表中的信息从外存中加载该页到内存中,以排除故障。因此,页表是实现虚拟存储系统的重要机制之一。

在操作系统中,为了实现线性地址到物理地址或虚拟地址到物理地址的转换,系统会为每个进程维护一个对应的页表结构。这个页表结构可以简化为包含三个字段(尽管实际上页表具有更多属性)。为了方便理解,我们可以将页表想象成一种类似于unordered_mapmap的数据结构,它在内存中为进程构建了一张映射表。这张映射表的内容用于保存对应的物理地址。需要注意的是,这种简化理解并不完全准确,但它有助于我们初步掌握页表的基本概念。后续我们会提供更详细和准确的信息来校正这一理解。目前,为了简化讨论,我们暂时采用这种简化的视角。

5.1 CR3寄存器

每个正在执行的进程在CPU内部都有一个特定的寄存器,即Cr3寄存器(在x86体系结构中),用于存储其页表的起始地址。这个寄存器是管理性的,它确保了当前运行进程的页表地址始终可得。因此,无论进程何时被切换走,我们都不必担心其页表地址的丢失。这是因为,在进程运行期间,Cr3寄存器中保存的页表地址本质上是该进程硬件上下文的一部分。

当进程不再运行,即被切换走时,它会带走其寄存器中的内容。当进程再次获得执行权时,它会恢复其原先保存在地址空间中的页表地址,从而能够重新访问自己的页表。这样,每个进程都能始终找到其对应的页表,确保地址转换的正确进行。

需要明确的是,Cr3寄存器中保存的页表起始地址是物理地址。在CPU需要访问进程的地址空间时,它会直接读取Cr3寄存器中的物理地址,并据此查找页表,实现地址的正确转换。这种机制确保了操作系统在硬件层面上能够高效地访问和管理进程的页表结构。

5.2 理解代码段和字符常量段是只读的

先看现象:

int main()
{char *str = "hello world";*str = 's';printf("%s",str);return 0;
}

在这里插入图片描述
这里可以看到程序直接挂掉了。
那么操作系统怎么知道我们要修改了只读常量区的数据呢?
在这里插入图片描述

当程序从磁盘加载到内存时,操作系统会负责将代码区和字符常量区等只读部分加载到相应的只读内存区域。虽然物理内存本身没有直接的只读或只写属性,但操作系统通过虚拟内存管理提供了这样的抽象。
CPU在访问内存时,实际上是通过访问页表来实现的。页表将虚拟地址映射到物理地址,同时页表中还包含了关于该内存页的访问权限等信息。对于只读的内存区域,页表中相应的条目会被标记为只读。
因此,当CPU试图修改只读内存区域时,操作系统会拦截这个操作,并直接杀掉这个进程。

5.3 缺页中断

基本概念: 缺页中断,也被称为页缺失或Page fault,是计算机科学中的一个概念。当软件试图访问已映射在虚拟地址空间中,但是并未被加载在物理内存中的一个分页时,由中央处理器的内存管理单元所发出的中断。
具体来说,进程线性地址空间里的页面不必常驻内存。在执行一条指令时,如果发现要访问的页没有在内存中(即存在位为0),那么会停止该指令的执行,并产生一个页不存在的异常,即缺页中断。此时,对应的故障处理程序会试图从外存加载该页到内存,以排除故障。如果加载成功,那么原先引起异常的指令就可以继续执行,而不再产生异常。
在虚拟存储系统中,缺页中断是实现内存管理、内存保护和内存扩充的重要手段。通过缺页中断,操作系统可以将不常用的页面换出到外存,从而为即将被访问的页面腾出空间。同时,缺页中断还可以用来实现内存保护,防止进程访问不属于它的内存空间。
虽然名为“中断”,但缺页中断并不总是意味着错误。在许多情况下,它是操作系统正常管理内存的一部分。然而,如果缺页中断频繁发生,可能会导致系统性能下降,因为每次中断都需要消耗一定的时间来处理。因此,在设计程序和系统时,需要尽量减少不必要的缺页中断。

我们现代操作系统,几乎不做任何浪费时间和浪费空间的事!

当我们玩一些特别大的游戏时,尽管游戏的体积可能达到几十个G,而我们计算机的物理内存可能只有4G或8G,但游戏仍然能够正常运行。这是因为操作系统采用了分批加载的策略来处理大文件。
操作系统不会一次性将整个游戏加载到内存中,因为这可能超出了内存的容量。相反,它会先将游戏的一部分数据,比如前1G或500M,加载到内存中供程序运行。当这部分内存被使用完毕后,操作系统会释放它,然后再加载下一批数据。
这种加载策略被称为惰性加载。对于可执行程序,操作系统在创建进程时,会先创建内核数据结构,包括为该进程维护的PCB(进程控制块)和地址空间页表等对应关系。然后,它才会开始逐步加载可执行程序。
在页表中,虚拟地址和物理地址的对应关系不是一开始就全部建立的。对于还未加载到内存中的部分,页表中的对应项会留空,并标记为指向磁盘中的特定地址。当程序试图访问这些地址时,会引发缺页中断。
在缺页中断发生时,操作系统会识别出所需的内存页或数据尚未加载到内存中。然后,它会在物理内存中为这部分数据申请空间,并将它从磁盘加载到内存中。加载完成后,操作系统会更新页表,将新加载的数据的地址填入对应的项中。
这个过程是自动完成的,对于用户来说是无感知的。因此,即使你的程序很快就启动了,但可执行程序可能并没有被完全加载到内存中。只有当程序实际需要使用某部分数据时,那部分数据才会被加载到内存中。这种按需加载的方式有效地利用了有限的内存资源,使得大程序也能在有限的内存空间中运行。

【总结】:

  • 当我们通过虚拟地址转化尝试访问物理内存时,如果发现所需的物理内存并未在系统中(即内存尚未申请),这时就会触发一个缺页中断。随后,操作系统会负责重新申请内存、加载数据,并更新页表。这一系列的操作,包括内存的申请、数据的加载以及页表的更新,都是由操作系统的内存管理模块来完成的。
    在页表的结构中,左侧部分我们通常称之为进程管理相关的部分,而右侧则与内存管理紧密相关。由于有了页表和地址空间的存在,进程管理部分无需直接关心内存管理的细节。进程只需使用虚拟地址,若虚拟地址不足或不存在,操作系统会自动触发缺页中断,并调用内存管理功能进行处理。
    因此,虚拟地址空间的存在在软件层面上实现了内存管理和进程管理的解耦,使得两者可以相对独立地工作,提高了系统的灵活性和可维护性。

在这里插入图片描述

🍀小结🍀

今天我们学习了"【Linux】初始进程地址空间相信大家看完有一定的收获。种一棵树的最好时间是十年前,其次是现在! 把握好当下,合理利用时间努力奋斗,相信大家一定会实现自己的目标!加油!创作不易,辛苦各位小伙伴们动动小手,三连一波💕💕~~~,本文中也有不足之处,欢迎各位随时私信点评指正!
在这里插入图片描述

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

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

相关文章

Https加密超文本传输协议的运用

一、https的相关知识 1.1 https的简介 HTTPS &#xff08;全称&#xff1a;Hypertext Transfer Protocol Secure &#xff09;&#xff0c;是以安全为目标的 HTTP 通道&#xff0c;在HTTP的基础上通过传输加密和身份认证保证了传输过程的安全性 。HTTPS 在HTTP 的基础下加…

ElementUI Form:Checkbox 多选框

ElementUI安装与使用指南 Checkbox 多选框 点击下载learnelementuispringboot项目源码 效果图 el-checkbox.vue &#xff08;Checkbox 多选框&#xff09;页面效果图 项目里el-checkbox.vue代码 <script> const cityOptions [上海, 北京, 广州, 深圳] export def…

C语言操作符

文章目录 1:算术操作符2:移位操作符(移动的是二进制序列中的补码)2.1:知识补充(原码,反码,补码与二进制)2.2:左移操作符(<<)2.2:右移操作符(>>)2.2.1:逻辑右移2.2.2:算术右移 3:位操作符(运算用的是二进制位的补码)3.1:按位与操作符(&)3.2:按位或操作符(|)3.3:…

系统分析师-22年-下午题目

系统分析师-22年-下午题目 更多软考知识请访问 https://ruankao.blog.csdn.net/ 试题一必答&#xff0c;二、三、四、五题中任选其中两题作答 试题一 (25分) 说明 某软件公司拟开发一套博客系统&#xff0c;要求能够向用户提供一个便捷发布自已心得&#xff0c;及时有效的…

使用 axios 请求库,设置请求拦截

什么是 axios&#xff1f; 基于promise网络请求库&#xff0c;可以同构&#xff08;同一套代码可以运行在浏览器&#xff09;&#xff0c;在服务端&#xff0c;使用原生node.js的http模块&#xff0c;在客户端&#xff08;浏览器&#xff09;中&#xff0c;使用XMLHttpRequests…

Kotlin快速入门系列2

Kotlin的基本数据类型 Kotlin 的基本数值类型包括 Byte、Short、Int、Long、Float、Double 等。不同于 Java 的是&#xff0c;字符不属于数值类型&#xff0c;是一个独立的数据类型。 Java和kotlin数据类型对照如下&#xff1a; Java基本数据类型 Kotlin对象数据类型 数据类…

【Linux】基本指令(上)

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:Linux ⚙️操作环境:Xshell (操作系统:CentOS 7.9 64位) 目录 Xshell快捷键 Linux基本指令 ls指令 pwd指令 cd指令 touch指令 mkdir指令 rmdir指令/rm指令 结语 Xshell快捷键 AltEnter 全屏/取消全屏 Tab 进…

HarmonyOS模拟器启动失败,电脑蓝屏解决办法

1、在Tool->Device Manager管理界面中&#xff0c;通过Wipe User Data清理模拟器用户数据&#xff0c;然后重启模拟器&#xff1b;如果该方法无效&#xff0c;需要Delete删除已创建的Local Emulater。 2、在Tool->SDK Manager管理界面的PlatForm选项卡中&#xff0c;取消…

开阳630hv100的代码编译以及软件制作步骤

打开项目功能步骤&#xff1a; 编译awtk功能&#xff1a; 选中awtk工程&#xff0c;先编译一次awtk sdk&#xff08;如下图的3和4步骤&#xff09;&#xff1b; 编译项目代码&#xff08;如下图步骤5和6&#xff09;&#xff1b; 编译完成后&#xff0c;软件路径&#xff1a;…

1.理解AOP,使用AOP

目录 1AOP基础 1.1 AOP概述 1.2AOP快速使用 2.3 AOP核心概念 1AOP基础 首先介绍一下什么是AOP&#xff0c;再通过一个快速入门程序&#xff0c;让大家快速体验AOP程序的开发。最后再介绍AOP当中所涉及到的一些核心的概念。 1.1 AOP概述 什么是AOP&#xff1f; 说白了&am…

FPGA高端项目:Xilinx Artix7系列FPGA 多路视频缩放拼接 工程解决方案 提供4套工程源码+技术支持

目录 1、前言版本更新说明给读者的一封信FPGA就业高端项目培训计划免责声明 2、相关方案推荐我这里已有的FPGA图像缩放方案我已有的FPGA视频拼接叠加融合方案本方案的Xilinx Kintex7系列FPGA上的ov5640版本本方案的Xilinx Kintex7系列FPGA上的HDMI版本 3、设计思路框架设计框图…

代理模式(静态代理、JDK 动态代理、CGLIB 动态代理)

代理模式(静态代理、JDK 动态代理、CGLIB 动态代理) 一、代理模式概述1. 生活中的代理案例2. 为什么要使用代理3. 代理模式在 Java 中的应用4. 概述5. 生活中代理图示二、代理的实现方式1. Java 中代理图示2. 静态代理2.1 案例2.2 实现案例2.3 静态代理存在的问题三、动态代理…

Vmware 无法开启虚拟化解决方法

最近遇到了Vmware无法开启虚拟化的问题,已经解决,记录一下解决经过。 我遇到的情况是BIOS已经开启虚拟化,HV服务也停用了,但是Vmware仍然提示模块“VPMC”启动失败。网上的解决方案千篇一律,基本都是排查BIOS、停用Windows的虚拟化功能、停用HV主机服务、Vmware配置中关闭…

RK3588平台开发系列讲解(视频篇)RKMedia的VDEC模块

文章目录 一、 VDEC模块支持的编码标准介绍二、VDEC API的调用三、VDEC解码流程沉淀、分享、成长,让自己和他人都能有所收获!😄 📢RKMedia是RK提供的一种多媒体处理方案,可实现音视频捕获、音视频输出、音视频编解码等功能。 一、 VDEC模块支持的编码标准介绍 RK3688 V…

【51单片机】点亮第一个LED灯

目录 点亮第一个LED灯单片机 GPIO 介绍GPIO 概念GPIO 结构 LED简介软件设计点亮D1指示灯LED流水灯 橙色 点亮第一个LED灯 单片机 GPIO 介绍 GPIO 概念 GPIO&#xff08;general purpose intput output&#xff09; 是通用输入输出端口的简称&#xff0c; 可以通过软件来控制…

使用宝塔面板访问MySQL数据库

文章目录 前言一、安装访问工具二、查看数据库总结 前言 前面我们已经部署了前后端项目&#xff0c;但是却不能得到数据库的信息&#xff0c;看有谁再使用你的项目。例如员工、用户等等。本次博客进行讲解如何在宝塔面板里面访问MySQL数据库。 一、安装访问工具 1、打开软件商…

分割头篇 | 原创自研 | YOLOv8 更换 SEResNeXtBottleneck 头 | 附详细结构图

左图:ResNet 的一个模块。右图:复杂度大致相同的 ResNeXt 模块,基数(cardinality)为32。图中的一层表示为(输入通道数,滤波器大小,输出通道数)。 1. 思路 ResNeXt是微软研究院在2017年发表的成果。它的设计灵感来自于经典的ResNet模型,但ResNeXt有个特别之处:它采用…

Redis单机-主从集群-哨兵集群-分片集群 搭建教程

Redis集群 本章是基于CentOS7下的Redis集群教程&#xff0c;包括&#xff1a; 单机安装RedisRedis主从Redis分片集群 1.单机安装Redis 首先需要安装Redis所需要的依赖&#xff1a; yum install -y gcc tclredis-6.2.4.tar.gz 然后将Redis安装包上传到虚拟机的任意目录&am…

【Vue3+Vite】Vue生命周期与组件 快速学习 第三期

文章目录 一、Vue生命周期1.1 生命周期简介1.2 生命周期案例 二、Vue组件2.1 组件基础2.2 组件化入门案例2.3 组件之间传递数据2.3.1父传子2.3.2 子传父2.3.3 兄弟传参 总结 一、Vue生命周期 1.1 生命周期简介 每个 Vue 组件实例在创建时都需要经历一系列的初始化步骤&#xf…

【JavaEE spring】SpringBoot 统一功能处理

SpringBoot 统一功能处理 1. 拦截器1.1 拦截器快速⼊⻔1.2 拦截器详解1.2.1 拦截路径1.2.2 拦截器执⾏流程 1.3 登录校验1.3.1 定义拦截器1.3.2 注册配置拦截器 2. 统⼀数据返回格式2.1 快速⼊⻔2.2 存在问题2.3 案例代码修改2.4 优点 3. 统⼀异常处理 1. 拦截器 后端程序根据…