HardBirch

linux内核分析-存储器管理-理论篇

时间:10-04-02 栏目:系统技术篇 作者:鲁智森也有文化 评论:0 点击: 2,014 次

存储器寻址
-----------------------------------------------------------------------------------------------------------------------------
存储器地址
80x86微处理器三种不同的地址
逻辑地址:包含在机器语言指令中用来指定一个操作数或一条指令的地址,每个逻辑地址都有一个段和偏移量组成,逻辑地址是intel为了兼容老式的段式内存管理方式而保留下来的,逻辑地址并不是真的地址
线性地址:是一个32位无符号整数可以表达高达4GB的地址,从0x00000000到0xffffffff
物理地址:用于存储器芯片级存储器单元寻址,32位无符号整数
CPU控制单元通过分段单元把一个逻辑地址转换为线性地址,再有分页单元把线性地址转换为物理地址如下图:
           ************              ************
逻辑地址-->* 分段单元 *-->线性地址-->* 分页单元 *-->物理地址
           ************              ************
在多处理器系统中,所有的CPU都共享同一内存,为了防止内存中的数据被意外更改,在硬件电路中都有一个存储器仲裁器
#############################################################################################################################
硬件中的分段
从80386模型开始,intel微处理器以两种方式执行地址转换:实模式,保护模式
实模式:寻址采用8086的16位段和偏移量,最大寻址1MB,最大分段64KB.可以使用32位指令,x86的CPU用作高速8086,DOS系统就是运行在CPU的实模式下的系统.
保护模式:采用32位的段和偏移量,最大寻址4GB,最大分段4GB.

段寄存器
一个逻辑地址由两部分组成:段标识符,段内偏移量
段标识符是一个16位长的字段,称为段选择符,偏移量是一个32位长的字段
段寄存器的唯一目的是存放段选择符,这些段寄存器为:cs,ss,ds,es,fs,gs,每个段寄存器的目的是不同的
有3个段寄存器有3个专门用途:
cs    代码段寄存器,指向包含程序指令的段
ss    栈段寄存器,指向包含当前程序栈的段
ds    数据段寄存器,指向包含静态数据或者外部数据的段
其中cs寄存器还有一个很重要的功能:它含有一个两位的值段,用来表明CPU当前的特权级.0代表最高优先级,3表示最低优先级.linux只用0和3,分别称为内核态和用户态

段描述符
每个段由一个8直接的段描述符表示,描述了段的特征.段描述符放在全局描述符表GDT或者局部描述符表中LDT
GDT在主存中的地址存放在gdtr寄存器中,当前正在使用的LDT放在ldtr寄存器中
每个段描述符组成:
1.    32为的Base字段,含有段的第一个字节的线性地址
2.    粒度标志G.如果该位清0,则段大小以字节为单位.否则以4096字节的倍数计
3.    20为的Limit字段指定段的长度.当G为0时,段的大小在1字节到1MB之间;否则,段的大小在4KB到4GB之间
4.    系统标志S.如果它被清0,则这是一个系统段,用于存储内核数据结构,否则它是一个普通的代码段或数据段
5.    4位的Type字段,描述了段的类型和它访问的权限
        代码段描述符:表示这个段描述符代表一个代码段,可以放在GDT和LDT中,S标志为1
        数据段描述符:表示这个段描述符代表一个数据段,可以放在GDT和LDT中,S表在为1,栈上通过一般的数据段实现的
        任务段状态描述符:表示这个段描述符代表一个任务状态段TSS,这个段用于保存处理器寄存器的内容.只能出现在GDT中,根据相应的进程是否在CPU上运行,其中Type的值分别为11或9.S标志为0
        局部描述符表描述符:这个段描述符代表一个LDT段,只出现在GDT中,Type字段为2,S标志为0
6.    2位描述符特权级DPL字段,用于限制对这个段的访问
7.    Segment-Present标志等于0表示段当前不在主存中,linux总是为0
8.    一个名为D或B的额外标志,取决于是代码段还是数据段
9.    第53位是保留位,总是设置为0
10.    AVL标志,linux不用

快速访问段描述符
80x86处理器提供附加的非编程寄存器共6个可编程段寄存器使用,用以加速逻辑地址到线性地址的转换,当一个段选择符被装入段寄存器时,相应的段描述符就由内存装入到对应的非编程CPU寄存器
每个段选择符包含字段:
1.    13位的索引,指定了放在GDT或LDT中相应段描述符项
2.    TI指明了段描述符是在GDT中TI=0或是LDT中TI=1
3.    两位请求者特权级RPL字段,当相应的段选择符装入到cs寄存器中时,正好是CPU的当前特权级

分段单元
分段单元执行的操作:
1.    先检查段选择符的TI字段,以决定段描述符保存在那一个描述符表中
2.    从段选择符的索引字段计算段描述符的地址,索引字段的值乘以8,再与gdtr或ldtr寄存器中的内容相加
3.    把逻辑地址的偏移量与段描述符基地址字段的值相加就得到了线性地址
#############################################################################################################################
linux中的分段
分页方式在linux中常用:
1.    当所有进程使用相同的段寄存器值时,存储器管理变得简单,能共享同样的一组线性地址
2.    linux的设计目标之一就是可以把它移植到绝大多数流行的处理器平台上
2.4版的linux只有在80x86结构下才需要使用分段
linux中用到的段
1.    内核代码段,在GDT中相应的段描述符的各子字段有以下值:
    Base=0x00000000
    limit=0xfffff
    G=1,段的大小,段位以页计
    S=1,正常的代码段或数据段
    Type=0xa,可读并可执行的代码段
    DPL=0,内核态
    D/B=1,表示偏移量为32位
    相应的段选择符由__KERNEL_CS宏定义
2.    内核数据段
    Base=0x00000000
    limit=0xfffff
    G=1
    S=1
    Type=2,可读写的数据段
    DPL=2
    D/B=1
    段选择符由__KERNEL_DS宏定义
3.    用户态下所有进程所共享的用户代码段
    Base=0x00000000
    limit=0xfffff
    G=1
    S=1
    Type=0xa
    DPL=3,用户态
    D/B=1
    段选择符由__USER_CS宏定义
4.    用户态下由所有进程所共享的用户数据段
    Base=0x00000000
    linux=0xfffff
    G=1
    S=1
    Type=2,可读写的数据段
    DPL=3,用户态
    D/B=1
    段选择符由__USER_DS宏定义
其中这些宏定义在/include/asm-i386/segment.h中
segment.h的源代码:

#ifndef _ASM_SEGMENT_H
#define _ASM_SEGMENT_H

#define __KERNEL_CS    0x10
#define __KERNEL_DS    0x18

#define __USER_CS    0x23
#define __USER_DS    0x2B

#endif

5.    任务状态段:每个处理器都有一个.每个TSS相应的线性地址空间都是内核数据段相应线性地址空间的一个小子集.所有的任务状态段都顺序的存放在init_tss数组中.第n个CPU的TSS描述符的Base字段指向init_tss数组的第n个元素.其中:
    G=0
    limit=0xeb
    Type=9或11
有关于init_tss的源代码在arch/i386/kernel/init_task.c中
init_tasj.c:

#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>

#include <asm/uaccess.h>
#include <asm/pgtable.h>
#include <asm/desc.h>

static struct fs_struct init_fs = INIT_FS;
static struct files_struct init_files = INIT_FILES;
static struct signal_struct init_signals = INIT_SIGNALS;
struct mm_struct init_mm = INIT_MM(init_mm);

/*
 * Initial task structure.
 *
 * We need to make sure that this is 8192-byte aligned due to the
 * way process stacks are handled. This is done by having a special
 * "init_task" linker map entry..
 */
