ioremap的完成细节[优质文档]
ioremap的实现细节
linux内核ioremap函数解析:其中涉及到页表的映射,以及高端虚
拟内存的使用,在linux中ioremap函数和vmalloc函数的实现大体
相同。
/*
* Remap an arbitrary physical address space into the kernel virtual
* address space. Needed when the kernel wants to access high addresses
* directly.
*
* NOTE! We need to allow non-page-aligned mappings too: we will obviously
* have to convert them into an offset in a page-aligned mapping, but the
* caller shouldn't need to know that small detail.
*
* 'flags' are the extra L_PTE_ flags that you want to specify for this
* mapping. See include/asm-arm/proc-armv/pgtable.h for more information.
*/
void __iomem * __ioremap(unsigned long phys_addr, size_t size, unsigned long flags,
unsigned long align)
{
void * addr;
struct vm_struct * area;
unsigned long offset, last_addr;
/* Don't allow wraparound or zero size */
last_addr = phys_addr + size - 1;
if (!size || last_addr < phys_addr)
return NULL;
/*
* Mappings have to be page-aligned
*/
offset = phys_addr & ~PAGE_MASK;
phys_addr &= PAGE_MASK;
size = PAGE_ALIGN(last_addr) - phys_addr;
/*
* Ok, go for it..
*/
/* 获得一个可用虚拟区间,对低端虚拟地址区间内核用vm_area_struct结构表示,
而高端虚拟地址区间用vm_struct表示 */
area = get_vm_area(size, VM_IOREMAP);
if (!area)
return NULL;
addr = area->addr;
/* 进行真正的页面映射,需要分配页表 */
if (remap_area_pages((unsigned long) addr, phys_addr, size,
flags)) {
vfree(addr);
return NULL;
}
/* 关于返回值,程序作者做了很好的解释,那就是内核实际的映射是要页
对齐的,这里把页对齐的地址转换成非页对齐的地址 We
need to allow non-page-aligned mappings too: we will obviously
have to convert them into an offset in a page-aligned mapping,
but the
caller shouldn't need to know that small detail.*/
return (void __iomem *) (offset + (char *)addr);
}
struct vm_struct *get_vm_area(unsigned long size, unsigned long flags)
{
/* #define VMALLOC_START (((unsigned long)high_memory + VMALLOC_OFFSET)
& ~(VMALLOC_OFFSET-1))
#define VMALLOC_VMADDR(x) ((unsigned long)(x))
#define VMALLOC_END (0xE0000000) */
/* 关于high_memory:linux内核规定,只映射0-896M真是物理
内存(如果有的话,
称为low memory)到内核空间,也就是0xc0000000+0到
0xc0000000+896M,而且是线性映射,有物理地址0<=x<=896M,
就有内核地址0xc0000000+x.如果物理内存y<896M,则
high_memory=0xc0000000+y(是虚拟地址);否则
high_memory=0xc0000000+896M或者说high_memory不能超
过0xc0000000+896M
//high_memory = (void *) __va(max_low_pfn * PAGE_SIZE);
那么 high_memory是“具体物理内存的上限对应的虚拟地
址”就比较好理解了.*/
return __get_vm_area(size, flags, VMALLOC_START,
VMALLOC_END);
}
struct vm_struct *__get_vm_area(unsigned long size, unsigned
long flags,
unsigned long start, unsigned long end)
{
struct vm_struct **p, *tmp, *area;
unsigned long align = 1;
unsigned long addr;
if (flags & VM_IOREMAP) {
int bit = fls(size); /* 用来计算所要分配的页面数 */
if (bit > IOREMAP_MAX_ORDER)
bit = IOREMAP_MAX_ORDER;
else if (bit < PAGE_SHIFT)
bit = PAGE_SHIFT;
align = 1ul << bit;
}
addr = ALIGN(start, align); /* start的值要bit页面对齐 */
area = kmalloc(sizeof(*area), GFP_KERNEL);
if (unlikely(!area))
return NULL;
/*
* We always allocate a guard page.
*/
size += PAGE_SIZE;/* 预留一个页面的空洞,这是linux的规定,据说容易检测到内存越
界访问 */
if (unlikely(!size)) {
kfree (area);/* unlikely是说(!size)为真的可能性小,这样编译器在编译时,就会
进行相应的代码优化 */
return NULL;
}
static int remap_area_pages(unsigned long start, unsigned long
phys_addr,
unsigned long size, unsigned long flags)
{
unsigned long address = start;
unsigned long end = start + size;
int err = 0;
pgd_t * dir;
phys_addr -= address;
/* 根据虚拟地址,计算出所在的目录项 */
dir = pgd_offset(&init_mm, address);
BUG_ON(address >= end);
spin_lock(&init_mm.page_table_lock);
do {
pmd_t *pmd = pmd_alloc(&init_mm, dir, address);
if (!pmd) {
err = -ENOMEM;
break;
}
/* 由于linux采用2映射,所以pmd==pgt */
if (remap_area_pmd(pmd, address, end - address,
phys_addr + address, flags)) {
err = -ENOMEM;
break;
}
/* PGIDR_SIZE = 1<<20 */
address = (address + PGDIR_SIZE) & PGDIR_MASK;
dir++;/* 在ARM中每个目录项对应1M */
} while (address && (address < end));
spin_unlock(&init_mm.page_table_lock);
flush_cache_vmap(start, end);
return err;
}
static inline int remap_area_pmd(pmd_t * pmd, unsigned long
address, unsigned long size,
unsigned long phys_addr, unsigned long flags)
{
unsigned long end;
pgprot_t pgprot;
address &= ~PGDIR_MASK;
end = address + size;
if (end > PGDIR_SIZE)
end = PGDIR_SIZE;
phys_addr -= address;
BUG_ON(address >= end);
/* 页面标志 */
pgprot = __pgprot(L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY
| L_PTE_WRITE | flags);
do {
pte_t * pte = pte_alloc_kernel(&init_mm, pmd, address);
if (!pte)
return -ENOMEM;
remap_area_pte(pte, address, end - address, address + phys_addr, pgprot);
address = (address + PMD_SIZE) & PMD_MASK;
pmd++;
} while (address && (address < end));/* linux采用2级映
射,所以此while循环一
次 */
return 0;
}
static inline void remap_area_pte(pte_t * pte, unsigned long
address, unsigned long size,
unsigned long phys_addr, pgprot_t pgprot)
{
unsigned long end;
address &= ~PMD_MASK;
end = address + size;
if (end > PMD_SIZE)
end = PMD_SIZE;
BUG_ON(address >= end);
do {
if (!pte_none(*pte))
goto bad;
/* 把物理地址设置到pte中 */
set_pte(pte, pfn_pte(phys_addr >> PAGE_SHIFT, pgprot));
address += PAGE_SIZE;
phys_addr += PAGE_SIZE;
pte++;
/* 一个pte对应4k */
} while (address && (address < end));
return;
bad:
printk("remap_area_pte: page already exists\n");
BUG();
}
本文档为【ioremap的完成细节[优质文档]】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑,
图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
该文档来自用户分享,如有侵权行为请发邮件ishare@vip.sina.com联系网站客服,我们会及时删除。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。