哈喽,我是子牙,一个很卷的硬核男人
深入研究计算机底层、Windows内核、Linux内核、Hotspot源码……聚焦做那些大家想学没地方学的课程。为了保证课程质量及教学效果,一年磨一剑,三年先后做了这些课程:手写JVM、手写OS、带你用纯汇编写OS、手写64位多核OS、实战Linux内核…
最近抽空把之前对Linux进程内存空间的研究整理了一下,分享给大家。依然,这篇文章与你以前看到的所有相关文章或视频都不同,无论是广度,还是深度,更重要的,我都会在Linux内核中证明给你看!
32位时代已经过去很久了,所以本篇文章为了保持干净整洁,只讲64位。32位与64位其实相差不大,甚至可以说,32位因为可操作内存空间更少,反而更复杂些,感兴趣的小伙伴举一反三,自行研究
阅读完本篇文章,这些问题你能得到答案:
一个进程的内存空间长啥样子
一个进程的用户空间、内核空间分别长啥样子
进程的用户空间与内核空间都有空洞(hole)吗?如何证明?
我们编写的一个C语言程序,是如何变成Linux进程的?
malloc底层是如何实现的?什么时候是通过brk申请内存?什么时候是通过mmap申请内存?如何证明?
如何证明进程用户空间中的堆(heap)是向上增长的?
如何证明进程用户空间中的映射区(mmap)是向下增长的?
Linux内核中与此知识点相关的资料或源码怎么看?
想独立研究Linux内核,能独立看得到Linux内核,有书或视频推荐吗?
道生一,一生二,二生三,三生万物。本篇文章就从这个程序开始吧
图片
这个程序包含了一个程序会使用内存的所有情况:未初始化的全局变量,初始化了的全局变量、静态字符串、函数参数、局部变量、通过mmap从映射区分配内存、通过malloc从堆区分配内存
来看下结果
图片
接下来我们就来分析:不同的变量对应着不同的使用内存的情况,这些变量为什么是这样的值?它们都分布在Linux进程内存空间的哪个区域?
一个64位进程的内存空间的完整态长这样
图片
内核空间是所有进程共享的,长这样。本篇文章主要讲Linux进程用户空间,这个我就不展开了
Linux进程的内核空间,内核源码中有个文件详细列出来了,路径
/linux-5.4.259/Documentation/x86/x86_64/mm.rst
Linux进程的用户空间,内核源码中没有给出明确的答案,需要我们阅读源码去分析
我们研究一个技术点,除了要研究它静态的时候,还有研究它的运行时,才能建立起对它的完整认知。光看源码,你能推理出Linux进程用户空间由哪些部分组成,但是你没办法知道哪些区域之间是有空洞(hole)的,但是通过看它的运行时,你就能得到答案!
图片
最终得出Linux进程用户空间最严谨的图
图片
亚里士多德说:我爱我师,我更爱真理。意思是:我尊重我的老师柏拉图,但是我认为天外有天,人外有人,我要不断的突破老师给我的认知、我自己的认知局限,最终建立我自己的认知,批判,最终确定为真理,坚持之。
我觉得我们求知者,要有开宗立派的决心,而不是盲从!更不是觉得谁就是不可逾越的天!这就要求我们去掌握本源知识,去学习更多、思考更多,融会贯通,举一反三,不断突破自己的局限!所以你看我的文章或者听我的视频,总能听到不一样的,总能豁然开朗,因为我深谙此道理!
教育最大的不公平,不是国家的不同、城市的不同、学校的不同……其实本质就是老师的不同。我发现真正厉害的老师,能够教出厉害学生的老师,都是开窍之人!他们独立思考、独特的观点、独特的做事风格,总能让你耳目一新!
试想:如果所有进程的env,arg、stack、mmap、head、data、code都在固定的地址,那么黑客想取这些信息是不是就非常方便,于是就出现了ASLR(地址空间随机化),用于随机化进程的地址空间布局,即每个进程的内存区域、某个程序每次运行的内存地址都是不一样的
如何查看你的Linux系统是否开启了ASLR呢?
cat /proc/sys/kernel/randomize_va_space
有三种可能的结果:
0:关闭
1:半随机:共享库、栈、mmap以及VDSO将被随机化
2:开启
注意:修改ASLR只影响新创建的进程地址空间,不影响已经存在的进程地址空间。真怕有人拿着问题来问:子牙老师,我改了没生效啊……我已经见怪不怪了
那如何修改呢?常用方式有两种(root权限)
1、sysctl -w kernel.randomize_va_space=0
2、echo 0 > /proc/sys/kernel/randomize_va_space
我们用gdb调试程序的时候,如果开了ASLR会非常不方便,所以默认是关闭的
图片
即Linux的ASLR是开启的,gdb会关闭调试进程的ASLR
如果想打开呢?
set disable-randomization on
set disable-randomization off
Linux内核发展至今,东西真的超级多!我最开始研究的时候也是一块一块的吃透,然后再试图将相关知识点进行关联。零散的知识是没有威力的,关联起来才能理解得更深刻!
04ELF文件与进程内存空间
Linux进程是怎么来的?是Linux运行可执行文件来的。Linux下的可执行文件是ELF文件结构
图片
Windows下的可执行文件是PE文件结构。为什么Linux不能运行Windows程序,就是因为Linux内核设计的时候,不支持PE文件结构,即无法解析它。那是否搞个PE文件解析器就可以了呢?如果是不依赖库函数的程序,确实是可以的。但是不依赖库函数,这个程序就相当于不用操作系统的能力,那也干不了什么事
所以如果你想做这么一件事:在Linux下运行Windows程序。那么大体上你需要做到:实现PE文件解析器、实现支撑程序运行的各种库。我后期准备在我自己写的操作系统上支持运行Windows程序、Linux程序,是不是很酷!我觉得有个自己写的JVM、OS,最有趣的就是我研究一个东西,我觉得很有趣,我有基础环境支撑我去进一步研究它!
接下来我们就来看一下,我们写的这个程序,ELF文件长啥样子,你可以通过以下命令去查看
查看ELF文件的头:readelf -h test-1
图片
我们研究它的内存空间,就取它的节表
readelf -SW test-1
图片
Address这一栏有值的,就是要载入内存的,我们来看下这两个值是怎么来的
图片
end_code的值就是.eh_fram的Address+Size得来的
end_data的值就是.data的Address+Size得来的
其他值都是运行时生成的,无法计算。比如brk,你通过malloc申请内存,就会改变它,比如
图片
我使用malloc申请内存时,我怎么知道它是走的brk还是mmap呢?看运行结果,阈值大概是128K,即0x20000
图片
那怎么确定heap是向上增长的呢?看运行结果
图片
怎么确定mmap是向下增长的呢?看运行结果
图片
恭喜你!彻底掌握了Linux进程内存空间布局!
关于ELF文件结构、PE文件结构,如果你想深入学习,推荐看这本书
图片
05实战Linux内核
这个数据你在用户态是看不到的,需要编写Linux驱动
图片
Linux是用汇编+C语言实现的,所以Linux驱动也只能用汇编+C语言实现
图片
其实作为一个coder,就算你是搞业务开发的,只要你想走出这条路,汇编跟C语言都是必须要学会的!不会这两门语言,你会发现,你能研究的东西很少很少……那些应用层的东西、语言层面的、机制层面的,研究得再多,在高手眼中,你还是菜鸟!裁员的时候还是有的份!因为大家都走过这条路,都知道具备什么样的底层实力才能成为高手!国内的计算机高手真的不是特别多,感觉大家都畏惧去研究底层,一看到汇编、C语言代码就退缩。
在计算机世界里,这两门语言是承上启下的。汇编、C语言玩明白了,C++无师自通!
图片
06总结
本篇文章为大家分享了64位Linux进程的内存空间布局,并详细讲了进程的用户空间
关于Linux进程的用户空间内存布局,Linux内核源码中是没有提供明确的答案的,不像内核空间内存布局,是有明确答案的
我们通过阅读Linux内核源码,做实验,推导出了Linux进程的用户空间内存布局,并从ELF文件、运行时,详细讲解了每一层。并通过malloc分配内存讲到了面试中经常问到的问题
最后,恭喜大家,这就是Linux进程用户空间内存布局的全部,你已经掌握了它!