C++ 全栈知识体系C++ 全栈知识体系
✿导航
  • 基础
  • 函数
  • 知识点
  • IO框架
  • 新版本特性
  • 数据库原理
  • SQL语言
  • SQL - MySQL
  • NoSQL - Redis
  • NoSQL - ElasticSearch
  • 算法基础
  • 常见算法
  • 领域算法
  • 分布式算法
  • 数据结构与算法
  • 计算机网络
  • 操作系统
  • 计算机组成
  • 开发
  • 测试
  • 架构基础
  • 分布式系统
  • 微服务
  • 中间件
  • 概念
  • 理论
  • 架构设计原则
  • 设计模式
  • 协议
  • 技术选型
  • 编码规范
  • 流水线构建 - CI/CD
  • 知识点 - Linux
  • 网站 - Nginx
  • 容器化 - Docker
  • 容器编排 - Kubernetes
  • 服务网格 - Service Mesh Istio
  • 常用快捷键 - Shortcut
  • 工具使用 - Tools
  • 开源项目
  • 学习项目
  • 个人项目
  • 项目开发
  • 项目Idea
  • 并发
  • 部署
  • 分布式
  • 知识
  • 问题
  • 编程语言与技术
  • 系统与架构
  • 软件开发实践
  • 数据处理与应用设计
  • 个人
  • 产品
  • 团队
  • 知识体系
  • Vue
关于
✿导航
  • 基础
  • 函数
  • 知识点
  • IO框架
  • 新版本特性
  • 数据库原理
  • SQL语言
  • SQL - MySQL
  • NoSQL - Redis
  • NoSQL - ElasticSearch
  • 算法基础
  • 常见算法
  • 领域算法
  • 分布式算法
  • 数据结构与算法
  • 计算机网络
  • 操作系统
  • 计算机组成
  • 开发
  • 测试
  • 架构基础
  • 分布式系统
  • 微服务
  • 中间件
  • 概念
  • 理论
  • 架构设计原则
  • 设计模式
  • 协议
  • 技术选型
  • 编码规范
  • 流水线构建 - CI/CD
  • 知识点 - Linux
  • 网站 - Nginx
  • 容器化 - Docker
  • 容器编排 - Kubernetes
  • 服务网格 - Service Mesh Istio
  • 常用快捷键 - Shortcut
  • 工具使用 - Tools
  • 开源项目
  • 学习项目
  • 个人项目
  • 项目开发
  • 项目Idea
  • 并发
  • 部署
  • 分布式
  • 知识
  • 问题
  • 编程语言与技术
  • 系统与架构
  • 软件开发实践
  • 数据处理与应用设计
  • 个人
  • 产品
  • 团队
  • 知识体系
  • Vue
