嵌入式C语言自我修养《内存堆栈管理》学习笔记

目录

一、Linux环境下的内存管理

二、栈的管理

三、堆内存管理

四、mmap映射区

五、内存泄漏与防范

六、常见的内存错误及检测


        C程序中定义的函数、全局变量、静态变量经过编译链接后,分别以section的形式存储在可执行文件的代码段、数据段和BSS段中。当程序运行时,可执行文件首先被加载到内存中,各个section分别加载到内存中对应的代码段、数据段和BSS段中。需要动态链接的动态库也被加载到内存中,完成代码的链接和重定位操作,以保证程序的正常运行。一个可执行文件被加载到内存中运行时,它在内存空间的分布如下图所示。

一、Linux环境下的内存管理

        在Linux环境下运行的程序,在编译时链接的起始地址都是相同的,而且是一个虚拟地址。Linux操作系统需要CPU内存管理单元的支持才能运行,Linux内核通过页表和MMU硬件来管理内存,完成虚拟地址到物理地址的转换、内存读写权限管理等功能

        可执行文件在运行时,加载器将可执行文件中的不同section加载到内存中读写权限不同的区域,如代码段、数据段、.bss段、.rodata段等。

        计算机上运行的程序主要分为两种:操作系统和应用程序。每一个应用程序进程都有4GB大小的虚拟地址空间。为了系统的安全稳定,0~4GB的虚拟地址空间一般分为两部分:用户空间和内核空间。0~3GB地址空间给应用程序使用,而操作系统一般运行在3~4GB内核空间。通过内存权限管理,应用程序没有权限访问内核空间,只能通过中断或系统调用来访问内核空间,这在一定程度上保障了操作系统核心代码的稳定运行。

        在Linux环境下,虽然所有的程序编译时使用相同的链接地址,但在程序运行时,相同的虚拟地址会通过MMU转换,映射到不同的物理内存区域各个可执行文件被加载到内存不同的物理页上。如下图所示,每个进程都有各自的页表,用来记录各自进程中虚拟地址到物理地址的映射关系。

二、栈的管理

        栈有两种基本操作:入栈(push)和出栈(pop)。入栈是把一个栈元素压入栈中,而出栈则是从栈中弹出一个栈元素。入栈和出栈都靠栈指针(Stack Pointer,SP)来维护,SP会随着入栈和出栈在栈顶上下移动。如下图所示,根据栈指针SP指向栈顶元素的不同,栈可分为满栈和空栈;根据栈的生长方向不同,栈又分为递增栈和递减栈

        满栈的栈指针SP总是指向栈顶元素,而空栈的栈指针则指向栈顶元素上方的可用空间;一个栈元素入栈时,递增栈的栈指针从低地址往高地址增长,而递减栈的栈指针则从高地址往低地址增长

        在栈的初始化过程中,栈在内存中的起始地址还是有点讲究的。ARM处理器使用的是满递减栈,在Linux环境下,栈的起始地址一般就是进程用户空间的最高地址,紧挨着内核空间,栈指针从高地址往低地址增长。为了防止黑客栈溢出攻击,新版本的Linux内核一般会将栈的起始地址设置成随机的,如下图所示,每次程序运行,栈的初始化起始地址都会基于用户空间的最高地址有一个随机的偏移,每次栈的起始地址都不一样。

        Linux默认给每一个用户进程栈分配8MB大小的空间。栈的容量如果设置得过大,则会增加内存开销和启动时间;如果设置得过小,则程序超出栈设置的内存空间又容易发生栈溢出(Stack Overflow),产生段错误

        在设置栈大小时,我们要根据程序中的变量、数组对栈空间的实际需求,设置合理的栈大小。用户在编写程序时,为了防止栈溢出,可以参考下面的一些原则。
        ● 尽量不要在函数内使用大数组,如果确实需要大块内存,则可以使用malloc申请动态内存。
        ● 函数的嵌套层数不宜过深
        ● 递归的层数不宜太深

        全局变量定义在函数体外,其作用域范围为从声明处到文件结束。其他文件如果想使用这个全局变量,则在自己的文件内使用extern声明后即可使用。全局变量的生命周期在整个程序运行期间都是有效的。

        局部变量定义在函数内,其作用域只能在函数体内使用函数只有在被调用的时候才会在内存中开辟一个栈帧空间,在这个栈空间里存储局部变量及传进来的函数实参等。函数调用结束,这个栈帧空间就被销毁释放了,变量也就随之消失,因此局部变量的生命周期仅仅存在于函数运行期间。每一次函数被调用,临时开辟的栈帧空间可能不相同,局部变量的地址也不相同。

        编译器在编译程序时,其实是根据一对大括号{}来限定一个变量的作用域的,示例:

