linux/mm/memory.c/free_page_tables()

看linux0.11的源码有一段时间了,发现前期的轮廓建立起来后,重点马上到了具体操作上。即函数,毕竟OS本身是由一系列函数组成的,“源码面前了无秘密”,所以要深刻理解操作系统的神奇,深入理解每一个函数的每一行代码很是关键。
接下来一段时间,会随着学习的步骤,参看赵炯博士的内核注释和网上其他达人的点评注解,以每个函数为题目进行一个个人的注解。姑且厚颜算作原创吧,不为其他,只为记录下学习印记,加深印象而已。

今天是第一个函数,linux/mm/memory.c/free_page_tables()

/*
 * This function frees a continuos block of page tables, as needed
 * by 'exit()'. As does copy_page_tables(), this handles only 4Mb blocks.
 */
int free_page_tables(unsigned long from,unsigned long size)
{
unsigned long *pg_table;
unsigned long * dir, nr;


// 这里的from是线性地址,需4MB对齐。因为free_page_tables函数用来释放一个页表的内存
// 而不是一个页面的内存。
if (from & 0x3fffff)
panic("free_page_tables called with wrong alignment");
if (!from)
panic("Trying to free up swapper memory space");
// size在计算前是字节为单位的需要释放的内存长度。而在右移22位并向上取整计算后则得到了
// 需要释放的内存页面的个数,即以4MB为单位的需要释放的内存页面个数。这里+0x3fffff是为了
// 向上取整,譬如4.01MB经过计算后也会得到2(4MB则刚好得到1)
size = (size + 0x3fffff) >> 22;
// dir意义为from地址对应的这个页表在页目录表中的表项首地址。(页目录表位于物理内存0x0000处,
// 占有1页内存,共有1K个页表项,每个页表项4字节。每个页表项的4字节作为一个指针又都指向
// 一个内存地址,这个地址就是一个页表的基地址。因此共有1K个页表,每个页表对应4M内存,因此
// linux0.11共支持4G的线性地址范围。这4G线性地址范围被64个进程瓜分,因此每个进程的独立
// 地址范围为4G/64 = 64M,这也就是每个进程最大线性地址的限制来历了。)
// 这里from为线性地址,因此可根据线性地址空间与页目录表中表项的对应关系来计算这个from
// 对应在哪个页表项中。from>>22即为对应的页表项号,但是每个页表项占4字节,因此要得到页表项
// 在页目录表内的首地址(注意区分页表项号与页表项号首地址),则要(from>>22)<<2 = from>>20
// & 0xffc是为了确保这个地址一定是页表项的首地址,而不会跑到4字节的中间某个字节去。实际上
// 在函数刚开始已经检验过from了,能执行到这里即说明from已经是4MB对齐了。但是linus还是执着
// 的进行了这个检测,可见linus编程风格之严谨。我能想到的这句代码的作用就是:在函数首部检验
// 代码和这句代码之间,若维护时不小心改变了from的值使其失去了4MB对齐的特性,这里又没检测
// 则会造成很大的崩溃。
dir = (unsigned long *) ((from>>20) & 0xffc); /* _pg_dir = 0 */
for ( ; size-->0 ; dir++) {
// dir为ulong型指针,占4个字节(即from对应的页目录表项首地址),指向一个ulong型数据,即
// 这个页目录表基地址。*dir即为这个页目录表项内容,也就是这个页目录表的基地址。此处检验
// 其最低位是不是1,查页目录表项结构即可知,最低位为P(Present,1表示这个表项可做映射,0
// 表示这个表项不可做映射),因此这个if是检验from要求释放的页表是不是有效页表。
if (!(1 & *dir))
continue;
// pg_table即为第一个要释放的页表的基地址指针,这里与0xfffff位与也是为了对齐验证
pg_table = (unsigned long *) (0xfffff000 & *dir);
// 找到了要释放的页表基地址,下面开始循环释放这个页表中1024个页面。调用free_page函数
// 实现释放一个物理页面。这里要注意free_page函数接收的addr为物理地址,而不是线性地址。
// 上面一直都在用线性地址,到了这里突然就成了物理地址,这里要注意的就是这个转折点了。
// 经过分析得知,从线性地址到物理地址的转折出现在从dir到*dir。也就是用线性地址得到dir,
// 然后*dir取出的却是物理地址,即页目录表中存的页表地址都是物理地址。整个地址转换系统中,
// 用户进程内的虚拟地址(逻辑地址)先经过段式转换到线性地址,然后页目录级使用线性地址,到了
// 页表级和页内都使用物理地址了。
// 这里也可以看出,一个进程初步建立时,至少要耗费2个page的主内存。第一个用来存task_struct
// 结构和内核栈;第二个即用来存该进程第一个页表。
// 这里我突然有个疑问:在这之后execve等耗费的内存可以由这个已经建立的页表来管理,那么这前两个
// 页面的内存由谁来管理呢?换句话说,这两个页面的物理内存由谁来释放呢?这里第一个页表所在page
// 的释放在下面就能找到答案,原来释free_page_tables函数中在释放完了一个页表中1024个页面后会
// 顺带释放这个页表本身占有的这个内存页面。而task_struct和内核栈共同占有的内存页的释放,
// 猜测只能由其父进程来代为释放了···具体如何,待继续寻找。
for (nr=0 ; nr<1024 ; nr++) {
if (1 & *pg_table)
// 调用free_page释放一个物理页面,释放的意思是在内核mem_map[]中对应该页
// 的项内数值减一。若减一后为0则代表该页内存成了空闲内存,若不为0则表示
// 该页内存还被别的进程引用,只是本进程对它的引用被释放断开了。
free_page(0xfffff000 & *pg_table);
// 页表中表项清零。因为这个表项指向的内存页面已经被释放了,与本进程无关了,因此
// 这里页表也再不用记录它了,直接清零即可。
*pg_table = 0;
pg_table++;
}
// 释放完第一个页表指向的1024个页面后,最后释放掉这个页表本身所在的那个页面。
free_page(0xfffff000 & *dir);
// 释放了页表所在页面,将该页表在页目录表中对应的页表项内容也清零
*dir = 0;
}
// 刷新页变换高速缓存,进入invalidate函数可知,这里是通过重新给CR3寄存器赋值来刷新的。
// 这里的页表换高速缓存是存在于CPU内部的,应该跟MMU有些关联。这一块暂时没搞明白···
// 现在能确定的是,free_page_tables是给exit函数调用的,因此是关闭进程时使用的。而关闭进程
// 必然导致CPU会访问的页面的变化,因此在这里进行了刷新
invalidate();
return 0;
}

你可能感兴趣的:(linux/mm/memory.c/free_page_tables())