关于
  • 编程语言与技术

    • Effective C++: 改善程序与设计的55个具体做法

      • 第2章 - 构造/析构/赋值运算(一)
      • 第2章 - 构造/析构/赋值运算(二)
      • 第2章 - 构造/析构/赋值运算(三)
      • 第3章 - 资源管理
      • 第4章 - 设计与声明(一)
      • 第4章 - 设计与声明(二)
      • 第5章 - 实现(一)
      • 第5章 - 实现(二)
      • 第6章 - 继承与面向对象设计
      • 第7章 - 模板与泛型编程
    • 深度探索C++对象模型

      • 第1章 - 关于对象
      • 第2章 - 构造函数语意学
      • 第3章 - Data 语意学
    • STL源码剖析

      • 第1章 - STL概论和版本简介
      • 第2章 - 空间配置器
      • 第3章 - 迭代器(iterators)概念与traits编程技法(一)
      • 第3章 - 迭代器(iterators)概念与traits编程技法(二)
      • 第4章 - 序列式容器 vector
      • 第4章 - 序列式容器 list
      • 第4章 - 序列式容器 deque
      • 第4章 - 序列式容器 stack和queue
      • 第4章 - 序列式容器 heap
      • 第4章 - 序列式容器 priority_queue
      • 第4章 - 序列式容器 slist
      • 第5章 - 关联式容器 RB-tree
      • 第5章 - 关联式容器 set和map
      • 第5章 - 关联式容器 hashtable
      • 第6章 - 算法
      • 第6章 - 算法之set
      • 第7章 - 仿函数
      • 第8章 - 配接器
  • 系统与架构

    • 深入理解计算机系统

      • 第1章 - 计算机系统漫游
      • 第2章 - 信息的表示和处理
      • 第3章 - 程序的机器级表示
      • 第5章 - 优化程序性能
      • 第6章 - 存储器层次结构
      • 第7章 - 链接
      • 第8章 - 异常控制流
      • 第9章 - 虚拟内存
      • 第10章 - 系统级I/O
      • 第11章 - 网络编程
      • 第12章 - 并发编程
    • 大型网站技术架构——核心原理与案例分析

      • 第1章 - 大型网站架构演化
      • 第2章 - 大型网站架构模式
      • 第3章 - 大型网站核心架构要素
      • 第4章 - 瞬时响应:网站的高性能架构
      • 第5章 - 万无一失:网站的高可用架构
      • 第6章 - 永无止境:网站的伸缩性架构
      • 第7章 - 随需应变:网站的可扩展架构
      • 第8章 - 固若金汤:网站的安全架构
    • 从零开始学架构

      • 架构基础
      • 架构设计原则
      • 高性能架构
      • 高可用架构
    • 程序员的自我修养————链接、装载与库

      • 第1章 - 简介
      • 第2章 - 静态链路
      • 第3章 - 目标文件里有什么
      • 第4章 - 静态链接
      • 第7章 - 动态链接
      • 第8章 - 共享库版本
      • 第10章 - 内存
      • 第11章 - 运行库
      • 第12章 - 系统调用与API
      • 第13章 - 运行库实现
  • 软件开发实践

    • 重构改善既有代码的设计

      • 第1章 - 重构,第一个示例
      • 第2章 - 重构的原则
      • 第3章 - 代码的坏味道
      • 第5章 - 重构列表
      • 第6章 - 重新组织函数
      • 第7章 - 在对象之间搬移特性
      • 第8章 - 重新组织数据
      • 第9章 - 简化条件表达式
      • 第10章 - 简化函数调用
      • 第11章 - 处理概括关系
      • 第12章 - 设计之大型重构
    • 代码大全2

      • 第1章 - 欢迎进入软件构建的世界
      • 第2章 - 用隐喻来更充分地理解软件开发
      • 第3章 - 三思而后行: 前期准备
      • 第4章 - 关键的构建决策
      • 第5章 - 软件构建中的设计
    • Linux多线程服务端编程——使用muduo C++ 网络库

      • Buffer类的设计
      • 设计与实现
      • 定时器与TimerQueue
      • Protobuf网络传输和Protobuf编解码器与消息分发器
      • EventLoop类剖析
      • EventLoopThread和EventLoopThreadPool剖析
      • TCP网络库和核心类
      • Connector剖析
      • TcpClient剖析
      • 学习总结
      • timing wheel
      • 消息广播服务
      • 线程安全的对象生命期管理
  • 数据处理与应用设计

    • 数据密集型应用系统设计

      • 第1章 - 可靠、可扩展与可维护的应用系统
      • 第2章 - 数据模型与查询语言
      • 第3章 - 数据存储与检索
      • 第4章 - 数据编码与演化
      • 第5章 - 数据复制
      • 第6章 - 数据分区
      • 第7章 - 事务

第10章 内存

  • 栈和调用惯例
    • 调用惯例
    • 函数返回值传递
  • 堆与内存管理
  • Linux进程堆管理
  • 堆分配算法

文章总结

程序环境

程序的内存布局

Linux默认将高地址的1GB空间分配给内核,用户使用剩下2GB或3GB的内存空间,也称用户空间。