#include <iostream>void functionWithStatic() {static int staticVar = 0; // 带有 static 修饰的局部变量int normalVar = 0;       // 普通局部变量staticVar++;normalVar++;std::cout << "Static Variable: " << staticVar << std::endl;std::cout << "Normal Variable: " << normalVar << std::endl;
}int main() {for (int i = 0; i < 3; i++) {functionWithStatic();}return 0;
}

运行结果:

最后我们对变量的作用域做下小结。
全局变量的作用域如下
● 全局变量的作用域由文件来限定。
● 可使用extern进行扩展,被其他文件引用。
● 也可以使用static进行限制,只能在本文件中被引用。
局部变量的作用域如下
● 局部变量的作用域由{}限定
● 可以使用static修饰局部变量来改变它们的存储属性(生命周期),但不能改变其作用域。

三、堆内存管理

        我们使用malloc()/free()函数申请/释放的动态内存就属于堆内存,如下图所示,堆是Linux进程空间中一片可动态扩展或缩减的内存区域,一般位于BSS段的后面

堆内存与栈相比,有相同点,也有区别。
● 堆内存是匿名的,不能像变量那样使用名字直接访问,一般通过指针间接访问。
● 在函数运行期间,对函数栈帧内的内存访问也不能像变量那样通过变量名直接访问,一般通过栈指针FP或SP相对寻址访问。
● 堆内存由程序员自己申请和释放,函数退出时,如果程序员没有主动释放,就会造成内存泄漏。
● 栈内存由编译器维护,函数运行时开辟一个栈帧空间,函数运行结束,栈帧空间随之销毁释放。

        malloc()/free()函数的底层实现,其实就是通过系统调用brk向内核的内存管理系统申请内存。内核批准后,就会在BSS段的后面留出一片内存空间,允许用户进行读写操作。申请的内存使用完毕后要通过free()函数释放,free()函数的底层实现也是通过系统调用来归还这块内存的。

        当用户要申请的内存比较大时,如大于128KB,一般会通过mmap系统调用直接映射一片内存,使用结束后再通过ummap系统调用归还这块内存。mmap区域是Linux进程中比较特殊的一块区域,主要用于程序运行时动态共享库的加载和mmap文件映射。早期的Linux内核将该区域设置在0x40000000附近,Linux 2.6以后的内核将该区域移到了栈附近,打印mmap映射区域的地址,你会发现大部分地址都在0xBxxxxxxx范围内,紧挨着进程的用户栈

示例代码:

运行结果:

        根据程序的打印结果,我们可以看到:对于用户申请的小块内存,Linux内存管理子系统会在BSS段的后面批准一块内存给用户使用。当用户申请的内存大于128KB时,Linux系统则通过mmap系统调用,映射一片内存给用户使用,映射区域在用户进程栈附近。两次申请的不同大小
