ARM Linux的访问权限控制

ARM1176JZF-S处理器为访问权限控制定义了两个层次:第一层是"域"(Domain)的访问类型,第二层是页或者段的"读写权限"(Access Permission)。具体来说,过程是这样的:

1. 在ARM处理器中,MMU将整个存储空间分成最多16个域,记作D0~D15,每个域对应一定的存储区域,该区域具有相同的访问控制属性。每个域的访问权限分别由CP15的C3寄存器中的两位来设定,c3寄存器的大小为32bits,刚好可以设置16个域的访问权限。

Bits 31, 30 29, 28 27, 26 25, 24 23, 22 21, 20 19, 18 17, 16 15, 14 13, 12 11, 10 9, 8 7, 6 5, 4 3, 2 1, 0
Domain D15 D14 D13 D12 D11 D10 D9 D8 D7 D6 D5 D4 D3 D2 D1 D0

对于每一个域所对应的两个bit,访问类型设置方法如下:

访问类型 含义
0b00 无访问权限 此时访问该域将产生访问失效
0b01 用户(client) 根据CP15的C1控制寄存器中的R和S位以及页表中地址变换条目中的访问权限控制位AP来确定是否允许各种系统工作模式的存储访问
0b10 保留 使用该值会产生不可预知的结果
0b11 管理者(Manager) 不考虑CP15的C1控制寄存器中的R和S位以及页表中地址变换条目中的访问权限控制位AP,在这种情况下不管系统工作在特权模式还是用户模式都不会产生访问失效

2. 如果域的访问类型是0b01的话,下面就进入第二层。所访问内存页或段的权限还要根据CP15的C1寄存器中的R和S位以及页/段表项中的访问权限控制位AP(X)来决定。
APX AP[1:0] 特权模式访问权限 用户模式访问权限
0 b00 禁止访问;S=1,R=0或S=0,R=1时只读 禁止访问;S=1,R=0时只读
0 b01 读写 禁止访问
0 b10 读写 只读
0 b11 读写 读写
1 b00 保留 保留
1 b01 只读 禁止访问
1 b10 只读 只读
1 b11 只读 只读
[8] 参考ARM1176JZF-S Revision: r0p7->6.5.2 Access permissions


下面看一下Linux是如何实现的。

在谈到create_mapping之前,必须说明一下Linux是如何实现对页面的访问控制的。
Linux使用结构体mem_type来定义不同的内存映射类型(arch/arm/mm/mm.h),不同的映射类型定义了不同的访问权限:
   [c]
struct mem_type {
unsigned int prot_pte;
unsigned int prot_l1;
unsigned int prot_sect;
unsigned int domain;
};
[/c]

其中处成员含义如下:
prot_pte代表页表项的访问控制权,pte即第二级映射表项(页表项)。
prot_l1代表段表项的访问控制位,l1即第一级映射表项(段表项/主页表项)。
prot_sect代表主页表(注意,不是主页表项)的访问控制位和内存域。
domain代表所属的内存域。
对于ARM处理器,Linux定义了一个类型为struct mem_type的局部静态数组(arch/arm/mm/mmu.c)。根据不同的映射类型,定义了不同的访问权限。

   [c]
static struct mem_type mem_types[] = {
[MT_DEVICE] = {
.prot_pte = PROT_PTE_DEVICE | L_PTE_MT_DEV_SHARED |
L_PTE_SHARED,
.prot_l1 = PMD_TYPE_TABLE,
.prot_sect = PROT_SECT_DEVICE | PMD_SECT_S,
.domain = DOMAIN_IO,
},
[MT_DEVICE_NONSHARED] = {
.prot_pte = PROT_PTE_DEVICE | L_PTE_MT_DEV_NONSHARED,
.prot_l1 = PMD_TYPE_TABLE,
.prot_sect = PROT_SECT_DEVICE,
.domain = DOMAIN_IO,
},
[MT_DEVICE_CACHED] = {
.prot_pte = PROT_PTE_DEVICE | L_PTE_MT_DEV_CACHED,
.prot_l1 = PMD_TYPE_TABLE,
.prot_sect = PROT_SECT_DEVICE | PMD_SECT_WB,
.domain = DOMAIN_IO,
},
[MT_DEVICE_WC] = {
.prot_pte = PROT_PTE_DEVICE | L_PTE_MT_DEV_WC,
.prot_l1 = PMD_TYPE_TABLE,
.prot_sect = PROT_SECT_DEVICE,
.domain = DOMAIN_IO,
},
[MT_UNCACHED] = {
.prot_pte = PROT_PTE_DEVICE,
.prot_l1 = PMD_TYPE_TABLE,
.prot_sect = PMD_TYPE_SECT | PMD_SECT_XN,
.domain = DOMAIN_IO,
},
[MT_CACHECLEAN] = {
.prot_sect = PMD_TYPE_SECT | PMD_SECT_XN,
.domain = DOMAIN_KERNEL,
},
[MT_MINICLEAN] = {
.prot_sect = PMD_TYPE_SECT | PMD_SECT_XN | PMD_SECT_MINICACHE,
.domain = DOMAIN_KERNEL,
},
[MT_LOW_VECTORS] = {
.prot_pte = L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY |
L_PTE_EXEC,
.prot_l1 = PMD_TYPE_TABLE,
.domain = DOMAIN_USER,
},
[MT_HIGH_VECTORS] = {
.prot_pte = L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY |
L_PTE_USER | L_PTE_EXEC,
.prot_l1 = PMD_TYPE_TABLE,
.domain = DOMAIN_USER,
},
[MT_MEMORY] = {
.prot_sect = PMD_TYPE_SECT | PMD_SECT_AP_WRITE,
.domain = DOMAIN_KERNEL,
},
[MT_ROM] = {
.prot_sect = PMD_TYPE_SECT,
.domain = DOMAIN_KERNEL,
},
[MT_MEMORY_NONCACHED] = {
.prot_sect = PMD_TYPE_SECT | PMD_SECT_AP_WRITE,
.domain = DOMAIN_KERNEL,
},
};
[/c]

