参考:
实际的硬件。相比较于vdev来说。
包括guest OS及运行于上的应用。
一个qvm process instance 负责一个guest
guest运行于qvm之上。
大多数场合,host指的是hypervisor或其他运行于hypervisor上的东西。
QHS - “QNX Hypervisor for Safety”.
QNX Hypervisor - QNX Hypervisor for Safety 2.0 Safety.
qvm (or qvm process)是hypervisor的一个process。
由hypervisor启动qvm process instances; 每个qvm process instance 代表一个虚拟机(VM),guest运行于上。
A qvm process就会运行于hypervisor host domain允许的level上,而且这个level比guest host的level低。
hypervisor上虚拟出来的任何device. 例如:中断控制(虚拟出来的),或以太网控制器(para-virtualized)。
就是一个qvm process instance,guest运行于其上,VM是guest的host。
.build
QNX hypervisor host domain或QNX guest的buildfile。
.img
bootable image文件。可能是一个hypervisor host domain, 一个guest, 或 一个host domain及一个或多个guests。
.qvmconf
VM配置文件; 由VM的qvm process instance解析。
下图是QNX hypervisor架构和配置(以访问virtual、physical的device):
Virtual device 对于这类设备,Guest访问(被分配的)虚拟地址设备(virtual 或 para-virtual)。 qvm process instance请求适当的特权级别更改,并将执行请求传递给所请求的设备。
Pass-through device 对于这类设备,Guest可以直接访问实际的物理设备。 qvm process instance什么都不做。
guest interrupt入口(entry)需要在VM configuration(.qvmconf文件)中声明,例如下所示:
例1:vdev pl011 loc 0x1c090000 intr gic:37
例2:pass loc 0xe6055400,0x050,rwn intr gic:42 # GPIO6
其中,entry格式描述如下:
例1(x86平台): vdev ioapic loc 0xf8000000 intr apic name myioapic
ARM平台,自动分配,也可以指明中断号,标上“gic”。
例2(ARM平台): vdev pl011 loc 0x1c090000 intr gic:37
.qvmconf文件中,中断号需要是惟一的(也可以不指定,系统会自动分配):intr gic:xxx (xxx是唯一的,不能重复)
打个比方:
就像VirtualBox里面建立虚拟机一样。
Guest OS是运行于硬件上的软件,这里,VM就相当于该“硬件”。
虽然VM仍然是运行于hypervisor host上的一个qvm process instance,是软件。
qvm process instance做以下动作:
创建 (assembles) 并配置VM:
分配RAM (r/w)及ROM (r only)给guests
为每个虚拟CPU开启一个thread
启动passthrough设备
定义并配置虚拟设备vdevs
详细描述参考这里。
过多的中断会显著降低guest的性能。
在使用hypervisor的系统中,虽然guest可以配置中断控制器硬件,但是,还是由hypervisor来管理硬件以完成guest的需求。所以说,当硬件device产生中断给guest时,hypervisor总是要介入的,这意味着需要至少有一次打断guest的执行,来允许hypervisor查看中断并决定怎么处理这个中断。
即使设备中断被配置成pass-through中断,hypervisor还是会管理中断控制器硬件。hypervisor必须屏蔽中断,把它传递给guest(通过更新vCPU线程thread),最后,再打开中断(物理EOI)。
hypervisor介入中断传递给guest的过程(即使是pass-through中断),好处:
*(从系统角度看)可以防止发生中断风暴(interrupt storm)
在虚拟系统中,如果硬件不提供特殊支持的话,hypervisor接管所有的事情。hypervisor捕获中断,并更新相应的vCPU thread结构体(中断相关信息)。这是纯软件的方法。然后,hypervisor会请求至少一个guest退出并引入最大的开销(?)。
许多ARM及x86平台提供了虚拟化支持,以减少中断的传递过程。
如何利用硬件的这些辅助特性呢?注意以下几点:
ARM
针对ARM平台,hypervisor提供了2种技术用于减少guest的退出然后去处理中断。
如果硬件允许,hypervisor可以设置guest的IRQ请求bit,即便guest disabled中断。设置该bit不会导致guest 退出。
许多ARM平台(with GICv2)及很多最近较新的GIC硬件会提供硬件辅助功能,用于中断的处理。
当发生中断时,这些GIC 硬件辅助不会消除guest exit,但是,在guest中断(interrupt delivery)传输(pass-through,vdev)上,会帮着guest操作虚拟GIC 状态。
x86(略)
LAPIC 硬件辅助(略)
(ARM GIC)Virtual Interrupt Handling
参考:
1 Architectural Options for LPDDR4 Implementation in YourNext Chip Design
基于宋宝华《Linux设备驱动开发详解-基于最新的Linux4.0内核》。
GDB是GNU开源组织发布的一个强大的UNIX下的程序调试工具,主要帮着工程师完成下面4个方面的功能(其图形化工具是DDDhttp://www.gnu.org/software/ddd/):
准备:
GDB命令 | 缩写 | 描述 | 备注 |
---|---|---|---|
list <linenum> | l | 显示源程序 | <linenum> - 显示第linenum行周围的源程序 <function> - 显示function函数周围的源程序 |
run | r | 运行程序 | |
break | b | 设置断点 | <linenum> - 断在指定行号 <function> - 断在指定函数 |
next | n | 单步运行 | 相当于step over |
step | s | 单步运行 | 相当于step in |
continue | c | 运行到结束或下一个断点 | |
p | 查看运行数据 | print <expr> | |
watch | w | 观察运行数据 | watch <expr> |
examine | x | 查看内存地址中的值 | x/<n/f/u> |
set | 修改内存 | set *(unsigned char *)p=’h’ | |
jump | j | 跳转 | jump <address> |
help | h | GDB帮助 | |
quit | q | 退出GDB |
内核调试方法:
目前,应用最广泛的方法是printk()。
printk()定义了8个消息级别(级别越低(数值越大),消息越不重要)。
可以通过/proc/sys/kernel/printk调节printk()的输出等级。
可以通过dmesg命令查看内核打印缓冲区,也可以使用cat /proc/kmsg命令来显示内核信息。
Oops:Linux内核发生不正确的行为,并产生一份错误日志。(oops其实是表示惊讶或后悔,翻译过来可以译成哎哟,带有自我吃惊的意思,有时候也有抱歉的含义,但是这个词语不能代替真正的抱歉)
在性能优化中的常用手段:
基于宋宝华《Linux设备驱动开发详解-基于最新的Linux4.0内核》。
为了让Linux在一个全新的SoC上运行,需要提供大量的底层支撑:
当前Linux多采用无节拍(根据系统的运行情况,以事件驱动的方式动态决定下一个节拍在何时产生)方案,并支持高精度定时器,内核的配置一般会使能NO_HZ和HIGH_RES_TIMERS。
实现:clock_event_device、clocksource。
...
static struct irqaction xxx_timer_irq = {
.name = "xxx_tick",
.flags = IRQF_TIMER,
.irq = 0,
.handler = xxx_timer_interrupt,
.dev_id = &xxx_clockevent,
}
对于多核处理器来说,一般的做法是给每个核分配一个独立的定时器,各个核根据自身的运行情况动态的设置自己时钟中断发生的时刻。
处理器间通讯,通过IPI(Internal Processor Interrupt)广播到其他核。在R-Car SoC,是通过MFIS(Multifunctional Interface)实现。
芯片供应商需要提供以下API的底层支持:
request_irq()
enable_irq() /* 与具体中断控制器有关 */
disable_irq() /* 与具体中断控制器有关 */
local_irq_enable() /* 与具体中断控制器无关 */
local_irq_disable() /* 与具体中断控制器无关 */
指令:
CPSID/CPSIE—— >= Arm v6
MRS/MSR—— < Arm v6
在内核中,通过irq_chip结构体来描述中断控制器。
struct irq_chip{
...
void (*irq_ack)(struct irq_data *data); /* 清中断 */
void (*irq_mask)(struct irq_data *data); /* mask */
...
void (*irq_unmask)(struct irq_data *data); /* unmask */
...
int (*irq_set_type)(struct irq_data *data, unsigned int flow_type); /* 设置触发方式 */
}
对于有多个中断控制器的,其之间可能是级联的。
drivers/pinctrl/sirf/pinctrl-sirf.c中,显示了如何实现级联:
static int sirfsoc_gpio_probe(struct device_node *np)
{
...
gpiochip_set_chained_irqchip(&sgpio->chip.gc,
&sirfsoc_irq_chip,
bank->parent_irq, /* 上一级中断号 */
sirfsoc_gpio_handle_irq); /* 上一级中断服务程序 */
...
}
...
static void sirfsoc_gpio_handle_irq(unsigned int irq, struct irq_desc *desc)
{
...
if((status & 0x01) && (ctrl & SIRFSOC_GPIO_CTL_INTR_EN_MASK))
{
generic_handle_irq(irq_find_mapping(gc->irqdomain,
idx+bank->id * SIRFSOC_GPIO_BANK_SIZE));
}
...
}
以上,假设,中断服务程序为deva_isr()。
如果GPIO0_5中断发生的时候,内核的调用顺序是:
sirfsoc_gpio_handle_irq()
->
generic_handle_irq()
->
deva_isr().
每个CPU都有一个自身的ID。
一般在上电时,ID不是0的CPU将自身置于WFI或WFE状态,并等待CPU0给其发CPU核间中断或事件(一般通过SEV指令)以唤醒它。
#echo 0 > /sys/devices/system/cpu/cpu1/online #卸载CPU1,并将CPU1上的任务全部迁移到其他CPU中
#echo 1 > /sys/devices/system/cpu/cpu1/online #再次启动CPU1。之后,CPU1会主动参与系统中各个CPU之间要运行任务的负载均衡工作
CPU0唤醒其他CPU的动作在内核中被封装为一个smp_operations的结构体。
struct smp_operations{
...
void (*smp_init_cpus)(void);
void (*smp_prepare_cpus)(void);
void (*smp_secondary_init)(void);
void (*smp_boot_secondary)(void);
...
}
在dirvers/gpio下实现了通用的基于gpiolib的GPIO驱动,其中定义了一个通用的用于描述底层GPIO控制器的gpio_chip结构体。
struct gpio_chip{
...
int (*request)(..);
int (*free)(..);
int (*direction_input)(..);
int (*direction_output)(..);
void (*set)(...);
...
}
clk结构体:
struct clk_init_data{
const char *name;
const struct clk_ops *ops;
const char **parent_name;
u8 num_parenets;
unsigned long flags;
}
struct clk_ops{
int (*prepare)(...);
...
int (*enable)(...);
int (*disable)(...);
...
int (*set_rate)(...);
...
}
struct clk_hw{
struc clk *clk;
const struct clk_init_data *init;
}
struct clk{
const char *name;
const struc clk_ops *ops;
struct clk_hw *hw;
...
}
配置一个或一组引脚的功能和特性
dmaengine是一套通用的DMA驱动框架,为具体使用DMA通道的设备驱动提供一套通用的API,而且也定义了用具体的DMA 控制器实现这一套API的方法。
使用DMA引擎的过程可分为几步:
...
desc = dmaengine_prep_dma_cyclic(...); /* 初始化描述符 */
...
prtd->cookie = dmaengine_submit(desc); /* 将该描述符插入dmaengine驱动的传输队列 */
...
dma_async_issue_pending(prtd_dma_chan); /* DMA传输开始 */
基于宋宝华《Linux设备驱动开发详解-基于最新的Linux4.0内核》。
Linux在中断处理中引入了顶半部与底半部分分离的机制
底半部(Bottom Half)——处理不紧急的耗时操作。
底半部的机制主要有:tasklet、工作队列、软中断、线程化irq
如果中断要处理的工作很少,完全可以在顶半部全部完成。
3种类型的中断:
Linux内核周期性任务实现:
Linux内核延时:
相关中断函数解释:
request_irq() /* 申请中断 */
dev_request_irq() /* 申请中断,申请的是内核managed的资源 */
free_irq() /* 释放中断 */
disable_irq_nosync() /* 立即返回 */
disable_irq() /* 等待目前的处理完成再返回 */
一些目录: