Linux 内核创建页表,Linux 内核临时页表的创建

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)。

0818b9ca8b590ca3270a3433284dd417.png

页表的建立

要映射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)处映射结束。

建立页表的示意图如下:

0818b9ca8b590ca3270a3433284dd417.png

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

0818b9ca8b590ca3270a3433284dd417.png

开启页面映射之后就可以引用内核的变量,但是还不能启动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

你可能感兴趣的:(Linux,内核创建页表)