【1.3 】内存空间分配,内存管理

一、内核空间和用户空间

Linux的虚拟地址空间也为0~4G.。Linux内核将这4G字节的空间分为两部分。将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为"内核空间"。而将较低的3G字节(从虚拟地址 0x00000000到0xBFFFFFFF),供各个进程使用,称为"用户空间")。因为每个进程可以通过系统调用进入内核,因此,Linux内核由系统内的所有进程共享。每个进程有各自的私有用户空间(0~3G),这个空间对系统中的其他进程是不可见的。最高的1GB字节虚拟内核空间则为所有进程以及内核所共享。

内核态(0级保护):当一个进程执行系统调用而陷入内核代码中执行时,称进行处于内核运行态(内核态)。此时处理器处于特权级别最高的(0级)内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。

用户态(3级保护):当进行在执行用户自己的代码时,则称其处于用户运行态(用户态)。此时处理器在特权级最低的(3级)用户代码中运行。当正在执行用户程序而突然被中断程序中断时,此时用户程序也可象征性的称为处于进行的内核态,因为中断处理程序使用当前进程的内核栈。

内核空间与用户空间通信方式:

  1. API(copy_to_user copy_from_user):主要应用于设备驱动读写函数中,通过系统调用触发。
  2. 系统调用(syscal):open、write、read函数。
  3. 使用proc文件系统(虚拟文件系统):/proc 文件系统是一种虚拟文件系统,procfs是Linux内核信息的抽象文件接口,大量内核中的信息以及可调参数都被作为常规文件映射到一个目录树中,这样我们就可以简单直接的通过echo或cat这样的文件操作命令对系统信息进行查取和调整了
  4. 使用sysfs文件系统+kobject:每个在内核中注册的kobject都对应着sysfs系统中的一个目录。可以通过读取根目录下的sys目录中的文件来获得相应的信息。
  5. netlink:用户态应用使用标准的 socket API 就可以使用 netlink 提供的强大功能;
  6. 内存映射(mmap)可以将内核空间的地址映射到用户空间。另一方面,也可以直接打开/dev/mem文件,把物理内存中的某一页映射到进程空间中的地址上。
  7. 信号:从内核空间向进程发送信号。

二、内存管理

1、分页内存管理:

交换内存管理中的机制——基址和极限  将程序发出的虚拟地址加上基址得到物理地址”。

其造成两大问题

  • 空间浪费:程序不断的执行并释放的过程中,造成了内存空间中的可用空间不连续就,难以加以应用,这种现象也称为“外部碎片化”。 
  • 程序大小受限 :当程序需要更多的内存空间时,需要将其全部从物理内存中“倒出”到磁盘上,再在内存中找到更大的一片区域去存放增长了的程序,这样使得程序的增长效率低下。同时,一个程序的大小还不能超过物理内存空间的大小。

分页内存管理解决这两个问题:将虚拟内存空间和物理内存空间皆划分成大小相同的页面,例如4KB、8KB和16KB等。并将页作为内存空间的最小分配单位,一个程序的一个页面(虚拟页面)可以存放在任何一个物理页面中。

  • 空间浪费:通过将内存空间划分成大小一样的页面,并且将其作为内存分配的基本单位,这样就避免了大量外部碎片的积累,让内存空间得到有效利用。
  • 程序受限:分页内存管理下,允许一个进程的部分虚拟页面存放在物理内存中,另一部分存放在磁盘上,等到需要使用时再将其从磁盘中加载到物理内存中。也就是说,当程序需要额外的空间时,只需要对其分配新的页即可,这样做使得程序的增长效率较高。

MMU为每一个程序都配备了一个页表,里面存放的是虚拟页面到物理页面的映射,如果MMU接收到了程序发出的虚拟地址,在查找相对应的物理页面号时,没有找到那么将会通过缺页中断来将需要的虚拟页面从磁盘中加载到物理内存的页面中。

2、虚拟地址和物理地址:当内核模块代码或线程访问内存时,代码中的内存地址都为逻辑地址,而对应到真正的物理内存地址,需要地址一对一的映射,如逻辑地址0xC0000003对应的物理地址为0×3,0xC0000004对应的物理地址为0×4

3、高端内存:即只能访问1G物理内存。若机器中安装8G物理内存,那么内核就只能访问前1G物理内存,后面7G物理内存将会无法访问,因为内核 的地址空间已经全部映射到物理内存地址范围0×0 ~ 0×40000000,0xc0000000 ~ 0xffffffff的地址空间已经被用完了,所以无法访问物理地址0×40000000以后的内存。因此内核地址空间划分三部分:ZONE_DMA、ZONE_NORMAL和 ZONE_HIGHMEM。ZONE_HIGHMEM即为高端内存,这就是内存高端内存概念的由来。

ZONE_DMA                内存开始的16MB

ZONE_NORMAL        16MB~896MB

ZONE_HIGHMEM       896MB ~ 结束