union task_union init_task_union
    __attribute__((__section__(".data.init_task"))) =
        { INIT_TASK(init_task_union.task) };

/*
 * per-CPU TSS segments. Threads are completely 'soft' on Linux,
 * no more per-task TSS's. The TSS size is kept cacheline-aligned
 * so they are allowed to end up in the .data.cacheline_aligned
 * section. Since TSS's are completely CPU-local, we want them
 * on exact cacheline boundaries, to eliminate cacheline ping-pong.
 */
struct tss_struct init_tss[NR_CPUS] __cacheline_aligned = { [0 ... NR_CPUS-1] = INIT_TSS };

其中struct tss_struct init_tss[NR_CPUS] __cacheline_aligned = { [0 ... NR_CPUS-1] = INIT_TSS };就是定义init_tss数组.

6.    一个通常由所有进程共享的缺省LDT段.这个段保存在defaule_ldt变量中.每个处理器都有自己的LDT描述符
#############################################################################################################################
硬件中的分页
分页单元把线性地址转换为物理地址
线性地址被分成以固定长度为单位的组,称为页.页内部连续的线性地址被映射到连续的物理地址中
分页单元把所有的RAM分成固定长度的页框.每个页框包含一个也,一个页框的长度与一个页的长度一致.页框是主存的一部分,因此也是一个存储区域
把线性地址映射到物理地址的数据结构称为页表.页表存放在主存中,并在启用分页单元以前必须由内核对页表进行适当的初始化
在80x86处理器中,通过设置cr0控制寄存器的PG标志启用分页.当PG=0时,线性地址就被解释成物理地址

常用的分页
Intel处理器的分页单元出来4KB的页
32位的线性地址被分成3个字段
目录Directory:最高10位
页表Table:中间10位
偏移量Offset:最低12位
线性地址的转换由两步完成,每一步都基于转换表.第一种转换表成为页目录表,第二种转换表称为页表
正在使用的页目录的物理地址存放在控制寄存器cr3中.线性地址内的Directory字段决定页目录的目录项,目录项指向适当的页表.Table字段依次决定页表中的表项,表项中含有页所在页框的物理地址.Offset字段决定页框内的相对位置.因Offset是12位长,所以每一页含有4096个字节的数据.
      Directory          Table          Offset
    31_______________22,21_______________12,11_______________0
        |             |             |
        |             |             |
        |             |             |
        |             |             |
        |             |             |
        |             |             |
       ***********        ***********         ***********
cr3------->*  页目录 *--------->*  页表   *-------->*    页   *
       ***********        ***********         ***********
页目录和页表项有同样的结构,每项包含的字段:
Present标志:如果为1,所指的页或页表就在主存中;如果为0,就不在主存中
包含页框物理地址最高20位的字段
Accessed标志:每当分页单元对相应页框进行寻址时就设置这个标志.分页单元从来不重置这个标志,而是必须由操作系统重置
Dirty标志:只用于页表项中.每当对一个页框进行写操作时就设置这个标志.分页单元从来不重置这个标志,而是必须由操作系统重置
Read/Write标志:含有页页表的访问权限
User/Supervisor标志:访问页或页表所需的特权级
PCD和PWT标志:控制硬件高速缓存处理页或页表的方式
Page Size标志:只用于页目录项.如果为1,页目录项指的是2MB或4MB的页框
Global标志:只用于页表项,用来防止常用页从TLB高速缓存中被刷出去,只有在cr4寄存器的页全局启用标志为1时,这个标志才起作用
#############################################################################################################################
扩展分页
通过设置页目录的Page Size标志为1来启用扩展分页功能,在这种情况下,分页的那元把32位的线性地址分成两个字段:
Directory:最高10位
Offset:其余22位
扩展分页和正常分页的页目录项基本相同,但又两点不同:
1.    Page Size为1
2.    20位物理地址字段只有最高10位是有意义的
通过设置cr4处理器寄存器的PSE标志能使扩展分页与常规分页共存
扩展分页用于把大段连续的线性地址转换成相应的物理地址,在这些情况下,内核可以不用中间表进行地址转换,从而节省内存并保持TLB项

硬件保护方案
与页和与表相关的特权级有两个:
1.    User/Supervisor=0,只有当处理器处于内核态时,才能对页寻址
2.    User/Supervisor=1,处理器在任何状态下都能对页寻址
与段的三种访问权限读,写,执行不同,页的访问权限只有两种读,写
如果Read/Write=0,则页是可读的,否则是可读写的

三级分页
1.    页框的大小为8K,Offset字段是13位
2.    只使用地址的最低43位
3.    引入三级页表,以便剩余的30位地址被分成三个10位字段.因此页表包含1024个表项

物理地址扩展(PAE)机制
处理器所支持的RAM容量受链接到地址总线上的地址管脚数限制.从80386到Pentium处理器使用32位物理地址.理论上可以安装4GB内存;而实际上,由于用户态进程线性地址空间的需要,内核不能直接对1GB以上的RAM进行寻址.
从Pentium Pro处理器开始,Intel引入了物理地址扩展机制
通过设置cr4控制寄存器中的物理地址扩展标志激活PAE
Intel为了支持PAE已经改变了分页机制
1.    64GB的RAM被分为2^24个页框,页表项的物理地址字段从2.位扩展到了24位
2.    引入一个名为页目录指针表的页表新级别,由4个64位表项组成
3.    cr3控制寄存器包含一个27位的页目录指针表PDPT基地址字段
4.    当把线性地址映射到4KB的页时,32位线性地址按下列方式解释
        cr3:指向一个PDPT
        位31-30:指向PDPT中4个项中的一个
        位29-21:指向页目录中512个项中的一个
        位20-12:指向页表中512项中的一个
        位11-0:4KB页中的偏移量
5.    当线性地址映射到2MB的页时,32位线性地址按下列方式解释
        cr3:指向一个PDPT
        位31-30:指向PDPT中的4个项中的一个
        位29-21:指向页目录中512个项中的一个
        位20-0:2MB页中的偏移量
#############################################################################################################################
硬件高速缓存
为了解决CPU和RAM之间的速度不匹配,引入了硬件高速缓存
硬件高速缓存基于局部性原理
局部性原理:程序的地址访问流有很强的时序相关性,未来的访问模式与最近已发生的访问模式相似。根据这一局部性原理,把主存储器中访问概率最高的内容存放在Cache 中,当CPU需要读取数据时就首先在Cache中查找是否有所需内容,如果有则直接从Cache中读取;若没有再从主存中读取该数据,然后同时送往CPU 和Cache
80x86体系结构中引入一个叫行(line)的新单位.行有几十个连续的字节组成,它们以突发模式在慢速DRAM和快速的用来实现高速缓存的片上静态RAM(SRAM)之间传送
高速缓存在被分为行的子集
大多数高速缓存是N路关联的,主存中的任意一个行可以存放在高速缓存N行中的任意一行中
高速缓存单元插在分页单元和主存储器之间.它包含一个硬件高速缓存存储器和一个高速缓存控制器.高速缓存存储器存放存储器中真正的行.高速缓存控制器存放一个入口数组,每个入口对应高速缓存存储器中的一个行.每个入口有一个标签和描述高速缓存行状态的几个标志.这个标签由一些为组成,这些为让高速缓存控制器能够辨识由这个行当前所映射的内存单元.这种内存物理地址通常分为三组:最高几位对应标签,中间几位对应高速缓存速度控制器的子集索引,最低几位对应行内偏移量
当访问一个RAM存储单元时,CPU从物理地址中分离出子集的索引号并把子集中所有行的标签与物理地址的高几位比较.如果发现某一个好的标签与这个物理地址的高位含有的标签相同,则CPU命中一个高速缓存;否则,高速缓存没有命中
当高速缓存没有命中时,高速缓存行被回写到存储器中
多处理器系统的每个处理器有有一个单独的硬件高速缓存,因此需要额外的硬件电路保持高速缓存内容的同步
现在跟新变得更耗时:只要一个CPU修改了它的硬件高速缓存,它就必须检查同样的数据是否包含在其他的硬件高速缓存中;如果是,它必须通知其它的CPU用适当的值对其更新.这种活动被叫做高速缓存侦听,但这一切都在硬件级处理,内核无需参与
处理器的cr0寄存器的CD标志位用来启用或禁用高速缓存电路.这个寄存器中的NW标志指明高速缓存是使用那个还是回写

