第8章 内存管理

1 内存和页框管理

RAM的某些部分永久分配给内核,用来存放内核代码和静态内核数据结构;

RAM的其他部分称为动态内存,内存管理指的是这块部分内存的管理,不管是内核进程的内存申请还是用户进程。

 

 

2 页框

Linux使用4KB页框大小作为标准的分页单元;

页描述符struct page记录了页框的信息,包括:包含的是代码还是数据,是否空闲等

 

 

3 保留的页框池

请求内存时,如果内存不足,必须回收一些内存,就会发生阻塞;

在中断中不允许阻塞,可以使用GFP_ATOMIC标志:如果没有足够的内存,仅仅是分配失败;

为了尽量减少原子请求的分配失败,内核为原子分配请求保留了一个页框池!

 

 

4 伙伴系统(buddy system)算法

4.1 内存外碎片

为了避免内存外碎片,可以:

① 把非连续的内存映射为连续的线性空间,提高内存利用率

② 记录页框状况,尽可能合理的分配内存大小

伙伴系统基于第二种方法。

 

4.2 算法原理

把所有的空闲页分组为11个块链表,每个块链表分别包含大小为1,2,4...1024(对应4MB)个连续的页框;

每个块的第一个页框的物理地址是该块大小的整数倍,比如大小为16个页框的块,起始地址应该是16*4k的整数倍;

申请内存时,从申请size往大检查是否存在满足要求的内存块;

如果找到,则把大的页块拆分,并存放到对应的页块链表中。

 

 

5 slab 分配器

buddy system以页框为单位,对于申请小内存不友好;

基于buddy system之上建立一个缓存,也就是slab是对从buddy system申请到的内存的包装和二次销售(分配);

用户申请内存首先从slab缓存中获取,归还内存时也归还给slab缓存,申请小内存时减少的对buddy system的使用;

slab分配器可以针对频繁申请/释放的结构体创建缓存,用构造/析构替代申请/释放;

slab的信息由高速缓存描述符kmem_cache_t表示。

 

 

6 非连续内存区管理

6.1 简介

优点:避免外碎片

缺点:必须打乱内核页表,增大平均访问时间

使用场景举例:

  • 为活动的交换区分配数据结构
  • 为模块分配空间
  • 给某些IO驱动分配缓冲区

非连续内存区使用vm_struct描述符表示;使用vmalloc()函数分配。

 

6.2 特点

  • 内核线性地址空间为3GB-4GB
  • 前896MB进行线性映射,既线性地址 = 物理地址 + 固定OFFSET,映射的末尾保存在high_memory变量中
  • 除了结尾永久内核映射和固定映射的线性地址,其余都是非连续内存区,也叫vmalloc区
  • vmalloc区之间有4KB大小的安全区,用于隔离不同的非连续内存区
  • 非连续内存区始于VMALLOC_START,终于VMALLOC_END
  • 非连续内存区的物理内存大小虽然只有128M,但是对应的线性地址范围却很大,因为这是虚拟地址,可以通过磁盘换入换出扩展

 

6.2 分配非连续区内存

vmalloc(size)
    // 需要申请一个非连续区内存描述符vm_struct管理这个非连续区
    // 到这里这组非连续内存的线性地址已经确定了(area->addr)
    // VM_ALLOC标志表示申请的非连续页框将要映射到连续的线性地址空间
    struct vm_struct *area = get_vm_area(size, VM_ALLOC);

    // 非连续区基本思路就是按页申请/释放内存
    // 先计算申请几页内存,再申请一组内存保存页描述符
    area->nr_pages = size >> PAGE_SHIFT
    array_size = area->nr_pages * sizeof(struct page*));
    area->pages = kzalloc(array_size, GFP_KERNEL);

    // 逐页申请内存
    for (i = 0; i < area->nr_pages; i++)
        area->pages[i] = alloc_page(GFP_KERNEL | GFP_HIGHMEM);

    // 至关重要:修改内核页表项,表明分配给非连续内存区的每个页框对应的线性地址
    map_vm_area(area, area->pages);

    // 注意返回的地址,释放内存的时候要从这个地址获取到area
    return area->addr;

 

6.3 非连续内存区和缺页异常

内核在更新非连续内存区对应的页表项时是非常懒惰的,即vmalloc()和vfree()函数只把自己限制在更新住内核页表(页全局目录init_mm.page及其子页表);

内核初始化结束时,任何进程都不直接使用主内核页表;

因此,当内核进程对非连续内存区进行第一次访问时,总会遇到该地址对应的页表项为空,触发缺页异常;

缺页异常处理程序认识这种情况,并把主内核页表项的页表拷贝给当前内核进程。

当然,如果住内核页表项中也没有该对应的页表项,则会报错误地址。

posted @ 2025-03-11 23:54  moonのsun  阅读(28)  评论(0)    收藏  举报