免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
楼主: javacool
打印 上一主题 下一主题

kernel 中如何访问管理pci memory [复制链接]

论坛徽章:
0
51 [报告]
发表于 2009-04-17 10:14 |只看该作者
原帖由 Solaris12 于 2009-4-16 21:15 发表


这点可以通过查看内核内存分配函数kmalloc的实现来确定,谁能讲一下。

最近神游太厉害,手头工作忙不过来了。


kmalloc是基于slab(slub没看过)实现的,当slab中没有空闲的object可以分配时,它会向buddy system请求新的物理页

cache_grow -> kmem_getpages -> alloc_page_node, 得到物理页后,就将这个物理页所对应的kernel virtual address返回给slab. 因为slab allocator只使用low memory,所以这个virtual addr就是phy_addr + PAGE_OFFSET。这里并没有对page table的操作,kernel就直接使用这个virtual address来访问物理内存,因为映射在初始化的时候早就做好了。

kmalloc()返回的指针就是这个virtual address + 偏移量(指向slab中不同的object).

如果在kernel中访问high memory的话,就需要通过kmap_atomic把这个物理页map到kernel[3G+896M,4G)的虚拟空间后,才能访问。

论坛徽章:
0
52 [报告]
发表于 2009-04-17 12:23 |只看该作者
原帖由 eexplorer 于 2009-4-17 10:14 发表


kmalloc是基于slab(slub没看过)实现的,当slab中没有空闲的object可以分配时,它会向buddy system请求新的物理页

cache_grow -> kmem_getpages -> alloc_page_node, 得到物理页后,就将这个物理页所对应 ...



看了一下kmem_getpages,没有找到动态影射highmem的地方,但看了一下page结构的定义,里面提到只要highmem需要把内核虚拟地址存放起来,所以说我已经倾向于相信你的说法。

等吃饭回来再看。

刚才又看了kmap/kunmap和kmap_atomic/kunmap_atomic的代码,像你所说的那样,如果不是highmem,就不会操作页表去影射,而是直接利用减法公式来返回内核虚拟地址。
但如果是高端内存,在页表中设置映射。

[ 本帖最后由 Solaris12 于 2009-4-17 13:15 编辑 ]

论坛徽章:
0
53 [报告]
发表于 2009-04-17 13:46 |只看该作者

回复 #52 Solaris12 的帖子

> 看了一下kmem_getpages,没有找到动态影射highmem的地方,但看了一下page结构的定义,里面
> 提到只要highmem需要把内核虚拟地址存放起来,所以说我已经倾向于相信你的说法。

hehe, 我要说的就是kmem_getpages里面没有动态修改page table, kernel直接拿到虚拟地址就使用了。
在cache_grow()中,kernel会check allocation flags
    ...
    BUG_ON(flags & GFP_SLAB_BUG_MASK))   (BUG_MASK中包含了__GFP_HIGHMEM)
    ...

这样就保证在kmem_getpages()中不会分配到high memory的page。也就不需要去动态修改page table了。

论坛徽章:
0
54 [报告]
发表于 2009-04-17 15:57 |只看该作者
原帖由 Solaris12 于 2009-4-17 12:23 发表



刚才又看了kmap/kunmap和kmap_atomic/kunmap_atomic的代码,像你所说的那样,如果不是highmem,就不会操作页表去影射,而是直接利用减法公式来返回内核虚拟地址。
但如果是高端内存,在页表中设置映射。...



刚才又看了Solaris的实现,哈哈,惊人的相似。


在64位的Solaris里,会把所有物理内存全部都影射到内核的虚拟地址空间。

Solaris也有类似于kmap/kunmap和kmap_atomic/kunmap_atomic的代码,对这种物理地址到虚拟地址的转换是直接用加减法算出来的,而不是查页表。

/*
* Return the page frame number for the kpm virtual address vaddr.
*/
pfn_t
hat_kpm_va2pfn(caddr_t vaddr)
{
        pfn_t           pfn;

        ASSERT(IS_KPM_ADDR(vaddr));

        pfn = (pfn_t)btop(vaddr - kpm_vbase);

        return (pfn);
}


/*
* Return the kpm virtual address for a specific pfn
*/
caddr_t
hat_kpm_pfn2va(pfn_t pfn)
{
        uintptr_t vaddr = (uintptr_t)kpm_vbase + mmu_ptob(pfn);

        ASSERT(!pfn_is_foreign(pfn));
        return ((caddr_t)vaddr);
}


这种方式在很久很久前就在SPARC系统上用了,呵呵,看来不同的系统实现也是大同小异啊。