转换后援缓冲器(TLB)
除了通用硬件高速缓存之外,80x86处理器还包含了另外一个名为转换后援缓冲器的高速缓存用于加快线性地址的转换
当一个线性地址被第一次使用时,通过慢速访问RAM中的页表计算出相应的物理地址.同时,物理地址被存放在一个TLB表项中,以便以后对同一个线性地址的引用可以快速地得到转换
在多处理器系统中每个CPU都自己的TLB,这叫做该CPU的本地TLB
当cr3控制寄存器被修改时,硬件自动使本地TLB中的所有项都无效
#############################################################################################################################
linux中的分页
linux采用三级分页模型
1.    页全局目录
2.    页中间目录
3.    页表
linux的进程处理很大程度上依赖于分页
1.    给每一个进程分配一块不同的物理地址空间,有效的避免了错误寻址
2.    区别页和页框之不同.这允许同一个页存放在某个页框中,然后保存到磁盘上,以后重新装入这一页时又被装在不同的页框中

线性地址字段
下列宏简化了页表处理
PAGE_SHIFT:指定Offset字段的位数;当用于80x86处理器时,它产生的值为12.由于页内所有地址都必须符合Offset字段,因此80x86系统哦的页的大小是2^12=4096字节.这个宏由PAGE_SIZE使用以返回页的大小.最后,PAGE_MASK宏产生的值为0xfffff000,用以屏蔽Offset字段的所有位
PMD_SHIFT:指定线性地址的Offset字段和Table字段的总位数.PMD_SIZE宏用于计算由页中间目录的一个单独表项所映射的区域大小,也就是一个页表的大小.PMD_MASK宏用于屏蔽Offset字段与Table字段的所有位.当PAE被禁用时,PMD_SHIFT产生的值为22(来自Offset的12位加上来自Table的20位),PMD_SIZE产生的值为4MB,PMD_MASK的值为0xffc00000.当PAE被启动时,PMD_SHIFT的值为21(Offset的12位和Table的9位),PMD_SIZE的值为2MB,PMD_MASK的值为0xffe00000
PGDIR_SHIFT:确定页全局目录项能映射的区域大小的对数.PGDIR_SIZE宏用于计算页全局目录中一个单独表项所能映射区域的大小.PGDIR_MASK宏用于屏蔽Offset,Table及Middle Dir字段的所有位.当PAE被禁用时,PGDIR_SHIFT产生的值为22,PGDIR_SIZE产生的值为4MB,PGDIR_MASK产生的值为0xffc00000.当PAE被启动时,PGDIR_SHIFT产生的值为30(12位Offset加9位Table再加9为的Middle Dir),PGDIR_SIZE产生的字为1GB,PGDIR_MASK产生的值为0xc0000000
PTRS_PER_PTE,PTRS_PER_PMD和PTRS_PER_PGD:用于计算页表,页中间目录和页全局目录表项个数,当PAE内禁止时,它们产生的值分别为1024,1,1024,当PAE被启用时,产生的值分别为512,512,4
与此部分有关的源代码被定义在include/asm-i386/page.h,include/asm-i386/pgtable.h中
page.h头文件,定义了PAGE_SHIFT,PAGE_SIZE,PAGE_MASK:
-----------------------------------------------------------------------------------------------------------------------------
#ifndef _I386_PAGE_H
#define _I386_PAGE_H

/* PAGE_SHIFT determines the page size */
#define PAGE_SHIFT    12/*注意,此行定义了PAGE_SHIFT*/
#define PAGE_SIZE    (1UL << PAGE_SHIFT)/*注意,此行定义了PAGE_SIZE*/
#define PAGE_MASK    (~(PAGE_SIZE-1))/*注意,此行定义了PAGE_MASK*/

#ifdef __KERNEL__
#ifndef __ASSEMBLY__

#include <linux/config.h>

#ifdef CONFIG_X86_USE_3DNOW

#include <asm/mmx.h>

#define clear_page(page)    mmx_clear_page((void *)(page))
#define copy_page(to,from)    mmx_copy_page(to,from)

#else

/*
 *    On older X86 processors its not a win to use MMX here it seems.
 *    Maybe the K6-III ?
 */
 
#define clear_page(page)    memset((void *)(page), 0, PAGE_SIZE)
#define copy_page(to,from)    memcpy((void *)(to), (void *)(from), PAGE_SIZE)

#endif

#define clear_user_page(page, vaddr)    clear_page(page)
#define copy_user_page(to, from, vaddr)    copy_page(to, from)

/*
 * These are used to make use of C type-checking..
 */
#if CONFIG_X86_PAE
typedef struct { unsigned long pte_low, pte_high; } pte_t;
typedef struct { unsigned long long pmd; } pmd_t;
typedef struct { unsigned long long pgd; } pgd_t;
#define pte_val(x)    ((x).pte_low | ((unsigned long long)(x).pte_high << 32))
#else
typedef struct { unsigned long pte_low; } pte_t;
typedef struct { unsigned long pmd; } pmd_t;
typedef struct { unsigned long pgd; } pgd_t;
#define pte_val(x)    ((x).pte_low)
#endif
#define PTE_MASK    PAGE_MASK

typedef struct { unsigned long pgprot; } pgprot_t;

#define pmd_val(x)    ((x).pmd)
#define pgd_val(x)    ((x).pgd)
#define pgprot_val(x)    ((x).pgprot)

#define __pte(x) ((pte_t) { (x) } )
#define __pmd(x) ((pmd_t) { (x) } )
#define __pgd(x) ((pgd_t) { (x) } )
#define __pgprot(x)    ((pgprot_t) { (x) } )

#endif /* !__ASSEMBLY__ */

/* to align the pointer to the (next) page boundary */
#define PAGE_ALIGN(addr)    (((addr)+PAGE_SIZE-1)&PAGE_MASK)

/*
 * This handles the memory map.. We could make this a config
 * option, but too many people screw it up, and too few need
 * it.
 *
 * A __PAGE_OFFSET of 0xC0000000 means that the kernel has
 * a virtual address space of one gigabyte, which limits the
 * amount of physical memory you can use to about 950MB.
 *
 * If you want more physical memory than this then see the CONFIG_HIGHMEM4G
 * and CONFIG_HIGHMEM64G options in the kernel configuration.
 */

#define __PAGE_OFFSET        (0xC0000000)

/*
 * This much address space is reserved for vmalloc() and iomap()
 * as well as fixmap mappings.
 */
#define __VMALLOC_RESERVE    (128 << 20)

#ifndef __ASSEMBLY__

/*
 * Tell the user there is some problem. Beep too, so we can
 * see^H^H^Hhear bugs in early bootup as well!
 */

#ifdef CONFIG_DEBUG_BUGVERBOSE
extern void do_BUG(const char *file, int line);
#define BUG() do {                    /
    do_BUG(__FILE__, __LINE__);            /
    __asm__ __volatile__("ud2");            /
} while (0)
#else
#define BUG() __asm__ __volatile__(".byte 0x0f,0x0b")
#endif

