linux内存管理-linux/mm/memory.c/free_page_tables()【转】

5 篇文章 0 订阅
4 篇文章 0 订阅

http://blog.csdn.net/astonqa/article/details/7598548

http://oss.org.cn/kernel-book/ch02/2.4.1.htm

看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页(4k)内存,共有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 ,0.页目录表位于物理内存0x0000处   1.线性 地址应该右移 22 位才能得出高10位的页目录中 目录项索引.    2. 因为一个目录项占用4个字节,所以在取目录项内容(也即页表基地址)时要把目录项索引*4. 也就是
(address>>22)*4.*/

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 0.11中的页目录表及页表内容分析

Linux0.11针对的内存是16M。采用了两级分页机制进行内存的管理。

根据head.s中第114行的.org 0x1000可知,物理地址0x1000之前的所有数据都将被页目录表覆盖(这个覆盖,是指更改了内存中的内核镜像文件,而不是磁盘上的内核镜像文件)。

1、首先,Linux从0x00000地址开始对五页内存进行清零。(1页页目录表+4页页表)

   setup_paging: 
       movl $1024*5,%ecx        /* 5 pages - pg_dir+4 page tables */ 
       xorl %eax,%eax 
       xorl %edi,%edi            /* pg_dir is at 0x000 */ 
       cld;rep;stosl

2、接着,填写页目录表(页目录表的位置为0x00000-0x00fff,大小为4K,每一项占4字节)。因为只有4个页表,所以只填写了前四项。

1       movl $pg0+7,pg_dir        /* set present bit/user r/w */ 
2       movl $pg1+7,pg_dir+4        /*  --------- " " --------- */ 
3       movl $pg2+7,pg_dir+8        /*  --------- " " --------- */ 
4       movl $pg3+7,pg_dir+12        /*  --------- " " --------- */ 

这里,4个页目录项的内容分别是$pg0(1,2,3)+7,分别是4个页表的物理地址+111B。前面的$pg0(1,2,3)是页表的物理地址,而111B则代表这4个页表权限为可读写。

3、填写页表项的内容

1       movl $pg3+4092,%edi 
2       movl $0xfff007,%eax        /*  16Mb - 4096 + 7 (r/w user,p) */ 
3       std 
4   1:    stosl            /* fill pages backwards - more efficient :-) */ 
5       subl $0x1000,%eax 
6       jge 1b 

这里的填写是逆序填写的,也就是首先将16M物理内存最后一页的启始地址+权限(16M-4K+111B)填写到第4张页表的最后一项。地址为$pg3+4092,其中$pg3为(第4张页表的起始地址),4092是因为1024*4(1024项,每项占用4字节)-4(最后一项页占用4字节)。所以第4张页表的最后一项是(16M-4K)0xfff000+111B=0xfff007。

最终,std以4递减edi寄存器(一个页表项占4字节,edi指向正在操作的页表项),subl $0x1000,%eax将减去0x1000(一页内存的大小,eax指向正在操作的内存边界),l:stosl是填写页表项,知道eax的内容为0,这样就填写完了4个页表。

这样,内存中的页目录表和页表分布就是:

物理内存地址 所含信息及备注 单元内容
0X00FF FFFC
……
  ……
……
0X0000 4FFC
0X0000 4FF8


0X0000 4004
0X0000 4000
页表4
4K (0X00004000-0X00004FFC)
4字节为一项
0X00FF F000+7
0X00FF E000+7


0X00C0 2000+7
0X00C0 1000+7
0X00C0 0000+7
0X0000 3FFC
0X0000 3FF8


0X0000 3004
0X0000 3000
页表3
4K (0X00003000-0X00003FFC)
4字节为一项
0X00BF F000+7
0X00BF E000+7


0X0080 2000+7
0X0080 1000+7
0X0080 0000+7
0X0000 2FFC
0X0000 2FF8


0X0000 2004
0X0000 2000
页表2
4K (0X00002000-0X00002FFC)
4字节为一项
0X007F F000+7
0X007F E000+7


0X0040 2000+7
0X0040 1000+7
0X0040 0000+7
0X0000 1FFC
0X0000 1FF8


0X0000 1004
0X0000 1000
页表1
4K (0X00001000-0X00001FFC)
4字节为一项
0X003F F000+7
0X003F E000+7


0X0000 2000+7
0X0000 1000+7
0X0000 0000+7
0X0000 0FFC
0X0000 0FF8


0X0000 0004
0X0000 0000
PDT(页目录表)
4K (0X000000-0X00000FFF)
只有前四项有内容

0x0000 4000+7
0x0000 3000+7
0x0000 2000+7
0x0000 1000+7

 

最后,把页目录表的地址(0x000000)写到控制寄存器CR3,然后置控制寄存器CR0的PG位,这样就开启了内存的分页管理功能。

 

Linux0.11在分页机制下的寻址(两级表寻址)

第一级表称为页目录。

存放在1页4k页面中。具有1k个4字节长度的表项。这些表项指向第二级表。线性地址的最高10位(位31-22)用作一级表(页目录)中的索引值来选择某个页目录项,用以选择某个二级表。

第二级表称为页表。

长度也是1个页面,每个表含有1k个4字节的表项。每个4字节表项含有相关页面的20位物理地址。二级页表使用线性地址中间的10位(位21-12)作为表项索引值,在表内索引含有页面20位物理地址的表项。该20位页面物理地址和线性地址中的低12位(页内偏移)组合在一起就得到了分页转换中的输出值,也就是最终的物理地址。

也就是说:

线性地址高10位---------索引页目录表----------->找到相应页表

线性地址中间10位---------索引页表----------->得到页表中相应的项,其中的高20位就是物理地址的高20位

线性地址低12位-------------------->物理地址的低12位

无标题

下面举两个例子说明Linux0.11的分页寻址。

1、寻址0x38

首先写成32位地址为0x0000 0038。取最高10位,0000 0000 00B,索引页目录项。这里找到页目录表的第0项,内容为$pg0+7=0x0000 1007。取线性地址的中间10位,00 0000 0000B,索引页表1(pg0)中的第0项,内容为

0x0000 0000+7,取他的高20位0x0000 0加上线性地址的低12位0x038就得到最终的物理地址0x0000 0038

2、寻址0x00f5 9f50

首先取最高10位,0000 0000 11,索引页目录表。这里为3就找到页目录表的第3项,指向页表4,内容为$pg3+7=0x0000 4000+7。取线性地址的中间10位,11 0101 1001B,索引页表4(pg3)中的0x359项,内容为0x00f5 9000+7。取其高20位0x00f5 9000加上线性地址的低12位0xf50就得到最终的物理地址0x00f5 9f50


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值