本文介绍页面管理的基础知识,并成语法角度对嵌入式Linux的内存管理进行详细的讲解。
1、页映射机制
要了解嵌入式Linux的页面映射机制,首先要了解嵌入式Linux的内存管理以及虚拟内存的基础知识。下面对其进行简单介绍。
内存管理系统是操作系统中最为重要的部分,系统的物理内存总是少于系统所需要的内存数量,虚拟内存就是为了克服这个矛盾而采用的策略。系统的虚拟内存通过各个进程之间共享内存而使系统看起来有多于实际内存的内存容量,虚拟内存提供以下功能:
(1)广阔的地址空间:系统的虚拟内存可以比系统的实际内存大很多倍。
(2)进程的保护:系统中的每一个进程都有自己的虚拟地址空间。这些虚拟地址空间是完全分开的,这样一个进程的运行不会影响其他进程。并且,硬件上的虚拟内存机制是被保护的,内存不能被写入,这样可以防止迷失的应用程序覆盖代码的数据。
(3)内存映射:内存映射用来把文件映射到进程的地址空间。在内存映射中,文件的内容直接连接到进程的虚拟地址空间。
(4)公平的物理内存分配:内存管理系统允许系统中每一个运行的进程都可以公平地得到系统的物理内存。
这里,有3种地址的概念需要进行区分。
逻辑地址:出现在机器指令中,用来制定操作数的地址。如“段:偏移”
线性地址:逻辑地址经过分段单元处理后得到线性地址,这是一个32为的无符号整数,可用于定位4G个存储单元。
物理地址:线性地址经过分页后得出物理地址,这个地址将被送到地址总线上指示所要访问的物理内存单元。
这3中地址的转换如下所示:
逻辑地址————>线性地址————>物理地址
分段 分页
这里需要注意的是,分段可以给每一个进程分配不同的线性地址空间,而分页可以把同一个线性地址映射到不同的物理地址空间。因此,分页实质就是一个将线性地址映射到物理地址的映射表,其索引值为线性地址,运算结果为物理地址。(在嵌入式Linux尽量避免使用段功能提高可移植性)
为了有效地利用地址空间,嵌入式Linux使用3层页表映射,它定义了3种类型的分页表:页全局目录(PGDID),页中间目录(PMD),页表。
页全局目录包含若干个页中间目录的地址,而页中间目录又包含若干个页表的地址。每一个页表指向一个实际的物理地址。
2、ARM-Linux页面映射实现
在上文中读者看到了定义PAGE_SHIFT的代码,下面,读者就来看一下有关定义页面大小的代码:
#define PAGE_SIZE (1UL<
中:
#define PMD_SHIFT 21
#define PGDIR_SHIFT 21
#define PMD_SIZE (1UL<中,该函数中的关键代码如下所示:
static void _init create_mapping(struct map_desc *md){
unsigned long virt,length;
int prot_set,prot_11,domain;
pgprot_t prot_pte;
long off;
...
/*虚拟地址*/
virt = md->virtual;
/*地址偏移*/
off = md->physical - virt;
/*地址长度*/
length = md->length;
...
/*while1:判断虚拟地址是否与1M对齐,并且其长度大于页面大小*/
while((virt & 0xfffff || (virt+off) & 0xfffff) && length >=PAGE_SIZE){
/*分配中间页表项映射*/
alloc_init_page(virt,virt + off,prot_11,prot_pte);
/*虚拟地址增加PAGE_SEZE大小*/
virt += PAGE_SIZE;
/*长度减小PAGE_SIZE大小*/
length -=PAGE_SIZE;
}
/*while2:判断长度是否大于全局页表项大小的一半*/
while(length >= (PGDIR_SIZE / 2)){
/*逐段建立单层映射*/
alloc_init_section(virt,virt + off, prot_sect);
/*虚拟地址增加PGDIR_SIZE/2大小*/
virt += (PGDIR_SIZE /2);
/*长度减小PGDIR_SIZE/2大小*/
length -=(PGDIR_SIZE /2);
}
/*while3:判断长度是否大于页大小*/
while(length >= PAGE_SIZE){
/*分配中间页表项映射*/
alloc_init_page(virt,virt + off,prot_11,prot_pte);
/*虚拟地址增加PAGE_SIZE大小*/
virt +=PAGE_SIZE;
/*长度减小PAGE_SIZE大小*/
length -=PAGE_SIZE;
}
}
Linux内核建立页面主要是通过这3个while循环语句来完成的,这里主要分析第一个while循环语句中的表达式:
(virt & 0xfffff || (virt + off) & 0xfffff) && length >= PAGE_SIZE
这句表达式用到了多种运算符,包括位运算符、关系运算符、逻辑运算符等,请读者根据运算符的优先级来分析下这条语句的逻辑结果。
这里的运算符中,括号的优先级最高,因此先计算括号内的内容:
virt & 0xfffff || (virt + off) & 0xfffff
可以看到这条语句里还有括号,因此先计算“virt + off”。接下来的运算符有“&”和“||”(逻辑或),由优先级口诀中欧冠可以看到,“&”(位与)的优先级为八,逻辑或的优先级为十二,因此先计算逻辑与,即“virt & 0xfffff” 和“(virt + off) & 0xfffff”,再计算它们的逻辑或。上述语句可等价为以下括号的语句:
(virt & 0xfffff)||((virt + off) & 0xfffff)
上述表达式的运算结果为:若“virt”或“virt + off”和“0xfffff”相与的结果中有一方非0,则表达式的结果为真,即只有“virt”和“virt+off”的低20为都为0时,表达式的结果才为假。
在计算完上述括号内的表达式后,原语句可等价为如下:
TRUE/FALSE && length >=PAGE_SIZE
这时,要判断先计算逻辑与还是先计算关系运算符“ >=".从优先级口诀中可以看出,关系运算符的优先级为六,逻辑与的优先级为十一,因此该语句首先计算“length>=PAGE_SIZE”。这样,上述语句可等价为以下括号的语句:
TRUE/FALSE && (length >= PAGE_SIZE)
因此,该表达式为真的条件就是:“virt”或“virt+off”和“0xfffff”相与的结果中有一方非0并且“length”大于等于“PAGE_SIZE”;表达式为假的条件是:“virt”和“virt+off”的低20位都为零或者“length”小于“PAGE_SIZE”.
这几个while语句的含义为:若地址与1M(2的20次方)没有对齐(即低20位不全为0),则建立二级页面映射;若地址1M对齐,且长度大于PGDIR_SIZE,则逐段建立单层映射;若地址与1M对齐,且长度大于PAGE_SIZE,则建立二级页表映射。