一个典型的Linux进程地址空间分布如下:

段错误(segment fault)或 非法操作,该内存地址不能 read/write 的错误信息。

栈和调用惯例

栈保存了一个函数调用所需要的维护信息,常被称为堆栈帧(Stack Frame)或活动记录(Activate Record)。

堆栈帧一般包括如下几方面内容:

  • 函数的返回地址和参数。
  • 临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量。
  • 保存的上下文:包括在函数调用前后需要保持不变的寄存器。

一个函数的活动记录用ebp和esp这两个寄存器划定范围。esp指向栈的顶部,ebp指向函数活动记录的一个固定位置,又称帧指针(Frame Pointer)

int foo()
{
    return 123;
}

foo函数汇编代码分析:

连续的0xCCCC,汉字编码就是“烫”;0xCDCD,汉字编码就是“屯”。

调用惯例

调用惯例:函数的调用方和被调用方对函数如何调用有着统一的理解。

调用惯例规定内容:

  • 函数参数的传递顺序和方式
  • 栈的维护方式
  • 名字修饰的策略

foo函数

int foo(int n, float m)
{
    int a=0, b=0;
    ...
}

foo函数栈布局:

函数返回值传递

eax是传递返回值的通道,8字节对象用eax和edx联合返回方式进行。大于8字节的返回类型,有以下处理:

  • 首先main函数在栈上额外开辟了一片空间,并将这块空间的一部分作为传递返回值的临时对象,这里称为temp。
  • 将temp对象的地址作为隐藏参数传递给return_test函数。
  • return_test函数将数据拷贝给temp对象,并将temp对象的地址用eax传出。
  • return_test返回之后,main函数将eax指向的temp对象的内容拷贝给n。

堆与内存管理

因为每次程序申请或者释放堆空间都需要进行系统调用,性能开销是很大的。比较好的做法就是程序向操作系统申清一块适当大小的堆空间,然后由程序自己管理这块空间,而具体来讲,管理着堆空间分配的往往是程序的运行库。 ———— tcmalloc

**运行库相当于是向操作系统“批发”了一块较大的堆空间,然后“零售”给程序用。**当全部“售完”或程序有大量的内存需求时,再根据实际需求向操作系统“进货”。当然运行 库在向程序零售堆空间时,必须管理它批发来的堆空间,不能把同一块地址出售两次,导致地址的冲突。于是运行库需要一个算法来管理堆空间,这个算法就是堆的分配算法。

Linux进程堆管理

Linux下的进程堆管理稍微有些复杂,因为它提供了两种堆空间分配的方式,即两个系统调用:一个是brk()系统调用,另外一个是mmap()。

int brk(void* end_data_segment)

brk()的作用就是设置进程数据段的结束地址,即它可以扩大或者缩小数据段(Linux下数据段和BSS合并在一起统称数据段)。如果我们将数据段的结束地址向高地址 移动,那么扩大的那部分空间就可以被我们使用,把这块空间拿来作为堆空间是最常见的做法。

mmap()的作用就是向操作系统申请一段虚拟地址空间,当然这块虚拟地址空间可以映射到某个文件(这也是这个系统调用的最初的作用),当它不将地址空间映射到某个文件时,我们又称这块空间为匿名(Anonymous)空间,匿名空间就可以拿来作为堆空间。它的声明如下:

man 2 mmap

#include<sys/mman.h>
/* 创建映射区 */
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);		
/* 释放映射区 */
int munmap(void *addr, size_t length);

mmap参数:
addr:指定映射区的首地址。通常传NULL,表示让系统自动分配
length:共享映射区的大小(<= 文件实际大小)
prot:共享内存区的读写属性
     PROT_READ
     PROT_WRITE
     PROT_READ | PROT_WRITE
flag:标注共享内存的共享属性
     MAP_SHARED(对内存的修改会反映到磁盘上)
     MAP_PRIVATE(对内存的修改不会反映到磁盘上)
