Chinaunix首页 | 论坛 | 博客
  • 博客访问: 486327
  • 博文数量: 130
  • 博客积分: 2111
  • 博客等级: 大尉
  • 技术积分: 1373
  • 用 户 组: 普通用户
  • 注册时间: 2009-08-28 09:55
个人简介

IT民工

文章分类

全部博文(130)

文章存档

2021年(1)

2019年(1)

2017年(3)

2014年(1)

2013年(3)

2012年(2)

2011年(3)

2010年(2)

2009年(114)

分类: 嵌入式

2009-09-03 13:16:49

arm linux 启动流程(四)[ZT]
2008-01-28 15:22:17
如果系统有flash,
#ifdef FLUSH_BASE
p->physical = FLUSH_BASE_PHYS;
p->virtual = FLUSH_BASE;
p->length = PGDIR_SIZE;
p->domain = DOMAIN_KERNEL;
p->prot_read = 1;
p->prot_write = 0;
p->cacheable = 1;
p->bufferable = 1;

p ++;
#endif

其中的prot_read和prot_write是用来设置页表的domain的,

下面就是逐个区段建立页表:

q = init_maps;
do {
if (address < q->virtual || q == p) {
clear_mapping(address);
address += PGDIR_SIZE;
} else {
create_mapping(q);

address = q->virtual + q->length;
address = (address + PGDIR_SIZE - 1) & PGDIR_MASK;

q ++;
}
} while (address != 0);


上次说到memtable_init中初始化页表的循环,
这个过程比较重要,我们看仔细些:


q = init_maps;
do {
if (address < q->virtual || q == p) {
//由于内核空间是从c000 0000开始,所以c000 0000
//以前的页表项全部清空

clear_mapping(address);
address += PGDIR_SIZE;
//每个表项增加1m,这里感到了section的好处
}

其中clear_mapping()是个宏,根据处理器的
不同,在920下被展开为

cpu_arm920_set_pmd(((pmd_t *)(((&init_mm )->pgd+
(( virt) >> 20 )))),((pmd_t){( 0 )}));

其中init_mm为内核的mm_struct,pgd指向
swapper_pg_dir,在arch/arm/kernel/init_task.c中定义

ENTRY(cpu_arm920_set_pmd)
#ifdef CONFIG_CPU_ARM920_WRITETHROUGH
eor r2, r1, #0x0a
tst r2, #0x0b
biceq r1, r1, #4
#endif
str r1, [r0]

把pmd_t填写到页表项中,由于pmd_t=0,
实际等于清除了这一项,由于d cache打开,
这一条指令实际并没有写回内存,而是写到cache中

mcr p15, 0, r0, c7, c10, 1

把cache中 地址r0对应的内容写回内存中,
这一条语句实际是写到了write buffer中,
还没有真正写回内存。

mcr p15, 0, r0, c7, c10, 4

等待把write buffer中的内容写回内存。在这之前core等待

mov pc, lr

在这里我们看到,由于页表的内容十分关键,为了确保写回内存,
采用了直接操作cache的方法。由于在arm core中,打开了d cache
则必定要用write buffer.所以还有wb的回写问题。
由于考虑到效率,我们使用了cache和buffer,
所以在某些地方要用指令保证数据被及时写回。


下面映射c000 0000后面的页表

else {
create_mapping(q);

address = q->virtual + q->length;
address = (address + PGDIR_SIZE - 1) & PGDIR_MASK;

q ++;
}
} while (address != 0);


create_mapping也在mm-armv.c中定义;

