关系经济人类预测化学自然
自然科学
知识物理
化学生物
地理解释
预测理解
本质社会
人类现象
行为研究
经济政治
心理结构
关系指导
人文遗产

一文读懂Linux内存分配策略

9月13日 飞仙轩投稿
  本篇主要以问答的方式来探索Linux内存系统的分配策略Linux进程的内存分布长什么样?
  在Linux操作系统中,虚拟地址空间的内部又被分为内核空间和用户空间两部分,不同位数的系统,地址空间的范围也不同。比如最常见的32位和64位系统,如下所示:
  通过这里可以看出:32位系统的内核空间占用1G,位于最高处,剩下的3G是用户空间;64位系统的内核空间和用户空间都是128T,分别占据整个内存空间的最高和最低处,剩下的中间部分是未定义的。
  再来说说,内核空间与用户空间的区别:进程在用户态时,只能访问用户空间内存;只有进入内核态后,才可以访问内核空间的内存;
  虽然每个进程都各自有独立的虚拟内存,但是每个虚拟内存中的内核地址,其实关联的都是相同的物理内存。这样,进程切换到内核态后,就可以很方便地访问内核空间内存。
  接下来,进一步了解虚拟空间的划分情况,用户空间和内核空间划分的方式是不同的,内核空间的分布情况就不多说了。
  我们看看用户空间分布的情况,以32位系统为例,我画了一张图来表示它们的关系:
  通过这张图你可以看到,用户空间内存从低到高分别是6种不同的内存段:
  程序文件段,包括二进制可执行代码;已初始化数据段,包括静态常量;未初始化数据段,包括未初始化的静态变量;堆段,包括动态分配的内存,从低地址开始向上增长;文件映射段,包括动态库、共享内存等,从低地址开始向上增长(跟硬件和内核版本有关);栈段,包括局部变量和函数调用的上下文等。栈的大小是固定的,一般是8MB。当然系统也提供了参数,以便我们自定义大小;
  在这6个内存段中,堆和文件映射段的内存是动态分配的。比如说,使用C标准库的malloc()或者mmap(),就可以分别在堆和文件映射段动态分配内存。malloc是如何分配内存的?
  实际上,malloc()并不是系统调用,而是C库里的函数,用于动态分配内存。
  malloc申请内存的时候,会有两种方式向操作系统申请堆内存。方式一:通过brk()系统调用从堆分配内存方式二:通过mmap()系统调用在文件映射区域分配内存;
  方式一实现的方式很简单,就是通过brk()函数将堆顶指针向高地址移动,获得新的内存空间。如下图:
  方式二通过mmap()系统调用中私有匿名映射的方式,在文件映射区分配一块内存,也就是从文件映射区偷了一块内存。如下图:
  什么场景下malloc()会通过brk()分配内存?又是什么场景下通过mmap()分配内存?
  malloc()源码里默认定义了一个阈值:如果用户分配的内存小于128KB,则通过brk()申请内存;如果用户分配的内存大于128KB,则通过mmap()申请内存;
  注意,不同的glibc版本定义的阈值也是不同的。
  malloc()分配的是物理内存吗?
  不是的,malloc()分配的是虚拟内存。
  如果分配后的虚拟内存没有被访问的话,虚拟内存是不会映射到物理内存的,这样就不会占用物理内存了。
  只有在访问已分配的虚拟地址空间的时候,操作系统通过查找页表,发现虚拟内存对应的页没有在物理内存中,就会触发缺页中断,然后操作系统会建立虚拟内存和物理内存之间的映射关系。malloc(1)会分配多大的虚拟内存?
  malloc()在分配内存的时候,并不是老老实实按用户预期申请的字节数来分配内存空间大小,而是会预分配更大的空间作为内存池。
  具体会预分配多大的空间,跟malloc使用的内存管理器有关系,我们就以malloc默认的内存管理器(Ptmalloc2)来分析。
  接下里,我们做个实验,用下面这个代码,通过malloc申请1字节的内存时,看看操作系统实际分配了多大的内存空间。includestdio。hincludemalloc。hintmain(){printf(使用catprocdmaps查看内存分配,getpid());申请1字节的内存voidaddrmalloc(1);printf(此1字节的内存起始地址:x,addr);printf(使用catprocdmaps查看内存分配,getpid());将程序阻塞,当输入任意字符时才往下执行getchar();释放内存free(addr);printf(释放了1字节的内存,但heap堆并不会释放);getchar();return0;}
  执行代码(先提前说明,我使用的glibc库的版本是2。17):
  我们可以通过procmaps文件查看进程的内存分布情况。我在maps文件通过此1字节的内存起始地址过滤出了内存地址的范围。〔rootxiaolin〕catproc3191mapsgrepd73000d7300000d94000rwp0000000000:000〔heap〕
  这个例子分配的内存小于128KB,所以是通过brk()系统调用向堆空间申请的内存,因此可以看到最右边有〔heap〕的标识。
  可以看到,堆空间的内存地址范围是00d7300000d94000,这个范围大小是132KB,也就说明了malloc(1)实际上预分配132K字节的内存。
  可能有的同学注意到了,程序里打印的内存起始地址是d73010,而maps文件显示堆内存空间的起始地址是d73000,为什么会多出来0x10(16字节)呢?这个问题,我们先放着,后面会说。
  free释放内存,会归还给操作系统吗?
  我们在上面的进程往下执行,看看通过free()函数释放内存后,堆内存还在吗?
  从下图可以看到,通过free释放内存后,堆内存还是存在的,并没有归还给操作系统。
  这是因为与其把这1字节释放给操作系统,不如先缓存着放进malloc的内存池里,当进程再次申请1字节的内存时就可以直接复用,这样速度快了很多。
  当然,当进程退出后,操作系统就会回收进程的所有资源。
  上面说的free内存后堆内存还存在,是针对malloc通过brk()方式申请的内存的情况。
  如果malloc通过mmap方式申请的内存,free释放内存后就会归归还给操作系统。
  我们做个实验验证下,通过malloc申请128KB字节的内存,来使得malloc通过mmap方式来分配内存。includestdio。hincludemalloc。hintmain(){申请1字节的内存voidaddrmalloc(1281024);printf(此128KB字节的内存起始地址:x,addr);printf(使用catprocdmaps查看内存分配,getpid());将程序阻塞,当输入任意字符时才往下执行getchar();释放内存free(addr);printf(释放了128KB字节的内存,内存也归还给了操作系统);getchar();return0;}
  执行代码:
  查看进程的内存的分布情况,可以发现最右边没有〔head〕标志,说明是通过mmap以匿名映射的方式从文件映射区分配的匿名内存。
  然后我们释放掉这个内存看看:
  再次查看该128KB内存的起始地址,可以发现已经不存在了,说明归还给了操作系统。
  对于malloc申请的内存,free释放内存会归还给操作系统吗?这个问题,我们可以做个总结了:malloc通过brk()方式申请的内存,free释放内存的时候,并不会把内存归还给操作系统,而是缓存在malloc的内存池中,待下次使用;malloc通过mmap()方式申请的内存,free释放内存的时候,会把内存归还给操作系统,内存得到真正的释放。为什么不全部使用mmap来分配内存?
  因为向操作系统申请内存,是要通过系统调用的,执行系统调用是要进入内核态的,然后在回到用户态,运行态的切换会耗费不少时间。
  所以,申请内存的操作应该避免频繁的系统调用,如果都用mmap来分配内存,等于每次都要执行系统调用。
  另外,因为mmap分配的内存每次释放的时候,都会归还给操作系统,于是每次mmap分配的虚拟地址都是缺页状态的,然后在第一次访问该虚拟地址的时候,就会触发缺页中断。
  也就是说,频繁通过mmap分配的内存话,不仅每次都会发生运行态的切换,还会发生缺页中断(在第一次访问虚拟地址后),这样会导致CPU消耗较大。
  为了改进这两个问题,malloc通过brk()系统调用在堆空间申请内存的时候,由于堆空间是连续的,所以直接预分配更大的内存来作为内存池,当内存释放的时候,就缓存在内存池中。
  等下次在申请内存的时候,就直接从内存池取出对应的内存块就行了,而且可能这个内存块的虚拟地址与物理地址的映射关系还存在,这样不仅减少了系统调用的次数,也减少了缺页中断的次数,这将大大降低CPU的消耗。既然brk那么牛逼,为什么不全部使用brk来分配?
  前面我们提到通过brk从堆空间分配的内存,并不会归还给操作系统,那么我们那考虑这样一个场景。
  如果我们连续申请了10k,20k,30k这三片内存,如果10k和20k这两片释放了,变为了空闲内存空间,如果下次申请的内存小于30k,那么就可以重用这个空闲内存空间。
  但是如果下次申请的内存大于30k,没有可用的空闲内存空间,必须向OS申请,实际使用内存继续增大。
  因此,随着系统频繁地malloc和free,尤其对于小块内存,堆内将产生越来越多不可用的碎片,导致内存泄露。而这种泄露现象使用valgrind是无法检测出来的。
  所以,malloc实现中,充分考虑了brk和mmap行为上的差异及优缺点,默认分配大块内存(128KB)才使用mmap分配内存空间。free()函数只传入一个内存地址,为什么能知道要释放多大的内存?
  还记得,我前面提到,malloc返回给用户态的内存起始地址比进程的堆空间起始地址多了16字节吗?
  这个多出来的16字节就是保存了该内存块的描述信息,比如有该内存块的大小。
  这样当执行free()函数时,free会对传入进来的内存地址向左偏移16字节,然后从这个16字节的分析出当前的内存块的大小,自然就知道要释放多大的内存了。