fd:用于创建共享内存映射区的那个文件的 文件描述符
offset:从文件偏移位置开始建立映射区 (必须是4k的整数倍4096)默认0,表示映射文件全部

参考:https://blog.csdn.net/m0_73604721/article/details/127928389

glibc的malloc函数处理机制:

  • 当开辟的空间小于 128K 时,调用brk()函数,malloc 的底层实现是系统调用函数 brk(),其主要移动指针 _enddata。
  • 当开辟的空间大于 128K 时,调用mmap()函数来分配一块匿名空间,然后在这个匿名空间中为用户分配空间。(mmap申请的空间的起始地址和大小都是必须是系统页大小的整数倍)

知识点:

内存和交换空间是计算机系统中用于存储数据和程序的两种重要资源。

内存是指计算机系统中的主存,用于临时存储当前正在执行的程序和数据。它是由物理内存芯片组成的,通过内存地址可以进行读取和写入操作。内存速度快,可以快速访问数据,但容量较有限。一般来说,内存中存放的是当前活跃的程序和数据,当程序执行完成或者不再活跃时,内存中的数据会被释放。

交换空间(也称为交换文件或虚拟内存)是计算机系统中的一部分磁盘空间,用于扩展可用的内存容量。当内存不足时,操作系统会将暂时不活跃或不常用的数据和程序从内存中移出,存储到交换空间中。这样可以释放内存空间,以便给活跃的程序和数据提供更多的空间。当需要访问被交换出去的数据时,操作系统会将其重新载入到内存中。然而,与内存相比,从交换空间中加载数据需要较长的时间,因为磁盘访问速度相对较慢。

内存和交换空间的组合可以帮助操作系统更有效地管理系统资源。内存用于存储活跃的程序和数据,而交换空间用于存储不活跃或不常用的数据。当需要更多内存时,可以将一部分内存中的数据移到交换空间,从而为活跃的程序和数据提供更多可用空间。

堆分配算法

堆分配算法:如何管理一大块连续的内存空间,能够按照需求分配、释放其中的空间。

  • 空闲链表:把堆中各个空闲的块按照链表的方式连接起来,当用户请求一块空间时,可以遍历整个列表,直到找到合适大小的块并且将它拆分:当用户释放空间时将它合并到空闲链表中。

  • 位图:将整个堆划分为大量的块(block),每个块的大小相同。当用户请求内存的时候,总是分配整数个块的空间给用户,第一个块我们称为已分配区域的头(Head),其余的称为已分配区域的主体(Body)。而我们可以使用一个整数数组来记录块的使用情况,由于每个块只有头/主体/空闲三种状态,因此仅仅需要两位即可表示一个块,因此称为位图。

这个堆分配了3片内存,分别有2/4/1个块,用虚线框标出。其对应的位图将是: (HIGE) 11 00 00 10 10 10 11 00 00 00 00 00 00 00 10 11 (LOW)

其中11表示(Head),10表示主体(Body),00表示空闲(Free)。

  • 对象池:如果每一次分配的空间大小都一样,那么就可以按照这个每次请求分配的大小作为一个单位,把整个堆空间划分为大量的小块,每次请求的时候只需要找到一个小块就可以了。对象池的管理方法可以采用空闲链表,也可以采用位图,与它们的区别仅仅在于它假定了每次请求的都是一个固定的大小,因此实现起来很容易。

实际上很多现实应用中,堆的分配算法往往是采取多种算法复合而成的。比如对于glibc来说,它对于小于64字节的空间申请是采用类似于对象池的方法:而对于大于512字节的空间申请采用的是最佳适配算法:对于大于64字节而小于512字节的,它会根据情况采取上述方法中的最佳折中策略;对于大于128KB的申请,它会使用mmap机制直接向操作系统申请空间。

Last Updated:
Contributors: klc407073648
Prev
第8章 - 共享库版本
Next
第11章 - 运行库