当前位置: 首页 > 编程日记 > 正文

Linux创建线程时 内存分配的那些事

文章目录

      • 问题描述
      • 问题分析
        • 针对问题1 的猜测:
        • 针对问题2 的猜测:
      • 原理追踪
      • 总结

问题描述

事情开始于一段内存问题,通过gperf工具抓取进程运行过程中的内存占用情况。
分析结果时发现一个有趣的事情,top看到的实际物理内存只有几兆,但是pprof统计的内存信息却达到了几个G(其实这个问题用gperf heap profiler的选项也能很好的验证想法,但是还是想探索一番)。
在这里插入图片描述
很明显是创建线程时产生的内存分配,且最终的分配函数是__pthread_create_2_1,这是当前版本glibc创建线程时的实现函数,且在该函数内进行线程空间的分配。

查看进程代码,发现确实有大量的线程创建,我们知道线程是有自己独立的栈空间,top的 RES统计的是当前进程占用物理内存的情况,也就是当用户进程想要申请物理内存的时候会发出缺页异常,进程切换到内核态,由内核调用对应的系统调用取一部分物理内存加入页表交给用户态进程。这个时候,使用的物理内存的大小才会被计算到RES之中。

回到top数据和pprof抓取的内存数据对不上的问题,难道单独线程的创建并不会占用物理内存?

到现在为止可以梳理出以下几个问题:

  1. 线程的创建消耗的内存在哪里? (猜测可能在栈上,因为top的VIRT确实很大)
  2. 消耗的内存大小 是如何判断的?(目前还不太清楚,不过以上进程代码是创建了800个线程,算下来平均每个线程的大小是10M了)

问题分析

  1. 为了单独聚焦线程创建时的内存分配问题,编写如下的简单测试代码,创建800个线程:

    #include <cstdio>
    #include <cstdlib>
    #include <thread>void f(long id) {fprintf(stdout, "create thread %ld\n",id);sleep(10000);}int main()
    {long thread_num = 800;                    // client thread numstd::vector<std::thread> v;for (long id = 0;id < thread_num; ++id ) {std::thread t(f,id); t.detach();fprintf(stdout, "exit ...\n");}printf("\n");sleep(4000);  return 0;
    }
    

    单纯的创建线程,并不做其他的内存分配操作。

  2. 为了抓取该进程的内存分配过程,我们加入gperf工具来运行查看。

    #当前shell的环境变量中加入tcmalloc动态库的路径
    #如果没有tcmalloc,则yum install gperftools即可
    env LD_PRELOAD="/usr/lib/libtcmalloc.so"#编译加入链接tcmalloc的选项
    g++ -std=c++11 test.cpp -pthread -ltcmalloc#使用会生成heap profile的方式启动进程
    #开启只监控mmap,mremap,sbrk的系统调用分配内存的方式,并且ctrl+c停止运行时生成heap文件
    HEAPPROFILESIGNAL=2  HEAP_PROFILE_ONLY_MMAP=true HEAP_PROFILE_INUSE_INTERVAL=1024 HEAPPROFILE=./thread ./a.out
    
  3. 进程运行的过程中我们使用pmap查看进程内存空间的分配情况
    pmap -X PID
    输出信息如下
    在这里插入图片描述
    其中:
    address为进程的虚拟地址
    size为当前字段分配的虚拟内存的大小,单位是KB
    Rss为占用的物理内存的大小
    Mapping为内存所处的区域

统计了一下size:10240KB 的区域刚好是800个,显然该区域为线程空间。所处的进程内存区域也不在heap上,占用的物理内存大小大小也就是一个指针的大小,8B
使用pmap PID再次查看发现线程的空间都分布在anno区域上,即使用的匿名页的方式
在这里插入图片描述

匿名页的描述信息如下:

The amount of anonymous memory is reported for each mapping. Anonymous memory shared with other address spaces is not included, unless the -a option is specified.
Anonymous memory is reported for the process heap, stack, for ‘copy on write’ pages with mappings mapped with MAP_PRIVATE.

即匿名页是使用mmap方式分配的,且会将使用的内存叶标记为MAP_PRIVATE,即仅为进程用户空间独立使用。

针对问题1 的猜测:

到现在为止我们通过工具发现了线程的内存分配貌似是通过mmap,使用匿名页的方式分配出来的,因为匿名页能够和其他进程共享内存空间,所以不会被计入当前进程的物理内存区域。
关于进程的内存分布可以参考进程内存分布,匿名页是在堆区域和栈区域之间的一部分内存区域,pmap的输出我们也能看出来mmapping的那一列。

针对问题2 的猜测:

那为什么会占用10M的虚拟内存呢(size那一列),显然也很好理解了。因为线程是独享自己的栈空间的,所以需要为每个线程开辟属于自己的函数栈空间来保存函数栈帧和局部变量。
ulimit -a能够看到stack size 那一行是属于当前系统默认的进程栈空间的大小。

这里可以通过ulimit -s 2048 将系统的默认分配的栈的大小设置为2M,再次运行程序会发现线程的虚拟内存占用变为了2M

是不是很有趣。
到了这里,我们仅仅是使用工具进行了线程内存的占用分析,但问题并没有追到底层。

原理追踪

我们上面使用了gperf的heap proflie运行了程序,此时我们ctrl+c终端进程之后会在当前目录下生成很多个.heap文件,使用pprof 的svg选项将文件内容导出
pprof --svg a.out thread.0001.heap > thread.svg
将导出的thread.svg放入浏览器中可以看到线程内存占用的一个calltrace,如下(如果程序中链入了glibc以及内核的静态库,估计calltrace会庞大很多):
在这里插入图片描述
也就是线程创建时的栈空间的分配最终是由函数__pthread_create_2_1分配的。

PS:这里的calltrace 仅仅包括mmap,mremap,sbrk的分配,因为我们在进程运行的时候指定了HEAP_PROFILE_ONLY_MMAP=true 选项,如果各位仅仅想要确认malloc,calloc,realloc等在堆上分配的内存大小可以去掉该选项来运行进程。
输出svg的时候增加pprof的--ignore选项来忽略mmap,sbrk的分配内存,这样的calltrace就没有他们的内存占用了,仅包括堆上的内存占用
pprof --ignore='DoAllocWithArena|SbrkSysAllocator::Alloc|MmapSysAllocator::Alloc' --svg a.out thread.0001.heap > thread.svg

查看glibc的线程创建源码pthread_create.c
函数__pthread_create_2_1 调用ALLOCATE_STACK为线程的数据结构pd分配内存空间。

