iCurry
2023/03/26阅读:41主题:默认主题
操作系统
1. 在 Linux 进程下,内存分布长什么样?
在 Linux 操作系统中,虚拟内存被分为 内核空间 和 用户空间。而对于 32位 和64位 操作系统,其内存分布也有所不同。
32位下内存空间为 4G, 1G 的内核空间 位于最顶层。其余都是用户空间。64 位下内存空间很大。最顶层是128T的内核空间,最底层是128T的用户空间。中间的内存空间处于未定义状态。
对于内核空间和用户空间。进程在用户态,只能访问用户空间的内存。进程在内核态的时候,才能访问内存空间的内存。
对于用户空间的内存,又可以进一步细分。从下向上 依次是 代码段、数据段 、BSS段 、堆 、文件映射段 、栈。
代码段:主要储存二进制可执行代码。
数据段:包括已经初始化的静态常量和全局变量。
BSS段 :包括未初始化的静态变量和全局变量。
堆段 :包括动态分配的内存,地址是从低到高。
文件映射段 : 包括动态库、共享内存等。地址从低到高增长。
栈段 :包括局部变量和函数调用的上下文等。栈的大小一般是固定的,大约是 8MB.不过我们也可以自定义。
2. malloc() 是如何分配内存的?
首先要明确malloc() 不是系统调用,是 C 标准库里的函数。是用来动态分配内存的。malloc()分配的是虚拟内存。而且所分配的内存不会立即就与物理地址建立映射关系。而是等到该内存被访问的时候,触发缺页中断,由操作系统为该内存建立与物理内存的映射关系。这样做的好处就是,如果分配的内存没有被访问,就不会占用物理内存资源。
malloc()通过两种方式向操作系统申请内存。
1、通过brk()函数从堆内分配内存。
2、通过mmap()系统调用在文件映射区分配内存。
brk() 函数 申请内存的方法就是更改 堆顶的指针,将堆顶的指针向高地址移动,获得新的内存空间。
mmap() 通过系统调用在文件映射区分配一块内存。该内存在文件映射区的内部。
至于 malloc() 函数 选择那种方式申请内存,取决于申请内存的大小。当要申请的内存小于128k 的时候,选择 brk() 函数调用。当要申请的内存大于 128k 的时候,使用mmap() 通过系统调用申请内存。
3. malloc() 分配内存,为什么要分成两种方式?
不妨假设 malloc() 在分配内存时,只是用 mmap() . 由于mmap() 是系统调用,执行系统调用是需要进入内核态,执行完之后再返回用户态。因此,如果每次分配内存都使用 mmap(),就会造成进程频繁的在用户态和内核态之间频繁切换。此外mmap() 分配的内存每次释放的时候都会归还给操作系统,这就使得每次mmap() 分配的虚拟地址都是缺页状态,在第一次访问时,都会触发缺页中断。
频繁的发生运行态的切换,以及缺页中断,会导致CPU消耗过大。基于以上原因,才有了brk()函数分配内存。
brk()函数分配内存是在堆区分配。而且每次分配的内存会比实际需要的更大,多余的会被当作内存池。当内存释放的时候,并不会立即还给操作系统,而是暂时缓存在内存池里,等下次再申请内存的时候,就可以直接从内存池中分配内存,甚至虚拟内存与物理内存的映射关系还存在,也可以减少缺页中断等现象。
但是如果只使用brk(),会造成内存碎片化。
可以设想一个场景,假设我们连续申请了 10、20、30的内存,随后10、20这两块内存被释放。我们接着申请内存,如果申请的内存小于30,那么就可以用刚才被释放的空间,如果申请的内存大于30,那就必须重新向操作系统申请空间。随着频繁的 申请、释放内存,最后就会有很多的内存碎片,更有甚者,还会造成内存泄露。
总结 : malloc() 在分配内存时,之所以使用brk() 和 mmap() 是因为 要避免内存碎片化,以及频繁切换状态对CPU造成的压力。因此 ,malloc() 选择在小于128k 时候使用 brk() ,大于128k 使用mmap().
4. 什么是虚拟内存,为什么要有虚拟内存?
定义: 虚拟内存是计算机管理内存时所用到的一种技术,它使得每个进程都好像独自占有所有的内存空间。
至于为什么要有虚拟内存技术,我们不妨想象一下没有虚拟内存,而是直接操作物理内存是什么样子的。
假设每个进程都直接访问物理地址,很有可能会出现两个进程访问同一个地址的情况,甚至还有可能出现 进程B 修改了 进程A 储存的数据。另外,真实的物理地址的大小是有限的,假设在进程A 占用了很多的内存,剩余的内存不够进程B 使用,那么进程B 就只能等待进程A 结束后才能执行。这样就会造成计算机的执行效率特别低。此外,为进程分配的内存是随机分配的,因此程序运行的地址也是不确定的。可见如果直接操作物理地址,会造成很多不必要的麻烦,因此才发展出了虚拟内存地址。
使用虚拟内存技术,使得每次进程访问,都访问虚拟地址,然后在虚拟地址和物理地址之间建立其映射关系。这样对每个进程而言,可以独占整个内存资源。然而实际上可能只占用了很小一部分物理地址。此外,采用采用虚拟内存技术,可以将不同的进程隔离开,进程之间互不影响,可以通过访问内存,即使访问了同一个内存,但是对应的物理地址也不相同,很好的避免了进程访问时候的地址冲突。由于进程之间相互隔离,也使得数据更加安全,进程A 的数据不会被进程B 更改。此外,虚拟内存技术也可以让我们实际使用的内存超过物理内存。根据计算机局部性原理,真正参与到程序运行的数据只占很小的一部分,多余数据可以先放到磁盘里,等到真用到的时候再加载。根据该原理,虽然某个进程占用了很多的虚拟内存,但是占用的物理内存可能只有很小的一部分,这样就空出了一部分内存空间供给其他进程使用。
总结:不使用虚拟内存可能会造成哪些问题。
1、物理地址可以被任意进程访问,数据不安全,进程B 有可能篡改进程A 的数据。
2、直接访问物理内存,有可能会因为内存不够用,而使得进程B 必须等待进程A 完成之后才能运行。
3、内存地址随机分配,使得不清楚程序运行的地址。
使用虚拟地址后:
1、能够隔离进程、各进程间互不影响。
2、根据局部性原理、可以将用不到的数据先换出(swap)到磁盘上,用时再加载回来。
3、虚拟内存和物理地址间存在映射关系,可以清楚的知道每个程序在物理内存上的地址。
作者介绍