ChCore_lab2 做题笔记

简介

文档链接:Lab2: 内存管理 - IPADS OS Course Lab Manual

本实验主要目的在于让同学们熟悉内核启动过程中对内存的初始化和内核启动后对物理内存和页表的管理,包括三个部分。

  1. 物理内存管理: 理解并完成伙伴系统以及SLAB系统
  2. 虚拟页表管理: 深入理解页表分配机制以及页表项权限机制,并完成页表分配函数。
  3. 缺页异常处理: 理解aarch64架构下的异常处理机制,并按照页表项的配置完成按需分配以及写时拷贝的缺页管理设置。

!本次实验由于时间紧任务重,只完成代码部分

代码导读(list_head, 页表原理并不在此列,自行了解)

main函数讲起,在初始化完cpu信息之后,就开始初始化页表(mm_init

image-20241117130710048
  • mm_init 函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
void mm_init(void *physmem_info)
{
        int physmem_map_idx;

        /* Step-1: 解析physmem_info以获取physmem的每个连续范围 */
        physmem_map_num = 0; //表示总共有多少个内存池
        parse_mem_map(physmem_info); // 解析完之后会修改上述的physmem_map_num值

        /* Step-2: init the buddy allocators for each continuous range of the
         * physmem. */
        for (physmem_map_idx = 0; physmem_map_idx < physmem_map_num;
             ++physmem_map_idx)
                init_buddy_for_one_physmem_map(physmem_map_idx); // 初始化内存池

        /* Step-3: init the slab allocator. */
        init_slab();
}
  • init_buddy_for_one_physmem_map函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
static void init_buddy_for_one_physmem_map(int physmem_map_idx)
{
        paddr_t free_mem_start = 0;
    
       /* ...省略变量初始化操作 */
    
        paddr_t free_page_start = 0;

        free_mem_start = physmem_map[physmem_map_idx][0]; // 该内存池的起始地址
        free_mem_end = physmem_map[physmem_map_idx][1]; // 该内存池的结束位置
    	// 计算该内存池可以分配的页表数
        npages = (free_mem_end - free_mem_start) // 内存大小
                 / (PAGE_SIZE + sizeof(struct page));  // 要加上页表元数据的大小
        free_page_start = ROUND_UP( // 可分配的页表内存起始地址(ROUND_UP表示按页向上对齐)
                free_mem_start + npages * sizeof(struct page), PAGE_SIZE);

        /* 对齐后重新计算 npages。*/
        npages1 = (free_mem_end - free_page_start) / PAGE_SIZE;
        npages = npages < npages1 ? npages : npages1;

    	// 将起始地址初始化为第一个页表元数据
        page_meta_start = (struct page *)phys_to_virt(free_mem_start);

        /* 根据该空闲内存区域初始化伙伴分配器。 */
        init_buddy(&global_mem[physmem_map_idx],
                   page_meta_start,
                   phys_to_virt(free_page_start),
                   npages);
}
  • 页表相关结构体
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
struct page {
        struct list_head node;
        int allocated; // 对应的物理页面现在是否空闲
        int order; // 该页面所属的内存块的阶数,判断内存页大小的唯一根据
        void *slab; // 用于 ChCore slab 分配器
        struct phys_mem_pool *pool; // 所属的内存池
};
struct free_list {
        struct list_head free_list;
        unsigned long nr_free;
};
struct phys_mem_pool { // 该结构体不归伙伴系统管辖
        vaddr_t pool_start_addr; // 该物理内存池的起始虚拟地址(供内核使用)
        unsigned long pool_mem_size; // 内存池管理的内存大小
		
    	// 该池的元数据区域的起始虚拟地址(供内核使用)
        struct page *page_metadata;  // 第一个页的地址
        struct lock buddy_lock; // 内存池的锁
        /* 内存块的空闲列表,根据order存储 */
        struct free_list free_lists[BUDDY_MAX_ORDER];

        /*
         * This field is only used in ChCore unit test.
         * The number of (4k) physical pages in this physical memory pool.
         * 不重要
         */
        unsigned long pool_phys_page_num;
};
  • 初始化伙伴系统
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
void init_buddy(struct phys_mem_pool *pool, struct page *start_page,
                vaddr_t start_addr, unsigned long page_num)
{
        int order;
        int page_idx;
        struct page *page;

        BUG_ON(lock_init(&pool->buddy_lock) != 0);

        /* 初始化物理内存池的元数据 */
        pool->pool_start_addr = start_addr;
        pool->page_metadata = start_page;
        pool->pool_mem_size = page_num * BUDDY_PAGE_SIZE;
        /* This field is for unit test only. (不重要)*/
        pool->pool_phys_page_num = page_num;

        /* 初始化内存池中的链表 */
        for (order = 0; order < BUDDY_MAX_ORDER; ++order) {
                pool->free_lists[order].nr_free = 0;
                init_list_head(&(pool->free_lists[order].free_list));
        }

        /* 初始化所有的页表元数据为 0 */
        memset((char *)start_page, 0, page_num * sizeof(struct page));

        /* 初始化所有的页表元数据为 已分配. */
        for (page_idx = 0; page_idx < page_num; ++page_idx) {
                page = start_page + page_idx;
                page->allocated = 1;
                page->order = 0;
                page->pool = pool;
        }

        /* 通过buddy_free_pages函数来将页表放入内存池的链表内 */
        for (page_idx = 0; page_idx < page_num; ++page_idx) {
                page = start_page + page_idx;
                buddy_free_pages(pool, page);
        }
}
  • 获取伙伴块以及地址转换函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
__maybe_unused static struct page *get_buddy_chunk(struct phys_mem_pool *pool,
                                                   struct page *chunk)
{
        vaddr_t chunk_addr;
        vaddr_t buddy_chunk_addr;
        int order;

        /* Get the address of the chunk. */
        chunk_addr = (vaddr_t)page_to_virt(chunk);
        order = chunk->order; 
        /*
         * Calculate the address of the buddy chunk according to the address
         * relationship between buddies.
         */ 
    	// 根据order和chunk在特定位取反来获取伙伴块地址
        buddy_chunk_addr = chunk_addr
                           ^ (1UL << (order + BUDDY_PAGE_SIZE_ORDER));

        /* Check whether the buddy_chunk_addr belongs to pool. */
    	// 判断该伙伴块是否在这个内存池的管辖范围
        if ((buddy_chunk_addr < pool->pool_start_addr)
            || ((buddy_chunk_addr + (1 << order) * BUDDY_PAGE_SIZE)
                > (pool->pool_start_addr + pool->pool_mem_size))) {
                return NULL;
        }

        return virt_to_page((void *)buddy_chunk_addr);
}

void *page_to_virt(struct page *page)
{
        vaddr_t addr;
        struct phys_mem_pool *pool = page->pool;

        BUG_ON(pool == NULL);

        /* page_idx * BUDDY_PAGE_SIZE + start_addr */
        addr = (page - pool->page_metadata) * BUDDY_PAGE_SIZE
               + pool->pool_start_addr;
        return (void *)addr;
}

struct page *virt_to_page(void *ptr)
{
        struct page *page;
        struct phys_mem_pool *pool = NULL;
        vaddr_t addr = (vaddr_t)ptr;
        int i;

        /* 找到对应的物理内存池 */
        for (i = 0; i < physmem_map_num; ++i) {
                if (addr >= global_mem[i].pool_start_addr
                    && addr < global_mem[i].pool_start_addr
                                       + global_mem[i].pool_mem_size) {
                        pool = &global_mem[i];
                        break;
                }
        }

        if (pool == NULL) {
                kdebug("invalid pool in %s", __func__);
                return NULL;
        }
		// 根据输入虚拟的地址计算出对应的页表地址
        page = pool->page_metadata
               + (((vaddr_t)addr - pool->pool_start_addr) / BUDDY_PAGE_SIZE);
        return page;
}

练习题1

完成 kernel/mm/buddy.c 中的 split_chunkmerge_chunkbuddy_get_pages、 和 buddy_free_pages 函数中的 LAB 2 TODO 1 部分,其中 buddy_get_pages 用于分配指定阶大小的连续物理页,buddy_free_pages 用于释放已分配的连续物理页。

根据init_buddy中对buddy_free_pages的使用,我们知道这个函数主要是将page->allocated设置回0,再结合伙伴系统的知识,我们知道在释放完内存之后还要尽可能地合并伙伴,即调用merge_chunk函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
void buddy_free_pages(struct phys_mem_pool *pool, struct page *page)
{
        int order;
        struct list_head *free_list;
        lock(&pool->buddy_lock); // 为了保证多线程数据安全,操作内存池时要获取锁

        /* LAB 2 TODO 1 BEGIN */
        /*
         * Hint: Merge the chunk with its buddy and put it into
         * a suitable free list.
         */
        /* BLANK BEGIN */
        page->allocated = 0; // 先设置为未分配
        page = merge_chunk(pool, page); // 合并伙伴块
        order = page->order;  // 获取合并之后的伙伴 order 值
    	// 由于merge之后page所指向不一定是原来的page,为了保险,再次设置为未分配
    	page->allocated = 0; 
    
    	// 将页表放入内存池中进行管理
        free_list = &(pool->free_lists[order].free_list);
        list_add(&(page->node), free_list);
        pool->free_lists[order].nr_free += 1;
        /* BLANK END */
        /* LAB 2 TODO 1 END */

        unlock(&pool->buddy_lock);
}

__maybe_unused static struct page *
merge_chunk(struct phys_mem_pool *__maybe_unused pool,
            struct page *__maybe_unused chunk)
{
        /* LAB 2 TODO 1 BEGIN */
        /*
         * Hint: Recursively merge current chunk with its buddy
         * if possible. 递归合并至不能合并
         */
        /* BLANK BEGIN */
        if (chunk->order == BUDDY_MAX_ORDER - 1) // 如果当前块已经是最大块,直接返回本身
                return chunk;

        struct page *buddy_chunk = get_buddy_chunk(pool, chunk); // 获取伙伴块

    	// 如果找不到伙伴、伙伴被分配、伙伴的阶数与自己不等(该页不完整)就停止合并
        if (buddy_chunk == NULL) 
                return chunk;
        if (buddy_chunk->allocated == 1) 
                return chunk;
        if (buddy_chunk->order != chunk->order)
                return chunk;
		// 如果能够合并,就将伙伴从内存池中取出
        list_del(&(buddy_chunk->node));
        pool->free_lists[buddy_chunk->order].nr_free -= 1;
		
    	// 将两个伙伴合并
        buddy_chunk->order += 1;
        chunk->order += 1;
  
        if (buddy_chunk < chunk) // 确保低地址在前
                chunk = buddy_chunk;
		
        return merge_chunk(pool, chunk); // 递归合并并返回合并后的块
        /* BLANK END */
        /* LAB 2 TODO 1 END */
}

参照merge_chunkbuddy_free_pages,实现split_chunkbuddy_get_pages

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
struct page *buddy_get_pages(struct phys_mem_pool *pool, int order)
{
        int cur_order;
        struct list_head *free_list;
        struct page *page = NULL;

        if (unlikely(order >= BUDDY_MAX_ORDER)) {
                kwarn("ChCore does not support allocating such too large "
                      "contious physical memory\n");
                return NULL;
        }

        lock(&pool->buddy_lock);

        /* LAB 2 TODO 1 BEGIN */
        /*
         * Hint: Find a chunk that satisfies the order requirement
         * in the free lists, then split it if necessary.
         */
        /* BLANK BEGIN */
        cur_order = order; // 找到符合条件的空闲块
        while (pool->free_lists[cur_order].nr_free <= 0
               && cur_order < BUDDY_MAX_ORDER)
                cur_order++;

        if (cur_order >= BUDDY_MAX_ORDER) // 如果没找到则返回
                goto out;
		// 取出一个空闲块
        free_list = &(pool->free_lists[cur_order].free_list);
    	// 这里的链表头是一个不存东西的
        page = list_entry(free_list->next, struct page, node);
    	pool->free_lists[page->order].nr_free -= 1;
        list_del(&(page->node));
		// 将空闲块分割为想要的块
        page = split_chunk(pool, order, page);
        page->allocated = 1;
        /* BLANK END */
        /* LAB 2 TODO 1 END */
out:
        __maybe_unused unlock(&pool->buddy_lock);
        return page;
}

__maybe_unused static struct page *
split_chunk(struct phys_mem_pool *__maybe_unused pool, int __maybe_unused order,
            struct page *__maybe_unused chunk)
{
        /* LAB 2 TODO 1 BEGIN */
        /*
         * Hint: Recursively put the buddy of current chunk into
         * a suitable free list.
         */
        /* BLANK BEGIN */
        if (chunk->order == order) // 如果当前块阶数等于想要的阶数
                return chunk;
		
        chunk->order -= 1; // 缩小当前快为原来一半
        struct page *buddy = get_buddy_chunk(pool, chunk); // 获取伙伴块
    	// 将伙伴块设置为未分配,并且将伙伴块放入内存池中
        buddy->allocated = 0;
        buddy->order = chunk->order;
        pool->free_lists[buddy->order].nr_free += 1;
        list_add(&(buddy->node), &(pool->free_lists[buddy->order].free_list));
    
        return split_chunk(pool, order, chunk);
        /* BLANK END */
        /* LAB 2 TODO 1 END */
}

练习题2

完成 kernel/mm/slab.c 中的 choose_new_current_slaballoc_in_slab_implfree_in_slab 函数中的 LAB 2 TODO 2 部分,其中 alloc_in_slab_impl 用于在 slab 分配器中分配指定阶大小的内存,而 free_in_slab 则用于释放上述已分配的内存。

前置代码:

在初始化完伙伴分配器之后,开始初始化slab分配器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
void init_slab(void)
{
        int order;
        /* slab obj size: 32, 64, 128, 256, 512, 1024, 2048 (单位:B)*/
    	// 根据上述单位大小,我们可以知道slab的阶数为 5 到 11 
        for (order = SLAB_MIN_ORDER; order <= SLAB_MAX_ORDER; order++) {
                lock_init(&slabs_locks[order]); // 初始化锁
                slab_pool[order].current_slab = NULL; // 初始化池
                init_list_head(&(slab_pool[order].partial_slab_list)); // 初始化链表
        }
        kdebug("mm: finish initing slab allocators\n");
}

slab_pool的定义

image-20241117150803653
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
struct slab_pointer slab_pool[SLAB_MAX_ORDER + 1];
struct slab_pointer {
        struct slab_header *current_slab;
        struct list_head partial_slab_list; // 串slab_header的链表头
};
/* slab_header 位于每个 slab 的开头 (内容存储在第一个 slot). */
struct slab_header {
        /* 空闲slot列表,可以转换为struct slab_slot_list. */
        void *free_list_head; // 用来将空闲位置串起来
        /* Partial slab list. */
        struct list_head node; // 每个slab都要和其它slab串起来

        int order; // 阶数
        unsigned short total_free_cnt; /* MAX: 65536 */
        unsigned short current_free_cnt; // 当前空闲数
};
/* 一个slab中的每个空闲slot都被视为slab_slot_list。. */
struct slab_slot_list {
        void *next_free;
};

static struct slab_header *init_slab_cache(int order, int size)
{
        void *addr;
        struct slab_slot_list *slot;
        struct slab_header *slab;
        unsigned long cnt, obj_size;
        int i;

        addr = alloc_slab_memory(size); // 分配一个内存来当作slab
        if (unlikely(addr == NULL))
                /* Fail: no available memory. */
                return NULL;
        slab = (struct slab_header *)addr;

        obj_size = order_to_size(order);  // 每个槽的大小
        /* The first slot is used as metadata (struct slab_header). */
        BUG_ON(obj_size == 0);
        cnt = size / obj_size - 1; // 总共可用的槽数量(第一个槽存元数据)

  		// 第二个槽的地址
        slot = (struct slab_slot_list *)((vaddr_t)addr + obj_size);
    	// 将第二个槽放入链表头,根据上下文,可以知道这个链表头是可用的,而不像伙伴系统的链表头
        slab->free_list_head = (void *)slot;
        slab->order = order;
        slab->total_free_cnt = cnt;
        slab->current_free_cnt = cnt;

        /* 循环将所有的空闲槽串起来 & The last slot has no next one. */
        for (i = 0; i < cnt - 1; ++i) {
                slot->next_free = (void *)((unsigned long)slot + obj_size);
                slot = (struct slab_slot_list *)((unsigned long)slot
                                                 + obj_size);
        }
        slot->next_free = NULL;

        return slab;
}

题解:

choose_new_current_slab函数,从slab_list中将下一个slab放到current_slab

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
static void choose_new_current_slab(struct slab_pointer *__maybe_unused pool)
{
        /* LAB 2 TODO 2 BEGIN */
        /* Hint: Choose a partial slab to be a new current slab. */
        /* BLANK BEGIN */
        struct list_head *slab_list;
        slab_list = &(pool->partial_slab_list); // 获取slab list链表头
        if (list_empty(slab_list)) { // 如果为空则将current_slab设置为空
                pool->current_slab = NULL;
        } else {// 获取下一个slab,并将current_slab设置为它,然后从链表中删除
                struct slab_header *slab;
                slab = list_entry(slab_list->next, struct slab_header, node);
                pool->current_slab = slab;
                list_del(slab_list->next);
        }
        /* BLANK END */
        /* LAB 2 TODO 2 END */
}

alloc_in_slab_impl函数,在slab中获取一个空闲空间

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
static void *alloc_in_slab_impl(int order)
{
        struct slab_header *current_slab;
        struct slab_slot_list *free_list;
        void *next_slot;
        UNUSED(next_slot);

        lock(&slabs_locks[order]);

        current_slab = slab_pool[order].current_slab; 
        /* When serving the first allocation request. */
        if (unlikely(current_slab == NULL)) { // 当这个slab没有被初始化时,先初始化
                current_slab = init_slab_cache(order, SIZE_OF_ONE_SLAB);
                if (current_slab == NULL) { // 初始化失败返回空
                        unlock(&slabs_locks[order]);
                        return NULL;
                }
                slab_pool[order].current_slab = current_slab;
        }

        /* LAB 2 TODO 2 BEGIN */
        /*
         * Hint: Find a free slot from the free list of current slab.
         * If current slab is full, choose a new slab as the current one.
         */
        /* BLANK BEGIN */
        free_list = (struct slab_slot_list *)(current_slab->free_list_head);
        BUG_ON(free_list == NULL); 
        next_slot = free_list->next_free; // 获得下一个空闲slot
        current_slab->free_list_head = next_slot; // 将head设置为next
        current_slab->current_free_cnt -= 1; // 空闲减一
    	// 如果分配完这次之后没有可分配的空间了,就将下一个slab的换上来
        if (unlikely(current_slab->current_free_cnt == 0)) 
                choose_new_current_slab(&(slab_pool[order]));
        /* BLANK END */
        /* LAB 2 TODO 2 END */

        unlock(&slabs_locks[order]);

        return (void *)free_list;
}
// 这里形成闭环,当所有的slab分配完之后,我们调用choose_new_current_slab就会将current_slab设置为NULL,再次调用这个函数时就又会从伙伴系统中分配一个slab

free_in_slab函数,释放已分配的slot

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
void free_in_slab(void *addr)
{
        struct page *page;
        struct slab_header *slab;
        struct slab_slot_list *slot;
        int order;

        slot = (struct slab_slot_list *)addr; 
        page = virt_to_page(addr); 
        if (!page) {
                kdebug("invalid page in %s", __func__);
                return;
        }

        slab = page->slab; // 获取对应的slab
        order = slab->order; 
        lock(&slabs_locks[order]);

        try_insert_full_slab_to_partial(slab);

#if ENABLE_DETECTING_DOUBLE_FREE_IN_SLAB == ON
        /*
         * SLAB double free detection: check whether the slot to free is
         * already in the free list.
         */
        if (check_slot_is_free(slab, slot) == 1) {
                kinfo("SLAB: double free detected. Address is %p\n",
                      (unsigned long)slot);
                BUG_ON(1);
        }
#endif

        /* LAB 2 TODO 2 BEGIN */
        /*
         * Hint: Free an allocated slot and put it back to the free list.
         */
        /* BLANK BEGIN */
    	// 类似于链栈操作
        slot->next_free = slab->free_list_head;
        slab->free_list_head = slot;
        slab->current_free_cnt += 1;
        /* BLANK END */
        /* LAB 2 TODO 2 END */

        try_return_slab_to_buddy(slab, order);

        unlock(&slabs_locks[order]);
}

练习题 3

完成 kernel/mm/kmalloc.c 中的 _kmalloc 函数中的 LAB 2 TODO 3 部分,在适当位置调用对应的函数,实现 kmalloc 功能

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
void *_kmalloc(size_t size, bool is_record, size_t *real_size)
{
        void *addr = NULL;
        int order;

        if (unlikely(size == 0))
                return ZERO_SIZE_PTR;

        if (size <= SLAB_MAX_SIZE) {
                /* LAB 2 TODO 3 BEGIN */
                /* Step 1: Allocate in slab for small requests. */
                /* BLANK BEGIN */
            	// 通过查看alloc_in_slab函数,发现只需要将参数传进去
                addr = alloc_in_slab(size, real_size);
                /* BLANK END */
#if ENABLE_MEMORY_USAGE_COLLECTING == ON
                if (is_record && collecting_switch) {
                        record_mem_usage(*real_size, addr);
                }
#endif
        } else {
                /* Step 2: Allocate in buddy for large requests. */
                /* BLANK BEGIN */
            	//
            	// 我们可以手搓获取order,但参考alloc_in_slab
            	// 我们发现已经有一个size_to_page_order的方法
                order = size_to_page_order(size);
                addr = get_pages(order);
                /* BLANK END */
                /* LAB 2 TODO 3 END */
        }

        BUG_ON(!addr);
        return addr;
}

练习题4

完成 kernel/arch/aarch64/mm/page_table.c 中的 query_in_pgtblmap_range_in_pgtbl_commonunmap_range_in_pgtblmprotect_in_pgtbl 函数中的 LAB 2 TODO 4 部分,分别实现页表查询、映射、取消映射和修改页表权限的操作,以 4KB 页为粒度。

前置代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
typedef union {
        struct {
            u64 is_valid        : 1,
                    is_table        : 1,
                    ignored1        : 10,
                    next_table_addr : 36,
                    reserved        : 4,
                    ignored2        : 7,
                    PXNTable        : 1,   // Privileged Execute-never for next level
                    XNTable         : 1,   // Execute-never for next level
                    APTable         : 2,   // Access permissions for next level
                    NSTable         : 1;
        } table;
        struct {
                u64 is_valid        : 1,
                    is_table        : 1,
                    attr_index      : 3,   // Memory attributes index
                    NS              : 1,   // Non-secure
                    AP              : 2,   // Data access permissions
                    SH              : 2,   // Shareability
                    AF              : 1,   // Accesss flag
                    nG              : 1,   // Not global bit
                    reserved1       : 4,
                    nT              : 1,
                    reserved2       : 13,
                    pfn             : 18,
                    reserved3       : 2,
                    GP              : 1,
                    reserved4       : 1,
                    DBM             : 1,   // Dirty bit modifier
                    Contiguous      : 1,
                    PXN             : 1,   // Privileged execute-never
                    UXN             : 1,   // Execute never
                    soft_reserved   : 4,
                    PBHA            : 4;   // Page based hardware attributes
        } l1_block;
        struct {
                u64 is_valid        : 1,
                    is_table        : 1,
                    attr_index      : 3,   // Memory attributes index
                    NS              : 1,   // Non-secure
                    AP              : 2,   // Data access permissions
                    SH              : 2,   // Shareability
                    AF              : 1,   // Accesss flag
                    nG              : 1,   // Not global bit
                    reserved1       : 4,
                    nT              : 1,
                    reserved2       : 4,
                    pfn             : 27,
                    reserved3       : 2,
                    GP              : 1,
                    reserved4       : 1,
                    DBM             : 1,   // Dirty bit modifier
                    Contiguous      : 1,
                    PXN             : 1,   // Privileged execute-never
                    UXN             : 1,   // Execute never
                    soft_reserved   : 4,
                    PBHA            : 4;   // Page based hardware attributes
        } l2_block;
        struct {
                u64 is_valid        : 1,
                    is_page         : 1,
                    attr_index      : 3,   // Memory attributes index
                    NS              : 1,   // Non-secure
                    AP              : 2,   // Data access permissions
                    SH              : 2,   // Shareability
                    AF              : 1,   // Accesss flag
                    nG              : 1,   // Not global bit
                    pfn             : 36,
                    reserved        : 3,
                    DBM             : 1,   // Dirty bit modifier
                    Contiguous      : 1,
                    PXN             : 1,   // Privileged execute-never
                    UXN             : 1,   // Execute never
                    soft_reserved   : 4,
                    PBHA            : 4,   // Page based hardware attributes
                    ignored         : 1;
        } l3_page;
        u64 pte;
} pte_t; // 页表项联合体,增加代码可读性

typedef struct {
        pte_t ent[PTP_ENTRIES];
} ptp_t; // 页表结构体,每个页表有512个页表项
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/*
cur_ptp: 当前页表地址
level: 当前页表级别
va: 虚拟地址
next_ptp: 下一级页表地址
pte: 返回的页表项地址
alloc: 如果页表项不合法时是否分配
rss: 用于记录内存占用
*/
static int get_next_ptp(ptp_t *cur_ptp, u32 level, vaddr_t va, ptp_t **next_ptp,
                        pte_t **pte, bool alloc, __maybe_unused long *rss)
{
        u32 index = 0;
        pte_t *entry;

        if (cur_ptp == NULL)
                return -ENOMAPPING;

        switch (level) {
        case L0:
                index = GET_L0_INDEX(va);
                break;
        case L1:
                index = GET_L1_INDEX(va);
                break;
        case L2:
                index = GET_L2_INDEX(va);
                break;
        case L3:
                index = GET_L3_INDEX(va);
                break;
        default:
                BUG("unexpected level\n");
                return -EINVAL;
        }

        entry = &(cur_ptp->ent[index]); // 获取页表项
        if (IS_PTE_INVALID(entry->pte)) { // ... 省略n行
        }

   		// 获取页表项对应的页表地址(也有可能是一个块)
        *next_ptp = (ptp_t *)GET_NEXT_PTP(entry); // 返回下一级页表地址
        *pte = entry; // 将获取到的页表项返回
        if (IS_PTE_TABLE(entry->pte)) // 判断是块还是页表
                return NORMAL_PTP; 
        else
                return BLOCK_PTP;
}

题解:

query_in_pgtbl函数, 将虚拟地址翻译成物理地址,并返回存储该物理地址的页表项

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/*
pgtbl: L0页表地址
va: 虚拟地址
pa: 物理地址
entry: 存储该物理地址的页表项
*/
int query_in_pgtbl(void *pgtbl, vaddr_t va, paddr_t *pa, pte_t **entry)
{
        /* LAB 2 TODO 4 BEGIN */
        /*
         * Hint: Walk through each level of page table using `get_next_ptp`,
         * return the pa and pte until a L2/L3 block or page, return
         * `-ENOMAPPING` if the va is not mapped.
         */
        /* BLANK BEGIN */
        ptp_t *l0_ptp, *l1_ptp, *l2_ptp, *l3_ptp;
        ptp_t *phys_p;
        pte_t *pte;
        int ret;
    	// 翻译0级页表,获得1级页表地址
        l0_ptp = (ptp_t *)pgtbl;
        ret = get_next_ptp(l0_ptp, 0, va, &l1_ptp, &pte, false, NULL);
        BUG_ON(ret != NORMAL_PTP); // 题目描述是以4KB为粒度,所以不考虑块地址
    	// 翻译1级页表,获得2级页表地址
        ret = get_next_ptp(l1_ptp, 1, va, &l2_ptp, &pte, false, NULL);
        BUG_ON(ret != NORMAL_PTP);
		// 翻译2级页表,获得3级页表地址
        ret = get_next_ptp(l2_ptp, 2, va, &l3_ptp, &pte, false, NULL);
       	BUG_ON(ret != NORMAL_PTP);
    	// 翻译3级页表,获得物理页地址
        ret = get_next_ptp(l3_ptp, 3, va, &phys_p, &pte, false, NULL);
        if (ret < 0)
                return ret;      
     	// 此时phys_p为物理页地址,而操作系统也只能通过虚拟地址访问内存
     	// get_next_ptp默认将物理地址转为虚拟地址返回
     	// 所以我们要将得到的虚拟地址先转为物理地址
     	// 最后,我们要再加上偏移值
        if (pa)
                *pa = virt_to_phys(phys_p) + GET_VA_OFFSET_L3(va);
        if (entry)
                *entry = pte;
        /* BLANK END */
        /* LAB 2 TODO 4 END */
        return 0;
}

map_range_in_pgtbl_common函数,建立页表映射

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
/*
pgtbl: L0页表地址
va: 虚拟地址(起始)
pa: 物理地址(起始)
len: 映射大小
flag: 权限标识,在set_pte_flags中用到
kind: 页表类型,0代表用户态页表,1代表内核页表
rss: 跟踪内存占用
*/
static int map_range_in_pgtbl_common(void *pgtbl, vaddr_t va, paddr_t pa,
                                     size_t len, vmr_prop_t flags, int kind,
                                     __maybe_unused long *rss)
{
        /* LAB 2 TODO 4 BEGIN */
        /*
         * Hint: Walk through each level of page table using `get_next_ptp`,
         * create new page table page if necessary, fill in the final level
         * pte with the help of `set_pte_flags`. Iterate until all pages are
         * mapped.
         * Since we are adding new mappings, there is no need to flush TLBs.
         * Return 0 on success.
         */
        /* BLANK BEGIN */
        u64 total_page_cnt;
        ptp_t *l0_ptp, *l1_ptp, *l2_ptp, *l3_ptp;
        pte_t *pte;
        int ret, pte_idx, i;

        BUG_ON(pgtbl == NULL);
    	// 由于映射单位是按页来分的,页内偏移量应该为0
        BUG_ON(va % PAGE_SIZE); 
		// 计算需要的页表数量
        total_page_cnt = len / PAGE_SIZE + (len % PAGE_SIZE > 0 ? 1 : 0);
        l0_ptp = (ptp_t *)pgtbl;
    	// 
        while (total_page_cnt > 0) {
            	// 先一路翻译到l3页表,如果页表不存在则先分配一个页表
                ret = get_next_ptp(l0_ptp, 0, va, &l1_ptp, &pte, true, rss);
                BUG_ON(ret != NORMAL_PTP); 
                ret = get_next_ptp(l1_ptp, 1, va, &l2_ptp, &pte, true, rss);
                BUG_ON(ret != NORMAL_PTP);
                ret = get_next_ptp(l2_ptp, 2, va, &l3_ptp, &pte, true, rss);
                BUG_ON(ret != NORMAL_PTP);
            	// 获取页表项的位置
                pte_idx = GET_L3_INDEX(va);
            	// 从该位置开始循环地分配页表项
                for (i = pte_idx; i < PTP_ENTRIES; i++) {
          				// 配置l3页表项
                        pte_t new_pte;
                        new_pte.pte = 0; // 这里是为了初始化
                        new_pte.l3_page.is_valid = 1;
                        new_pte.l3_page.is_page = 1;
                    	// 获取页表项映射的物理地址位置
                        new_pte.l3_page.pfn = pa >> PAGE_SHIFT;
                    	// 设置内存权限
                        set_pte_flags(&new_pte, flags, kind);
                    	// 赋值给l3页表对应的位置
                        l3_ptp->ent[i].pte = new_pte.pte;
						
                    	// 这里是为了保证下一次循环能够正确的得到起始的va和pa
                        va += PAGE_SIZE;
                        pa += PAGE_SIZE;
                        if (rss) // 记录内存占用
                                *rss += PAGE_SIZE;
                        total_page_cnt -= 1; // 所需页表数减1
                        if (total_page_cnt == 0) // 如果已经分配完毕则退出
                                break;
                }
        }
        /* BLANK END */
        /* LAB 2 TODO 4 END */
    	// 用于保障共享资源的更新
        dsb(ishst); // 确保所有之前的内存操作已完成
        isb(); // 刷新指令流水线
        return 0;
}

unmap_range_in_pgtbl函数,取消内存映射,是map_range_in_pgtbl_common的相反操作,我们只需要复制之后进行修改就好了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
int unmap_range_in_pgtbl(void *pgtbl, vaddr_t va, size_t len,
                         __maybe_unused long *rss)
{
        /* LAB 2 TODO 4 BEGIN */
        /*
         * Hint: Walk through each level of page table using `get_next_ptp`,
         * mark the final level pte as invalid. Iterate until all pages are
         * unmapped.
         * You don't need to flush tlb here since tlb is now flushed after
         * this function is called.
         * Return 0 on success.
         */
        /* BLANK BEGIN */
        u64 total_page_cnt;
        ptp_t *l0_ptp, *l1_ptp, *l2_ptp, *l3_ptp;
        pte_t *pte;
        int ret, pte_idx, i;

        BUG_ON(pgtbl == NULL);
        BUG_ON(va % PAGE_SIZE);

        total_page_cnt = len / PAGE_SIZE + (len % PAGE_SIZE > 0 ? 1 : 0);
        l0_ptp = (ptp_t *)pgtbl;
        while (total_page_cnt > 0) {
                ret = get_next_ptp(l0_ptp, 0, va, &l1_ptp, &pte, false, rss);
                if (ret == -ENOMAPPING) { 
                // 这里与之前不同,当l0_ptp为0
                // 或者对应的页表项不合法的时候才会返回-ENOMAPPING
                // 这意味着对应的内存已经被取消映射
                // 我们只需要保证当前函数逻辑能继续运行下去就好
                        total_page_cnt -= L0_PER_ENTRY_PAGES;
                        va += L0_PER_ENTRY_PAGES * PAGE_SIZE;
                        if (rss)
                                *rss -= L0_PER_ENTRY_PAGES * PAGE_SIZE;
                        continue;
                }
                ret = get_next_ptp(l1_ptp, 1, va, &l2_ptp, &pte, false, rss);
                if (ret == -ENOMAPPING) { // 同上
                        total_page_cnt -= L1_PER_ENTRY_PAGES;
                        va += L1_PER_ENTRY_PAGES * PAGE_SIZE;
                        if (rss)
                                *rss -= L1_PER_ENTRY_PAGES * PAGE_SIZE;
                        continue;
                }
                ret = get_next_ptp(l2_ptp, 2, va, &l3_ptp, &pte, false, rss);
                if (ret == -ENOMAPPING) { // 同上
                        total_page_cnt -= L2_PER_ENTRY_PAGES;
                        va += L2_PER_ENTRY_PAGES * PAGE_SIZE;
                        if (rss)
                                *rss -= L2_PER_ENTRY_PAGES * PAGE_SIZE;
                        continue;
                }
            	// 获取l3页表项的下标
                pte_idx = GET_L3_INDEX(va);
                for (i = pte_idx; i < PTP_ENTRIES; i++) {
                        l3_ptp->ent[i].pte = 0; // 直接将所有值都设置为0即可
                    	// 同 map_range_in_pgtbl_common
                        va += PAGE_SIZE; 
                        if (rss) // 记录内存占用
                                *rss -= PAGE_SIZE;
                        total_page_cnt -= 1;
                        if (total_page_cnt == 0)
                                break;
                }
        }
        /* BLANK END */
        /* LAB 2 TODO 4 END */

        dsb(ishst);
        isb();

        return 0;
}

mprotect_in_pgtbl函数,跟上面一样,只需要改一两句代码,没什么好讲的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
int mprotect_in_pgtbl(void *pgtbl, vaddr_t va, size_t len, vmr_prop_t flags)
{
        /* LAB 2 TODO 4 BEGIN */
        /*
         * Hint: Walk through each level of page table using `get_next_ptp`,
         * modify the permission in the final level pte using `set_pte_flags`.
         * The `kind` argument of `set_pte_flags` should always be `USER_PTE`.
         * Return 0 on success.
         */
        /* BLANK BEGIN */
        u64 total_page_cnt;
        ptp_t *l0_ptp, *l1_ptp, *l2_ptp, *l3_ptp;
        pte_t *pte;
        int ret, pte_idx, i;

        BUG_ON(pgtbl == NULL);
        BUG_ON(va % PAGE_SIZE);

        total_page_cnt = len / PAGE_SIZE + (len % PAGE_SIZE > 0 ? 1 : 0);
        l0_ptp = (ptp_t *)pgtbl;
        while (total_page_cnt > 0) {
                ret = get_next_ptp(l0_ptp, 0, va, &l1_ptp, &pte, false, NULL);
                if (ret == -ENOMAPPING) {
                        total_page_cnt -= L0_PER_ENTRY_PAGES;
                        va += L0_PER_ENTRY_PAGES * PAGE_SIZE;
                        continue;
                }
                ret = get_next_ptp(l1_ptp, 1, va, &l2_ptp, &pte, false, NULL);
                if (ret == -ENOMAPPING) {
                        total_page_cnt -= L1_PER_ENTRY_PAGES;
                        va += L1_PER_ENTRY_PAGES * PAGE_SIZE;
                        continue;
                }
                ret = get_next_ptp(l2_ptp, 2, va, &l3_ptp, &pte, false, NULL);
                if (ret == -ENOMAPPING) {
                        total_page_cnt -= L2_PER_ENTRY_PAGES;
                        va += L2_PER_ENTRY_PAGES * PAGE_SIZE;
                        continue;
                }
                pte_idx = GET_L3_INDEX(va);
                for (i = pte_idx; i < PTP_ENTRIES; i++) {
                        set_pte_flags(&(l3_ptp->ent[i]), flags, USER_PTE);
                        va += PAGE_SIZE;
                        total_page_cnt -= 1;
                        if (total_page_cnt == 0)
                                break;
                }
        }
        /* BLANK END */
        /* LAB 2 TODO 4 END */
        return 0;
}

挑战题7

使用前面实现的 page_table.c 中的函数,在内核启动后的 main 函数中重新配置内核页表,进行细粒度的映射。

这题的意思应该是将所有的内核地址都映射到内核页表内,这需要根据lab1中的介绍来获取内核地址范围。但由于笔者实力不够,还不太明白这题的意思,并且没有多少时间来钻研这个,所以并没有进行实现

练习题 8, 9, 10

  • 完成 kernel/arch/aarch64/irq/pgfault.c 中的 do_page_fault 函数中的 LAB 2 TODO 5 部分,将缺页异常转发给 handle_trans_fault 函数。
  • 完成 kernel/mm/vmspace.c 中的 find_vmr_for_va 函数中的 LAB 2 TODO 6 部分,找到一个虚拟地址找在其虚拟地址空间中的 VMR。
  • 完成 kernel/mm/pgfault_handler.c 中的 handle_trans_fault 函数中的 LAB 2 TODO 7 部分(函数内共有 3 处填空,不要遗漏),实现 PMO_SHMPMO_ANONYM 的按需物理页分配。你可以阅读代码注释,调用你之前见到过的相关函数来实现功能。

以上三题基本等于一题,所以放一起看

do_page_fault 函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
void do_page_fault(u64 esr, u64 fault_ins_addr, int type, u64 *fix_addr)
{
        vaddr_t fault_addr;
        int fsc; // fault status code
        int wnr;
        int ret = 0;

        fault_addr = get_fault_addr(); // 获取缺页异常的地址时要访问的地址
        fsc = GET_ESR_EL1_FSC(esr);	// 获取缺页异常类型
        switch (fsc) {
        case DFSC_TRANS_FAULT_L0:
        case DFSC_TRANS_FAULT_L1:
        case DFSC_TRANS_FAULT_L2:
        case DFSC_TRANS_FAULT_L3: {
                /* LAB 2 TODO 5 BEGIN */
                /* BLANK BEGIN */
            	// 根据上下文,这是一个翻译异常,所以我们参考下面的权限异常来调用异常处理函数
                ret = handle_trans_fault(current_thread->vmspace, fault_addr);
                /* BLANK END */
                /* LAB 2 TODO 5 END */
                if (ret != 0) {
                        /* The trap happens in the kernel */
                        if (type < SYNC_EL0_64) {
                                goto no_context;
                        }

                        kinfo("do_page_fault: faulting ip is 0x%lx (real IP),"
                              "faulting address is 0x%lx,"
                              "fsc is trans_fault (0b%b),"
                              "type is %d\n",
                              fault_ins_addr,
                              fault_addr,
                              fsc,
                              type);
                        kprint_vmr(current_thread->vmspace);

                        kinfo("current_cap_group is %s\n",
                              current_cap_group->cap_group_name);

                        sys_exit_group(-1);
                }
                break;
        }
    // ...省略n行
}

find_vmr_for_va 函数,在vmspace中查找相应vmr

前置代码:

  • 对应的结构体定义
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
struct vmregion {
        struct list_head list_node; /* As one node of the vmr_list */
        struct rb_node tree_node; /* As one node of the vmr_tree */
        /* As one node of the pmo's mapping_list */
        struct list_head mapping_list_node; 

        struct vmspace *vmspace; // 所在用户态空间
        vaddr_t start; // 地址段起始位置
        size_t size; // 地址段大小
        /* Offset of underlying pmo */
        size_t offset; 
        vmr_prop_t perm; // 地址段权限
        struct pmobject *pmo; // 物理内存对象
        struct list_head cow_private_pages; // 由copy-on-write使用
};

/* This struct represents one virtual address space */
struct vmspace { // 用户态空间
        /* List head of vmregion (vmr_list) */
        struct list_head vmr_list; // 用户态地址段链表
        /* rbtree root node of vmregion (vmr_tree) */
        struct rb_root vmr_tree; // 用户态地址段红黑树,用于快速查找相应地址

        /* Root page table */
        void *pgtbl; // L0页表地址
        /* Address space ID for avoiding TLB conflicts */
        unsigned long pcid; // 用户id

        /* The lock for manipulating vmregions */
        struct lock vmspace_lock; 
        /* The lock for manipulating the page table */
        struct lock pgtbl_lock;

        /*
         * For TLB flushing:
         * Record the all the CPU that a vmspace ran on.
         */
        unsigned char history_cpus[PLAT_CPU_NUM]; 

        struct vmregion *heap_boundary_vmr; // 没用到,感兴趣可以通过vsc的搜索功能看一下用法

        /* Records size of memory mapped. Protected by pgtbl_lock. */
        long rss; // 内存管理标志
};
  • 红黑树的查询函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
struct rb_node *rb_search(struct rb_root *this, const void *key,
                          comp_key_func cmp)
{
        struct rb_node *cur = this->root_node;
        int cmp_ret;
        while (cur) {
                cmp_ret = cmp(key, cur);
                if (cmp_ret < 0) {
                        cur = cur->left_child;
                } else if (cmp_ret > 0) {
                        cur = cur->right_child;
                } else {
                        return cur;
                }
        }

        return NULL;
}

题解

我们其实不需要了解太多相关信息,只需要知道怎么查找就好了,这里使用的是红黑树来查找,根据内核实现的红黑树查找函数,以及已经实现了的比较函数cmp_vmr_and_varb_entry,即可完成题目

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
__maybe_unused struct vmregion *find_vmr_for_va(struct vmspace *vmspace,
                                                vaddr_t addr)
{
        /* LAB 2 TODO 6 BEGIN */
        /* Hint: Find the corresponding vmr for @addr in @vmspace */
        /* BLANK BEGIN */
        struct vmregion *vmr;
        struct rb_node *node;
        node = rb_search( // 查找相应的节点
                &(vmspace->vmr_tree), (const void *)addr, cmp_vmr_and_va);
        if (unlikely(node == NULL)) { // 没找到返回NULL
                return NULL;
        }
        vmr = rb_entry(node, struct vmregion, tree_node); // 获取节点所在结构体
        return vmr;
        /* BLANK END */
        /* LAB 2 TODO 6 END */
}

handle_trans_fault 函数

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
int handle_trans_fault(struct vmspace *vmspace, vaddr_t fault_addr)
{
        struct vmregion *vmr;
        struct pmobject *pmo;
        paddr_t pa;
        unsigned long offset;
        unsigned long index;
        int ret = 0;

        /*
         * Grab lock here.
         * Because two threads (in same process) on different cores
         * may fault on the same page, so we need to prevent them
         * from adding the same mapping twice.
         */
        lock(&vmspace->vmspace_lock);
        vmr = find_vmr_for_va(vmspace, fault_addr); // 查找对应vmr

        if (vmr == NULL) { // 如果为空则该地址不合法,直接退出
                kinfo("handle_trans_fault: no vmr found for va 0x%lx!\n",
                      fault_addr);
                dump_pgfault_error();
                unlock(&vmspace->vmspace_lock);

#if defined(CHCORE_ARCH_AARCH64) || defined(CHCORE_ARCH_SPARC)
                /* kernel fault fixup is only supported on AArch64 and Sparc */
                return -EFAULT;
#endif
                sys_exit_group(-1);

                BUG("should not reach here");
        }

        pmo = vmr->pmo; // 获取物理内存对象
        /* Get the offset in the pmo for faulting addr */
        offset = ROUND_DOWN(fault_addr, PAGE_SIZE) - vmr->start + vmr->offset;
        vmr_prop_t perm = vmr->perm; // 获取权限记录
        switch (pmo->type) { // 根据内存对象类型进行处理
        case PMO_ANONYM:
        case PMO_SHM: {
                /* Boundary check */
                BUG_ON(offset >= pmo->size);

                /* Get the index in the pmo radix for faulting addr */
                index = offset / PAGE_SIZE;
				// 处理fault_addr,去除页内偏移以便后续处理
                fault_addr = ROUND_DOWN(fault_addr, PAGE_SIZE);

                pa = get_page_from_pmo(pmo, index); // 从pmo中获取物理地址
                if (pa == 0) { // 如果地址为空,则为未分配,我们直接分配即可
                        /*
                         * Not committed before. Then, allocate the physical
                         * page.
                         */
                        /* LAB 2 TODO 7 BEGIN */
                        /* BLANK BEGIN */
                        /* Hint: Allocate a physical page and clear it to 0. */
                        void *new_va = get_pages(0); // 获取一个页
                        BUG_ON(new_va == NULL);
                        pa = virt_to_phys(new_va); // 转化为物理地址
                        BUG_ON(pa == 0);
                        memset((char *)new_va, 0, PAGE_SIZE); // 将页内清空

                        /* BLANK END */
                        /*
                         * Record the physical page in the radix tree:
                         * the offset is used as index in the radix tree
                         */
                        kdebug("commit: index: %ld, 0x%lx\n", index, pa);
                        commit_page_to_pmo(pmo, index, pa);
                        /* Add mapping in the page table */
                        lock(&vmspace->pgtbl_lock);
                        /* BLANK BEGIN */
                    	// 将地址映射到页表中
                        map_range_in_pgtbl(vmspace->pgtbl,
                                           fault_addr,
                                           pa,
                                           PAGE_SIZE,
                                           perm,
                                           &(vmspace->rss));

                        /* BLANK END */
                        unlock(&vmspace->pgtbl_lock);
                } else {
                        if (pmo->type == PMO_SHM || pmo->type == PMO_ANONYM) {
                                /* Add mapping in the page table */
                                lock(&vmspace->pgtbl_lock);
                                /* BLANK BEGIN */
                            	// 此时内存已被分配,只是没有映射页表,只需要映射页表
                                map_range_in_pgtbl(vmspace->pgtbl,
                                                   fault_addr,
                                                   pa,
                                                   PAGE_SIZE,
                                                   perm,
                                                   &(vmspace->rss));
                                /* BLANK END */
                                /* LAB 2 TODO 7 END */
                                unlock(&vmspace->pgtbl_lock);
                        }
                }

                if (perm & VMR_EXEC) {
                        arch_flush_cache(fault_addr, PAGE_SIZE, SYNC_IDCACHE);
                }

                break;
        }
   //...省略n行
}

挑战题 11

我们在map_range_in_pgtbl_commonunmap_range_in_pgtbl 函数中预留了没有被使用过的参数rss 用来来统计map映射中实际的物理内存使用量1, 你需要修改相关的代码来通过Compute physical memory测试,不实现该挑战题并不影响其他部分功能的实现及测试。如果你想检测是否通过此部分测试,需要修改kernel/config.cmakeCHCORE_KERNEL_PM_USAGE_TEST为ON

题解:在上面的实现中笔者已经实现了大部分的内存记录,此时只需要稍作修改

get_next_ptp 函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
if (IS_PTE_INVALID(entry->pte)) {
	if (alloc == false) {
		return -ENOMAPPING;
	} else {
		/* alloc a new page table page */
		ptp_t *new_ptp;
		paddr_t new_ptp_paddr;
		pte_t new_pte_val;

		/* alloc a single physical page as a new page table page
		*/
		new_ptp = get_pages(0);
		if (new_ptp == NULL)
			return -ENOMEM;
		memset((void *)new_ptp, 0, PAGE_SIZE);

		new_ptp_paddr = virt_to_phys((vaddr_t)new_ptp);

		new_pte_val.pte = 0;
		new_pte_val.table.is_valid = 1;
		new_pte_val.table.is_table = 1;
		new_pte_val.table.next_table_addr = new_ptp_paddr
												>> PAGE_SHIFT;

		/* same effect as: cur_ptp->ent[index] = new_pte_val; */
		entry->pte = new_pte_val.pte;
        // 只需要在这里加一个内存记录
		if (rss)
			*rss += PAGE_SIZE;
	}
}

try_release_ptp 函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
static int try_release_ptp(ptp_t *high_ptp, ptp_t *low_ptp, int index,
                           __maybe_unused long *rss)
{
        int i;

        for (i = 0; i < PTP_ENTRIES; i++) {
                if (low_ptp->ent[i].pte != PTE_DESCRIPTOR_INVALID) {
                        return 0;
                }
        }

        BUG_ON(index < 0 || index >= PTP_ENTRIES);

        high_ptp->ent[index].pte = PTE_DESCRIPTOR_INVALID;
        kfree(low_ptp);
        if (rss) // 加一个内存记录
                *rss -= PAGE_SIZE;
        return 1;
}

unmap_range_in_pgtbl函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
pte_idx = GET_L3_INDEX(va);
pre_va = va; // 先记录原来的地址
for (i = pte_idx; i < PTP_ENTRIES; i++) {
	l3_ptp->ent[i].pte = PTE_DESCRIPTOR_INVALID;
	va += PAGE_SIZE;
	if (rss)
		*rss -= PAGE_SIZE;
		total_page_cnt -= 1;
		if (total_page_cnt == 0) {
            // 如果此时已经将所有的页表取消了映射,调用页表回收函数,已经被实现
			recycle_pgtable_entry(l0_ptp, l1_ptp, l2_ptp, l3_ptp, pre_va, rss);
			break;
		}
	}
// 每次大循环都要回收尝试回收一次页表
if (total_page_cnt != 0) // 防止多次调用回收函数导致计算错误,所以要先判断
	recycle_pgtable_entry(l0_ptp, l1_ptp, l2_ptp, l3_ptp, pre_va, rss);