|  |  |  | @ -0,0 +1,440 @@ | 
			
		
	
		
			
				
					|  |  |  |  | ## 第四章.(实验3)物理内存管理 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | ### 4.1 实验内容 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 实验要求:了解物理内存,管理物理内存。  | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | **4.1.1 练习一:OS内存的初始化过程** | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   **在**"pk/mmap.c"内有 pk_vm_init()函数,阅读该函数,了解OS内存初始化的过程。 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | ``` | 
			
		
	
		
			
				
					|  |  |  |  | 364  uintptr_t pk_vm_init() | 
			
		
	
		
			
				
					|  |  |  |  | 365  { | 
			
		
	
		
			
				
					|  |  |  |  | 366      // HTIF address signedness and va2pa macro both cap memory size to 2 GiB | 
			
		
	
		
			
				
					|  |  |  |  |          //设置物理内存大小 | 
			
		
	
		
			
				
					|  |  |  |  | 367      mem_size = MIN(mem_size, 1U << 31); | 
			
		
	
		
			
				
					|  |  |  |  |               //计算物理页的数量 | 
			
		
	
		
			
				
					|  |  |  |  | 368      size_t mem_pages = mem_size >> RISCV_PGSHIFT; | 
			
		
	
		
			
				
					|  |  |  |  | 369      free_pages = MAX(8, mem_pages >> (RISCV_PGLEVEL_BITS-1)); | 
			
		
	
		
			
				
					|  |  |  |  | 370           | 
			
		
	
		
			
				
					|  |  |  |  |               //_end为内核结束地址 | 
			
		
	
		
			
				
					|  |  |  |  | 371      extern char _end; | 
			
		
	
		
			
				
					|  |  |  |  | 372      first_free_page = ROUNDUP((uintptr_t)&_end, RISCV_PGSIZE); | 
			
		
	
		
			
				
					|  |  |  |  | 373      first_free_paddr = first_free_page + free_pages * RISCV_PGSIZE; | 
			
		
	
		
			
				
					|  |  |  |  | 374               | 
			
		
	
		
			
				
					|  |  |  |  |        //映射内核的物理空间 | 
			
		
	
		
			
				
					|  |  |  |  | 375      root_page_table = (void*)__page_alloc(); | 
			
		
	
		
			
				
					|  |  |  |  | 376      __map_kernel_range(DRAM_BASE, DRAM_BASE, first_free_paddr - DRAM_BASE, PROT_READ|PROT_WRITE|PROT_EXEC); | 
			
		
	
		
			
				
					|  |  |  |  | 377               | 
			
		
	
		
			
				
					|  |  |  |  |        //crrent.mmap_max: 0x000000007f7ea000 | 
			
		
	
		
			
				
					|  |  |  |  | 378      current.mmap_max = current.brk_max = | 
			
		
	
		
			
				
					|  |  |  |  | 379       MIN(DRAM_BASE, mem_size - (first_free_paddr - DRAM_BASE)); | 
			
		
	
		
			
				
					|  |  |  |  | 380           | 
			
		
	
		
			
				
					|  |  |  |  |               //映射用户栈 | 
			
		
	
		
			
				
					|  |  |  |  | 381      size_t stack_size = MIN(mem_pages >> 5, 2048) * RISCV_PGSIZE; | 
			
		
	
		
			
				
					|  |  |  |  | 382      size_t stack_bottom = __do_mmap(current.mmap_max - stack_size, stack_size, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED , 0, 0); | 
			
		
	
		
			
				
					|  |  |  |  | 383      kassert(stack_bottom != (uintptr_t)-1); | 
			
		
	
		
			
				
					|  |  |  |  | 384      current.stack_top = stack_bottom + stack_size; | 
			
		
	
		
			
				
					|  |  |  |  | 385 | 
			
		
	
		
			
				
					|  |  |  |  |               //开启分页 | 
			
		
	
		
			
				
					|  |  |  |  | 386      flush_tlb(); | 
			
		
	
		
			
				
					|  |  |  |  | 387      write_csr(sptbr, ((uintptr_t)root_page_table >> RISCV_PGSHIFT) | SATP_MODE_CHOICE); | 
			
		
	
		
			
				
					|  |  |  |  | 388 | 
			
		
	
		
			
				
					|  |  |  |  |               //分配内核栈空间, | 
			
		
	
		
			
				
					|  |  |  |  | 389      uintptr_t kernel_stack_top = __page_alloc() + RISCV_PGSIZE; | 
			
		
	
		
			
				
					|  |  |  |  | 390      return kernel_stack_top; | 
			
		
	
		
			
				
					|  |  |  |  | 391  } | 
			
		
	
		
			
				
					|  |  |  |  | ``` | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 以上代码中,我们给出了大体的注释,请根据以上代码,尝试画出RISCV的物理内存结构图。 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | **4.1.2 练习二:first_fit内存页分配算法(需要编程)** | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 在"pk/pmm.c" 中,我们实现了对物理内存的管理。 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 构建了物理内存页管理器框架:struct pmm_manager结构如下: | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 135     const struct pmm_manager default_pmm_manager = { | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 136       .name = "default_pmm_manager", | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 137       .init = default_init, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 138       .init_memmap = default_init_memmap, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 139       .alloc_pages = default_alloc_pages, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 140       .free_pages = default_free_pages, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 141       .nr_free_pages = default_nr_free_pages, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 142       .pmm_check = basic_check, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 143     }; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 默认的内存管理器有如下属性: | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | l name:内存管理器的名字 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | l init:对内存管理算法所使用的数据结构进行初始化 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | l init_ memmap:根据物理内存设置内存管理算法的数据结构 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | l alloc_pages:分配物理页 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | l free_pages:释放物理页 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | l nr_free_pages:空闲物理页的数量 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | l pmm_check :检查校验函数 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 参考已经实现的函数,完成default_alloc_pages()和default_free_pages(),实现first_fit内存页分配算法。 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | first_fit分配算法需要维护一个查找有序(地址按从小到大排列)空闲块(以页为最小单位的连续地址空间)的数据结构,而双向链表是一个很好的选择。pk/list.h定义了可挂接任意元素的通用双向链表结构和对应的操作,所以需要了解如何使用这个文件提供的各种函数,从而可以完成对双向链表的初始化/插入/删除等。 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |      | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 你可以使用python脚本检查你的输出: | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | ./pke-lab3 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 若得到如下输出,那么恭喜你,你已经成功完成了实验三!!! | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | build pk : OK | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | running app3 m2048 : OK | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |  test3_m2048 : OK | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | running app3 m1024 : OK | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |  test3_m1024 : OK | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | Score: 20/20  | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | ### 4.2 基础知识 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | **4.2.1 物理内存空间与空间编址** | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 计算机的存储结构可以抽象的看做由N个连续的字节组成的数组。想一想,在数组中我们如何找到一个元素?对了!是下标!!那么我们如何在内存中如何找打一个元素呢?自然也是‘下标’。这个下标的起始位置和位数由机器本身决定,我们称之为“物理地址”。 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 在riscv中,内存地址是从0x80000000 开始的。在pke的连接文件pke.lds中,我们可以看到这样两行: | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 14  /* Begining of code and text segment */ | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 15  . = 0x80000000; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 至于内存的大小,还记得实验二中的-m选项吗?spike模拟器可以通过-m选项配置物理内存的大小。 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 现在,思考一下问题:首先,为什么需要物理内存的管理?这个问题可以用另一个问题回答:当程序如malloc申请一段内存空间的时候,你如何准确的给出一片符合大小要求的,且安全可用的内存空间。 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 其次,我们现在管理的可用内存是那一部分? 代理内核的本质也是一段程序,他本身是需要内存空间的,而这一段空间自然不能再被分配。除去内核本身占的空间,内核可支配的物理空间从0x80016000开始,大小在PKE设定为8M,2048个页面,故而供内核支配的的内存的范围为(first_free_page~first_free_paddr)。如下图所示。 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | KERNTOP------->+---------------------------------+ 0x80816000 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | (first_free_paddr)  |                 | | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |          |    Kern Physical Memory    |  | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |           |                 | 8M 2048pages | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |  (first_free_page)  |                 | | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |  KERNBSE -----> +---------------------------------+ 0x80016000 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |          |     Kern Text/Data/BBS     | | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |  KERN  ------>+---------------------------------+ 0x80000000 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 再往上便是我们可以支配的空间了(KERNTOP~Top Memory): | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | kernel/user | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |  4G ------------> +-------------------------------------------------+ | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |          |                 | | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |          |     Empty Memory (*)     | | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |          |                 | | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |  Top Memmory ---> +----------------------------------------------+ 随物理内存大小移动 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |                   |                 | | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |          |    User Remapped Memory    | | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |          |                 | | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |          |                 |   | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | first_free_paddr->+------------------------------------------------------+ 0x80816000 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 最后,我们来看物理内存分配的单位:操作系统中,物理页是物理内存分配的基本单位。一个物理页的大小是4KB,我们使用结构体Page来表示,其结构如图: | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | struct Page { | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   sint_t ref;           | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   uint_t flags;         | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   uint_t property;      | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   list_entry_t page_link;    | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | }; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | l ref表示这样页被页表的引用记数 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | l flags表示此物理页的状态标记 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | l property用来记录某连续内存空闲块的大小(即地址连续的空闲页的个数) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | l page_link是维持空闲物理页链表的重要结构。 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | Page结构体对应着物理页,我们来看Page结构体同物理地址之间是如何转换的。首先,我们需要先了解一下物理地址。 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |                                 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     图4.1 RISCV64 物理地址 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 总的来说,物理地址分为两部分,页号(PPN)+offset | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 页号可以理解为物理页的编码,而offset则为页内偏移量。现在考虑一下12位的offset对应的内存大小是多少呢? | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 2<<12=4096也就是4KB,还记得我们讲过的物PA理页大小是多少吗?没错是4KB。12位的offset设计便是由此而来。 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 有了物理地址(PA)这一概念,那PA和Pages结构体又是如何转换? | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 实际上在初始化空闲页链表之前,系统会定义一个Page结构体的数组,而链表的节点也正是来自于这些数组,这个数组的每一项代表着一个物理页,而且它们的数组下标就代表着每一项具体代表的是哪一个物理页,就如下图所示: | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | **3.2.2** **中断的处理过程** | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |    考虑一下,当程序执行到中断之前,程序是有自己的运行状态的,例如寄存器里保持的上下文数据。当中断发生,硬件在自动设置完中断原因和中断地址后,就会调转到中断处理程序,而中断处理程序同样会使用寄存器,于是当程序从中断处理程序返回时需要保存需要被调用者保存的寄存器,我们称之为callee-saved寄存器。 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |    在PK的machine/minit.c中间中,便通过delegate_traps(),将部分中断及同步异常委托给S模式。(同学们可以查看具体是哪些中断及同步异常) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |  43     // send S-mode interrupts and most exceptions straight to S-mode | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |  44     static void delegate_traps() | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |  45     { | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |  46      if (!supports_extension('S')) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |  47       return; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |  48 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |  49      uintptr_t interrupts = MIP_SSIP | MIP_STIP | MIP_SEIP; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |  50      uintptr_t exceptions = | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |  51       (1U << CAUSE_MISALIGNED_FETCH) | | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |  52       (1U << CAUSE_FETCH_PAGE_FAULT) | | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |  53       (1U << CAUSE_BREAKPOINT) | | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |  54       (1U << CAUSE_LOAD_PAGE_FAULT) | | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |  55       (1U << CAUSE_STORE_PAGE_FAULT) | | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |  56       (1U << CAUSE_USER_ECALL); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |  57 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |  58      write_csr(mideleg, interrupts); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |  59      write_csr(medeleg, exceptions); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |  60      assert(read_csr(mideleg) == interrupts); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |  61      assert(read_csr(medeleg) == exceptions); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |  62     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     这里介绍一下RISCV的中断委托机制,在默认的情况下,所有的异常都会被交由机器模式处理。但正如我们知道的那样,大部分的系统调用都是在S模式下处理的,因此RISCV提供了这一委托机制,可以选择性的将中断交由S模式处理,从而完全绕过M模式。 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     接下,我们继续看S模式下的中断处理。在pk目录下的pk.c文件中的boot_loader函数中将&trap_entry写入了stvec寄存器中,stvec保存着发生异常时处理器需要跳转到的地址,也就是说当中断发生,我们将跳转至trap_entry,现在我们继续跟踪trap_entry。trap_entry在pk目录下的entry.S中,其代码如下: | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |  60     trap_entry: | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |  61      csrrw sp, sscratch, sp | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |  62      bnez sp, 1f | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |  63      csrr sp, sscratch | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |  64     1:addi sp,sp,-320 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |  65      save_tf | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |  66      move a0,sp | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |  67      jal handle_trap | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     在61行,交换了sp与sscratch的值,这里是为了根据sscratch的值判断该中断是来源于U模式还是S模式。 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     如果sp也就是传入的sscratch值不为零,则跳转至64行,若sscratch的值为零,则恢复原sp中的值。这是因为,当中断来源于S模式是,sscratch的值为0,sp中存储的就是内核的堆栈地址。而当中断来源于U模式时,sp中存储的是用户的堆栈地址,sscratch中存储的则是内核的堆栈地址,需要交换二者,是sp指向内核的堆栈地址。 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     接着在64,65行保存上下文,最后跳转至67行处理trap。handle_trap在pk目录下的handlers.c文件中,代码如下: | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 112     void handle_trap(trapframe_t* tf) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 113     { | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 114      if ((intptr_t)tf->cause < 0) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 115       return handle_interrupt(tf); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 116 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 117      typedef void (*trap_handler)(trapframe_t*); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 118 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 119      const static trap_handler trap_handlers[] = { | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 120       [CAUSE_MISALIGNED_FETCH] = handle_misaligned_fetch, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 121       [CAUSE_FETCH_ACCESS] = handle_instruction_access_fault, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 122       [CAUSE_LOAD_ACCESS] = handle_load_access_fault, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 123       [CAUSE_STORE_ACCESS] = handle_store_access_fault, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 124       [CAUSE_FETCH_PAGE_FAULT] = handle_fault_fetch, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 125       [CAUSE_ILLEGAL_INSTRUCTION] = handle_illegal_instruction, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 126       [CAUSE_USER_ECALL] = handle_syscall, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 127       [CAUSE_BREAKPOINT] = handle_breakpoint, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 128       [CAUSE_MISALIGNED_LOAD] = handle_misaligned_load, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 129       [CAUSE_MISALIGNED_STORE] = handle_misaligned_store, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 130       [CAUSE_LOAD_PAGE_FAULT] = handle_fault_load, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 131       [CAUSE_STORE_PAGE_FAULT] = handle_fault_store, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 132      }; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     handle_trap函数中实现了S模式下各类中断的处理。可以看到,代码的126行就对应着系统调用的处理,handle_syscall的实现如下: | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 100     static void handle_syscall(trapframe_t* tf) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 101     { | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 102      tf->gpr[10] = do_syscall(tf->gpr[10], tf->gpr[11], tf->gpr[12], tf->gpr[13], | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 103                  tf->gpr[14], tf->gpr[15], tf->gpr[17]); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 104      tf->epc += 4; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 105     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     还记得我们在例3.1中是将中断号写入x17寄存器嘛?其对应的就是这里do_syscall的最后一个参数,我们跟踪进入do_syscall函数,其代码如下: | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 313 long do_syscall(long a0, long a1, long a2, long a3, long a4, long a5, unsigned long n) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 314     { | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 315      const static void* syscall_table[] = { | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 316      // your code here: | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 317      // add get_init_memsize syscall | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 318       [SYS_init_memsize ] = sys_get_init_memsize, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 319       [SYS_exit] = sys_exit, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 320       [SYS_exit_group] = sys_exit, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 321       [SYS_read] = sys_read, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 322       [SYS_pread] = sys_pread, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 323       [SYS_write] = sys_write, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 324       [SYS_openat] = sys_openat, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 325       [SYS_close] = sys_close, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 326       [SYS_fstat] = sys_fstat, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 327       [SYS_lseek] = sys_lseek, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 328       [SYS_renameat] = sys_renameat, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 329       [SYS_mkdirat] = sys_mkdirat, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 330       [SYS_getcwd] = sys_getcwd, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 331       [SYS_brk] = sys_brk, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 332       [SYS_uname] = sys_uname, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 333       [SYS_prlimit64] = sys_stub_nosys, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 334       [SYS_rt_sigaction] = sys_rt_sigaction, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 335       [SYS_times] = sys_times, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 336       [SYS_writev] = sys_writev, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 337       [SYS_readlinkat] = sys_stub_nosys, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 338       [SYS_rt_sigprocmask] = sys_stub_success, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 339       [SYS_ioctl] = sys_stub_nosys, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 340       [SYS_getrusage] = sys_stub_nosys, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 341       [SYS_getrlimit] = sys_stub_nosys, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 342       [SYS_setrlimit] = sys_stub_nosys, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 343       [SYS_set_tid_address] = sys_stub_nosys, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 344       [SYS_set_robust_list] = sys_stub_nosys, | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 345      }; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 346 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 347      syscall_t f = 0; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 348 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 349      if (n < ARRAY_SIZE(syscall_table)) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 350       f = syscall_table[n]; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 351      if (!f) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 352       panic("bad syscall #%ld!",n); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 353 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 354      return f(a0, a1, a2, a3, a4, a5, n); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 355     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     do_syscall中通过传入的系统调用号n,查询syscall_table得到对应的函数,并最终执行系统调用。 |