另外,我前面说过,存储都是零copy实现的,所以都是用bp_mapin直接影射用户内存到内核空间的,这种影射不需要查页表,直接加减法就可以了,

bp_mapin -> bp_mapin_common -> hat_kpm_mapin-> hat_kpm_page2va->hat_kpm_pfn2va->hat_kpm_pfn2va

前面有兄弟说的Linux aio用这种方式,我也看了Solaris的aio,也是一样一样一样地。

论坛徽章:
0
55 [报告]
发表于 2009-04-17 16:16 |只看该作者
freebsd初始化时为内核分配的物理内存好像30M不到,都是一些管理用的数据结构和内核代码,内核占用了3G-4G的虚拟空间,内核已经分配的物理内存也是映射到3G-4G虚拟空间。每个页表项里有一个标志表明某个页面是否对应有物理内存,不需要为每个虚拟页面分配物理内存,只有需要时才分配的,不用时说不定会被回收。

论坛徽章:
0
56 [报告]
发表于 2009-04-17 16:19 |只看该作者
原帖由 Solaris12 于 2009-4-17 15:57 发表



刚才又看了Solaris的实现,哈哈,惊人的相似。


在64位的Solaris里,会把所有物理内存全部都影射到内核的虚拟地址空间。


Solaris为什么要一次映射所有的物理内存,usr/src/uts/common/vm/seg_kpm.c 这里面有段注释,


* Kernel Physical Mapping (kpm) segment driver (segkpm).
*
* This driver delivers along with the hat_kpm* interfaces an alternative
* mechanism for kernel mappings within the 64-bit Solaris operating system,
* which allows the mapping of all physical memory into the kernel address
* space at once
. This is feasible in 64 bit kernels, e.g. for Ultrasparc II
* and beyond processors, since the available VA range is much larger than
* possible physical memory. Momentarily all physical memory is supported,
* that is represented by the list of memory segments (memsegs).
*
* Segkpm mappings have also very low overhead and large pages are used
* (when possible) to minimize the TLB and TSB footprint. It is also
* extentable for other than Sparc architectures (e.g. AMD64). Main
* advantage is the avoidance of the TLB-shootdown X-calls, which are
* normally needed when a kernel (global) mapping has to be removed.

*

论坛徽章:
0
57 [报告]
发表于 2009-04-17 16:31 |只看该作者
原帖由 bhpang2 于 2009-4-17 16:16 发表
freebsd初始化时为内核分配的物理内存好像30M不到,都是一些管理用的数据结构和内核代码,内核占用了3G-4G的虚拟空间,内核已经分配的物理内存也是映射到3G-4G虚拟空间。每个页表项里有一个标志表明某个页面是否 ...


请注意一次映射所有物理内存到内核空间并不意味着这些物理内存被内核分配和占用了,只是在页表里有相应的记录而已。
BSD什么样子我不清楚,回头可以问问BSD的大牛。

论坛徽章:
0
58 [报告]
发表于 2009-04-18 00:15 |只看该作者
恩,我觉得这是误解的关键所在,映射并不意味着已经被使用。以下是几年前的一份笔记,当中
只是流程分析,没有过多细节。现在看来里面一些用词也不太严谨,自己能明白意思就行了。


在网上查资料时看到几篇介绍 linux driver 编写的文章,其中
提到 kmalloc() 与 __get_free_page() 返回地址的问题,我们
都知道 kmalloc() 与 __get_free_page() 分配的是物理内存,
但它返回的到底是什么?那几篇关于驱动编写的文章中提到申请
的是物理地址,返回的依然是物理地址。但有一篇文章中,作者
对此提出了质疑,但没有给出答案。这也就是我写这篇笔记的
原因。在找答案的同时也将 linux kernel 分配物理内存的流程
做了下分析。这仅是篇笔记,写的比较乱。自己能看懂就行了。


这里只分析分配连续物理地址的函数。对于 vmalloc() 这种分
配非连续物理地址的函数不在本记录范围之内。

1、kmalloc() 分配连续的物理地址,用于小内存分配。
2、__get_free_page() 分配连续的物理地址,用于整页分配。

至于为什么说以上函数分配的是连续的物理地址和返回的到底
是物理地址还是虚拟地址,下面的记录会做出解释。