#define PAGE_BUG(page) do { /
    BUG(); /
} while (0)

/* Pure 2^n version of get_order */
static __inline__ int get_order(unsigned long size)
{
    int order;

    size = (size-1) >> (PAGE_SHIFT-1);
    order = -1;
    do {
        size >>= 1;
        order++;
    } while (size);
    return order;
}

#endif /* __ASSEMBLY__ */

#define PAGE_OFFSET        ((unsigned long)__PAGE_OFFSET)
#define VMALLOC_RESERVE        ((unsigned long)__VMALLOC_RESERVE)
#define __MAXMEM        (-__PAGE_OFFSET-__VMALLOC_RESERVE)
#define MAXMEM            ((unsigned long)(-PAGE_OFFSET-VMALLOC_RESERVE))
#define __pa(x)            ((unsigned long)(x)-PAGE_OFFSET)
#define __va(x)            ((void *)((unsigned long)(x)+PAGE_OFFSET))
#define virt_to_page(kaddr)    (mem_map + (__pa(kaddr) >> PAGE_SHIFT))
#define VALID_PAGE(page)    ((page - mem_map) < max_mapnr)

#endif /* __KERNEL__ */

#endif /* _I386_PAGE_H */
-----------------------------------------------------------------------------------------------------------------------------
pgtable.h头文件定义了PMD_SHIFT,PMD_SIZE,PMD_MASK,PGDIR_SHIFT,PGDIR_SIZE,PGDIR_MASK,PTRS_PER_PTE,PTRS_PER_PMD,PTRS_PER_PGD:
-----------------------------------------------------------------------------------------------------------------------------
#ifndef _I386_PGTABLE_H
#define _I386_PGTABLE_H

#include <linux/config.h>

/*
 * The Linux memory management assumes a three-level page table setup. On
 * the i386, we use that, but "fold" the mid level into the top-level page
 * table, so that we physically have the same two-level page table as the
 * i386 mmu expects.
 *
 * This file contains the functions and defines necessary to modify and use
 * the i386 page table tree.
 */
#ifndef __ASSEMBLY__
#include <asm/processor.h>
#include <asm/fixmap.h>
#include <linux/threads.h>

#ifndef _I386_BITOPS_H
#include <asm/bitops.h>
#endif

extern pgd_t swapper_pg_dir[1024];
extern void paging_init(void);

/* Caches aren't brain-dead on the intel. */
#define flush_cache_all()            do { } while (0)
#define flush_cache_mm(mm)            do { } while (0)
#define flush_cache_range(mm, start, end)    do { } while (0)
#define flush_cache_page(vma, vmaddr)        do { } while (0)
#define flush_page_to_ram(page)            do { } while (0)
#define flush_dcache_page(page)            do { } while (0)
#define flush_icache_range(start, end)        do { } while (0)
#define flush_icache_page(vma,pg)        do { } while (0)

#define __flush_tlb()                            /
    do {                                /
        unsigned int tmpreg;                    /
                                    /
        __asm__ __volatile__(                    /
            "movl %%cr3, %0;  # flush TLB /n"        /
            "movl %0, %%cr3;              /n"        /
            : "=r" (tmpreg)                    /
            :: "memory");                    /
    } while (0)

/*
 * Global pages have to be flushed a bit differently. Not a real
 * performance problem because this does not happen often.
 */
#define __flush_tlb_global()                        /
    do {                                /
        unsigned int tmpreg;                    /
                                    /
        __asm__ __volatile__(                    /
            "movl %1, %%cr4;  # turn off PGE     /n"    /
            "movl %%cr3, %0;  # flush TLB        /n"    /
            "movl %0, %%cr3;                     /n"    /
            "movl %2, %%cr4;  # turn PGE back on /n"    /
            : "=&r" (tmpreg)                /
            : "r" (mmu_cr4_features & ~X86_CR4_PGE),    /
              "r" (mmu_cr4_features)            /
            : "memory");                    /
    } while (0)

extern unsigned long pgkern_mask;

/*
 * Do not check the PGE bit unnecesserily if this is a PPro+ kernel.
 */
#ifdef CONFIG_X86_PGE
# define __flush_tlb_all() __flush_tlb_global()
#else
# define __flush_tlb_all()                        /
    do {                                /
        if (cpu_has_pge)                    /
            __flush_tlb_global();                /
        else                            /
            __flush_tlb();                    /
    } while (0)
#endif

#ifndef CONFIG_X86_INVLPG
#define __flush_tlb_one(addr) __flush_tlb()
#else
#define __flush_tlb_one(addr) /
__asm__ __volatile__("invlpg %0": :"m" (*(char *) addr))
#endif

/*
 * ZERO_PAGE is a global shared page that is always zero: used
 * for zero-mapped memory areas etc..
 */
extern unsigned long empty_zero_page[1024];
#define ZERO_PAGE(vaddr) (virt_to_page(empty_zero_page))

#endif /* !__ASSEMBLY__ */

/*
 * The Linux x86 paging architecture is 'compile-time dual-mode', it
 * implements both the traditional 2-level x86 page tables and the
 * newer 3-level PAE-mode page tables.
 */
#ifndef __ASSEMBLY__
#if CONFIG_X86_PAE
# include <asm/pgtable-3level.h>

/*
 * Need to initialise the X86 PAE caches
 */
extern void pgtable_cache_init(void);

#else
# include <asm/pgtable-2level.h>

/*
 * No page table caches to initialise
 */
#define pgtable_cache_init()    do { } while (0)

#endif
#endif

#define __beep() asm("movb $0x3,%al; outb %al,$0x61")

#define PMD_SIZE    (1UL << PMD_SHIFT)
#define PMD_MASK    (~(PMD_SIZE-1))
#define PGDIR_SIZE    (1UL << PGDIR_SHIFT)
#define PGDIR_MASK    (~(PGDIR_SIZE-1))

#define USER_PTRS_PER_PGD    (TASK_SIZE/PGDIR_SIZE)
#define FIRST_USER_PGD_NR    0

#define USER_PGD_PTRS (PAGE_OFFSET >> PGDIR_SHIFT)
#define KERNEL_PGD_PTRS (PTRS_PER_PGD-USER_PGD_PTRS)

#define TWOLEVEL_PGDIR_SHIFT    22
#define BOOT_USER_PGD_PTRS (__PAGE_OFFSET >> TWOLEVEL_PGDIR_SHIFT)
#define BOOT_KERNEL_PGD_PTRS (1024-BOOT_USER_PGD_PTRS)

#ifndef __ASSEMBLY__
/* Just any arbitrary offset to the start of the vmalloc VM area: the
 * current 8MB value just means that there will be a 8MB "hole" after the
 * physical memory until the kernel virtual memory starts.  That means that
 * any out-of-bounds memory accesses will hopefully be caught.
 * The vmalloc() routines leaves a hole of 4kB between each vmalloced
 * area for the same reason. ;)
 */
#define VMALLOC_OFFSET    (8*1024*1024)
#define VMALLOC_START    (((unsigned long) high_memory + 2*VMALLOC_OFFSET-1) & /
                        ~(VMALLOC_OFFSET-1))
#define VMALLOC_VMADDR(x) ((unsigned long)(x))
#if CONFIG_HIGHMEM
# define VMALLOC_END    (PKMAP_BASE-2*PAGE_SIZE)
#else
# define VMALLOC_END    (FIXADDR_START-2*PAGE_SIZE)
#endif

