QQ登录

只需一步,快速开始

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 1124|回复: 4

请教高手两个内存管理的疑问(斑主帮帮忙呀)

[复制链接]
发表于 2004-7-13 17:27:54 | 显示全部楼层 |阅读模式
最近在看《深入理解linux内核》这本书,书中的内核版本是2.2.14,在内存管理部分有个疑问:
在内核源码\include\asm-i386\Page.h中有这样几个宏:
#define __pa(x)                        ((unsigned long)(x)-PAGE_OFFSET)
#define __va(x)                        ((void *)((unsigned long)(x)+PAGE_OFFSET))
#define MAP_NR(addr)                (__pa(addr) >> PAGE_SHIFT)
分别用于实现内核空间中线性地址和物理地址之间的转换,MAP_NR(addr)则是用来获得线性地址addr所对应的页框在MEM_MAP中的索引。

从PAGE_OFFSET开始的线性地址范围(这个线性范围通常为1个G,即PAGE_OFFSET=0xc0000000)必须要能够映射整个物理内存。但是通常实际内存并没有1个G这么大,假设物理内存有256M,那么剩余的700多M的线性地址范围linux可以用来映射非连续内存区,当用vmalloc()申请内存区时,内核分配非连续内存区,返回这段内存区的起始的线性地址,在vmalloc()内部改变了一部分页表,使得我们获得的线性地址可以映射新获得的非连续的内存区。

我的第一个疑问是:
对于可以用来映射非连续内存区的线性地址,用__pa()宏的方法为什么也能获得正确的物理地址?
假设这个线性地址为address, address-0xc0000000得到的值是不是已经超过了实际的物理内存的大小?疑惑中,请各位指点

我的第二个疑问是:
在内核中我们获得内存通常会用到get_free_page(),kmalloc(),vmalloc() 等函数,但是这些函数在根本上其实还是调用基于伙伴算法的__get_free_pages()来获得空闲的内存块,同样以上三个函数释放内存时根本上也调用了基于伙伴算法的free_pages_ok()函数。可是在free_pages_ok()函数里,仅仅是完成了类似注册该块为空闲块,并根据伙伴算法作了一些相应的处理,而对原来这个内存块里存放的内容并没有作任何处理,比如说清零等,那样的话,下一个请求到来,如果获得了这个块,岂不是可以窃取这个块里原来的内容?不知道linux里在这一点上是不是有别的处理,请教各位

谢谢!
 楼主| 发表于 2004-7-13 17:54:23 | 显示全部楼层
自己顶一顶,各位高手帮帮忙呀
回复

使用道具 举报

发表于 2004-7-14 00:24:01 | 显示全部楼层
让我们一起探讨一下关于内存管理的问题吧。
首先我不知道你所谓的“假设物理内存有256M,那么剩余的700多M的线性地址范围linux可以用来映射非连续内存区,”到底是是什么意思。
我们可以随便看一个进程的地址影射,cat /proc/**/maps (**是进程号,你随便选一个吧):
08048000-080dc000 r-xp 00000000 03:0a 115006     /bin/bash
080dc000-080e2000 rw-p 00094000 03:0a 115006     /bin/bash
080e2000-08135000 rwxp 00000000 00:00 0
40000000-40016000 r-xp 00000000 03:0a 163952     /lib/ld-2.3.2.so
40016000-40017000 rw-p 00015000 03:0a 163952     /lib/ld-2.3.2.so
40017000-40018000 rw-p 00000000 00:00 0
40023000-40059000 r-xp 00000000 03:0a 163859     /lib/libncurses.so.5.4
40059000-40062000 rw-p 00035000 03:0a 163859     /lib/libncurses.so.5.4
40062000-40064000 r-xp 00000000 03:0a 116386     /lib/tls/libdl-2.3.2.so
40064000-40065000 rw-p 00001000 03:0a 116386     /lib/tls/libdl-2.3.2.so
40065000-40066000 rw-p 00000000 00:00 0
40066000-40196000 r-xp 00000000 03:0a 116384     /lib/tls/libc-2.3.2.so
40196000-4019f000 rw-p 0012f000 03:0a 116384     /lib/tls/libc-2.3.2.so
4019f000-401a1000 rw-p 00000000 00:00 0
401a1000-403a1000 r--p 00000000 03:0a 475474     /usr/lib/locale/locale-archive
403a1000-403a4000 r-xp 00000000 03:0a 427331     /usr/lib/gconv/EUC-CN.so
403a4000-403a5000 rw-p 00002000 03:0a 427331     /usr/lib/gconv/EUC-CN.so
403ac000-403b3000 r-xp 00000000 03:0a 116390     /lib/tls/libnss_compat-2.3.2.so
403b3000-403b4000 rw-p 00007000 03:0a 116390     /lib/tls/libnss_compat-2.3.2.so
403b4000-403c6000 r-xp 00000000 03:0a 116389     /lib/tls/libnsl-2.3.2.so
403c6000-403c7000 rw-p 00011000 03:0a 116389     /lib/tls/libnsl-2.3.2.so
403c7000-403c9000 rw-p 00000000 00:00 0
403c9000-403d2000 r-xp 00000000 03:0a 116394     /lib/tls/libnss_nis-2.3.2.so
403d2000-403d3000 rw-p 00008000 03:0a 116394     /lib/tls/libnss_nis-2.3.2.so
403d3000-403dc000 r-xp 00000000 03:0a 116392     /lib/tls/libnss_files-2.3.2.so
403dc000-403dd000 rw-p 00008000 03:0a 116392     /lib/tls/libnss_files-2.3.2.so
403dd000-403ed000 r-xp 00000000 03:0a 427457     /usr/lib/gconv/libGB.so
403ed000-403ee000 rw-p 0000f000 03:0a 427457     /usr/lib/gconv/libGB.so
bfff7000-c0000000 rwxp ffff8000 00:00 0
从上面的内容我们可以解释__pa()和__va()的用法,你看一看代码就知道,这两个函数都是在内核态转换地址的。
如果还是不明白,看看上面的进程影射地址,上面的显示是用户态的线性地址,都是中止于0xc0000000。这代表的意思就是任何进程的内核态的地址都是一样的。我记得不错的话,在kernel-2.4.*里面内核态是非抢夺的。就是说不管有多少进程,进入内核态都是一个一个的。内核态的物理和线性地址影射相当简单,就是0xc0000000的关系,这在系统初始化的时候设置的,具体什么地方我不记得了,你自己找找吧。
而用户态的线性地址和物理地址则不是单纯的用__pa()和__va(),而是通过影射表。
你想编译出来的内核总共有多大?不会是256M吧。里面的堆栈大小也是严格设定的(我记得不错的话)。其实好像是在用户从实模式进入保护模式的时候一开始内核态地址就是内存某块地址,然后进入模式转换的时候被强行的加上0xc0000000,变成了传说中的内核态线性地址。
我很久没看源代码了,不知道上面说的对不对,不过你给我的感觉是似乎没有特别的理解里面的概念。

