仔细的分析了一下各个内存管理函数的实现,发现里面涉及到了几个技巧,如果知道了这几个技巧,那么阅读内存管理源码将会事半功倍(主要是这几个技巧在几个函数中都出现过),当然也会选择性的分析几个比较重要的函数实现;
函数实现技巧
1、向上取整:以一个页面为了例,如果地址是1,那么向上取整就是4096;如果地址是 4095,向上取整就是4096;如果地址是4098,向上取整就是4096 x 2.......
#define CODE_SPACE(addr) ((((addr)+4095)&~4095) < \
current->start_code + current->end_code)
这个是比较addr地址是否在进程的代码段内,至于为什么要这样实现,到现在我还没弄清楚(等看完进程调度那块再到回来看看);这里用到一个技巧就是向上取整,addr是一个随机地址,要找出该地址的下一个页面的物理起始地址,那么就要向上取整了。(addr + 4095) & (~4095) :addr如果是4096的倍数,那么(addr+4095) & (~4095)结果就是addr;如果addr不是4096的倍数,那么addr以4096取余后得到的值范围为:1~4095,再加上4095得到范围:4096~4096+4094;然后&(~4095)就可以得到一个完整的4096了;其实上面的 &(~4095)就是相当于整除4096:/4096;可以使用(x + 9)/ 10,(其中x为任意数)来验证下;
在free_page_tables()函数中:size = (size + 0x3fffff) >> 22;也是向上取整的实例;
2、获取目录项/表项的物理起始地址:一般是从线性地址中获取到目录项/表项号,然后右移2个字节(因为一个项占用4个字节)就可以得到物理起始地址,也称目录项指针;
dir = (unsigned long *) ((from>>20) & 0xffc);
上面函数在free_page_tables()中(其他函数中有),from是线性地址,要得到目录项号,则:from = from >> 22(线性地址中和目录项有关的只有高10,这里就是获取到高10位的地址);注意这里获取到的仅仅只是目录项的号,而不是目录项的物理地址。
又根据一个目录项占用4个字节,那么from = from << 2(左移2位表示 乘以 2^2);所以两个合起来就干脆只右移20位得了,那么就有上面的 from >> 20了。但是这样有个问题:开始数据为1111,右移2位结果:1111 >> 2 为 0011;接着左移回2位结果:0011 << 2 为 1100,而开始的结果为1111,所以若按照这个来移动的话就会出错的。但是如果后面两位为0,则结果是正确的,相当于少移的2位是空的。
为了解决上面的问题,干脆把低2位和谐掉,因为如果11,12位是0,那倒无所谓;但如果不是0,那么结果就出错了。0xffc == 1111 1111 1100 ,或上它就是把低2位干掉(用这种方法可以验证下 1111 移动问题)。其实本来低2位就是要舍弃的,因为目录项是4字节对齐的;
同理,在其他函数中要获取页表项的物理地址方法类似:((address>>10) & 0xffc) 在write_verify()函数中有实现;
3、修改数组映射值:这个应该不是函数设计技巧,但在内存管理中却频繁出现,而且很重要。