的内存,其地址分别位于内存中两个不同的区域:heap区和mmap区

        让a.out进程先不退出,一直死循环运行,以方便我们通过cat命令查看a.out进程的内存布局。

        在32位X86平台下我们可以看到,heap区域在.bss段的后面,而mmap区域则紧挨着stack,mmap区域包括进程动态链接时加载到内存的动态链接器ld-2.23.so、动态共享库、使用mmap申请的动态内存

        使用kill命令杀掉a.out进程再重新运行,你会发现&mem_100和&mem_256K的地址打印值发生了变化,每次程序运行的地址可能都不相同。这是因为heap区和mmap区的起始地址和stack一样,也不是固定不变的。为了防止黑客攻击,每次程序运行时,它们都会以一个随机偏移作为起始地址。

        通过a.out进程的内存布局我们看到,栈的起始地址并不紧挨着内核空间0xc0000000,而是从0xbf9a2000作为起始地址,中间有一个大约6MB的偏移heap区也不紧挨着.bss段,它们之间也有一个offset;mmap区也是如此,它和stack区之间也有一个offset

        大量的系统调用会让处理器和操作系统在不同的工作模式之间来回切换:操作系统要在用户态和内核态之间来回切换,CPU要在普通模式和特权模式之间来回切换,每一次切换都意味着各种上下文环境的保存和恢复,频繁地系统调用会降低系统的性能。系统调用还有一个不人性化的地方是不支持任意大小的内存分配,有的平台甚至只支持一个或数倍物理页大小的内存申请,这在一定程度上会造成内存的浪费。举个通俗的例子,内存申请有点类似你去银行存取款。当你需要用钱时,如果每次都是用多少取多少,用1块取1块,用10块取10块,那么估计你天天都得往银行跑,花费在交通、排队上的时间开销无疑是巨大的。同样的道理,当你往银行存钱时,如果只要口袋里有钱,就往银行里存,有1块存1块,有10块存10块,那么估计你也得天天往银行跑。正确的做法应该是:每次取钱时多取一些,放到自己的钱包里,可以多次使用;存钱时也是如此,先存放到钱包里,等攒够了一定数额,再存到银行里,这样就可以大大减少去银行的次数。

        为了提高内存申请效率,减少系统调用带来的开销,我们可以参考上面的钱包模式,在用户空间层面对堆内存介入管理。如在glibc中实现的内存分配器(allocator)可以直接对堆内存进行维护和管理。如图5-27所示,内存分配器通过系统调用brk()/mmap()向Linux内存管理子系统“批发”内存,同时实现了malloc()/free()等API函数给用户使用,满足用户动态内存的申请与释放请求。
        当用户使用free()释放内存时,释放的内存并不会立即返回给内核,而是被内存分配器接收,缓存在用户空间。内存分配器将这些内存块通过链表收集起来,等下次有用户再去申请内存时,可以直接从链表上查找合适大小的内存块给用户使用,如果缓存的内存不够用再通过brk()系统调用去内核“批发”内存。内存分配器相当于一个内存池缓存,通过这种操作方式,大大减少了系统调用的次数,从而提升了程序申请内存的效率,提高了系统的整体性能。

四、mmap映射区

        当用户使用malloc申请大于128KB的堆内存时,内存分配器会通过mmap系统调用,在Linux进程虚拟空间中直接映射一片内存给用户使用。

        当我们运行一个程序时,需要从磁盘上将该可执行文件加载到内存。将文件加载到内存有两种常用的操作方法,一种是通过常规的文件I/O操作,如read/write等系统调用接口;一种是使用mmap系统调用将文件映射到进程的虚拟空间,然后直接对这片映射区域读写即可

        文件I/O操作使用文件的API函数(open、read、write、close)对文件进行打开和读写操作。文件存储于磁盘中,我们通过指定的文件名打开一个文件,就会得到一个文件描述符,通过该文件描述符就可以找到该文件的索引节点inode,根据inode就可以找到该文件在磁盘上的存储位置。然后我们就可以直接调用read()/write()函数到磁盘指定的位置读写数据了。文件的读写流程如图5-34左侧图所示。

        磁盘属于机械设备,程序每次读写磁盘都要经过转动磁盘、磁头定位等操作,读写速度较慢。为了提高读写效率,减少I/O读盘次数以保护磁盘,Linux内核基于程序的局部原理提供了一种磁盘缓冲机制。如图5-34所示,在内存中以物理页为单位缓存磁盘上的普通文件或块设备文件。当应用程序读磁盘文件时,会先到缓存中看数据是否存在,若数据存在就直接读取并复制到用户空间;若不存在,则先将磁盘数据读取到页缓存(page cache)中然后从页缓存中复制数据到用户空间的buf中。当应用程序写数据到磁盘文件时,会先将用户空间buf中的数据写入page cache,当page cache中缓存的数据达到设定的阈值或者刷新时间超时,Linux内核会将这些数据回写到磁盘中。

        当动态库第一次被链接器加载到内存参与动态链接时,如图5-43所示,动态库映射到了当前进程虚拟空间的mmap区域动态链接和重定位结束后,程序就开始运行。当程序访问mmap映射区域,去调用动态库的一些函数时,发现此时还没有为这片虚拟空间分配物理内存,就会产生一个请页异常。内核接着会为这片映射内存区域分配物理内存,将动态库文件libtest.so加载到物理内存,并将虚拟地址和物理地址之间的映射关系更新到进程的页表项,此时动态库才真正加载到物理内存,程序才可以正常运行。

        对于已经加载到物理内存的文件,Linux内核会通过一个radix tree的树结构来管理这些页缓存对象。在图5-44中,当进程B运行也需要加载动态库libtest.so时,动态链接器会将库文件libtest.so映射到进程B的一片虚拟内存空间上,链接重定位完成后进程B开始运行。当通过映射内存地址访问libtest.so时也会触发一个请页异常,Linux内核在分配物理内存之前会先从radix tree树中查询libtest.so是否已经加载到物理内存,当内核发现libtest.so库文件已经加载到内存后就不会给进程B分配新的物理内存,而是直接修改进程B的页表项,将进程B中的这片映射区域直接映射到libtest.so所在的物理内存上。

        通过上面的分析我们可以看到,动态库libtest.so只加载到物理内存一次,后面的进程如果需要链接这个动态库,直接将该库文件映射到自身进程的虚拟空间即可,同一个动态库虽然被映射到了多个进程的不同虚拟地址空间,但是通过MMU地址转换,都指向了物理内存中的同一块区域。此时动态库libtest.so也被多个进程共享使用,因此动态库也被称作动态共享库