/*
 * The 4MB page is guessing..  Detailed in the infamous "Chapter H"
 * of the Pentium details, but assuming intel did the straightforward
 * thing, this bit set in the page directory entry just means that
 * the page directory entry points directly to a 4MB-aligned block of
 * memory.
 */
#define _PAGE_BIT_PRESENT    0
#define _PAGE_BIT_RW        1
#define _PAGE_BIT_USER        2
#define _PAGE_BIT_PWT        3
#define _PAGE_BIT_PCD        4
#define _PAGE_BIT_ACCESSED    5
#define _PAGE_BIT_DIRTY        6
#define _PAGE_BIT_PSE        7    /* 4 MB (or 2MB) page, Pentium+, if present.. */
#define _PAGE_BIT_GLOBAL    8    /* Global TLB entry PPro+ */

#define _PAGE_PRESENT    0x001
#define _PAGE_RW    0x002
#define _PAGE_USER    0x004
#define _PAGE_PWT    0x008
#define _PAGE_PCD    0x010
#define _PAGE_ACCESSED    0x020
#define _PAGE_DIRTY    0x040
#define _PAGE_PSE    0x080    /* 4 MB (or 2MB) page, Pentium+, if present.. */
#define _PAGE_GLOBAL    0x100    /* Global TLB entry PPro+ */

#define _PAGE_PROTNONE    0x080    /* If not present */

#define _PAGE_TABLE    (_PAGE_PRESENT | _PAGE_RW | _PAGE_USER | _PAGE_ACCESSED | _PAGE_DIRTY)
#define _KERNPG_TABLE    (_PAGE_PRESENT | _PAGE_RW | _PAGE_ACCESSED | _PAGE_DIRTY)
#define _PAGE_CHG_MASK    (PTE_MASK | _PAGE_ACCESSED | _PAGE_DIRTY)

#define PAGE_NONE    __pgprot(_PAGE_PROTNONE | _PAGE_ACCESSED)
#define PAGE_SHARED    __pgprot(_PAGE_PRESENT | _PAGE_RW | _PAGE_USER | _PAGE_ACCESSED)
#define PAGE_COPY    __pgprot(_PAGE_PRESENT | _PAGE_USER | _PAGE_ACCESSED)
#define PAGE_READONLY    __pgprot(_PAGE_PRESENT | _PAGE_USER | _PAGE_ACCESSED)

#define __PAGE_KERNEL /
    (_PAGE_PRESENT | _PAGE_RW | _PAGE_DIRTY | _PAGE_ACCESSED)
#define __PAGE_KERNEL_NOCACHE /
    (_PAGE_PRESENT | _PAGE_RW | _PAGE_DIRTY | _PAGE_PCD | _PAGE_ACCESSED)
#define __PAGE_KERNEL_RO /
    (_PAGE_PRESENT | _PAGE_DIRTY | _PAGE_ACCESSED)

#ifdef CONFIG_X86_PGE
# define MAKE_GLOBAL(x) __pgprot((x) | _PAGE_GLOBAL)
#else
# define MAKE_GLOBAL(x)                        /
    ({                            /
        pgprot_t __ret;                    /
                                /
        if (cpu_has_pge)                /
            __ret = __pgprot((x) | _PAGE_GLOBAL);    /
        else                        /
            __ret = __pgprot(x);            /
        __ret;                        /
    })
#endif

#define PAGE_KERNEL MAKE_GLOBAL(__PAGE_KERNEL)
#define PAGE_KERNEL_RO MAKE_GLOBAL(__PAGE_KERNEL_RO)
#define PAGE_KERNEL_NOCACHE MAKE_GLOBAL(__PAGE_KERNEL_NOCACHE)

/*
 * The i386 can't do page protection for execute, and considers that
 * the same are read. Also, write permissions imply read permissions.
 * This is the closest we can get..
 */
#define __P000    PAGE_NONE
#define __P001    PAGE_READONLY
#define __P010    PAGE_COPY
#define __P011    PAGE_COPY
#define __P100    PAGE_READONLY
#define __P101    PAGE_READONLY
#define __P110    PAGE_COPY
#define __P111    PAGE_COPY

#define __S000    PAGE_NONE
#define __S001    PAGE_READONLY
#define __S010    PAGE_SHARED
#define __S011    PAGE_SHARED
#define __S100    PAGE_READONLY
#define __S101    PAGE_READONLY
#define __S110    PAGE_SHARED
#define __S111    PAGE_SHARED

/*
 * Define this if things work differently on an i386 and an i486:
 * it will (on an i486) warn about kernel memory accesses that are
 * done without a 'verify_area(VERIFY_WRITE,..)'
 */
#undef TEST_VERIFY_AREA

/* page table for 0-4MB for everybody */
extern unsigned long pg0[1024];

#define pte_present(x)    ((x).pte_low & (_PAGE_PRESENT | _PAGE_PROTNONE))
#define pte_clear(xp)    do { set_pte(xp, __pte(0)); } while (0)

#define pmd_none(x)    (!pmd_val(x))
#define pmd_present(x)    (pmd_val(x) & _PAGE_PRESENT)
#define pmd_clear(xp)    do { set_pmd(xp, __pmd(0)); } while (0)
#define    pmd_bad(x)    ((pmd_val(x) & (~PAGE_MASK & ~_PAGE_USER)) != _KERNPG_TABLE)

/*
 * Permanent address of a page. Obviously must never be
 * called on a highmem page.
 */
#define page_address(page) ((page)->virtual)
#define pages_to_mb(x) ((x) >> (20-PAGE_SHIFT))

/*
 * The following only work if pte_present() is true.
 * Undefined behaviour if not..
 */
static inline int pte_read(pte_t pte)        { return (pte).pte_low & _PAGE_USER; }
static inline int pte_exec(pte_t pte)        { return (pte).pte_low & _PAGE_USER; }
static inline int pte_dirty(pte_t pte)        { return (pte).pte_low & _PAGE_DIRTY; }
static inline int pte_young(pte_t pte)        { return (pte).pte_low & _PAGE_ACCESSED; }
static inline int pte_write(pte_t pte)        { return (pte).pte_low & _PAGE_RW; }

static inline pte_t pte_rdprotect(pte_t pte)    { (pte).pte_low &= ~_PAGE_USER; return pte; }
static inline pte_t pte_exprotect(pte_t pte)    { (pte).pte_low &= ~_PAGE_USER; return pte; }
static inline pte_t pte_mkclean(pte_t pte)    { (pte).pte_low &= ~_PAGE_DIRTY; return pte; }
static inline pte_t pte_mkold(pte_t pte)    { (pte).pte_low &= ~_PAGE_ACCESSED; return pte; }
static inline pte_t pte_wrprotect(pte_t pte)    { (pte).pte_low &= ~_PAGE_RW; return pte; }
static inline pte_t pte_mkread(pte_t pte)    { (pte).pte_low |= _PAGE_USER; return pte; }
static inline pte_t pte_mkexec(pte_t pte)    { (pte).pte_low |= _PAGE_USER; return pte; }
static inline pte_t pte_mkdirty(pte_t pte)    { (pte).pte_low |= _PAGE_DIRTY; return pte; }
static inline pte_t pte_mkyoung(pte_t pte)    { (pte).pte_low |= _PAGE_ACCESSED; return pte; }
static inline pte_t pte_mkwrite(pte_t pte)    { (pte).pte_low |= _PAGE_RW; return pte; }