kmalloc() 函数本身是基于 slab 实现的。slab 是为分配小内存
提供的一种高效机制。但 slab 这种分配机制又不是独立的,它
本身也是在页分配器的基础上来划分更细粒度的内存供调用者使
用。也就是说系统先用页分配器分配以页为最小单位的连续物理
地址,然后 kmalloc() 再在这上面根据调用者的需要进行切分。
关于以上论述,我们可以查看 kmalloc() 的实现,kmalloc()
函数的实现是在 __do_kmalloc() 中,可以看到在 __do_kmalloc()
代码里最终调用了 __cache_alloc() 来分配一个 slab,其实
kmem_cache_alloc() 等函数的实现也是调用了这个函数来分配
新的 slab。我们按照 __cache_alloc() 函数的调用路径一直跟
踪下去会发现在 cache_grow() 函数中使用了 kmem_getpages()
函数来分配一个物理页面,kmem_getpages() 函数中调用的
alloc_pages_node() 最终是使用 __alloc_pages() 来返回一个
struct page 结构,而这个结构正是系统用来描述物理页面的。
这样也就证实了上面所说的,slab 是在物理页面基础上实现的。
kmalloc() 分配的是物理地址。


__get_free_page() 是页面分配器提供给调用者的最底层的内
存分配函数。它分配连续的物理内存。__get_free_page() 函数
本身是基于 buddy 实现的。在使用 buddy 实现的物理内存管理中
最小分配粒度是以页为单位的。关于以上论述,我们可以查看
__get_free_page() 的实现,可以看到 __get_free_page() 函
数只是一个非常简单的封状,它的整个函数实现就是无条件的调
用 __alloc_pages() 函数来分配物理内存,上面记录 kmalloc()
实现时也提到过是在调用 __alloc_pages() 函数来分配物理页面
的前提下进行的 slab 管理。那么这个函数是如何分配到物理页
面又是在什么区域中进行分配的?回答这个问题只能看下相关的
实现。可以看到在 __alloc_pages() 函数中,多次尝试调用
get_page_from_freelist() 函数从 zonelist 中取得相关 zone,
并从其中返回一个可用的 struct page 页面(这里的有些调用分
支是因为标志不同)。至此,可以知道一个物理页面的分配是
从 zonelist(一个 zone 的结构数组)中的 zone 返回的。那
么 zonelist/zone 是如何与物理页面关联,又是如何初始化的
呢?继续来看 free_area_init_nodes() 函数,此函数在系统初始
化时由 zone_sizes_init() 函数间接调用的,zone_sizes_init()
函数填充了三个区域:ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEM。
并把他们作为参数调用 free_area_init_nodes(),在这个函数中
会分配一个 pglist_data 结构,此结构中包含了 zonelist/zone
结构和一个 struct page 的物理页结构,在函数最后用此结构作
为参数调用了 free_area_init_node() 函数,在这个函数中首先
使用 calculate_node_totalpages() 函数标记 pglist_data 相关
区域,然后调用 alloc_node_mem_map() 函数初始化 pglist_data
结构中的 struct page 物理页。最后使用 free_area_init_core()
函数关联 pglist_data 与 zonelist。现在通以上分析已经明确了
__get_free_page() 函数分配物理内存的流程。但这里又引出了几
个新问题,那就是此函数分配的物理页面是如何映射的?映射到了
什么位置?到这里不得不去看下与 VMM 相关的引导代码。


在看 VMM 相关的引导代码前,先来看一下 virt_to_phys() 与
phys_to_virt 这两个函数。顾名思义,即是虚拟地址到物理地址
和物理地址到虚拟地址的转换。函数实现十分简单,前者调用了
__pa( address ) 转换虚拟地址到物理地址,后者调用 __va(
addrress ) 将物理地址转换为虚拟地址。再看下 __pa __va 这
两个宏到底做了什么。

#define __pa(x) ((unsigned long)(x)-PAGE_OFFSET)
#define __va(x) ((void *)((unsigned long)(x)+PAGE_OFFSET))

通过上面可以看到仅仅是把地址加上或减去 PAGE_OFFSET,而
PAGE_OFFSET 在 x86 下定义为 0xC0000000。这里又引出了疑问,
在 linux 下写过 driver 的人都知道,在使用 kmalloc() 与
__get_free_page() 分配完物理地址后,如果想得到正确的物理
地址需要使用 virt_to_phys() 进行转换。那么为什么要有这一步
呢?我们不分配的不就是物理地址么?怎么分配完成还需要转换?
如果返回的是虚拟地址,那么根据如上对 virt_to_phys() 的分析,
为什么仅仅对 PAGE_OFFSET 操作就能实现地址转换呢?虚拟地址与
物理地址之间的转换不需要查页表么?代着以上诸多疑问来看 VMM
相关的引导代码。


