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

通过计算可以得到,PMD_SIZE产生的值为2的21次方,即为2M,PMD_MASK产生的值为0Xffe00000.

PGDIR_SHIFT是全局目录项所能映射的区域大小的对数,PGDIR_SIZE宏是用于计算页全局目录中一个单独表项所能映射区域的大小,它与PMD_SIZE一样。(用户通常可以使用减1取反来产生某个数的掩码)

有了前面基础知识的理解和掌握后,下面来介绍ARM-Linux中真正的页面映射部分的内容,着重从语法角度来介绍。

下面这个函数是嵌入式Linux中非常重要的一个函数,它用于创建页面映射,其源代码位于<arch/arm/mm/mm-armv.c>中,该函数中的关键代码如下所示:

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,则建立二级页表映射。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值