static inline  int ptep_test_and_clear_dirty(pte_t *ptep)    { return test_and_clear_bit(_PAGE_BIT_DIRTY, ptep); }
static inline  int ptep_test_and_clear_young(pte_t *ptep)    { return test_and_clear_bit(_PAGE_BIT_ACCESSED, ptep); }
static inline void ptep_set_wrprotect(pte_t *ptep)        { clear_bit(_PAGE_BIT_RW, ptep); }
static inline void ptep_mkdirty(pte_t *ptep)            { set_bit(_PAGE_BIT_DIRTY, ptep); }

/*
 * Conversion functions: convert a page and protection to a page entry,
 * and a page entry and page directory to the page they refer to.
 */

#define mk_pte(page, pgprot)    __mk_pte((page) - mem_map, (pgprot))

/* This takes a physical page address that is used by the remapping functions */
#define mk_pte_phys(physpage, pgprot)    __mk_pte((physpage) >> PAGE_SHIFT, pgprot)

static inline pte_t pte_modify(pte_t pte, pgprot_t newprot)
{
    pte.pte_low &= _PAGE_CHG_MASK;
    pte.pte_low |= pgprot_val(newprot);
    return pte;
}

#define page_pte(page) page_pte_prot(page, __pgprot(0))

#define pmd_page(pmd) /
((unsigned long) __va(pmd_val(pmd) & PAGE_MASK))

/* to find an entry in a page-table-directory. */
#define pgd_index(address) ((address >> PGDIR_SHIFT) & (PTRS_PER_PGD-1))

#define __pgd_offset(address) pgd_index(address)

#define pgd_offset(mm, address) ((mm)->pgd+pgd_index(address))

/* to find an entry in a kernel page-table-directory */
#define pgd_offset_k(address) pgd_offset(&init_mm, address)

#define __pmd_offset(address) /
        (((address) >> PMD_SHIFT) & (PTRS_PER_PMD-1))

/* Find an entry in the third-level page table.. */
#define __pte_offset(address) /
        ((address >> PAGE_SHIFT) & (PTRS_PER_PTE - 1))
#define pte_offset(dir, address) ((pte_t *) pmd_page(*(dir)) + /
            __pte_offset(address))

/*
 * The i386 doesn't have any external MMU info: the kernel page
 * tables contain all the necessary information.
 */
#define update_mmu_cache(vma,address,pte) do { } while (0)

/* Encode and de-code a swap entry */
#define SWP_TYPE(x)            (((x).val >> 1) & 0x3f)
#define SWP_OFFSET(x)            ((x).val >> 8)
#define SWP_ENTRY(type, offset)        ((swp_entry_t) { ((type) << 1) | ((offset) << 8) })
#define pte_to_swp_entry(pte)        ((swp_entry_t) { (pte).pte_low })
#define swp_entry_to_pte(x)        ((pte_t) { (x).val })

#endif /* !__ASSEMBLY__ */

/* Needs to be defined here and not in linux/mm.h, as it is arch dependent */
#define PageSkip(page)        (0)
#define kern_addr_valid(addr)    (1)

#define io_remap_page_range remap_page_range

#endif /* _I386_PGTABLE_H */
-----------------------------------------------------------------------------------------------------------------------------
页表处理
pte_t,pmd_t,pgd_t分别描述页表项,页中间目录项和页全局项的格式,当PAE被禁用时,它们是32位数据类型,当PAE被启用时,它们是64位数据类型
pgprot_t是另一个32位的数据类型,它表示与一个单独表项相关的保护标志
四个类型转换宏__pte(),__pmd(),__pgd(),__pgprot()把一个无符号整数转换成所需要的类型
另外四个类型转换宏pte_val(),pmd_val(),pgd_val(),pgprot_val()执行相反的转换,即把上面的四种特殊的类型转换成一个无符号整数
以上所提到的所有的符号均定义在include/asm-i386/page.h

内核提供的修改页表项的宏和函数:
1.    如果相应的表项值为0,那么,宏pte_none(),pmd_none()和,pgd_none()产生值为1,否则为0
2.    如果相应的表项的Present标志位为1,那么,宏pte_present(),pmd_present(),pgd_present()产生的值为1
3.    宏pte_clear(),pmd_clear(),pgd_clear()清除相应页表的一个表项.由此强制进程使用由该页表项映射的线性地址
宏pmd_bad()和pgd_bad()由函数使用,以检查页全局目录项和页中间项.如果目录指向一个不能使用的页表,以下三种情况中的一个,则这两个宏产生的值为1:
1.    页不再主存中
2.    页只允许读访问
3.    Accessed或者Dirty位被清除
以上所提到的符号均定义在include/asm-i386/pgtable.h中

有关于pte的几个函数:
pte_read():返回User/Supervisor标志值
pte_write():如果Read/Write标志都被设置则返回1
pte_exec():返回User/Supervisor标志的值.80x86处理器上的页不受代码执行保护
pte_dirty():返回Dirty标志的值
pte_young():返回Accessed标志的值
pte_wrprotect():清除Read/Write标志
pte_rdprotect()和pte_exprotect():清除User/Supervisor标志
pte_mkwrite():设置Read/Write标志
pte_mkread()和pte_mkexec():设置User/Supervisor标志
pte_mkdirty()和pte_mkclean():把Dirty标志分别置为1或0,因而把此页标记为修改过或没修改过
pte_mkyoung()和pte_mkold():把Accessed标志置为1或0,因而把此页标记为访问过(young)或没访问过(old)
pte_modify(p,v):把页表项p的所有访问权限设置为指定的值v
set_pte,set_pmd,set_pgd:把一个指定值分别写进一个页表项,页中间目录,页全局目录项中
以上函数和符号均定义在include/asm-i386/pgtable.h中

下面的宏把一个页地址和一组保护标志组合成页表项,或者执行相反的操作,从一个页表项中提取出页地址:
mk_pte:接受一个线性地址和一组访问权限作为参数并创建一个页表项
mk_pte_phys:通过合并页的物理地址和访问权限创建一个页表项
pte_page():返回页表项所包含的页框描述符的地址
pmd_page():返回页中间目录项所包含页表的线性地址
pgd_offset(p,a):接受内存描述符p和线性地址a作为参数.a是一个页全局目录对应的地址,这个宏产生一个表项在该表内的偏移地址;通过内存描述符p内的一个指针可以找到页全局目录.宏pgd_offset_k()也很类似,不同之处在于它指向主内核页表
pmd_offset(p,a):接受页全局目录项q和线性地址a作为输入参数.p指向页中间目录,该宏产生地址a所对应的目录项在页中间目录中的偏移地址
pte_offset(p,a):与pmd_offset类似,但p是一个页中间目录项,它指向页表,该宏产生a对应的表项在该页表内的偏移地址
以上符号和函数定义在include/asm-i386/pgtable.h中

