📄 mm.c
字号:
/* * ApOS (Another Project software for s3c2410) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * Copyright caiyuqing * */#include "../include/mm/mm.h"#include "../include/kernel/task.h"extern struct task_struct* current;//#define _MEM_DEBUG/* * 很重要的一个数组,ram_map是对主内存区域(用户空间)0x30a00000~0x33ffffff * 的一个映射,该内存区域被分成13824(RAM_PAGES)个页,每个页对应ram_map中 * 的一个元素,当该页是干净的(未被映射的),则对应的ram_map数组 * 元素的值为0 */#define MAP_INDEX(mv_addr) (((mv_addr)-USER_RAM_BASE)/PAGE_SIZE)unsigned char ram_map[RAM_PAGES];int memcpy(unsigned char* dest,unsigned char* src,int n){ for(;n>0;n--) { *dest++=*src++; }}/* * 将ram_map数组清0 */void clear_ram_map(){ unsigned char *ram_map_base=ram_map; unsigned int pages=RAM_PAGES; asm volatile( "ldr r0,%0 \n\t" "ldr r1,%1 \n\t" "add r1,r0,r1 \n\t" "ldrb r2,=0x00 \n\t" "1: strb r2,[r0],#1 \n\t" "cmp r0,r1 \n\t" "bne 1b \n\t" : :"m"(ram_map_base),"m"(pages) :"r0","r1","r2","memory" );}void ram_init(){ init_kmalloc(); clear_ram_map();}void oom(){ panic("error:out of memory.\n");}/* * 获得"干净"的物理页地址,并将该页对应的ram_map元素增1 */unsigned int get_free_physical_page(){ unsigned int ram_page_index; unsigned int page_address; for(ram_page_index=0; ram_page_index<RAM_PAGES&&ram_map[ram_page_index]; ram_page_index++); if(ram_page_index==RAM_PAGES) { return 0; } ram_map[ram_page_index]++; page_address=USER_RAM_BASE+ram_page_index*PAGE_SIZE; /* * 将新获得的页面清零。 */ asm volatile( "ldr r0,%0 \n\t" "ldr r1,=4096 \n\t" "add r1,r0,r1 \n\t" "ldr r2,=0x00000000 \n\t" "1: str r2,[r0],#4 \n\t" "cmp r0,r1 \n\t" "bne 1b \n\t" : :"m"(page_address) :"r0","r1","r2" ); return page_address;}/* * 释放物理地址 p_page 指定的一页内存 */int free_physical_page(unsigned int p_page){ unsigned int ram_page_index; //页面号 /* * 若该页地址属于内核地址范围内则不进行处理,只打印警告信息并返回-1, */ if(p_page>=SYS_RAM_BASE && p_page<SYS_RAM_LIMIT) {#ifdef _MEM_DEBUG printk("warning: free_physical_page trying to free the kernel's page\n");#endif return -1; } /* * 若该页地址大于系统所含物理内存的最高端(0x34000000)则显示出错信息 * 并死机 */ if(p_page>=(SDRAM_LIMIT)) { panic("error: free_physical_page trying to free nonexistent page\n"); } /* * 否则将物理页地址转换成页面号(ram_map数组中的索引号),根据该页面号对 * ram_map数组中的元素进行递减操作,页面号 = (page_addr-USER_RAM_BASE)/PAGE_SIZE * 在递减操作之间先判断该元素是否为0,若该元素不为0,则递减并返回该页面号, * 若该元素为0,则说明我们要释放一个空闲页,则报错死机 */ else { ram_page_index=(p_page-USER_RAM_BASE)/PAGE_SIZE; if(ram_map[ram_page_index]) { ram_map[ram_page_index]--; return ram_page_index; } else { panic("error: free_physical_page tryint to free free page\n"); } }}int free_virtual_page(unsigned int v_page){ unsigned int *dir_entry; unsigned int *page_entry; //若v_page不是4KB的边界,说明该地址不是页地址,报错死机 if(v_page&0xfff) { panic("error: free_virtual_page called with wrong alignment\n"); } /* * 若v_page为EXCEPTION_BASE或为内核使用的地址范围则警告并返回-1 */ if( (v_page>=SYS_VADDR_BASE&&v_page<SYS_VADDR_LIMIT)|| v_page==EXCEPTION_BASE) { printk("warning: free_virtual_page trying to free kernel's page\n"); return -1; } dir_entry=(unsigned int *)DIRECTORY_BASE+(v_page>>20); if(DIR_VALID(*dir_entry)) { page_entry= *dir_entry&0xfffffc00; if(PAGE_VALID(*page_entry)) { free_physical_page(*page_entry&0xFFFFF000); *page_entry=0; } //刷新 TLB invalidate_TLBs(); return v_page; } return 0;}/* * free_page_tables 用于释放连续的内存区域。 * 注意 这个函数只能够处理1MB为单位的内存块,也就是说若size<1MB * 也按1MB处理。 * mv_addr必须处于1MB的边界 * */void free_page_tables(unsigned int mv_addr,unsigned int size){ unsigned int *dir_entry; unsigned int *page_entry; unsigned int nr; //检查mv_addr是否处于1MB的边界 if(mv_addr&0xfffff) { panic("error: free_page_table called with wrong alignment\n"); } /* * size用于表明总共有多少MB(有多少个页目录项)。或许你会感到奇怪,为什么 * size必须先加上0xfffff? * size+0xfffff的原因是因为会发生这种情况,若size=1.1MB,则 size>>20=1, * 也就是说余数部分被去掉了。但size=1.1MB的话,我们要按2MB进行处理,所以 * 必须加上0xfffff */ size=(size+0xfffff)>>20; dir_entry=(unsigned int *)DIRECTORY_BASE+(mv_addr>>20); #ifdef _MEM_DEBUG printk("free page_table\n"); printk("@virtual address:0x%0x\n",mv_addr); #endif for(;size-->0;dir_entry++) { if(DIR_INVALID(*dir_entry)) { panic("error: free_page_table trying to free unmap virtual address\n"); } page_entry=(*dir_entry&0xFFFFFC00); for(nr=0;nr<256;nr++) { if(*page_entry&0x00000003) { //若p_addr为0xfff00000或为SYS_RAM地址范围,报错死机 if(((*page_entry&0xFFFFF000)>=SYS_RAM_BASE&&(*page_entry&0xFFFFF000)<SYS_RAM_LIMIT )) { panic("error: free_page_table trying to free kernel memory space\n"); }#ifdef _MEM_DEBUG printk(" #physic address:0x%0x \n",*page_entry&0xFFFFF000);#endif free_physical_page(*page_entry&0xFFFFF000); *page_entry=0; } else { panic("error: free_page_table trying to free unmap virtual address\n"); } page_entry++; } *dir_entry=0; } //刷新 TLB invalidate_TLBs();}/* * 内存复制函数,实际上是复制页表 */void copy_page_tables(unsigned int mv_addr_src, unsigned int mv_addr_dest, unsigned int size){ unsigned int *dir_entry_src=(unsigned int *)DIRECTORY_BASE+(mv_addr_src>>20); unsigned int *dir_entry_dest=(unsigned int *)DIRECTORY_BASE+(mv_addr_dest>>20); unsigned int *page_entry_src; unsigned int *page_entry_desc; unsigned int tmp; unsigned int page_count; //源地址和目标地址必须从1MB边界开始 if((mv_addr_src&0xfffff)||(mv_addr_dest&0xfffff)) { panic("error: copy_page_tables called with wrong alignment\n"); } //size的单位由byte变为MB size=(size+0xfffff)>>20; //每次至少复制1MB(一个目录项) for(;size--;dir_entry_src++,dir_entry_dest++) { //源目录没有使用,不用复制 if(DIR_INVALID(*dir_entry_src)) continue; if(DIR_INVALID(*dir_entry_dest))//目标地址页目录对应的页表不存在 { //分配一页内存给目标地址存放页表 if((tmp=get_free_physical_page())==0) oom(); *dir_entry_dest=tmp|COARSE_PAGE_DESC|CLIENT; } page_entry_src=(unsigned int *)(*dir_entry_src&0xFFFFFC00); page_entry_desc=(unsigned int *)(*dir_entry_dest&0xFFFFFC00); for(page_count=0;page_count<256;page_count++) { /* * 该页面被父进程和子进程共项,我们要从新设置该内存页面 * 的访问权限。User模式只读,System模式允许读写,这样当 * 两个进程中的某一个要对其进行写操作时将引起permission fault */ *page_entry_src=(*page_entry_src&~(0xfff))|USER_SMALL_PAGE_DESC_R; *page_entry_desc=*page_entry_src; //若该页地址属于用户空间,则将内存引用数组的对应项增一 if((*page_entry_src&0xfffff000)>=USER_RAM_BASE) ram_map[MAP_INDEX(*page_entry_src&0xfffff000)]++; page_entry_desc++; page_entry_src++; } } //刷新 TLB invalidate_TLBs();}void see_addr_map(unsigned int mv_addr){ unsigned int *dir_entry=(unsigned int *)DIRECTORY_BASE+(mv_addr>>20); unsigned int *page_entry=(unsigned int *)(*dir_entry&0xFFFFFC00)+((mv_addr>>12)&0xff); unsigned int phy_page_index=(((*page_entry&~(0xfff))-USER_RAM_BASE)/PAGE_SIZE); printf("TLB index :0x%0x [0x%0x]\n",(mv_addr>>20),*dir_entry); printf("page_entry :0x%0x [0x%0x]\n",page_entry,*page_entry); printf("physic page index: 0x%0x\n",phy_page_index); printf("reference count: 0x%0x\n",ram_map[phy_page_index]); printf("page type: 0x%0x.\n",*page_entry&0x3); printf("C bit: 0x%0x.\n",(*page_entry>>3)&1); printf("B bit: 0x%0x.\n",(*page_entry>>2)&1); printf("AP0:0x%0x AP1:0x%0x AP2:0x%0x AP3:0x%0x.\n", (*page_entry>>4)&3,(*page_entry>>6)&3,(*page_entry>>8)&3,(*page_entry>>10)&3);}/* * 缺页处理函数 */void do_no_page(unsigned int mv_addr){ unsigned int *dir_entry=(unsigned int *)DIRECTORY_BASE+(mv_addr>>20); unsigned int *page_entry; unsigned int tmp; //该页的页目录不存在 if(DIR_INVALID(*dir_entry)) { //请求一页内存作为页目录 if((tmp=get_free_physical_page())==0) oom(); /* * CLIENT 将使分配的内存在被访问的时候进行权限检测 */ *dir_entry=tmp|COARSE_PAGE_DESC|CLIENT; page_entry=(unsigned int *)(*dir_entry&0xFFFFFC00)+((mv_addr>>12)&0xff); //请求一页内存作为页表 if((tmp=get_free_physical_page())==0) oom(); /* * USER_SMALL_PAGE_DESC_RW 将使分配的内存在内核 * 级和用户级都具有读写的访问许可 */ *page_entry=tmp|USER_SMALL_PAGE_DESC_RW; } else//该页的页目录存在 { //请求一页内存作为页表 page_entry=(unsigned int *)(*dir_entry&0xFFFFFC00)+((mv_addr>>12)&0xff); if(PAGE_INVALID(*page_entry)) { if((tmp=get_free_physical_page())==0) oom(); *page_entry=tmp|USER_SMALL_PAGE_DESC_RW; } }}void un_wp_page(unsigned int mv_addr){ unsigned int *dir_entry=(unsigned int *)DIRECTORY_BASE+(mv_addr>>20); unsigned int *page_entry; unsigned int tmp; unsigned int old_page,new_page; //获得该页页表项 page_entry=(unsigned int *)(*dir_entry&0xFFFFFC00)+((mv_addr>>12)&0xff); /* * 若该地址对应的物理页面仅被一个进程使用,则将该页面的访问权限标志为R/W * * 我们知道当父进程创建子进程之后他们的内存地址是共享的,此时该内存空间的 * 访问权限由原来的R/W被修改为R,内存空间对应的内存页引用次数将增一.当其中 * 的某个进程要对内存进行写操作时则发生permission fault,这样系统会为该进 * 程分配一页内存,并将原来的内存页引用次数减1,当原来共享的内存页对应的物 * 理页引用次数递减到1时,说明此时只有一个进程在使用该页面,于是我们只需把 * 该页的访问权限修改回R/W即可 */ if(ram_map[MAP_INDEX(*page_entry&(0xfffff000))]==1) { *page_entry=(*page_entry&(0xfffff000))|USER_SMALL_PAGE_DESC_RW; } else { /* * 该页被多于一个进程共项,所以必须为当前进程分配新的一个内存页供 * 其进行写操作,同时将原来的页面引用次数减一 * */ //保存原页面地址用于新旧页面的数据拷贝 old_page=(*page_entry&(0xfffff000)); //将原共享的物理内存页引用数减1 ram_map[MAP_INDEX(old_page)]--; //申请一页内存给当前进程 if((tmp=get_free_physical_page())==0) oom(); *page_entry=tmp|USER_SMALL_PAGE_DESC_RW; //获得新页面地址 new_page=(*page_entry&(0xfffff000)); //页面间的数据拷贝 memcpy((char *)new_page,(char *)old_page,4096); } //刷新 TLB invalidate_TLBs();}void do_wp_page(unsigned int mv_addr){ un_wp_page(mv_addr);}/* * 本系统会产生permission_fault的原因只有一个,那 * 就是在用户模式下对一个只有读权限的内存页执行了, * 写操作,其他的permission_fault都将引起系统死机 */void do_permission_fault(unsigned int mv_addr){ /* printk("permission fault.\n"); printk(" pid: %d\n",current->pid); printk(" fault address :0x%0x\n",mv_addr); */ //取消写保护 do_wp_page(mv_addr); }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -