setup_arch函数用于处理与架构相关的一系列事物。
它调用了很多函数,先贴一张其调用的函数关系图。这些函数部分在本文介绍,部分会在后面几篇介绍。
setup_arch
void __init setup_arch(char **cmdline_p)
{
struct tag *tags = (struct tag *)&init_tags;
struct machine_desc *mdesc;
char *from = default_command_line;
默认的命令行参数是CONFIG_CMDLINE,应该是配置文件里定义的。如果uboot出来的atags有ATAG_CMDLINE类型,函数parse_tag_cmdline会用atags里的cmdline内容替换默认配置。应该是在下面的parse_tags函数里做的。
unwind_init();
初始化unwind信息,发生异常时,unwind信息判断将控制权移到何处
setup_processor();
设置处理器
mdesc = setup_machine(machine_arch_type);
查找机器信息结构体
machine_name = mdesc->name;
if (mdesc->soft_reboot)
根据机器信息结构体中的soft_reboot变量,设置reboot标示(变量reboot_mode)为‘s’(默认‘h’)。见reboot过程一节,这个玩意一般没什么用。
reboot_setup("s");
if (__atags_pointer)
这个变量是在函数__mmap_switched(汇编标识符)中保存的uboot传过来的第二个变量:atags指针。
tags = phys_to_virt(__atags_pointer);
将uboot传过来的第二个参数atags的地址转换为虚拟地址,存放到struct
tag结构体指针。
else if (mdesc->boot_params)
如果atags不存在,则用机器信息结构体中的boot_params参数替代。
tags = phys_to_virt(mdesc->boot_params);
下面开始解析atags
if (tags->hdr.tag != ATAG_CORE)
这个判断如果成立,表示实际的atags使用的存储方式现在无法适用(deprecated),需要转换格式。
convert_to_tag_list(tags);
if (tags->hdr.tag != ATAG_CORE)
如果转换后还没法用,那么就用内核自己默认的tags(init_tags)。
tags = (struct tag *)&init_tags;
if (mdesc->fixup)
见标题
” 在arm中设置meminfo的方法“
mdesc->fixup(mdesc, tags, &from,
&meminfo);
设置内存相关信息(设置到meminfo结构体),包含bank数,起始地址,大小等。
if (tags->hdr.tag == ATAG_CORE) {
if (meminfo.nr_banks != 0)
squash_mem_tags(tags);
这个函数的意思是,如果之前fixup函数已经设置了meminfo信息,则忽略atag中的mem设置信息。如果有好几个ATAG_MEM类型的atags,将分别设置到meminfo中的不同bank(是个数组)中。
save_atags(tags);
保存备份atags
parse_tags(tags);
分析atags信息。每种atags(ATAG_CORE、ATAG_MEM、ATAG_SERIAL等),都设置了自己的解析函数。使用宏__tagtable做初始化,比如__tagtable(ATAG_MEM,
parse_tag_mem32);
表示ATAG_MEM类型的atags使用函数parse_tag_mem32做解析。
}
init_mm.start_code = (unsigned long) _text;
init_mm.end_code = (unsigned long)
_etext;
init_mm.end_data = (unsigned long)
_edata;
init_mm.brk = (unsigned
long) _end;
memcpy(boot_command_line, from, COMMAND_LINE_SIZE);
boot_command_line[COMMAND_LINE_SIZE-1] = '\0';
parse_cmdline(cmdline_p, from);
分析并处理命令行参数,见标题
“parse_cmdline
处理启动参数”
paging_init(mdesc);
分页相关的准备工作(含有初始化启动过程中用到的内存分配器bootmem的初始化,使启动时以页为单位分配内存)
request_standard_resources(&meminfo, mdesc);
将不依赖平台而共同管理的资源信息构建成树状结构
#ifdef CONFIG_SMP
smp_init_cpus();
SMP环境下,初始化cpu_possible_map
#endif
cpu_init();
指定按ARM的IRQ、ABORT、SVC、UND(undefined instruction)模式使用的栈空间
init_arch_irq = mdesc->init_irq;
system_timer = mdesc->timer;
init_machine = mdesc->init_machine;
#ifdef CONFIG_VT
#if defined(CONFIG_VGA_CONSOLE)
conswitchp = &vga_con;
#elif defined(CONFIG_DUMMY_CONSOLE)
conswitchp = &dummy_con;
#endif
#endif
early_trap_init();
为了调用中断及异常代码,将各处理器代码及辅助器(helper)代码复制到异常向量表基址,并设置CPU域
}
unwind_init
unwind调试信息的初始化,可用于对栈异常的回溯。
int __init unwind_init(void)
{
struct unwind_idx *idx;
for (idx = __start_unwind_idx; idx <
__stop_unwind_idx; idx++)
idx->addr =
prel31_to_addr(&idx->addr);
pr_debug("unwind: ARM stack unwinding initialised\n");
return 0;
}
setup_machine
最终会调到之前讲的__lookup_machine_type函数,获取保存当前机器信息的结构体
machine_desc 的指针
arch/arm/kernel/head-common.S
ENTRY(lookup_machine_type)
这么一搞,可以让C函数去调用汇编函数
stmfdsp!, {r4 - r6, lr}
movr1, r0
bl__lookup_machine_type
movr0, r5
ldmfdsp!, {r4 - r6, pc}
ENDPROC(lookup_machine_type)
arch/arm/kernel/setup.c
static struct machine_desc * __init setup_machine(unsigned int
nr)
{
struct machine_desc *list;
list = lookup_machine_type
(nr);
if (!list) {
printk("Machine configuration botched (nr %d), unable "
"to continue.\n", nr);
while (1);
}
printk("Machine: %s\n", list->name);
return list;
}
reboot过程
执行reboot命令后,内核中依次调用:
kernel_restart() --> machine_restart()
--> arm_pm_restart()
这里的arm_pm_restart是个函数指针,初始化为arm_machine_restart(),它里面会调用setup_mm_for_reboot(reset前配置mmu,做到soft
boot)、arch_reset(architecture专有的配置代码),有些架构里会根据reboot_mode的值执行不同的代码。不过大部分系统都不使用。
在arm中设置meminfo的方法
1. 在机器信息结构体中的fixup函数中设置。
2. 接收启动加载项(uboot传来的)atags中的ATAG_MEM标签项并设置。
3. 利用内核命令行的"mem=参数"设置
parse_cmdline 处理启动参数
启动内核需要多种信息,这些信息是从uboot传过来的,或者是内核编译时静态获得的默认值。
传过来的信息一般是以“initrd = XXXX”这种格式。
内核在获得这些信息后,会调用相应的函数进行解析,以设置内核启动环境。
这些参数和函数的对应信息,是通过宏__early_param声明的
arch/arm/include/asm/setup.h
struct early_params {
const char *arg;
void (*fn)(char **p);
};
#define __early_param(name,fn)\
static struct early_params __early_##fn __used\
__attribute__((__section__(".early_param.init"))) = { name, fn
}
以initrd为例:
arch/arm/mm/init.c
__early_param("initrd=", early_initrd);
声明了一个struct
early_params结构体,名字是__early_early_initrd,其成员arg=
"initrd=",fn=early_initrd。
early_initrd函数将用来解析参数”initrd =
XXXX“。
这个结构体将会被放在.early_param.init段。
在vmlinux.lds.S中,这个段分配在变量__early_begin和__early_end之间。所以才有了下面的“for
(p = &__early_begin; p <
&__early_end;
p++) ”
arch/arm/kernel/vmlinux.lds.S
__early_begin = .;
*(.early_param.init)
__early_end = .;
static void __init parse_cmdline(char **cmdline_p, char
*from)
{
char c = ' ', *to = command_line;
int len = 0;
for (;;) {
if (c == ' ') {
extern struct early_params __early_begin, __early_end;
struct early_params *p;
for (p = &__early_begin; p <
&__early_end; p++) {
在“.early_param.init”段依次查找
int arglen = strlen(p->arg);
if (memcmp(from, p->arg, arglen) == 0) {
找到相应变量名后
if (to != command_line)
to -= 1;
from += arglen;
p->fn(&from);
调用相应函数解析
while (*from != ' ' && *from !=
'\0')
from++;
break;
}
}
}
c = *from++;
if (!c)
break;
if (COMMAND_LINE_SIZE <= ++len)
剩余的保存到command_line中的数据不能太长,按照arch/arm/include/asm/setup.h中的数据,不能超过1024(#define
COMMAND_LINE_SIZE 1024)
break;
*to++ = c;
}
*to = '\0';
*cmdline_p = command_line;
剩余没有处理的参数,保存到command_line中,保存到入参中返回。
}
内核参数很多,举例如下:
initrd
指定初始虚拟内存盘(ramdisk)的位置
console 提供控制台输出设备及选项
root
指定根文件系统
vmalloc
强制指定可用vmalloc区域的大小
request_standard_resources 构建资源树
该函数将不依赖于平台且被共同管理的资源信息构建成树状结构。可通过/proc/iomem查看此处构成的信息。
例如:
/mnt3 # cat /proc/iomem
00000000-7fffffff : System RAM
f80000000-f9fffffff : /pcie@ffe200000
f80000000-f9fffffff : PCI Bus 0000:01
f80000000-f8003ffff :
0000:01:00.0
ffe11c500-ffe11c507 : serial
ffe11c600-ffe11c607 : serial
ffe200e00-ffe200fff : mpc85xx_pci_err
arch/arm/kernel/setup.c
static void __init
request_standard_resources(struct meminfo *mi, struct
machine_desc *mdesc)
{
struct resource *res;
int i;
kernel_code.start = virt_to_phys(_text);
将内核code、data段的起始/结束虚拟地址转换为物理地址,保存到相应resource结构中
kernel_code.end =
virt_to_phys(_etext - 1);
kernel_data.start = virt_to_phys(_data);
kernel_data.end =
virt_to_phys(_end - 1);
for (i = 0; i < mi->nr_banks; i++)
{
利用mem_info中的信息,申请ram相应的resource,有几个有效bank就申请几个
if (mi->bank[i].size == 0)
continue;
res = alloc_bootmem_low(sizeof(*res));
在bootmem中为resource结构体分配内存
res->name = "System RAM";
res->start =
mi->bank[i].start;
res->end =
mi->bank[i].start + mi->bank[i].size
- 1;
res->flags = IORESOURCE_MEM |
IORESOURCE_BUSY;
request_resource(&iomem_resource, res);
挂载到iomem_resource的下一级
if (kernel_code.start >=
res->start &&
把kernel的code、data起始/结束地址对应的resource结构,挂载到对应的ram
bank的resource的下一级
kernel_code.end <=
res->end)
request_resource(res, &kernel_code);
if (kernel_data.start >=
res->start &&
kernel_data.end <=
res->end)
request_resource(res, &kernel_data);
}
if (mdesc->video_start) {
video_ram.start = mdesc->video_start;
video_ram.end =
mdesc->video_end;
request_resource(&iomem_resource,
&video_ram);
}
if (mdesc->reserve_lp0)
request_resource(&ioport_resource,
&lp0);
if (mdesc->reserve_lp1)
request_resource(&ioport_resource,
&lp1);
if (mdesc->reserve_lp2)
request_resource(&ioport_resource,
&lp2);
}
__request_resource是实际构造资源树的函数。是个比较简单的构建树的函数,注意在存放新的字节点时,会按照地址在原有子节点间排序。
static struct resource * __request_resource(struct resource
*root, struct resource *new)
{
resource_size_t start = new->start;
resource_size_t end = new->end;
struct resource *tmp, **p;
if (end < start)
return root;
if (start < root->start)
return root;
if (end > root->end)
return root;
p = &root->child;
for (;;) {
tmp = *p;
if (!tmp || tmp->start > end) {
如果子节点为0,或者原子节点的内存范围在新子节点的后面,则添加新的字节点到原子节点的前面。
new->sibling = tmp;
*p = new;
new->parent = root;
return NULL;
}
p = &tmp->sibling;
如果不符合之前的条件,则继续指向原子节点的兄弟节点。
if (tmp->end < start)
continue;
return tmp;
}
}
smp_init_cpus() 初始化 cpu possible 位图
cpu_possible_map是表示存在于系统中的所有CPU的位图
void __init smp_init_cpus(void)
{
unsigned int i, ncores = get_core_count();
获取core的数量
for (i = 0; i < ncores; i++)
cpu_set(i, cpu_possible_map);
所有可用的core设置到位图里
}
获取core的数量
static unsigned int __init get_core_count(void)
{
unsigned int ncores;
void __iomem*scu_base
= scu_base_addr();
从宏获取SCU寄存器的基地址
这里的__iomem表示告诉编译器要访问I/O内存,实际上这里的编译器会忽略它,所以这里的作用类似于注释。
if (scu_base) {
ncores = __raw_readl(scu_base + SCU_CONFIG);
ncores = (ncores & 0x03) + 1;
获取CPU数量后,用03进行位屏蔽,也就是核心数量最少1个,最多4个。
} else
ncores = 1;
return ncores;
}
cpu_init 指定各异常模式的栈指针
指定ARM的各种模式(IRQ/ABORD/SVC/UND)模式使用的栈空间
void cpu_init(void)
{
unsigned int cpu = smp_processor_id();
struct stack *stk = &stacks[cpu];
if (cpu >= NR_CPUS) {
printk(KERN_CRIT "CPU%u: bad primary CPU number\n", cpu);
BUG();
}
__asm__ (
"msrcpsr_c, %1\n\t"
进入IRQ模式
"addsp, %0, %2\n\t"
把当前模式的sp设置为
(&stacks[cpu])
->irq
(注:这是个地址)把一个模式的sp,指定为一个结构的变量的地址?
"msrcpsr_c, %3\n\t"
进入ABORT模式
"addsp, %0,
%4\n\t"把当前模式的sp设置为
(&stacks[cpu])
->irq
"msrcpsr_c, %5\n\t"
进入UND(undefined)模式
"addsp, %0, %6\n\t"
把当前模式的sp设置为
(&stacks[cpu])
->irq
"msrcpsr_c, %7"
返回到SVC模式
:
: "r"
(stk),
"I" (PSR_F_BIT | PSR_I_BIT | IRQ_MODE),
"I" (offsetof(struct stack, irq[0])),
"I" (PSR_F_BIT | PSR_I_BIT | ABT_MODE),
"I" (offsetof(struct stack, abt[0])),
"I" (PSR_F_BIT | PSR_I_BIT | UND_MODE),
"I" (offsetof(struct stack, und[0])),
"I" (PSR_F_BIT | PSR_I_BIT | SVC_MODE)
:
"r14");
}
struct stack {
u32 irq[3];
u32 abt[3];
u32 und[3];
} ____cacheline_aligned;
static struct stack stacks[NR_CPUS];
每个core一个
early_trap_init 初始化异常处理
void __init early_trap_init(void)
{
unsigned long vectors = CONFIG_VECTORS_BASE;
根据内核编译器的配置,获取异常向量基地址(arm中可以设置为0xFFFF0000或0x00000000)
extern char __stubs_start[], __stubs_end[];
extern char __vectors_start[], __vectors_end[];
extern char __kuser_helper_start[], __kuser_helper_end[];
int kuser_sz = __kuser_helper_end - __kuser_helper_start;
memcpy((void *)vectors, __vectors_start, __vectors_end -
__vectors_start);
复制发生异常时要执行的代码。其中有跳转到相应处理器的代码(其实是分不同的处理器模式),但现有状态下无效(完成下一句才行)。
memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end -
__stubs_start);
在跳转对象地址中复制要执行的代码。__stubs_start和__stubs_end之间定义了按不同模式执行的异常处理器代码。
memcpy((void *)vectors + 0x1000 - kuser_sz,
__kuser_helper_start, kuser_sz);
这部分区域中有__kuser_memory_barrier、__kernel_helper_version等辅助器代码。
memcpy((void *)KERN_SIGRETURN_CODE, sigreturn_codes,
将sigreturn_codes中存储的二进制代码拷贝到CONFIG_VECTORS_BASE+0x500的位置
sizeof(sigreturn_codes));
flush_icache_range(vectors, vectors + PAGE_SIZE);
刷新指令cache,使之前的拷贝生效,代码可执行
modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
设置CPU域(domain),这里把DOMAIN_USER(专门针对内存向量空间)这个domain设置成了DOMAIN_CLIENT权限,关于这一点起一个新标题"domain的概念"
}
下图是函数执行完毕后的异常向量分布表
看下实际的异常向量是如何定义的
.globl__vectors_start
__vectors_start:
swiSYS_ERROR0
bvector_und + stubs_offset
ldrpc, .LCvswi + stubs_offset
bvector_pabt + stubs_offset
bvector_dabt + stubs_offset
bvector_addrexcptn + stubs_offset
bvector_irq + stubs_offset
bvector_fiq + stubs_offset
.globl__vectors_end
__vectors_end:
一段汇编宏:
利用这段宏生成的代码将会被拷贝到0xffff0200(就是上面early_trap_init函数中__stubs_start开始的那段),这段代码长度不能超过0x300(因为0x500开始有别的用途)。
.macrovector_stub, name, mode, correction=0
表示这个宏接收要生成的标签名(name),要变更的模式(mode),LR偏差矫正(correction)
.align5
vector_\name:
.if \correction
sublr, lr, #\correction
根据参数矫正返回地址。为什么会需要矫正?根据发生的异常的不同,LR中保存的地址和需要真正返回的地址是不同的,有时会有一两个指令的地址差,需要矫正。这个问题新起一节《ARM
--异常发生时校正LR》
.endif
@
@ Save r0, lr_ (parent PC) and spsr_
@ (parent CPSR)
@
stmiasp, {r0, lr}@ save r0, lr
为了在处理异常后能够修复上下文,将寄存器r0和发生异常模式的lr、spsr保存到栈里。
mrslr, spsr
strlr, [sp, #8]@ save spsr
@
@ Prepare for SVC32 mode. IRQs remain
disabled.
@
mrsr0, cpsr
禁用中断,设置spsr(之前模式的状态寄存器),准备进入SVC模式。
eorr0, r0, #(\mode ^ SVC_MODE)
msrspsr_cxsf, r0
@
@ the branch table must immediately follow this code
(异常处理列表必须紧跟在本段代码之后)
@
andlr, lr, #0x0f
movr0, sp
ldrlr, [pc, lr, lsl #2]
准备跳转到在当前代码之后出现的异常处理器列表。该列表中有符合各模式的异常处理器地址
movspc, lr@ branch to handler in SVC mode
转换到SVC模式,这句话会把lr的值加载到pc,并将spsr值复制到cpsr
ENDPROC(vector_\name)
.endm
中断的处理
中断处理在异常向量表中的定义如下:
arch/arm/kernel/entry-armv.S
bvector_irq + stubs_offset
vector_irq是用vector_stub定义的:
vector_stubirq, IRQ_MODE, 4
定义了vector_irq标签,进入SVC模式,并跳转到相应模式的处理函数。相应模式的处理函数如下。这些定义是紧连在一起的。
.long__irq_usr@ 0
(USR_26 / USR_32)
.long__irq_invalid@ 1
(FIQ_26 / FIQ_32)
.long__irq_invalid@ 2
(IRQ_26 / IRQ_32)
.long__irq_svc@ 3
(SVC_26 / SVC_32)
.long__irq_invalid@ 4
.long__irq_invalid@ 5
.long__irq_invalid@ 6
.long__irq_invalid@ 7
.long__irq_invalid@ 8
.long__irq_invalid@ 9
.long__irq_invalid@ a
.long__irq_invalid@ b
.long__irq_invalid@ c
.long__irq_invalid@ d
.long__irq_invalid@ e
.long__irq_invalid@ f
所有模式都会发生中断,因此,每个模式均有自己的处理函数。linux内核中只有用户模式和SVC模式,因此,只有在这2个模式中的处理函数才有效。
下面看一下其中一个函数__irq_user。
__irq_usr:
usr_entry
这个宏将用户模式的上下文(主要是一些寄存器)保存到SVC模式的栈。
kuser_cmpxchg_check
如果在原子操作执行过程中产生了中断,可能引发问题,这里会处理这种异常问题。
get_thread_info tsk
tsk = 当前任务的thread_info的地址, tsk是寄存器r9(定义为
tsk.reqr9@ current thread_info)
#ifdef CONFIG_PREEMPT
ldrr8, [tsk, #TI_PREEMPT]@ get preempt count
addr7, r8, #1@ increment it
thread_info->preempt_count+1
表示禁止抢占
strr7, [tsk, #TI_PREEMPT]
#endif
irq_handler
调用宏irq_handler,用于获取中断号,并调用asm_do_IRQ()函数(另一标题有解释)。如果是SMP,还可能调用asm_do_IPI函数
#ifdef CONFIG_PREEMPT
ldrr0, [tsk, #TI_PREEMPT]
strr8, [tsk, #TI_PREEMPT]
thread_info->preempt_count-1
恢复
teqr0, r7
strner0, [r0, -r0]
#endif
movwhy, #0
bret_to_user
通过ret_to_user(另一标题有解释)返回发生过中断的用户模式
UNWIND(.fnend)
ENDPROC(__irq_usr)
调用asm_do_IRQ
之前讲的是从发生中断到调用此函数的全过程。
arch/arm/kernel/irq.c
asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct
pt_regs *regs)
{
struct pt_regs *old_regs = set_irq_regs(regs);
保存原有上下文的值,主要指一系列寄存器
irq_enter();
把thread_info->preempt_count+加上HARDIRQ_OFFSET,这里应该是1<<8,不知为何。
if (irq >= NR_IRQS)
handle_bad_irq(irq, &bad_irq_desc);
else
generic_handle_irq(irq);
这个函数会去调用经过set_irq_handler(注册符合各结构的中断处理函数)注册过的中断处理函数,这个处理函数内部又会去调用经过set_irq注册过的irqaction结构体的处理函数(就是一般我们在驱动中注册的中断处理函数),好绕。。
irq_finish(irq);
irq_exit();
thread_info->preempt_count再减去HARDIRQ_OFFSET。
这个函数很有学问,涉及到软中断的操作,在此不展开。
set_irq_regs(old_regs);
恢复原有上下文的值
}
ret_to_user 中断返回
处理中断后,需要返回之前正在执行的状态,这个标签就干这个事。
不过如果在thread_info->flags中设置了某些状态(需要重新调度或者有未处理的信号),会做相应处理。如果重新调度,就去schedule。如果有未处理信号,就去处理信号,然后回来继续尝试ret_to_user。
ENTRY(ret_to_user)
ret_slow_syscall:
disable_irq@ disable interrupts
ldrr1, [tsk, #TI_FLAGS]
如果thread_info->flags的值中的低级8位已被设置了值,就跳到work_pending(下面说明),否则就继续往下走。
tstr1, #_TIF_WORK_MASK
bnework_pending
no_work_pending:
arch_ret_to_user r1, lr
@ slow_restore_user_regs
ldrr1, [sp, #S_PSR]@ get calling cpsr
ldrlr, [sp, #S_PC]!@ get pc
将发生中断时保存到栈的上下文恢复到用户模式寄存器
msrspsr_cxsf, r1@ save in spsr_svc
ldmdbsp, {r0 - lr}^@ get calling r0 - lr
movr0, r0
addsp, sp, #S_FRAME_SIZE - S_PC
将当前(中断模式下)栈指针缩小到保存上下文之前的大小
movspc, lr@ return & move spsr_svc into cpsr
返回用户模式,继续执行之前的代码
ENDPROC(ret_to_user)
work_pending:
tstr1, #_TIF_NEED_RESCHED
如果thread_info->flags值中设置了_TIF_NEED_RESCHED,表示需要重新调度任务,就跳转到work_resched,会调用schedule()去尝试切换新任务
bnework_resched
tstr1, #_TIF_SIGPENDING
如果thread_info->flags值中设置了_TIF_SIGPENDING,表示有未处理的信号,就调用do_notify_resume,其会调用do_signal去处理信号。处理完后会再次执行ret_to_user,尝试返回中断前的状态继续执行。
beqno_work_pending
movr0, sp@ 'regs'
movr2, why@ 'syscall'
bldo_notify_resume
bret_slow_syscall@ Check work again
work_resched:
blschedule
ARM中domain的概念
下面这段蓝色字体摘自《页表机制
》,http://lli_njupt.0fees.net/ar01s12.html
domain代表了内存域。在ARM处理器中,MMU将整个存储空间分成最多16个域,记作D0~D15,每个域对应一定的存储区域,该区域具有相同的访问控制属性。
arch/arm/include/asm/domain.h #define DOMAIN_KERNEL 0
#define DOMAIN_TABLE 0 #define DOMAIN_USER 1 #define DOMAIN_IO
2
ARM Linux
中只是用了16个域中的三个域D0-D2。它们由上面的宏来定义,在系统引导时初始化MMU的过程中将对这三个域设置域访问权限。以下是内存空间和域的对应表:
表 21. 内存空间和域的对应表
内存空间
域
设备空间
DOMAIN_IO
内部高速SRAM空间/内部MINI Cache空间
DOMAIN_KERNEL
RAM内存空间/ROM内存空间
DOMAIN_KERNEL
高低端中断向量空间
DOMAIN_USER
在ARM处理器中,MMU中的每个域的访问权限分别由CP15的C3寄存器中的两位来设定,c3寄存器的小为32bits,刚好可以设置16个域的访问权限。下表列出了域的访问控制字段不同取值及含义:
表 22. ARM内存访问控制字
值
访问类型
含义
0b00
无访问权限
此时访问该域将产生访问失效
0b01
用户(client)
根据CP15的C1控制寄存器中的R和S位以及页表中地址变换条目中的访问权限控制位AP来确定是否允许各种系统工作模式的存储访问
0b10
保留
使用该值会产生不可预知的结果
0b11
管理者(Manager)
不考虑CP15的C1控制寄存器中的R和S位以及页表中地址变换条目中的访问权限控制位AP,在这种情况下不管系统工作在特权模式还是用户模式都不会产生访问失效
Linux定义了其中可以使用的三种域控制:
arch/arm/include/asm/domain.h #define DOMAIN_NOACCESS 0
#define DOMAIN_CLIENT 1 #define DOMAIN_MANAGER 3
Linux在系统引导设置MMU时初始化c3寄存器来实现对内存域的访问控制。其中对DOMAIN_USER,DOMAIN_KERNEL和DOMAIN_TABLE均设置DOMAIN_MANAGER权限;对DOMAIN_IO设置DOMAIN_CLIENT权限。
ARM --异常发生时校正LR
ARM指令的执行,一般采用多管道流水线的方法。
一个指令的运行分为获取、解码、执行三步。
简单的说,比如当前指令如果在执行时遇到问题需要处理异常(比如data
abort),此时的PC(LR也是)其实指向的是获取阶段的地址,就是指向当前指令后两个的那条指令的地址(一般是当前指令地址+8)。异常处理完成后,需要返回到当前指令再次执行,此时就需要将LR-8。
具体说明几种异常:
1. reset
硬件信号导致,无需返回,也就无需设置返回地址
2. undefined
解码阶段无法解析指令时触发。PC/LR指向获取阶段的地址(即当前地址+4)。异常处理返回时,需返回发生异常地址的下一个地址,所以LR无需偏移。
3. prefetch abort
在获取指令阶段,没有权限或者试图读取错误地址时发生。PC指向预取指令的地址,但此时LR指向下一个地址(即+4)。异常处理后,会再次执行发生异常的指令,此时需LR-4.
4. data abort
实际执行时才能知道数据的有效性。所以此异常在执行时发生,PC指向fetch阶段的地址(当前地址+8)。异常处理后,LR-8,重新执行发生异常的指令。
5. SWI(software interrupt)
发生在解码阶段,系统程序员为了转换成SVC模式而标明命令,由此引发SWI异常(我觉得应该指系统调用?)。PC指向获取阶段的地址(即当前地址+4)。返回时执行下一条指令,LR无需处理。
6. IRQ
硬件引发。PC指向获取阶段的地址(当前+8)。异常处理完成后需要执行下一条指令,所以LR-4。