ARM Linux基本运算符综合实例
本文介绍页面管理的基础知识,并成语法角度对嵌入式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<<PAGE_SHIFT)
#define PAGE_MASK (~(PAGE_SIZE -1))
这里显示的是#define的另一个用途,预处理指令"#define"不仅可以定义常量,还可以定义表达式。这里的“1UL”代表的就是无符号长整形的意思,将“1”左移PAGE_SHIFT位实际就是2的PAGE_SHIFT次方。可以看到,在Linux内核中,若想要表达2的N次方,通常使用位移操作来实现。
PAGE_MASK是用于产生页表掩码的,当PAGE_SHIFT为12时,PAGE_SIZE的值就为0x1000,而PAGE_MASK是将PAGE_SIZE先减1,再取反,因此,它的值为0xfffff000.一个线性地址通过和它想与可以屏蔽掉所有的偏移位(Offset字段)。
在这里为什么要在“PAGE_SIZE-1”两侧加上括号呢?读者可以回忆一下,“~”操作符是单目运算符,它的优先级位列第二,仅次于“()”操作符,因此,若不加括号,则该语句先运算PAGE_SIZE取反,再将结果减一,这显然是不对的。
下面介绍页全局目录表项和页中间目录表项的相关代码,这些代码的相关定义在<include/asm-arm/pgtable.h>中:
#define PMD_SHIFT 21
#define PGDIR_SHIFT 21
#define PMD_SIZE (1UL<<PMD_SHIFT)
#define PMD_MASK (~(PMD_SIZE-1))
#define PGDIR_SIZE (1UL<<PGDIR_SHIFT)
#define PGDIR_MASK (~(PGDIR_SIZE-1))
可以看到,这里计算的方式很类似,都是通过移位操作和取反操作来实现的。其中的PMD_SHIFT用于指定线性地址的偏移字段和页表字段的总位数,由于前面的PAGE_SHIFT已经指定了偏移字段为12位,因此,在ARM中页表字段为9位。