versioned_symbol (libpthread, __pthread_create_2_1, pthread_create, GLIBC_2_1)int
__pthread_create_2_1 (newthread, attr, start_routine, arg)pthread_t *newthread;const pthread_attr_t *attr;void *(*start_routine) (void *);void *arg;
{......struct pthread *pd = NULL;int err = ALLOCATE_STACK (iattr, &pd);if (__builtin_expect (err != 0, 0)......
}

ALLOCATE_STACK函数实现入下allocatestack.c
分配的空间大小会优先从用户设置的pthread_attr属性 attr.stacksize中获取,如果用户进程没有设置stacksize,就会获取系统默认的stacksize的大小。

接下来会调用get_cached_stack函数来获取栈上面可以获得的空间大小size以及所处的虚拟内存空间的地址mem。

最后通过mmap将当前线程所需要的内存叶标记为MAP_PRIVATE和MAP_ANONYMOUS表示当前内存区域仅属于用户进程且被用户进程共享。

详细实现如下:

static int
allocate_stack (const struct pthread_attr *attr, struct pthread **pdp,ALLOCATE_STACK_PARMS)
{....../* Get the stack size from the attribute if it is set.  Otherwise weuse the default we determined at start time.  */size = attr->stacksize ?: __default_stacksize;......void *mem;....../* Try to get a stack from the cache.  */reqsize = size;pd = get_cached_stack (&size, &mem);if (pd == NULL){/* To avoid aliasing effects on a larger scale than pages weadjust the allocated stack size if necessary.  This wayallocations directly following each other will not havealiasing problems.  */#if MULTI_PAGE_ALIASING != 0if ((size % MULTI_PAGE_ALIASING) == 0)size += pagesize_m1 + 1;#endif/*mmap分配物理内存,并进行内存区域的标记*/mem = mmap (NULL, size, prot,MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);if (__builtin_expect (mem == MAP_FAILED, 0)){if (errno == ENOMEM)__set_errno (EAGAIN);return errno;}

总结

glibc用户态的调用到最后仍然还是内核态进行实际的物理操作。
至此,关于线程创建时的内存分配追踪就到这里了。我们会发现操作系统的博大精深和环环相扣,使用一个个工具验证自己的猜测, 再从原理发掘前人的设计,这样就会对整个链路有了一个更加深刻的理解。

至于更加底层的内核实现,如何将物理内存与用户进程进行隔离且互不影响,这又是一段庞大复杂的设计链路。有趣的事情很多,慢慢来~

相关文章:

mysql plsql循环语句吗,Oracle PLSQL 在游标中用while循环实例程序

Oracle PLSQL 在游标中用while循环实例程序Oracle PLSQL 在游标中用while循环实例程序Oracle PLSQL 在游标中用while循环实例程序declarecursor emp_cur is select * from emp;v_emp emp%rowType;beginopen emp_cur;while emp_cur%notfound --while肯定要跟loop一起用的 且是控…

【原创】Linux环境下的图形系统和AMD R600显卡编程(11)——R600指令集

1 低级着色语言tgsi OpenGL程序使用GLSL语言对可编程图形处理器进行编程&#xff0c;GLSL语言&#xff08;以下高级着色语言就是指GLSL&#xff09;是语法类似C的高级语言&#xff0c;在GLSL规范中&#xff0c;GLSL语言被先翻译成教低级的类汇编语言&#xff0c;然后被翻译成硬…

VBScript中InStr函数的用法

InStr([start, ]str1, str2[, compare]) [用途]&#xff1a;返回str2在str1中的位置。匹配成功时&#xff0c;返回值最小值为1&#xff0c;未匹配到时返回0。 [参数说明]&#xff1a; start:在str1中开始匹配的位置&#xff0c;1表示从头开始&#xff0c;不能为0或更小值。 可选…

洛谷P3122 [USACO15FEB]圈住牛Fencing the Herd(计算几何+CDQ分治)

题面 传送门 题解 题目转化一下就是所有点都在直线\(AxBy-C0\)的同一侧&#xff0c;也就可以看做所有点代入\(AxBy-C\)之后的值符号相同&#xff0c;我们只要维护每一个点代入直线之后的最大值和最小值&#xff0c;看看每条直线的最大最小值符号是否相同就好了 以最大值为例&am…

skiplist跳表的 实现

文章目录前言跳表结构时间复杂度空间复杂度高效的动态插入和删除跳表索引的动态更新总结详细实现前言 rocksdb 的memtable中默认使用跳表数据结构对有序数据进行的管理&#xff0c;为什么呢&#xff1f; 同时redis 也用跳表作为管理自己有序集合的数据结构&#xff0c;为什么…

php的反射作用是什么意思,php反射的作用是什么

反射是在PHP运行状态中&#xff0c;扩展分析PHP程序&#xff0c;导出或提取出关于类、方法、属性、参数等的详细信息&#xff0c;包括注释。这种动态获取的信息以及动态调用对象的方法的功能称为反射API。反射是操纵面向对象范型中元模型的API&#xff0c;其功能十分强大&#…

《BI项目笔记》用Excel2013连接和浏览OLAP多维数据集

《BI项目笔记》用Excel2013连接和浏览OLAP多维数据集 原文:《BI项目笔记》用Excel2013连接和浏览OLAP多维数据集用Excel2013连接和浏览OLAP多维数据集 posted on 2014-12-02 08:58 NET未来之路 阅读(...) 评论(...) 编辑 收藏 转载于:https://www.cnblogs.com/lonelyxmas/p/413…

mac 拷贝文件时报错 8060 解决方案

解决如下&#xff1a; 即某文件夹下出现多重子目录&#xff0c;级数很多&#xff0c;删除多余的子文件夹即可。 至于如何产生的&#xff0c;有人说是xcode升级导致&#xff0c;不过没有见证 。我的不属于这类情况的。 &#xff08;参见&#xff1a;http://macosx.com/forums/ma…

C#连接数据库

VScode 配置C#环境 https://blog.csdn.net/qq_40346899/article/details/80955788VScode 配置C#开发环境 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;using System.Data; using System.Data.SqlCli…

C++ 中emplace_back和push_back差异

前言 最近看rocskdb源码&#xff0c;发现了大量的设计模式和C高级特性&#xff0c;特此补充一下&#xff0c;巩固基础。 问题描述 其中关于动态数组的元素添加&#xff0c;代码中基本将push_back抛弃掉了&#xff0c;全部替换为emplace_back进行元素的添加。 看了一下官网描…

[51单片机学习笔记ONE]-----LED灯的多种使用方法

一.交替闪烁8个LED灯&#xff0c;时间间隔为1s 1 /******************************************************2 实验名称&#xff1a; 交替闪烁8个LED灯,时间间隔1s3 实验时间&#xff1a; 2014年12月2日4 ******************************************************/…

php 伪协议 lfi,php://伪协议(I/O)总能给你惊喜——Bugku CTF-welcome to bugkuctf

今天一大早BugkuCTF 的welcome to bugkuctf 就给了我一发暴击&#xff1a;完全不会啊。。。光看源码就发现不知道怎么处理了&#xff0c;于是转向writeup求助。结果发现这是一道非常有营养的题目&#xff0c;赶紧记录一下。题目链接&#xff1a;http://123.206.87.240:8006/tes…

Pascal's Triangle

帕斯卡三角形&#xff0c;主要考察vector的用法。 vector<vector<int> > generate(int numRows){vector<vector<int> > result;vector<int> tmp;result.clear();tmp.clear();int i,j;if(numRows 0)return result;else if(numRows 1){tmp.push_…

SpringBoot请求转发与重定向

但是可能由于B网址相对于A网址过于复杂,这样搜索引擎就会觉得网址A对用户更加友好,因而在重定向之后任然显示旧的网址A,但是显示网址B的内容。在平常使用手机的过程当中,有时候会发现网页上会有浮动的窗口,或者访问的页面不是正常的页面,这就可能是运营商通过某种方式篡改了用户正常访问的页面。重定向,是指在Nginx中,重定向是指通过修改URL地址,将客户端的请求重定向到另一个URL地址的过程,Nginx中实现重定向的方式有多种,比如使用rewrite模块、return指令等。使用场景:在返回视图的前面加上。

SSO 单点登录和 OAuth2.0 有何区别?

此方法的缺点是它依赖于浏览器和会话状态,对于分布式或者微服务系统而言,可能需要在服务端做会话共享,但是服务端会话共享效率比较低,这不是一个好的方案。在单点登录的上下文中,OAuth 可以用作一个中介,用户在一个“授权服务器”上登录,并获得一个访问令牌,该令牌可以用于访问其他“资源服务器”上的资源。首先,SSO 主要关注用户在多个应用程序和服务之间的无缝切换和保持登录状态的问题。这种方法通过将登录认证和业务系统分离,使用独立的登录中心,实现了在登录中心登录后,所有相关的业务系统都能免登录访问资源。

【转】linux服务器性能查看

转载自https://blog.csdn.net/achenyuan/article/details/78974729 1.1 cpu性能查看 1、查看物理cpu个数&#xff1a; cat /proc/cpuinfo |grep "physical id"|sort|uniq|wc -l 2、查看每个物理cpu中的core个数&#xff1a; cat /proc/cpuinfo |grep "cpu cores…

Rocksdb 内存“不释放”问题 分析

文章目录问题场景描述问题复现编写随机写 测试工具使用工具抓取内存分配过程源码分析memtable逻辑table_cache逻辑总结整体的IO场景到底层的源码分析过程如上导图&#xff0c;接下来将详细阐述具体的过程。问题场景描述 我们的rocksdb作为单机存储引擎&#xff0c;跑在用分布式…

GitHub上整理的一些工具【转载】

技术站点Hacker News&#xff1a;非常棒的针对编程的链接聚合网站Programming reddit&#xff1a;同上MSDN&#xff1a;微软相关的官方技术集中地&#xff0c;主要是文档类infoq&#xff1a;企业级应用&#xff0c;关注软件开发领域OSChina&#xff1a;开源技术社区&#xff0c…

show在php,show.php

我的留言板function dodel(id){if(confirm("确定要删除么&#xff1f;")){window.location del.php?idid;}}我的留言板添加留言查看留言查看留言留言标题留言人留言内容IP地址留言时间操作// 获取留言信息&#xff0c;解析后输出到表格中// 1.从留言liuyan.txt中获取…

#天天复制,今天写一个# 把文字转为图片

/*** 把文字转为图片* * param text* 要写的内容* throws IOException*/public static void textToImg(String text) throws IOException {int len text.length();int fontSize 1000;int width len * fontSize;Font font new Font("楷体", Font2D.NAT…

spark(3) - scala独立编程

>>非集成&#xff1a; 环境需要 * spark 2.4.0 * scala 2.11.12 * sbt &#xff08;打包&#xff09; 开发过程 1、shell命令下创建项目目录结构 *****/ project / src / main / scala -> . / ClassName.scala &#xff08; touch gedit 命令&#xff09; …

C++ STL: 基本六大部件概览 及 各个容器使用方式和底层实现概览

文章目录STL六大部件容器的使用Arrayvectorlistdequemutisetmultimapunordered_multiset/set使用一个东西&#xff0c;却不明白它的道理&#xff0c;不高明。STL六大部件 容器 Containers 用来存放数据分配器 Allocators 为容器内的数据分配存储空间算法 Algorithms 计算数据迭…

Android窗口管理服务WindowManagerService计算窗口Z轴位置的过程分析

文章转载至CSDN社区罗升阳的安卓之旅&#xff0c;原文地址&#xff1a;http://blog.csdn.net/luoshengyang/article/details/8570428 通过前面几篇文章的学习&#xff0c;我们知道了在 Android系统中&#xff0c;无论是普通的Activity窗口&#xff0c;还是特殊的输入法窗口和壁…

oracle非归档模式下如何备份,Oracle之RMAN数据库在非归档模式下的备份和恢复

1.数据库在非归档模式下的备份 SQLgt; archive log list;数据库日志模式 非存档模式自动存档 禁用存档终点 USE_DB_RECOVERY_FIL1.数据库在非归档模式下的备份SQL> archive log list;数据库日志模式 非存档模式自动存档 禁用存档终点 USE_DB_RECOVERY_FILE_DEST最早的联机日…

C# 视频多人脸识别的实现

上一篇内容的调整&#xff0c;提交到git了&#xff0c;https://github.com/catzhou2002/ArcFaceDemo基本思路如下&#xff1a;一、识别线程1.获取当前图片2.识别当前图片的人脸位置&#xff0c;并将结果存入列表3.分别获取人脸的特征值并比对&#xff0c;并将结果存入列表4.如果…

C++ STL: 分配器allocators 源码分析

STL 基本的六大组件作用以及功能如下&#xff1a; 可以看到allocator是数据存储组件container的幕后支持者&#xff0c;负责为其数据存储分配对应的存储空间。 operator::new 在详细介绍alloctor之前&#xff0c;先描述一下new运算符&#xff0c;我们使用C new一个对象的时候…

android xUtils的使用

gethub地址&#xff1a;https://github.com/wyouflf/xUtils/ xUtils简介 xUtils 包含了很多实用的android工具。xUtils 支持大文件上传&#xff0c;更全面的http请求协议支持(10种谓词)&#xff0c;拥有更加灵活的ORM&#xff0c;更多的事件注解支持且不受混淆影响...xUitls 最…

oracle 条件反转,Oracle反转倒置函数

Oracle提供了一个反转倒置函数reverse&#xff0c;但此函数不能分组倒置&#xff0c;本文提供了一个即可分组倒置的函数&#xff0c;如下所示&#xff1a;CREATE OR REPLACE FUNCTION REVERSE_F(p_str VARCHAR2, p_delimiter VARCHAR2:)RETURN VARCHAR2 ISv_return VARCHAR2(40…

android读取大图片并缓存

最近开发电视版的云存储应用&#xff0c;要求”我的相册“模块有全屏预览图片的功能&#xff0c;全屏分辨率是1920*1080超清。UI组件方面采用GalleryImageSwitcher组合&#xff0c;这里略过&#xff0c;详情参见google Android API。相册图片预取缓存策略是内存缓存&#xff08…

[ZJOI2018]历史

Description: 给定一棵树,定义每个点的操作为把这个点到1号点的路径覆盖上颜色i,每次该点到1号点经过的不同颜色段数会加到答案中,要使所有点按某一顺序操作完后答案最大 给定每个点要执行的操作次数,并给出m次修改,问每次修改后的最大答案 Hint: \(n,m \le 4*10^5\) Solution:…