投诉 评论 转载

刘晓庆70岁还不忘扮嫩!穿紧身裙勒出细腰,却没普通老人看着舒刘晓庆的岁数都已经70岁了,但是说实话,她还是比较喜欢装嫩的那一种。其实有的时候也可以看到她不装嫩的状态呀,其实那样的状态反而会让人觉得比较的舒服自然吧。她平时在家里面穿的都是……NBA总裁肖华针对杜兰特转会发声!讨厌球员申请交易NBA办公室盯上了杜兰特,也盯上了勇士。近日,NBA总裁萧华针对杜兰特提出转会一事,做了公开表态:这事如果是自由市场的球星转会,或是交易导致球星流动,我也不会感到困……夏季赛RNG再现逆风翻盘!呼吸哥风格惨遭质疑,君泽不习惯?众所周知目前国内电子竞技领域当中热度最高的游戏当属《英雄联盟》,在上线十多年的时间当中不仅积累到无数游戏玩家基础,同时也衍生出一套专业的职业联赛体系。现如今2022年LPL夏季……拯救者R9000P2022款卡点618活动发布,网友却说没那日前,备受关注的联想拯救者R9000P2022款如期而至,为广大游戏玩家带来了又一款618活动游戏本选择目标。此次新机搭载采用Zen3架构的AMD锐龙76800H处理器,内存、……一文读懂Linux内存分配策略本篇主要以问答的方式来探索Linux内存系统的分配策略Linux进程的内存分布长什么样?在Linux操作系统中,虚拟地址空间的内部又被分为内核空间和用户空间两部分,不同位……一个人越来越有福气的12个习惯嗨,我是小张,点击右上方关注,每天分享一些生活习惯和成长知识,共同成长。自己的福气,运气是自己修来的,平常多注意习惯,生活细节。1、赞美自己,赞美他人。可以把……珠三角外贸冷暖从前是我求货代,现在是货代求我《中国经济周刊》记者伍素文丨广东报道珠三角外贸企业竞速出海抢订单大年初七以后,广东蒙娜丽莎贸易公司(下称蒙娜丽莎)总经理冯旭锋跟同事就开始忙着外出参展和拜访、接待一……40岁女人不想当黄脸婆,保持2个习惯多用2个护肤品,皮肤会更编辑:小颜哈喽,大家好,我是小颜,带你一起了解时尚、了解美女人不论到了哪个年纪都具有爱美的权力,不管是通过穿搭还是妆发,都能够让我们的气质更加精致美丽。当然在……一个家庭幸福的秘诀一:从前;有一个长相帅气、事业有成的男人,在某一次下班的途中,突然,被医院门口的一幕吸引住了。有一对夫妻抱着一个生病的孩子,在台阶哪里呆呆地坐着,一家三口紧紧地依偎……这才是鸡蛋最好吃的做法,很多人都没见过,出锅外酥里香,超解馋国以民为本,民以食为天,各位老饕大家好!今天我要给大家教一道东北的特色菜,名字叫酥黄菜。这道菜主要的食材是鸡蛋,做出来口味偏甜,吃起来外皮酸甜酥脆,里面柔软细腻,作为家常菜,是……王老吉推出百家姓版本佳能宣布停产单反Whatampamp392022年第一件好笑的事,莫过于王老吉的百家姓版本营销,看了你肯定也会忍不住买一瓶X老吉,为自己代言;相机巨头佳能却在新的一年宣布停产旗舰机型,请珍惜你手中的EOS吧;以一滴血……在微信群发送违规信息未经公司允许发布增员广告保险机构销售人员每经记者:涂颖浩每经编辑:廖丹《每日经济新闻》记者从业内获悉,银保监会近日下发《关于开展保险机构销售人员互联网营销宣传合规性自查整改工作的通知》(以下简称《通知》),决定……
同路人造句用同路人造句大全不寻常的春节拒绝人流!下半年就去这9个地方我和爸爸的故事冷眼造句用冷眼造句大全秋冬季节多干燥,推荐七款家常汤,暖心暖胃又润燥,大人小孩爱喝新竹造句用新竹造句大全房屋面积测量知识简介官宣!广州队将踢这个联赛,广州城未过准入怀柔安装15块警示牌!擅爬野长城,组织者最高罚5万地毯容易滋生细菌吗这样清洁更健康宝宝长牙疼痛都有哪些护理方法
那天我懂得了坚持孩子喝什么粥好呢叶鹏智:宠物小精灵的439版的绿宝石神兽,在哪?!?!?!热 怎样洗头头发更顺洗头前注意这三件事夏至之后,吃好三样,喝好三样幽默人生的句子那片绿绿的爬山虎教学设计1大学城中的高校教育资源共享问题及对策探究甘南攻略(甘南五日游攻略)生存的家园作文700字范文告别失眠的八个小妙招人生的十大意境,现实的真实人生

友情链接:中准网聚热点快百科快传网快生活快软网快好知文好找菏泽德阳山西湖州宝鸡上海茂名内江三亚信阳长春北海西安安徽黄石烟台沧州湛江肇庆鹤壁六安韶关成都钦州