五、内存泄漏与防范

 什么是内存泄漏?

        在一个C函数中,如果我们使用malloc()申请的内存在使用结束后没有及时被释放,则C标准库中的内存分配器ptmalloc和内核中的内存管理子系统都失去了对这块内存的追踪和管理。失去管理和追踪的这块内存,一直孤零零地躺在内存的某片区域,用户、内存分配器和内存管理子系统都不知道它的存在,它就像内存中的一块漏洞,我们称这种现象为内存泄漏。

预防内存泄漏:

        预防内存泄漏最好的方法就是:内存申请后及时地释放,两者要配对使用,内存释放后要及时将指针设置为NULL,使用内存指针前要进行非空判断

内存泄漏检测工具:

        MTrace是Linux系统自带的一个工具,它通过跟踪内存的使用记录来动态定位用户代码中内存泄漏的位置。使用MTrace很简单,在代码中添加下面的接口函数就可以了。

        mtrace()函数用来开启内存使用的记录跟踪功能muntrace()函数用来关闭内存使用的记录跟踪功能。如果想检测一段代码是否有内存泄漏,则可以把这两个函数添加到要检测的程序代码中。 

        开启跟踪功能后,MTrace会跟踪程序代码中使用动态内存的记录,并把跟踪记录保存在一个文件里,这个文件可以由用户通过MALLOC_TRACE来指定。接下来我们编译、运行这个程序,并使用MTrace来定位内存泄漏的位置。

        通过生成的日志文件mtrace.log来定位内存泄漏在程序中的位置。

        根据动态内存的使用记录,我们可以很快定位到内存泄漏发生在mcheck.c文件中的第11行代码。 

六、常见的内存错误及检测

        如图5-47所示,内存管理子系统将一个进程的虚拟空间划分为不同的区域,如代码段、数据段、BSS段、堆、栈、mmap映射区域、内核空间等,每个区域都有不同的读、写、执行权限

        通过内存管理,每个区域都有具体的访问权限,如只读、读写、禁止访问等。数据段、BSS段、堆栈区域都属于读写区,而代码段则属于只读区,如果你往代码段的地址空间上写数据就会发生一个段错误。在Linux用户进程的4GB虚拟空间上,除了上面我们熟悉的区域,还剩下很多区域,如代码段之前的区域、堆和mmap区域之间的进程空间、内核空间等。这部分内存空间是禁止用户程序访问的。当一个用户进程试图访问这部分空间时,就会被系统检测到,在Linux下系统会向当前进程发送一个信号SIGSEGV,终止该进程的运行。

        对于应用程序来说,常见的内存错误一般主要分为以下几种类型内存越界、内存踩踏、多次释放、非法指针

内存越界:导致段错误

内存踩踏:如果一个进程中有多个线程,多个线程都申请堆内存,这些堆内存就可能彼此相邻,使用时需要谨慎,提防越界。在内核驱动开发中,驱动代码运行在特权状态,对内存访问比较自由,多个驱动程序申请的物理内存也可能彼此相邻。如果你的程序代码经常莫名其妙地崩溃,而且每次出错的地方也不一样,在确保自己的代码没问题后,也可以大胆地去怀疑一下是不是内存踩踏的问题。