当内核想访问高于896MB物理地址内存时,从0xF8000000 ~ 0xFFFFFFFF地址空间范围内找一段相应大小空闲的逻辑地址空间,借用一会。借用这段逻辑地址空间,建立映射到想访问的那段物理内存(即填充内核PTE页面表),临时用一会,用完后归还。这样别人也可以借用这段地址空间访问其他物理内存,实现了使用有限的地址空间,访问所有所有物理内存。

映射高端内存的方法

动态映射空间

通过 vmalloc() ,在”内核动态映射空间”申请内存的时候,就可能从高端内存获得页面,映射的地址在VMALLOC_START~VMALLOC_END之间,因此说高端内存有可能映射到”内核动态映射空间”中。

持久内核映射
内核专门为此留出一块线性空间,从 PKMAP_BASE 到 FIXADDR_START ,用于映射高端内存。在 2.6内核上,这个地址范围是 4G-8M 到 4G-4M 之间。这个空间起叫”内核永久映射空间”或者”永久内核映射空间”。通常情况下,这个空间是 4M 大小,因此仅仅需要一个页表即可,内核通过来 pkmap_page_table 寻找这个页表。通过 kmap(),可以把一个 page 映射到这个空间来。由于这个空间是 4M 大小,最多能同时映射 1024 个 page,这个函数会睡眠,用在进程上下文中。因此,对于不使用的的 page,及应该时从这个空间释放掉(也就是解除映射关系),通过 kunmap() ,可以把一个 page 对应的线性地址从这个空间释放出来。

临时映射

内核在 FIXADDR_START 到 FIXADDR_TOP 之间保留了一些线性空间用于特殊需求。这个空间称为”固定映射空间”在这个空间中,有一部分用于高端内存的临时映射。

当要进行一次临时映射的时候,需要指定映射的目的,根据映射目的,可以找到对应的小空间,然后把这个空间的地址作为映射地址。这意味着下一次临时映射会导致以前的映射被覆盖。通过 kmap_atomic() 可实现临时映射,该函数不会阻塞,可以用在中断上下文。

4、申请内存:

  • kmalloc:与用户空间malloc类似,只不过多了一个flags函数,以字节为单位申请内存。最常用的标志是GFP_KERNEL(可能会引起睡眠);在中断处理程序、软中断、tasklet选择GFP_ATOMIC。最后,使用kfree释放内存空间。
  • vmalloc:分配的内存虚拟地址是连续的,而物理地址则无需连续。这也与用户空间分配函数malloc工作方式。vmalloc通过分配非连续的物理内存卡,再修正页表,把内存映射到逻辑地址空间连续区域中。一般使用kmalloc获得内存,vmalloc需要专门建立页表项,而且页必须以个一个的进行映射,当迫不得己需要大块内存时使用vmalloc。最后使用vfree释放。
  • alloc_pages;分配2的n次方个连续的物理页,返回第一个页的page结构体指针。通过_free_pages释放。
  • _get_free_pages:与alloc_pages相同,但是返回第一个页的逻辑地址。通过free_pages释放。
  • slab:高速缓存,便于数据的频繁分配和回收,需要一个数据结构实例时,直接从中抓取,而不需要分配内存, 高速缓存划分 多个slab,slab由一页组成。kem_cache_creae创建指定类型对象的高速缓存,然后使用kmem_cache_alloc从高速缓存中申请一个对象。通过kmem_cache_free释放对象返回给slab高速缓存,kmem_cache_destroy消除。

5、内存碎片:操作系统在分配内存时,有时候会产生一些空闲但是无法被正常使用的内存区域,这些就是内存碎片,或者称为内存零头,这些内存零头一共分为两类:内零头和外零头。

  • 内零头:是指进程在向操作系统请求内存分配时,系统满足了进程所需要的内存需求后,还额外还多分了一些内存给该进程, 也就是说额外多出来的这部分内存归该进程所有,其他进程是无法访问的。(页式存储管理是以页为单位进程内存分配的,这样就不会产生外零头,但是段式分配会产生内零头。)
  • 外零头:是指内存中存在着一些空闲的内存区域, 这些内存区域虽然不归任何进程所有,但是因为内存区域太小,无法满足其他进程所申请的内存大小而形成的内存零头。(段式存储管理是段为单位向进程进行内存分配的,进程申请多少内存,系统就给进程分配多少内存,这样就不会产生内零头,但是段式分配会产生外零头。)

6、内存泄漏:一般我们常说的内存泄漏是指堆内存的泄漏,应用程序一般使用 函数从堆中分配到一块内存,使用完后,程序必须负责相应的调用 free 或 delete 释放该内存块。

7、内存溢出:内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出。

三、可执行程序的内存布局

在内存中,从低地址向高地址,依次是代码段(code)、只读数据段(RO data)、读写段(RW)、未初始化代码段(BSS)、堆区域和栈区域。

未初始化代码段(BSS)在程序初始化即加载时开辟,而堆栈段在程序运行时动态开辟。

对于程序运行过程中的内存使用,堆和栈一般是相向扩展的。堆的分配由程序来分配,但是栈是由编译器管理的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值