嵌入式实时Hypervisor:XtratuM (14)

2.8 系统移植
系统移植的目的是将XtratuM向不同的Linux内核上面移植,从而可以让XtratuM支持多种Linux内核。早期,XtratuM是在Linux-2.6.17-4的内核上。现在我们已经成功将XtratuM移植到了多种Linux内核上。图2-19给出了XtratuM当前支持的内核版本。
图2-19. XtratuM支持的Linux内核

由于XtratuM运行在整个系统的低端,尤其是对底层设备进行管理,并其在不同版本的Linux内核中,相应实现机制差别较大,为此,在XtratuM的移植过程中,存在许多值得读者明确的事情。本节中,将就XtratuM向Linux系统移植过程中存在的中断、系统调用以及任务管理等内容进行讨论。对这些内容的介绍是很具体的,其目的不是让读者了解内核版本的不同,而是明确底层对中断、系统调用的实现过程。
2.8.1 中断管理
中断管理是操作系统中一个重要的内容,由于中断管理位于系统的底层,并且,在XtratuM系统中,中断管理位于系统的内核中,可见其对系统的影响力。为此,对中断管理的了解程度,直接关系系统移植和实现的难度系数。在XtratuM系统的移植过程中,由于Linux系统对中断管理的变化,导致了在XtratuM的移植过程中碰到了一些难题。本节就是详细介绍中断管理。
谈到中断管理,首先要介绍的是中断向量表。中断向量表是一个保存中断入口地址信息的数组,其首地址根据平台的不同,采用的策略不一样。例如在x86中,中断向量的首地址在sidt寄存器中,有些平台将中断向量表直接存放在以“0”开始的物理内存中。在x86中,向量中的元素是具有8个字节,除了包含相应的中断函数入口地址以外,还有中断门类型以及中断门权限。
那么什么时候CPU开始访问中断向量表并且进入中断向量表中指向的函数呢?在x86系统中,由一个重要的保存寄存器数值的结构体,它就是struct pt_regs。
struct pt_regs {
long ebx;
long ecx;
long edx;
long esi;
long edi;
long ebp;
long eax;
int xds;
int xes;
long orig_eax;
long eip;
int xcs; -42-
兰州大学硕士学位论文
long eflags;
long esp;
int xss;
};
通常,这个结构体就存放在栈中。当中断发生的时候,CPU会首先将XSS,ESP,EFlags,XCS,EIP寄存器的内容压入栈中,然后CPU跳转到中断向量表中对应元素指向的地址。那么进入函数后系统会做些什么呢?下面的表1给出了Linux 2.6.17.4与Linux 2.6.18.8给出了公用中断定义的函数代码。
Linux 2.6.18.8
Linux 2.6.17.4
vector=0
ENTRY(irq_entries_start)
pushl $~(vector)
jmp common_interrupt
.text
vector=vector+1
.endr
common_interrupt:
SAVE_ALL
movl %esp,%eax
call do_IRQ
jmp ret_from_intr
vector=0
ENTRY(irq_entries_start)
1: pushl $vector-256
jmp common_interrupt
.text
vector=vector+1
.endr
common_interrupt:
SAVE_ALL
movl %esp,%eax
call do_IRQ
jmp ret_from_intr
表1 Linux内核中通用中断函数的定义
其中vector是中断向量号。中断向量表的地址就是指向irq_entries_start,当CPU将前面几个寄存器压入栈后,系统就跳到该地址,继续执行。在Linux 2.6.17.4中,CPU将[vector-256]压入栈中,也就填入orig_eax的地方,然后执行SAVE_ALL,SAVE_ALL的作用就是将其余的寄存器值压入栈中,movl %esp,%eax指令将esp指针的数值给eax寄存器,最好调用do_IRQ()函数。do_IRQ()的参数是一个pt_regs指针,也就是eax寄存器的值,即esp的值,从而可以通过访问pt_regs->orig_eax来获取中断号。即
irq = pt_regs->orig_eax & 0xFF;
但是在Linux 2.6.18.8中,由于orig_eax的数值是由[~vector]得到,因此,在do_IRQ()中,中断号的获取也有了相应的变化。
irq=~(pt_regs->orig_eax);
在XtratuM系统,同样有一套类似的中断公用函数,但是有一定的区别。首先通用中断函数的参数是struct pt_regs,而不是struct pt_regs *,从而可以不需要eax传递指针。下面是XtratuM产生中断函数的宏定义。
#define BUILD_IRQ(irq) /
asmlinkage void IRQ_NAME(irq); /
__asm__ (".section irq_handlers_addr,/"a/"/n/t" /
SYMBOL_NAME_STR(irq_handler_) #irq ":/n/t" /
"pushl $"#irq"-256/n/t" /
"jmp " SYMBOL_NAME_STR(common_irq_body) "/n/t")
#define BUILD_COMMON_IRQ_BODY() /
__asm__ (".text/n/t" /
"/n" __ALIGN_STR"/n" /
SYMBOL_NAME_STR(common_irq_body) ":/n/t" /
"cld/n/t" /
HW_SAVE_ALL /
"call " SYMBOL_NAME_STR(irq_handler) "/n/t" /
"testl %eax,%eax/n/t" /
"jnz 1f/n/t" /
HW_RESTORE_ALL /
"1: cld/n/t" /
"jmp *(" SYMBOL_NAME_STR(XM_root_func) " + 20)/n")
在XtratuM中,系统首先跳转到(irq_handler_) #irq地址处,与Linux一样,它将[$"#irq"-256 ]压入栈中,然后调用common_irq_body,继续执行HW_SAVE_ALL,HW_SAVE_ALL的任务通Linux系统中的SAVE_ALL 一样,它将需要的寄存器压入栈中。下面是XtratuM调用irq_handler()函数,进入通用中断处理函数。但是与Linux不同,irq_handler()会返回一个数值,这个数值是用来判断当前执行域是否是Linux,如果是Linux,返回真,否则是假,并且将结果保存在eax寄存器中。 testl %eax,%eax就是用来判断返回结果,如果是“真值”,则跳到*(" SYMBOL_NAME_STR(XM_root_func) " + 20)这个函数,实际上,该函数就是Linux的do_IRQ()函数。问题就在该函数的调用上,在Linux-2.6.17.4中,中断号保存都采用[irq-256]这个数值,因此不会出现问题,但是在Linux-2.6.18.8中,XtratuM依然用[irq-256]填充orig_eax的数值,但是Linux-2.6.18.8的do_IRQ()则是用irq=~(pt_regs->orig_eax)获取,因此系统会获取错误的中断号,从而引起系统崩溃。
2.8.2 系统调用
系统调用是一个特殊的中断事件,它是由软件指令引起的,即INT族指令。在Linux系统中,采用INT 0x80指令引起系统调用,这说明1)Linux系统存在系统调用,并且不能丢弃;2)0x80已经被占用。因此,在XtratuM系统,0x82被选用作为Hypercall的INT号,当然,设计者也可以选择其它的没有占用的INT号。
作为一种特殊的系统调用,系统调用除了具有中断处理的一般过程之外,它还具有其特殊的性质,每个系统调用函数具有唯一的系统调用号,并且系统调用函数可以具有参数。类似于中断向量表,在系统调用的机制中,也存在一个系统调用表,该表用来存放系统调用函数的地址。但是,中断号可以是系统分配好的,因此中断发生后,CPU会直接选中中断的处理函数,但是,系统调用是软件的,应该由程序告诉CPU应该如何获取系统调用号。因此,在系统调用中,程序应该指定系统调用号以及相应参数。
通常,系统采用两种方式传递参数,一种是使用寄存器,另外一种是使用栈。因此,在具体的实现中,这是一个不可忽视的问题。在Linux-2.6.17.4中,Hypercall定义了一些列的系统调用宏,例如__syscall2是用来定义具有两个参数的系统调用。
#define __syscall2(arg1, arg2, name_nr, __res) /
__asm__ __volatile__ ("int $0x82/n/t" /
: "=a" (__res) /
: "0" (name_nr),"b" ((long)(arg1)), /
"c" ((long)(arg2)))
从上面这个代码中,可以很清楚的看到,系统调用号name_nr是通过eax寄存器,而arg1和arg2是分别通过ebx和ecx寄存器,并且从上面的中断介绍中,可以清楚的认识到进入中断服务程序(这里是系统调用程序)的时候,栈顶部的数据恰好是ebx, ecx的值。因此,在系统调用函数中,就通过这种栈的方式传递参数。
但是,在Linux-2.6.20中却不一样,下面这段代码是XtratuM在针对Linux-2.6.20平台进行的的__syscall2定义。其中系统调用号被放在了ebx寄存器中,而不是早期的eax寄存器中。另外,参数依次放在eax和edx寄存器中。所以,在不同的Linux内核中,系统调用采用的参数传递方式是有差别的,这是在XtratuM系统移植过程中要考虑的内容。
#define __syscall2(arg1, arg2, name_nr, __res) /
__asm__ __volatile__ ("int $0x82/n/t" /
: "=a" (__res) /
: "b" (name_nr),"a" ((long)(arg1)), /
"d" ((long)(arg2)))

你可能感兴趣的:(Linux,RTOS)