内存践踏检测API:mprotect()页(page)是Linux内存管理的基本单元,在32位系统中,一个页通常是4096字节,mprotect()要保护的内存单元通常要以页地址对齐,我们可以使用memalign()函数申请一个以页地址对齐的一片内存

内存检测神器:Valgrind,包含一套工具集,其中一个内存检测工具Memcheck可以对我们的内存进行内存覆盖、内存泄漏、内存越界检测

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

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

相关文章

【mysql】 bash: mysql: command not found

在linux 服务器上安装了mysql 也可以正常运行。 但是执行命令&#xff0c;系统提示&#xff1a;bash: mysql: command not found bash:mysql:找不到命令 执行的命令是&#xff1a; mysql -u root -h 127.0.0.1 -p由于系统默认会查找的 /usr/bin/ 中下的命令&#xff0c;如…

Ant Design Form.List基础用法

使用 Form.List 使用 项目中需要在新增可以多个如图 代码如下 // An highlighted block <Card title"产品信息" bordered{false}><Form.List name"productList" >{(fields, {add, remove}) > (<>{fields.map((field) > (<Ro…

XPath在数据采集中的应用:从XML和HTML中提取数据

目录 一、XPath简介 二、XPath的语法 三、XPath在数据采集中的应用 四、XPath和其他数据格式 总结 在当今的数据驱动时代&#xff0c;从各种数据源中提取有用的信息变得至关重要。其中&#xff0c;XML和HTML作为主流的数据源格式&#xff0c;常常出现在我们的数据提取任务…

电气设备漏电保护方式研究

安科瑞 崔丽洁 摘要&#xff1a;电气设备漏电故障可能对无防范意识人员产生触电危害&#xff0c;轻者灼伤人体接触位置&#xff0c;重者危及人员生命&#xff0c;甚至会产生漏电火花引起火灾&#xff0c;给企业带来不可估计的损失。文中浅谈电气设备漏电危害性及漏电保护方式&…

java模拟GPT流式问答

流式请求gpt并且流式推送相关前端页面 1&#xff09;java流式获取gpt答案 1、读取文件流的方式 使用post请求数据&#xff0c;由于gpt是eventsource的方式返回数据&#xff0c;所以格式是data&#xff1a;&#xff0c;需要手动替换一下值 /** org.apache.http.client.metho…

WebDAV之π-Disk派盘 + 恒星播放器

想要拥有一款万能视频播放器,全能解码播放器,无需转码,支持所有格式的视频和音频,直接播放的播放器?那就选恒星播放器。 恒星播放器支持视频投屏,倍速播放,后台播放等功能,还能一键截图和录制gif动图。支持全格式超高清真4K解码,蓝光HDR低占用,支持ISO文件直出的播放…

[Spring] SpringMVC 简介(二)

目录 五、域对象共享数据 1、使用 ServletAPI 向 request 域对象共享数据 2、使用 ModelAndView 向 request 域对象共享数据 3、使用 Model、Map、ModelMap 向 request 域对象共享数据 4、向 session 域和 application 域共享数据 六、SpringMVC 的视图 1、ThymeleafVie…

golang/云原生/Docker/DevOps/K8S/持续 集成/分布式/etcd 教程

3-6个月帮助学员掌握golang后端开发岗位必备技术点 教程时长: 150小时 五大核心专栏,原理源码案例分析项目实战直击工作岗位 golang&#xff1a;解决go语言编程问题 工程组件&#xff1a;解决golang工程化问题 分布式中间件&#xff1a;解决技术栈单一及分布式开发问题 云原生…

Excel 中使用数据透视图进行数据可视化

使用数据透视表&#xff08;PivotTable&#xff09;是在Excel中进行数据可视化的强大工具。下面将提供详细的步骤来使用数据透视表进行数据可视化。 **步骤一&#xff1a;准备数据** 首先&#xff0c;确保你有一个包含所需数据的Excel表格。数据应该按照一定的结构和格式组织…

使用Swift开发Framework遇到的问题及解决方法

