页机制下的内存分配与管理(上)

本文深入探讨了操作系统内核中页内存管理的实现,包括使用Page结构体管理内存,采用链表组织空闲页,以及内存分配与释放的算法。在分配页时,遍历空闲链表寻找合适的空闲块,释放页后进行合并操作。同时,文章还介绍了页表未映射、物理页不在内存和访问权限错误等导致的页异常情况,以及页异常处理的流程。
摘要由CSDN通过智能技术生成

对于操作系统内核中页内存管理有必要理一下。
其中涉及到采用什么数据结构管理内存,内存的分配与释放应该采用什么算法,而这其中要造哪些轮子,以便于达到目的。所谓轮子可以是宏、普通函数或者内联函数,好的轮子可以提高代码的编写效率,可读性也越好。对于已有的轮子要明确,输入什么输出什么。

在分页机制下,物理内存将以页为单位进行组织,页内存管理的需求是:

  • 能够分配指定的页数(>=1);
  • 能够释放某页。

每一页通过一个结构体描述其状态,所有的页对应一个Page型的数组,大小由管理页的数量而定:

struct Page {
    int ref;                        //页被引用的次数
    uint32_t flags;                 //页的状态标志
    unsigned int property;          //可以用来记录空闲块的大小(空闲块中包含页若干)
    list_entry_t page_link;         //链表中的公共部分,链接的媒介
};

为了便于对空闲块进行管理,将空闲块中第一页(头页)对应的Page数组中的那个元素用链表的方式管理起来,元素按照分配的先后顺序插入链表,称此链表为空闲链表。元素中property成员记录了空闲块中的页数。
当分配页时,先遍历空闲链表,寻找property满足要求的空闲块,当找到时将此元素的地址记录下来(假设p),此为分配页的开始,然后根据要分配页的数量n,p+n即为分配页的结束,其间的Page数组元素flags也要进行相关改变。如果n==property,则没有新的空闲块产生,否则会产生新的空闲块,对应Page数组地址为p+n的元素为头页,将其插入空闲链表中,原链表结点删除。

static struct Page *
default_alloc_pages(size_t n) {
    assert(n > 0);
    if (n > nr_free) {
        return NULL;
    }
    struct Page *page = NULL;
    list_entry_t *le = &free_list;
    /*获取满足分配大小空闲块头页的地址*/
    while ((le = list_next(le)) != &free_list) {
        struct Page *p = le2page(le, page_link); //每次循环都要这样转化下
        if (p->property >= n) {
            page = p; //page指向页数组项
            break;
        }
    }
    if (page != NULL) {
        list_del(&(page->page_link));
        if (page->property > n) {
            struct Page *p = page + n;
            p->property = page->property - n;
            list_add(&free_list, &(p->page_link));
            SetPageProperty(p); //from ShiHao 用不用对Page数组中的元素进行操作
        }
        nr_free -= n;
        ClearPageProperty(page);
    }
    return page;
}

对于页的释放需要明确指出要释放的页的位置,以及页的数量,在页释放后应当立即进行合并操作,如果释放后的空闲块其前后有空闲块那么就进行合并。需要注意的是,空闲链表只是空闲块的组织形式,是为了便于查找空闲块,并不会改变Page数组的元素顺序。 采用后进先出的顺序维护链表,将释放的块放置在链表开始处。

static void
default_free_pages(struct Page *base, size_t n) {
    assert(n > 0);
    struct Page *p = base;
    for (; p != base + n; p ++) {
        assert(!PageReserved(p) && !PageProperty(p)); //释放的页需要是非内核的,非空的
        p->flags = 0; //标记位清零
        set_page_ref(p, 0); //释放后,引用清零
    }
    base->property = n;
    SetPageProperty(base); //成为头页,可以用于分配,后面要加入空闲链表
    list_entry_t *le = list_next(&free_list);
    while (le != &free_list) {
        p = le2page(le, page_link); //p是从空闲链表第一个结点开始
        /* 
         * 释放后空闲块**结尾的地址**指向的还是一个空闲块(妙:去空闲链表中找而不是在Page数组中找,
         * 因为那样会有超出边界的问题,即base + base->property指向未知的区域,不好判断)
         * 随着p在空闲链表中跳转,一个循环下来可以实现释放块和其前后块的合并。
         */
        if (base + base->property == p) { 
            base->property += p->property; //和后空闲块进行合并
            ClearPageProperty(p); 
            list_del(&(p->page_link));
        }
        else if (p + p->property == base) { //和前空闲块进行合并
            p->property += base->property;
            ClearPageProperty(base);
            base = p;
            list_del(&(p->page_link));
        }
        le = list_next(le);
    }
    nr_free += n;
    list_add(&free_list, &(base->page_link)); //将新释放的块放置在链表的开始处 
}

当线性地址送到页部件翻译时,发现对应的页目录什么也没有,便会产生缺页异常,通常异常产生的情况有这么几种:

  • 目标页帧不存在(页表项全为0,即该线性地址与物理地址尚未建立映射或者已经撤销);
  • 相应的物理页帧不在内存中(页表项非空,但Present标志位=0,比如在swap分区或磁盘文件上);
  • 不满足访问权限(此时页表项P标志=1,但低权限的程序试图访问高权限的地址空间,或者有程序试图写只读页面)。

当出现上面情况之一,那么就会产生页面page fault(#PF)异常。CPU会把产生异常的线性地址存储在CR2中,并且把表示页访问异常类型的值(简称页访问异常错误码,errorCode)保存在中断栈中。页访问异常错误码有32位,大多数位为0。其他各位的含义为:

  • 位0为1表示对应物理页不存在,位0为0表示保护错;
  • 位1为1表示写异常(比如写了只读页),位1为0表示读异常;
  • 位2为1表示用户访问权限异常(比如用户态程序访问内核空间的数据),位2为0表示内核访问异常。

当上述异常发生时,应当执行中断服务例程,进行相应的处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是浩浩子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值