pgd_alloc(m):通过调用get_pgd_slow()函数分配一个新的页全局目录.如果PAE被启用,则后一个函数也分配四个子页中间目录,定义在include/
asm-i396/pgalloc.h中
pmd_alloc(m,p,a):这个函数使三级分页系统可以为线性地址a分配一个新的页中间目录.如果PAE未被启用,则这个行数只是返回输入参数p的值.如果PAE未被启用,则该函数返回创建页全局努力时分配给页中间目录的地址.参数m被忽略.定义在include/linux/mm.h中
pte_alloc(m,p,a):接受页中间目录项的地址p和线性地址a作为参数,返回a对应的页表项的地址.如果页中间目录项为空,那么这个函数必须分配一个新的页表.新页框是通过调用pte_alloc_one()分配的.如果新页表被分配,那么与a对应的表项得以初始化,且User/Supervisor标志被设置.参数m被忽略.定义在mm/memory.c中
pte_free()和pgd_free():释放一个页表.pmd_free()函数什么也不做.因为页中间目录的分配和是释放是随同它们的父页全局目录一同进行的.pte_free()定义在include/asm-i386/pgalloc.h  pgd_free()定义在include/asm-i386/pgtable.h中
free_one_pmd():调用pte_free()函数释放一个页表,并把页中间目录中对应的项设置为空.定义在mm/memory.c中
free_one_pgd():反复调用free_one_pmd()释放一个页中间目录中的所有页表.然后调用pmd_free()函数释放页中间目录.定义在mm/memory.c中
clear_page_tables():通过反复调用free_one_pgd()函数清除一个进程页表的内容.定义在mm/memory.c中
#############################################################################################################################
保留的页框
linux内核安装在RAM中从物理地址0x00100000开始的地方
所需页框总数依赖于内核的配置方案:典型的配置所得到的内核可以内安装在小于2MB的RAM中
内核安装必须考虑到:
1.    页框0由BIOS使用,存放加电自检期间检查到的硬件配置
2.    物理地址从0x000a0000到0x000fffff的范围通常留给BIOS例程,并且映射ISA图形卡的内部存储器.物理地址存在但被保留,对应的页框不能由操作系统使用
3.    前1MB内的其他页框可能由特定计算机模型保留
在启动的早期阶段,内核询问BIOS并了解物理内存的大小.早新近的计算机中,内核也调用BIOS过程建立一组物理地址范围和其对应的内存类型.随后.内核执行setup_memory_region()函数.定义在arch/kernel/setup.c中
#############################################################################################################################
进程页表
进程的线性地址空间分为两部分
1.    从0x00000000到0xbfffffff地线性地址,无论用户态还是内核态的进程都可以寻址
2.    从0xc0000000到0xffffffff的线性地址,只有内核态的进程才能寻址
宏PAGE_OFFSET产生的值是0xc0000000,这是进程在线性地址空间的偏移量,也是内核生存空间的开始处
页全局目录的第一部分表项映射的线性地址小于0xc0000000,具体大小取决于特定的进程.相反,剩余的表项对所有进程来说都应该是相同的,它们等于主内核页全局目录的相应表项
#############################################################################################################################
内核页表
内核维持着一组自己使用的页表,驻留在所谓主内核页全局目录中.系统初始化后,这组页表永远不会被任何进程或任何内核线程直接使用
内核映像刚刚被装入内存后,CPU仍然运行于实模式,所以分页功能没有启用
第一个阶段,内核仅创建足以把自己装入到RAM中的8MB地址空间
第二个阶段,内核充分利用剩余的RAM并适当地建立分页表

临时内核页表
临时页全局目录是内核在编译过程中静态地初始化的,而临时页表是由startup_32()汇编语言函数(arch/i386/kernel/head.S)初始化的
页全局目录放在swapper_pg_dir(arch/i386/kernel/head.S)变量中,而映射前8MB RAM的两个页表放在pg0和pg1变量中
分页第一个阶段的目标是允许在实模式下和在保护模式下都很容易地对着8MB寻址.内核在初始化的第一阶段,可以通过与物理地址相同的线性地址或者通过从0xc0000000开始的8MB线性地址对RAM的前8MB进行寻址
内核通过把swapper_pg_dir是所有项都填充为0来创建期望的映射,0,1,0x300,0x301这四项除外;后两项包含了从0xc0000000到0xc07fffff间所有的线性地址
0,1,0x300,0x301初始化:
1.    0项和0x300项的地址字段设置为pg0的物理地址,而1项和0x301项的地址字段设置为pg1的物理地址
2.    把这四个项的Present,Read/Write和User/Supervisor标志置位
3.    把这四个项的Accessed,Dirty,PCD,PWD和Page Size标志清0
汇编语言函数startup_32()也启用分页功能.通过向cr3控制寄存器装入swapper_pg_dir的地址及设置cr0控制寄存器的PG标志来达到这一目的.代码为:
movl $swapper_pg_dir-__PAGE_OFFSET,%eax
movl %eax,%cr3
movl %cr0,%eax
orl $0x80000000,%eax
movl %eax,%cr0

当RAM小于896MB时的最终内核页表
由内核页表所提供的最终映射必须把从0xc0000000开始的线性地址转化为从0开始的物理地址
宏_pa用于把从PAGE_OFFSET开始的线性地址转换成相应的物理地址,而宏_va做相反的转化
主内核页全局目录仍然保留在swapper_pg_dir变量中.它由paging_init()函数(arch/i386/mm/init.c)初始化.该函数的操作:
1.    调用pagetable_init()适当地建立页表项
2.    把swapper_pg_dir的物理地址写入cr3寄存器中
3.    调用__flush_tlb_all()使TLB的所有项都无效
#############################################################################################################################
处理硬件高速缓存和TLB
处理硬件高速缓存是通过高速缓存行寻址的.L1_CACHE_BYTES宏产生以字节为单位的高速缓存行的大小
为了使高速缓存的命中率达到最优化,内核在下列决策中考虑的体系:
1.    一个数据结构中最常使用的字段放在该数据结构内的低偏移部分,以便他们能够处于高速缓存的同一行
2.    为了一大组数据结构分配空间时,内核试图把它们都存放在内存中,以便所有高速缓存行按同一方式使用
3.    当执行进程切换时,如果某个进程使用的页表与前一个运行进程所使用的一组页表相同,则内核优先选择这样的进程
处理TLB
一般来说,任何进程的切换都意味着改变当前页表的集合.相对于旧页表的本地TLB表项必须被刷新;这是在内核把新的页全局目录地址写入cr3控制寄存器时自动完成的.但是,内核在某些情况下成功地避免TLB刷新:
1.    在使用同组页表的两个普通进程之间执行进程切换
2.    在一个普通进程和一个内核线程之间执行进程切换时
为了使TLB表项无效,内核使用下列函数和宏:
__flush_tlb_one:使给定地址所在页的本地TLB表项无效.定义在include/asm-i386/pgtable.h
flush_tlb_page:使所有CPU上给定地址所在页的所有本地TLB表项无效.定义在include/asm-i386/pgalloc.h
local_flush_tlb和__flush_tlb:把与当前进程所有页相关的本地TLB表项都被刷新.定义在include/asm-i386/pgalloc.h
flush_tlb:把与所有当前进程的非全局页相关的TLB表项刷新.定义在include/asm-i386/pgalloc.h
flush_tlb_mm:在指定的页表集中,把所有非全局相关的TLB表项刷新.定义在include/asm-i386/pgalloc.h
flush_tlb_range:在给定页表的一个指定地址范围上,使其给全局页的所有TLB表项无效.定义在include/asm-i386/pgalloc.h
__flush_tlb_all:刷新所有本地TLB表项.定义在include/asm-i386/pgalloc.h
flush_tlb_all:刷新所有CPU上的所有TLB表项.定义在include/asm-i386/pgalloc.h
include/asm-i386/pgalloc.h:
#ifndef _I386_PGALLOC_H
#define _I386_PGALLOC_H

#include <linux/config.h>
#include <asm/processor.h>
#include <asm/fixmap.h>
#include <linux/threads.h>

#define pgd_quicklist (current_cpu_data.pgd_quick)
#define pmd_quicklist (current_cpu_data.pmd_quick)
#define pte_quicklist (current_cpu_data.pte_quick)
#define pgtable_cache_size (current_cpu_data.pgtable_cache_sz)

#define pmd_populate(mm, pmd, pte) /
        set_pmd(pmd, __pmd(_PAGE_TABLE + __pa(pte)))

/*
 * Allocate and free page tables.
 */

#if defined (CONFIG_X86_PAE)
/*
 * We can't include <linux/slab.h> here, thus these uglinesses.
 */
struct kmem_cache_s;