文章目录 一、Swift 旧版本Xcode 打出来的framework 新版本不兼容问题 一、Swift 旧版本Xcode 打出来的framework 新版本不兼容问题 Cannot load module xxx built with SDK ihphoneos16.4 when using SDK iphoneos17.0:XXX/xxx.framework/Modules/xxx.swiftmodule/arm64-appl…

如何提升网站排名和用户体验:优化网站速度

网站的排名和用户满意度直接受到站点内容的加载速度影响深远。通过精心的网站优化&#xff0c;您不仅可以提高排名&#xff0c;还可以提供更出色的用户体验&#xff0c;尽管用户可能不会察觉到您的网站加载得更快&#xff0c;但这是一个非常有意义的改进。在这篇文章中&#xf…

stm32学习笔记:EXIT中断

1、中断系统 中断系统是管理和执行中断的逻辑结构&#xff0c;外部中断是众多能产生中断的外设之一。 1.中断&#xff1a; 在主程序运行过程中&#xff0c;出现了特定的中断触发条件 (中断源&#xff0c;如对于外部中断来说可以是引脚发生了电平跳变&#xff0c;对于定时器来…

4WE6Y61B/CG24N9Z5L液压电磁阀

特点 1.直动式电磁铁操作方向滑阀作为标准类型; 2.安装面按DIN24 340 A型ISO4401和CETOP-RP 121H;3.电磁铁可任意旋转&#xff0c;线圈可拆卸的直流或交流湿式电磁铁; 4.可不放油液更换线圈; 5.可带有手动应急操作推杆&#xff0c;

G1 GC详解及设置

一、概述 G1 GC&#xff0c;全称Garbage-First Garbage Collector&#xff0c;在JDK1.7中引入了G1 GC&#xff0c;从JAVA 9开始&#xff0c;G1 GC是默认的GC算法。通过-XX:UseG1GC参数来启用。G1收集器是工作在堆内不同分区上的收集器&#xff0c;分区既可以是年轻代也可以是老…

androidx和v4包资源冲突解决方法

一、资源包会报如下错误&#xff1a; 错误类似 (androidx.core:core:1.10.0) 和 (com.android.support:support-compat:24.2.0) 表示资源重复&#xff0c;不知调用androidx包下面的&#xff0c;还是v4包下面的 Duplicate class android.support.v4.app.INotificationSideCha…

一图看懂CodeArts Inspector 三大特性,带你玩转漏洞管理服务

华为云漏洞管理服务CodeArts Inspector是面向软件研发和服务运维提供的一站式漏洞管理能力&#xff0c;通过持续评估系统和应用等资产&#xff0c;内置风险量化管理和在线风险分析处置能力&#xff0c;帮助组织快速感应和响应漏洞&#xff0c;并及时有效地完成漏洞修复工作&…

探索UI设计|栅格系统的深入分析和应用

界面排版太乱了。你知道网格系统的用途吗&#xff1f;网格系统困扰着许多初级网页设计师&#xff0c;就像一个谜。如果您对网格在设计中的应用有任何疑问&#xff0c;本文是为您量身定制的&#xff0c;并深入分析UI设计中网格系统的基本要素和优点。 什么是网格系统 网格系统…

甘特图组件DHTMLX Gantt示例 - 如何有效管理团队工作时间?(一)

如果没有有效的时间管理工具&#xff0c;如工作时间日历&#xff0c;很难想象一个项目如何成功运转。这就是为什么我们的开发团队非常重视项目管理&#xff0c;并提供了多种选择来安排DHTMLX Gantt的工作时间。使用DHTMLX Gantt这个JavaScript库&#xff0c;您可以创建一个强大…

【SoC FPGA】HPS启动过程

SoC HPS启动流程 Boot ROMPreloaderBoot Loader HPS的启动是一个多阶段的过程&#xff0c;每一个阶段都会完成对应的工作并且将下一个阶段的执行代码引导起来。每个阶段均负责加载下一个阶段。第一个软件阶段是引导 ROM&#xff0c;引导 ROM 代码查找并且执行称为预加载器的第 …

消息队列 Kafka

Kafka Kafka 是一个分布式的基于发布/订阅模式的消息队列&#xff08;MQ&#xff0c;Message Queue&#xff09;&#xff0c;主要应用于大数据实时处理领域 为什么使用消息队列MQ 在高并发环境下&#xff0c;同步请求来不及处理会发生堵塞&#xff0c;从而触发too many conne…