static void __init create_mapping(struct map_desc *md)
{
unsigned long virt, length;
int prot_sect, prot_pte;
long off;

prot_pte = L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY |
(md->prot_read ? L_PTE_USER : 0) |
(md->prot_write ? L_PTE_WRITE : 0) |
(md->cacheable ? L_PTE_CACHEABLE : 0) |
(md->bufferable ? L_PTE_BUFFERABLE : 0);

prot_sect = PMD_TYPE_SECT | PMD_DOMAIN(md->domain) |
(md->prot_read ? PMD_SECT_AP_READ : 0) |
(md->prot_write ? PMD_SECT_AP_WRITE : 0) |
(md->cacheable ? PMD_SECT_CACHEABLE : 0) |
(md->bufferable ? PMD_SECT_BUFFERABLE : 0);

由于arm中section表项的权限位和page表项的位置不同,
所以根据struct map_desc 中的保护标志,分别计算页表项
中的AP,domain,CB标志位。

有一段时间没有写了,道歉先,前一段时间在做arm linux的xip,终于找到了
在flash中运行kernel的方法,同时对系统的存储管理
的理解更深了一层,我们继续从上回的create_mapping往下看:

while ((virt & 0xfffff || (virt + off) & 0xfffff) && length >= PAGE_SIZE) {
alloc_init_page(virt, virt + off, md->domain, prot_pte);

virt += PAGE_SIZE;
length -= PAGE_SIZE;
}

while (length >= PGDIR_SIZE) {
alloc_init_section(virt, virt + off, prot_sect);

virt += PGDIR_SIZE;
length -= PGDIR_SIZE;
}

while (length >= PAGE_SIZE) {
alloc_init_page(virt, virt + off, md->domain, prot_pte);

virt += PAGE_SIZE;
length -= PAGE_SIZE;
}
这3个循环的设计还是很巧妙的,create_mapping的作用是设置虚地址virt
到物理地址virt + off的映射页目录和页表。arm提供了4种尺寸的页表:
1M,4K,16K,64K,armlinux只用到了1M和4K两种。

这3个while的作用分别是“掐头“,“去尾“,“砍中间“。
第一个while是判断要映射的地址长度是否大于1m,且是不是1m对齐,
如果不是,则需要创建页表,例如,如果要映射的长度为1m零4k,则先要将“零头“
去掉,4k的一段需要中间页表,通过第一个while创建中间页表,
而剩下的1M则交给第二个while循环。最后剩下的交给第三个while循环。


alloc_init_page分配并填充中间页表项
static inline void
alloc_init_page(unsigned long virt, unsigned long phys, int domain, int prot)
{
pmd_t *pmdp;
pte_t *ptep;

pmdp = pmd_offset(pgd_offset_k(virt), virt);//返回页目录中virt对应的表项

if (pmd_none(*pmdp)) {//如果表项是空的,则分配一个中间页表
pte_t *ptep = alloc_bootmem_low_pages(2 * PTRS_PER_PTE *
sizeof(pte_t));

ptep += PTRS_PER_PTE;
//设置页目录表项
set_pmd(pmdp, __mk_pmd(ptep, PMD_TYPE_TABLE | PMD_DOMAIN(domain)));
}
ptep = pte_offset(pmdp, virt);
//如果表项不是空的,则表项已经存在,只需要设置中间页表表项
set_pte(ptep, mk_pte_phys(phys, __pgprot(prot)));
}

alloc_init_section只需要填充页目录项

alloc_init_section(unsigned long virt, unsigned long phys, int prot)
{
pmd_t pmd;

pmd_val(pmd) = phys | prot;//将物理地址和保护标志合成页目录项

set_pmd(pmd_offset(pgd_offset_k(virt), virt), pmd);
}

通过create_mapping可为内核建立所有的地址映射,最后是映射中断向量表
所在的区域:

init_maps->physical = virt_to_phys(init_maps);
init_maps->virtual = vectors_base();
init_maps->length = PAGE_SIZE;
init_maps->domain = DOMAIN_USER;
init_maps->prot_read = 0;
init_maps->prot_write = 0;
init_maps->cacheable = 1;
init_maps->bufferable = 0;

create_mapping(init_maps);

中断向量表的虚地址init_maps,是用alloc_bootmem_low_pages分配的,
通常是在c000 8000前面的某一页, vectors_base()是个宏,arm规定中断
向量表的地址只能是0或ffff0000,在cp15中设置。所以上述代码映射一页到
0或ffff0000,下面我们还会看到,中断处理程序中的汇编部分也被拷贝到
这一页中。
阅读(1264) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~