extern struct kmem_cache_s *pae_pgd_cachep;
extern void *kmem_cache_alloc(struct kmem_cache_s *, int);
extern void kmem_cache_free(struct kmem_cache_s *, void *);

static inline pgd_t *get_pgd_slow(void)
{
    int i;
    pgd_t *pgd = kmem_cache_alloc(pae_pgd_cachep, GFP_KERNEL);

    if (pgd) {
        for (i = 0; i < USER_PTRS_PER_PGD; i++) {
            unsigned long pmd = __get_free_page(GFP_KERNEL);
            if (!pmd)
                goto out_oom;
            clear_page(pmd);
            set_pgd(pgd + i, __pgd(1 + __pa(pmd)));
        }
        memcpy(pgd + USER_PTRS_PER_PGD,
            swapper_pg_dir + USER_PTRS_PER_PGD,
            (PTRS_PER_PGD - USER_PTRS_PER_PGD) * sizeof(pgd_t));
    }
    return pgd;
out_oom:
    for (i--; i >= 0; i--)
        free_page((unsigned long)__va(pgd_val(pgd[i])-1));
    kmem_cache_free(pae_pgd_cachep, pgd);
    return NULL;
}

#else

static inline pgd_t *get_pgd_slow(void)
{
    pgd_t *pgd = (pgd_t *)__get_free_page(GFP_KERNEL);

    if (pgd) {
        memset(pgd, 0, USER_PTRS_PER_PGD * sizeof(pgd_t));
        memcpy(pgd + USER_PTRS_PER_PGD,
            swapper_pg_dir + USER_PTRS_PER_PGD,
            (PTRS_PER_PGD - USER_PTRS_PER_PGD) * sizeof(pgd_t));
    }
    return pgd;
}

#endif /* CONFIG_X86_PAE */

static inline pgd_t *get_pgd_fast(void)
{
    unsigned long *ret;

    if ((ret = pgd_quicklist) != NULL) {
        pgd_quicklist = (unsigned long *)(*ret);
        ret[0] = 0;
        pgtable_cache_size--;
    } else
        ret = (unsigned long *)get_pgd_slow();
    return (pgd_t *)ret;
}

static inline void free_pgd_fast(pgd_t *pgd)
{
    *(unsigned long *)pgd = (unsigned long) pgd_quicklist;
    pgd_quicklist = (unsigned long *) pgd;
    pgtable_cache_size++;
}

static inline void free_pgd_slow(pgd_t *pgd)
{
#if defined(CONFIG_X86_PAE)
    int i;

    for (i = 0; i < USER_PTRS_PER_PGD; i++)
        free_page((unsigned long)__va(pgd_val(pgd[i])-1));
    kmem_cache_free(pae_pgd_cachep, pgd);
#else
    free_page((unsigned long)pgd);
#endif
}

static inline pte_t *pte_alloc_one(struct mm_struct *mm, unsigned long address)
{
    pte_t *pte;

    pte = (pte_t *) __get_free_page(GFP_KERNEL);
    if (pte)
        clear_page(pte);
    return pte;
}

static inline pte_t *pte_alloc_one_fast(struct mm_struct *mm,
                    unsigned long address)
{
    unsigned long *ret;

    if ((ret = (unsigned long *)pte_quicklist) != NULL) {
        pte_quicklist = (unsigned long *)(*ret);
        ret[0] = ret[1];
        pgtable_cache_size--;
    }
    return (pte_t *)ret;
}

static inline void pte_free_fast(pte_t *pte)
{
    *(unsigned long *)pte = (unsigned long) pte_quicklist;
    pte_quicklist = (unsigned long *) pte;
    pgtable_cache_size++;
}

static __inline__ void pte_free_slow(pte_t *pte)
{
    free_page((unsigned long)pte);
}

#define pte_free(pte)        pte_free_slow(pte)
#define pgd_free(pgd)        free_pgd_slow(pgd)
#define pgd_alloc(mm)        get_pgd_fast()

/*
 * allocating and freeing a pmd is trivial: the 1-entry pmd is
 * inside the pgd, so has no extra memory associated with it.
 * (In the PAE case we free the pmds as part of the pgd.)
 */

#define pmd_alloc_one_fast(mm, addr)    ({ BUG(); ((pmd_t *)1); })
#define pmd_alloc_one(mm, addr)        ({ BUG(); ((pmd_t *)2); })
#define pmd_free_slow(x)        do { } while (0)
#define pmd_free_fast(x)        do { } while (0)
#define pmd_free(x)            do { } while (0)
#define pgd_populate(mm, pmd, pte)    BUG()

extern int do_check_pgt_cache(int, int);

/*
 * TLB flushing:
 *
 *  - flush_tlb() flushes the current mm struct TLBs
 *  - flush_tlb_all() flushes all processes TLBs
 *  - flush_tlb_mm(mm) flushes the specified mm context TLB's
 *  - flush_tlb_page(vma, vmaddr) flushes one page
 *  - flush_tlb_range(mm, start, end) flushes a range of pages
 *  - flush_tlb_pgtables(mm, start, end) flushes a range of page tables
 *
 * ..but the i386 has somewhat limited tlb flushing capabilities,
 * and page-granular flushes are available only on i486 and up.
 */

#ifndef CONFIG_SMP

#define flush_tlb() __flush_tlb()
#define flush_tlb_all() __flush_tlb_all()
#define local_flush_tlb() __flush_tlb()

static inline void flush_tlb_mm(struct mm_struct *mm)
{
    if (mm == current->active_mm)
        __flush_tlb();
}

static inline void flush_tlb_page(struct vm_area_struct *vma,
    unsigned long addr)
{
    if (vma->vm_mm == current->active_mm)
        __flush_tlb_one(addr);
}

static inline void flush_tlb_range(struct mm_struct *mm,
    unsigned long start, unsigned long end)
{
    if (mm == current->active_mm)
        __flush_tlb();
}

#else

#include <asm/smp.h>

#define local_flush_tlb() /
    __flush_tlb()

extern void flush_tlb_all(void);
extern void flush_tlb_current_task(void);
extern void flush_tlb_mm(struct mm_struct *);
extern void flush_tlb_page(struct vm_area_struct *, unsigned long);

#define flush_tlb()    flush_tlb_current_task()

static inline void flush_tlb_range(struct mm_struct * mm, unsigned long start, unsigned long end)
{
    flush_tlb_mm(mm);
}

#define TLBSTATE_OK    1
#define TLBSTATE_LAZY    2

struct tlb_state
{
    struct mm_struct *active_mm;
    int state;
};
extern struct tlb_state cpu_tlbstate[NR_CPUS];

#endif

static inline void flush_tlb_pgtables(struct mm_struct *mm,
                      unsigned long start, unsigned long end)
{
    /* i386 does not keep any page table caches in TLB */
}

#endif /* _I386_PGALLOC_H */

为了避免多处理器系统上无用的TLB刷新,内核使用一种叫做懒惰TLB模式的技术:如果几个CPU正在使用相同的页表,且必须对这些CPU上的一个TLB表项刷新,那么,在某些情况下,正在运行内核线程的那些CPU上的刷新就可以延迟

声明: 本文由( 鲁智森也有文化 )原创编译,转载请保留链接: linux内核分析-存储器管理-理论篇

linux内核分析-存储器管理-理论篇:等您坐沙发呢!

发表评论


QQ群互动

Linux系统与内核学习群:194051772

WP建站技术学习交流群:194062106

魔豆之路QR

魔豆的Linux内核之路

魔豆的Linux内核之路

优秀工程师当看优秀书籍

优秀程序员,要看优秀书!

赞助商广告

友荐云推荐