系统中定义了多个映射类型,最常用的是MT_MEMORY,它对应RAM;MT_DEVICE则对应了其他I/O设备,应用于ioremap;MT_ROM对应于ROM;MT_LOW_VECTORS对应0地址开始的向量;MT_HIGH_VECTORS对应高地址开始的向量,它有vector_base宏决定。

   [c]
arch/arm/include/asm/io.h

#define MT_DEVICE 0
#define MT_DEVICE_NONSHARED 1
#define MT_DEVICE_CACHED 2
#define MT_DEVICE_WC 3

arch/arm/include/asm/mach/map.h

#define MT_UNCACHED 4
#define MT_CACHECLEAN 5
#define MT_MINICLEAN 6
#define MT_LOW_VECTORS 7
#define MT_HIGH_VECTORS 8
#define MT_MEMORY 9
#define MT_ROM 10
[/c]

尽管ARM定义了16种不同的域,但是Linux只使用其中的三种:D0 ~ D2 (arch/arm/include/asm/domain.h)

   [c]
#define DOMAIN_KERNEL 0
#define DOMAIN_TABLE 0
#define DOMAIN_USER 1
#define DOMAIN_IO 2
[/c]

内存空间和三种域的对应关系如下:

内存空间
设备空间 DOMAIN_IO
内部高速SRAM空间/内部MINI Cache空间 DOMAIN_KERNEL
RAM内存空间/ROM内存空间 DOMAIN_KERNEL
高低端中断向量空间 DOMAIN_USER


ARM处理器为每一个域定义了四种不两只的访问类型(0b00 ~ 0x11),Linux使用其中的三种(0b10不用),宏定义如下:
arch/arm/include/asm/domain.h

   [c]
#define DOMAIN_NOACCESS 0
#define DOMAIN_CLIENT 1
#define DOMAIN_MANAGER 3
[/c]

Linux在系统引导设置MMU时初始化c3寄存器来实现对内存域的访问控制。其中对DOMAIN_USER,DOMAIN_KERNEL和DOMAIN_TABLE均设置DOMAIN_MANAGER权限;对DOMAIN_IO设置DOMAIN_CLIENT权限。
arch/arm/include/asm/domain.h

   [c]
#define domain_val(dom,type) ((type) << (2*(dom)))

arch/arm/kernel/head.S
......
mov r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \
domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \
domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \
domain_val(DOMAIN_IO, DOMAIN_CLIENT))
mcr p15, 0, r5, c3, c0, 0 @ load domain access register
mcr p15, 0, r4, c2, c0, 0 @ load page table pointer
b __turn_mmu_on
ENDPROC(__enable_mmu)
[/c]

在系统的引导过程中对这3个域的访问控制位并不是一成不变的,它提供了一个名为modify_domain的宏来修改域访问控制位。系统在setup_arch中调用early_trap_init后,DOMAIN_USER的权限位将被设置成DOMAIN_CLIENT。
arch/arm/include/asm/domain.h

   [c]
#define set_domain(x) \
do { \
__asm__ __volatile__( \
"mcr p15, 0, %0, c3, c0 @ set domain" \
: : "r" (x)); \
isb(); \
} while (0)

#define modify_domain(dom,type) \
do { \
struct thread_info *thread = current_thread_info(); \
unsigned int domain = thread->cpu_domain; \
domain &= ~domain_val(dom, DOMAIN_MANAGER); \
thread->cpu_domain = domain | domain_val(dom, type); \
set_domain(thread->cpu_domain); \
} while (0)
[/c]