3 基于Linux的实现
考虑到从硬件层开始构建一个操作系统的难度,Adeos并没有并没有从零开始构建一个硬件抽象层;目前,Adeos是基于Linux内核实现的,这样的话,就可以将系统的启动和初始化工作都由Linux来完成,在系统完成初始化后,再进行Adeos的初始化工作(包括接管Linux的中断管理机制),Adeos功能既可以直接编译进内核,也可以作为一个内核模块在系统运行时动态加载,就和内核的驱动程序模块一样。
在这种实现方法下,Linux作为Adeos的一个特殊的域存在,我们称之为根域(Root Domain)。Adeos的很多功能都是依靠根域(也就是Linux内核)来实现的,例如,动态注册其它的域模块是通过Linux的动态模块加载功能实现的,为其它域的任务分配任务堆栈是通过Linux内存分配接口实现的等。根域的初始化是在Adeos的初始化过程中完成的;根域对于Adeos来说,有一点类似于Linux初始化过程中创建的INIT进程;
------------------
3.1 基本架构
Adeos在Linux配置中增加了三个配置开关来配置Adeos的代码:CONFIG_ADEOS_CORE,CONFIG_ADEOS和CONFIG_ADEOS_MODULE。如果定义了CONFIG_ADEOS_CORE,Adeos的核心支持就被编译进了Linux内核,不论Adeos功能最终编译进内核还是编译成可动态加载的模块,这个编译选项都必须被定义;如果定义了CONFIG_ADEOS,则也隐含着对CONFIG_ADEOS_CORE的定义,Adeos功能就被编译进了内核,那么,从Linux启动以后,Adeos功能就被使能了;如果定义了CONFIG_ADEOS_MODULE,则也隐含着对CONFIG_ADEOS_CORE的定义,Adeos功能被编译成可动态加载的模块,只有当这个模块被加载后,Adeos的功能才会起作用。
Adeos对Linux源代码树的修改涉及到30多个文件(包括新增加的文件),如图3-4所示。
其中的Makefie和Config.in文件,是为Adeos代码添加内核编译选项和内核配置选项;
adeos目录下的generic.c包含了与平台无关的通用的Adeos代码,而armv.c则包含了与ARM平台相关的通用于ARM平台的Adeos代码;
在arch/arm/kernel目录下,adeos.c中包含了Adeos关于中断处理的代码;armksyms.c中增加了代码,导出了ARM体系相关的Adeos接口;entry-armv.S和entry-common.S中增加了截获Linux中断和系统事件的代码,域的切换代码也在此处实现;同时,修改了irq.c以适应Adeos的中断处理机制;在process.c中修改了Linux的idle进程,当Linux进入idle状态时,将通知Adeos;time.c中增加了Adeos中修改定时器频率的接口;
Documentation/adeos.txt对Adeos进行了简单的介绍;
在init/main.c中增加了对Adeos进行初始化的代码;
kernel/adeos.c中包含的是Adeos对根域也就是Linux操作的代码;对kernel目录下的其它文件的修改,主要是为了增加Adeos对Linux系统事件进行捕获所需的代码;
3.2 源代码分析
-------------------------
3.2.1 Adeos相关实体
这里根据Adeos内全局量的重要性,对所有组成Adeos实体的全局量进行分析,包括其初始化、取值范围、功能等。
struct list_head __adeos_pipeline;
spinlock_t __adeos_pipelock = SPIN_LOCK_UNLOCKED;
在文件kernel/adeos.c中定义;Adeos的中断管道是用Linux的标准链表来实现的,而这个链表的头就是__adeos_pipeline,它实际上就是中断管道的访问入口,每个域都通过一个链表项按优先级连接在这个链表上。而__adeos_pipelock则是用来保护中断管道的互斥访问。
static adomain_t adeos_root_domain;
adomain_t *adp_root = &adeos_root_domain;
adomain_t *adp_cpu_current[ADEOS_NR_CPUS] = { [ 0 ... ADEOS_NR_CPUS - 1] = &adeos_root_domain };
在文件kernel/adeos.c中定义(本分析中的文件路径都是相对于Linux内核代码树的根目录而言的);adeos_root_domain是由Adeos静态定义的根域(也就是Linux);adp_root是指向根域的指针;而数组adp_cpu_current[]则是保存着指向CPU上当前运行的域的指针,ADEOS_NR_CPUS是系统中CPU的个数。
int __adeos_event_monitors[ADEOS_NR_EVENTS] = { [ 0 ... ADEOS_NR_EVENTS - 1] = 0 };
在文件kernel/adeos.c中定义;此数组中的每个元素对应着Linux中的一个系统事件(例如,进入系统调用事件、退出系统调用事件等),当某个域想要跟踪某个系统事件是,它就调用adeos_catch_event,告诉Adeos它想要跟踪这个系统事件,并将事件的处理函数传递给Adeos。Adeos根据事件的类型将__adeos_event_monitors数组中的某个元素的值加一或减一(当事件处理函数为空时减一,否则加一)。当某个系统事件发生时,Adeos根据事件类型,检查__adeos_event_monitors数组中的相应元素的值是否大于零,如果大于零,则说明某个域想跟踪这个系统事件,那么就将这个事件传递给中断管道处理,处理过程中会调用域的事件处理程序。
static struct irqdesc __adeos_std_irq_desc[NR_IRQS];
在文件adeos/armv.c中定义的,它的赋值是在__adeos_enable_pipeline中进行的,代码段如下:
for (irq = 0; irq < NR_IRQS; irq++)
__adeos_std_irq_desc[irq] = irq_desc[irq];
其中的数组irq_desc[]是标准Linux中用来保存中断描述符的;所以,从这里就可以看出,__adeos_std_irq_desc[]数组保存的是标准的Linux中断描述符;由于Adeos需要接管Linux的中断管理,就要修改irq_desc[]数组的内容,因此,就将修改前的标准的Linux中断描述符保存起来。一方面,在卸载Adeos模块的时候可以恢复(在函数__adeos_disable_pipeline中调用),另一方面,在Adeos的代码中还需要调用标准描述符中的代码(例如,在__adeos_override_irq_mask中)。
unsigned long __adeos_virtual_irq_map = 0;
在文件kernel/adeos.c中定义的;变量__adeos_virtual_irq_map记录着虚拟中断的分配情况,在32位处理器上,共有32个虚拟中断,从0到31;当某一位为1时,表示虚拟中断已经被分配了,为0时,则没有分配;Adeos用虚拟中断来提供域之间的通信,当某个域需要执行另一个域的某些操作时,可以触发一个虚拟中断,当这个虚拟中断沿着中断管道传播时,接收这个虚拟中断的域就会执行相应的操作,这在处理上和硬件中断类似,只不过这个中断是由域触发的而已。
int adp_pipelined = 0;
在文件arch/arm/kernel/adeos.c中定义的;表示Adeos是否已经接管根域的中断管理,也就是说,表示中断管道当前是否有效。很多代码都要通过判断这个标志量来确定应该执行的操作。
----------------------------
3.2.2 域(Domain)相关实体
Adeos中的每个域都由一个域结构变量表示,这个结构变量保存着域的属性和和状态;域结构adomain_t的定义如下:
typedef struct adomain {
/* -- Section: offset-based references are made on these fields
from inline assembly code. Please don't move or reorder. */
void (*dswitch)(void); /* Domain switch hook */
int *esp[ADEOS_NR_CPUS]; /* Domain stack pointers */
/* -- End of section. */
int *estackbase[ADEOS_NR_CPUS];
unsigned domid;
const char *name;
int priority;
struct adcpudata {
unsigned long status;
unsigned long irq_pending_hi;
unsigned long irq_pending_lo[IPIPE_IRQ_IWORDS];
unsigned irq_hits[IPIPE_NR_IRQS];
adevinfo_t event_info;
} cpudata[ADEOS_NR_CPUS];
struct {
int (*acknowledge)(unsigned irq);
void (*handler)(unsigned irq);
unsigned long control;
} irqs[IPIPE_NR_IRQS];
struct {
void (*handler)(adevinfo_t *evinfo);
} events[ADEOS_NR_EVENTS];
int ptd_keymax;
int ptd_keycount;
unsigned long ptd_keymap;
void (*ptd_setfun)(int, void *);
void *(*ptd_getfun)(int);
struct adomain *m_link; /* Link in mutex sleep queue */
struct list_head p_link; /* Link in pipeline */
} adomain_t;
dswitch是一个函数指针,指向一个钩子函数,这个函数在域的切换过程中被调用;esp[ADEOS_NR_CPUS]是一个指针数组,域在不同的CPU上运行的时候有不同的栈,而esp则用来在域切换之前保存当前栈的位置;estackbase[ADEOS_NR_CPUS]则保存着每个栈的栈底;domid是域的识别号,这个值对每个域都是唯一的;name则是域的名字;priority表示域的优先级,也就是域在中断管道中的优先级;cpudata和irqs都与中断处理有关,在后面分析Adeos的中断处理的时候再详细讨论;events保存着域的事件处理程序;所有以ptd_开头的字段,都和所谓的Per-thread data key有关;m_link是一个指向域的指针,用于域睡眠时加入等待队列;p_link则是用于中断管道的连接。
------------------------
3.2.3 Adeos初始化
Adeos的初始化是在Linux的初始化过程中调用的,是由函数start_kernel来调用,初始化函数为__adeos_init();这个函数的定义在文件kernel/adeos.c中,它完成的主要工作如下:
初始化根域adeos_root_domain;
分配一个虚拟中断__adeos_printk_virq,主要用来完成其它域对根域请求的printk内核输出操作;
Adeos接管Linux的中断管理机制,这是通过调用__adeos_takeover()实现的;
__adeos_takeover()仅仅调用__adeos_enable_pipeline()来完成中断的接管操作;__adeos_enable_pipeline()定义在adeos/armv.c中,它的主要功能如下:
1.调用函数adeos_virtualize_irq指定所有根域中断的中断处理函数和中断确认函数;
2.将Linux原来的中断描述符保存在数组__adeos_std_irq_desc[]中;
3.用Adeos相关的中断屏蔽、中断响应等函数替换原有Linux的相应函数;
4.将标志变量adp_pipelined置为1;
至此,Adeos的初始化就已经完成;初始化完成后,系统中存在着一个域,那就是根域(root domain),这时Linux的中断管理机制已经由Adeos来管理。
----------------
3.2.4 域注册
Adeos系统中,除了根域(root domain)是在Adeos初始化过程中被静态创建的之外,其它的所谓的客户域(client domain),在使用Adeos提供的服务之前,都必须通过域注册函数int adeos_register_domain (adomain_t *adp, adattr_t *attr)先进行注册。参数adp指向将要被注册的域实体结构变量,参数attr则指向一个域属性结构变量,注册函数将根据attr中的属性对adp进行初始化;域注册函数完成的功能如下:
1.检查当前域是否是根域(Linux),只有根域才可以注册其它的域,因为在目前的Adeos实现下,其它的域都要以Linux下内核动态模块的形式存在;
2.遍历所有的域,检查当前需要注册的域是否已经存在(根据域标志domid来判断),如果已经存在,则出错返回;
3.对域结构变量内的字段进行初始化;通过__adeos_init_domain()函数对域的栈进行初始化,并给栈赋初值,为域的首次运行做好准备;
4.查找中断管道的域列表,根据域的优先级找到新域在中断管道中的位置;并将新域插入中断管道;
5.调用__adeos_switch_to()函数切换到新域;
在attr所指的域属性结构中,有一个字段attr->entry,它是一个函数指针,它指向域自己定义的初始化函数,这个函数地址在初始化域的程序栈的时候被保存在了栈中,当域第一次执行的时候,将会从栈中取出这个初始化函数的地址,执行域自己定义的初始化。
----------------------------
3.2.5 Adeos中断处理
Adeos中断处理函数的入口是asmlinkage int __adeos_handle_irq (int irq, struct pt_regs *regs),它的定义在arch/arm/kernel/adeos.c中;这个函数既用来处理硬件中断,也用来处理软件中断(这里所说的软件中断是由域触发,用于域之间的通信);所以,这个函数在两个地方被调用。
第一个,是在系统的中断处理入口,在基于ARM的平台中,中断处理入口都在文件arch/arm/kernel/entry-armv.S中,Adeos用这个函数替换了Linux原来的中断处理函数do_IRQ(),例如,USER MODE模式下中断处理代码片断如下:
__irq_usr: sub sp, sp, #S_FRAME_SIZE
stmia sp, {r0 - r12} @ save r0 - r12
ldr r4, .LCirq
add r8, sp, #S_PC
ldmia r4, {r5 - r7} @ get saved PC, SPSR
stmia r8, {r5 - r7} @ save pc, psr, old_r0
stmdb r8, {sp, lr}^
alignment_trap r4, r7, __temp_irq
zero_fp
1: get_irqnr_and_base r0, r6, r5, lr
movne r1, sp
adrsvc ne, lr, 1b
@
@ routine called with r0 = irq number, r1 = struct pt_regs *
@
#ifdef CONFIG_ADEOS_CORE
bne __adeos_handle_irq
#else /* !CONFIG_ADEOS_CORE */
bne do_IRQ
#endif /* CONFIG_ADEOS_CORE */
mov why, #0
get_current_task tsk
b ret_to_user
第二个,是在函数adeos_trigger_irq中被调用,当某个域想要触发某个软件中断时,就可以调用软件中断触发函数adeos_trigger_irq产生一个软件中断,这个软件中断就会沿着中断管道传播,触发其它域执行相应的处理函数,这样就可以达到域之间通信的目的。
Adeos中断处理函数__adeos_handle_irq完成的功能如下:
1.判断标志变量adp_pipelined的值,如果为零,则说明中断管道现在不可用,则调用Linux的中断处理入口函数do_IRQ来进行中断处理;
2.依次遍历中断管道,如果域接受相应的中断,则将此中断记录在域的中断日志中,包括记录中断类型和中断次数,然后调用中断应答函数;
3.调用函数__adeos_walk_pipeline,沿着中断管道执行域的中断处理函数,在执行完域的中断处理函数后,Adeos会切换到这个域,也就是将CPU的控制权交给这个域,当这个域执行完所有的操作后,域就主动挂起,控制权交回Adeos,Adeos在执行中断管道中随后的域的中断处理程序并根据需要交换控制权,直到所有的域都执行完。
每个域与中断相关的数据主要存放在域结构变量的两个字段中,分别是cpudata和irqs,它们的定义如下:
struct adcpudata {
unsigned long status;
unsigned long irq_pending_hi;
unsigned long irq_pending_lo[IPIPE_IRQ_IWORDS];
unsigned irq_hits[IPIPE_NR_IRQS];
adevinfo_t event_info;
} cpudata[ADEOS_NR_CPUS];
struct {
int (*acknowledge)(unsigned irq);
void (*handler)(unsigned irq);
unsigned long control;
} irqs[IPIPE_NR_IRQS];
在结构变量cpudata[ADEOS_NR_CPUS]中,每个元素包含在某个CPU上的中断信息;其中,字段status表示当前域的状态,它的取值范围如下:
IPIPE_STALL_FLAG:表示域禁止中断,当硬件中断沿着中断管道传递到这个域的时候,Adoes将不会执行域的中断处理程序,也不会将中断沿着中断管道继续向下传播;
IPIPE_SYNC_FLAG:表示Adoes正在为这个域执行中断处理程序(被称为IRQ同步),保护同步代码不被同步执行;
IPIPE_XPEND_FLAG:表示域收到了异常(或者说系统事件,如进入、退出系统调用等)通知消息,而且还没有对其进行处理;
IPIPE_SLEEP_FLAG:表示域已经将自己挂起,也就是说,现在是其它的域在运行;当一个域刚被注册的时候就是处于被挂起的状态;
在cpudata中,字段irq_pending_hi,irq_pending_lo[IPIPE_IRQ_IWORDS]和irq_hits[IPIPE_NR_IRQS]用于保存域接收到的中断信息。IPIPE_NR_IRQS为系统中断个数(包括用于域间通信的软件中断)irq_hits[]的每个元素对应于某一个中断发生的次数;irq_pending_lo[]是一个映射表,每一位对应于一个中断,当某个中断发生时,就将这个映射表中的相应位置1表示相应的中断发生了;irq_pending_hi也是一个映射表,其中的每一位对应于irq_pending_lo数组中的每一个元素;例如,当接收到32号中断后,irqs_hits[32]的值加1,irq_pending_lo[1]的位0被置1(相当于映射表的位32),irq_pending_hi的位1被置1(表示数组irq_pending_lo的第2个元素有位被置1)。
在cpudata中的字段event_info则是用来保存接收到的系统事件通知的信息,它的结构如下:
typedef struct adevinfo {
unsigned domid;
unsigned event;
void *evdata;
volatile int propagate; /* Private */
} adevinfo_t;
其中的字段domid表示系统事件是在哪个域发生的;event表示事件类型;evdata则是传递给事件处理函数的参数;propagate指示执行完事件处理函数后是否将这个事件通知传递给中断管道中后面的域。
irqs[IPIPE_NR_IRQS]保存着中断的中断处理函数、中断应答函数和中断的处理模式;中断处理模式字段control是一个位掩码,目前中断处理共有四种模式:
1.域不处理中断而直接将中断传递给低优先级的域(control = IPIPE_PASS_MASK);
2.域对中断进行处理,但不再将中断传递给低优先级的域(control = IPIPE_HANDLE_MASK);
3.域对中断进行处理并将之传递给低优先级的域(control = IPIPE_HANDLE_MASK | IPIPE_PASS_MASK);
4.域既不对中断进行处理,也不将中断传递给中断管道中优先级的域;
从上面的分析可以看出,Adeos的中断入口函数仅仅只是将中断信息纪录在每个域的中断相关的日志内,每个域的中断处理函数的执行实际上是由中断管道来调度的(根据域的优先级);中断管道对域的中断函数的执行是通过对函数__adeos_walk_pipeline的调用实现的(在函数__adeos_handle_irq的最后被调用)。
在分析__adeos_walk_pipeline之前,先考虑一下一个域在Adeos系统中所处的状态,将会有助于对后面分析的理解。Adeos系统中的每个域,都处于以下3种状态之一:
第一种是域处于运行状态,在这种情况下,中断管道中比当前运行状态的域优先级高的域都处于挂起状态,也就是说,高优先级的域都没有任务、中断或系统事件需要处理,他们都通过调用函数adeos_suspend_domain将本身挂起;而中断管道中比当前运行域优先级的的域则或者处于挂起状态或者处于被中断状态;
第二种是域处于挂起状态,这说明这个域是通过调用adeos_suspend_domain将本身挂起的,在挂起的时候域没有任务或中断或事件需要处理。但经过一段时间后,处于挂起状态的域,可能也有需要处理的中断或事件通知,只不过现在有更高优先级的域在运行而已。
第三种是域处于被中断的状态,当一个域正处于运行态时,如果这个时候产生中断,或是触发了一个系统事件,Adeos就开始进行中断的处理,如有更高优先级的域需要处理这个中断的时候,Adoes就会调度高优先级的域先运行,那么,这个被中断的域就处于被中断状态了。
__adeos_walk_pipeline定义在文件arch/arm/kernel/adeos.c中,它完成的主要功能如下:
1.沿着中断管道从头开始对域进行处理;
2.如果域禁止中断,则不再对之后的域进行中断的处理;
3.如果某个域有需要处理的中断,则调用域切换函数__adeos_switch_to从当前域切换到那个域,那个域在恢复运行的时候会通过调用函数__adeos_sync_stage执行中断处理函数;
4.如果遍历到了当前域,则直接调用__adeos_sync_stage执行当前域的中断处理函数,然后就可以退出函数__adeos_walk_pipeline,紧接着退出Adeos的中断处理函数__adeos_handle_irq,返回被中断的域继续运行。
__adeos_sync_stage的主要功能是执行域的中断处理函数,并更新域结构的中断相关日志。
__adeos_switch_to的功能就是通过调用汇编函数__adeos_switch_domain进行域切换,然后再调用域切换的钩子函数dswitch。