linux2.4.19下__ioremap函数中remap_area_pages虚拟地址映射建立函数的代码分析
文章来源:http://gliethttp.cublog.cn
接续前一篇文章《linux2.4.19下__ioremap和get_vm_area的粗略理解》未完成的部分-remap_area_pages,进行进一步阐述:
虚拟内存vm技术,采用分页机制,使得进程可以使用比实际物理内存大甚多的空间;linux的地址有三种:
<1>物理地址
就是没有启用mmu之前cpu操作的地址,如:sdram的总线地址等.
<2>线性地址:
不采用分页机制,可以通过简单的加、减一个基地址方式,完成vm地址和物理地址的转换,如:物理内存sdram的虚拟地址位于3G~vmalloc_start,可以
通过virt_to_phys和phys_to_virt两个函数实现虚拟地址和sdram物理地址的直接线性转换,另一例子就是at91rm9200控制寄存器的控制,同样直接使用
AT91_IO_P2V(AT91C_BASE_SYS)将物理寄存器AT91C_BASE_SYS线性的映射成mmu下的虚拟地址(AT91_IO_P2V在include/asm-arm/arch-at91rm9200/Hardware.h中定义).
<3>逻辑地址,也被称为虚拟地址:逻辑地址和物理地址都被linux进行了分页管理,两者的页大小可以不同,但是为了管理上的方便,一般at91rm9200处理器下的linux系统将逻
辑地址和物理地址均划分大小为4k的页面,逻辑地址经过vm技术的2级(at91rm9200处理器对应的linux系统采用二级mmu映射机制)表转换后最终映射到的真正使用的物理地
址空间,at91rm9200处理器最终操作的还是总线地址,也就是物理地址,所以linux系统的工作之一就是将线性地址和逻辑地址,于是linux需要首先建立vm的2级转换表;
这之前我们先来了解一下mmu的分页机制:mmu可以对4种大小进行映射管理,
1)微页:构成1k的存储区
2)小页:构成4k的存储区
3)大页:构成64k的存储区
4)节:构成1M的存储区(arch/arm/kernel/head-armv.S启动代码中就暂用4个节映射,完成mmu的正常启动)
at91rm9200处理器采用了2级小页mmu管理方式,因此存在dir页目录4k[虚拟地址的31~20共12位],每个dir页目录项存放着第2级pte页表的基地址
dir页目录项内容如下:
bit 31 ... 10 9 ... 0
| 页表基地址 | mmu控制域 |
pte页表项内容如下:
bit 31 ... 12 11 ... 0
| 小页基地址 | mmu控制域 |
经过pte之后,pte表项内容的31~12位和虚拟地址的低12位组合成最终32位物理地址.
虚拟地址的结构:
bit 31 ... 20 19 ... 12 11 ... 0
| 第一级表索引 | 第二级表索引 | 页索引[偏移量] |
举个例子来说明2级小页mmu的结构,先假设虚拟地址为addr,那么dir目录项的索引号为:addr>>20;该dir目录索引号对应的目录项的内容31~10位作为pte的
基地址,addr的19~12位作为相对该pte基址的偏移,即:pte页表项,将pte页表项的31~12位和addr的11~0位偏移量组合成最终的物理地址,所以通过以上分析我们可以
总结出如下:dir共4k个,每个挂带1M数据,该1M数据又由256个pte分开管理,每个pte对应1个页4k,也就是1个dir页目录含有1个pte页表基地址对应256个pte页表项,1个页表项对应1个4k空间,可以用如下示意图简易的表示:
dir+0000->pte+000->4k
|->pte+001->4k
|->pte+002->4k
...
|->pte+253->4k
|->pte+254->4k
|->pte+255->4k
dir+0001->pte+000->4k
|->pte+001->4k
|->pte+002->4k
...
|->pte+253->4k
|->pte+254->4k
|->pte+255->4k
...
dir+4094->pte+000->4k
|->pte+001->4k
|->pte+002->4k
...
|->pte+253->4k
|->pte+254->4k
|->pte+255->4k
dir+4095->pte+000->4k
|->pte+001->4k
|->pte+002->4k
...
|->pte+253->4k
|->pte+254->4k
|->pte+255->4k
所以通过上述的2级小页mmu就轻松的实现了4k*256*4k=4G虚拟空间到物理空间的无缝转换.[注意:2级小页mmu结构中,dir和pte表项中的地址均为物理地址]
通过上面的mmu的转换路径分析,我们大体掌握了mmu的机理,现在转入正题,来看看__ioremap函数中remap_area_page子函数体源码:
static int
remap_area_pages(unsigned long address, unsigned long pfn,
unsigned long size, unsigned long flags)
{
int error;
pgd_t * dir;
unsigned long end = address + size;
pfn -= address >> PAGE_SHIFT;
//#define PGDIR_SHIFT 20
//#define pgd_index(addr) ((addr) >> PGDIR_SHIFT)
//#define pgd_offset(mm, addr) ((mm)->pgd+pgd_index(addr))
//dir=address对应的高12值[0~4095]
dir = pgd_offset(&init_mm, address);
flush_cache_all();
BUG_ON(address >= end);
spin_lock(&init_mm.page_table_lock);
do {
pmd_t *pmd;
//at91rm9200处理器采用2级小页mmu方式,所以pmd_alloc将直接返回dir,即:pmd = dir;
//对于采用3级页表的系统来说,pmd_alloc将会向dir目录项填入pmd的物理基地址
pmd = pmd_alloc(&init_mm, dir, address);
error = -ENOMEM;
if (!pmd)
break;
//remap_area_pmd函数将中间页pmd填入pte内容,因为采用的是2级小页mmu方式,所以pmd=dir;
if (remap_area_pmd(pmd, address, end - address,
pfn + (address >> PAGE_SHIFT), flags))
break;
error = 0;
//address+1M,如果size=5.2M,那么会连续占用接下的5个dir区域
address = (address + PGDIR_SIZE) & PGDIR_MASK;
dir++;
} while (address && (address < end));
spin_unlock(&init_mm.page_table_lock);
flush_tlb_all();
return error;
}
static inline int
remap_area_pmd(pmd_t * pmd, unsigned long address, unsigned long size,
unsigned long pfn, unsigned long flags)
{
unsigned long end;
pgprot_t pgprot;
//只对address的低1M地址空间进行pte创建
address &= ~PGDIR_MASK;
end = address + size;
//#define PGDIR_SHIFT 20
//#define PGDIR_SIZE (1UL << PGDIR_SHIFT) 1级目录以1M字节为dir++自加单位
if (end > PGDIR_SIZE)
end = PGDIR_SIZE;
pfn -= address >> PAGE_SHIFT;
BUG_ON(address >= end);
pgprot = __pgprot(L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY | L_PTE_WRITE | flags);
do {
//pte_alloc将获取256个连续pte的空间,并将地址指针指向这256个pte地址空间的最后一个地址处
pte_t * pte = pte_alloc(&init_mm, pmd, address);
if (!pte)
return -ENOMEM;
//remap_area_pte为256个连续的pte填充pfn物理地址
remap_area_pte(pte, address, end - address, pfn + (address >> PAGE_SHIFT), pgprot);
//因为是2级小页mmu方式,所以PMD_SIZE = PGD_SIZE = 1M
//又因为外层dir已经做了size总大小的处理,所以pmd这里,将只对1M空间进行pte内容填充,也就是while循环仅仅执行一次
address = (address + PMD_SIZE) & PMD_MASK;
pmd++;
} while (address && (address < end));
return 0;
}
以下为一些宏函数的出处和简单定义:
#define pmd_populate(mm,pmdp,pte) \
do { \
unsigned long __prot; \
if (mm == &init_mm) \
__prot = _PAGE_KERNEL_TABLE; \
else \
__prot = _PAGE_USER_TABLE; \
set_pmd(pmdp, __mk_pmd(pte, __prot)); \
} while (0)
//函数__mk_pmd用来把pte数值赋给pmd单元
//函数pte_alloc
static inline pmd_t __mk_pmd(pte_t *ptep, unsigned long prot)
{
unsigned long pte_ptr = (unsigned long)ptep;
pmd_t pmd;
//#define PTRS_PER_PTE 256
pte_ptr -= PTRS_PER_PTE * sizeof(void *);
pmd_val(pmd) = __virt_to_phys(pte_ptr) | prot;
return pmd;
}
include/asm-arm/cpu-multi32.h:#
include/asm-arm/cpu-single.h中有如下定义:
#define cpu_set_pgd cpu_fn(CPU_NAME,_set_pgd)
#define cpu_set_pmd cpu_fn(CPU_NAME,_set_pmd)
#define cpu_set_pte cpu_fn(CPU_NAME,_set_pte)
而cpu_fn定义如下:
#ifdef __STDC__
#define __cpu_fn(name,x) cpu_##name##x
#else
#define __cpu_fn(name,x) cpu_/**/name/**/x
#endif
#define cpu_fn(name,x) __cpu_fn(name,x)
所以最后at91rm9200处理器cpu_set_pgd,cpu_set_pmd和cpu_set_pte对应的函数原型为:
cpu_set_pgd -- cpu_arm920_set_pgd
cpu_set_pmd -- cpu_arm920_set_pmd
cpu_set_pte -- cpu_arm920_set_pte
以上三个函数位于arch/arm/mm/proc-arm920.S
include/asm-arm/proc-armv/Pgtable.h
#define PTRS_PER_PTE 256 //第二级,表示有256个表项,每个表项描述4K的二级页面
#define PTRS_PER_PMD 1 //因为at91rm9200处理器不需要该级,所以此处忽略
#define PTRS_PER_PGD 4096 //第一级,表示有4K个表项,每个表项描述1M的一级页面
include/asm-arm/pgtable.h中
extern pgd_t swapper_pg_dir[PTRS_PER_PGD];