📄 memory.c
字号:
if ((*page_table)&1) page_table = (unsigned long *) (0xfffff000 & *page_table); else {
// 否则,申请空闲页面给页表使用,并在对应目录项中置相应标志7(User, U/S, R/W)。然后将
// 该页表的地址 -> page_table。 if (!(tmp=get_free_page())) return 0; *page_table = tmp|7; page_table = (unsigned long *) tmp; }
// 在页表中设置指定地址的物理内存页面的页表项内容。每个页表共可有1024 项(0x3ff)。 page_table[(address>>12) & 0x3ff] = page | 7;/* 不需要刷新页变换高速缓冲 */ return page;// 返回页面地址。}
//// 取消写保护页面函数。用于页异常中断过程中写保护异常的处理(写时复制)。
// 输入参数为页表项指针。
// [ un_wp_page 意思是取消页面的写保护:Un-Write Protected。]void un_wp_page(unsigned long * table_entry){ unsigned long old_page,new_page; old_page = 0xfffff000 & *table_entry;// 取原页面对应的目录项号。
// 如果原页面地址大于内存低端LOW_MEM(1Mb),并且其在页面映射字节图数组中值为1(表示仅
// 被引用1 次,页面没有被共享),则在该页面的页表项中置R/W 标志(可写),并刷新页变换
// 高速缓冲,然后返回。 if (old_page >= LOW_MEM && mem_map[MAP_NR(old_page)]==1) { *table_entry |= 2; invalidate(); return; }
// 否则,在主内存区内申请一页空闲页面。 if (!(new_page=get_free_page())) oom();// Out of Memory。内存不够处理。
// 如果原页面大于内存低端(则意味着mem_map[]>1,页面是共享的),则将原页面的页面映射
// 数组值递减1。然后将指定页表项内容更新为新页面的地址,并置可读写等标志(U/S, R/W, P)。
// 刷新页变换高速缓冲。最后将原页面内容复制到新页面。 if (old_page >= LOW_MEM) mem_map[MAP_NR(old_page)]--; *table_entry = new_page | 7; invalidate(); copy_page(old_page,new_page);} /* * 当用户试图往一个共享页面上写时,该函数处理已存在的内存页面,(写时复制)
* 它是通过将页面复制到一个新地址上并递减原页面的共享页面计数值实现的。 * * 如果它在代码空间,我们就以段错误信息退出。 */
//// 页异常中断处理调用的C 函数。写共享页面处理函数。在page.s 程序中被调用。
// 参数error_code 是由CPU 自动产生,address 是页面线性地址。
// 写共享页面时,需复制页面(写时复制)。void do_wp_page(unsigned long error_code,unsigned long address){#if 0/* 我们现在还不能这样做:因为estdio 库会在代码空间执行写操作 *//* 真是太愚蠢了。我真想从GNU 得到libc.a 库。 */ if (CODE_SPACE(address)) // 如果地址位于代码空间,则终止执行程序。 do_exit(SIGSEGV);#endif
// 处理取消页面保护。参数指定页面在页表中的页表项指针,其计算方法是:
// ((address>>10) & 0xffc):计算指定地址的页面在页表中的偏移地址;
// (0xfffff000 &((address>>20) &0xffc)):取目录项中页表的地址值,
// 其中((address>>20) &0xffc)计算页面所在页表的目录项指针;
// 两者相加即得指定地址对应页面的页表项指针。这里对共享的页面进行复制。 un_wp_page( (unsigned long *)(((address>>10) & 0xffc) + (0xfffff000 & *((unsigned long *) ((address>>20) &0xffc))))
);}
//// 写页面验证。
// 若页面不可写,则复制页面。在fork.c 第34 行被调用。void write_verify(unsigned long address){ unsigned long page;
// 判断指定地址所对应页目录项的页表是否存在(P),若不存在(P=0)则返回。 if (!( (page = *((unsigned long *) ((address>>20) & 0xffc)) )&1)) return;
// 取页表的地址,加上指定地址的页面在页表中的页表项偏移值,得对应物理页面的页表项指针。 page &= 0xfffff000; page += ((address>>10) & 0xffc);
// 如果该页面不可写(标志R/W 没有置位),则执行共享检验和复制页面操作(写时复制)。 if ((3 & *(unsigned long *) page) == 1) /* non-writeable, present */ un_wp_page((unsigned long *) page); return;}
//// 取得一页空闲内存并映射到指定线性地址处。
// 与get_free_page()不同。get_free_page()仅是申请取得了主内存区的一页物理内存。而该函数
// 不仅是获取到一页物理内存页面,还进一步调用put_page(),将物理页面映射到指定的线性地址
// 处。void get_empty_page(unsigned long address){ unsigned long tmp;
// 若不能取得一空闲页面,或者不能将页面放置到指定地址处,则显示内存不够的信息。
// 279 行上英文注释的含义是:即使执行get_free_page()返回0 也无所谓,因为put_page()
// 中还会对此情况再次申请空闲物理页面的,见210 行。 if (!(tmp=get_free_page()) || !put_page(tmp,address)) { free_page(tmp); /* 0 is ok - ignored */ oom(); }}/* * try_to_share()在任务"p"中检查位于地址"address"处的页面,看页面是否存在,是否干净。
* 如果是干净的话,就与当前任务共享。
*
* 注意!这里我们已假定p !=当前任务,并且它们共享同一个执行程序。 */
//// 尝试对进程指定地址处的页面进行共享操作。
// 同时还验证指定的地址处是否已经申请了页面,若是则出错,死机。
// 返回1-成功,0-失败。static int try_to_share(unsigned long address, struct task_struct * p){ unsigned long from; unsigned long to; unsigned long from_page; unsigned long to_page; unsigned long phys_addr;
// 求指定内存地址的页目录项。 from_page = to_page = ((address>>20) & 0xffc);
// 计算进程p 的代码起始地址所对应的页目录项。 from_page += ((p->start_code>>20) & 0xffc);
// 计算当前进程中代码起始地址所对应的页目录项。 to_page += ((current->start_code>>20) & 0xffc);/* 在from 处是否存在页目录? */
// *** 对p 进程页面进行操作。
// 取页目录项内容。如果该目录项无效(P=0),则返回。否则取该目录项对应页表地址-> from。 from = *(unsigned long *) from_page; if (!(from & 1)) return 0; from &= 0xfffff000;
// 计算地址对应的页表项指针值,并取出该页表项内容 -> phys_addr。 from_page = from + ((address>>10) & 0xffc); phys_addr = *(unsigned long *) from_page;/* 页面干净并且存在吗? */
// 0x41 对应页表项中的Dirty 和Present 标志。如果页面不干净或无效则返回。 if ((phys_addr & 0x41) != 0x01) return 0;
// 取页面的地址 -> phys_addr。如果该页面地址不存在或小于内存低端(1M)也返回退出。 phys_addr &= 0xfffff000; if (phys_addr >= HIGH_MEMORY || phys_addr < LOW_MEM) return 0;
// *** 对当前进程页面进行操作。
// 取页目录项内容 -> to。如果该目录项无效(P=0),则取空闲页面,并更新to_page 所指的目录项。 to = *(unsigned long *) to_page; if (!(to & 1)) if (to = get_free_page()) *(unsigned long *) to_page = to | 7; else oom();
// 取对应页表地址 -> to,页表项地址 to_page。如果对应的页面已经存在,则出错,死机。 to &= 0xfffff000; to_page = to + ((address>>10) & 0xffc); if (1 & *(unsigned long *) to_page) panic("try_to_share: to_page already exists");/* 对它们进行共享处理:写保护 */
// 对p 进程中页面置写保护标志(置R/W=0 只读)。并且当前进程中的对应页表项指向它。 *(unsigned long *) from_page &= ~2; *(unsigned long *) to_page = *(unsigned long *) from_page;
// 刷新页变换高速缓冲。 invalidate();
// 计算所操作页面的页面号,并将对应页面映射数组项中的引用递增1。 phys_addr -= LOW_MEM; phys_addr >>= 12; mem_map[phys_addr]++; return 1;}/* * share_page()试图找到一个进程,它可以与当前进程共享页面。参数address 是
* 当前数据空间中期望共享的某页面地址。 * * 首先我们通过检测executable->i_count 来查证是否可行。如果有其它任务已共享 * 该inode,则它应该大于1。 */
//// 共享页面。在缺页处理时看看能否共享页面
// 返回1 - 成功,0 - 失败。。static int share_page(unsigned long address){ struct task_struct ** p;
// 如果是不可执行的,则返回。excutable 是执行进程的内存i 节点结构。 if (!current->executable) return 0;
// 如果只能单独执行(executable->i_count=1),也退出。 if (current->executable->i_count < 2) return 0;
// 搜索任务数组中所有任务。寻找与当前进程可共享页面的进程,
// 并尝试对指定地址的页面进行共享。 for (p = &LAST_TASK ; p > &FIRST_TASK ; --p) { if (!*p)// 如果该任务项空闲,则继续寻找。 continue; if (current == *p)// 如果就是当前任务,也继续寻找。 continue; if ((*p)->executable != current->executable)// 如果executable 不等,也继续。 continue; if (try_to_share(address,*p))// 尝试共享页面。 return 1; } return 0;}
//// 页异常中断处理调用的函数。处理缺页异常情况。在page.s 程序中被调用。
// 参数error_code 是由CPU 自动产生,address 是页面线性地址。void do_no_page(unsigned long error_code,unsigned long address){ int nr[4]; unsigned long tmp; unsigned long page; int block,i; address &= 0xfffff000;// 页面地址。
// 首先算出指定线性地址在进程空间中相对于进程基址的偏移长度值。 tmp = address - current->start_code;
// 若当前进程的executable 空,或者指定地址超出代码+数据长度,则申请一页物理内存,并映射
// 影射到指定的线性地址处。executable 是进程的i 节点结构。该值为0,表明进程刚开始设置,
// 需要内存;而指定的线性地址超出代码加数据长度,表明进程在申请新的内存空间,也需要给予。
// 因此就直接调用get_empty_page()函数,申请一页物理内存并映射到指定线性地址处即可。
// start_code 是进程代码段地址,end_data 是代码加数据长度。对于linux 内核,它的代码段和
// 数据段是起始基址是相同的。 if (!current->executable || tmp >= current->end_data) { get_empty_page(address); return; }
// 如果尝试共享页面成功,则退出。 if (share_page(tmp)) return;
// 取空闲页面,如果内存不够了,则显示内存不够,终止进程。 if (!(page = get_free_page())) oom();/* 记住,(程序)头要使用1 个数据块 */
// 首先计算缺页所在的数据块项。BLOCK_SIZE = 1024 字节,因此一页内存需要4 个数据块。 block = 1 + tmp/BLOCK_SIZE;
// 根据i 节点信息,取数据块在设备上的对应的逻辑块号。 for (i=0 ; i<4 ; block++,i++) nr[i] = bmap(current->executable,block);
// 读设备上一个页面的数据(4 个逻辑块)到指定物理地址page 处。 bread_page(page,current->executable->i_dev,nr);
// 在增加了一页内存后,该页内存的部分可能会超过进程的end_data 位置。下面的循环即是对物理
// 页面超出的部分进行清零处理。 i = tmp + 4096 - current->end_data; tmp = page + 4096; while (i-- > 0) { tmp--; *(char *)tmp = 0; }
// 如果把物理页面映射到指定线性地址的操作成功,就返回。否则就释放内存页,显示内存不够。 if (put_page(page,address)) return; free_page(page); oom();}
//// 物理内存初始化。
// 参数:start_mem - 可用作分页处理的物理内存起始位置(已去除RAMDISK 所占内存空间等)。
// end_mem - 实际物理内存最大地址。
// 在该版的linux 内核中,最多能使用16Mb 的内存,大于16Mb 的内存将不于考虑,弃置不用。
// 0 - 1Mb 内存空间用于内核系统(其实是0-640Kb)。void mem_init(long start_mem, long end_mem){ int i; HIGH_MEMORY = end_mem;// 设置内存最高端。 for (i=0 ; i<PAGING_PAGES ; i++)// 首先置所有页面为已占用(USED=100)状态, mem_map[i] = USED;// 即将页面映射数组全置成USED。 i = MAP_NR(start_mem);// 然后计算可使用起始内存的页面号。 end_mem -= start_mem;// 再计算可分页处理的内存块大小。 end_mem >>= 12;// 从而计算出可用于分页处理的页面数。 while (end_mem-->0)// 最后将这些可用页面对应的页面映射数组清零。 mem_map[i++]=0;}
// 计算内存空闲页面数并显示。void calc_mem(void){ int i,j,k,free=0; long * pg_tbl;
// 扫描内存页面映射数组mem_map[],获取空闲页面数并显示。 for(i=0 ; i<PAGING_PAGES ; i++) if (!mem_map[i]) free++; printk("%d pages free (of %d)\n\r",free,PAGING_PAGES);
// 扫描所有页目录项(除0,1 项),如果页目录项有效,则统计对应页表中有效页面数,并显示。 for(i=2 ; i<1024 ; i++) { if (1&pg_dir[i]) { pg_tbl=(long *) (0xfffff000 & pg_dir[i]); for(j=k=0 ; j<1024 ; j++) if (pg_tbl[j]&1) k++; printk("Pg-dir[%d] uses %d pages\n",i,k); } }}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -