实战讲解Linux进程内存空间

2024-01-08 10:30:56

哈喽,我是子牙,一个很卷的硬核男人

深入研究计算机底层、Windows内核、Linux内核、Hotspot源码……聚焦做那些大家想学没地方学的课程。为了保证课程质量及教学效果,一年磨一剑,三年先后做了这些课程:手写JVM、手写OS、带你用纯汇编写OS、手写64位多核OS、实战Linux内核…

最近抽空把之前对Linux进程内存空间的研究整理了一下,分享给大家。依然,这篇文章与你以前看到的所有相关文章或视频都不同,无论是广度,还是深度,更重要的,我都会在Linux内核中证明给你看!

32位时代已经过去很久了,所以本篇文章为了保持干净整洁,只讲64位。32位与64位其实相差不大,甚至可以说,32位因为可操作内存空间更少,反而更复杂些,感兴趣的小伙伴举一反三,自行研究

阅读完本篇文章,这些问题你能得到答案:

  1. 一个进程的内存空间长啥样子
  2. 一个进程的用户空间、内核空间分别长啥样子
  3. 进程的用户空间与内核空间都有空洞(hole)吗?如何证明?
  4. 我们编写的一个C语言程序,是如何变成Linux进程的?
  5. malloc底层是如何实现的?什么时候是通过brk申请内存?什么时候是通过mmap申请内存?如何证明?
  6. 如何证明进程用户空间中的堆(heap)是向上增长的?
  7. 如何证明进程用户空间中的映射区(mmap)是向下增长的?
  8. Linux内核中与此知识点相关的资料或源码怎么看?
  9. 想独立研究Linux内核,能独立看得到Linux内核,有书或视频推荐吗?

混沌由此开

道生一,一生二,二生三,三生万物。本篇文章就从这个程序开始吧

这个程序包含了一个程序会使用内存的所有情况:未初始化的全局变量,初始化了的全局变量、静态字符串、函数参数、局部变量、通过mmap从映射区分配内存、通过malloc从堆区分配内存

来看下结果

接下来我们就来分析:不同的变量对应着不同的使用内存的情况,这些变量为什么是这样的值?它们都分布在Linux进程内存空间的哪个区域?

进程内存空间

一个64位进程的内存空间的完整态长这样

内核空间是所有进程共享的,长这样。本篇文章主要讲Linux进程用户空间,这个我就不展开了

Linux进程的内核空间,内核源码中有个文件详细列出来了,路径

/linux-5.4.259/Documentation/x86/x86_64/mm.rst

Linux进程的用户空间,内核源码中没有给出明确的答案,需要我们阅读源码去分析

我们研究一个技术点,除了要研究它静态的时候,还有研究它的运行时,才能建立起对它的完整认知。光看源码,你能推理出Linux进程用户空间由哪些部分组成,但是你没办法知道哪些区域之间是有空洞(hole)的,但是通过看它的运行时,你就能得到答案!

最终得出Linux进程用户空间最严谨的图

亚里士多德说:我爱我师,我更爱真理。意思是:我尊重我的老师柏拉图,但是我认为天外有天,人外有人,我要不断的突破老师给我的认知、我自己的认知局限,最终建立我自己的认知,批判,最终确定为真理,坚持之。

我觉得我们求知者,要有开宗立派的决心,而不是盲从!更不是觉得谁就是不可逾越的天!这就要求我们去掌握本源知识,去学习更多、思考更多,融会贯通,举一反三,不断突破自己的局限!所以你看我的文章或者听我的视频,总能听到不一样的,总能豁然开朗,因为我深谙此道理!

教育最大的不公平,不是国家的不同、城市的不同、学校的不同……其实本质就是老师的不同。我发现真正厉害的老师,能够教出厉害学生的老师,都是开窍之人!他们独立思考、独特的观点、独特的做事风格,总能让你耳目一新!

ASLR是什么?

试想:如果所有进程的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内核发展至今,东西真的超级多!我最开始研究的时候也是一块一块的吃透,然后再试图将相关知识点进行关联。零散的知识是没有威力的,关联起来才能理解得更深刻!

ELF文件与进程内存空间

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文件结构,如果你想深入学习,推荐看这本书

关注公众号【硬核子牙】回复【Linux进程内存】免费领取电子书

实战Linux内核

这个数据你在用户态是看不到的,需要编写Linux驱动

Linux是用汇编+C语言实现的,所以Linux驱动也只能用汇编+C语言实现

其实作为一个coder,就算你是搞业务开发的,只要你想走出这条路,汇编跟C语言都是必须要学会的!不会这两门语言,你会发现,你能研究的东西很少很少……那些应用层的东西、语言层面的、机制层面的,研究得再多,在高手眼中,你还是菜鸟!裁员的时候还是有的份!因为大家都走过这条路,都知道具备什么样的底层实力才能成为高手!国内的计算机高手真的不是特别多,感觉大家都畏惧去研究底层,一看到汇编、C语言代码就退缩。

在计算机世界里,这两门语言是承上启下的。汇编、C语言玩明白了,C++无师自通!

关注公众号【硬核子牙】回复【Linux进程内存】免费领取测试代码

总结

本篇文章为大家分享了64位Linux进程的内存空间布局,并详细讲了进程的用户空间

关于Linux进程的用户空间内存布局,Linux内核源码中是没有提供明确的答案的,不像内核空间内存布局,是有明确答案的

我们通过阅读Linux内核源码,做实验,推导出了Linux进程的用户空间内存布局,并从ELF文件、运行时,详细讲解了每一层。并通过malloc分配内存讲到了面试中经常问到的问题

最后,恭喜大家,这就是Linux进程用户空间内存布局的全部,你已经掌握了它!

文章来源:https://blog.csdn.net/b98982016/article/details/135450186
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。