关于内核页表和进程页表的一个问题

昨天回复了一封电子邮件,有朋友问个问题很有代表性,内核初始化时会将896M前的物理页面作一一映射,那么用户进程分配到896M前的页面建立用户映射时是否要清除内核的一一映射。

关于这个问题,我的前面的文章已经有了解释,但是不甚详细,现在通过一个例子详细解释一下。实际上并不需要清除内核的一一映射,内核的一一映射只有内核自己使用,而且带来了很多的方便,内核巧妙的通过一一映射快速的执行内核路径,其实内核的一一映射也只有内核自己知道,用户进程根本涉及不到,内核只要管理好自己的内存没有什么是不可以的。页面的页表映射是硬件MMU的机制,而OS内核中拥有的是内存管理机制,二者并不冲突,它们都是独立的机制,并且层次不同,完全可以独立存在,比如你可以在没有MMU的嵌入式设备上实现复杂的内存管理,同时你也可以在同一个硬件MMU下实现不同的内存管理,比如windows和linux的就不同,我的观点大致总结如下(缺漏的部分前面的文章中有):在linux中内核一般不会介入用户的策略,它只是提供机制,向上就到系统调用接口为止,它没有upcall接口;只要用户不会访问内核,内核不会随意访问用户内存就不会有冲突,用户是难缠的,而且行为是不确定的,内核的行为是确定的,用户显然不能访问内核内存,但是内核却可以访问用户内存,然而内核十分清楚自己拥有哪些内存,比如初始化的时候将896以下页面作了一一映射,可是内核不一定用得了那么多,当用户进程需要内存时,完全可以从还在伙伴系统的512M处拽一个页面分配之并且映射到用户空间同时保留着内核的一一映射,这时这个512M处的页面已经从伙伴系统脱离了,内核如果这时也需要内存是不会被分配到该页面的,这样就不会有冲突,如果该页面本来就由内核所使用,那么它就不会在伙伴系统也不可能分配到用户进程,这么来说也不会有冲突,linux的内存管理和硬件的 MMU是两码事,如果说有联系那就是映射,影射仅仅是一个纽带和一个适配器而已。

下面我就通过一个例子来说明,当然要写内核模块了,我的机器是512M内存,少于896M,也就是全部作了一一映射。我们首先执行以下命令得到insmod程序的一些信息,因为模块加载是在insmod进程的上下文中:

[root@zhaoya ~]#objdump -d /sbin/insmod

...

08048ab0 <.fini>:

8048ab0: 55 push %ebp

8048ab1: 89 e5 mov %esp,%ebp

8048ab3: 53 push %ebx

...//0xcebe0000

我们看到进程地址空间0x8048ab3处是53,我们如果直接访问0x8048ab3,那么得到的就是53,这是显然的,但是我们还可以得到0x8048ab3所在页面的内核一一映射地址,然后访问那个地址看是不是还是53,如果是,那么就说明用户页面可能存在两份映射,接下来写一个模块:

#include

#include

#include

#include

#include

#include

static __init int test_init(void)

{

char * user_addr = (char *)0x8048ab3; //常规访问这个地址

printk("%x/n",*str);

struct page *page;

int n = get_user_pages(current, current->mm, user_addr, 1, 0, 1,&page,NULL);

printk("count:%d/n",n);

if(n>0)

{

unsigned long addr = page_address(page);

printk("address:%X/n",addr);

unsigned char * p = (unsigned char *)(address + user_addr&0xfff) ;//最后加入偏移

printk("%x/n",*p); //按照内核一一映射访问页面的内核地址

}

return 0;

}

static __exit void test_exit(void)

{

return ;

}

module_init(test_init);

module_exit(test_exit);

MODULE_LICENSE("Dual BSD/GPL");

MODULE_AUTHOR("Zhaoya");

MODULE_DESCRIPTION("kernel map");

MODULE_VERSION("Ver 0.1");

我们实在没有必要用cr3得到页目录,然后用二级或者三级乃至多级页面映射的方式去得到页面,那是非常复杂的,而且和体系结构相关,如果非要那样做的话首先你必须熟悉你的机器,其次还要熟悉C语言,幸运的是,内核提供了get_user_pages这个函数,我们可以轻而易举的根据虚拟地址得到页面。结果当然显而易见了,两次打印都是53,如果觉得不保险可以多试几个虚拟地址。

实际上一个页面保持多个映射并不是稀罕事,原因还是前面说的,内存管理和MMU管理是两码事。比如共享内存页面就可能保持多个映射。


 本文转自 dog250 51CTO博客,原文链接:http://blog.51cto.com/dog250/1273945


上一篇:IDEA14:maven快速排查依赖包冲突


下一篇:python协程系列(一)——生成器generator以及yield表达式详解