eslab2013-ustc / RTFSC_Linux_Kernel

USTC 嵌入式系统实验室2013级研究生阅读内核记录
64 stars 55 forks source link

临时内核页表的建立过程 #11

Open hangc0276 opened 10 years ago

hangc0276 commented 10 years ago

Motivation:当内核被解压到线性地址0x100000后,为了继续启动内核,即启动内核的第一个swapper进程,内核需要建立一张临时页表供其使用。

当内核从16位的实模式进入保护模式(通过在汇编代码中的setup函数中设置linux的cr0寄存器的PE位),内核要创建一个有限的地址空间,容纳内核的代码段、数据段、初始页表和用于存放动态数据结构的128KB大小的空间。程序设计者假定,内核使用的代码段、数据段、临时页表和128KB的内存范围可以全部存放到RAM的前8MB的空间内。于是我们需要做的工作就是建立一个页表映射使得可以对内存的前8MB的物理地址进行寻址。

进程的线性地址空间可分为两部分: 0x00000000~0xbfffffff (0~3G),无论进程运行于用户态还是内核态都可以访问的地址空间 0xc0000000~0xffffffff (3G~4G),只有处于内核态的进程才能访问该地址空间。

为了保证在实模式和保护模式下,进程都可以对这8MB的空间进行寻址,内核必须建立两个映射。将0x00000000~0x007fffff的线性地址和0xc0000000~0xc07fffff的线性地址都映射到0x00000000~的物理地址空间中。此时便可以通过与物理地址相同的线性地址或是通过从0xc0000000开始的8MB线性地址对RAM的前8MB进行寻址。

建立内核临时页表 采用二级页表的形式建立临时映射。由于要映射8MB的内核空间,一个页表有1024项,每一项页表对应一个4KB的页,8MB=2_1024_4KB,故需要两个全局页目录项和两张页表。

建立页全局目录项 一张页全局目录表有1024项,但我们只需要寻址8MB的地址空间,所以只需要2个页全局目录项即可。由于我们要同时保证进程处于用户空间和内核空间下都能对这8MB的内存空间进行寻址,所有我们需要4个页全局目录项分别寻址8MB的用户空间和8MB的内核空间。

要建立页全局目录表,首先要知道页全局目录表存放的物理地址,其中变量swapper_pg_dir存放了页全局目录的线性地址。我们可以通过swapper_pg_dir - __PAGE_OFFSET计算可以获得的swapper_pg_dir的物理地址(其中__PAGE_OFFSET = 0xc0000000是内核线性空间的起始地址)

知道了临时页全局目录的地址之后,下面便可以初始化临时页全局目录:

ENTRY(swapper_pg_dir)
    .fill 1024,4,0

这两行汇编代码执行了页全局目录表的初始化,它的意思是:从swapper_pg_dir开始,填充1024项,每一项为4字节,值为0,正好是4KB的页面

_下面要对页全局目录进一步初始化,从而保证对页表的映射_ 为了保证进程处于用户态和内核态下都能对8MB的物理地址进行寻址,内核需要填充全局页目录表(swapper_pg_dir)的第0、1、0x300(十进制768)、0x301(十进制769)(768和769是通过计算内核线性地址空间对应的页目录偏移量获得的,具体的计算方法请参见补充内容)项。前两项是给用户空间线性地址映射,后两项是给内核空间线性地址映射。内核会将swapper_gp_dir的第0项和第768项字段设置为pg0的物理地址(pg0中存放第一张页表的地址),第1项和第769项字段设置为pg1的物理地址(pg0+4K)。

页表的建立 要映射8MB的物理地址,8MB = 2_1024_4KB,所以要对应2048个页框,建立页表只需要从0x00000000开始,每一项间隔4KB,即: 0x0000, 0x1000, 0x2000, .... ... 0x7ff000

内核中实现页表建立的代码如下:

page_pde_offset = (__PAGE_OFFSET >> 20);

    movl $pa(pg0), %edi
    movl $pa(swapper_pg_dir), %edx
    movl $PTE_ATTR, %eax
10:
    leal PDE_ATTR(%edi),%ecx        /* Create PDE entry */
    movl %ecx,(%edx)            /* Store identity PDE entry */
    movl %ecx,page_pde_offset(%edx)     /* Store kernel PDE entry */
    addl $4,%edx
    movl $1024, %ecx
11:
    stosl
    addl $0x1000,%eax
    loop 11b
    /*
     * End condition: we must map up to and including INIT_MAP_BEYOND_END
     * bytes beyond the end of our own page tables; the +0x007 is
     * the attribute bits
     */
    leal (INIT_MAP_BEYOND_END+PTE_ATTR)(%edi),%ebp
    cmpl %ebp,%eax
    jb 10b
    movl %edi,pa(init_pg_tables_end)

    /* Do early initialization of the fixmap area */
    movl $pa(swapper_pg_fixmap)+PDE_ATTR,%eax
    movl %eax,pa(swapper_pg_dir+0xffc)

大致意思是从0开始,把连续的线性地址映射到物理地址。0x007正好表示PRESENT+RW+USER(在内存中,可读写,用户页面,这样在用户态和内核 态都可读写)。由于每个页表项有32位,但其实只需保存物理地址的高20位 就够了,所以剩下的低12位可以用来表示页的属性。

结束条件:从代码中可知,当映射到当前所操作的页表项往下INIT_MAP_BEYOND_END(128K)处映射结束。

建立页表的示意图如下:

页全局目录表和页表建成之后如下图所示:

开启页面映射之后就可以引用内核的变量,但是还不能启动start_kernel,因为要启动swapper进程还需要设置内核堆栈

    lss stack_start,%esp
    //然后设置中断向量表
    call setup_idt

    检查CPU类型

    载入gdt(原来的gdt是临时的)和ldt

    lgdt cpu_gdt_descr

    lidt idt_descr

    最后,调用start_kernel

    call start_kernel

    至此启动start_kernel函数

补充:页全局目录表映射图

    |------------|
    |            | 0x3FF (1023)
    |            |
    |            |                        <----> 对应128MB虚拟内存
    |            |
    |            | 0x3EO (992)
    |------------|----------------------------
    |            |
    |            |
    |            |
    |            |
    |            |                        <----> 对应896MB虚拟内存
    |            |
    |            |
    |            | 0x301 (769) pg1 --> 5~8MB
    |            | 0x300 (768) pg0 --> 1~4MB
    |------------|----------------------------
    |            |
    |            |
    |            |
    |            |
    |            |
    |            |
    |            |
    |     .      |
    |     .      |
    |     .      |
    |     .      |                        <----> 对应3GB虚拟内存
    |            |
    |            |
    |            |
    |            |
    |            |
    |            |
    |            | 1    pg1 --> 5~8MB
    |            | 0    pg0 --> 1~4MB
+-->|------------|
|

1023 - 768 = 255 256个主内核页全局目录项,一个目录项可以管理4MB物理内存,所以理论上256个目录项可以管理1GB物理内存(映射1GB的线性地址 -- 1GB的线性地址可以映射大于1GB的物理地址,这就需要借助128MB特殊映射);

1023 - 992 = 31 256个主内核页全局目录项中的最高32个目录项(128MB)用作特殊映射,所以主内核全局目录项可以直接映射的线性地址空间为1GB - 128MB = 896MB;

页全局目录放在swapper_pg_dir变量中:pgd_t swapper_pg_dir[1024];

线性地址:0xC000 0000的高20位为1100000000(2)=768