至于你的第二个问题,首先要明确内核是很懒的(不到万不得已不会收拾垃圾,当然高版本的内核不知道有没有改变算法)。__get_free_pages()类似这样的函数虽然没有对实际的内存进行你所谓的清零样的操作(那样做对于操作系统将是灾难性的后果,cpu的速度和读取内存的速度可以让内核疲于奔命了),但是他已经对这些页面的数据结构设置了相应的位,比如说是干净的还是脏的。

其实这也是操作系统里常做的事情,比如你删除一个文件,其实他并没有将你原来文件所占磁盘(指清零),而只是干掉了描述这个文件的数据结构。同样的道理存在于__get_free_pages()这样的函数。同时,内核还有相应处理脏而无用内存的daemon process。在必要的时候他会出手的。呵呵。

里面关系错综复杂,不知道有没有回答你的问题。以上是我的记忆,大家一起讨论吧。
回复

使用道具 举报

 楼主| 发表于 2004-7-14 10:29:56 | 显示全部楼层
谢谢jamesxuruo, 第二个问题我想你已经解答了我的疑问。
第一个问题,我说的不是太清楚,所以造成了你的误解。我的意思是,linux在内核态有一个分配内存块的函数,vmalloc()。这个函数会返回一段连续的线性地址范围来映射一段物理上并不连续的页框。能够映射非连续内存区的线性地址范围在linux中做出了限制,即从VMALLOC_START开始的线性地址。VMALLOC_START的值=0xc0000000+实际的物理内存的大小(因为从0xc0000000开始的内核态线性地址必须要能够映射整个可用的物理内存)+8M(防止内存非法访问)。比如你的物理内存是256M(不是内核的大小),那么VMALLOC_START=0xc0000000+0x10000000+0x00800000=0xd0800000,也就是说从0xd0800000开始的线性地址范围(这段线性地址范围当然是内核态的,所以我想应该也是用__pa()宏来转换成物理地址),linux用来映射非连续内存区。

我的疑问是,用vmalloc()获得的线性地址,在调用vfree()释放时会调用MAP_NR()宏来获得实际的物理页框索引,其实就是用(线性地址-0xc0000000)>>page_shift(值一般等于12)。可是这个(线性地址-0xc0000000)得到的值应该已经超过了真正的物理页框数了。这样获得的索引值还有什么意义呢?

内核态的页表的初始化是在paging_init()函数里进行的,对于0xc0000000,其实是PAGE_OFFSET宏,它的值和你的物理内存的大小有关系,一般如果物理内存小于1G,那么PAGE_OFFSET=0xc0000000,就是说从0xc0000000--0xffffffff总共1个G的线性范围能够映射不超过1个G的物理内存,如果你在配置内核时配置内存〉1G,那么PAGE_OFFSET会是另外一个值,0x80000000,或0x40000000。分别拥有2个G或3个G可以用来映射物理内存。
回复

使用道具 举报

 楼主| 发表于 2004-7-16 11:59:15 | 显示全部楼层
顶一顶,高手帮忙解释一下
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

GMT+8, 2024-11-16 08:57 , Processed in 0.035689 second(s), 15 queries .

© 2021 Powered by Discuz! X3.5.

快速回复 返回顶部 返回列表