直接从 start_kernel() 内核引导部分来查找 VMM 相关内容。可以
看到第一个应该关注的函数是 setup_arch(),在这个函数当中使用
paging_init() 函数来初始化和映射硬件页表(在初始化前已有 8M
内存被映射,在这里不做记录),而 paging_init() 则是调用的
pagetable_init() 来完成内核物理地址的映射以及相关内存的初始化。
在 pagetable_init() 函数中,首先是一些 PAE/PSE/PGE 相关判断
与设置,然后使用 kernel_physical_mapping_init() 函数来实现内
核物理内存的映射。在这个函数中可以很清楚的看到,pgd_idx 是以
PAGE_OFFSET 为启始地址进行映射的,也就是说循环初始化所有物
理地址是以 PAGE_OFFSET 为起点的。继续观察我们可以看到在 PMD
被初始化后,所有地址计算均是以 PAGE_OFFSET 作为标记来递增的。
分析到这里已经很明显的可以看出,物理地址被映射到以 PAGE_OFFSET
开始的虚拟地址空间。这样以上所有疑问就都有了答案。kmalloc() 与
__get_free_page() 所分配的物理页面被映射到了 PAGE_OFFSET 开始
的虚拟地址,也就是说实际物理地址与虚拟地址有一组一一对应的关系,
正是因为有了这种映射关系,对内核以 PAGE_OFFSET 启始的虚拟地址的分
配也就是对物理地址的分配(当然这有一定的范围,应该在 PAGE_OFFSET
与 VMALLOC_START 之间,后者为 vmalloc() 函数分配内存的启始地址)。
这也就解释了为什么 virt_to_phys() 与 phys_to_virt() 函数的实现
仅仅是加/减 PAGE_OFFSET 即可在虚拟地址与物理地址之间转换,正是
因为了有了这种映射,且固定不变,所以才不用去查页表进行转换。这也
同样回答了开始的问题,即 kmalloc() / __get_free_page() 分配的是
物理地址,而返回的则是虚拟地址(虽然这听上去有些别扭)。正是因
为有了这种映射关系,所以需要将它们的返回地址减去 PAGE_OFFSET 才
可以得到真正的物理地址。



参考:linux kernel source 2.6.19.1

/mm/page_alloc.c
/mm/slab.c
/arch/i386/mm/init.c
/init/main.c

论坛徽章:
0
59 [报告]
发表于 2009-04-18 10:57 |只看该作者
原帖由 sinister 于 2009-4-18 00:15 发表
恩,我觉得这是误解的关键所在,映射并不意味着已经被使用。以下是几年前的一份笔记,当中
只是流程分析,没有过多细节。现在看来里面一些用词也不太严谨,自己能明白意思就行了。
...


不错的笔记。

昨天和同事又聊了一下,同事说Windows也是类似的处理。

这样做最大的好处就是内核影射一个物理地址到内核不需要差TLB和页表了,的确是很方便。

另外,从设计角度来说,内核要管理起来这些物理内存,其实是需要给所有物理内存映射出一个地址才便于管理的,直接利用物理地址管理不大方便的。

论坛徽章:
0
60 [报告]
发表于 2009-04-19 16:05 |只看该作者
>>内核中可以访问所有的物理页面,换而言之所有的物理页面在系统空间都有映射.

这个貌似有问题.
物理内存地址大于HIMEM的时候开始只在低端内存在保存了它们的page信息.
高端内存只在有需要的时候映射,可以是vmalloc映射到内核虚拟地址空间,也是可能映射到用户地址空间.
ldd.ulk甚至ldk(我想作者应该都应该列于a版主所说的30人中...)上都是这么说的:内核虚拟地址空间就是3G-4G的空间,内核逻辑地址空间就虚拟地址空间的一部分,就是3G+的一部分,内存少于896M,则就是3G---(3G+内存大小)(当然包括其他的非内存),全部是线形的映射,__pa()操作有效,建立了映射(这里我有个疑问,__pa只是程序里用的方便吧,CPU寻址的时候仍然是是查表吧,懂硬件的说说....).

逻辑地址以上到4G的虚拟空间保留,用来vmalloc或者ioremap之内.也就是说vmalloc本身的得到虚拟地址范围并不局限于上128M.
有128M的原因是内存大于896M,为了vmalloc能有连续的虚拟空间可用而做的保留,但是某些体系结构中定义了VMALLOC_START和VMALLOC_END,强制vmalloc只能在这个范围内分配虚拟地址.
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP