2007/0910-2007/0914工作周记

来源:互联网 发布:php 7.0.12 安装posix 编辑:程序博客网 时间:2024/05/18 03:07

    经过上个星期的试验,我们觉得有必要关注Leon3MMU

    硬件背景:

    由于Leon3Sparc架构,所以我查看了Sparc V8手册。SparcMMU大体结构是将32位虚拟地址分为4个段。(这里贴图不能直接贴,好麻烦!)

    第一,二层为PTD:PTP为下一级页表的物理地址

    PTD:(Page Table Descriptor)

    |_________PTP_______________|__ET_|

    31                             2 1 0

    第三层为PTE:PPN36位物理地址的高24(Leon3PPN20)

    PTE:(Page Table Entry )

    |_________PPN___________|_________|

    31                       8 7        0

    最后的当然就是偏移地址了。

    具体的寻址过程和x86几乎一样,不过x86cr3就变为一个特定的上下文寄存器,这个寄存器标识当前进程。

    具体过程:

    上下文寄存器从上下文表中获得当前进程的标识Root PointerRoot Pointer指向当前进程的全局页表的物理地址PA,然后从PTD获得偏移量PTPPA+PTP就是第二层页表的物理地址,以此类推,直到第三层。从指向的PTE中获得PPN也就是物理地址的前20位,然后再加上OFFSET12位,最后得到相应的物理地址。

    看看头文件的定义是否和手册说的一样。

../include/asm-sparc/pgtsrmmu.h

/* Number of contexts is implementation-dependent; 64k is the most we support */

#define SRMMU_MAX_CONTEXTS 65536


/* PMD_SHIFT determines the size of the area a second-level page table entry can map */

#define SRMMU_REAL_PMD_SHIFT 18

#define SRMMU_REAL_PMD_SIZE (1UL << SRMMU_REAL_PMD_SHIFT)

#define SRMMU_REAL_PMD_MASK (~(SRMMU_REAL_PMD_SIZE-1))

#define SRMMU_REAL_PMD_ALIGN(__addr) (((__addr)+SRMMU_REAL_PMD_SIZE-1)&SRMMU_REAL_PMD_MASK)


/* PGDIR_SHIFT determines what a third-level page table entry can map */

#define SRMMU_PGDIR_SHIFT 24

#define SRMMU_PGDIR_SIZE (1UL << SRMMU_PGDIR_SHIFT)

#define SRMMU_PGDIR_MASK (~(SRMMU_PGDIR_SIZE-1))

#define SRMMU_PGDIR_ALIGN(addr) (((addr)+SRMMU_PGDIR_SIZE-1)&SRMMU_PGDIR_MASK)


#define SRMMU_REAL_PTRS_PER_PTE 64

#define SRMMU_REAL_PTRS_PER_PMD 64

#define SRMMU_PTRS_PER_PGD 256

    单从上面的定义,可以看出定义和手册说明是一样的。但是现实往往要想象的残酷,在头文件中作者(David Miller)提到了一个问题。原文是:

/*

* To support pagetables in highmem, Linux introduces APIs which

* return struct page* and generally manipulate page tables when

* they are not mapped into kernel space. Our hardware page tables

* are smaller than pages. We lump hardware tabes into big,

* page sized software tables.

*

* PMD_SHIFT determines the size of the area a second-level page

* table entry can map, and our pmd_t is 16 times larger than

* normal. The values which were once defined here are now

* generic for 4c and srmmu, so they're found in pgtable.h.

*/

    大概的意思就是为了映射到高位内存,内存的页表大小必须作出调整。但是很遗憾他没有继续说明如何调整,我也找不到其他的文档说明。这也直接导致了没办法看懂源代码的一些页表操作。

    后来也发现了另外一个问题,Linux2.6.11开始,页表映射统一形式为四层:

pgd->pud->pmd->ptd。这真的让我很诧异,因为从头文件的定义怎样都看不出是分四层,而我们的内核是2.6.21.1。这是否说明这个内核Sparc部分处理MMU是有一定问题的?那么多问题,看来也只能是阅读源代码来解决了。

    以我目前的水平,读内核源代码带来的痛苦远远比快乐要多。错综复杂的宏定义、言简意赅的内嵌汇编、微妙而陌生的Sparc架构都让我沮丧不已,放弃的念头曾经象春天的野草一样在我大脑疯长。不过老子说得好:合抱之木,生于毫末;九层之台,起于累土;千里之行,始于足下。唉,以此自勉吧。

    我们主要关注的文件是../arch/sparc/srmmu.c。首先要认识一些类型转换宏(特别是:__pte__pgd __pgprot)把一个无符号整数转换成所需的类型。这里值得注意的是__pmd被注释了。

../include/asm/page.h

typedef struct { unsigned long pte; } pte_t;

typedef struct { unsigned long iopte; } iopte_t;

typedef struct { unsigned long pmdv[16]; } pmd_t;

typedef struct { unsigned long pgd; } pgd_t;

typedef struct { unsigned long ctxd; } ctxd_t;

typedef struct { unsigned long pgprot; } pgprot_t;

typedef struct { unsigned long iopgprot; } iopgprot_t;



#define __pte(x) ((pte_t) { (x) } )

#define __iopte(x) ((iopte_t) { (x) } )

/* #define __pmd(x) ((pmd_t) { (x) } ) */ /* XXX procedure with loop */

#define __pgd(x) ((pgd_t) { (x) } )

#define __ctxd(x) ((ctxd_t) { (x) } )

#define __pgprot(x) ((pgprot_t) { (x) } )

#define __iopgprot(x) ((iopgprot_t) { (x) } )

    另外的类型转换宏执行和以上宏相反的转换,即把上面提到的特殊的类型转换成一个无符号整数。

#define pte_val(x) ((x).pte)

#define iopte_val(x) ((x).iopte)

#define pmd_val(x) ((x).pmdv[0])

#define pgd_val(x) ((x).pgd)

#define ctxd_val(x) ((x).ctxd)

#define pgprot_val(x) ((x).pgprot)

#define iopgprot_val(x) ((x).iopgprot)

    下面还有很多操作页表的宏和函数。不过这些函数用得并不多,但是理解它们对我们理解Sparc架构很有好处。

../arch/sparc/mm/srmmu.c

    向一个pte页表写入指定的值。

static inline void srmmu_set_pte(pte_t *ptep, pte_t pteval)

{

    srmmu_swap((unsigned long *)ptep, pte_val(pteval));

}


static inline unsigned long srmmu_swap(unsigned long *addr, unsigned long value)

{

    __asm__ __volatile__(

        "swap [%2], %0" :

        "=&r" (value) :

        "0" (value), "r" (addr));

        return value;

}

    汇编代码的意思也很简单,执行的语句就是Sparcswap汇编指令,%2就是指addr%0就是value。语句中的“0”也可以用r互换。

如果pte页表为0,返回1,否则为0

static inline int srmmu_pte_none(pte_t pte)

{

    return !(pte_val(pte) & 0xFFFFFFF);

}

    利用标志位,检测页面是否pte页面,是返回1,否则为0

static inline int srmmu_pte_present(pte_t pte)

{

    return ((pte_val(pte) & SRMMU_ET_MASK) == SRMMU_ET_PTE);

}

    检测pte页指向的物理页是否有执行和读的权限。

static inline int srmmu_pte_read(pte_t pte)

{

    return !(pte_val(pte) & SRMMU_NOREAD);

}

    清除相应pte页表的一个表项,由此禁止进程使用由该页表 项映射的线性地址。

static inline void srmmu_pte_clear(pte_t *ptep)

{

    srmmu_set_pte(ptep, __pte(0));

}

    如果pmd表为0,返回1,否则为0

static inline int srmmu_pmd_none(pmd_t pmd)

{

    return !(pmd_val(pmd) & 0xFFFFFFF);

}

    利用标志位,检测页面是否pmd页面,是返回0,否则为1(yes is bad)

static inline int srmmu_pmd_bad(pmd_t pmd)

{

    return (pmd_val(pmd) & SRMMU_ET_MASK) != SRMMU_ET_PTD; }

    利用标志位,检测页面是否pmd页面,是返回1,否则为0

static inline int srmmu_pmd_present(pmd_t pmd)

{

    return ((pmd_val(pmd) & SRMMU_ET_MASK) == SRMMU_ET_PTD);

}

    清除相应pmd页表的一个表项,由此禁止进程使用由该页表 项映射的线性地址。只是代码的for循环实现的有点莫名其妙。强制转换为pte_t类型是为了重用srmmu_set_pte 函数。

static inline void srmmu_pmd_clear(pmd_t *pmdp)

{

    int i;

    for (i = 0; i < PTRS_PER_PTE/SRMMU_REAL_PTRS_PER_PTE; i++)

    srmmu_set_pte((pte_t *)&pmdp->pmdv[i], __pte(0));

}

pgtable.h:

#define PTRS_PER_PTE 1024

#define PTE_SIZE (PTRS_PER_PTE*4)

pgtsrmmu.h:

typedef struct { unsigned long pmdv[16]; } pmd_t;(page.h)

#define SRMMU_REAL_PTRS_PER_PTE 64

#define SRMMU_REAL_PTE_TABLE_SIZE (SRMMU_REAL_PTRS_PER_PTE*4)


    如果pgd表为0,返回1,否则为0

static inline int srmmu_pgd_none(pgd_t pgd)

{

    return !(pgd_val(pgd) & 0xFFFFFFF);

}

    利用标志位,检测页面是否pgd页面,是返回0,否则为1(yes is bad)

static inline int srmmu_pgd_bad(pgd_t pgd)

{

    return (pgd_val(pgd) & SRMMU_ET_MASK) != SRMMU_ET_PTD; }

    利用标志位,检测页面是否pmd页面,是返回1,否则为0

static inline int srmmu_pgd_present(pgd_t pgd)

{

    return ((pgd_val(pgd) & SRMMU_ET_MASK) == SRMMU_ET_PTD);

}

    清除相应pgd页表的一个表项,由此禁止进程使用由该页表 项映射的线性地址。

static inline void srmmu_pgd_clear(pgd_t * pgdp)

{

    srmmu_set_pte((pte_t *)pgdp, __pte(0));

}

page.h:

typedef struct { unsigned long pgd; } pgd_t;

    清除pte的读写标志。

static inline pte_t srmmu_pte_wrprotect(pte_t pte)

{

    return __pte(pte_val(pte) & ~SRMMU_WRITE);

}

    清除ptedirty位。

static inline pte_t srmmu_pte_mkclean(pte_t pte)

{

    return __pte(pte_val(pte) & ~SRMMU_DIRTY);

}

    清除pteReferenced位,意味着把此页标志为未访问。

static inline pte_t srmmu_pte_mkold(pte_t pte)

{

    return __pte(pte_val(pte) & ~SRMMU_REF);

}

    设置pte的读写位。

static inline pte_t srmmu_pte_mkwrite(pte_t pte)

{

    return __pte(pte_val(pte) | SRMMU_WRITE);

}

    设置ptedirty位。

static inline pte_t srmmu_pte_mkdirty(pte_t pte)

{

    return __pte(pte_val(pte) | SRMMU_DIRTY);

}

    设置pteReferenced位,意味着把此页标志为访问过。

static inline pte_t srmmu_pte_mkyoung(pte_t pte)

{

    return __pte(pte_val(pte) | SRMMU_REF);

}



原创粉丝点击